V4L/DVB (10048): gspca - stv06xx: New subdriver.

Signed-off-by: Erik Andren <erik.andren@gmail.com>
Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
diff --git a/Documentation/video4linux/gspca.txt b/Documentation/video4linux/gspca.txt
index 5daf2c8..f54281d 100644
--- a/Documentation/video4linux/gspca.txt
+++ b/Documentation/video4linux/gspca.txt
@@ -50,6 +50,9 @@
 spca508		0461:0815	Micro Innovation IC200
 sunplus		0461:0821	Fujifilm MV-1
 zc3xx		0461:0a00	MicroInnovation WebCam320
+stv06xx		046d:0840	QuickCam Express
+stv06xx		046d:0850	LEGO cam / QuickCam Web
+stv06xx		046d:0870	Dexxa WebCam USB
 spca500		046d:0890	Logitech QuickCam traveler
 vc032x		046d:0892	Logitech Orbicam
 vc032x		046d:0896	Logitech Orbicam
diff --git a/drivers/media/video/gspca/Kconfig b/drivers/media/video/gspca/Kconfig
index 770fb69..ee6a691 100644
--- a/drivers/media/video/gspca/Kconfig
+++ b/drivers/media/video/gspca/Kconfig
@@ -18,6 +18,7 @@
 if USB_GSPCA && VIDEO_V4L2
 
 source "drivers/media/video/gspca/m5602/Kconfig"
+source "drivers/media/video/gspca/stv06xx/Kconfig"
 
 config USB_GSPCA_CONEX
 	tristate "Conexant Camera Driver"
diff --git a/drivers/media/video/gspca/Makefile b/drivers/media/video/gspca/Makefile
index 6c8046e..bd8d9ee 100644
--- a/drivers/media/video/gspca/Makefile
+++ b/drivers/media/video/gspca/Makefile
@@ -47,4 +47,4 @@
 gspca_zc3xx-objs		:= zc3xx.o
 
 obj-$(CONFIG_USB_M5602)		+= m5602/
