aboutsummaryrefslogtreecommitdiff
path: root/drivers/staging/se401
diff options
context:
space:
mode:
authorHans Verkuil <hverkuil@xs4all.nl>2010-12-25 07:36:55 -0300
committerMauro Carvalho Chehab <mchehab@redhat.com>2010-12-29 08:17:08 -0200
commit71bb2876a3b2af8e6fd5ac20921ee0de9e07d809 (patch)
treec3da81f48bc84945d19b7e271888a844fb27ef0d /drivers/staging/se401
parent745da4280f272840976d47afba22ed853f07e1b2 (diff)
[media] se401: deprecate driver, move to staging
The se401 driver is deprecated and is moved to staging. If no one will convert this driver to V4L2, then it will be removed in 2.6.39. Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/staging/se401')
-rw-r--r--drivers/staging/se401/Kconfig13
-rw-r--r--drivers/staging/se401/Makefile1
-rw-r--r--drivers/staging/se401/se401.c1492
-rw-r--r--drivers/staging/se401/se401.h236
4 files changed, 1742 insertions, 0 deletions
diff --git a/drivers/staging/se401/Kconfig b/drivers/staging/se401/Kconfig
new file mode 100644
index 00000000000..586fc0432a4
--- /dev/null
+++ b/drivers/staging/se401/Kconfig
@@ -0,0 +1,13 @@
+config USB_SE401
+ tristate "USB SE401 Camera support (DEPRECATED)"
+ depends on VIDEO_V4L1
+ ---help---
+ Say Y here if you want to connect this type of camera to your
+ computer's USB port. See <file:Documentation/video4linux/se401.txt>
+ for more information and for a list of supported cameras.
+
+ This driver uses the deprecated V4L1 API and will be removed in
+ 2.6.39, unless someone converts it to the V4L2 API.
+
+ To compile this driver as a module, choose M here: the
+ module will be called se401.
diff --git a/drivers/staging/se401/Makefile b/drivers/staging/se401/Makefile
new file mode 100644
index 00000000000..b465d49783a
--- /dev/null
+++ b/drivers/staging/se401/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_USB_SE401) += se401.o
diff --git a/drivers/staging/se401/se401.c b/drivers/staging/se401/se401.c
new file mode 100644
index 00000000000..41360d7c3e9
--- /dev/null
+++ b/drivers/staging/se401/se401.c
@@ -0,0 +1,1492 @@
+/*
+ * Endpoints (formerly known as AOX) se401 USB Camera Driver
+ *
+ * Copyright (c) 2000 Jeroen B. Vreeken (pe1rxq@amsat.org)
+ *
+ * Still somewhat based on the Linux ov511 driver.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *
+ * Thanks to Endpoints Inc. (www.endpoints.com) for making documentation on
+ * their chipset available and supporting me while writing this driver.
+ * - Jeroen Vreeken
+ */
+
+static const char version[] = "0.24";
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <linux/pagemap.h>
+#include <linux/usb.h>
+#include "se401.h"
+
+static int flickerless;
+static int video_nr = -1;
+
+static 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);
+
+MODULE_AUTHOR("Jeroen Vreeken <pe1rxq@amsat.org>");
+MODULE_DESCRIPTION("SE401 USB Camera Driver");
+MODULE_LICENSE("GPL");
+module_param(flickerless, int, 0);
+MODULE_PARM_DESC(flickerless,
+ "Net frequency to adjust exposure time to (0/50/60)");
+module_param(video_nr, int, 0);
+
+static struct usb_driver se401_driver;
+
+
+/**********************************************************************
+ *
+ * Memory management
+ *
+ **********************************************************************/
+static void *rvmalloc(unsigned long size)
+{
+ void *mem;
+ unsigned long adr;
+
+ size = PAGE_ALIGN(size);
+ mem = vmalloc_32(size);
+ if (!mem)
+ return NULL;
+
+ memset(mem, 0, size); /* Clear the ram out, no junk to the user */
+ adr = (unsigned long) mem;
+ while (size > 0) {
+ SetPageReserved(vmalloc_to_page((void *)adr));
+ adr += PAGE_SIZE;
+ size -= PAGE_SIZE;
+ }
+
+ return mem;
+}
+
+static void rvfree(void *mem, unsigned long size)
+{
+ unsigned long adr;
+
+ if (!mem)
+ return;
+
+ adr = (unsigned long) mem;
+ while ((long) size > 0) {
+ ClearPageReserved(vmalloc_to_page((void *)adr));
+ adr += PAGE_SIZE;
+ size -= PAGE_SIZE;
+ }
+ vfree(mem);
+}
+
+
+
+/****************************************************************************
+ *
+ * se401 register read/write functions
+ *
+ ***************************************************************************/
+
+static int se401_sndctrl(int set, struct usb_se401 *se401, unsigned short req,
+ unsigned short value, unsigned char *cp, int size)
+{
+ return usb_control_msg(
+ se401->dev,
+ set ? usb_sndctrlpipe(se401->dev, 0) : usb_rcvctrlpipe(se401->dev, 0),
+ req,
+ (set ? USB_DIR_OUT : USB_DIR_IN) | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ value,
+ 0,
+ cp,
+ size,
+ 1000
+ );
+}
+
+static int se401_set_feature(struct usb_se401 *se401, unsigned short selector,
+ unsigned short param)
+{
+ /* specs say that the selector (address) should go in the value field
+ and the param in index, but in the logs of the windows driver they do
+ this the other way around...
+ */
+ return usb_control_msg(
+ se401->dev,
+ usb_sndctrlpipe(se401->dev, 0),
+ SE401_REQ_SET_EXT_FEATURE,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ param,
+ selector,
+ NULL,
+ 0,
+ 1000
+ );
+}
+
+static unsigned short se401_get_feature(struct usb_se401 *se401,
+ unsigned short selector)
+{
+ /* For 'set' the selecetor should be in index, not sure if the spec is
+ wrong here to....
+ */
+ unsigned char cp[2];
+ usb_control_msg(
+ se401->dev,
+ usb_rcvctrlpipe(se401->dev, 0),
+ SE401_REQ_GET_EXT_FEATURE,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0,
+ selector,
+ cp,
+ 2,
+ 1000
+ );
+ return cp[0]+cp[1]*256;
+}
+
+/****************************************************************************
+ *
+ * Camera control
+ *
+ ***************************************************************************/
+
+
+static int se401_send_pict(struct usb_se401 *se401)
+{
+ /* integration time low */
+ se401_set_feature(se401, HV7131_REG_TITL, se401->expose_l);
+ /* integration time mid */
+ se401_set_feature(se401, HV7131_REG_TITM, se401->expose_m);
+ /* integration time mid */
+ se401_set_feature(se401, HV7131_REG_TITU, se401->expose_h);
+ /* reset level value */
+ se401_set_feature(se401, HV7131_REG_ARLV, se401->resetlevel);
+ /* red color gain */
+ se401_set_feature(se401, HV7131_REG_ARCG, se401->rgain);
+ /* green color gain */
+ se401_set_feature(se401, HV7131_REG_AGCG, se401->ggain);
+ /* blue color gain */
+ se401_set_feature(se401, HV7131_REG_ABCG, se401->bgain);
+
+ return 0;
+}
+
+static void se401_set_exposure(struct usb_se401 *se401, int brightness)
+{
+ int integration = brightness << 5;
+
+ if (flickerless == 50)
+ integration = integration-integration % 106667;
+ if (flickerless == 60)
+ integration = integration-integration % 88889;
+ se401->brightness = integration >> 5;
+ se401->expose_h = (integration >> 16) & 0xff;
+ se401->expose_m = (integration >> 8) & 0xff;
+ se401->expose_l = integration & 0xff;
+}
+
+static int se401_get_pict(struct usb_se401 *se401, struct video_picture *p)
+{
+ p->brightness = se401->brightness;
+ if (se401->enhance)
+ p->whiteness = 32768;
+ else
+ p->whiteness = 0;
+
+ p->colour = 65535;
+ p->contrast = 65535;
+ p->hue = se401->rgain << 10;
+ p->palette = se401->palette;
+ p->depth = 3; /* rgb24 */
+ return 0;
+}
+
+
+static int se401_set_pict(struct usb_se401 *se401, struct video_picture *p)
+{
+ if (p->palette != VIDEO_PALETTE_RGB24)
+ return 1;
+ se401->palette = p->palette;
+ if (p->hue != se401->hue) {
+ se401->rgain = p->hue >> 10;
+ se401->bgain = 0x40-(p->hue >> 10);
+ se401->hue = p->hue;
+ }
+ if (p->brightness != se401->brightness)
+ se401_set_exposure(se401, p->brightness);
+
+ if (p->whiteness >= 32768)
+ se401->enhance = 1;
+ else
+ se401->enhance = 0;
+ se401_send_pict(se401);
+ se401_send_pict(se401);
+ return 0;
+}
+
+/*
+ Hyundai have some really nice docs about this and other sensor related
+ stuff on their homepage: www.hei.co.kr
+*/
+static void se401_auto_resetlevel(struct usb_se401 *se401)
+{
+ unsigned int ahrc, alrc;
+ int oldreset = se401->resetlevel;
+
+ /* For some reason this normally read-only register doesn't get reset
+ to zero after reading them just once...
+ */
+ se401_get_feature(se401, HV7131_REG_HIREFNOH);
+ se401_get_feature(se401, HV7131_REG_HIREFNOL);
+ se401_get_feature(se401, HV7131_REG_LOREFNOH);
+ se401_get_feature(se401, HV7131_REG_LOREFNOL);
+ ahrc = 256*se401_get_feature(se401, HV7131_REG_HIREFNOH) +
+ se401_get_feature(se401, HV7131_REG_HIREFNOL);
+ alrc = 256*se401_get_feature(se401, HV7131_REG_LOREFNOH) +
+ se401_get_feature(se401, HV7131_REG_LOREFNOL);
+
+ /* Not an exact science, but it seems to work pretty well... */
+ if (alrc > 10) {
+ while (alrc >= 10 && se401->resetlevel < 63) {
+ se401->resetlevel++;
+ alrc /= 2;
+ }
+ } else if (ahrc > 20) {
+ while (ahrc >= 20 && se401->resetlevel > 0) {
+ se401->resetlevel--;
+ ahrc /= 2;
+ }
+ }
+ if (se401->resetlevel != oldreset)
+ se401_set_feature(se401, HV7131_REG_ARLV, se401->resetlevel);
+
+ return;
+}
+
+/* irq handler for snapshot button */
+static void se401_button_irq(struct urb *urb)
+{
+ struct usb_se401 *se401 = urb->context;
+ int status;
+
+ if (!se401->dev) {
+ dev_info(&urb->dev->dev, "device vapourished\n");
+ return;
+ }
+
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dbg("%s - urb shutting down with status: %d",
+ __func__, urb->status);
+ return;
+ default:
+ dbg("%s - nonzero urb status received: %d",
+ __func__, urb->status);
+ goto exit;
+ }
+
+ if (urb->actual_length >= 2)
+ if (se401->button)
+ se401->buttonpressed = 1;
+exit:
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status)
+ err("%s - usb_submit_urb failed with result %d",
+ __func__, status);
+}
+
+static void se401_video_irq(struct urb *urb)
+{
+ struct usb_se401 *se401 = urb->context;
+ int length = urb->actual_length;
+
+ /* ohoh... */
+ if (!se401->streaming)
+ return;
+
+ if (!se401->dev) {
+ dev_info(&urb->dev->dev, "device vapourished\n");
+ return;
+ }
+
+ /* 0 sized packets happen if we are to fast, but sometimes the camera
+ keeps sending them forever...
+ */
+ if (length && !urb->status) {
+ se401->nullpackets = 0;
+ switch (se401->scratch[se401->scratch_next].state) {
+ case BUFFER_READY:
+ case BUFFER_BUSY:
+ se401->dropped++;
+ break;
+ case BUFFER_UNUSED:
+ memcpy(se401->scratch[se401->scratch_next].data,
+ (unsigned char *)urb->transfer_buffer, length);
+ se401->scratch[se401->scratch_next].state
+ = BUFFER_READY;
+ se401->scratch[se401->scratch_next].offset
+ = se401->bayeroffset;
+ se401->scratch[se401->scratch_next].length = length;
+ if (waitqueue_active(&se401->wq))
+ wake_up_interruptible(&se401->wq);
+ se401->scratch_overflow = 0;
+ se401->scratch_next++;
+ if (se401->scratch_next >= SE401_NUMSCRATCH)
+ se401->scratch_next = 0;
+ break;
+ }
+ se401->bayeroffset += length;
+ if (se401->bayeroffset >= se401->cheight * se401->cwidth)
+ se401->bayeroffset = 0;
+ } else {
+ se401->nullpackets++;
+ if (se401->nullpackets > SE401_MAX_NULLPACKETS)
+ if (waitqueue_active(&se401->wq))
+ wake_up_interruptible(&se401->wq);
+ }
+
+ /* Resubmit urb for new data */
+ urb->status = 0;
+ urb->dev = se401->dev;
+ if (usb_submit_urb(urb, GFP_KERNEL))
+ dev_info(&urb->dev->dev, "urb burned down\n");
+ return;
+}
+
+static void se401_send_size(struct usb_se401 *se401, int width, int height)
+{
+ int i = 0;
+ int mode = 0x03; /* No compression */
+ int sendheight = height;
+ int sendwidth = width;
+
+ /* JangGu compression can only be used with the camera supported sizes,
+ but bayer seems to work with any size that fits on the sensor.
+ We check if we can use compression with the current size with either
+ 4 or 16 times subcapturing, if not we use uncompressed bayer data
+ but this will result in cutouts of the maximum size....
+ */
+ while (i < se401->sizes && !(se401->width[i] == width &&
+ se401->height[i] == height))
+ i++;
+ while (i < se401->sizes) {
+ if (se401->width[i] == width * 2 &&
+ se401->height[i] == height * 2) {
+ sendheight = se401->height[i];
+ sendwidth = se401->width[i];
+ mode = 0x40;
+ }
+ if (se401->width[i] == width * 4 &&
+ se401->height[i] == height * 4) {
+ sendheight = se401->height[i];
+ sendwidth = se401->width[i];
+ mode = 0x42;
+ }
+ i++;
+ }
+
+ se401_sndctrl(1, se401, SE401_REQ_SET_WIDTH, sendwidth, NULL, 0);
+ se401_sndctrl(1, se401, SE401_REQ_SET_HEIGHT, sendheight, NULL, 0);
+ se401_set_feature(se401, SE401_OPERATINGMODE, mode);
+
+ if (mode == 0x03)
+ se401->format = FMT_BAYER;
+ else
+ se401->format = FMT_JANGGU;
+}
+
+/*
+ In this function se401_send_pict is called several times,
+ for some reason (depending on the state of the sensor and the phase of
+ the moon :) doing this only in either place doesn't always work...
+*/
+static int se401_start_stream(struct usb_se401 *se401)
+{
+ struct urb *urb;
+ int err = 0, i;
+ se401->streaming = 1;
+
+ se401_sndctrl(1, se401, SE401_REQ_CAMERA_POWER, 1, NULL, 0);
+ se401_sndctrl(1, se401, SE401_REQ_LED_CONTROL, 1, NULL, 0);
+
+ /* Set picture settings */
+ /* windowed + pix intg */
+ se401_set_feature(se401, HV7131_REG_MODE_B, 0x05);
+ se401_send_pict(se401);
+
+ se401_send_size(se401, se401->cwidth, se401->cheight);
+
+ se401_sndctrl(1, se401, SE401_REQ_START_CONTINUOUS_CAPTURE,
+ 0, NULL, 0);
+
+ /* Do some memory allocation */
+ for (i = 0; i < SE401_NUMFRAMES; i++) {
+ se401->frame[i].data = se401->fbuf + i * se401->maxframesize;
+ se401->frame[i].curpix = 0;
+ }
+ for (i = 0; i < SE401_NUMSBUF; i++) {
+ se401->sbuf[i].data = kmalloc(SE401_PACKETSIZE, GFP_KERNEL);
+ if (!se401->sbuf[i].data) {
+ for (i = i - 1; i >= 0; i--) {
+ kfree(se401->sbuf[i].data);
+ se401->sbuf[i].data = NULL;
+ }
+ return -ENOMEM;
+ }
+ }
+
+ se401->bayeroffset = 0;
+ se401->scratch_next = 0;
+ se401->scratch_use = 0;
+ se401->scratch_overflow = 0;
+ for (i = 0; i < SE401_NUMSCRATCH; i++) {
+ se401->scratch[i].data = kmalloc(SE401_PACKETSIZE, GFP_KERNEL);
+ if (!se401->scratch[i].data) {
+ for (i = i - 1; i >= 0; i--) {
+ kfree(se401->scratch[i].data);
+ se401->scratch[i].data = NULL;
+ }
+ goto nomem_sbuf;
+ }
+ se401->scratch[i].state = BUFFER_UNUSED;
+ }
+
+ for (i = 0; i < SE401_NUMSBUF; i++) {
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ for (i = i - 1; i >= 0; i--) {
+ usb_kill_urb(se401->urb[i]);
+ usb_free_urb(se401->urb[i]);
+ se401->urb[i] = NULL;
+ }
+ goto nomem_scratch;
+ }
+
+ usb_fill_bulk_urb(urb, se401->dev,
+ usb_rcvbulkpipe(se401->dev, SE401_VIDEO_ENDPOINT),
+ se401->sbuf[i].data, SE401_PACKETSIZE,
+ se401_video_irq,
+ se401);
+
+ se401->urb[i] = urb;
+
+ err = usb_submit_urb(se401->urb[i], GFP_KERNEL);
+ if (err)
+ err("urb burned down");
+ }
+
+ se401->framecount = 0;
+
+ return 0;
+
+ nomem_scratch:
+ for (i = 0; i < SE401_NUMSCRATCH; i++) {
+ kfree(se401->scratch[i].data);
+ se401->scratch[i].data = NULL;
+ }
+ nomem_sbuf:
+ for (i = 0; i < SE401_NUMSBUF; i++) {
+ kfree(se401->sbuf[i].data);
+ se401->sbuf[i].data = NULL;
+ }
+ return -ENOMEM;
+}
+
+static int se401_stop_stream(struct usb_se401 *se401)
+{
+ int i;
+
+ if (!se401->streaming || !se401->dev)
+ return 1;
+
+ se401->streaming = 0;
+
+ se401_sndctrl(1, se401, SE401_REQ_STOP_CONTINUOUS_CAPTURE, 0, NULL, 0);
+
+ se401_sndctrl(1, se401, SE401_REQ_LED_CONTROL, 0, NULL, 0);
+ se401_sndctrl(1, se401, SE401_REQ_CAMERA_POWER, 0, NULL, 0);
+
+ for (i = 0; i < SE401_NUMSBUF; i++)
+ if (se401->urb[i]) {
+ usb_kill_urb(se401->urb[i]);
+ usb_free_urb(se401->urb[i]);
+ se401->urb[i] = NULL;
+ kfree(se401->sbuf[i].data);
+ }
+ for (i = 0; i < SE401_NUMSCRATCH; i++) {
+ kfree(se401->scratch[i].data);
+ se401->scratch[i].data = NULL;
+ }
+
+ return 0;
+}
+
+static int se401_set_size(struct usb_se401 *se401, int width, int height)
+{
+ int wasstreaming = se401->streaming;
+ /* Check to see if we need to change */
+ if (se401->cwidth == width && se401->cheight == height)
+ return 0;
+
+ /* Check for a valid mode */
+ if (!width || !height)
+ return 1;
+ if ((width & 1) || (height & 1))
+ return 1;
+ if (width > se401->width[se401->sizes-1])
+ return 1;
+ if (height > se401->height[se401->sizes-1])
+ return 1;
+
+ /* Stop a current stream and start it again at the new size */
+ if (wasstreaming)
+ se401_stop_stream(se401);
+ se401->cwidth = width;
+ se401->cheight = height;
+ if (wasstreaming)
+ se401_start_stream(se401);
+ return 0;
+}
+
+
+/****************************************************************************
+ *
+ * Video Decoding
+ *
+ ***************************************************************************/
+
+/*
+ This shouldn't really be done in a v4l driver....
+ But it does make the image look a lot more usable.
+ Basically it lifts the dark pixels more than the light pixels.
+*/
+static inline void enhance_picture(unsigned char *frame, int len)
+{
+ while (len--) {
+ *frame = (((*frame^255)*(*frame^255))/255)^255;
+ frame++;
+ }
+}
+
+static inline void decode_JangGu_integrate(struct usb_se401 *se401, int data)
+{
+ struct se401_frame *frame = &se401->frame[se401->curframe];
+ int linelength = se401->cwidth * 3;
+
+ if (frame->curlinepix >= linelength) {
+ frame->curlinepix = 0;
+ frame->curline += linelength;
+ }
+
+ /* First three are absolute, all others relative.
+ * Format is rgb from right to left (mirrorred image),
+ * we flip it to get bgr from left to right. */
+ if (frame->curlinepix < 3)
+ *(frame->curline-frame->curlinepix) = 1 + data * 4;
+ else
+ *(frame->curline-frame->curlinepix) =
+ *(frame->curline-frame->curlinepix + 3) + data * 4;
+ frame->curlinepix++;
+}
+
+static inline void decode_JangGu_vlc(struct usb_se401 *se401,
+ unsigned char *data, int bit_exp, int packetlength)
+{
+ int pos = 0;
+ int vlc_cod = 0;
+ int vlc_size = 0;
+ int vlc_data = 0;
+ int bit_cur;
+ int bit;
+ data += 4;
+ while (pos < packetlength) {
+ bit_cur = 8;
+ while (bit_cur && bit_exp) {
+ bit = ((*data) >> (bit_cur-1))&1;
+ if (!vlc_cod) {
+ if (bit) {
+ vlc_size++;
+ } else {
+ if (!vlc_size)
+ decode_JangGu_integrate(se401, 0);
+ else {
+ vlc_cod = 2;
+ vlc_data = 0;
+ }
+ }
+ } else {
+ if (vlc_cod == 2) {
+ if (!bit)
+ vlc_data = -(1 << vlc_size) + 1;
+ vlc_cod--;
+ }
+ vlc_size--;
+ vlc_data += bit << vlc_size;
+ if (!vlc_size) {
+ decode_JangGu_integrate(se401, vlc_data);
+ vlc_cod = 0;
+ }
+ }
+ bit_cur--;
+ bit_exp--;
+ }
+ pos++;
+ data++;
+ }
+}
+
+static inline void decode_JangGu(struct usb_se401 *se401,
+ struct se401_scratch *buffer)
+{
+ unsigned char *data = buffer->data;
+ int len = buffer->length;
+ int bit_exp = 0, pix_exp = 0, frameinfo = 0, packetlength = 0, size;
+ int datapos = 0;
+
+ /* New image? */
+ if (!se401->frame[se401->curframe].curpix) {
+ se401->frame[se401->curframe].curlinepix = 0;
+ se401->frame[se401->curframe].curline =
+ se401->frame[se401->curframe].data+
+ se401->cwidth * 3 - 1;
+ if (se401->frame[se401->curframe].grabstate == FRAME_READY)
+ se401->frame[se401->curframe].grabstate = FRAME_GRABBING;
+ se401->vlcdatapos = 0;
+ }
+ while (datapos < len) {
+ size = 1024 - se401->vlcdatapos;
+ if (size+datapos > len)
+ size = len-datapos;
+ memcpy(se401->vlcdata+se401->vlcdatapos, data+datapos, size);
+ se401->vlcdatapos += size;
+ packetlength = 0;
+ if (se401->vlcdatapos >= 4) {
+ bit_exp = se401->vlcdata[3] + (se401->vlcdata[2] << 8);
+ pix_exp = se401->vlcdata[1] +
+ ((se401->vlcdata[0] & 0x3f) << 8);
+ frameinfo = se401->vlcdata[0] & 0xc0;
+ packetlength = ((bit_exp + 47) >> 4) << 1;
+ if (packetlength > 1024) {
+ se401->vlcdatapos = 0;
+ datapos = len;
+ packetlength = 0;
+ se401->error++;
+ se401->frame[se401->curframe].curpix = 0;
+ }
+ }
+ if (packetlength && se401->vlcdatapos >= packetlength) {
+ decode_JangGu_vlc(se401, se401->vlcdata, bit_exp,
+ packetlength);
+ se401->frame[se401->curframe].curpix += pix_exp * 3;
+ datapos += size-(se401->vlcdatapos-packetlength);
+ se401->vlcdatapos = 0;
+ if (se401->frame[se401->curframe].curpix >= se401->cwidth * se401->cheight * 3) {
+ if (se401->frame[se401->curframe].curpix == se401->cwidth * se401->cheight * 3) {
+ if (se401->frame[se401->curframe].grabstate == FRAME_GRABBING) {
+ se401->frame[se401->curframe].grabstate = FRAME_DONE;
+ se401->framecount++;
+ se401->readcount++;
+ }
+ if (se401->frame[(se401->curframe + 1) & (SE401_NUMFRAMES - 1)].grabstate == FRAME_READY)
+ se401->curframe = (se401->curframe + 1) & (SE401_NUMFRAMES - 1);
+ } else
+ se401->error++;
+ se401->frame[se401->curframe].curpix = 0;
+ datapos = len;
+ }
+ } else
+ datapos += size;
+ }
+}
+
+static inline void decode_bayer(struct usb_se401 *se401,
+ struct se401_scratch *buffer)
+{
+ unsigned char *data = buffer->data;
+ int len = buffer->length;
+ int offset = buffer->offset;
+ int datasize = se401->cwidth * se401->cheight;
+ struct se401_frame *frame = &se401->frame[se401->curframe];
+ unsigned char *framedata = frame->data, *curline, *nextline;
+ int width = se401->cwidth;
+ int blineoffset = 0, bline;
+ int linelength = width * 3, i;
+
+
+ if (frame->curpix == 0) {
+ if (frame->grabstate == FRAME_READY)
+ frame->grabstate = FRAME_GRABBING;
+
+ frame->curline = framedata + linelength;
+ frame->curlinepix = 0;
+ }
+
+ if (offset != frame->curpix) {
+ /* Regard frame as lost :( */
+ frame->curpix = 0;
+ se401->error++;
+ return;
+ }
+
+ /* Check if we have to much data */
+ if (frame->curpix + len > datasize)
+ len = datasize-frame->curpix;
+
+ if (se401->cheight % 4)
+ blineoffset = 1;
+ bline = frame->curpix / se401->cwidth+blineoffset;
+
+ curline = frame->curline;
+ nextline = curline + linelength;
+ if (nextline >= framedata+datasize * 3)
+ nextline = curline;
+ while (len) {
+ if (frame->curlinepix >= width) {
+ frame->curlinepix -= width;
+ bline = frame->curpix / width + blineoffset;
+ curline += linelength*2;
+ nextline += linelength*2;
+ if (curline >= framedata+datasize * 3) {
+ frame->curlinepix++;
+ curline -= 3;
+ nextline -= 3;
+ len--;
+ data++;
+ frame->curpix++;
+ }
+ if (nextline >= framedata+datasize*3)
+ nextline = curline;
+ }
+ if (bline & 1) {
+ if (frame->curlinepix & 1) {
+ *(curline + 2) = *data;
+ *(curline - 1) = *data;
+ *(nextline + 2) = *data;
+ *(nextline - 1) = *data;
+ } else {
+ *(curline + 1) =
+ (*(curline + 1) + *data) / 2;
+ *(curline-2) =
+ (*(curline - 2) + *data) / 2;
+ *(nextline + 1) = *data;
+ *(nextline - 2) = *data;
+ }
+ } else {
+ if (frame->curlinepix & 1) {
+ *(curline + 1) =
+ (*(curline + 1) + *data) / 2;
+ *(curline - 2) =
+ (*(curline - 2) + *data) / 2;
+ *(nextline + 1) = *data;
+ *(nextline - 2) = *data;
+ } else {
+ *curline = *data;
+ *(curline - 3) = *data;
+ *nextline = *data;
+ *(nextline - 3) = *data;
+ }
+ }
+ frame->curlinepix++;
+ curline -= 3;
+ nextline -= 3;
+ len--;
+ data++;
+ frame->curpix++;
+ }
+ frame->curline = curline;
+
+ if (frame->curpix >= datasize) {
+ /* Fix the top line */
+ framedata += linelength;
+ for (i = 0; i < linelength; i++) {
+ framedata--;
+ *framedata = *(framedata + linelength);
+ }
+ /* Fix the left side (green is already present) */
+ for (i = 0; i < se401->cheight; i++) {
+ *framedata = *(framedata + 3);
+ *(framedata + 1) = *(framedata + 4);
+ *(framedata + 2) = *(framedata + 5);
+ framedata += linelength;
+ }
+ frame->curpix = 0;
+ frame->grabstate = FRAME_DONE;
+ se401->framecount++;
+ se401->readcount++;
+ if (se401->frame[(se401->curframe + 1) &
+ (SE401_NUMFRAMES - 1)].grabstate == FRAME_READY) {
+ se401->curframe = (se401->curframe+1) &
+ (SE401_NUMFRAMES-1);
+ }
+ }
+}
+
+static int se401_newframe(struct usb_se401 *se401, int framenr)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ int errors = 0;
+
+ while (se401->streaming &&
+ (se401->frame[framenr].grabstate == FRAME_READY ||
+ se401->frame[framenr].grabstate == FRAME_GRABBING)) {
+ if (!se401->frame[framenr].curpix)
+ errors++;
+
+ wait_interruptible(
+ se401->scratch[se401->scratch_use].state != BUFFER_READY,
+ &se401->wq, &wait);
+ if (se401->nullpackets > SE401_MAX_NULLPACKETS) {
+ se401->nullpackets = 0;
+ dev_info(&se401->dev->dev,
+ "too many null length packets, restarting capture\n");
+ se401_stop_stream(se401);
+ se401_start_stream(se401);
+ } else {
+ if (se401->scratch[se401->scratch_use].state !=
+ BUFFER_READY) {
+ se401->frame[framenr].grabstate = FRAME_ERROR;
+ return -EIO;
+ }
+ se401->scratch[se401->scratch_use].state = BUFFER_BUSY;
+ if (se401->format == FMT_JANGGU)
+ decode_JangGu(se401,
+ &se401->scratch[se401->scratch_use]);
+ else
+ decode_bayer(se401,
+ &se401->scratch[se401->scratch_use]);
+
+ se401->scratch[se401->scratch_use].state =
+ BUFFER_UNUSED;
+ se401->scratch_use++;
+ if (se401->scratch_use >= SE401_NUMSCRATCH)
+ se401->scratch_use = 0;
+ if (errors > SE401_MAX_ERRORS) {
+ errors = 0;
+ dev_info(&se401->dev->dev,
+ "too many errors, restarting capture\n");
+ se401_stop_stream(se401);
+ se401_start_stream(se401);
+ }
+ }
+ }
+
+ if (se401->frame[framenr].grabstate == FRAME_DONE)
+ if (se401->enhance)
+ enhance_picture(se401->frame[framenr].data,
+ se401->cheight * se401->cwidth * 3);
+ return 0;
+}
+
+static void usb_se401_remove_disconnected(struct usb_se401 *se401)
+{
+ int i;
+
+ se401->dev = NULL;
+
+ for (i = 0; i < SE401_NUMSBUF; i++)
+ if (se401->urb[i]) {
+ usb_kill_urb(se401->urb[i]);
+ usb_free_urb(se401->urb[i]);
+ se401->urb[i] = NULL;
+ kfree(se401->sbuf[i].data);
+ }
+
+ for (i = 0; i < SE401_NUMSCRATCH; i++)
+ kfree(se401->scratch[i].data);
+
+ if (se401->inturb) {
+ usb_kill_urb(se401->inturb);
+ usb_free_urb(se401->inturb);
+ }
+ dev_info(&se401->dev->dev, "%s disconnected", se401->camera_name);
+
+ /* Free the memory */
+ kfree(se401->width);
+ kfree(se401->height);
+ kfree(se401);
+}
+
+
+
+/****************************************************************************
+ *
+ * Video4Linux
+ *
+ ***************************************************************************/
+
+
+static int se401_open(struct file *file)
+{
+ struct video_device *dev = video_devdata(file);
+ struct usb_se401 *se401 = (struct usb_se401 *)dev;
+ int err = 0;
+
+ mutex_lock(&se401->lock);
+ if (se401->user) {
+ mutex_unlock(&se401->lock);
+ return -EBUSY;
+ }
+ se401->fbuf = rvmalloc(se401->maxframesize * SE401_NUMFRAMES);
+ if (se401->fbuf)
+ file->private_data = dev;
+ else
+ err = -ENOMEM;
+ se401->user = !err;
+ mutex_unlock(&se401->lock);
+
+ return err;
+}
+
+static int se401_close(struct file *file)
+{
+ struct video_device *dev = file->private_data;
+ struct usb_se401 *se401 = (struct usb_se401 *)dev;
+ int i;
+
+ rvfree(se401->fbuf, se401->maxframesize * SE401_NUMFRAMES);
+ if (se401->removed) {
+ dev_info(&se401->dev->dev, "device unregistered\n");
+ usb_se401_remove_disconnected(se401);
+ } else {
+ for (i = 0; i < SE401_NUMFRAMES; i++)
+ se401->frame[i].grabstate = FRAME_UNUSED;
+ if (se401->streaming)
+ se401_stop_stream(se401);
+ se401->user = 0;
+ }
+ file->private_data = NULL;
+ return 0;
+}
+
+static long se401_do_ioctl(struct file *file, unsigned int cmd, void *arg)
+{
+ struct video_device *vdev = file->private_data;
+ struct usb_se401 *se401 = (struct usb_se401 *)vdev;
+
+ if (!se401->dev)
+ return -EIO;
+
+ switch (cmd) {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *b = arg;
+ strcpy(b->name, se401->camera_name);
+ b->type = VID_TYPE_CAPTURE;
+ b->channels = 1;
+ b->audios = 0;
+ b->maxwidth = se401->width[se401->sizes-1];
+ b->maxheight = se401->height[se401->sizes-1];
+ b->minwidth = se401->width[0];
+ b->minheight = se401->height[0];
+ return 0;
+ }
+ case VIDIOCGCHAN:
+ {
+ struct video_channel *v = arg;
+
+ if (v->channel != 0)
+ return -EINVAL;
+ v->flags = 0;
+ v->tuners = 0;
+ v->type = VIDEO_TYPE_CAMERA;
+ strcpy(v->name, "Camera");
+ return 0;
+ }
+ case VIDIOCSCHAN:
+ {
+ struct video_channel *v = arg;
+
+ if (v->channel != 0)
+ return -EINVAL;
+ return 0;
+ }
+ case VIDIOCGPICT:
+ {
+ struct video_picture *p = arg;
+
+ se401_get_pict(se401, p);
+ return 0;
+ }
+ case VIDIOCSPICT:
+ {
+ struct video_picture *p = arg;
+
+ if (se401_set_pict(se401, p))
+ return -EINVAL;
+ return 0;
+ }
+ case VIDIOCSWIN:
+ {
+ struct video_window *vw = arg;
+
+ if (vw->flags)
+ return -EINVAL;
+ if (vw->clipcount)
+ return -EINVAL;
+ if (se401_set_size(se401, vw->width, vw->height))
+ return -EINVAL;
+ return 0;
+ }
+ case VIDIOCGWIN:
+ {
+ struct video_window *vw = arg;
+
+ vw->x = 0; /* FIXME */
+ vw->y = 0;
+ vw->chromakey = 0;
+ vw->flags = 0;
+ vw->clipcount = 0;
+ vw->width = se401->cwidth;
+ vw->height = se401->cheight;
+ return 0;
+ }
+ case VIDIOCGMBUF:
+ {
+ struct video_mbuf *vm = arg;
+ int i;
+
+ memset(vm, 0, sizeof(*vm));
+ vm->size = SE401_NUMFRAMES * se401->maxframesize;
+ vm->frames = SE401_NUMFRAMES;
+ for (i = 0; i < SE401_NUMFRAMES; i++)
+ vm->offsets[i] = se401->maxframesize * i;
+ return 0;
+ }
+ case VIDIOCMCAPTURE:
+ {
+ struct video_mmap *vm = arg;
+
+ if (vm->format != VIDEO_PALETTE_RGB24)
+ return -EINVAL;
+ if (vm->frame >= SE401_NUMFRAMES)
+ return -EINVAL;
+ if (se401->frame[vm->frame].grabstate != FRAME_UNUSED)
+ return -EBUSY;
+
+ /* Is this according to the v4l spec??? */
+ if (se401_set_size(se401, vm->width, vm->height))
+ return -EINVAL;
+ se401->frame[vm->frame].grabstate = FRAME_READY;
+
+ if (!se401->streaming)
+ se401_start_stream(se401);
+
+ /* Set the picture properties */
+ if (se401->framecount == 0)
+ se401_send_pict(se401);
+ /* Calibrate the reset level after a few frames. */
+ if (se401->framecount % 20 == 1)
+ se401_auto_resetlevel(se401);
+
+ return 0;
+ }
+ case VIDIOCSYNC:
+ {
+ int *frame = arg;
+ int ret = 0;
+
+ if (*frame < 0 || *frame >= SE401_NUMFRAMES)
+ return -EINVAL;
+
+ ret = se401_newframe(se401, *frame);
+ se401->frame[*frame].grabstate = FRAME_UNUSED;
+ return ret;
+ }
+ case VIDIOCGFBUF:
+ {
+ struct video_buffer *vb = arg;
+
+ memset(vb, 0, sizeof(*vb));
+ return 0;
+ }
+ case VIDIOCKEY:
+ return 0;
+ case VIDIOCCAPTURE:
+ return -EINVAL;
+ case VIDIOCSFBUF:
+ return -EINVAL;
+ case VIDIOCGTUNER:
+ case VIDIOCSTUNER:
+ return -EINVAL;
+ case VIDIOCGFREQ:
+ case VIDIOCSFREQ:
+ return -EINVAL;
+ case VIDIOCGAUDIO:
+ case VIDIOCSAUDIO:
+ return -EINVAL;
+ default:
+ return -ENOIOCTLCMD;
+ } /* end switch */
+
+ return 0;
+}
+
+static long se401_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(file, cmd, arg, se401_do_ioctl);
+}
+
+static ssize_t se401_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int realcount = count, ret = 0;
+ struct video_device *dev = file->private_data;
+ struct usb_se401 *se401 = (struct usb_se401 *)dev;
+
+
+ if (se401->dev == NULL)
+ return -EIO;
+ if (realcount > se401->cwidth*se401->cheight*3)
+ realcount = se401->cwidth*se401->cheight*3;
+
+ /* Shouldn't happen: */
+ if (se401->frame[0].grabstate == FRAME_GRABBING)
+ return -EBUSY;
+ se401->frame[0].grabstate = FRAME_READY;
+ se401->frame[1].grabstate = FRAME_UNUSED;
+ se401->curframe = 0;
+
+ if (!se401->streaming)
+ se401_start_stream(se401);
+
+ /* Set the picture properties */
+ if (se401->framecount == 0)
+ se401_send_pict(se401);
+ /* Calibrate the reset level after a few frames. */
+ if (se401->framecount%20 == 1)
+ se401_auto_resetlevel(se401);
+
+ ret = se401_newframe(se401, 0);
+
+ se401->frame[0].grabstate = FRAME_UNUSED;
+ if (ret)
+ return ret;
+ if (copy_to_user(buf, se401->frame[0].data, realcount))
+ return -EFAULT;
+
+ return realcount;
+}
+
+static int se401_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct video_device *dev = file->private_data;
+ struct usb_se401 *se401 = (struct usb_se401 *)dev;
+ unsigned long start = vma->vm_start;
+ unsigned long size = vma->vm_end-vma->vm_start;
+ unsigned long page, pos;
+
+ mutex_lock(&se401->lock);
+
+ if (se401->dev == NULL) {
+ mutex_unlock(&se401->lock);
+ return -EIO;
+ }
+ if (size > (((SE401_NUMFRAMES * se401->maxframesize) + PAGE_SIZE - 1)
+ & ~(PAGE_SIZE - 1))) {
+ mutex_unlock(&se401->lock);
+ return -EINVAL;
+ }
+ pos = (unsigned long)se401->fbuf;
+ while (size > 0) {
+ page = vmalloc_to_pfn((void *)pos);
+ if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) {
+ mutex_unlock(&se401->lock);
+ return -EAGAIN;
+ }
+ start += PAGE_SIZE;
+ pos += PAGE_SIZE;
+ if (size > PAGE_SIZE)
+ size -= PAGE_SIZE;
+ else
+ size = 0;
+ }
+ mutex_unlock(&se401->lock);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations se401_fops = {
+ .owner = THIS_MODULE,
+ .open = se401_open,
+ .release = se401_close,
+ .read = se401_read,
+ .mmap = se401_mmap,
+ .ioctl = se401_ioctl,
+};
+static struct video_device se401_template = {
+ .name = "se401 USB camera",
+ .fops = &se401_fops,
+ .release = video_device_release_empty,
+};
+
+
+
+/***************************/
+static int se401_init(struct usb_se401 *se401, int button)
+{
+ int i = 0, rc;
+ unsigned char cp[0x40];
+ char temp[200];
+ int slen;
+
+ /* led on */
+ se401_sndctrl(1, se401, SE401_REQ_LED_CONTROL, 1, NULL, 0);
+
+ /* get camera descriptor */
+ rc = se401_sndctrl(0, se401, SE401_REQ_GET_CAMERA_DESCRIPTOR, 0,
+ cp, sizeof(cp));
+ if (cp[1] != 0x41) {
+ err("Wrong descriptor type");
+ return 1;
+ }
+ slen = snprintf(temp, 200, "ExtraFeatures: %d", cp[3]);
+
+ se401->sizes = cp[4] + cp[5] * 256;
+ se401->width = kmalloc(se401->sizes*sizeof(int), GFP_KERNEL);
+ if (!se401->width)
+ return 1;
+ se401->height = kmalloc(se401->sizes*sizeof(int), GFP_KERNEL);
+ if (!se401->height) {
+ kfree(se401->width);
+ return 1;
+ }
+ for (i = 0; i < se401->sizes; i++) {
+ se401->width[i] = cp[6 + i * 4 + 0] + cp[6 + i*4 + 1] * 256;
+ se401->height[i] = cp[6 + i * 4 + 2] + cp[6 + i * 4 + 3] * 256;
+ }
+ slen += snprintf(temp + slen, 200 - slen, " Sizes:");
+ for (i = 0; i < se401->sizes; i++) {
+ slen += snprintf(temp + slen, 200 - slen,
+ " %dx%d", se401->width[i], se401->height[i]);
+ }
+ dev_info(&se401->dev->dev, "%s\n", temp);
+ se401->maxframesize = se401->width[se401->sizes-1] *
+ se401->height[se401->sizes - 1] * 3;
+
+ rc = se401_sndctrl(0, se401, SE401_REQ_GET_WIDTH, 0, cp, sizeof(cp));
+ se401->cwidth = cp[0]+cp[1]*256;
+ rc = se401_sndctrl(0, se401, SE401_REQ_GET_HEIGHT, 0, cp, sizeof(cp));
+ se401->cheight = cp[0]+cp[1]*256;
+
+ if (!(cp[2] & SE401_FORMAT_BAYER)) {
+ err("Bayer format not supported!");
+ return 1;
+ }
+ /* set output mode (BAYER) */
+ se401_sndctrl(1, se401, SE401_REQ_SET_OUTPUT_MODE,
+ SE401_FORMAT_BAYER, NULL, 0);
+
+ rc = se401_sndctrl(0, se401, SE401_REQ_GET_BRT, 0, cp, sizeof(cp));
+ se401->brightness = cp[0]+cp[1]*256;
+ /* some default values */
+ se401->resetlevel = 0x2d;
+ se401->rgain = 0x20;
+ se401->ggain = 0x20;
+ se401->bgain = 0x20;
+ se401_set_exposure(se401, 20000);
+ se401->palette = VIDEO_PALETTE_RGB24;
+ se401->enhance = 1;
+ se401->dropped = 0;
+ se401->error = 0;
+ se401->framecount = 0;
+ se401->readcount = 0;
+
+ /* Start interrupt transfers for snapshot button */
+ if (button) {
+ se401->inturb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!se401->inturb) {
+ dev_info(&se401->dev->dev,
+ "Allocation of inturb failed\n");
+ return 1;
+ }
+ usb_fill_int_urb(se401->inturb, se401->dev,
+ usb_rcvintpipe(se401->dev, SE401_BUTTON_ENDPOINT),
+ &se401->button, sizeof(se401->button),
+ se401_button_irq,
+ se401,
+ 8
+ );
+ if (usb_submit_urb(se401->inturb, GFP_KERNEL)) {
+ dev_info(&se401->dev->dev, "int urb burned down\n");
+ return 1;
+ }
+ } else
+ se401->inturb = NULL;
+
+ /* Flash the led */
+ se401_sndctrl(1, se401, SE401_REQ_CAMERA_POWER, 1, NULL, 0);
+ se401_sndctrl(1, se401, SE401_REQ_LED_CONTROL, 1, NULL, 0);
+ se401_sndctrl(1, se401, SE401_REQ_CAMERA_POWER, 0, NULL, 0);
+ se401_sndctrl(1, se401, SE401_REQ_LED_CONTROL, 0, NULL, 0);
+
+ return 0;
+}
+
+static int se401_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *dev = interface_to_usbdev(intf);
+ struct usb_interface_descriptor *interface;
+ struct usb_se401 *se401;
+ char *camera_name = NULL;
+ int button = 1;
+
+ /* We don't handle multi-config cameras */
+ if (dev->descriptor.bNumConfigurations != 1)
+ return -ENODEV;
+
+ interface = &intf->cur_altsetting->desc;
+
+ /* Is it an se401? */
+ if (le16_to_cpu(dev->descriptor.idVendor) == 0x03e8 &&
+ le16_to_cpu(dev->descriptor.idProduct) == 0x0004) {
+ camera_name = "Endpoints/Aox SE401";
+ } else if (le16_to_cpu(dev->descriptor.idVendor) == 0x0471 &&
+ le16_to_cpu(dev->descriptor.idProduct) == 0x030b) {
+ camera_name = "Philips PCVC665K";
+ } else if (le16_to_cpu(dev->descriptor.idVendor) == 0x047d &&
+ le16_to_cpu(dev->descriptor.idProduct) == 0x5001) {
+ camera_name = "Kensington VideoCAM 67014";
+ } else if (le16_to_cpu(dev->descriptor.idVendor) == 0x047d &&
+ le16_to_cpu(dev->descriptor.idProduct) == 0x5002) {
+ camera_name = "Kensington VideoCAM 6701(5/7)";
+ } else if (le16_to_cpu(dev->descriptor.idVendor) == 0x047d &&
+ le16_to_cpu(dev->descriptor.idProduct) == 0x5003) {
+ camera_name = "Kensington VideoCAM 67016";
+ button = 0;
+ } else
+ return -ENODEV;
+
+ /* Checking vendor/product should be enough, but what the hell */
+ if (interface->bInterfaceClass != 0x00)
+ return -ENODEV;
+ if (interface->bInterfaceSubClass != 0x00)
+ return -ENODEV;
+
+ /* We found one */
+ dev_info(&intf->dev, "SE401 camera found: %s\n", camera_name);
+
+ se401 = kzalloc(sizeof(*se401), GFP_KERNEL);
+ if (se401 == NULL) {
+ err("couldn't kmalloc se401 struct");
+ return -ENOMEM;
+ }
+
+ se401->dev = dev;
+ se401->iface = interface->bInterfaceNumber;
+ se401->camera_name = camera_name;
+
+ dev_info(&intf->dev, "firmware version: %02x\n",
+ le16_to_cpu(dev->descriptor.bcdDevice) & 255);
+
+ if (se401_init(se401, button)) {
+ kfree(se401);
+ return -EIO;
+ }
+
+ memcpy(&se401->vdev, &se401_template, sizeof(se401_template));
+ memcpy(se401->vdev.name, se401->camera_name,
+ strlen(se401->camera_name));
+ init_waitqueue_head(&se401->wq);
+ mutex_init(&se401->lock);
+ wmb();
+
+ if (video_register_device(&se401->vdev,
+ VFL_TYPE_GRABBER, video_nr) < 0) {
+ kfree(se401);
+ err("video_register_device failed");
+ return -EIO;
+ }
+ dev_info(&intf->dev, "registered new video device: %s\n",
+ video_device_node_name(&se401->vdev));
+
+ usb_set_intfdata(intf, se401);
+ return 0;
+}
+
+static void se401_disconnect(struct usb_interface *intf)
+{
+ struct usb_se401 *se401 = usb_get_intfdata(intf);
+
+ usb_set_intfdata(intf, NULL);
+ if (se401) {
+ video_unregister_device(&se401->vdev);
+ if (!se401->user)
+ usb_se401_remove_disconnected(se401);
+ else {
+ se401->frame[0].grabstate = FRAME_ERROR;
+ se401->frame[0].grabstate = FRAME_ERROR;
+
+ se401->streaming = 0;
+
+ wake_up_interruptible(&se401->wq);
+ se401->removed = 1;
+ }
+ }
+}
+
+static struct usb_driver se401_driver = {
+ .name = "se401",
+ .id_table = device_table,
+ .probe = se401_probe,
+ .disconnect = se401_disconnect,
+};
+
+
+
+/****************************************************************************
+ *
+ * Module routines
+ *
+ ***************************************************************************/
+
+static int __init usb_se401_init(void)
+{
+ printk(KERN_INFO "SE401 usb camera driver version %s registering\n",
+ version);
+ if (flickerless)
+ if (flickerless != 50 && flickerless != 60) {
+ printk(KERN_ERR "Invallid flickerless value, use 0, 50 or 60.\n");
+ return -1;
+ }
+ return usb_register(&se401_driver);
+}
+
+static void __exit usb_se401_exit(void)
+{
+ usb_deregister(&se401_driver);
+ printk(KERN_INFO "SE401 driver deregistered\frame");
+}
+
+module_init(usb_se401_init);
+module_exit(usb_se401_exit);
diff --git a/drivers/staging/se401/se401.h b/drivers/staging/se401/se401.h
new file mode 100644
index 00000000000..bf7d2e9765b
--- /dev/null
+++ b/drivers/staging/se401/se401.h
@@ -0,0 +1,236 @@
+
+#ifndef __LINUX_se401_H
+#define __LINUX_se401_H
+
+#include <linux/uaccess.h>
+#include <linux/videodev.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <linux/mutex.h>
+
+#define se401_DEBUG /* Turn on debug messages */
+
+#ifdef se401_DEBUG
+# define PDEBUG(level, fmt, args...) \
+if (debug >= level) \
+ info("[" __PRETTY_FUNCTION__ ":%d] " fmt, __LINE__ , ## args)
+#else
+# define PDEBUG(level, fmt, args...) do {} while (0)
+#endif
+
+/* An almost drop-in replacement for sleep_on_interruptible */
+#define wait_interruptible(test, queue, wait) \
+{ \
+ add_wait_queue(queue, wait); \
+ set_current_state(TASK_INTERRUPTIBLE); \
+ if (test) \
+ schedule(); \
+ remove_wait_queue(queue, wait); \
+ set_current_state(TASK_RUNNING); \
+ if (signal_pending(current)) \
+ break; \
+}
+
+#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
+
+
+/* size of usb transfers */
+#define SE401_PACKETSIZE 4096
+/* number of queued bulk transfers to use, should be about 8 */
+#define SE401_NUMSBUF 1
+/* read the usb specs for this one :) */
+#define SE401_VIDEO_ENDPOINT 1
+#define SE401_BUTTON_ENDPOINT 2
+/* number of frames supported by the v4l part */
+#define SE401_NUMFRAMES 2
+/* scratch buffers for passing data to the decoders */
+#define SE401_NUMSCRATCH 32
+/* maximum amount of data in a JangGu packet */
+#define SE401_VLCDATALEN 1024
+/* number of nul sized packets to receive before kicking the camera */
+#define SE401_MAX_NULLPACKETS 4000
+/* number of decoding errors before kicking the camera */
+#define SE401_MAX_ERRORS 200
+
+struct usb_device;
+
+struct se401_sbuf {
+ unsigned char *data;
+};
+
+enum {
+ FRAME_UNUSED, /* Unused (no MCAPTURE) */
+ FRAME_READY, /* Ready to start grabbing */
+ FRAME_GRABBING, /* In the process of being grabbed into */
+ FRAME_DONE, /* Finished grabbing, but not been synced yet */
+ FRAME_ERROR, /* Something bad happened while processing */
+};
+
+enum {
+ FMT_BAYER,
+ FMT_JANGGU,
+};
+
+enum {
+ BUFFER_UNUSED,
+ BUFFER_READY,
+ BUFFER_BUSY,
+ BUFFER_DONE,
+};
+
+struct se401_scratch {
+ unsigned char *data;
+ volatile int state;
+ int offset;
+ int length;
+};
+
+struct se401_frame {
+ unsigned char *data; /* Frame buffer */
+
+ volatile int grabstate; /* State of grabbing */
+
+ unsigned char *curline;
+ int curlinepix;
+ int curpix;
+};
+
+struct usb_se401 {
+ struct video_device vdev;
+
+ /* Device structure */
+ struct usb_device *dev;
+
+ unsigned char iface;
+
+ char *camera_name;
+
+ int change;
+ int brightness;
+ int hue;
+ int rgain;
+ int ggain;
+ int bgain;
+ int expose_h;
+ int expose_m;
+ int expose_l;
+ int resetlevel;
+
+ int enhance;
+
+ int format;
+ int sizes;
+ int *width;
+ int *height;
+ int cwidth; /* current width */
+ int cheight; /* current height */
+ int palette;
+ int maxframesize;
+ int cframesize; /* current framesize */
+
+ struct mutex lock;
+ int user; /* user count for exclusive use */
+ int removed; /* device disconnected */
+
+ int streaming; /* Are we streaming video? */
+
+ char *fbuf; /* Videodev buffer area */
+
+ struct urb *urb[SE401_NUMSBUF];
+ struct urb *inturb;
+
+ int button;
+ int buttonpressed;
+
+ int curframe; /* Current receiving frame */
+ struct se401_frame frame[SE401_NUMFRAMES];
+ int readcount;
+ int framecount;
+ int error;
+ int dropped;
+
+ int scratch_next;
+ int scratch_use;
+ int scratch_overflow;
+ struct se401_scratch scratch[SE401_NUMSCRATCH];
+
+ /* Decoder specific data: */
+ unsigned char vlcdata[SE401_VLCDATALEN];
+ int vlcdatapos;
+ int bayeroffset;
+
+ struct se401_sbuf sbuf[SE401_NUMSBUF];
+
+ wait_queue_head_t wq; /* Processes waiting */
+
+ int nullpackets;
+};
+
+
+
+#endif
+