V4L/DVB (9682): gspca: New subdriver parameter 'bulk_nurbs'.
[safe/jmp/linux-2.6] / drivers / media / video / gspca / gspca.c
index 3461bc9..a9a4a5f 100644 (file)
@@ -21,6 +21,7 @@
 #define MODULE_NAME "gspca"
 
 #include <linux/init.h>
+#include <linux/version.h>
 #include <linux/fs.h>
 #include <linux/vmalloc.h>
 #include <linux/sched.h>
@@ -29,6 +30,7 @@
 #include <linux/string.h>
 #include <linux/pagemap.h>
 #include <linux/io.h>
+#include <linux/kref.h>
 #include <asm/page.h>
 #include <linux/uaccess.h>
 #include <linux/jiffies.h>
@@ -43,7 +45,7 @@ MODULE_AUTHOR("Jean-Francois Moine <http://moinejf.free.fr>");
 MODULE_DESCRIPTION("GSPCA USB Camera Driver");
 MODULE_LICENSE("GPL");
 
-#define DRIVER_VERSION_NUMBER  KERNEL_VERSION(2, 2, 0)
+#define DRIVER_VERSION_NUMBER  KERNEL_VERSION(2, 4, 0)
 
 static int video_nr = -1;
 
@@ -102,6 +104,22 @@ static struct vm_operations_struct gspca_vm_ops = {
        .close          = gspca_vm_close,
 };
 
+/* get the current input frame buffer */
+struct gspca_frame *gspca_get_i_frame(struct gspca_dev *gspca_dev)
+{
+       struct gspca_frame *frame;
+       int i;
+
+       i = gspca_dev->fr_i;
+       i = gspca_dev->fr_queue[i];
+       frame = &gspca_dev->frame[i];
+       if ((frame->v4l2_buf.flags & BUF_ALL_FLAGS)
+                               != V4L2_BUF_FLAG_QUEUED)
+               return NULL;
+       return frame;
+}
+EXPORT_SYMBOL(gspca_get_i_frame);
+
 /*
  * fill a video frame from an URB and resubmit
  */
