diff --git a/drivers/media/usb/gspca/Kconfig b/drivers/media/usb/gspca/Kconfig
index 6345f93..233debd 100644
--- a/drivers/media/usb/gspca/Kconfig
+++ b/drivers/media/usb/gspca/Kconfig
@@ -50,6 +50,15 @@ config USB_GSPCA_CPIA1
 	  To compile this driver as a module, choose M here: the
 	  module will be called gspca_cpia1.
 
+config USB_GSPCA_EM27XX
+	tristate "EM27xx USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on EM27xx chips.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_em27xx.
+
 config USB_GSPCA_ETOMS
 	tristate "Etoms USB Camera Driver"
 	depends on VIDEO_V4L2 && USB_GSPCA
diff --git a/drivers/media/usb/gspca/Makefile b/drivers/media/usb/gspca/Makefile
index c901da0..651b1a6 100644
--- a/drivers/media/usb/gspca/Makefile
+++ b/drivers/media/usb/gspca/Makefile
@@ -2,6 +2,7 @@ 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_EM27XX)   += gspca_em27xx.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
@@ -47,6 +48,7 @@ gspca_main-objs     := gspca.o autogain_functions.o
 gspca_benq-objs     := benq.o
 gspca_conex-objs    := conex.o
 gspca_cpia1-objs    := cpia1.o
+gspca_em27xx-objs    := em27xx.o
 gspca_etoms-objs    := etoms.o
 gspca_finepix-objs  := finepix.o
 gspca_jeilinj-objs  := jeilinj.o