-
+obj-$(CONFIG_USB_STV06XX) 	+= stv06xx/
diff --git a/drivers/media/video/gspca/stv06xx/Kconfig b/drivers/media/video/gspca/stv06xx/Kconfig
new file mode 100644
index 0000000..634ad38d
--- /dev/null
+++ b/drivers/media/video/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/video/gspca/stv06xx/Makefile b/drivers/media/video/gspca/stv06xx/Makefile
new file mode 100644
index 0000000..8f002b6
--- /dev/null
+++ b/drivers/media/video/gspca/stv06xx/Makefile
@@ -0,0 +1,6 @@
+obj-$(CONFIG_USB_STV06XX) += gspca_stv06xx.o
+
+gspca_stv06xx-objs := stv06xx.o \
+		      stv06xx_vv6410.o \
+		      stv06xx_hdcs.o \
+		      stv06xx_pb0100.o
diff --git a/drivers/media/video/gspca/stv06xx/stv06xx.c b/drivers/media/video/gspca/stv06xx/stv06xx.c
new file mode 100644
index 0000000..29e4371
--- /dev/null
+++ b/drivers/media/video/gspca/stv06xx/stv06xx.c
@@ -0,0 +1,522 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ * 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
+ */
+
+#include "stv06xx_sensor.h"
+
+MODULE_AUTHOR("Erik Andrén");
+MODULE_DESCRIPTION("STV06XX USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+int dump_bridge;
+int dump_sensor;
+
+int stv06xx_write_bridge(struct sd *sd, u16 address, u16 i2c_data)
+{
+	int err;
+	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);
+
+
+	PDEBUG(D_CONF, "Written 0x%x to address 0x%x, status: %d",
+	       i2c_data, address, err);
+
+	return (err < 0) ? err : 0;
+}
+
+int stv06xx_read_bridge(struct sd *sd, u16 address, u8 *i2c_data)
+{
+	int err;
+	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];
+
+	PDEBUG(D_CONF, "Read 0x%x from address 0x%x, status %d",
+	       *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 (IS_850(sd)) {
+		struct usb_device *udev = sd->gspca_dev.dev;
+		__u8 *buf = sd->gspca_dev.usb_buf;
+
+		/* Quickam Web needs an extra packet */
+		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 usb_device *udev = sd->gspca_dev.dev;
+	__u8 *buf = sd->gspca_dev.usb_buf;
+
+	PDEBUG(D_USBO, "I2C: Command buffer contains %d entries", 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];
+			PDEBUG(D_USBO, "I2C: Writing 0x%02x to reg 0x%02x",
+			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 usb_device *udev = sd->gspca_dev.dev;
+	__u8 *buf = sd->gspca_dev.usb_buf;
+
+	PDEBUG(D_USBO, "I2C: Command buffer contains %d entries", 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;
+			PDEBUG(D_USBO, "I2C: Writing 0x%04x to reg 0x%02x",
+				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 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) {
+		PDEBUG(D_ERR, "I2C Read: error writing address: %d", 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];
+
+	PDEBUG(D_USBO, "I2C: Read 0x%x from address 0x%x, status: %d",
+	       *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;
+
+	info("Dumping all stv06xx bridge registers");
+	for (i = 0x1400; i < 0x160f; i++) {
+		stv06xx_read_bridge(sd, i, &data);
+
+		info("Read 0x%x from address 0x%x", data, i);
+	}
+
+	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)
+			info("Register 0x%x is read/write", i);
+		else if (data != buf)
+			info("Register 0x%x is read/write,"
+			     "but only partially", i);
+		else
+			info("Register 0x%x is read-only", 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;
+
+	PDEBUG(D_PROBE, "Initializing camera");
+
+	/* 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);
+
+	return (err < 0) ? err : 0;
+}
+
+/* Start the camera */
+static int stv06xx_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int 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)
+		PDEBUG(D_STREAM, "Starting stream failed");
+	else
+		PDEBUG(D_STREAM, "Started streaming");
+
+	return (err < 0) ? err : 0;
+}
+
+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);
+	if (err < 0)
+		goto out;
+
+out:
+	if (err < 0)
+		PDEBUG(D_STREAM, "Failed to stop stream");
+	else
+		PDEBUG(D_STREAM, "Stopped streaming");
+}
+
+/*
+ * 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,
+			struct gspca_frame *frame,	/* target */
+			__u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	PDEBUG(D_PACK, "Packet of length %d arrived", len);
+
+	/* A packet may contain several frames
+	   loop until the whole packet is reached */
+	while (len) {
+		int id, chunk_len;
+
+		if (len < 4) {
+			PDEBUG(D_PACK, "Packet is smaller than 4 bytes");
+			return;
+		}
+
+		/* Capture the id */
+		id = (data[0] << 8) | data[1];
+
+		/* Capture the chunk length */
+		chunk_len = (data[2] << 8) | data[3];
+		PDEBUG(D_PACK, "Chunk id: %x, length: %d", id, chunk_len);
+
+		data += 4;
+		len -= 4;
+
+		if (len < chunk_len) {
+			PDEBUG(D_ERR, "URB packet length is smaller"
+				" than the specified chunk length");
+			return;
+		}
+
+		switch (id) {
+		case 0x0200:
+		case 0x4200:
+			PDEBUG(D_PACK, "Frame data packet detected");
+
+			gspca_frame_add(gspca_dev, INTER_PACKET, frame,
+					data, chunk_len);
+			break;
+
+		case 0x8001:
+		case 0x8005:
+		case 0xc001:
+		case 0xc005:
+			PDEBUG(D_PACK, "Starting new frame");
+
+			/* Create a new frame, chunk length should be zero */
+			gspca_frame_add(gspca_dev, FIRST_PACKET,
+					frame, data, 0);
+
+			if (chunk_len)
+				PDEBUG(D_ERR, "Chunk length is "
+					      "non-zero on a SOF");
+			break;
+
+		case 0x8002:
+		case 0x8006:
+		case 0xc002:
+			PDEBUG(D_PACK, "End of frame detected");
+
+			/* Complete the last frame (if any) */
+			gspca_frame_add(gspca_dev, LAST_PACKET, frame, data, 0);
+
+			if (chunk_len)
+				PDEBUG(D_ERR, "Chunk length is "
+					      "non-zero on a EOF");
+			break;
+
+		case 0x0005:
+			PDEBUG(D_PACK, "Chunk 0x005 detected");
+			/* Unknown chunk with 11 bytes of data,
+			   occurs just before end of each frame
+			   in compressed mode */
+			break;
+
+		case 0x0100:
+			PDEBUG(D_PACK, "Chunk 0x0100 detected");
+			/* Unknown chunk with 2 bytes of data,
+			   occurs 2-3 times per USB interrupt */
+			break;
+		default:
+			PDEBUG(D_PACK, "Unknown chunk %d detected", id);
+			/* Unknown chunk */
+		}
+		data    += chunk_len;
+		len     -= chunk_len;
+	}
+}
+
+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,
+	.start = stv06xx_start,
+	.stopN = stv06xx_stopN,
+	.pkt_scan = stv06xx_pkt_scan
+};
+
+/* 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;
+	struct cam *cam;
+
+	PDEBUG(D_PROBE, "Configuring camera");
+
+	cam = &gspca_dev->cam;
+	cam->epaddr = STV_ISOC_ENDPOINT_ADDR;
+	sd->desc = sd_desc;
+	gspca_dev->sd_desc = &sd->desc;
+
+	if (dump_bridge)
+		stv06xx_dump_bridge(sd);
+
+	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 __devinitdata struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x046d, 0x0840)}, /* QuickCam Express */
+	{USB_DEVICE(0x046d, 0x0850)}, /* LEGO cam / QuickCam Web */
+	{USB_DEVICE(0x046d, 0x0870)}, /* Dexxa WebCam USB */
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	PDEBUG(D_PROBE, "Probing for a stv06xx device");
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+			       THIS_MODULE);
+}
+
+void sd_disconnect(struct usb_interface *intf)
+{
+	struct gspca_dev *gspca_dev = usb_get_intfdata(intf);
+	struct sd *sd = (struct sd *) gspca_dev;
+	PDEBUG(D_PROBE, "Disconnecting the stv06xx device");
+
+	if (sd->sensor->disconnect)
+		sd->sensor->disconnect(sd);
+	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,
+#endif
+};
+
+/* -- module insert / remove -- */
+static int __init sd_mod_init(void)
+{
+	if (usb_register(&sd_driver) < 0)
+		return -1;
+	PDEBUG(D_PROBE, "registered");
+	return 0;
+}
+static void __exit sd_mod_exit(void)
+{
+	usb_deregister(&sd_driver);
+	PDEBUG(D_PROBE, "deregistered");
+}
+
+module_init(sd_mod_init);
+module_exit(sd_mod_exit);
+
+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/video/gspca/stv06xx/stv06xx.h b/drivers/media/video/gspca/stv06xx/stv06xx.h
new file mode 100644
index 0000000..1207e7d
--- /dev/null
+++ b/drivers/media/video/gspca/stv06xx/stv06xx.h
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ * 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 "gspca.h"
+
+#define MODULE_NAME "STV06xx"
+
+#define STV_ISOC_ENDPOINT_ADDR		0x81
+
+#ifndef V4L2_PIX_FMT_SGRBG8
+#define V4L2_PIX_FMT_SGRBG8 v4l2_fourcc('G', 'R', 'B', 'G')
+#endif
+
+#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
+
+/* 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;
+
+	/* A pointer to the sd_desc struct */
+	struct sd_desc desc;
+
+	/* Sensor private data */
+	void *sensor_priv;
+};
+
+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/video/gspca/stv06xx/stv06xx_hdcs.c b/drivers/media/video/gspca/stv06xx/stv06xx_hdcs.c
new file mode 100644
index 0000000..1cfe585
--- /dev/null
+++ b/drivers/media/video/gspca/stv06xx/stv06xx_hdcs.c
@@ -0,0 +1,533 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ * 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
+ */
+
+#include "stv06xx_hdcs.h"
+
+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++, reg++) {
+		regs[2*i] = reg;
+		regs[2*i+1] = vals[i];
+	}
+
+	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);
+	if (ret < 0)
+		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_get_exposure(struct gspca_dev *gspca_dev, __s32 *val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct hdcs *hdcs = sd->sensor_priv;
+
+	/* Column time period */
+	int ct;
+	/* Column processing period */
+	int cp;
+	/* Row processing period */
+	int rp;
+	int cycles;
+	int err;
+	int rowexp;
+	u16 data[2];
+
+	err = stv06xx_read_sensor(sd, HDCS_ROWEXPL, &data[0]);
+	if (err < 0)
+		return err;
+
+	err = stv06xx_read_sensor(sd, HDCS_ROWEXPH, &data[1]);
+	if (err < 0)
+		return err;
+
+	rowexp = (data[1] << 8) | data[0];
+
+	ct = hdcs->exp.cto + hdcs->psmp + (HDCS_ADC_START_SIG_DUR + 2);
+	cp = hdcs->exp.cto + (hdcs->w * ct / 2);
+	rp = hdcs->exp.rs + cp;
+
+	cycles = rp * rowexp;
+	*val = cycles / HDCS_CLK_FREQ_MHZ;
+	PDEBUG(D_V4L2, "Read exposure %d", *val);
+	return 0;
+}
+
+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[4];
+
+	cycles = val * HDCS_CLK_FREQ_MHZ;
+
+	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] = rowexp & 0xff;
+		exp[1] = rowexp >> 8;
+		exp[2] = (srowexp >> 2) & 0xff;
+		/* this clears exposure error flag */
+		exp[3] = 0x1;
+		err = hdcs_reg_write_seq(sd, HDCS_ROWEXPL, exp, 4);
+	} else {
+		exp[0] = rowexp & 0xff;
+		exp[1] = rowexp >> 8;
+		exp[2] = srowexp & 0xff;
+		exp[3] = srowexp >> 8;
+		err = hdcs_reg_write_seq(sd, HDCS_ROWEXPL, exp, 4);
+		if (err < 0)
+			return err;
+
+		/* clear exposure error flag */
+		err = stv06xx_write_sensor(sd,
+		     HDCS_STATUS, BIT(4));
+	}
+	PDEBUG(D_V4L2, "Writing exposure %d, rowexp %d, srowexp %d",
+	       val, rowexp, srowexp);
+	return err;
+}
+
+static int hdcs_set_gains(struct sd *sd, u8 r, u8 g, u8 b)
+{
+	u8 gains[4];
+
+	/* the voltage gain Av = (1 + 19 * val / 127) * (1 + bit7) */
+	if (r > 127)
+		r = 0x80 | (r / 2);
+	if (g > 127)
+		g = 0x80 | (g / 2);
+	if (b > 127)
+		b = 0x80 | (b / 2);
+
+	gains[0] = g;
+	gains[1] = r;
+	gains[2] = b;
+	gains[3] = g;
+
+	return hdcs_reg_write_seq(sd, HDCS_ERECPGA, gains, 4);
+}
+
+static int hdcs_get_gain(struct gspca_dev *gspca_dev, __s32 *val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int err;
+	u16 data;
+
+	err = stv06xx_read_sensor(sd, HDCS_ERECPGA, &data);
+
+	/* Bit 7 doubles the gain */
+	if (data & 0x80)
+		*val = (data & 0x7f) * 2;
+	else
+		*val = data;
+
+	PDEBUG(D_V4L2, "Read gain %d", *val);
+	return err;
+}
+
+static int hdcs_set_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	PDEBUG(D_V4L2, "Writing gain %d", val);
+	return hdcs_set_gains((struct sd *) gspca_dev,
+			       val & 0xff, val & 0xff, 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_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;
+
+	info("HDCS-1000/1100 sensor detected");
+
+	sd->gspca_dev.cam.cam_mode = stv06xx_sensor_hdcs1x00.modes;
+	sd->gspca_dev.cam.nmodes = stv06xx_sensor_hdcs1x00.nmodes;
+	sd->desc.ctrls = stv06xx_sensor_hdcs1x00.ctrls;
+	sd->desc.nctrls = stv06xx_sensor_hdcs1x00.nctrls;
+
+	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 0x46D:0x840 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 0x46D:0x870 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 = IS_870(sd) ? 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;
+
+	info("HDCS-1020 sensor detected");
+
+	sd->gspca_dev.cam.cam_mode = stv06xx_sensor_hdcs1020.modes;
+	sd->gspca_dev.cam.nmodes = stv06xx_sensor_hdcs1020.nmodes;
+	sd->desc.ctrls = stv06xx_sensor_hdcs1020.ctrls;
+	sd->desc.nctrls = stv06xx_sensor_hdcs1020.nctrls;
+
+	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)
+{
+	PDEBUG(D_STREAM, "Starting stream");
+
+	return hdcs_set_state(sd, HDCS_STATE_RUN);
+}
+
+static int hdcs_stop(struct sd *sd)
+{
+	PDEBUG(D_STREAM, "Halting stream");
+
+	return hdcs_set_state(sd, HDCS_STATE_SLEEP);
+}
+
+static void hdcs_disconnect(struct sd *sd)
+{
+	PDEBUG(D_PROBE, "Disconnecting the sensor");
+	kfree(sd->sensor_priv);
+}
+
+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 (IS_870(sd))
+		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 continous 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 IS_870, 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;
+
+	err = hdcs_set_gains(sd, HDCS_DEFAULT_GAIN, HDCS_DEFAULT_GAIN,
+			     HDCS_DEFAULT_GAIN);
+	if (err < 0)
+		return err;
+
+	err = hdcs_set_exposure(&sd->gspca_dev, HDCS_DEFAULT_EXPOSURE);
+	if (err < 0)
+		return err;
+
+	err = hdcs_set_size(sd, hdcs->array.width, hdcs->array.height);
+	return err;
+}
+
+static int hdcs_dump(struct sd *sd)
+{
+	u16 reg, val;
+
+	info("Dumping sensor registers:");
+
+	for (reg = HDCS_IDENT; reg <= HDCS_ROWEXPH; reg++) {
+		stv06xx_read_sensor(sd, reg, &val);
+		info("reg 0x%02x = 0x%02x", reg, val);
+	}
+	return 0;
+}
diff --git a/drivers/media/video/gspca/stv06xx/stv06xx_hdcs.h b/drivers/media/video/gspca/stv06xx/stv06xx_hdcs.h
new file mode 100644
index 0000000..9c7279a
--- /dev/null
+++ b/drivers/media/video/gspca/stv06xx/stv06xx_hdcs.h
@@ -0,0 +1,263 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ * 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	5000
+#define HDCS_DEFAULT_GAIN	128
+
+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_stop(struct sd *sd);
+static int hdcs_dump(struct sd *sd);
+static void hdcs_disconnect(struct sd *sd);
+
+static int hdcs_get_exposure(struct gspca_dev *gspca_dev, __s32 *val);
+static int hdcs_set_exposure(struct gspca_dev *gspca_dev, __s32 val);
+static int hdcs_set_gain(struct gspca_dev *gspca_dev, __s32 val);
+static int hdcs_get_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,
+
+	.init = hdcs_init,
+	.probe = hdcs_probe_1x00,
+	.start = hdcs_start,
+	.stop = hdcs_stop,
+	.disconnect = hdcs_disconnect,
+	.dump = hdcs_dump,
+
+	.nctrls = 2,
+	.ctrls = {
+	{
+		{
+			.id		= V4L2_CID_EXPOSURE,
+			.type		= V4L2_CTRL_TYPE_INTEGER,
+			.name		= "exposure",
+			.minimum	= 0x00,
+			.maximum	= 0xffff,
+			.step		= 0x1,
+			.default_value 	= HDCS_DEFAULT_EXPOSURE,
+			.flags         	= V4L2_CTRL_FLAG_SLIDER
+		},
+		.set = hdcs_set_exposure,
+		.get = hdcs_get_exposure
+	},
+	{
+		{
+			.id		= V4L2_CID_GAIN,
+			.type		= V4L2_CTRL_TYPE_INTEGER,
+			.name		= "gain",
+			.minimum	= 0x00,
+			.maximum	= 0xff,
+			.step		= 0x1,
+			.default_value 	= HDCS_DEFAULT_GAIN,
+			.flags         	= V4L2_CTRL_FLAG_SLIDER
+		},
+		.set = hdcs_set_gain,
+		.get = hdcs_get_gain
+	}
+	},
+
+	.nmodes = 1,
+	.modes = {
+	{
+		HDCS_1X00_DEF_WIDTH,
+		HDCS_1X00_DEF_HEIGHT,
+		V4L2_PIX_FMT_SBGGR8,
+		V4L2_FIELD_NONE,
+		.sizeimage =
+			HDCS_1X00_DEF_WIDTH * HDCS_1X00_DEF_HEIGHT,
+		.bytesperline = HDCS_1X00_DEF_WIDTH,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1
+	}
+	}
+};
+
+const struct stv06xx_sensor stv06xx_sensor_hdcs1020 = {
+	.name = "HDCS-1020",
+	.i2c_flush = 0,
+	.i2c_addr = (0x55 << 1),
+	.i2c_len = 1,
+
+	.nctrls = 0,
+	.ctrls = {},
+
+	.init = hdcs_init,
+	.probe = hdcs_probe_1020,
+	.start = hdcs_start,
+	.stop = hdcs_stop,
+	.dump = hdcs_dump,
+
+	.nmodes = 1,
+	.modes = {
+	{
+		HDCS_1020_DEF_WIDTH,
+		HDCS_1020_DEF_HEIGHT,
+		V4L2_PIX_FMT_SBGGR8,
+		V4L2_FIELD_NONE,
+		.sizeimage =
+			HDCS_1020_DEF_WIDTH * HDCS_1020_DEF_HEIGHT,
+		.bytesperline = HDCS_1020_DEF_WIDTH,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1
+	}
+	}
+};
+
+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_ISO_SIZE_L, 847},
+	{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/video/gspca/stv06xx/stv06xx_pb0100.c b/drivers/media/video/gspca/stv06xx/stv06xx_pb0100.c
new file mode 100644
index 0000000..d0a0f85
--- /dev/null
+++ b/drivers/media/video/gspca/stv06xx/stv06xx_pb0100.c
@@ -0,0 +1,430 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ * 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
+ */
+
+#include "stv06xx_pb0100.h"
+
+static int pb0100_probe(struct sd *sd)
+{
+	u16 sensor;
+	int i, err;
+	s32 *sensor_settings;
+
+	err = stv06xx_read_sensor(sd, PB_IDENT, &sensor);
+
+	if (err < 0)
+		return -ENODEV;
+
+	if ((sensor >> 8) == 0x64) {
+		sensor_settings = kmalloc(
+				stv06xx_sensor_pb0100.nctrls * sizeof(s32),
+				GFP_KERNEL);
+		if (!sensor_settings)
+			return -ENOMEM;
+
+		info("Photobit pb0100 sensor detected");
+
+		sd->gspca_dev.cam.cam_mode = stv06xx_sensor_pb0100.modes;
+		sd->gspca_dev.cam.nmodes = stv06xx_sensor_pb0100.nmodes;
+		sd->desc.ctrls = stv06xx_sensor_pb0100.ctrls;
+		sd->desc.nctrls = stv06xx_sensor_pb0100.nctrls;
+		for (i = 0; i < stv06xx_sensor_pb0100.nctrls; i++)
+			sensor_settings[i] = stv06xx_sensor_pb0100.
+					     ctrls[i].qctrl.default_value;
+		sd->sensor_priv = sensor_settings;
+
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
+static int pb0100_start(struct sd *sd)
+{
+	int err;
+	struct cam *cam = &sd->gspca_dev.cam;
+	s32 *sensor_settings = sd->sensor_priv;
+	u32 mode = cam->cam_mode[sd->gspca_dev.curr_mode].priv;
+
+	/* 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);
+	}
+
+	/* set_gain also sets red and blue balance */
+	pb0100_set_gain(&sd->gspca_dev, sensor_settings[GAIN_IDX]);
+	pb0100_set_exposure(&sd->gspca_dev, sensor_settings[EXPOSURE_IDX]);
+	pb0100_set_autogain_target(&sd->gspca_dev,
+				   sensor_settings[AUTOGAIN_TARGET_IDX]);
+	pb0100_set_autogain(&sd->gspca_dev, sensor_settings[AUTOGAIN_IDX]);
+
+	err = stv06xx_write_sensor(sd, PB_CONTROL, BIT(5)|BIT(3)|BIT(1));
+	PDEBUG(D_STREAM, "Started stream, status: %d", err);
+
+	return (err < 0) ? err : 0;
+}
+
+static int pb0100_stop(struct sd *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));
+
+	PDEBUG(D_STREAM, "Halting stream");
+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);
+
+	/* ISO-Size (0x27b: 635... why? - HDCS uses 847) */
+	stv06xx_write_bridge(sd, STV_ISO_SIZE_L, 847);
+
+	/* 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_get_gain(struct gspca_dev *gspca_dev, __s32 *val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	*val = sensor_settings[GAIN_IDX];
+
+	return 0;
+}
+
+static int pb0100_set_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	if (sensor_settings[AUTOGAIN_IDX])
+		return -EBUSY;
+
+	sensor_settings[GAIN_IDX] = val;
+	err = stv06xx_write_sensor(sd, PB_G1GAIN, val);
+	if (!err)
+		err = stv06xx_write_sensor(sd, PB_G2GAIN, val);
+	PDEBUG(D_V4L2, "Set green gain to %d, status: %d", val, err);
+
+	if (!err)
+		err = pb0100_set_red_balance(gspca_dev,
+					     sensor_settings[RED_BALANCE_IDX]);
+	if (!err)
+		err = pb0100_set_blue_balance(gspca_dev,
+					    sensor_settings[BLUE_BALANCE_IDX]);
+
+	return err;
+}
+
+static int pb0100_get_red_balance(struct gspca_dev *gspca_dev, __s32 *val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	*val = sensor_settings[RED_BALANCE_IDX];
+
+	return 0;
+}
+
+static int pb0100_set_red_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	if (sensor_settings[AUTOGAIN_IDX])
+		return -EBUSY;
+
+	sensor_settings[RED_BALANCE_IDX] = val;
+	val += sensor_settings[GAIN_IDX];
+	if (val < 0)
+		val = 0;
+	else if (val > 255)
+		val = 255;
+
+	err = stv06xx_write_sensor(sd, PB_RGAIN, val);
+	PDEBUG(D_V4L2, "Set red gain to %d, status: %d", val, err);
+
+	return err;
+}
+
+static int pb0100_get_blue_balance(struct gspca_dev *gspca_dev, __s32 *val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	*val = sensor_settings[BLUE_BALANCE_IDX];
+
+	return 0;
+}
+
+static int pb0100_set_blue_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	if (sensor_settings[AUTOGAIN_IDX])
+		return -EBUSY;
+
+	sensor_settings[BLUE_BALANCE_IDX] = val;
+	val += sensor_settings[GAIN_IDX];
+	if (val < 0)
+		val = 0;
+	else if (val > 255)
+		val = 255;
+
+	err = stv06xx_write_sensor(sd, PB_BGAIN, val);
+	PDEBUG(D_V4L2, "Set blue gain to %d, status: %d", val, err);
+
+	return err;
+}
+
+static int pb0100_get_exposure(struct gspca_dev *gspca_dev, __s32 *val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	*val = sensor_settings[EXPOSURE_IDX];
+
+	return 0;
+}
+
+static int pb0100_set_exposure(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	if (sensor_settings[AUTOGAIN_IDX])
+		return -EBUSY;
+
+	sensor_settings[EXPOSURE_IDX] = val;
+	err = stv06xx_write_sensor(sd, PB_RINTTIME, val);
+	PDEBUG(D_V4L2, "Set exposure to %d, status: %d", val, err);
+
+	return err;
+}
+
+static int pb0100_get_autogain(struct gspca_dev *gspca_dev, __s32 *val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	*val = sensor_settings[AUTOGAIN_IDX];
+
+	return 0;
+}
+
+static int pb0100_set_autogain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	sensor_settings[AUTOGAIN_IDX] = val;
+	if (sensor_settings[AUTOGAIN_IDX]) {
+		if (sensor_settings[NATURAL_IDX])
+			val = BIT(6)|BIT(4)|BIT(0);
+		else
+			val = BIT(4)|BIT(0);
+	} else
+		val = 0;
+
+	err = stv06xx_write_sensor(sd, PB_EXPGAIN, val);
+	PDEBUG(D_V4L2, "Set autogain to %d (natural: %d), status: %d",
+	       sensor_settings[AUTOGAIN_IDX], sensor_settings[NATURAL_IDX],
+	       err);
+
+	return err;
+}
+
+static int pb0100_get_autogain_target(struct gspca_dev *gspca_dev, __s32 *val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	*val = sensor_settings[AUTOGAIN_TARGET_IDX];
+
+	return 0;
+}
+
+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;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	sensor_settings[AUTOGAIN_TARGET_IDX] = val;
+
+	/* Number of pixels counted by the sensor when subsampling the pixels.
+	 * Slightly larger than the real value to avoid oscillation */
+	totalpixels = gspca_dev->width * gspca_dev->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);
+
+	PDEBUG(D_V4L2, "Set autogain target to %d, status: %d", val, err);
+
+	return err;
+}
+
+static int pb0100_get_natural(struct gspca_dev *gspca_dev, __s32 *val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	*val = sensor_settings[NATURAL_IDX];
+
+	return 0;
+}
+
+static int pb0100_set_natural(struct gspca_dev *gspca_dev, __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 *sensor_settings = sd->sensor_priv;
+
+	sensor_settings[NATURAL_IDX] = val;
+
+	return pb0100_set_autogain(gspca_dev, sensor_settings[AUTOGAIN_IDX]);
+}
diff --git a/drivers/media/video/gspca/stv06xx/stv06xx_pb0100.h b/drivers/media/video/gspca/stv06xx/stv06xx_pb0100.h
new file mode 100644
index 0000000..5ea21a1
--- /dev/null
+++ b/drivers/media/video/gspca/stv06xx/stv06xx_pb0100.h
@@ -0,0 +1,275 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ * 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_stop(struct sd *sd);
+static int pb0100_dump(struct sd *sd);
+
+/* V4L2 controls supported by the driver */
+static int pb0100_get_gain(struct gspca_dev *gspca_dev, __s32 *val);
+static int pb0100_set_gain(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_get_red_balance(struct gspca_dev *gspca_dev, __s32 *val);
+static int pb0100_set_red_balance(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_get_blue_balance(struct gspca_dev *gspca_dev, __s32 *val);
+static int pb0100_set_blue_balance(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_get_exposure(struct gspca_dev *gspca_dev, __s32 *val);
+static int pb0100_set_exposure(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_get_autogain(struct gspca_dev *gspca_dev, __s32 *val);
+static int pb0100_set_autogain(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_get_autogain_target(struct gspca_dev *gspca_dev, __s32 *val);
+static int pb0100_set_autogain_target(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_get_natural(struct gspca_dev *gspca_dev, __s32 *val);
+static int pb0100_set_natural(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,
+
+	.nctrls = 7,
+	.ctrls = {
+#define GAIN_IDX 0
+	{
+		{
+			.id		= V4L2_CID_GAIN,
+			.type		= V4L2_CTRL_TYPE_INTEGER,
+			.name		= "Gain",
+			.minimum	= 0,
+			.maximum	= 255,
+			.step		= 1,
+			.default_value  = 128
+		},
+		.set = pb0100_set_gain,
+		.get = pb0100_get_gain
+	},
+#define RED_BALANCE_IDX 1
+	{
+		{
+			.id		= V4L2_CID_RED_BALANCE,
+			.type		= V4L2_CTRL_TYPE_INTEGER,
+			.name		= "Red Balance",
+			.minimum	= -255,
+			.maximum	= 255,
+			.step		= 1,
+			.default_value  = 0
+		},
+		.set = pb0100_set_red_balance,
+		.get = pb0100_get_red_balance
+	},
+#define BLUE_BALANCE_IDX 2
+	{
+		{
+			.id		= V4L2_CID_BLUE_BALANCE,
+			.type		= V4L2_CTRL_TYPE_INTEGER,
+			.name		= "Blue Balance",
+			.minimum	= -255,
+			.maximum	= 255,
+			.step		= 1,
+			.default_value  = 0
+		},
+		.set = pb0100_set_blue_balance,
+		.get = pb0100_get_blue_balance
+	},
+#define EXPOSURE_IDX 3
+	{
+		{
+			.id		= V4L2_CID_EXPOSURE,
+			.type		= V4L2_CTRL_TYPE_INTEGER,
+			.name		= "Exposure",
+			.minimum	= 0,
+			.maximum	= 511,
+			.step		= 1,
+			.default_value  = 12
+		},
+		.set = pb0100_set_exposure,
+		.get = pb0100_get_exposure
+	},
+#define AUTOGAIN_IDX 4
+	{
+		{
+			.id		= V4L2_CID_AUTOGAIN,
+			.type		= V4L2_CTRL_TYPE_BOOLEAN,
+			.name		= "Automatic Gain and Exposure",
+			.minimum	= 0,
+			.maximum	= 1,
+			.step		= 1,
+			.default_value  = 1
+		},
+		.set = pb0100_set_autogain,
+		.get = pb0100_get_autogain
+	},
+#define AUTOGAIN_TARGET_IDX 5
+	{
+		{
+			.id		= V4L2_CTRL_CLASS_USER + 0x1000,
+			.type		= V4L2_CTRL_TYPE_INTEGER,
+			.name		= "Automatic Gain Target",
+			.minimum	= 0,
+			.maximum	= 255,
+			.step		= 1,
+			.default_value  = 128
+		},
+		.set = pb0100_set_autogain_target,
+		.get = pb0100_get_autogain_target
+	},
+#define NATURAL_IDX 6
+	{
+		{
+			.id		= V4L2_CTRL_CLASS_USER + 0x1001,
+			.type		= V4L2_CTRL_TYPE_BOOLEAN,
+			.name		= "Natural Light Source",
+			.minimum	= 0,
+			.maximum	= 1,
+			.step		= 1,
+			.default_value  = 1
+		},
+		.set = pb0100_set_natural,
+		.get = pb0100_get_natural
+	},
+	},
+
+	.init = pb0100_init,
+	.probe = pb0100_probe,
+	.start = pb0100_start,
+	.stop = pb0100_stop,
+	.dump = pb0100_dump,
+
+	.nmodes = 2,
+	.modes = {
+/* 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
+	},
+	}
+};
+
+#endif
diff --git a/drivers/media/video/gspca/stv06xx/stv06xx_sensor.h b/drivers/media/video/gspca/stv06xx/stv06xx_sensor.h
new file mode 100644
index 0000000..c726dac
--- /dev/null
+++ b/drivers/media/video/gspca/stv06xx/stv06xx_sensor.h
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ * 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_850(sd)	((sd)->gspca_dev.dev->descriptor.idProduct == 0x850)
+#define IS_870(sd)	((sd)->gspca_dev.dev->descriptor.idProduct == 0x870)
+#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;
+
+#define STV06XX_MAX_CTRLS		(V4L2_CID_LASTP1 - V4L2_CID_BASE + 10)
+
+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;
+
+	/* Probes if the sensor is connected */
+	int (*probe)(struct sd *sd);
+
+	/* Performs a initialization sequence */
+	int (*init)(struct sd *sd);
+
+	/* Executed at device disconnect */
+	void (*disconnect)(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);
+
+	int nctrls;
+	struct ctrl ctrls[STV06XX_MAX_CTRLS];
+
+	char nmodes;
+	struct v4l2_pix_format modes[];
+};
+
+#endif
diff --git a/drivers/media/video/gspca/stv06xx/stv06xx_vv6410.c b/drivers/media/video/gspca/stv06xx/stv06xx_vv6410.c
new file mode 100644
index 0000000..1ca91f2
--- /dev/null
+++ b/drivers/media/video/gspca/stv06xx/stv06xx_vv6410.c
@@ -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.
+ *
+ * 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
+ *
+ * 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
+ */
+
+#include "stv06xx_vv6410.h"
+
+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) {
+		info("vv6410 sensor detected");
+
+		sd->gspca_dev.cam.cam_mode = stv06xx_sensor_vv6410.modes;
+		sd->gspca_dev.cam.nmodes = stv06xx_sensor_vv6410.nmodes;
+		sd->desc.ctrls = stv06xx_sensor_vv6410.ctrls;
+		sd->desc.nctrls = stv06xx_sensor_vv6410.nctrls;
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
+static int vv6410_init(struct sd *sd)
+{
+	int err = 0, i;
+
+	for (i = 0; i < ARRAY_SIZE(stv_bridge_init); i++) {
+		/* if NULL then len contains single value */
+		if (stv_bridge_init[i].data == NULL) {
+			err = stv06xx_write_bridge(sd,
+				stv_bridge_init[i].start,
+				stv_bridge_init[i].len);
+		} else {
+			int j;
+			for (j = 0; j < stv_bridge_init[i].len; j++)
+				err = stv06xx_write_bridge(sd,
+					stv_bridge_init[i].start + j,
+					stv_bridge_init[i].data[j]);
+		}
+	}
+
+	if (err < 0)
+		return err;
+
+	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 cam *cam = &sd->gspca_dev.cam;
+	u32 priv = cam->cam_mode[sd->gspca_dev.curr_mode].priv;
+
+	if (priv & VV6410_CROP_TO_QVGA) {
+		PDEBUG(D_CONF, "Cropping to QVGA");
+		stv06xx_write_sensor(sd, VV6410_XENDH, 320 - 1);
+		stv06xx_write_sensor(sd, VV6410_YENDH, 240 - 1);
+	} else {
+		stv06xx_write_sensor(sd, VV6410_XENDH, 360 - 1);
+		stv06xx_write_sensor(sd, VV6410_YENDH, 294 - 1);
+	}
+
+	if (priv & VV6410_SUBSAMPLE) {
+		PDEBUG(D_CONF, "Enabling subsampling");
+		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, 0x20);
+	}
+
+	/* 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;
+
+	PDEBUG(D_STREAM, "Starting stream");
+
+	return 0;
+}
+
+static int vv6410_stop(struct sd *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;
+
+	PDEBUG(D_STREAM, "Halting stream");
+
+	return (err < 0) ? err : 0;
+}
+
+static int vv6410_dump(struct sd *sd)
+{
+	u8 i;
+	int err = 0;
+
+	info("Dumping all vv6410 sensor registers");
+	for (i = 0; i < 0xff && !err; i++) {
+		u16 data;
+		err = stv06xx_read_sensor(sd, i, &data);
+		info("Register 0x%x contained 0x%x", i, data);
+	}
+	return (err < 0) ? err : 0;
+}
+
+static int vv6410_get_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);
+
+	*val = (i2c_data & VV6410_HFLIP) ? 1 : 0;
+
+	PDEBUG(D_V4L2, "Read horizontal flip %d", *val);
+
+	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;
+
+	PDEBUG(D_V4L2, "Set horizontal flip to %d", val);
+	err = stv06xx_write_sensor(sd, VV6410_DATAFORMAT, i2c_data);
+
+	return (err < 0) ? err : 0;
+}
+
+static int vv6410_get_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);
+
+	*val = (i2c_data & VV6410_VFLIP) ? 1 : 0;
+
+	PDEBUG(D_V4L2, "Read vertical flip %d", *val);
+
+	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;
+
+	PDEBUG(D_V4L2, "Set vertical flip to %d", val);
+	err = stv06xx_write_sensor(sd, VV6410_DATAFORMAT, i2c_data);
+
+	return (err < 0) ? err : 0;
+}
+
+static int vv6410_get_analog_gain(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_ANALOGGAIN, &i2c_data);
+
+	*val = i2c_data & 0xf;
+
+	PDEBUG(D_V4L2, "Read analog gain %d", *val);
+
+	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;
+
+	PDEBUG(D_V4L2, "Set analog gain to %d", val);
+	err = stv06xx_write_sensor(sd, VV6410_ANALOGGAIN, 0xf0 | (val & 0xf));
+
+	return (err < 0) ? err : 0;
+}
diff --git a/drivers/media/video/gspca/stv06xx/stv06xx_vv6410.h b/drivers/media/video/gspca/stv06xx/stv06xx_vv6410.h
new file mode 100644
index 0000000..3ff8c4e
--- /dev/null
+++ b/drivers/media/video/gspca/stv06xx/stv06xx_vv6410.h
@@ -0,0 +1,315 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ * 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
+
+static int vv6410_probe(struct sd *sd);
+static int vv6410_start(struct sd *sd);
+static int vv6410_init(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_get_hflip(struct gspca_dev *gspca_dev, __s32 *val);
+static int vv6410_set_hflip(struct gspca_dev *gspca_dev, __s32 val);
+static int vv6410_get_vflip(struct gspca_dev *gspca_dev, __s32 *val);
+static int vv6410_set_vflip(struct gspca_dev *gspca_dev, __s32 val);
+static int vv6410_get_analog_gain(struct gspca_dev *gspca_dev, __s32 *val);
+static int vv6410_set_analog_gain(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,
+	.init = vv6410_init,
+	.probe = vv6410_probe,
+	.start = vv6410_start,
+	.stop = vv6410_stop,
+	.dump = vv6410_dump,
+
+	.nctrls = 3,
+	.ctrls = {
+	{
+		{
+			.id		= V4L2_CID_HFLIP,
+			.type		= V4L2_CTRL_TYPE_BOOLEAN,
+			.name		= "horizontal flip",
+			.minimum	= 0,
+			.maximum	= 1,
+			.step		= 1,
+			.default_value	= 0
+		},
+		.set = vv6410_set_hflip,
+		.get = vv6410_get_hflip
+	}, {
+		{
+			.id		= V4L2_CID_VFLIP,
+			.type		= V4L2_CTRL_TYPE_BOOLEAN,
+			.name		= "vertical flip",
+			.minimum	= 0,
+			.maximum	= 1,
+			.step		= 1,
+			.default_value 	= 0
+		},
+		.set = vv6410_set_vflip,
+		.get = vv6410_get_vflip
+	}, {
+		{
+			.id		= V4L2_CID_GAIN,
+			.type		= V4L2_CTRL_TYPE_INTEGER,
+			.name		= "analog gain",
+			.minimum	= 0,
+			.maximum	= 15,
+			.step		= 1,
+			.default_value  = 0
+		},
+		.set = vv6410_set_analog_gain,
+		.get = vv6410_get_analog_gain
+	}
+	},
+
+	.nmodes = 1,
+	.modes = {
+	{
+		356,
+		292,
+		V4L2_PIX_FMT_SGRBG8,
+		V4L2_FIELD_NONE,
+		.sizeimage =
+			356 * 292,
+		.bytesperline = 356,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0
+	}
+	}
+};
+
+/* If NULL, only single value to write, stored in len */
+struct stv_init {
+	const u8 *data;
+	u16 start;
+	u8 len;
+};
+
+static const u8 x1500[] = {	/* 0x1500 - 0x150f */
+	0x0b, 0xa7, 0xb7, 0x00, 0x00
+};
+
+static const u8 x1536[] = {	/* 0x1536 - 0x153b */
+	0x02, 0x00, 0x60, 0x01, 0x20, 0x01
+};
+
+static const u8 x15c1[] = {	/* 0x15c1 - 0x15c2 */
+	0xff, 0x03 /* Output word 0x03ff = 1023 (ISO size) */
+};
+
+static const struct stv_init stv_bridge_init[] = {
+	/* This reg is written twice. Some kind of reset? */
+	{NULL,  0x1620, 0x80},
+	{NULL,  0x1620, 0x00},
+	{NULL,  0x1423, 0x04},
+	{x1500, 0x1500, ARRAY_SIZE(x1500)},
+	{x1536, 0x1536, ARRAY_SIZE(x1536)},
+	{x15c1, 0x15c1, ARRAY_SIZE(x15c1)}
+};
+
+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 */
+	{VV6410_FGMODES,	BIT(6) | BIT(4) | BIT(2) | BIT(0)},
+	{VV6410_PINMAPPING,	0x00},
+	/* Pre-clock generator divide off */
+	{VV6410_DATAFORMAT,	BIT(7) | BIT(0)},
+
+	/* Exposure registers */
+	{VV6410_FINEH,		VV6410_FINE_EXPOSURE >> 8},
+	{VV6410_FINEL,		VV6410_FINE_EXPOSURE & 0xff},
+	{VV6410_COARSEH,	VV6410_COARSE_EXPOSURE >> 8},
+	{VV6410_COARSEL,	VV6410_COARSE_EXPOSURE & 0xff},
+	{VV6410_ANALOGGAIN,	0xf0 | VV6410_DEFAULT_GAIN},
+	{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