[media] gspca: Add new se401 camera driver
authorHans de Goede <hdegoede@redhat.com>
Sun, 5 Jun 2011 10:44:34 +0000 (07:44 -0300)
committerMauro Carvalho Chehab <mchehab@redhat.com>
Wed, 27 Jul 2011 20:53:41 +0000 (17:53 -0300)
Based on the old v4l1 camera by Jeroen Vreeken driver which recently got
removed from the kernel.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
drivers/media/video/gspca/Kconfig
drivers/media/video/gspca/Makefile
drivers/media/video/gspca/se401.c [new file with mode: 0644]
drivers/media/video/gspca/se401.h [new file with mode: 0644]

index 34ae2c299799c350f255e0988bc4325d4dedf124..43d9a20caebc3f6439693e0833530e247c4b94ee 100644 (file)
@@ -179,6 +179,16 @@ config USB_GSPCA_PAC7311
          To compile this driver as a module, choose M here: the
          module will be called gspca_pac7311.
 
+config USB_GSPCA_SE401
+       tristate "SE401 USB Camera Driver"
+       depends on VIDEO_V4L2 && USB_GSPCA
+       help
+        Say Y here if you want support for cameras based on the
+        Endpoints (formerly known as AOX) se401 chip.
+
+        To compile this driver as a module, choose M here: the
+        module will be called gspca_se401.
+
 config USB_GSPCA_SN9C2028
        tristate "SONIX Dual-Mode USB Camera Driver"
        depends on VIDEO_V4L2 && USB_GSPCA
index 802fbe1bff4a009f2787f749771f4e57ce9ccda9..d6364a86333ab8382d5ab11f0f360087a927ad39 100644 (file)
@@ -16,6 +16,7 @@ obj-$(CONFIG_USB_GSPCA_OV534_9)  += gspca_ov534_9.o
 obj-$(CONFIG_USB_GSPCA_PAC207)   += gspca_pac207.o
 obj-$(CONFIG_USB_GSPCA_PAC7302)  += gspca_pac7302.o
 obj-$(CONFIG_USB_GSPCA_PAC7311)  += gspca_pac7311.o
+obj-$(CONFIG_USB_GSPCA_SE401)    += gspca_se401.o
 obj-$(CONFIG_USB_GSPCA_SN9C2028) += gspca_sn9c2028.o
 obj-$(CONFIG_USB_GSPCA_SN9C20X)  += gspca_sn9c20x.o
 obj-$(CONFIG_USB_GSPCA_SONIXB)   += gspca_sonixb.o
@@ -58,6 +59,7 @@ gspca_ov534_9-objs  := ov534_9.o
 gspca_pac207-objs   := pac207.o
 gspca_pac7302-objs  := pac7302.o
 gspca_pac7311-objs  := pac7311.o
+gspca_se401-objs    := se401.o
 gspca_sn9c2028-objs := sn9c2028.o
 gspca_sn9c20x-objs  := sn9c20x.o
 gspca_sonixb-objs   := sonixb.o