diff --git a/drivers/media/usb/gspca/em27xx.c b/drivers/media/usb/gspca/em27xx.c
new file mode 100644
index 0000000..13d6b0a
--- /dev/null
+++ b/drivers/media/usb/gspca/em27xx.c
@@ -0,0 +1,1792 @@
+/*
+ * gspca subdriver for em27xx cameras
+ *
+ * Copyright (C) 2011-2012  Frank Schaefer <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
+ * 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
+ */
+
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+
+#define MODULE_NAME "em27xx"
+
+
+#include "gspca.h"
+#include <media/v4l2-chip-ident.h>
+#include <linux/workqueue.h>
+#include <linux/input.h>
+
+
+#define CHIP_ID_EM2765		0x36
+
+#define CHIP_ID_OMNIVISION	0x7fa2
+#define	CHIP_ID_OV2640		0x2642 /* also 0x2641 ? */
+
+
+/* EM27xx */
+#define EM27XX_R05_I2CSTATUS		0x05
+#define EM27XX_R06_I2CCLK		0x06
+#define EM27XX_R08_GPIO			0x08
+#define EM27XX_R0A_CHIPID		0x0a
+#define EM27XX_R0C_USBSUSP		0x0c
+#define EM27XX_R0D			0x0d
+#define EM27XX_R0F_XCLK			0x0f
+#define EM27XX_R10_VINMODE		0x10
+#define EM27XX_R11_VINCTRL		0x11
+#define EM27XX_R12_VINENABLE		0x12
+#define EM27XX_R13			0x13
+#define EM27XX_R14_GAMMA		0x14
+#define EM27XX_R15_RGAIN		0x15
+#define EM27XX_R16_GGAIN		0x16
+#define EM27XX_R17_BGAIN		0x17
+#define EM27XX_R18_ROFFSET		0x18
+#define EM27XX_R19_GOFFSET		0x19
+#define EM27XX_R1A_BOFFSET		0x1a
+#define EM27XX_R1B_OFLOW		0x1b
+#define EM27XX_R1C_HSTART		0x1c
+#define EM27XX_R1D_VSTART		0x1d
+#define EM27XX_R1E_CWIDTH		0x1e
+#define EM27XX_R1F_CHEIGHT		0x1f
+#define EM27XX_R20_YGAIN		0x20	/* Contrast */
+#define EM27XX_R21_YOFFSET		0x21	/* Brightness */
+#define EM27XX_R22_UVGAIN		0x22	/* Saturation */
+#define EM27XX_R23_UOFFSET		0x23
+#define EM27XX_R24_VOFFSET		0x24
+#define EM27XX_R25_SHARPNESS		0x25	/* Sharpness */
+#define EM27XX_R26_COMPR		0x26
+#define EM27XX_R27_OUTFMT		0x27
+#define EM27XX_R28_XMIN			0x28
+#define EM27XX_R29_XMAX			0x29
+#define EM27XX_R2A_YMIN			0x2a
+#define EM27XX_R2B_YMAX			0x2b
+#define EM27XX_R30_HSCALELOW		0x30
+#define EM27XX_R31_HSCALEHIGH		0x31
+#define EM27XX_R32_VSCALELOW		0x32
+#define EM27XX_R33_VSCALEHIGH		0x33
+#define EM27XX_R34_START_H		0x34
+#define EM27XX_R35_START_V		0x35
+#define EM27XX_R44_AUDIOCTRL		0x44
+#define EM27XX_R80_GPIO_1_W		0x80
+#define EM27XX_R84_GPIO_1_R		0x84
+#define EM27XX_R85_GPIO_2_R		0x85
+
+/* EM27XX_R84_GPIO_1_R + EM27XX_R80_GPIO_1_W */
+#define EM27XX_GPIO_1_LED_STREAM	0x01	/* inverted */
+#define EM27XX_GPIO_1_LED_LIGHT		0x40	/* inverted */
+#define EM27XX_GPIO_1_BUTTON_MUTE	0x04	/* inverted */
+#define EM27XX_GPIO_1_BUTTON_LIGHT	0x08	/* inverted */
+
+/* EM27XX_R85_GPIO_1_R */
+#define EM27XX_GPIO_2_BUTTON_SNAPSHOT	0x80	/* inverted */
+
+
+#define USB_XFER_TIMEOUT		HZ	/* 1 sec */
+#define USB_XFER_ERR_WAIT		10	/* [ms] */
+#define GPIO_POLL_INTERVAL		100	/* [ms] */
+#define LED_BLINK_INTERVAL		500	/* [ms] */
+
+
+#define BRIGHTNESS_DEFAULT		-0x0e	/* -0x80 to 0x7f (s8 !) */
+#define CONTRAST_DEFAULT		0x0f	/* 0x00 to 0x1f */
+#define SATURATION_DEFAULT		0x0f	/* 0x00 to 0x1f */
+#define SHARPNESS_DEFAULT		0x00	/* 0x00 to 0x0f */
+#define POWERLINEFREQFILTER_DEFAULT	0x00	/* 0 to 2 */
+
+
+#define BULK_HEADER_FRAME_STILL_IMAGE	0x20
+#define BULK_HEADER_FRAME_END		0x02
+#define BULK_HEADER_FRAME_ID		0x01
+
+
+
+MODULE_AUTHOR("Frank Schaefer <fschaefer.oss@googlemail.com>");
+MODULE_DESCRIPTION("GSPCA/em27xx camera driver");
+MODULE_LICENSE("GPL");
+
+
+
+struct sd {
+	struct gspca_dev gspca_dev; /* must be the first item */
+	u8 sensor;
+	u8 sensor_addr;
+
+	u8 eeprom_addr;
+
+	bool muted;
+	bool illuminated;
+
+	bool mutebutton_locked;
+	bool lightbutton_locked;
+
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct v4l2_ctrl *brightness;
+	struct v4l2_ctrl *contrast;
+	struct v4l2_ctrl *saturation;
+	struct v4l2_ctrl *sharpness;
+	struct v4l2_ctrl *powerlinefreq;
+
+	struct delayed_work gpio_query_work;
+	struct delayed_work led_blink_work;
+};
+
+static u8 sensor_slave_addresses[] = {
+	0x22,	/* UNKNOWN */
+	0x66,	/* UNKNOWN */
+	0x42,	/* UNKNOWN */
+	0x60,	/* OV2640 */
+};
+
+#define	SENSOR_OV2640		0	/* must match index in sensor_ident */
+
+static u16 sensor_ident[] = {
+	V4L2_IDENT_OV2640,
+};
+
+/* Picture formats supported by the driver */
+static const struct v4l2_pix_format video_camera_mode[] = {
+	/* NOTE: The windows driver provides 176x144 and 160x120 resolutions
+	 * by software downscaling from 320x240
+	 */
+	{320, 240, V4L2_PIX_FMT_RGB565, V4L2_FIELD_NONE,
+	 .bytesperline = 320 * 2,
+	 .sizeimage = 320 * 240 * 2,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = 0},
+	{320, 240, V4L2_PIX_FMT_YUYV, V4L2_FIELD_NONE,
+	 .bytesperline = 320 * 2,
+	 .sizeimage = 320 * 240 * 2,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = 0},
+	{640, 480, V4L2_PIX_FMT_RGB565, V4L2_FIELD_NONE,
+	 .bytesperline = 640 * 2,
+	 .sizeimage = 640 * 480 * 2,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = 0},
+	{640, 480, V4L2_PIX_FMT_YUYV, V4L2_FIELD_NONE,
+	 .bytesperline = 640 * 2,
+	 .sizeimage = 640 * 480 * 2,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = 0},
+	{1280, 1024, V4L2_PIX_FMT_SRGGB8, V4L2_FIELD_NONE,
+	 .bytesperline = 1280 * 1,
+	 .sizeimage = 1280 * 1024 * 1,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = 0},
+/*	{1280, 1024, V4L2_PIX_FMT_YUV211, V4L2_FIELD_NONE,
+	 .bytesperline = 1280 * 1,
+	 .sizeimage = 1280 * 1024 * 1,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = 0},						*/
+	/* TODO: add support to the kernel */
+	{1600, 1200, V4L2_PIX_FMT_SRGGB8, V4L2_FIELD_NONE,
+	 .bytesperline = 1600 * 1,
+	 .sizeimage = 1600 * 1200 * 1,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = 0},
+/*	{1600, 1200, V4L2_PIX_FMT_YUV211, V4L2_FIELD_NONE,
+	 .bytesperline = 1600 * 1,
+	 .sizeimage = 1600 * 1200 * 1,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = 0},						*/
+	/* TODO: add support to the kernel */
+};
+
+/* This sequence is common to all resolutions / operation modes */
+static u8 ov2640_init[][2] = {
+	{0xff, 0x01}, {0x12, 0x80}, {0xff, 0x00}, {0x2c, 0xff}, {0x2e, 0xdf},
+	{0xff, 0x01}, {0x3c, 0x32}, {0x11, 0x00}, {0x09, 0x02}, {0x04, 0x28},
+	{0x13, 0xe5}, {0x14, 0x40}, {0x2c, 0x0c}, {0x33, 0x78}, {0x3a, 0x33},
+	{0x3b, 0xfb}, {0x3e, 0x00}, {0x43, 0x11}, {0x16, 0x10}, {0x39, 0x02},
+	{0x35, 0x88}, {0x22, 0x0a}, {0x37, 0x40}, {0x23, 0x00}, {0x34, 0xa0},
+	{0x36, 0x1a}, {0x06, 0x02}, {0x07, 0xc0}, {0x0d, 0xb7}, {0x0e, 0x01},
+	{0x4c, 0x00}, {0x4a, 0x81}, {0x21, 0x99}, {0x24, 0x40}, {0x25, 0x38},
+	{0x26, 0x82}, {0x5c, 0x00}, {0x63, 0x00}, {0x20, 0x80}, {0x28, 0x30},
+	{0x6c, 0x00}, {0x6d, 0x80}, {0x6e, 0x00}, {0x70, 0x02}, {0x71, 0x94},
+	{0x73, 0xc1}, {0x3d, 0x34}, {0x5a, 0x57}, {0x4e, 0x00}, {0x4f, 0xca},
+	{0x50, 0xa8}, {0xff, 0x00}, {0xe5, 0x7f}, {0xf9, 0xc0}, {0x41, 0x24},
+	{0xe0, 0x14}, {0x76, 0xff}, {0x33, 0xa0}, {0x42, 0x20}, {0x43, 0x18},
+	{0x4c, 0x00}, {0x87, 0xd0}, {0x88, 0x3f}, {0xd7, 0x03}, {0xd9, 0x10},
+	{0xd3, 0x82}, {0xc8, 0x08}, {0xc9, 0x80}, {0x7c, 0x00}, {0x7d, 0x00},
+	{0x7c, 0x03}, {0x7d, 0x48}, {0x7d, 0x48}, {0x7c, 0x08}, {0x7d, 0x20},
+	{0x7d, 0x10}, {0x7d, 0x0e}, {0x92, 0x00}, {0x93, 0x06}, {0x93, 0xe4},
+	{0x93, 0x05}, {0x93, 0x05}, {0x93, 0x00}, {0x93, 0x04}, {0x93, 0x00},
+	{0x93, 0x00}, {0x93, 0x00}, {0x93, 0x00}, {0x93, 0x00}, {0x93, 0x00},
+	{0x93, 0x00}, {0xc3, 0xed}, {0xa4, 0x00}, {0xa8, 0x00}, {0xc5, 0x11},
+	{0xc6, 0x51}, {0xbf, 0x80}, {0xc7, 0x00}, {0xb6, 0x4d}, {0xb8, 0xa5},
+	{0xb7, 0x64}, {0xb9, 0x7c}, {0xb3, 0xaf}, {0xb4, 0x97}, {0xb5, 0xff},
+	{0xb0, 0xc5}, {0xb1, 0x94}, {0xb2, 0x0f}, {0xc4, 0x5c}, {0xc0, 0xc8},
+	{0xc1, 0x96}, {0x86, 0x1d}, {0x50, 0x00}, {0x51, 0x90}, {0x52, 0x18},
+	{0x53, 0x00}, {0x54, 0x00}, {0x55, 0x88}, {0x57, 0x00}, {0x5a, 0x90},
+	{0x5b, 0x18}, {0x5c, 0x05}, {0xc3, 0xed}, {0x7f, 0x00}, {0xda, 0x00},
+	{0xe5, 0x1f}, {0xe1, 0x67}, {0xe0, 0x00}, {0xdd, 0x7f}, {0x05, 0x00},
+	{0xff, 0x01}, {0x7c, 0x05}, {0x79, 0xa3}, {0x62, 0x6f}, {0x61, 0x63},
+	{0x75, 0xe0}, {0x76, 0xe0}, {0x77, 0xf0}, {0x78, 0xef}, {0xff, 0x00},
+	{0x90, 0x00}, {0x91, 0x0e}, {0x91, 0x1a}, {0x91, 0x31}, {0x91, 0x5a},
+	{0x91, 0x69}, {0x91, 0x75}, {0x91, 0x7e}, {0x91, 0x88}, {0x91, 0x8f},
+	{0x91, 0x96}, {0x91, 0xa3}, {0x91, 0xaf}, {0x91, 0xc4}, {0x91, 0xd7},
+	{0x91, 0xe8}, {0x91, 0x20}, {0xff, 0x00}, {0xc8, 0x00}, {0x96, 0x00},
+	{0x97, 0x08}, {0x97, 0x19}, {0x97, 0x02}, {0x97, 0x0c}, {0x97, 0x23},
+	{0x97, 0x31}, {0x97, 0x29}, {0x97, 0x25}, {0x97, 0x02}, {0x97, 0x98},
+	{0x97, 0x80}, {0x97, 0x00}, {0xff, 0x00}, {0xa6, 0x00}, {0xa7, 0xb8},
+	{0xa7, 0x38}, {0xa7, 0x19}, {0xa7, 0x21}, {0xa7, 0x59}, {0xa7, 0x23},
+	{0xa7, 0xb8}, {0xa7, 0x38}, {0xa7, 0x17}, {0xa7, 0x21}, {0xa7, 0x59},
+	{0xa7, 0x24}, {0xa7, 0xb8}, {0xa7, 0x38}, {0xa7, 0x18}, {0xa7, 0x21},
+	{0xa7, 0x59}, {0xa7, 0x27}, {0xc3, 0xef}, {0xff, 0x01}, {0x14, 0x40},
+	{0x0f, 0x4b}, {0x03, 0x8f}, {0xff, 0x00}, {0xbf, 0x00}, {0xba, 0xff},
+	{0xbb, 0x00}, {0xb6, 0x4d}, {0xb8, 0x78}, {0xb7, 0x20}, {0xb9, 0x40},
+	{0xb3, 0xb8}, {0xb4, 0xc5}, {0xb5, 0xed}, {0xb0, 0x7f}, {0xb1, 0x5e},
+	{0xb2, 0x07}, {0xc7, 0x00}, {0xc6, 0x51}, {0xc5, 0x11}, {0xc4, 0x5c},
+	{0xff, 0x00}, {0x86, 0x1d}, {0xc8, 0x08}, {0xc9, 0x92}, {0x7c, 0x03},
+	{0xff, 0x01}, {0x22, 0x0a},
+};
+
+static int read_usbdev(struct gspca_dev *gspca_dev, u8 request, u16 index,
+		       u8 *data, u16 length)
+{
+	int ret;
+	int err;
+	if (unlikely(length > USB_BUF_SZ))
+		return -EMSGSIZE;
+	for (err = 0; err < 3; err++) {
+		ret = usb_control_msg(gspca_dev->dev,
+				      usb_rcvctrlpipe(gspca_dev->dev, 0),
+				      request,
+				      USB_DIR_IN | USB_TYPE_VENDOR
+				       | USB_RECIP_DEVICE,
+				      0,
+				      index,
+				      gspca_dev->usb_buf, length,
+				      USB_XFER_TIMEOUT);
+		if (ret == length) {
+			memcpy(data, gspca_dev->usb_buf, length);
+			break;
+		} else {
+			msleep(USB_XFER_ERR_WAIT);
+		}
+	}
+	return ret;
+}
+
+static int write_usbdev(struct gspca_dev *gspca_dev, u8 request, u16 index,
+			u8 *data, u16 length)
+{
+	int ret;
+	int err;
+	if (unlikely(length > USB_BUF_SZ))
+		return -EMSGSIZE;
+	memcpy(gspca_dev->usb_buf, data, length);
+	for (err = 0; err < 3; err++) {
+		ret = usb_control_msg(gspca_dev->dev,
+				      usb_sndctrlpipe(gspca_dev->dev, 0),
+				      request,
+				      USB_DIR_OUT | USB_TYPE_VENDOR
+				       | USB_RECIP_DEVICE,
+				      0,
+				      index,
+				      gspca_dev->usb_buf, length,
+				      USB_XFER_TIMEOUT);
+		if (ret == length)
+			break;
+		else
+			msleep(USB_XFER_ERR_WAIT);
+	}
+	return ret;
+}
+
+static int read_em27xx(struct gspca_dev *gspca_dev, u16 reg, u8 *data,
+		       u16 len)
+{
+	u8 request = 0x00;
+	u16 index = reg;
+	int ret;
+
+	if ((reg + len - 1) > 0xffff)
+		return -EOVERFLOW;
+	ret = read_usbdev(gspca_dev, request, index, data, len);
+	if (ret < 0)
+		PDEBUG(D_ERR,
+		       "error: failed to read %d bytes from em27xx "
+		       "register 0x%04x\n",
+		       len, reg);
+	return ret;
+}
+
+static int write_em27xx(struct gspca_dev *gspca_dev, u16 reg, u8 *data,
+			u16 len)
+{
+	u8 request = 0x00;
+	u16 index = reg;
+	int ret;
+
+	if ((reg + len - 1) > 0xffff)
+		return -EOVERFLOW;
+	ret =  write_usbdev(gspca_dev, request, index, data, len);
+	if (ret < 0)
+		PDEBUG(D_ERR,
+		       "error: failed to write %d bytes to em27xx "
+		       "register 0x%04x\n",
+		       len, reg);
+	return ret;
+}
+
+static int write_em27xx_single(struct gspca_dev *gspca_dev, u16 reg, u8 data)
+{
+	return write_em27xx(gspca_dev, reg, &data, 1);
+}
+
+/* 16 bit address and 8 bit register width */
+static int read_i2c(struct gspca_dev *gspca_dev, u8 i2c_slave_addr, u16 reg,
+		    u8 *data, u16 len)
+{
+	u8 request;
+	u16 index;
+	u8 buf[2];
+	int ret;
+	int err;
+
+	if ((reg + len - 1) > 0xffff)
+		return -EOVERFLOW;
+
+	index = i2c_slave_addr;
+
+	for (err = 0; err < 3; err++) {
+		/* Set register */
+		request = 0x03;
+		buf[0] = reg >> 8;
+		buf[1] = reg & 0xff;
+		ret = write_usbdev(gspca_dev, request, index, buf, 2);
+		if (ret < 0) {
+			PDEBUG(D_ERR,
+			       "error: sending i2c set register request "
+			       "failed: %d\n",
+			       ret);
+			break;
+		}
+
+		/* Check success */
+		ret = read_em27xx(gspca_dev, EM27XX_R05_I2CSTATUS, buf, 1);
+		if (ret < 0) {
+			PDEBUG(D_ERR,
+			       "error: sending check i2c status request "
+			       "failed: %d\n",
+			       ret);
+			continue;
+		}
+		if (buf[0] != 0x00) {
+			/* NOTE: the only error we've seen so far is
+			 * 0x10 when the slave device is not present */
+			PDEBUG(D_ERR,
+			       "error: setting i2c register failed: "
+			       "i2c status 0x%02x\n",
+			       buf[0]);
+			ret = -EIO;
+			continue;
+		}
+
+		/* Read value */
+		request = 0x02;
+		ret = read_usbdev(gspca_dev, request, index, data, len);
+		if (ret < 0) {
+			PDEBUG(D_ERR,
+			       "error: sending i2c read register failed: %d\n",
+			       ret);
+			continue;
+		}
+
+		/* Check success */
+		ret = read_em27xx(gspca_dev, EM27XX_R05_I2CSTATUS, buf, 1);
+		if (ret < 0) {
+			PDEBUG(D_ERR,
+			       "error: sending check i2c status request "
+			       "failed: %d\n",
+			       ret);
+			continue;
+		}
+		if (buf[0] != 0x00) {
+			PDEBUG(D_ERR,
+			       "error: reading i2c register failed: "
+			       "i2c status 0x%02x\n",
+			       buf[0]);
+			ret = -EIO;
+			continue;
+		}
+
+		return len;
+	}
+
+	PDEBUG(D_ERR,
+	       "error: failed to read %d byte(s) from i2c slave "
+	       "address 0x%02x, register 0x%04x\n",
+	       len, i2c_slave_addr, reg);
+	return ret;
+}
+
+#if 0
+/* 16 bit address and 8 bit register width */
+static int write_i2c_single(struct gspca_dev *gspca_dev, u8 i2c_slave_addr,
+			    u16 reg, u8 data)
+{
+	u8 request;
+	u16 index;
+	u8 buf[3];
+	int ret;
+	int err;
+
+	for (err = 0; err < 3; err++) {
+		/* Set register and write data */
+		request = 0x03;
+		index = i2c_slave_addr;
+		buf[0] = reg >> 8;
+		buf[1] = reg & 0xff;
+		buf[2] = data;
+		ret = write_usbdev(gspca_dev, request, index, buf, 3);
+		if (ret < 0) {
+			PDEBUG(D_ERR,
+			       "error: sending i2c write register request "
+			       "failed: %d\n",
+			       ret);
+			break;
+		}
+
+		/* Check success */
+		ret = read_em27xx(gspca_dev, EM27XX_R05_I2CSTATUS, buf, 1);
+		if (ret < 0) {
+			PDEBUG(D_ERR,
+			       "error: sending check i2c status request "
+			       "failed: %d\n",
+			       ret);
+			continue;
+		}
+		if (buf[0] != 0x00) {
+			/* NOTE: the only error we've seen so far is
+			 * 0x10 when the slave device is not present */
+			PDEBUG(D_ERR,
+			       "error: reading i2c register failed: "
+			       "i2c status 0x%02x\n",
+			       buf[0]);
+			ret = -EIO;
+			continue;
+		}
+
+		return 1;
+	}
+	PDEBUG(D_ERR,
+	       "error: failed to write 1 byte to slave address 0x%02x, "
+	       "register 0x%04x\n",
+	       i2c_slave_addr, reg);
+	return ret;
+
+	/* NOTE: we could write more than 1 byte... */
+
+} /* NOT YET NEEDED, COMPLETELY UNTESTED ! */
+#endif
+
+/* 8 bit address and register width */
+static int read_propr(struct gspca_dev *gspca_dev, u8 slave_addr, u8 reg,
+		      u8 *data, u8 len)
+{
+	u8 request;
+	u16 index;
+	u8 buf[2];
+	int ret;
+	int err;
+	int i;
+
+	if ((reg + len - 1) > 0xff)
+		return -EOVERFLOW;
+
+	for (i = 0; i < len; i++) {
+		for (err = 0; err < 3; err++) {
+			/* Set register */
+			request = 0x06;
+			index = slave_addr;
+			buf[0] = reg + i;
+			ret = write_usbdev(gspca_dev, request, index, buf, 1);
+			if (ret < 0) {
+				PDEBUG(D_ERR,
+				       "error: sending proprietary set "
+				       "register request failed: %d\n",
+				       ret);
+				return ret;
+			}
+
+			/* Check success */
+			request = 0x08;
+			index = 0x0000;
+			ret = read_usbdev(gspca_dev, request, index, buf, 1);
+			if (ret < 0) {
+				PDEBUG(D_ERR,
+				       "error: sending check proprietary comm"
+				       "unication status request failed: %d\n",
+				       ret);
+				continue;
+			}
+			if (buf[0] != 0x00) {
+				/* NOTE: the only error we've seen so far is
+				 * 0x01 when the slave device is not present */
+				PDEBUG(D_ERR,
+				       "error: proprietary set register failed: "
+				       "status 0x%02x\n",
+				       buf[0]);
+				ret = -EIO;
+				continue;
+			}
+
+			/* Read value  */
+			request = 0x06;
+			index = slave_addr;
+			ret = read_usbdev(gspca_dev, request, index,
+					  data + i, 1);
+			if (ret < 0) {
+				PDEBUG(D_ERR,
+				       "error: sending proprietary read "
+				       "register request failed: %d\n",
+				       ret);
+				continue;
+			}
+			/* NOTE:
+			 * Only 1 byte can be read per request. If n > 1 bytes
+			 * are requested, the device returns n bytes which have
+			 * all the same value (the value of the current
+			 * register).					     */
+
+			/* Check success */
+			request = 0x08;
+			index = 0x0000;
+			ret = read_usbdev(gspca_dev, request, index, buf, 1);
+			if (ret < 0) {
+				PDEBUG(D_ERR,
+				       "error: sending check proprietary comm"
+				       "unication status request failed: %d\n",
+				       ret);
+				continue;
+			}
+			if (buf[0] != 0x00) {
+				PDEBUG(D_ERR,
+				       "error: proprietary read register "
+				       "failed: status 0x%02x\n",
+				       buf[0]);
+				ret = -EIO;
+				continue;
+			}
+
+			break;
+		}
+		if (ret < 0) {
+			PDEBUG(D_ERR,
+			       "error: failed to read %d byte(s) from slave "
+			       "address 0x%02x, register 0x%02x\n",
+			       len, slave_addr, reg);
+			return ret;
+		}
+	}
+
+	return len;
+}
+
+/* 8 bit address and register width */
+static int write_propr(struct gspca_dev *gspca_dev, u8 slave_addr,
+		       u8 reg, u8 *data, u8 len)
+{
+	u8 request;
+	u16 index;
+	u8 buf[2];
+	int ret;
+	int err;
+	int i;
+
+	if ((reg + len - 1) > 0xff)
+		return -EOVERFLOW;
+
+	for (i = 0; i < len; i++) {
+		for (err = 0; err < 3; err++) {
+			/* Set register and write value */
+			request = 0x06;
+			index = slave_addr;
+			buf[0] = reg + i;
+			buf[1] = data[i];
+			ret = write_usbdev(gspca_dev, request, index, buf, 2);
+			if (ret < 0) {
+				PDEBUG(D_ERR,
+				       "error: sending proprietary write "
+				       "register request failed: %d\n",
+				       ret);
+				break;
+			}
+			/* NOTE:
+			 * The device always uses the first submitted byte as
+			 * address and the last submitted byte as value.
+			 * All other bytes are ignored !		     */
+
+			/* Check success */
+			request = 0x08;
+			index = 0x0000;
+			ret = read_usbdev(gspca_dev, request, index, buf, 1);
+			if (ret < 0) {
+				PDEBUG(D_ERR,
+				       "error: sending check proprietary comm"
+				       "unication status request failed: %d\n",
+				       ret);
+				continue;
+			}
+			if (buf[0] != 0x00) {
+				PDEBUG(D_ERR,
+				       "error: proprietary write register "
+				       "failed: status 0x%02x\n",
+				       buf[0]);
+				ret = -EIO;
+				continue;
+			}
+
+			break;
+		}
+		if (ret < 0) {
+			PDEBUG(D_ERR,
+			       "error: failed to write %d byte(s) to slave "
+			       "address 0x%02x, register 0x%02x\n",
+			       len, slave_addr, reg);
+			return ret;
+		}
+	}
+
+	return len;
+}
+
+/* 8 bit address and register width */
+static int write_propr_single(struct gspca_dev *gspca_dev, u8 slave_addr,
+			      u8 reg, u8 data)
+{
+	return write_propr(gspca_dev, slave_addr, reg, &data, 1);
+}
+
+static int set_brightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	int ret;
+	struct sd *sd = (struct sd *) gspca_dev;
+	if (!sd->muted) {
+		ret = write_em27xx_single(gspca_dev, EM27XX_R21_YOFFSET,
+					  (u8)val);
+		if (ret < 0) {
+			PDEBUG(D_ERR, "error: failed to set brightness\n");
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static int set_contrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	int ret;
+	struct sd *sd = (struct sd *) gspca_dev;
+	if (!sd->muted) {
+		ret = write_em27xx_single(gspca_dev, EM27XX_R20_YGAIN, val);
+		if (ret < 0) {
+			PDEBUG(D_ERR, "error: failed to set contrast\n");
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static int set_saturation(struct gspca_dev *gspca_dev, s32 val)
+{
+	int ret;
+	struct sd *sd = (struct sd *) gspca_dev;
+	if (!sd->muted) {
+		ret = write_em27xx_single(gspca_dev, EM27XX_R22_UVGAIN, val);
+		if (ret < 0) {
+			PDEBUG(D_ERR, "error: failed to set saturation\n");
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static int set_sharpness(struct gspca_dev *gspca_dev, s32 val)
+{
+	int ret;
+	ret = write_em27xx_single(gspca_dev, EM27XX_R25_SHARPNESS, val);
+	if (ret < 0) {
+		PDEBUG(D_ERR, "error: failed to set sharpness\n");
+		return ret;
+	}
+	return 0;
+}
+
+static int set_powerlinefreqfilter(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int ret;
+
+	if (sd->sensor != SENSOR_OV2640)
+		return -ENODEV;
+
+	ret = write_propr_single(gspca_dev, sd->sensor_addr, 0xff, 0x01);
+	if (ret < 0)
+		return ret;
+	 /* NOTE: reg 0xff = register set selection
+	  *       => too risky to continue on error ! */
+
+	/* COM8: auto exposure + auto AGC + banding filter on */
+	write_propr_single(gspca_dev, sd->sensor_addr, 0x13, 0xe5);
+
+	if (val == V4L2_CID_POWER_LINE_FREQUENCY_50HZ) {
+		/* COM3: snapshot option=live video output after snapshot seq.,
+		 *       manual banding selection, banding=50Hz               */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x0c, 0x3c);
+		/* banding 50 Hz AEC value */
+		if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width
+		     <= 640)                                            {
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x4e, 0x00); /* 2 MSBs */
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x4f, 0xca); /* 8 LSBs */
+		} else {
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x4e, 0x50);
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x4f, 0x74);
+		}
+	} else if (val == V4L2_CID_POWER_LINE_FREQUENCY_60HZ) {
+		/* COM3: snapshot option=live video output after snapshot seq.,
+		 *       manual banding selection, banding=60Hz               */
+		write_propr_single(gspca_dev, sd->sensor_addr,
+				   0x0c, 0x38);
+		/* banding 60 Hz AEC value */
+		if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width
+		     <= 640)                                            {
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x4e, 0x00); /* 2 MSBs */
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x50, 0xa8); /* 8 LSBs */
+		} else {
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x4e, 0x50);
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x50, 0x38);
+		}
+	}
+
+	/* UNKNOWN/RESERVED */
+	write_propr_single(gspca_dev, sd->sensor_addr, 0x4a, 0x81);
+	write_propr_single(gspca_dev, sd->sensor_addr, 0x5a, 0x23);
+	return 0;
+}
+
+static int sd_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;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		return set_brightness(&sd->gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		return set_contrast(&sd->gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		return set_saturation(&sd->gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SHARPNESS:
+		return set_sharpness(&sd->gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		return set_powerlinefreqfilter(&sd->gspca_dev, ctrl->val);
+		break;
+	}
+	return 0;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static void mute(struct gspca_dev *gspca_dev)
+{
+	u8 value;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	pr_info("muting audio/video\n");
+	/* Mute audio */
+	value = 0x00;
+	if (read_em27xx(gspca_dev, EM27XX_R44_AUDIOCTRL, &value, 1) < 0)
+		PDEBUG(D_ERR,
+		       "em27xx_mute: error: failed to read from reg 0x%02x\n",
+		       EM27XX_R44_AUDIOCTRL);
+	value |= 0x80;	/* switch LED off */
+	if (write_em27xx_single(gspca_dev, EM27XX_R44_AUDIOCTRL, value) < 0)
+		PDEBUG(D_ERR, "em27xx_mute: error: muting audio failed\n");
+	/* NOTE: the windows driver reads value 0x00 from this register and
+	 *       then writes 0x80, so we assume that only bit 7 is relevant */
+	/* Mute video */
+	if (write_em27xx_single(gspca_dev, EM27XX_R20_YGAIN, 0x00) < 0)
+		PDEBUG(D_ERR,
+		       "em27xx_mute: error: setting contrast to 0 failed\n");
+	if (write_em27xx_single(gspca_dev, EM27XX_R21_YOFFSET, 0x80) < 0)
+		PDEBUG(D_ERR,
+		       "em27xx_mute: error: setting brightness to 0 failed\n");
+	if (write_em27xx_single(gspca_dev, EM27XX_R22_UVGAIN, 0x00) < 0)
+		PDEBUG(D_ERR,
+		       "em27xx_mute: error: setting saturation to 0 failed\n");
+	/* Switch off LED and start blinking */
+	read_em27xx(gspca_dev, EM27XX_R84_GPIO_1_R, &value, 1);
+	value |= EM27XX_GPIO_1_LED_STREAM;	/* switch LED off */
+	write_em27xx_single(gspca_dev, EM27XX_R80_GPIO_1_W, value);
+	schedule_delayed_work(&sd->led_blink_work,
+			      msecs_to_jiffies(LED_BLINK_INTERVAL));
+	sd->muted = true;
+}
+
+static void unmute(struct gspca_dev *gspca_dev)
+{
+	u8 value;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	pr_info("unmuting audio/video\n");
+	/* Unmute audio */
+	value = 0x80;
+	if (read_em27xx(gspca_dev, EM27XX_R44_AUDIOCTRL, &value, 1) < 0)
+		PDEBUG(D_ERR,
+		       "em27xx_mute: error: failed to read from reg 0x%02x\n",
+		       EM27XX_R44_AUDIOCTRL);
+	value &= ~0x80;	/* switch LED off */
+	if (write_em27xx_single(gspca_dev, EM27XX_R44_AUDIOCTRL, value) < 0)
+		PDEBUG(D_ERR, "em27xx_unmute: error: unmuting audio failed\n");
+	/* NOTE: the windows driver reads value 0x80 from this register and
+	 *       then writes 0x00, so we assume that only bit 7 is relevant */
+	/* Unmute video */
+	/* NOTE: do NOT call set_... because this will not work when
+	 *       sd->muted is set                                    */
+	if (write_em27xx_single(gspca_dev, EM27XX_R20_YGAIN,
+				v4l2_ctrl_g_ctrl(sd->contrast)) < 0)
+		PDEBUG(D_ERR,
+		       "em27xx_unmute: error: "
+		       "failed to restore contrast setting\n");
+	if (write_em27xx_single(gspca_dev, EM27XX_R21_YOFFSET,
+				v4l2_ctrl_g_ctrl(sd->brightness)) < 0)
+		PDEBUG(D_ERR,
+		       "em27xx_unmute: error: "
+		       "failed to restore brightness setting\n");
+	if (write_em27xx_single(gspca_dev, EM27XX_R22_UVGAIN,
+				v4l2_ctrl_g_ctrl(sd->saturation)) < 0)
+		PDEBUG(D_ERR,
+		       "em27xx_unmute: error: "
+		       "failed to restore saturation setting\n");
+	/* Stop blinking and switch on LED */
+	cancel_delayed_work_sync(&sd->led_blink_work);
+	read_em27xx(gspca_dev, EM27XX_R84_GPIO_1_R, &value, 1);
+	value &= ~EM27XX_GPIO_1_LED_STREAM;	/* switch LED on */
+	write_em27xx_single(gspca_dev, EM27XX_R80_GPIO_1_W, value);
+	sd->muted = false;
+}
+
+static void toggle_led(struct work_struct *work)
+{
+	u8 value;
+	struct delayed_work *dw
+		 = container_of(work, struct delayed_work, work);
+	struct sd *sd = container_of(dw, struct sd, led_blink_work);
+	struct gspca_dev *gspca_dev = &sd->gspca_dev;
+
+	if (mutex_lock_interruptible(&gspca_dev->usb_lock))
+		return;
+	if (read_em27xx(gspca_dev, EM27XX_R84_GPIO_1_R, &value, 1) < 1) {
+		PDEBUG(D_ERR,
+		       "toggle_led: error: reading of the capturing "
+		       "LED status failed\n");
+		goto end;
+	}
+	if (value & EM27XX_GPIO_1_LED_STREAM)	/* LED is off */
+		value &= ~EM27XX_GPIO_1_LED_STREAM;	/* switch LED on */
+	else					/* LED is on */
+		value |= EM27XX_GPIO_1_LED_STREAM;	/* switch LED off */
+	if (write_em27xx_single(gspca_dev, EM27XX_R80_GPIO_1_W, value) < 1)
+		PDEBUG(D_ERR,
+		       "toggle_led: error: toggling the capturing "
+		       "LED failed\n");
+
+end:
+	mutex_unlock(&gspca_dev->usb_lock);
+	schedule_delayed_work(&sd->led_blink_work,
+			      msecs_to_jiffies(LED_BLINK_INTERVAL));
+}
+
+static void check_button(struct work_struct *work)
+{
+	uint8_t value;
+	struct delayed_work *dw = container_of(work, struct delayed_work, work);
+	struct sd *sd = container_of(dw, struct sd, gpio_query_work);
+	struct gspca_dev *gspca_dev = &sd->gspca_dev;
+
+	if (mutex_lock_interruptible(&gspca_dev->usb_lock))
+		return;
+	if (read_em27xx(gspca_dev, EM27XX_R84_GPIO_1_R, &value, 1) < 0) {
+		PDEBUG(D_ERR,
+		       "check_button: error: reading of the "
+		       "button states failed\n");
+	} else {
+		/* MUTE-BUTTON */
+		if (!(value & EM27XX_GPIO_1_BUTTON_MUTE)) {
+			if (!sd->mutebutton_locked) {
+				if (gspca_dev->streaming) {
+					if (!sd->muted) {
+						mute(gspca_dev);
+						value
+						  |= EM27XX_GPIO_1_LED_STREAM;
+					} else {
+						unmute(gspca_dev);
+						value
+						  &= ~EM27XX_GPIO_1_LED_STREAM;
+					}
+				}
+				sd->mutebutton_locked = true;
+			}
+		} else {
+			sd->mutebutton_locked = false;
+		}
+		/* LIGHT-BUTTON */
+		if (!(value & EM27XX_GPIO_1_BUTTON_LIGHT)) {
+			if (!sd->lightbutton_locked) {
+				if (value & EM27XX_GPIO_1_LED_LIGHT) {
+					pr_info("switching light on\n");
+					value &= ~EM27XX_GPIO_1_LED_LIGHT;
+					sd->illuminated = true;
+				} else {
+					pr_info("switching light off\n");
+					value |= EM27XX_GPIO_1_LED_LIGHT;
+					sd->illuminated = false;
+				}
+				sd->lightbutton_locked = true;
+			}
+		} else {
+			sd->lightbutton_locked = false;
+		}
+		/* Reset button states */
+		value |= EM27XX_GPIO_1_BUTTON_MUTE;
+		value |= EM27XX_GPIO_1_BUTTON_LIGHT;
+
+		if (write_em27xx_single(gspca_dev, EM27XX_R80_GPIO_1_W, value)
+		     < 0)
+			PDEBUG(D_ERR,
+			       "check_button: error: write to GPIO register 0x80 failed\n");
+	}
+
+#if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE)
+	/* SNAPSHOT-BUTTON */
+	if (read_em27xx(gspca_dev, EM27XX_R85_GPIO_2_R, &value, 1) < 0)
+		PDEBUG(D_ERR,
+		       "check_button: error: reading of snapshot "
+		       "button status failed\n");
+	else {
+		if (!(value & EM27XX_GPIO_2_BUTTON_SNAPSHOT)) {
+			pr_info("snapshot button pressed\n");
+			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
+	mutex_unlock(&gspca_dev->usb_lock);
+	/* Schedule next poll */
+	schedule_delayed_work(&sd->gpio_query_work,
+			      msecs_to_jiffies(GPIO_POLL_INTERVAL));
+}
+
+static int probe_sensor(struct gspca_dev *gspca_dev)
+{
+	u8 slave_addr;
+	u8 reg;
+	u8 buf[2];
+	u16 id;
+	int ret;
+	int i;
+	struct sd *sd = (struct sd *) gspca_dev;
+#ifdef GSPCA_DEBUG
+	int gspca_debug_bak = gspca_debug;
+
+	gspca_debug &= ~D_ERR; /* suppress error while probing */
+#endif
+	for (i = 0; i < ARRAY_SIZE(sensor_slave_addresses); i++) {
+		slave_addr = sensor_slave_addresses[i];
+		/* OmniVision sensors */
+		reg = 0x1c;	/* OmniVision manufacturer ID (MSB) */
+		ret = read_propr(gspca_dev, slave_addr, reg, buf, 2);
+		if (ret == 2) {
+			id = (buf[0] << 8) + buf[1];
+			if (id == CHIP_ID_OMNIVISION) {
+#ifdef GSPCA_DEBUG
+				gspca_debug = gspca_debug_bak;
+#endif
+				reg = 0x0a; /* OmniVision product ID (MSB) */
+				ret = read_propr(gspca_dev, slave_addr, reg,
+						buf, 2);
+				if (ret == 2) {
+					id = (buf[0] << 8) + buf[1];
+					if (id == CHIP_ID_OV2640) {
+						PDEBUG(D_PROBE,
+						       "OV2640 sensor "
+						       "detected at slave "
+						       "address 0x%02x\n",
+						       slave_addr);
+						sd->sensor = SENSOR_OV2640;
+						sd->sensor_addr = slave_addr;
+						return 0;
+					} else {
+						PDEBUG(D_PROBE | D_ERR,
+						       "unknown OmniVision "
+						       "sensor detected: "
+						       "0x%02x\n",
+						       id);
+						return -ENODEV;
+					}
+				}
+			}
+			PDEBUG(D_PROBE | D_ERR,
+			       "unknown sensor detected at slave "
+			       "address 0x%02x\n",
+			       slave_addr);
+			return -ENODEV;
+		} else {
+			PDEBUG(D_PROBE,
+			       "no sensor detected at slave address 0x%02x\n",
+			       slave_addr);
+		}
+	}
+#ifdef GSPCA_DEBUG
+	gspca_debug = gspca_debug_bak;
+#endif
+
+	PDEBUG(D_PROBE | D_ERR, "error: no sensor detected\n");
+	return -ENODEV;
+}
+
+/* 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;
+
+	cam->cam_mode = video_camera_mode;
+	cam->nmodes = ARRAY_SIZE(video_camera_mode);
+
+	cam->bulk = USB_ENDPOINT_XFER_BULK;
+	cam->bulk_nurbs = 1;
+
+	INIT_DELAYED_WORK(&sd->led_blink_work, toggle_led);
+	INIT_DELAYED_WORK(&sd->gpio_query_work, check_button);
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	u8 i2c_slave_addr;
+	u8 buf[4];
+	int ret;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (mutex_lock_interruptible(&gspca_dev->usb_lock))
+		return -ERESTARTSYS;
+	if (!sd->sensor_addr) {
+		/* Search for EEPROM */
+		i2c_slave_addr = 0xa0;	/* NOTE: also 0xa1 */
+		ret = read_i2c(gspca_dev, i2c_slave_addr, 0x0000, buf, 4);
+		if (ret < 0) {
+			PDEBUG(D_PROBE, "no EEPROM found\n");
+		} else {
+			struct sd *sd = (struct sd *) gspca_dev;
+			sd->eeprom_addr = i2c_slave_addr;
+
+			if ((buf[0] == 0x26) && (buf[3] == 0x00)) {
+				PDEBUG(D_PROBE, "EEPROM found: type em25xx\n");
+			} else if ((buf[0] == 0x1a) && (buf[1] == 0xeb)
+				    && (buf[2] == 0x67) && (buf[3] == 0x95)) {
+				PDEBUG(D_PROBE, "EEPROM found: type em28xx\n");
+			} else {
+				PDEBUG(D_PROBE, "EEPROM found: unknown type\n");
+				PDEBUG(D_PROBE,
+				       "EEPROM data at addresses "
+				       "0x0000 to 0x0003: "
+				       "%02x %02x %02x %02x\n",
+				       buf[0], buf[1], buf[2], buf[3]);
+			}
+		}
+
+		/* Verify bridge */
+		ret = read_em27xx(gspca_dev, EM27XX_R0A_CHIPID, buf, 1);
+		if (ret < 0)
+			return ret;
+		if (buf[0] == CHIP_ID_EM2765) {
+			PDEBUG(D_PROBE, "EM2765 bridge detected\n");
+		} else {
+			PDEBUG(D_PROBE | D_ERR,
+			       "error: unknown bridge detected: %02x\n",
+			       buf[0]);
+			return -ENODEV;
+		}
+
+		/* NOTE: the windows driver now does the following:
+		* - read addresses 0x68-0x6b with a single 4 byte read
+		*   from (em25xx-) eeprom: 00 00 00 00
+		* - read addresses 0x70, 0x6c, 0x71, 0x6d, 0x72, 0x6e, 0x73,
+		*   0x6f from (em25xx-) eeprom: all 00			     */
+	}
+
+	/* Bridge init part 1 */
+	write_em27xx_single(gspca_dev, EM27XX_R06_I2CCLK, 0x40);
+	write_em27xx_single(gspca_dev, EM27XX_R08_GPIO, 0xf7);
+	write_em27xx_single(gspca_dev, EM27XX_R0C_USBSUSP, 0x00);
+
+	/* Probe sensor */
+	if (!sd->sensor_addr) {
+		ret = probe_sensor(gspca_dev);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* NOTE: the windows driver now does the following:
+	 * - read addresses 0x86-0x89 with a single 4 byte read
+	 *   from (em25xx-) eeprom: 1e 40 1e 72
+	 * - read addresses 0x8a-0x91 with 1 byte reads from (em25xx-) eeprom:
+	 *   00 20 01 01 00 01 01 00
+	 */
+
+	/* Bridge init part 2 */
+	write_em27xx_single(gspca_dev, EM27XX_R12_VINENABLE, 0x27);
+	write_em27xx_single(gspca_dev, EM27XX_R0D, 0x42);
+	if (read_em27xx(gspca_dev, EM27XX_R84_GPIO_1_R, buf, 1) < 1)
+		buf[0] = 0xff;
+	buf[0] |= EM27XX_GPIO_1_LED_STREAM;	/* switch LED off */
+	if (sd->illuminated)
+		buf[0] &= ~EM27XX_GPIO_1_LED_LIGHT;	/* switch light on */
+	else
+		buf[0] |= EM27XX_GPIO_1_LED_LIGHT;	/* switch light on */
+	write_em27xx_single(gspca_dev, EM27XX_R80_GPIO_1_W, buf[0]);
+
+	mutex_unlock(&gspca_dev->usb_lock);
+
+	schedule_delayed_work(&sd->gpio_query_work,
+			      msecs_to_jiffies(GPIO_POLL_INTERVAL));
+
+	return 0;
+}
+
+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;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 5);
+
+	sd->brightness = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					   V4L2_CID_BRIGHTNESS, -0x80, 0x7f, 1,
+					   BRIGHTNESS_DEFAULT);
+	/* NOTE: value stored in register as signed 8 bit */
+	sd->contrast = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					 V4L2_CID_CONTRAST, 0, 0x1f, 1,
+					 CONTRAST_DEFAULT);
+	sd->saturation = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					   V4L2_CID_SATURATION, 0, 0x1f, 1,
+					   SATURATION_DEFAULT);
+	sd->sharpness = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					  V4L2_CID_SHARPNESS, 0, 0x0f, 1,
+					  SHARPNESS_DEFAULT);
+	sd->powerlinefreq
+	  = v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+				   V4L2_CID_POWER_LINE_FREQUENCY,
+				   V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 0,
+				   POWERLINEFREQFILTER_DEFAULT);
+
+	if (hdl->error) {
+		pr_err("error: could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* called on stream on before getting the EP */
+static int sd_init_transfer(struct gspca_dev *gspca_dev)
+{
+	/* Set bulk packet size */
+	gspca_dev->cam.bulk_size = gspca_dev
+				   ->cam.cam_mode[gspca_dev->curr_mode]
+				   .sizeimage + 2;
+	PDEBUG(D_CONF,
+	       "sd_init_transfer: setting bulk transfer buffer size to %d\n",
+	       gspca_dev->cam.bulk_size);
+	/* NOTE: DO NOT USE gspca_dev->frsz !
+	 * frame_alloc() in gspca.c calls PAGE_ALIGN(frsz)
+	 * which can increase this value !			 */
+	return 0;
+}
+
+static int set_videooutfmt(struct gspca_dev *gspca_dev)
+{
+	u8 value;
+	switch (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].pixelformat) {
+	case V4L2_PIX_FMT_SRGGB8:
+		value = 0x00;	/* RGB_8_RGRG */
+		break;
+	case V4L2_PIX_FMT_RGB565:
+		value = 0x04;	/* RGB_16_656 */
+		break;
+/*	case V4L2_PIX_FMT_YUV211:
+		value = 0x10;	// YUV211
+		break;				*/
+		/* TODO: add support to the kernel */
+	case V4L2_PIX_FMT_YUYV:
+		value = 0x14;	/* YUV422_Y0UY1V */
+		break;
+	default:
+		pr_err("error: invalid pixel format selected\n");
+		return -EINVAL;
+	}
+	return write_em27xx_single(gspca_dev, EM27XX_R27_OUTFMT, value);
+}
+
+static void set_resolution(struct gspca_dev *gspca_dev)
+{
+	if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width < 1600)
+		write_em27xx_single(gspca_dev, EM27XX_R0F_XCLK, 0x0b); /*24MHz*/
+	else
+		write_em27xx_single(gspca_dev, EM27XX_R0F_XCLK, 0x07); /*12MHz*/
+
+	if ((gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width == 320)
+	    || (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width == 640)) {
+
+		write_em27xx_single(gspca_dev, EM27XX_R28_XMIN, 0x1b);
+		write_em27xx_single(gspca_dev, EM27XX_R29_XMAX, 0x83);
+		write_em27xx_single(gspca_dev, EM27XX_R2A_YMIN, 0x13);
+		write_em27xx_single(gspca_dev, EM27XX_R2B_YMAX, 0x63);
+
+		write_em27xx_single(gspca_dev, EM27XX_R1C_HSTART, 0x00);
+		write_em27xx_single(gspca_dev, EM27XX_R1D_VSTART, 0x00);
+
+		write_em27xx_single(gspca_dev, EM27XX_R1E_CWIDTH, 0xa0);
+		write_em27xx_single(gspca_dev, EM27XX_R1F_CHEIGHT, 0x78);
+
+		write_em27xx_single(gspca_dev, EM27XX_R1B_OFLOW, 0x00);
+		write_em27xx_single(gspca_dev, EM27XX_R1B_OFLOW, 0x00);
+
+		if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width
+		     == 640)                                            {
+			write_em27xx_single(gspca_dev,
+					    EM27XX_R34_START_H, 0x28);
+			write_em27xx_single(gspca_dev,
+					    EM27XX_R35_START_V, 0x1e);
+		} else { /* 320x240 */
+			write_em27xx_single(gspca_dev,
+					    EM27XX_R34_START_H, 0x14);
+			write_em27xx_single(gspca_dev,
+					    EM27XX_R35_START_V, 0x0f);
+		}
+	} else if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width
+		    == 1280)                                           {
+		write_em27xx_single(gspca_dev, EM27XX_R28_XMIN, 0x6b);
+		write_em27xx_single(gspca_dev, EM27XX_R29_XMAX, 0xd3);
+		write_em27xx_single(gspca_dev, EM27XX_R2A_YMIN, 0x57);
+		write_em27xx_single(gspca_dev, EM27XX_R2B_YMAX, 0xa7);
+
+		write_em27xx_single(gspca_dev, EM27XX_R1C_HSTART, 0x00);
+		write_em27xx_single(gspca_dev, EM27XX_R1D_VSTART, 0x00);
+
+		write_em27xx_single(gspca_dev, EM27XX_R1E_CWIDTH, 0x40);
+		write_em27xx_single(gspca_dev, EM27XX_R1F_CHEIGHT, 0x00);
+
+		write_em27xx_single(gspca_dev, EM27XX_R1B_OFLOW, 0x01);
+		write_em27xx_single(gspca_dev, EM27XX_R1B_OFLOW, 0x03);
+
+		write_em27xx_single(gspca_dev, EM27XX_R34_START_H, 0x50);
+		write_em27xx_single(gspca_dev, EM27XX_R35_START_V, 0x40);
+	} else if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width
+		    == 1600)                                           {
+		write_em27xx_single(gspca_dev, EM27XX_R28_XMIN, 0x93);
+		write_em27xx_single(gspca_dev, EM27XX_R29_XMAX, 0xfb);
+		write_em27xx_single(gspca_dev, EM27XX_R2A_YMIN, 0x6d);
+		write_em27xx_single(gspca_dev, EM27XX_R2B_YMAX, 0xbd);
+
+		write_em27xx_single(gspca_dev, EM27XX_R1C_HSTART, 0x00);
+		write_em27xx_single(gspca_dev, EM27XX_R1D_VSTART, 0x00);
+
+		write_em27xx_single(gspca_dev, EM27XX_R1E_CWIDTH, 0x90);
+		write_em27xx_single(gspca_dev, EM27XX_R1F_CHEIGHT, 0x2c);
+
+		write_em27xx_single(gspca_dev, EM27XX_R1B_OFLOW, 0x03);
+		write_em27xx_single(gspca_dev, EM27XX_R1B_OFLOW, 0x03);
+
+		write_em27xx_single(gspca_dev, EM27XX_R34_START_H, 0x64);
+		write_em27xx_single(gspca_dev, EM27XX_R35_START_V, 0x4b);
+	} else {
+		PDEBUG(D_ERR,
+		       "em27xx_set_resolution: error: invalid resolution\n");
+	}
+
+	if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width < 640) {
+		write_em27xx_single(gspca_dev, EM27XX_R26_COMPR, 0x10);
+		write_em27xx_single(gspca_dev, EM27XX_R30_HSCALELOW, 0x00);
+		write_em27xx_single(gspca_dev, EM27XX_R31_HSCALEHIGH, 0x10);
+		write_em27xx_single(gspca_dev, EM27XX_R26_COMPR, 0x30);
+		write_em27xx_single(gspca_dev, EM27XX_R32_VSCALELOW, 0x00);
+		write_em27xx_single(gspca_dev, EM27XX_R33_VSCALEHIGH, 0x10);
+	}
+}
+
+static void ov2640_config(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* Select sensor register set */
+	if (write_propr_single(gspca_dev, sd->sensor_addr, 0xff, 0x01) < 0)
+		return;
+
+	/* COM1: dummy frames, vertical window start/end line control */
+	if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width < 1600)
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x03, 0x8f);
+	else
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x03, 0x4f);
+
+	if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width < 1280) {
+		/* Select SVGA mode */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x12, 0x40);
+		/* Configure Window */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x17, 0x11);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x18, 0x43);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x19, 0x00);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x1a, 0x4b);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x32, 0x09);
+		/* UNKNOWN/RESERVED */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x6d, 0x00);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x3d, 0x38);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x39, 0x12);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x35, 0xda);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x22, 0x1a);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x37, 0xc3);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x23, 0x00);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x34, 0xc0);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x36, 0x1a);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x06, 0x88);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x07, 0xc0);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x0d, 0x87);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x0e, 0x41);
+	} else {
+		/* Select UXGA (full size) mode */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x12, 0x00);
+		/* Configure Window */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x17, 0x11);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x18, 0x75);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x19, 0x01);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x1a, 0x97);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x32, 0x36);
+		/* UNKNOWN/RESERVED */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x6d, 0x80);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x3d, 0x34);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x39, 0x02);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x35, 0x88);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x22, 0x0a);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x37, 0x40);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x23, 0x00);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x34, 0xa0);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x36, 0x1a);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x06, 0x02);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x07, 0xc0);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x0d, 0xb7);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x0e, 0x01);
+	}
+	write_propr_single(gspca_dev, sd->sensor_addr, 0x4c, 0x00);
+
+	/* Select DSP register set */
+	if (write_propr_single(gspca_dev, sd->sensor_addr, 0xff, 0x00) < 0)
+		return;
+
+	/* CTRL2: module enable */
+	if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width == 1600)
+		/* enable SDE+UV_ADJ+UV_AVG+CMX */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x86, 0x1d);
+	else
+		/* enable DCW+SDE+UV_ADJ+UV_AVG+CMX */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x86, 0x3d);
+
+	if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width < 1280) {
+		/* CTRL3: enable BPC + WPC + UNKNOWN */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x87, 0xd5);
+		/* Image size */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0xc0, 0x64);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0xc1, 0x4b);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x8c, 0x00);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x50, 0x00);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x51, 0xc8);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x52, 0x96);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x53, 0x00);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x54, 0x00);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x55, 0x00);
+		/* Zoom settings */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x5a, 0xa0);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x5b, 0x78);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x5c, 0x00);
+	} else {
+		/* CTRL3: enable BPC + WPC + UNKNOWN */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x87, 0xd0);
+		/* Image size */
+		write_propr_single(gspca_dev, sd->sensor_addr, 0xc0, 0xc8);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0xc1, 0x96);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x8c, 0x00);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x50, 0x00);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x51, 0x90);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x52, 0x2c);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x53, 0x00);
+		write_propr_single(gspca_dev, sd->sensor_addr, 0x54, 0x00);
+		if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].width
+		     < 1600)                                            {
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x55, 0x88);
+			/* Zoom settings */
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x5a, 0x40);
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x5b, 0x00);
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x5c, 0x05);
+		} else {
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x55, 0xc8);
+			/* Zoom settings */
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x5a, 0x90);
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x5b, 0x2c);
+			write_propr_single(gspca_dev, sd->sensor_addr,
+					   0x5c, 0x05);
+		}
+	}
+	/* DVP speed control */
+	write_propr_single(gspca_dev, sd->sensor_addr, 0xd3, 0x82);
+	/* Disable reset mode */
+	write_propr_single(gspca_dev, sd->sensor_addr, 0xe0, 0x00);
+
+	set_powerlinefreqfilter(gspca_dev, v4l2_ctrl_g_ctrl(sd->powerlinefreq));
+}
+
+/* called on stream on after URBs creation */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	int ret, i;
+	u8 reg, val;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	PDEBUG(D_STREAM, "sd_start: starting streaming\n");
+
+	write_em27xx_single(gspca_dev, EM27XX_R0F_XCLK, 0x08);      /* 20MHz */
+	write_em27xx_single(gspca_dev, EM27XX_R26_COMPR, 0x00);
+	write_em27xx_single(gspca_dev, EM27XX_R13, 0x08);
+
+	set_videooutfmt(gspca_dev);
+	set_resolution(gspca_dev);
+
+	if (sd->sensor == SENSOR_OV2640) {
+		/* Basic sensor setup (constant register settings) */
+		/* NOTE: in theory, we could already do this in sd_init but
+		 * that causes strong exposure/gain fluctuations during the
+		 * first 1-2 seconds !
+		 * It seems that the sensor needs to be reset each time and
+		 * this is also what the windows driver does...            */
+		for (i = 0; i < ARRAY_SIZE(ov2640_init); i++) {
+			reg = ov2640_init[i][0];
+			val = ov2640_init[i][1];
+			ret = write_propr_single(gspca_dev, sd->sensor_addr,
+						 reg, val);
+			if (ret < 0) {
+				pr_err("error: sensor initialization failed: reg %02x\n",
+				       reg);
+				if (reg == 0xff)
+					break;
+				/* NOTE: reg 0xff = register set selection
+				  * => too risky to continue on error ! */
+			}
+		}
+		/* Variable sensor settings */
+		ov2640_config(gspca_dev);
+	} else
+		return -ENODEV;
+
+	write_em27xx_single(gspca_dev, EM27XX_R15_RGAIN, 0x20);
+	write_em27xx_single(gspca_dev, EM27XX_R16_GGAIN, 0x20);
+	write_em27xx_single(gspca_dev, EM27XX_R17_BGAIN, 0x20);
+	write_em27xx_single(gspca_dev, EM27XX_R18_ROFFSET, 0x00);
+	write_em27xx_single(gspca_dev, EM27XX_R19_GOFFSET, 0x00);
+	write_em27xx_single(gspca_dev, EM27XX_R1A_BOFFSET, 0x00);
+	write_em27xx_single(gspca_dev, EM27XX_R23_UOFFSET, 0x00);
+	write_em27xx_single(gspca_dev, EM27XX_R24_VOFFSET, 0x00);
+	set_brightness(gspca_dev, v4l2_ctrl_g_ctrl(sd->brightness));
+	set_contrast(gspca_dev, v4l2_ctrl_g_ctrl(sd->contrast));
+	set_saturation(gspca_dev, v4l2_ctrl_g_ctrl(sd->saturation));
+	write_em27xx_single(gspca_dev, EM27XX_R14_GAMMA, 0x20);
+	set_sharpness(gspca_dev, v4l2_ctrl_g_ctrl(sd->sharpness));
+
+	write_em27xx_single(gspca_dev, EM27XX_R10_VINMODE, 0x08);
+	write_em27xx_single(gspca_dev, EM27XX_R11_VINCTRL, 0x00);
+	write_em27xx_single(gspca_dev, EM27XX_R12_VINENABLE, 0x67);
+
+	write_em27xx_single(gspca_dev, EM27XX_R0C_USBSUSP, 0x10);
+
+	read_em27xx(gspca_dev, EM27XX_R84_GPIO_1_R, &val, 1);
+	val &= ~EM27XX_GPIO_1_LED_STREAM;			/* LED ON */
+	write_em27xx_single(gspca_dev, EM27XX_R80_GPIO_1_W, val);
+
+	return 0;
+}
+
+static void sd_stop(struct gspca_dev *gspca_dev)
+{
+	u8 value;
+
+	PDEBUG(D_STREAM, "sd_stop: stopping streaming\n");
+	unmute(gspca_dev);
+
+	write_em27xx_single(gspca_dev, EM27XX_R12_VINENABLE, 0x27);
+	write_em27xx_single(gspca_dev, EM27XX_R0C_USBSUSP, 0x00);
+
+	read_em27xx(gspca_dev, EM27XX_R84_GPIO_1_R, &value, 1);
+	value |= EM27XX_GPIO_1_LED_STREAM;	/* turn off LED */
+	write_em27xx_single(gspca_dev, EM27XX_R80_GPIO_1_W, value);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev, u8 *data, int len)
+{
+	u8 header[2];
+	u32 img_size;
+
+	header[0] = data[0];
+	header[1] = data[1];
+	if (header[0] != 0x02) {
+		PDEBUG(D_PACK,
+		       "sd_pkt_scan: invalid packet header, ignoring packet\n");
+		/* NOTE: should we already discard the packet here ? */
+		return;
+	}
+
+	img_size = gspca_dev->image_len;
+
+	if ((gspca_dev->last_packet_type == LAST_PACKET)
+	    || (gspca_dev->last_packet_type == DISCARD_PACKET)) {
+		PDEBUG(D_PACK | D_FRAM, "sd_pkt_scan: setting up new frame\n");
+		gspca_frame_add(gspca_dev, FIRST_PACKET, data + 2, len - 2);
+	} else {
+		PDEBUG(D_PACK | D_FRAM,
+		       "sd_pkt_scan: adding packet to current frame\n");
+		gspca_frame_add(gspca_dev, INTER_PACKET, data + 2, len - 2);
+	}
+
+	if (header[1] & BULK_HEADER_FRAME_END) {
+		PDEBUG(D_PACK,
+		       "sd_pkt_scan: packet header indicates frame end\n");
+		/* NOTE: DO NOT CHECK gspca_dev->frsz !
+		 * frame_alloc() in gspca.c calls PAGE_ALIGN(frsz)
+		 * which can increase this value !			 */
+		if (img_size + (len - 2)
+		    != gspca_dev->cam.cam_mode[gspca_dev->curr_mode]
+		       .sizeimage) {
+			PDEBUG(D_PACK | D_FRAM,
+			       "sd_pkt_scan: discarding frame due to invalid "
+			       "frame size\n");
+			gspca_frame_add(gspca_dev, DISCARD_PACKET, NULL, 0);
+			gspca_dev->image_len = 0;
+		} else {
+			gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+			PDEBUG(D_FRAM, "sd_pkt_scan: frame complete\n");
+		}
+	}
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int sd_dbg_get_register(struct gspca_dev *gspca_dev,
+			struct v4l2_dbg_register *reg)
+{
+	u8 val;
+	struct sd *sd = (struct sd *) gspca_dev;
+	switch (reg->match.type) {
+	case V4L2_CHIP_MATCH_HOST:
+		if (reg->match.addr != 0)
+			return -ENXIO;
+		if (reg->reg > 0xffff)
+			return -EINVAL;
+		if (read_em27xx(gspca_dev, reg->reg, &val, 1) < 0)
+			return -EIO;
+		reg->val = val;
+		reg->size = 1;
+		return 0;
+	case V4L2_CHIP_MATCH_I2C_ADDR:
+		if (reg->match.addr == sd->sensor_addr) {
+			if (reg->reg > 0xff)
+				return -EINVAL;
+			if (read_propr(gspca_dev, reg->match.addr,
+				       reg->reg, &val, 1)          < 0)
+				return -EIO;
+		} else if (sd->eeprom_addr
+			   && (reg->match.addr == sd->eeprom_addr)) {
+			if (reg->reg > 0xffff)
+				return -EINVAL;
+			if (read_i2c(gspca_dev, reg->match.addr,
+				      reg->reg, &val, 1)         < 0)
+				return -EIO;
+		} else {
+			return -ENXIO;
+		}
+		reg->val = val;
+		reg->size = 1;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int sd_dbg_set_register(struct gspca_dev *gspca_dev,
+			       struct v4l2_dbg_register *reg)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	switch (reg->match.type) {
+	case V4L2_CHIP_MATCH_HOST:
+		if (reg->match.addr != 0)
+			return -ENXIO;
+		if (reg->reg > 0xffff)
+			return -EINVAL;
+		if (reg->val > 0xff)
+			return -EINVAL;
+		if (write_em27xx_single(gspca_dev, reg->reg, reg->val) < 0)
+			return -EIO;
+		return 0;
+	case V4L2_CHIP_MATCH_I2C_ADDR:
+		if (reg->match.addr == sd->sensor_addr) {
+			if (reg->reg > 0xff)
+				return -EINVAL;
+			if (reg->val > 0xff)
+				return -EINVAL;
+			if (write_propr_single(gspca_dev, sd->sensor_addr,
+					       reg->reg, reg->val)         < 0)
+				return -EIO;
+		} else if (sd->eeprom_addr
+			   && (reg->match.addr == sd->eeprom_addr)) {
+			return -EACCES;
+		} else {
+			return -ENXIO;
+		}
+		return 0;
+	}
+	return -EINVAL;
+}
+#endif
+
+static int sd_get_chip_ident(struct gspca_dev *gspca_dev,
+			     struct v4l2_dbg_chip_ident *chip)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (chip->match.type) {
+	case V4L2_CHIP_MATCH_HOST:
+		if (chip->match.addr != 0)
+			return -EINVAL;
+		chip->revision = 0;
+		chip->ident = 0; /* V4L2_IDENT_EM2765; */	/* FIXME: ADD TO KERNEL */
+		return 0;
+	case V4L2_CHIP_MATCH_I2C_ADDR:
+		if (chip->match.addr != sd->sensor_addr)
+			return -EINVAL;
+		chip->revision = 0;
+		chip->ident = sensor_ident[sd->sensor];
+		return 0;
+	}
+	return -EINVAL;
+}
+
+/* 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,
+	.isoc_init = sd_init_transfer,
+	.stopN     = sd_stop,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.set_register = sd_dbg_set_register,
+	.get_register = sd_dbg_get_register,
+#endif
+	.get_chip_ident = sd_get_chip_ident,
+#if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE)
+	.other_input = 1,
+#endif
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x1ae7, 0x9003)},
+	{USB_DEVICE(0x1ae7, 0x9004)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	if (intf->cur_altsetting->desc.bInterfaceNumber != 3)
+		return -ENODEV;
+	/* NOTE: devices 1ae7:9003 and 1ae7:9004 have 3 interfaces:
+	 *       0, 1: audio; 2: [missing]; 3: video (bulk)           */
+	return gspca_dev_probe2(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;
+
+	cancel_delayed_work_sync(&sd->gpio_query_work);
+	cancel_delayed_work_sync(&sd->led_blink_work);
+
+	sd->muted = false;
+	sd->illuminated = false;
+
+	sd->mutebutton_locked = false;
+	sd->lightbutton_locked = false;
+
+	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
+};
+
+module_usb_driver(sd_driver);
