@@ -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
@@ -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
new file mode 100644
@@ -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);