diff --git a/drivers/media/video/gspca/se401.c b/drivers/media/video/gspca/se401.c
new file mode 100644 (file)
index 0000000..4c283c2
--- /dev/null
@@ -0,0 +1,774 @@
+/*
+ * GSPCA Endpoints (formerly known as AOX) se401 USB Camera sub Driver
+ *
+ * Copyright (C) 2011 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Based on the v4l1 se401 driver which is:
+ *
+ * Copyright (c) 2000 Jeroen B. Vreeken (pe1rxq@amsat.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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 MODULE_NAME "se401"
+
+#define BULK_SIZE 4096
+#define PACKET_SIZE 1024
+#define READ_REQ_SIZE 64
+#define MAX_MODES ((READ_REQ_SIZE - 6) / 4)
+/* The se401 compression algorithm uses a fixed quant factor, which
+   can be configured by setting the high nibble of the SE401_OPERATINGMODE
+   feature. This needs to exactly match what is in libv4l! */
+#define SE401_QUANT_FACT 8
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include "gspca.h"
+#include "se401.h"
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("Endpoints se401");
+MODULE_LICENSE("GPL");
+
+/* controls */
+enum e_ctrl {
+       BRIGHTNESS,
+       GAIN,
+       EXPOSURE,
+       FREQ,
+       NCTRL   /* number of controls */
+};
+
+/* exposure change state machine states */
+enum {
+       EXPO_CHANGED,
+       EXPO_DROP_FRAME,
+       EXPO_NO_CHANGE,
+};
+
+/* specific webcam descriptor */
+struct sd {
+       struct gspca_dev gspca_dev;     /* !! must be the first item */
+       struct gspca_ctrl ctrls[NCTRL];
+       struct v4l2_pix_format fmts[MAX_MODES];
+       int pixels_read;
+       int packet_read;
+       u8 packet[PACKET_SIZE];
+       u8 restart_stream;
+       u8 button_state;
+       u8 resetlevel;
+       u8 resetlevel_frame_count;
+       int resetlevel_adjust_dir;
+       int expo_change_state;
+};
+
+static void setbrightness(struct gspca_dev *gspca_dev);
+static void setgain(struct gspca_dev *gspca_dev);
+static void setexposure(struct gspca_dev *gspca_dev);
+
+static const struct ctrl sd_ctrls[NCTRL] = {
+[BRIGHTNESS] = {
+               {
+                       .id      = V4L2_CID_BRIGHTNESS,
+                       .type    = V4L2_CTRL_TYPE_INTEGER,
+                       .name    = "Brightness",
+                       .minimum = 0,
+                       .maximum = 255,
+                       .step    = 1,
+                       .default_value = 15,
+               },
+               .set_control = setbrightness
+       },
+[GAIN] = {
+               {
+                       .id      = V4L2_CID_GAIN,
+                       .type    = V4L2_CTRL_TYPE_INTEGER,
+                       .name    = "Gain",
+                       .minimum = 0,
+                       .maximum = 50, /* Really 63 but > 50 is not pretty */
+                       .step    = 1,
+                       .default_value = 25,
+               },
+               .set_control = setgain
+       },
+[EXPOSURE] = {
+               {
+                       .id = V4L2_CID_EXPOSURE,
+                       .type = V4L2_CTRL_TYPE_INTEGER,
+                       .name = "Exposure",
+                       .minimum = 0,
+                       .maximum = 32767,
+                       .step = 1,
+                       .default_value = 15000,
+               },
+               .set_control = setexposure
+       },
+[FREQ] = {
+               {
+                       .id      = V4L2_CID_POWER_LINE_FREQUENCY,
+                       .type    = V4L2_CTRL_TYPE_MENU,
+                       .name    = "Light frequency filter",
+                       .minimum = 0,
+                       .maximum = 2,
+                       .step    = 1,
+                       .default_value = 0,
+               },
+               .set_control = setexposure
+       },
+};
+
+static void se401_write_req(struct gspca_dev *gspca_dev, u16 req, u16 value,
+                           int silent)
+{
+       int err;
+
+       if (gspca_dev->usb_err < 0)
+               return;
+
+       err = usb_control_msg(gspca_dev->dev,
+                             usb_sndctrlpipe(gspca_dev->dev, 0), req,
+                             USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+                             value, 0, NULL, 0, 1000);
+       if (err < 0) {
+               if (!silent)
+                       err("write req failed req %#04x val %#04x error %d",
+                           req, value, err);
+               gspca_dev->usb_err = err;
+       }
+}
+
+static void se401_read_req(struct gspca_dev *gspca_dev, u16 req, int silent)
+{
+       int err;
+
+       if (gspca_dev->usb_err < 0)
+               return;
+
+       if (USB_BUF_SZ < READ_REQ_SIZE) {
+               err("USB_BUF_SZ too small!!");
+               gspca_dev->usb_err = -ENOBUFS;
+               return;
+       }
+
+       err = usb_control_msg(gspca_dev->dev,
+                             usb_rcvctrlpipe(gspca_dev->dev, 0), req,
+                             USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+                             0, 0, gspca_dev->usb_buf, READ_REQ_SIZE, 1000);
+       if (err < 0) {
+               if (!silent)
+                       err("read req failed req %#04x error %d", req, err);
+               gspca_dev->usb_err = err;
+       }
+}
+
+static void se401_set_feature(struct gspca_dev *gspca_dev,
+                             u16 selector, u16 param)
+{
+       int err;
+
+       if (gspca_dev->usb_err < 0)
+               return;
+
+       err = usb_control_msg(gspca_dev->dev,
+                             usb_sndctrlpipe(gspca_dev->dev, 0),
+                             SE401_REQ_SET_EXT_FEATURE,
+                             USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+                             param, selector, NULL, 0, 1000);
+       if (err < 0) {
+               err("set feature failed sel %#04x param %#04x error %d",
+                   selector, param, err);
+               gspca_dev->usb_err = err;
+       }
+}
+
+static int se401_get_feature(struct gspca_dev *gspca_dev, u16 selector)
+{
+       int err;
+
+       if (gspca_dev->usb_err < 0)
+               return gspca_dev->usb_err;
+
+       if (USB_BUF_SZ < 2) {
+               err("USB_BUF_SZ too small!!");
+               gspca_dev->usb_err = -ENOBUFS;
+               return gspca_dev->usb_err;
+       }
+
+       err = usb_control_msg(gspca_dev->dev,
+                             usb_rcvctrlpipe(gspca_dev->dev, 0),
+                             SE401_REQ_GET_EXT_FEATURE,
+                             USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+                             0, selector, gspca_dev->usb_buf, 2, 1000);
+       if (err < 0) {
+               err("get feature failed sel %#04x error %d", selector, err);
+               gspca_dev->usb_err = err;
+               return err;
+       }
+       return gspca_dev->usb_buf[0] | (gspca_dev->usb_buf[1] << 8);
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev)
+{
+       struct sd *sd = (struct sd *) gspca_dev;
+
+       if (gspca_dev->ctrl_dis & (1 << BRIGHTNESS))
+               return;
+
+       /* HDG: this does not seem to do anything on my cam */
+       se401_write_req(gspca_dev, SE401_REQ_SET_BRT,
+                       sd->ctrls[BRIGHTNESS].val, 0);
+}
+
+static void setgain(struct gspca_dev *gspca_dev)
+{
+       struct sd *sd = (struct sd *) gspca_dev;
+       u16 gain = 63 - sd->ctrls[GAIN].val;
+
+       /* red color gain */
+       se401_set_feature(gspca_dev, HV7131_REG_ARCG, gain);
+       /* green color gain */
+       se401_set_feature(gspca_dev, HV7131_REG_AGCG, gain);
+       /* blue color gain */
+       se401_set_feature(gspca_dev, HV7131_REG_ABCG, gain);
+}
+
+static void setexposure(struct gspca_dev *gspca_dev)
+{
+       struct sd *sd = (struct sd *) gspca_dev;
+       int integration = sd->ctrls[EXPOSURE].val << 6;
+       u8 expose_h, expose_m, expose_l;
+
+       /* Do this before the set_feature calls, for proper timing wrt
+          the interrupt driven pkt_scan. Note we may still race but that
+          is not a big issue, the expo change state machine is merely for
+          avoiding underexposed frames getting send out, if one sneaks
+          through so be it */
+       sd->expo_change_state = EXPO_CHANGED;
+
+       if (sd->ctrls[FREQ].val == V4L2_CID_POWER_LINE_FREQUENCY_50HZ)
+               integration = integration - integration % 106667;
+       if (sd->ctrls[FREQ].val == V4L2_CID_POWER_LINE_FREQUENCY_60HZ)
+               integration = integration - integration % 88889;
+
+       expose_h = (integration >> 16);
+       expose_m = (integration >> 8);
+       expose_l = integration;
+
+       /* integration time low */
+       se401_set_feature(gspca_dev, HV7131_REG_TITL, expose_l);
+       /* integration time mid */
+       se401_set_feature(gspca_dev, HV7131_REG_TITM, expose_m);
+       /* integration time high */
+       se401_set_feature(gspca_dev, HV7131_REG_TITU, expose_h);
+}
+
+static int sd_config(struct gspca_dev *gspca_dev,
+                       const struct usb_device_id *id)
+{
+       struct sd *sd = (struct sd *)gspca_dev;
+       struct cam *cam = &gspca_dev->cam;
+       u8 *cd = gspca_dev->usb_buf;
+       int i, j, n;
+       int widths[MAX_MODES], heights[MAX_MODES];
+
+       /* Read the camera descriptor */
+       se401_read_req(gspca_dev, SE401_REQ_GET_CAMERA_DESCRIPTOR, 1);
+       if (gspca_dev->usb_err) {
+               /* Sometimes after being idle for a while the se401 won't
+                  respond and needs a good kicking  */
+               usb_reset_device(gspca_dev->dev);
+               gspca_dev->usb_err = 0;
+               se401_read_req(gspca_dev, SE401_REQ_GET_CAMERA_DESCRIPTOR, 0);
+       }
+
+       /* Some cameras start with their LED on */
+       se401_write_req(gspca_dev, SE401_REQ_LED_CONTROL, 0, 0);
+       if (gspca_dev->usb_err)
+               return gspca_dev->usb_err;
+
+       if (cd[1] != 0x41) {
+               err("Wrong descriptor type");
+               return -ENODEV;
+       }
+
+       if (!(cd[2] & SE401_FORMAT_BAYER)) {
+               err("Bayer format not supported!");
+               return -ENODEV;
+       }
+
+       if (cd[3])
+               info("ExtraFeatures: %d", cd[3]);
+
+       n = cd[4] | (cd[5] << 8);
+       if (n > MAX_MODES) {
+               err("Too many frame sizes");
+               return -ENODEV;
+       }
+
+       for (i = 0; i < n ; i++) {
+               widths[i] = cd[6 + i * 4 + 0] | (cd[6 + i * 4 + 1] << 8);
+               heights[i] = cd[6 + i * 4 + 2] | (cd[6 + i * 4 + 3] << 8);
+       }
+
+       for (i = 0; i < n ; i++) {
+               sd->fmts[i].width = widths[i];
+               sd->fmts[i].height = heights[i];
+               sd->fmts[i].field = V4L2_FIELD_NONE;
+               sd->fmts[i].colorspace = V4L2_COLORSPACE_SRGB;
+               sd->fmts[i].priv = 1;
+
+               /* janggu compression only works for 1/4th or 1/16th res */
+               for (j = 0; j < n; j++) {
+                       if (widths[j] / 2 == widths[i] &&
+                           heights[j] / 2 == heights[i]) {
+                               sd->fmts[i].priv = 2;
+                               break;
+                       }
+               }
+               /* 1/16th if available too is better then 1/4th, because
+                  we then use a larger area of the sensor */
+               for (j = 0; j < n; j++) {
+                       if (widths[j] / 4 == widths[i] &&
+                           heights[j] / 4 == heights[i]) {
+                               sd->fmts[i].priv = 4;
+                               break;
+                       }
+               }
+
+               if (sd->fmts[i].priv == 1) {
+                       /* Not a 1/4th or 1/16th res, use bayer */
+                       sd->fmts[i].pixelformat = V4L2_PIX_FMT_SBGGR8;
+                       sd->fmts[i].bytesperline = widths[i];
+                       sd->fmts[i].sizeimage = widths[i] * heights[i];
+                       info("Frame size: %dx%d bayer", widths[i], heights[i]);
+               } else {
+                       /* Found a match use janggu compression */
+                       sd->fmts[i].pixelformat = V4L2_PIX_FMT_SE401;
+                       sd->fmts[i].bytesperline = 0;
+                       sd->fmts[i].sizeimage = widths[i] * heights[i] * 3;
+                       info("Frame size: %dx%d 1/%dth janggu",
+                            widths[i], heights[i],
+                            sd->fmts[i].priv * sd->fmts[i].priv);
+               }
+       }
+
+       cam->cam_mode = sd->fmts;
+       cam->nmodes = n;
+       cam->bulk = 1;
+       cam->bulk_size = BULK_SIZE;
+       cam->bulk_nurbs = 4;
+       cam->ctrls = sd->ctrls;
+       gspca_dev->nbalt = 1;  /* Ignore the bogus isoc alt settings */
+       sd->resetlevel = 0x2d; /* Set initial resetlevel */
+
+       /* See if the camera supports brightness */
+       se401_read_req(gspca_dev, SE401_REQ_GET_BRT, 1);
+       if (gspca_dev->usb_err) {
+               gspca_dev->ctrl_dis = (1 << BRIGHTNESS);
+               gspca_dev->usb_err = 0;
+       }
+
+       return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+       return 0;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+       struct sd *sd = (struct sd *)gspca_dev;
+       int mult = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+       int mode = 0;
+
+       se401_write_req(gspca_dev, SE401_REQ_CAMERA_POWER, 1, 1);
+       if (gspca_dev->usb_err) {
+               /* Sometimes after being idle for a while the se401 won't
+                  respond and needs a good kicking  */
+               usb_reset_device(gspca_dev->dev);
+               gspca_dev->usb_err = 0;
+               se401_write_req(gspca_dev, SE401_REQ_CAMERA_POWER, 1, 0);
+       }
+       se401_write_req(gspca_dev, SE401_REQ_LED_CONTROL, 1, 0);
+
+       se401_set_feature(gspca_dev, HV7131_REG_MODE_B, 0x05);
+
+       /* set size + mode */
+       se401_write_req(gspca_dev, SE401_REQ_SET_WIDTH,
+                       gspca_dev->width * mult, 0);
+       se401_write_req(gspca_dev, SE401_REQ_SET_HEIGHT,
+                       gspca_dev->height * mult, 0);
+       /*
+        * HDG: disabled this as it does not seem to do anything
+        * se401_write_req(gspca_dev, SE401_REQ_SET_OUTPUT_MODE,
+        *                 SE401_FORMAT_BAYER, 0);
+        */
+
+       switch (mult) {
+       case 1: /* Raw bayer */
+               mode = 0x03; break;
+       case 2: /* 1/4th janggu */
+               mode = SE401_QUANT_FACT << 4; break;
+       case 4: /* 1/16th janggu */
+               mode = (SE401_QUANT_FACT << 4) | 0x02; break;
+       }
+       se401_set_feature(gspca_dev, SE401_OPERATINGMODE, mode);
+
+       setbrightness(gspca_dev);
+       setgain(gspca_dev);
+       setexposure(gspca_dev);
+       se401_set_feature(gspca_dev, HV7131_REG_ARLV, sd->resetlevel);
+
+       sd->packet_read = 0;
+       sd->pixels_read = 0;
+       sd->restart_stream = 0;
+       sd->resetlevel_frame_count = 0;
+       sd->resetlevel_adjust_dir = 0;
+       sd->expo_change_state = EXPO_NO_CHANGE;
+
+       se401_write_req(gspca_dev, SE401_REQ_START_CONTINUOUS_CAPTURE, 0, 0);
+
+       return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+       se401_write_req(gspca_dev, SE401_REQ_STOP_CONTINUOUS_CAPTURE, 0, 0);
+       se401_write_req(gspca_dev, SE401_REQ_LED_CONTROL, 0, 0);
+       se401_write_req(gspca_dev, SE401_REQ_CAMERA_POWER, 0, 0);
+}
+
+static void sd_dq_callback(struct gspca_dev *gspca_dev)
+{
+       struct sd *sd = (struct sd *)gspca_dev;
+       unsigned int ahrc, alrc;
+       int oldreset, adjust_dir;
+
+       /* Restart the stream if requested do so by pkt_scan */
+       if (sd->restart_stream) {
+               sd_stopN(gspca_dev);
+               sd_start(gspca_dev);
+               sd->restart_stream = 0;
+       }
+
+       /* Automatically adjust sensor reset level
+          Hyundai have some really nice docs about this and other sensor
+          related stuff on their homepage: www.hei.co.kr */
+       sd->resetlevel_frame_count++;
+       if (sd->resetlevel_frame_count < 20)
+               return;
+
+       /* For some reason this normally read-only register doesn't get reset
+          to zero after reading them just once... */
+       se401_get_feature(gspca_dev, HV7131_REG_HIREFNOH);
+       se401_get_feature(gspca_dev, HV7131_REG_HIREFNOL);
+       se401_get_feature(gspca_dev, HV7131_REG_LOREFNOH);
+       se401_get_feature(gspca_dev, HV7131_REG_LOREFNOL);
+       ahrc = 256*se401_get_feature(gspca_dev, HV7131_REG_HIREFNOH) +
+           se401_get_feature(gspca_dev, HV7131_REG_HIREFNOL);
+       alrc = 256*se401_get_feature(gspca_dev, HV7131_REG_LOREFNOH) +
+           se401_get_feature(gspca_dev, HV7131_REG_LOREFNOL);
+
+       /* Not an exact science, but it seems to work pretty well... */
+       oldreset = sd->resetlevel;
+       if (alrc > 10) {
+               while (alrc >= 10 && sd->resetlevel < 63) {
+                       sd->resetlevel++;
+                       alrc /= 2;
+               }
+       } else if (ahrc > 20) {
+               while (ahrc >= 20 && sd->resetlevel > 0) {
+                       sd->resetlevel--;
+                       ahrc /= 2;
+               }
+       }
+       /* Detect ping-pong-ing and halve adjustment to avoid overshoot */
+       if (sd->resetlevel > oldreset)
+               adjust_dir = 1;
+       else
+               adjust_dir = -1;
+       if (sd->resetlevel_adjust_dir &&
+           sd->resetlevel_adjust_dir != adjust_dir)
+               sd->resetlevel = oldreset + (sd->resetlevel - oldreset) / 2;
+
+       if (sd->resetlevel != oldreset) {
+               sd->resetlevel_adjust_dir = adjust_dir;
+               se401_set_feature(gspca_dev, HV7131_REG_ARLV, sd->resetlevel);
+       }
+
+       sd->resetlevel_frame_count = 0;
+}
+
+static void sd_complete_frame(struct gspca_dev *gspca_dev, u8 *data, int len)
+{
+       struct sd *sd = (struct sd *)gspca_dev;
+
+       switch (sd->expo_change_state) {
+       case EXPO_CHANGED:
+               /* The exposure was changed while this frame
+                  was being send, so this frame is ok */
+               sd->expo_change_state = EXPO_DROP_FRAME;
+               break;
+       case EXPO_DROP_FRAME:
+               /* The exposure was changed while this frame
+                  was being captured, drop it! */
+               gspca_dev->last_packet_type = DISCARD_PACKET;
+               sd->expo_change_state = EXPO_NO_CHANGE;
+               break;
+       case EXPO_NO_CHANGE:
+               break;
+       }
+       gspca_frame_add(gspca_dev, LAST_PACKET, data, len);
+}
+
+static void sd_pkt_scan_janggu(struct gspca_dev *gspca_dev, u8 *data, int len)
+{
+       struct sd *sd = (struct sd *)gspca_dev;
+       int imagesize = gspca_dev->width * gspca_dev->height;
+       int i, plen, bits, pixels, info, count;
+
+       if (sd->restart_stream)
+               return;
+
+       /* Sometimes a 1024 bytes garbage bulk packet is send between frames */
+       if (gspca_dev->last_packet_type == LAST_PACKET && len == 1024) {
+               gspca_dev->last_packet_type = DISCARD_PACKET;
+               return;
+       }
+
+       i = 0;
+       while (i < len) {
+               /* Read header if not already be present from prev bulk pkt */
+               if (sd->packet_read < 4) {
+                       count = 4 - sd->packet_read;
+                       if (count > len - i)
+                               count = len - i;
+                       memcpy(&sd->packet[sd->packet_read], &data[i], count);
+                       sd->packet_read += count;
+                       i += count;
+                       if (sd->packet_read < 4)
+                               break;
+               }
+               bits   = sd->packet[3] + (sd->packet[2] << 8);
+               pixels = sd->packet[1] + ((sd->packet[0] & 0x3f) << 8);
+               info   = (sd->packet[0] & 0xc0) >> 6;
+               plen   = ((bits + 47) >> 4) << 1;
+               /* Sanity checks */
+               if (plen > 1024) {
+                       err("invalid packet len %d restarting stream", plen);
+                       goto error;
+               }
+               if (info == 3) {
+                       err("unknown frame info value restarting stream");
+                       goto error;
+               }
+
+               /* Read (remainder of) packet contents */
+               count = plen - sd->packet_read;
+               if (count > len - i)
+                       count = len - i;
+               memcpy(&sd->packet[sd->packet_read], &data[i], count);
+               sd->packet_read += count;
+               i += count;
+               if (sd->packet_read < plen)
+                       break;
+
+               sd->pixels_read += pixels;
+               sd->packet_read = 0;
+
+               switch (info) {
+               case 0: /* Frame data */
+                       gspca_frame_add(gspca_dev, INTER_PACKET, sd->packet,
+                                       plen);
+                       break;
+               case 1: /* EOF */
+                       if (sd->pixels_read != imagesize) {
+                               err("frame size %d expected %d",
+                                   sd->pixels_read, imagesize);
+                               goto error;
+                       }
+                       sd_complete_frame(gspca_dev, sd->packet, plen);
+                       return; /* Discard the rest of the bulk packet !! */
+               case 2: /* SOF */
+                       gspca_frame_add(gspca_dev, FIRST_PACKET, sd->packet,
+                                       plen);
+                       sd->pixels_read = pixels;
+                       break;
+               }
+       }
+       return;
+
+error:
+       sd->restart_stream = 1;
+       /* Give userspace a 0 bytes frame, so our dq callback gets
+          called and it can restart the stream */
+       gspca_frame_add(gspca_dev, FIRST_PACKET, NULL, 0);
+       gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+}
+
+static void sd_pkt_scan_bayer(struct gspca_dev *gspca_dev, u8 *data, int len)
+{
+       struct cam *cam = &gspca_dev->cam;
+       int imagesize = cam->cam_mode[gspca_dev->curr_mode].sizeimage;
+
+       if (gspca_dev->image_len == 0) {
+               gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+               return;
+       }
+
+       if (gspca_dev->image_len + len >= imagesize) {
+               sd_complete_frame(gspca_dev, data, len);
+               return;
+       }
+
+       gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev, u8 *data, int len)
+{
+       int mult = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+
+       if (len == 0)
+               return;
+
+       if (mult == 1) /* mult == 1 means raw bayer */
+               sd_pkt_scan_bayer(gspca_dev, data, len);
+       else
+               sd_pkt_scan_janggu(gspca_dev, data, len);
+}
+
+static int sd_querymenu(struct gspca_dev *gspca_dev,
+                       struct v4l2_querymenu *menu)
+{
+       switch (menu->id) {
+       case V4L2_CID_POWER_LINE_FREQUENCY:
+               switch (menu->index) {
+               case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED:
+                       strcpy((char *) menu->name, "NoFliker");
+                       return 0;
+               case V4L2_CID_POWER_LINE_FREQUENCY_50HZ:
+                       strcpy((char *) menu->name, "50 Hz");
+                       return 0;
+               case V4L2_CID_POWER_LINE_FREQUENCY_60HZ:
+                       strcpy((char *) menu->name, "60 Hz");
+                       return 0;
+               }
+               break;
+       }
+       return -EINVAL;
+}
+
+#if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE)
+static int sd_int_pkt_scan(struct gspca_dev *gspca_dev, u8 *data, int len)
+{
+       struct sd *sd = (struct sd *)gspca_dev;
+       u8 state;
+
+       if (len != 2)
+               return -EINVAL;
+
+       switch (data[0]) {
+       case 0:
+       case 1:
+               state = data[0];
+               break;
+       default:
+               return -EINVAL;
+       }
+       if (sd->button_state != state) {
+               input_report_key(gspca_dev->input_dev, KEY_CAMERA, state);
+               input_sync(gspca_dev->input_dev);
+               sd->button_state = state;
+       }
+
+       return 0;
+}
+#endif
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+       .name = MODULE_NAME,
+       .ctrls = sd_ctrls,
+       .nctrls = ARRAY_SIZE(sd_ctrls),
+       .config = sd_config,
+       .init = sd_init,
+       .start = sd_start,
+       .stopN = sd_stopN,
+       .dq_callback = sd_dq_callback,
+       .pkt_scan = sd_pkt_scan,
+       .querymenu = sd_querymenu,
+#if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE)
+       .int_pkt_scan = sd_int_pkt_scan,
+#endif
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+       {USB_DEVICE(0x03e8, 0x0004)}, /* Endpoints/Aox SE401 */
+       {USB_DEVICE(0x0471, 0x030b)}, /* Philips PCVC665K */
+       {USB_DEVICE(0x047d, 0x5001)}, /* Kensington 67014 */
+       {USB_DEVICE(0x047d, 0x5002)}, /* Kensington 6701(5/7) */
+       {USB_DEVICE(0x047d, 0x5003)}, /* Kensington 67016 */
+       {}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+                       const struct usb_device_id *id)
+{
+       return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+                               THIS_MODULE);
+}
+
+static int sd_pre_reset(struct usb_interface *intf)
+{
+       return 0;
+}
+
+static int sd_post_reset(struct usb_interface *intf)
+{
+       return 0;
+}
+
+static struct usb_driver sd_driver = {
+       .name = MODULE_NAME,
+       .id_table = device_table,
+       .probe = sd_probe,
+       .disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+       .suspend = gspca_suspend,
+       .resume = gspca_resume,
+#endif
+       .pre_reset = sd_pre_reset,
+       .post_reset = sd_post_reset,
+};
+
+/* -- module insert / remove -- */
+static int __init sd_mod_init(void)
+{
+       return usb_register(&sd_driver);
+}
+static void __exit sd_mod_exit(void)
+{
+       usb_deregister(&sd_driver);
+}
+
+module_init(sd_mod_init);
+module_exit(sd_mod_exit);
diff --git a/drivers/media/video/gspca/se401.h b/drivers/media/video/gspca/se401.h
new file mode 100644 (file)
index 0000000..96d8ebf
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * GSPCA Endpoints (formerly known as AOX) se401 USB Camera sub Driver
+ *
+ * Copyright (C) 2011 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Based on the v4l1 se401 driver which is:
+ *
+ * Copyright (c) 2000 Jeroen B. Vreeken (pe1rxq@amsat.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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 SE401_REQ_GET_CAMERA_DESCRIPTOR                0x06
+#define SE401_REQ_START_CONTINUOUS_CAPTURE     0x41
+#define SE401_REQ_STOP_CONTINUOUS_CAPTURE      0x42
+#define SE401_REQ_CAPTURE_FRAME                        0x43
+#define SE401_REQ_GET_BRT                      0x44
+#define SE401_REQ_SET_BRT                      0x45
+#define SE401_REQ_GET_WIDTH                    0x4c
+#define SE401_REQ_SET_WIDTH                    0x4d
+#define SE401_REQ_GET_HEIGHT                   0x4e
+#define SE401_REQ_SET_HEIGHT                   0x4f
+#define SE401_REQ_GET_OUTPUT_MODE              0x50
+#define SE401_REQ_SET_OUTPUT_MODE              0x51
+#define SE401_REQ_GET_EXT_FEATURE              0x52
+#define SE401_REQ_SET_EXT_FEATURE              0x53
+#define SE401_REQ_CAMERA_POWER                 0x56
+#define SE401_REQ_LED_CONTROL                  0x57
+#define SE401_REQ_BIOS                         0xff
+
+#define SE401_BIOS_READ                                0x07
+
+#define SE401_FORMAT_BAYER     0x40
+
+/* Hyundai hv7131b registers
+   7121 and 7141 should be the same (haven't really checked...) */
+/* Mode registers: */
+#define HV7131_REG_MODE_A              0x00
+#define HV7131_REG_MODE_B              0x01
+#define HV7131_REG_MODE_C              0x02
+/* Frame registers: */
+#define HV7131_REG_FRSU                0x10
+#define HV7131_REG_FRSL                0x11
+#define HV7131_REG_FCSU                0x12
+#define HV7131_REG_FCSL                0x13
+#define HV7131_REG_FWHU                0x14
+#define HV7131_REG_FWHL                0x15
+#define HV7131_REG_FWWU                0x16
+#define HV7131_REG_FWWL                0x17
+/* Timing registers: */
+#define HV7131_REG_THBU                0x20
+#define HV7131_REG_THBL                0x21
+#define HV7131_REG_TVBU                0x22
+#define HV7131_REG_TVBL                0x23
+#define HV7131_REG_TITU                0x25
+#define HV7131_REG_TITM                0x26
+#define HV7131_REG_TITL                0x27
+#define HV7131_REG_TMCD                0x28
+/* Adjust Registers: */
+#define HV7131_REG_ARLV                0x30
+#define HV7131_REG_ARCG                0x31
+#define HV7131_REG_AGCG                0x32
+#define HV7131_REG_ABCG                0x33
+#define HV7131_REG_APBV                0x34
+#define HV7131_REG_ASLP                0x54
+/* Offset Registers: */
+#define HV7131_REG_OFSR                0x50
+#define HV7131_REG_OFSG                0x51
+#define HV7131_REG_OFSB                0x52
+/* REset level statistics registers: */
+#define HV7131_REG_LOREFNOH    0x57
+#define HV7131_REG_LOREFNOL    0x58
+#define HV7131_REG_HIREFNOH    0x59
+#define HV7131_REG_HIREFNOL    0x5a
+
+/* se401 registers */
+#define SE401_OPERATINGMODE    0x2000