@@ -110,11 +128,13 @@ static void fill_frame(struct gspca_dev *gspca_dev,
 {
        struct gspca_frame *frame;
        __u8 *data;             /* address of data in the iso message */
-       int i, j, len, st;
+       int i, len, st;
        cam_pkt_op pkt_scan;
 
        if (urb->status != 0) {
+#ifdef CONFIG_PM
                if (!gspca_dev->frozen)
+#endif
                        PDEBUG(D_ERR|D_PACK, "urb status: %d", urb->status);
                return;         /* disconnection ? */
        }
@@ -122,19 +142,19 @@ static void fill_frame(struct gspca_dev *gspca_dev,
        for (i = 0; i < urb->number_of_packets; i++) {
 
                /* check the availability of the frame buffer */
-               j = gspca_dev->fr_i;
-               j = gspca_dev->fr_queue[j];
-               frame = &gspca_dev->frame[j];
-               if ((frame->v4l2_buf.flags & BUF_ALL_FLAGS)
-                                       != V4L2_BUF_FLAG_QUEUED) {
+               frame = gspca_get_i_frame(gspca_dev);
+               if (!frame) {
                        gspca_dev->last_packet_type = DISCARD_PACKET;
                        break;
                }
 
                /* check the packet status and length */
                len = urb->iso_frame_desc[i].actual_length;
-               if (len == 0)
+               if (len == 0) {
+                       if (gspca_dev->empty_packet == 0)
+                               gspca_dev->empty_packet = 1;
                        continue;
+               }
                st = urb->iso_frame_desc[i].status;
                if (st) {
                        PDEBUG(D_ERR,
@@ -176,6 +196,47 @@ static void isoc_irq(struct urb *urb
 }
 
 /*
+ * bulk message interrupt from the USB device
+ */
+static void bulk_irq(struct urb *urb
+)
+{
+       struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context;
+       struct gspca_frame *frame;
+       int st;
+
+       PDEBUG(D_PACK, "bulk irq");
+       if (!gspca_dev->streaming)
+               return;
+       if (urb->status != 0 && urb->status != -ECONNRESET) {
+#ifdef CONFIG_PM
+               if (!gspca_dev->frozen)
+#endif
+                       PDEBUG(D_ERR|D_PACK, "urb status: %d", urb->status);
+               return;         /* disconnection ? */
+       }
+
+       /* check the availability of the frame buffer */
+       frame = gspca_get_i_frame(gspca_dev);
+       if (!frame) {
+               gspca_dev->last_packet_type = DISCARD_PACKET;
+       } else {
+               PDEBUG(D_PACK, "packet l:%d", urb->actual_length);
+               gspca_dev->sd_desc->pkt_scan(gspca_dev,
+                                       frame,
+                                       urb->transfer_buffer,
+                                       urb->actual_length);
+       }
+
+       /* resubmit the URB */
+       if (gspca_dev->cam.bulk_nurbs != 0) {
+               st = usb_submit_urb(urb, GFP_ATOMIC);
+               if (st < 0)
+                       PDEBUG(D_ERR|D_PACK, "usb_submit_urb() ret %d", st);
+       }
+}
+
+/*
  * add data to the current frame
  *
  * This function is called by the subdrivers at interrupt level.
@@ -188,7 +249,7 @@ static void isoc_irq(struct urb *urb
  * On LAST_PACKET, a new frame is returned.
  */
 struct gspca_frame *gspca_frame_add(struct gspca_dev *gspca_dev,
-                                   int packet_type,
+                                   enum gspca_packet_type packet_type,
                                    struct gspca_frame *frame,
                                    const __u8 *data,
                                    int len)
@@ -230,7 +291,7 @@ struct gspca_frame *gspca_frame_add(struct gspca_dev *gspca_dev,
        }
        gspca_dev->last_packet_type = packet_type;
 
-       /* if last packet, wake the application and advance in the queue */
+       /* if last packet, wake up the application and advance in the queue */
        if (packet_type == LAST_PACKET) {
                frame->v4l2_buf.bytesused = frame->data_end - frame->data;
                frame->v4l2_buf.flags &= ~V4L2_BUF_FLAG_QUEUED;
@@ -268,7 +329,6 @@ static void *rvmalloc(unsigned long size)
        void *mem;
        unsigned long adr;
 
-/*     size = PAGE_ALIGN(size);        (already done) */
        mem = vmalloc_32(size);
        if (mem != NULL) {
                adr = (unsigned long) mem;
@@ -355,7 +415,7 @@ static void destroy_urbs(struct gspca_dev *gspca_dev)
        unsigned int i;
 
        PDEBUG(D_STREAM, "kill transfer");
-       for (i = 0; i < MAX_NURBS; ++i) {
+       for (i = 0; i < MAX_NURBS; i++) {
                urb = gspca_dev->urb[i];
                if (urb == NULL)
                        break;
@@ -372,10 +432,11 @@ static void destroy_urbs(struct gspca_dev *gspca_dev)
 }
 
 /*
- * search an input isochronous endpoint in an alternate setting
+ * look for an input transfer endpoint in an alternate setting
  */
-static struct usb_host_endpoint *alt_isoc(struct usb_host_interface *alt,
-                                         __u8 epaddr)
+static struct usb_host_endpoint *alt_xfer(struct usb_host_interface *alt,
+                                         __u8 epaddr,
+                                         __u8 xfer)
 {
        struct usb_host_endpoint *ep;
        int i, attr;
@@ -386,7 +447,7 @@ static struct usb_host_endpoint *alt_isoc(struct usb_host_interface *alt,
                if (ep->desc.bEndpointAddress == epaddr) {
                        attr = ep->desc.bmAttributes
                                                & USB_ENDPOINT_XFERTYPE_MASK;
-                       if (attr == USB_ENDPOINT_XFER_ISOC)
+                       if (attr == xfer)
                                return ep;
                        break;
                }
@@ -395,14 +456,14 @@ static struct usb_host_endpoint *alt_isoc(struct usb_host_interface *alt,
 }
 
 /*
- * search an input isochronous endpoint
+ * look for an input (isoc or bulk) endpoint
  *
  * The endpoint is defined by the subdriver.
  * Use only the first isoc (some Zoran - 0x0572:0x0001 - have two such ep).
  * This routine may be called many times when the bandwidth is too small
  * (the bandwidth is checked on urb submit).
  */
-static struct usb_host_endpoint *get_isoc_ep(struct gspca_dev *gspca_dev)
+static struct usb_host_endpoint *get_ep(struct gspca_dev *gspca_dev)
 {
        struct usb_interface *intf;
        struct usb_host_endpoint *ep;
@@ -411,28 +472,41 @@ static struct usb_host_endpoint *get_isoc_ep(struct gspca_dev *gspca_dev)
        intf = usb_ifnum_to_if(gspca_dev->dev, gspca_dev->iface);
        ep = NULL;
        i = gspca_dev->alt;                     /* previous alt setting */
+
+       /* try isoc */
        while (--i > 0) {                       /* alt 0 is unusable */
-               ep = alt_isoc(&intf->altsetting[i], gspca_dev->cam.epaddr);
+               ep = alt_xfer(&intf->altsetting[i],
+                               gspca_dev->cam.epaddr,
+                               USB_ENDPOINT_XFER_ISOC);
                if (ep)
                        break;
        }
+
+       /* if no isoc, try bulk */
        if (ep == NULL) {
-               err("no ISOC endpoint found");
-               return NULL;
+               ep = alt_xfer(&intf->altsetting[0],
+                               gspca_dev->cam.epaddr,
+                               USB_ENDPOINT_XFER_BULK);
+               if (ep == NULL) {
+                       err("no transfer endpoint found");
+                       return NULL;
+               }
        }
-       PDEBUG(D_STREAM, "use ISOC alt %d ep 0x%02x",
+       PDEBUG(D_STREAM, "use alt %d ep 0x%02x",
                        i, ep->desc.bEndpointAddress);
-       ret = usb_set_interface(gspca_dev->dev, gspca_dev->iface, i);
-       if (ret < 0) {
-               err("set interface err %d", ret);
-               return NULL;
+       if (i > 0) {
+               ret = usb_set_interface(gspca_dev->dev, gspca_dev->iface, i);
+               if (ret < 0) {
+                       err("set interface err %d", ret);
+                       return NULL;
+               }
        }
        gspca_dev->alt = i;             /* memorize the current alt setting */
        return ep;
 }
 
 /*
- * create the isochronous URBs
+ * create the URBs for image transfer
  */
 static int create_urbs(struct gspca_dev *gspca_dev,
                        struct usb_host_endpoint *ep)
@@ -443,20 +517,36 @@ static int create_urbs(struct gspca_dev *gspca_dev,
        /* calculate the packet size and the number of packets */
        psize = le16_to_cpu(ep->desc.wMaxPacketSize);
 
-       /* See paragraph 5.9 / table 5-11 of the usb 2.0 spec. */
-       psize = (psize & 0x07ff) * (1 + ((psize >> 11) & 3));
-       npkt = ISO_MAX_SIZE / psize;
-       if (npkt > ISO_MAX_PKT)
-               npkt = ISO_MAX_PKT;
-       bsize = psize * npkt;
-       PDEBUG(D_STREAM,
-               "isoc %d pkts size %d (bsize:%d)", npkt, psize, bsize);
-       nurbs = DEF_NURBS;
+       if (gspca_dev->alt != 0) {              /* isoc */
+
+               /* See paragraph 5.9 / table 5-11 of the usb 2.0 spec. */
+               psize = (psize & 0x07ff) * (1 + ((psize >> 11) & 3));
+               npkt = ISO_MAX_SIZE / psize;
+               if (npkt > ISO_MAX_PKT)
+                       npkt = ISO_MAX_PKT;
+               bsize = psize * npkt;
+               PDEBUG(D_STREAM,
+                       "isoc %d pkts size %d = bsize:%d",
+                       npkt, psize, bsize);
+               nurbs = DEF_NURBS;
+       } else {                                /* bulk */
+               npkt = 0;
+               bsize = gspca_dev->cam.bulk_size;
+               if (bsize == 0)
+                       bsize = psize;
+               PDEBUG(D_STREAM, "bulk bsize:%d", bsize);
+               if (gspca_dev->cam.bulk_nurbs != 0)
+                       nurbs = gspca_dev->cam.bulk_nurbs;
+               else
+                       nurbs = 1;
+       }
+
        gspca_dev->nurbs = nurbs;
        for (n = 0; n < nurbs; n++) {
                urb = usb_alloc_urb(npkt, GFP_KERNEL);
                if (!urb) {
                        err("usb_alloc_urb failed");
+                       destroy_urbs(gspca_dev);
                        return -ENOMEM;
                }
                urb->transfer_buffer = usb_buffer_alloc(gspca_dev->dev,
@@ -466,24 +556,31 @@ static int create_urbs(struct gspca_dev *gspca_dev,
 
                if (urb->transfer_buffer == NULL) {
                        usb_free_urb(urb);
-                       destroy_urbs(gspca_dev);
                        err("usb_buffer_urb failed");
+                       destroy_urbs(gspca_dev);
                        return -ENOMEM;
                }
                gspca_dev->urb[n] = urb;
                urb->dev = gspca_dev->dev;
                urb->context = gspca_dev;
-               urb->pipe = usb_rcvisocpipe(gspca_dev->dev,
-                                           ep->desc.bEndpointAddress);
-               urb->transfer_flags = URB_ISO_ASAP
-                                       | URB_NO_TRANSFER_DMA_MAP;
-               urb->interval = ep->desc.bInterval;
-               urb->complete = isoc_irq;
-               urb->number_of_packets = npkt;
                urb->transfer_buffer_length = bsize;
-               for (i = 0; i < npkt; i++) {
-                       urb->iso_frame_desc[i].length = psize;
-                       urb->iso_frame_desc[i].offset = psize * i;
+               if (npkt != 0) {                /* ISOC */
+                       urb->pipe = usb_rcvisocpipe(gspca_dev->dev,
+                                                   ep->desc.bEndpointAddress);
+                       urb->transfer_flags = URB_ISO_ASAP
+                                       | URB_NO_TRANSFER_DMA_MAP;
+                       urb->interval = ep->desc.bInterval;
+                       urb->complete = isoc_irq;
+                       urb->number_of_packets = npkt;
+                       for (i = 0; i < npkt; i++) {
+                               urb->iso_frame_desc[i].length = psize;
+                               urb->iso_frame_desc[i].offset = psize * i;
+                       }
+               } else {                /* bulk */
+                       urb->pipe = usb_rcvbulkpipe(gspca_dev->dev,
+                                               ep->desc.bEndpointAddress),
+                       urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+                       urb->complete = bulk_irq;
                }
        }
        return 0;
@@ -505,7 +602,7 @@ static int gspca_init_transfer(struct gspca_dev *gspca_dev)
        gspca_dev->alt = gspca_dev->nbalt;
        for (;;) {
                PDEBUG(D_STREAM, "init transfer alt %d", gspca_dev->alt);
-               ep = get_isoc_ep(gspca_dev);
+               ep = get_ep(gspca_dev);
                if (ep == NULL) {
                        ret = -EIO;
                        goto out;
@@ -514,11 +611,25 @@ static int gspca_init_transfer(struct gspca_dev *gspca_dev)
                if (ret < 0)
                        goto out;
 
+               /* clear the bulk endpoint */
+               if (gspca_dev->alt == 0)        /* if bulk transfer */
+                       usb_clear_halt(gspca_dev->dev,
+                                       usb_rcvintpipe(gspca_dev->dev,
+                                                gspca_dev->cam.epaddr));
+
                /* start the cam */
-               gspca_dev->sd_desc->start(gspca_dev);
+               ret = gspca_dev->sd_desc->start(gspca_dev);
+               if (ret < 0) {
+                       destroy_urbs(gspca_dev);
+                       goto out;
+               }
                gspca_dev->streaming = 1;
                atomic_set(&gspca_dev->nevent, 0);
 
+               /* some bulk transfers are started by the subdriver */
+               if (gspca_dev->alt == 0 && gspca_dev->cam.bulk_nurbs == 0)
+                       break;
+
                /* submit the URBs */
                for (n = 0; n < gspca_dev->nurbs; n++) {
                        ret = usb_submit_urb(gspca_dev->urb[n], GFP_KERNEL);
@@ -550,18 +661,19 @@ static int gspca_set_alt0(struct gspca_dev *gspca_dev)
        return ret;
 }
 
-/* Note both the queue and the usb lock should be hold when calling this */
+/* Note: both the queue and the usb locks should be held when calling this */
 static void gspca_stream_off(struct gspca_dev *gspca_dev)
 {
        gspca_dev->streaming = 0;
        atomic_set(&gspca_dev->nevent, 0);
-       if (gspca_dev->present) {
+       if (gspca_dev->present
+           && gspca_dev->sd_desc->stopN)
                gspca_dev->sd_desc->stopN(gspca_dev);
-               destroy_urbs(gspca_dev);
-               gspca_set_alt0(gspca_dev);
+       destroy_urbs(gspca_dev);
+       gspca_set_alt0(gspca_dev);
+       if (gspca_dev->sd_desc->stop0)
                gspca_dev->sd_desc->stop0(gspca_dev);
-               PDEBUG(D_STREAM, "stream off OK");
-       }
+       PDEBUG(D_STREAM, "stream off OK");
 }
 
 static void gspca_set_default_mode(struct gspca_dev *gspca_dev)
@@ -635,7 +747,7 @@ static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
                        if (fmtdesc->index == index)
                                break;          /* new format */
                        index++;
-                       if (index >= sizeof fmt_tb / sizeof fmt_tb[0])
+                       if (index >= ARRAY_SIZE(fmt_tb))
                                return -EINVAL;
                }
        }
@@ -754,6 +866,16 @@ out:
        return ret;
 }
 
+static void gspca_delete(struct kref *kref)
+{
+       struct gspca_dev *gspca_dev = container_of(kref, struct gspca_dev, kref);
+
+       PDEBUG(D_STREAM, "device deleted");
+
+       kfree(gspca_dev->usb_buf);
+       kfree(gspca_dev);
+}
+
 static int dev_open(struct inode *inode, struct file *file)
 {
        struct gspca_dev *gspca_dev;
@@ -768,31 +890,33 @@ static int dev_open(struct inode *inode, struct file *file)
                goto out;
        }
 
-       /* if not done yet, initialize the sensor */
-       if (gspca_dev->users == 0) {
-               if (mutex_lock_interruptible(&gspca_dev->usb_lock)) {
-                       ret = -ERESTARTSYS;
-                       goto out;
-               }
-               ret = gspca_dev->sd_desc->open(gspca_dev);
-               mutex_unlock(&gspca_dev->usb_lock);
-               if (ret != 0) {
-                       PDEBUG(D_ERR|D_CONF, "init device failed %d", ret);
-                       goto out;
-               }
-       } else if (gspca_dev->users > 4) {      /* (arbitrary value) */
+       if (gspca_dev->users > 4) {     /* (arbitrary value) */
                ret = -EBUSY;
                goto out;
        }
+
+       /* protect the subdriver against rmmod */
+       if (!try_module_get(gspca_dev->module)) {
+               ret = -ENODEV;
+               goto out;
+       }
+
        gspca_dev->users++;
+
+       /* one more user */
+       kref_get(&gspca_dev->kref);
+
        file->private_data = gspca_dev;
 #ifdef GSPCA_DEBUG
        /* activate the v4l2 debug */
        if (gspca_debug & D_V4L2)
-               gspca_dev->vdev.debug |= 3;
+               gspca_dev->vdev.debug |= V4L2_DEBUG_IOCTL
+                                       | V4L2_DEBUG_IOCTL_ARG;
        else
-               gspca_dev->vdev.debug &= ~3;
+               gspca_dev->vdev.debug &= ~(V4L2_DEBUG_IOCTL
+                                       | V4L2_DEBUG_IOCTL_ARG);
 #endif
+       ret = 0;
 out:
        mutex_unlock(&gspca_dev->queue_lock);
        if (ret != 0)
@@ -813,18 +937,23 @@ static int dev_close(struct inode *inode, struct file *file)
 
        /* if the file did the capture, free the streaming resources */
        if (gspca_dev->capt_file == file) {
-               mutex_lock(&gspca_dev->usb_lock);
-               if (gspca_dev->streaming)
+               if (gspca_dev->streaming) {
+                       mutex_lock(&gspca_dev->usb_lock);
                        gspca_stream_off(gspca_dev);
-               gspca_dev->sd_desc->close(gspca_dev);
-               mutex_unlock(&gspca_dev->usb_lock);
+                       mutex_unlock(&gspca_dev->usb_lock);
+               }
                frame_free(gspca_dev);
                gspca_dev->capt_file = NULL;
                gspca_dev->memory = GSPCA_MEMORY_NO;
        }
        file->private_data = NULL;
+       module_put(gspca_dev->module);
        mutex_unlock(&gspca_dev->queue_lock);
+
        PDEBUG(D_STREAM, "close done");
+
+       kref_put(&gspca_dev->kref, gspca_delete);
+
        return 0;
 }
 
@@ -835,7 +964,6 @@ static int vidioc_querycap(struct file *file, void  *priv,
 
        memset(cap, 0, sizeof *cap);
        strncpy(cap->driver, gspca_dev->sd_desc->name, sizeof cap->driver);
-/*     strncpy(cap->card, gspca_dev->cam.dev_name, sizeof cap->card); */
        if (gspca_dev->dev->product != NULL) {
                strncpy(cap->card, gspca_dev->dev->product,
                        sizeof cap->card);
@@ -851,42 +979,47 @@ static int vidioc_querycap(struct file *file, void  *priv,
        cap->capabilities = V4L2_CAP_VIDEO_CAPTURE
                          | V4L2_CAP_STREAMING
                          | V4L2_CAP_READWRITE;
-       if (gspca_dev->flags & GSPCA_SENSOR_UPSIDE_DOWN_FLAG)
-               cap->capabilities |= V4L2_CAP_SENSOR_UPSIDE_DOWN;
        return 0;
 }
 
-/* the use of V4L2_CTRL_FLAG_NEXT_CTRL asks for the controls to be sorted */
 static int vidioc_queryctrl(struct file *file, void *priv,
                           struct v4l2_queryctrl *q_ctrl)
 {
        struct gspca_dev *gspca_dev = priv;
-       int i;
+       int i, ix;
        u32 id;
 
+       ix = -1;
        id = q_ctrl->id;
        if (id & V4L2_CTRL_FLAG_NEXT_CTRL) {
                id &= V4L2_CTRL_ID_MASK;
                id++;
                for (i = 0; i < gspca_dev->sd_desc->nctrls; i++) {
-                       if (id >= gspca_dev->sd_desc->ctrls[i].qctrl.id) {
-                               memcpy(q_ctrl,
-                                       &gspca_dev->sd_desc->ctrls[i].qctrl,
-                                       sizeof *q_ctrl);
-                               return 0;
+                       if (gspca_dev->sd_desc->ctrls[i].qctrl.id < id)
+                               continue;
+                       if (ix < 0) {
+                               ix = i;
+                               continue;
                        }
+                       if (gspca_dev->sd_desc->ctrls[i].qctrl.id
+                                   > gspca_dev->sd_desc->ctrls[ix].qctrl.id)
+                               continue;
+                       ix = i;
                }
-               return -EINVAL;
        }
        for (i = 0; i < gspca_dev->sd_desc->nctrls; i++) {
                if (id == gspca_dev->sd_desc->ctrls[i].qctrl.id) {
-                       memcpy(q_ctrl,
-                               &gspca_dev->sd_desc->ctrls[i].qctrl,
-                               sizeof *q_ctrl);
-                       return 0;
+                       ix = i;
+                       break;
                }
        }
-       return -EINVAL;
+       if (ix < 0)
+               return -EINVAL;
+       memcpy(q_ctrl, &gspca_dev->sd_desc->ctrls[ix].qctrl,
+               sizeof *q_ctrl);
+       if (gspca_dev->ctrl_dis & (1 << ix))
+               q_ctrl->flags |= V4L2_CTRL_FLAG_DISABLED;
+       return 0;
 }
 
 static int vidioc_s_ctrl(struct file *file, void *priv,
@@ -901,6 +1034,8 @@ static int vidioc_s_ctrl(struct file *file, void *priv,
             i++, ctrls++) {
                if (ctrl->id != ctrls->qctrl.id)
                        continue;
+               if (gspca_dev->ctrl_dis & (1 << i))
+                       return -EINVAL;
                if (ctrl->value < ctrls->qctrl.minimum
                    || ctrl->value > ctrls->qctrl.maximum)
                        return -ERANGE;
@@ -927,6 +1062,8 @@ static int vidioc_g_ctrl(struct file *file, void *priv,
             i++, ctrls++) {
                if (ctrl->id != ctrls->qctrl.id)
                        continue;
+               if (gspca_dev->ctrl_dis & (1 << i))
+                       return -EINVAL;
                if (mutex_lock_interruptible(&gspca_dev->usb_lock))
                        return -ERESTARTSYS;
                ret = ctrls->get(gspca_dev, &ctrl->value);
@@ -1147,10 +1284,10 @@ static int vidioc_s_jpegcomp(struct file *file, void *priv,
        struct gspca_dev *gspca_dev = priv;
        int ret;
 
-       if (mutex_lock_interruptible(&gspca_dev->usb_lock))
-               return -ERESTARTSYS;
        if (!gspca_dev->sd_desc->set_jcomp)
                return -EINVAL;
+       if (mutex_lock_interruptible(&gspca_dev->usb_lock))
+               return -ERESTARTSYS;
        ret = gspca_dev->sd_desc->set_jcomp(gspca_dev, jpegcomp);
        mutex_unlock(&gspca_dev->usb_lock);
        return ret;
@@ -1401,7 +1538,7 @@ static int vidioc_dqbuf(struct file *file, void *priv,
        i = ret;                                /* frame index */
        frame = &gspca_dev->frame[i];
        if (gspca_dev->memory == V4L2_MEMORY_USERPTR) {
-               if (copy_to_user((__u8 *) frame->v4l2_buf.m.userptr,
+               if (copy_to_user((__u8 __user *) frame->v4l2_buf.m.userptr,
                                 frame->data,
                                 frame->v4l2_buf.bytesused)) {
                        PDEBUG(D_ERR|D_STREAM,
@@ -1460,7 +1597,6 @@ static int vidioc_qbuf(struct file *file, void *priv,
        }
 
        frame->v4l2_buf.flags |= V4L2_BUF_FLAG_QUEUED;
-/*     frame->v4l2_buf.flags &= ~V4L2_BUF_FLAG_DONE; */
 
        if (frame->v4l2_buf.memory == V4L2_MEMORY_USERPTR) {
                frame->v4l2_buf.m.userptr = v4l2_buf->m.userptr;
@@ -1607,7 +1743,7 @@ static ssize_t dev_read(struct file *file, char __user *data,
                }
 
                /* if the process slept for more than 1 second,
-                * get anewer frame */
+                * get a newer frame */
                frame = &gspca_dev->frame[v4l2_buf.index];
                if (--n < 0)
                        break;                  /* avoid infinite loop */
@@ -1639,11 +1775,6 @@ out:
        return ret;
 }
 
-static void dev_release(struct video_device *vfd)
-{
-       /* nothing */
-}
-
 static struct file_operations dev_fops = {
        .owner = THIS_MODULE,
        .open = dev_open,
@@ -1691,7 +1822,7 @@ static struct video_device gspca_template = {
        .name = "gspca main driver",
        .fops = &dev_fops,
        .ioctl_ops = &dev_ioctl_ops,
-       .release = dev_release,         /* mandatory */
+       .release = video_device_release,
        .minor = -1,
 };
 
@@ -1725,21 +1856,31 @@ int gspca_dev_probe(struct usb_interface *intf,
        if (dev_size < sizeof *gspca_dev)
                dev_size = sizeof *gspca_dev;
        gspca_dev = kzalloc(dev_size, GFP_KERNEL);
-       if (gspca_dev == NULL) {
+       if (!gspca_dev) {
                err("couldn't kzalloc gspca struct");
-               return -EIO;
+               return -ENOMEM;
+       }
+       kref_init(&gspca_dev->kref);
+       gspca_dev->usb_buf = kmalloc(USB_BUF_SZ, GFP_KERNEL);
+       if (!gspca_dev->usb_buf) {
+               err("out of memory");
+               ret = -ENOMEM;
+               goto out;
        }
        gspca_dev->dev = dev;
        gspca_dev->iface = interface->bInterfaceNumber;
        gspca_dev->nbalt = intf->num_altsetting;
        gspca_dev->sd_desc = sd_desc;
-/*     gspca_dev->users = 0;                   (done by kzalloc) */
        gspca_dev->nbufread = 2;
+       gspca_dev->empty_packet = -1;   /* don't check the empty packets */
 
-       /* configure the subdriver */
+       /* configure the subdriver and initialize the USB device */
        ret = gspca_dev->sd_desc->config(gspca_dev, id);
        if (ret < 0)
                goto out;
+       ret = gspca_dev->sd_desc->init(gspca_dev);
+       if (ret < 0)
+               goto out;
        ret = gspca_set_alt0(gspca_dev);
        if (ret < 0)
                goto out;
@@ -1751,11 +1892,10 @@ int gspca_dev_probe(struct usb_interface *intf,
        init_waitqueue_head(&gspca_dev->wq);
 
        /* init video stuff */
-       memcpy(&gspca_dev->vdev, &gspca_template, sizeof gspca_template);
-       gspca_dev->vdev.parent = &dev->dev;
-       memcpy(&gspca_dev->fops, &dev_fops, sizeof gspca_dev->fops);
-       gspca_dev->vdev.fops = &gspca_dev->fops;
-       gspca_dev->fops.owner = module;         /* module protection */
+       gspca_dev->vdev = video_device_alloc();
+       memcpy(gspca_dev->vdev, &gspca_template, sizeof gspca_template);
+       gspca_dev->vdev->parent = &dev->dev;
+       gspca_dev->module = module;
        gspca_dev->present = 1;
        ret = video_register_device(&gspca_dev->vdev,
                                  VFL_TYPE_GRABBER,
@@ -1769,6 +1909,7 @@ int gspca_dev_probe(struct usb_interface *intf,
        PDEBUG(D_PROBE, "probe ok");
        return 0;
 out:
+       kfree(gspca_dev->usb_buf);
        kfree(gspca_dev);
        return ret;
 }
@@ -1784,24 +1925,14 @@ void gspca_disconnect(struct usb_interface *intf)
 {
        struct gspca_dev *gspca_dev = usb_get_intfdata(intf);
 
-       if (!gspca_dev)
-               return;
-       gspca_dev->present = 0;
-       mutex_lock(&gspca_dev->queue_lock);
-       mutex_lock(&gspca_dev->usb_lock);
-       gspca_dev->streaming = 0;
-       destroy_urbs(gspca_dev);
-       mutex_unlock(&gspca_dev->usb_lock);
-       mutex_unlock(&gspca_dev->queue_lock);
-       while (gspca_dev->users != 0) {         /* wait until fully closed */
-               atomic_inc(&gspca_dev->nevent);
-               wake_up_interruptible(&gspca_dev->wq);  /* wake processes */
-               schedule();
-       }
-/* We don't want people trying to open up the device */
+       usb_set_intfdata(intf, NULL);
+
+       /* release the device */
+       /* (this will call gspca_release() immediatly or on last close) */
        video_unregister_device(&gspca_dev->vdev);
-/* Free the memory */
-       kfree(gspca_dev);
+
+       kref_put(&gspca_dev->kref, gspca_delete);
+
        PDEBUG(D_PROBE, "disconnect complete");
 }
 EXPORT_SYMBOL(gspca_disconnect);
@@ -1814,10 +1945,12 @@ int gspca_suspend(struct usb_interface *intf, pm_message_t message)
        if (!gspca_dev->streaming)
                return 0;
        gspca_dev->frozen = 1;          /* avoid urb error messages */
-       gspca_dev->sd_desc->stopN(gspca_dev);
+       if (gspca_dev->sd_desc->stopN)
+               gspca_dev->sd_desc->stopN(gspca_dev);
        destroy_urbs(gspca_dev);
        gspca_set_alt0(gspca_dev);
-       gspca_dev->sd_desc->stop0(gspca_dev);
+       if (gspca_dev->sd_desc->stop0)
+               gspca_dev->sd_desc->stop0(gspca_dev);
        return 0;
 }
 EXPORT_SYMBOL(gspca_suspend);
@@ -1827,9 +1960,10 @@ int gspca_resume(struct usb_interface *intf)
        struct gspca_dev *gspca_dev = usb_get_intfdata(intf);
 
        gspca_dev->frozen = 0;
-       if (!gspca_dev->streaming)
-               return 0;
-       return gspca_init_transfer(gspca_dev);
+       gspca_dev->sd_desc->init(gspca_dev);
+       if (gspca_dev->streaming)
+               return gspca_init_transfer(gspca_dev);
+       return 0;
 }
 EXPORT_SYMBOL(gspca_resume);
 #endif
@@ -1875,7 +2009,7 @@ int gspca_auto_gain_n_exposure(struct gspca_dev *gspca_dev, int avg_lum,
           desired lumination fast (with the risc of a slight overshoot) */
        steps = abs(desired_avg_lum - avg_lum) / deadzone;
 
-       PDEBUG(D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n",
+       PDEBUG(D_FRAM, "autogain: lum: %d, desired: %d, steps: %d",
                avg_lum, desired_avg_lum, steps);
 
        for (i = 0; i < steps; i++) {