]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/media/video/gspca/gspca.c
V4L/DVB (8458): gspca_sonixb remove one more no longer needed special case from the...
[linux-2.6-omap-h63xx.git] / drivers / media / video / gspca / gspca.c
index cb0aeb0c0a45476b92e435c2c20788146089fc08..0f09784631adb41ddb6a83fbf99bb73f9fc5548b 100644 (file)
 #include <asm/page.h>
 #include <linux/uaccess.h>
 #include <linux/jiffies.h>
+#include <media/v4l2-ioctl.h>
 
 #include "gspca.h"
 
 /* global values */
-#define DEF_NURBS 2            /* default number of URBs (mmap) */
-#define USR_NURBS 5            /* default number of URBs (userptr) */
+#define DEF_NURBS 2            /* default number of URBs */
 
 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, 1, 5)
-static const char version[] = "2.1.5";
+#define DRIVER_VERSION_NUMBER  KERNEL_VERSION(2, 2, 0)
 
 static int video_nr = -1;
 
@@ -153,7 +152,6 @@ static void fill_frame(struct gspca_dev *gspca_dev,
        }
 
        /* resubmit the URB */
-/*fixme: don't do that when userptr and too many URBs sent*/
        urb->status = 0;
        st = usb_submit_urb(urb, GFP_ATOMIC);
        if (st < 0)
@@ -163,78 +161,24 @@ static void fill_frame(struct gspca_dev *gspca_dev,
 /*
  * ISOC message interrupt from the USB device
  *
- * Analyse each packet and call the subdriver for copy
- * to the frame buffer.
- *
- * There are 2 functions:
- *     - the first one (isoc_irq_mmap) is used when the application
- *       buffers are mapped. The frame detection and copy is done
- *       at interrupt level.
- *     - the second one (isoc_irq_user) is used when the application
- *       buffers are in user space (userptr). The frame detection
- *       and copy is done by the application.
+ * Analyse each packet and call the subdriver for copy to the frame buffer.
  */
-static void isoc_irq_mmap(struct urb *urb
+static void isoc_irq(struct urb *urb
 )
 {
        struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context;
 
-       PDEBUG(D_PACK, "isoc irq mmap");
+       PDEBUG(D_PACK, "isoc irq");
        if (!gspca_dev->streaming)
                return;
        fill_frame(gspca_dev, urb);
 }
 
-static void isoc_irq_user(struct urb *urb
-)
-{
-       struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context;
-       int i;
-
-       PDEBUG(D_PACK, "isoc irq user");
-       if (!gspca_dev->streaming)
-               return;
-
-       i = gspca_dev->urb_in % gspca_dev->nurbs;
-       if (urb != gspca_dev->urb[i]) {
-               PDEBUG(D_ERR|D_PACK, "urb out of sequence");
-               return;                 /* should never occur */
-       }
-
-       gspca_dev->urb_in++;
-       atomic_inc(&gspca_dev->nevent);         /* new event */
-       wake_up_interruptible(&gspca_dev->wq);
-/*fixme: submit a new URBs until urb_in == urb_out (% nurbs)*/
-}
-
-/*
- * treat the isoc messages
- *
- * This routine is called by the application (case userptr).
- */
-static void isoc_transfer(struct gspca_dev *gspca_dev)
-{
-       struct urb *urb;
-       int i;
-
-       for (;;) {
-               i = gspca_dev->urb_out;
-               PDEBUG(D_PACK, "isoc transf i:%d o:%d", gspca_dev->urb_in, i);
-               if (i == gspca_dev->urb_in)     /* isoc message to read */
-                       break;                  /* no (more) message */
-               atomic_dec(&gspca_dev->nevent);
-/*PDEBUG(D_PACK, "isoc_trf nevent: %d", atomic_read(&gspca_dev->nevent));*/
-               gspca_dev->urb_out = i + 1;     /* message treated */
-               urb = gspca_dev->urb[i % gspca_dev->nurbs];
-               fill_frame(gspca_dev, urb);
-       }
-}
-
 /*
  * add data to the current frame
  *
- * This function is called by the subdrivers at interrupt level
- * or user level.
+ * This function is called by the subdrivers at interrupt level.
+ *
  * To build a frame, these ones must add
  *     - one FIRST_PACKET
  *     - 0 or many INTER_PACKETs
@@ -265,6 +209,8 @@ struct gspca_frame *gspca_frame_add(struct gspca_dev *gspca_dev,
                                   &frame->v4l2_buf.timestamp);
                frame->v4l2_buf.sequence = ++gspca_dev->sequence;
        } else if (gspca_dev->last_packet_type == DISCARD_PACKET) {
+               if (packet_type == LAST_PACKET)
+                       gspca_dev->last_packet_type = packet_type;
                return frame;
        }
 
@@ -272,21 +218,12 @@ struct gspca_frame *gspca_frame_add(struct gspca_dev *gspca_dev,
        if (len > 0) {
                if (frame->data_end - frame->data + len
                                                 > frame->v4l2_buf.length) {
-                       PDEBUG(D_ERR|D_PACK, "frame overflow %d > %d",
+                       PDEBUG(D_ERR|D_PACK, "frame overflow %zd > %d",
                                frame->data_end - frame->data + len,
                                frame->v4l2_buf.length);
                        packet_type = DISCARD_PACKET;
                } else {
-                       if (frame->v4l2_buf.memory != V4L2_MEMORY_USERPTR) {
-                               memcpy(frame->data_end, data, len);
-                       } else {
-                               if (copy_to_user(frame->data_end,
-                                                data, len) != 0) {
-                                       PDEBUG(D_ERR|D_PACK,
-                                                       "copy to user failed");
-                                       packet_type = DISCARD_PACKET;
-                               }
-                       }
+                       memcpy(frame->data_end, data, len);
                        frame->data_end += len;
                }
        }
@@ -333,7 +270,6 @@ static void *rvmalloc(unsigned long size)
 /*     size = PAGE_ALIGN(size);        (already done) */
        mem = vmalloc_32(size);
        if (mem != NULL) {
-               memset(mem, 0, size);
                adr = (unsigned long) mem;
                while ((long) size > 0) {
                        SetPageReserved(vmalloc_to_page((void *) adr));
@@ -344,14 +280,12 @@ static void *rvmalloc(unsigned long size)
        return mem;
 }
 
-static void rvfree(void *mem, unsigned long size)
+static void rvfree(void *mem, long size)
 {
        unsigned long adr;
 
-       if (!mem)
-               return;
        adr = (unsigned long) mem;
-       while ((long) size > 0) {
+       while (size > 0) {
                ClearPageReserved(vmalloc_to_page((void *) adr));
                adr += PAGE_SIZE;
                size -= PAGE_SIZE;
@@ -370,16 +304,13 @@ static int frame_alloc(struct gspca_dev *gspca_dev,
        frsz = gspca_dev->cam.cam_mode[i].sizeimage;
        PDEBUG(D_STREAM, "frame alloc frsz: %d", frsz);
        frsz = PAGE_ALIGN(frsz);
-       PDEBUG(D_STREAM, "new fr_sz: %d", frsz);
        gspca_dev->frsz = frsz;
        if (count > GSPCA_MAX_FRAMES)
                count = GSPCA_MAX_FRAMES;
-       if (gspca_dev->memory == V4L2_MEMORY_MMAP) {
-               gspca_dev->frbuf = rvmalloc(frsz * count);
-               if (!gspca_dev->frbuf) {
-                       err("frame alloc failed");
-                       return -ENOMEM;
-               }
+       gspca_dev->frbuf = rvmalloc(frsz * count);
+       if (!gspca_dev->frbuf) {
+               err("frame alloc failed");
+               return -ENOMEM;
        }
        gspca_dev->nframes = count;
        for (i = 0; i < count; i++) {
@@ -391,11 +322,9 @@ static int frame_alloc(struct gspca_dev *gspca_dev,
                frame->v4l2_buf.length = frsz;
                frame->v4l2_buf.memory = gspca_dev->memory;
                frame->v4l2_buf.sequence = 0;
-               if (gspca_dev->memory == V4L2_MEMORY_MMAP) {
-                       frame->data = frame->data_end =
+               frame->data = frame->data_end =
                                        gspca_dev->frbuf + i * frsz;
-                       frame->v4l2_buf.m.offset = i * frsz;
-               }
+               frame->v4l2_buf.m.offset = i * frsz;
        }
        gspca_dev->fr_i = gspca_dev->fr_o = gspca_dev->fr_q = 0;
        gspca_dev->last_packet_type = DISCARD_PACKET;
@@ -472,7 +401,7 @@ static struct usb_host_endpoint *alt_isoc(struct usb_host_interface *alt,
  * This routine may be called many times when the bandwidth is too small
  * (the bandwidth is checked on urb submit).
  */
-struct usb_host_endpoint *get_isoc_ep(struct gspca_dev *gspca_dev)
+static struct usb_host_endpoint *get_isoc_ep(struct gspca_dev *gspca_dev)
 {
        struct usb_interface *intf;
        struct usb_host_endpoint *ep;
@@ -509,7 +438,6 @@ static int create_urbs(struct gspca_dev *gspca_dev,
 {
        struct urb *urb;
        int n, nurbs, i, psize, npkt, bsize;
-       usb_complete_t usb_complete;
 
        /* calculate the packet size and the number of packets */
        psize = le16_to_cpu(ep->desc.wMaxPacketSize);
@@ -522,14 +450,7 @@ static int create_urbs(struct gspca_dev *gspca_dev,
        bsize = psize * npkt;
        PDEBUG(D_STREAM,
                "isoc %d pkts size %d (bsize:%d)", npkt, psize, bsize);
-/*fixme:don't submit all URBs when userptr*/
-       if (gspca_dev->memory != V4L2_MEMORY_USERPTR) {
-               usb_complete = isoc_irq_mmap;
-               nurbs = DEF_NURBS;
-       } else {
-               usb_complete = isoc_irq_user;
-               nurbs = USR_NURBS;
-       }
+       nurbs = DEF_NURBS;
        gspca_dev->nurbs = nurbs;
        for (n = 0; n < nurbs; n++) {
                urb = usb_alloc_urb(npkt, GFP_KERNEL);
@@ -556,7 +477,7 @@ static int create_urbs(struct gspca_dev *gspca_dev,
                urb->transfer_flags = URB_ISO_ASAP
                                        | URB_NO_TRANSFER_DMA_MAP;
                urb->interval = ep->desc.bInterval;
-               urb->complete = usb_complete;
+               urb->complete = isoc_irq;
                urb->number_of_packets = npkt;
                urb->transfer_buffer_length = bsize;
                for (i = 0; i < npkt; i++) {
@@ -564,7 +485,6 @@ static int create_urbs(struct gspca_dev *gspca_dev,
                        urb->iso_frame_desc[i].offset = psize * i;
                }
        }
-       gspca_dev->urb_in = gspca_dev->urb_out = 0;
        return 0;
 }
 
@@ -640,11 +560,6 @@ static void gspca_stream_off(struct gspca_dev *gspca_dev)
                gspca_set_alt0(gspca_dev);
                gspca_dev->sd_desc->stop0(gspca_dev);
                PDEBUG(D_STREAM, "stream off OK");
-       } else {
-               destroy_urbs(gspca_dev);
-               atomic_inc(&gspca_dev->nevent);
-               wake_up_interruptible(&gspca_dev->wq);
-               PDEBUG(D_ERR|D_STREAM, "stream off no device ??");
        }
 }
 
@@ -762,9 +677,6 @@ static int try_fmt_vid_cap(struct gspca_dev *gspca_dev,
        w = fmt->fmt.pix.width;
        h = fmt->fmt.pix.height;
 
-       /* (luvcview problem) */
-       if (fmt->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG)
-               fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;
 #ifdef CONFIG_VIDEO_ADV_DEBUG
        if (gspca_debug & D_CONF)
                PDEBUG_MODE("try fmt cap", fmt->fmt.pix.pixelformat, w, h);
@@ -898,7 +810,7 @@ static int dev_close(struct inode *inode, struct file *file)
                return -ERESTARTSYS;
        gspca_dev->users--;
 
-       /* if the file did capture, free the streaming resources */
+       /* 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)
@@ -1063,6 +975,7 @@ static int vidioc_reqbufs(struct file *file, void *priv,
        if (rb->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
                return -EINVAL;
        switch (rb->memory) {
+       case GSPCA_MEMORY_READ:                 /* (internal call) */
        case V4L2_MEMORY_MMAP:
        case V4L2_MEMORY_USERPTR:
                break;
@@ -1072,6 +985,20 @@ static int vidioc_reqbufs(struct file *file, void *priv,
        if (mutex_lock_interruptible(&gspca_dev->queue_lock))
                return -ERESTARTSYS;
 
+       if (gspca_dev->memory != GSPCA_MEMORY_NO
+           && gspca_dev->memory != rb->memory) {
+               ret = -EBUSY;
+               goto out;
+       }
+
+       /* only one file may do the capture */
+       if (gspca_dev->capt_file != NULL
+           && gspca_dev->capt_file != file) {
+               ret = -EBUSY;
+               goto out;
+       }
+
+       /* if allocated, the buffers must not be mapped */
        for (i = 0; i < gspca_dev->nframes; i++) {
                if (gspca_dev->frame[i].vma_use_count) {
                        ret = -EBUSY;
@@ -1079,23 +1006,25 @@ static int vidioc_reqbufs(struct file *file, void *priv,
                }
        }
 
-       /* only one file may do capture */
-       if ((gspca_dev->capt_file != NULL && gspca_dev->capt_file != file)
-           || gspca_dev->streaming) {
-               ret = -EBUSY;
-               goto out;
+       /* stop streaming */
+       if (gspca_dev->streaming) {
+               mutex_lock(&gspca_dev->usb_lock);
+               gspca_stream_off(gspca_dev);
+               mutex_unlock(&gspca_dev->usb_lock);
        }
 
-       if (rb->count == 0) { /* unrequest? */
+       /* free the previous allocated buffers, if any */
+       if (gspca_dev->nframes != 0) {
                frame_free(gspca_dev);
                gspca_dev->capt_file = NULL;
-       } else {
-               gspca_dev->memory = rb->memory;
-               ret = frame_alloc(gspca_dev, rb->count);
-               if (ret == 0) {
-                       rb->count = gspca_dev->nframes;
-                       gspca_dev->capt_file = file;
-               }
+       }
+       if (rb->count == 0)                     /* unrequest */
+               goto out;
+       gspca_dev->memory = rb->memory;
+       ret = frame_alloc(gspca_dev, rb->count);
+       if (ret == 0) {
+               rb->count = gspca_dev->nframes;
+               gspca_dev->capt_file = file;
        }
 out:
        mutex_unlock(&gspca_dev->queue_lock);
@@ -1137,10 +1066,6 @@ static int vidioc_streamon(struct file *file, void *priv,
                ret = -EINVAL;
                goto out;
        }
-       if (gspca_dev->capt_file != file) {
-               ret = -EINVAL;
-               goto out;
-       }
        if (!gspca_dev->streaming) {
                ret = gspca_init_transfer(gspca_dev);
                if (ret < 0)
@@ -1164,7 +1089,7 @@ static int vidioc_streamoff(struct file *file, void *priv,
                                enum v4l2_buf_type buf_type)
 {
        struct gspca_dev *gspca_dev = priv;
-       int ret;
+       int i, ret;
 
        if (buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
                return -EINVAL;
@@ -1172,18 +1097,23 @@ static int vidioc_streamoff(struct file *file, void *priv,
                return 0;
        if (mutex_lock_interruptible(&gspca_dev->queue_lock))
                return -ERESTARTSYS;
+
+       /* stop streaming */
        if (mutex_lock_interruptible(&gspca_dev->usb_lock)) {
                ret = -ERESTARTSYS;
                goto out;
        }
-       if (gspca_dev->capt_file != file) {
-               ret = -EINVAL;
-               goto out2;
-       }
        gspca_stream_off(gspca_dev);
-       ret = 0;
-out2:
        mutex_unlock(&gspca_dev->usb_lock);
+
+       /* empty the application queues */
+       for (i = 0; i < gspca_dev->nframes; i++)
+               gspca_dev->frame[i].v4l2_buf.flags &= ~BUF_ALL_FLAGS;
+       gspca_dev->fr_i = gspca_dev->fr_o = gspca_dev->fr_q = 0;
+       gspca_dev->last_packet_type = DISCARD_PACKET;
+       gspca_dev->sequence = 0;
+       atomic_set(&gspca_dev->nevent, 0);
+       ret = 0;
 out:
        mutex_unlock(&gspca_dev->queue_lock);
        return ret;
@@ -1301,9 +1231,6 @@ static int dev_mmap(struct file *file, struct vm_area_struct *vma)
        struct page *page;
        unsigned long addr, start, size;
        int i, ret;
-#ifdef CONFIG_VIDEO_V4L1_COMPAT
-       int compat = 0;
-#endif
 
        start = vma->vm_start;
        size = vma->vm_end - vma->vm_start;
@@ -1338,11 +1265,11 @@ static int dev_mmap(struct file *file, struct vm_area_struct *vma)
                goto out;
        }
 #ifdef CONFIG_VIDEO_V4L1_COMPAT
-       if (i == 0 && size == frame->v4l2_buf.length * gspca_dev->nframes)
-               compat = 1;
-       else
+       /* v4l1 maps all the buffers */
+       if (i != 0
+           || size != frame->v4l2_buf.length * gspca_dev->nframes)
 #endif
-       if (size != frame->v4l2_buf.length) {
+           if (size != frame->v4l2_buf.length) {
                PDEBUG(D_STREAM, "mmap bad size");
                ret = -EINVAL;
                goto out;
@@ -1368,14 +1295,6 @@ static int dev_mmap(struct file *file, struct vm_area_struct *vma)
        vma->vm_ops = &gspca_vm_ops;
        vma->vm_private_data = frame;
        gspca_vm_open(vma);
-#ifdef CONFIG_VIDEO_V4L1_COMPAT
-       if (compat) {
-/*fixme: ugly*/
-               for (i = 1; i < gspca_dev->nframes; ++i)
-                       gspca_dev->frame[i].v4l2_buf.flags |=
-                                               V4L2_BUF_FLAG_MAPPED;
-       }
-#endif
        ret = 0;
 out:
        mutex_unlock(&gspca_dev->queue_lock);
@@ -1393,10 +1312,6 @@ static int frame_wait(struct gspca_dev *gspca_dev,
        struct gspca_frame *frame;
        int i, j, ret;
 
-       /* if userptr, treat the awaiting URBs */
-       if (gspca_dev->memory == V4L2_MEMORY_USERPTR)
-               isoc_transfer(gspca_dev);
-
        /* check if a frame is ready */
        i = gspca_dev->fr_o;
        j = gspca_dev->fr_queue[i];
@@ -1421,8 +1336,6 @@ static int frame_wait(struct gspca_dev *gspca_dev,
                atomic_dec(&gspca_dev->nevent);
                if (!gspca_dev->streaming || !gspca_dev->present)
                        return -EIO;
-               if (gspca_dev->memory == V4L2_MEMORY_USERPTR)
-                       isoc_transfer(gspca_dev);
                i = gspca_dev->fr_o;
                j = gspca_dev->fr_queue[i];
                frame = &gspca_dev->frame[j];
@@ -1436,9 +1349,11 @@ ok:
                gspca_dev->fr_i,
                gspca_dev->fr_o);
 
-       if (gspca_dev->sd_desc->dq_callback)
+       if (gspca_dev->sd_desc->dq_callback) {
+               mutex_lock(&gspca_dev->usb_lock);
                gspca_dev->sd_desc->dq_callback(gspca_dev);
-
+               mutex_unlock(&gspca_dev->usb_lock);
+       }
        return j;
 }
 
@@ -1455,18 +1370,21 @@ static int vidioc_dqbuf(struct file *file, void *priv,
        int i, ret;
 
        PDEBUG(D_FRAM, "dqbuf");
-       if (v4l2_buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE
-           || (v4l2_buf->memory != V4L2_MEMORY_MMAP
-               && v4l2_buf->memory != V4L2_MEMORY_USERPTR))
+       if (v4l2_buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
                return -EINVAL;
-       if (!gspca_dev->streaming)
+       if (v4l2_buf->memory != gspca_dev->memory)
                return -EINVAL;
-       if (gspca_dev->capt_file != file) {
-               ret = -EINVAL;
-               goto out;
-       }
 
-       /* only one read */
+       /* if not streaming, be sure the application will not loop forever */
+       if (!(file->f_flags & O_NONBLOCK)
+           && !gspca_dev->streaming && gspca_dev->users == 1)
+               return -EINVAL;
+
+       /* only the capturing file may dequeue */
+       if (gspca_dev->capt_file != file)
+               return -EINVAL;
+
+       /* only one dequeue / read at a time */
        if (mutex_lock_interruptible(&gspca_dev->read_lock))
                return -ERESTARTSYS;
 
@@ -1475,6 +1393,16 @@ static int vidioc_dqbuf(struct file *file, void *priv,
                goto out;
        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,
+                                frame->data,
+                                frame->v4l2_buf.bytesused)) {
+                       PDEBUG(D_ERR|D_STREAM,
+                               "dqbuf cp to user failed");
+                       ret = -EFAULT;
+                       goto out;
+               }
+       }
        frame->v4l2_buf.flags &= ~V4L2_BUF_FLAG_DONE;
        memcpy(v4l2_buf, &frame->v4l2_buf, sizeof *v4l2_buf);
        PDEBUG(D_FRAM, "dqbuf %d", i);
@@ -1501,24 +1429,23 @@ static int vidioc_qbuf(struct file *file, void *priv,
        if (v4l2_buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
                return -EINVAL;
 
+       if (mutex_lock_interruptible(&gspca_dev->queue_lock))
+               return -ERESTARTSYS;
+
        index = v4l2_buf->index;
        if ((unsigned) index >= gspca_dev->nframes) {
                PDEBUG(D_FRAM,
                        "qbuf idx %d >= %d", index, gspca_dev->nframes);
-               return -EINVAL;
+               ret = -EINVAL;
+               goto out;
        }
-       frame = &gspca_dev->frame[index];
-
-       if (v4l2_buf->memory != frame->v4l2_buf.memory) {
+       if (v4l2_buf->memory != gspca_dev->memory) {
                PDEBUG(D_FRAM, "qbuf bad memory type");
-               return -EINVAL;
+               ret = -EINVAL;
+               goto out;
        }
-       if (gspca_dev->capt_file != file)
-               return -EINVAL;
-
-       if (mutex_lock_interruptible(&gspca_dev->queue_lock))
-               return -ERESTARTSYS;
 
+       frame = &gspca_dev->frame[index];
        if (frame->v4l2_buf.flags & BUF_ALL_FLAGS) {
                PDEBUG(D_FRAM, "qbuf bad state");
                ret = -EINVAL;
@@ -1529,8 +1456,6 @@ static int vidioc_qbuf(struct file *file, void *priv,
 /*     frame->v4l2_buf.flags &= ~V4L2_BUF_FLAG_DONE; */
 
        if (frame->v4l2_buf.memory == V4L2_MEMORY_USERPTR) {
-               frame->data = frame->data_end =
-                               (__u8 *) v4l2_buf->m.userptr;
                frame->v4l2_buf.m.userptr = v4l2_buf->m.userptr;
                frame->v4l2_buf.length = v4l2_buf->length;
        }
@@ -1568,7 +1493,7 @@ static int read_alloc(struct gspca_dev *gspca_dev,
                memset(&rb, 0, sizeof rb);
                rb.count = gspca_dev->nbufread;
                rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-               rb.memory = V4L2_MEMORY_MMAP;
+               rb.memory = GSPCA_MEMORY_READ;
                ret = vidioc_reqbufs(file, gspca_dev, &rb);
                if (ret != 0) {
                        PDEBUG(D_STREAM, "read reqbuf err %d", ret);
@@ -1576,12 +1501,9 @@ static int read_alloc(struct gspca_dev *gspca_dev,
                }
                memset(&v4l2_buf, 0, sizeof v4l2_buf);
                v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-               v4l2_buf.memory = V4L2_MEMORY_MMAP;
+               v4l2_buf.memory = GSPCA_MEMORY_READ;
                for (i = 0; i < gspca_dev->nbufread; i++) {
                        v4l2_buf.index = i;
-/*fixme: ugly!*/
-                       gspca_dev->frame[i].v4l2_buf.flags |=
-                                                       V4L2_BUF_FLAG_MAPPED;
                        ret = vidioc_qbuf(file, gspca_dev, &v4l2_buf);
                        if (ret != 0) {
                                PDEBUG(D_STREAM, "read qbuf err: %d", ret);
@@ -1609,17 +1531,13 @@ static unsigned int dev_poll(struct file *file, poll_table *wait)
        if (!gspca_dev->present)
                return POLLERR;
 
-       /* if not streaming, the user would use read() */
-       if (!gspca_dev->streaming) {
-               if (gspca_dev->memory != GSPCA_MEMORY_NO) {
-                       ret = POLLERR;          /* not the 1st time */
-                       goto out;
-               }
+       /* if reqbufs is not done, the user would use read() */
+       if (gspca_dev->nframes == 0) {
+               if (gspca_dev->memory != GSPCA_MEMORY_NO)
+                       return POLLERR;         /* not the 1st time */
                ret = read_alloc(gspca_dev, file);
-               if (ret != 0) {
-                       ret = POLLERR;
-                       goto out;
-               }
+               if (ret != 0)
+                       return POLLERR;
        }
 
        if (mutex_lock_interruptible(&gspca_dev->queue_lock) != 0)
@@ -1629,11 +1547,7 @@ static unsigned int dev_poll(struct file *file, poll_table *wait)
                goto out;
        }
 
-       /* if userptr, treat the awaiting URBs */
-       if (gspca_dev->memory == V4L2_MEMORY_USERPTR
-           && gspca_dev->capt_file == file)
-               isoc_transfer(gspca_dev);
-
+       /* check the next incoming buffer */
        i = gspca_dev->fr_o;
        i = gspca_dev->fr_queue[i];
        if (gspca_dev->frame[i].v4l2_buf.flags & V4L2_BUF_FLAG_DONE)
@@ -1654,7 +1568,7 @@ static ssize_t dev_read(struct file *file, char __user *data,
        struct timeval timestamp;
        int n, ret, ret2;
 
-       PDEBUG(D_FRAM, "read (%d)", count);
+       PDEBUG(D_FRAM, "read (%zd)", count);
        if (!gspca_dev->present)
                return -ENODEV;
        switch (gspca_dev->memory) {
@@ -1678,7 +1592,7 @@ static ssize_t dev_read(struct file *file, char __user *data,
        for (;;) {
                memset(&v4l2_buf, 0, sizeof v4l2_buf);
                v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-               v4l2_buf.memory = V4L2_MEMORY_MMAP;
+               v4l2_buf.memory = GSPCA_MEMORY_READ;
                ret = vidioc_dqbuf(file, gspca_dev, &v4l2_buf);
                if (ret != 0) {
                        PDEBUG(D_STREAM, "read dqbuf err %d", ret);
@@ -1700,18 +1614,12 @@ static ssize_t dev_read(struct file *file, char __user *data,
        }
 
        /* copy the frame */
-       if (count < frame->v4l2_buf.bytesused) {
-               PDEBUG(D_STREAM, "read bad count: %d < %d",
-                       count, frame->v4l2_buf.bytesused);
-/*fixme: special errno?*/
-               ret = -EINVAL;
-               goto out;
-       }
-       count = frame->v4l2_buf.bytesused;
+       if (count > frame->v4l2_buf.bytesused)
+               count = frame->v4l2_buf.bytesused;
        ret = copy_to_user(data, frame->data, count);
        if (ret != 0) {
                PDEBUG(D_ERR|D_STREAM,
-                       "read cp to user lack %d / %d", ret, count);
+                       "read cp to user lack %d / %zd", ret, count);
                ret = -EFAULT;
                goto out;
        }
@@ -1834,7 +1742,7 @@ int gspca_dev_probe(struct usb_interface *intf,
 
        /* init video stuff */
        memcpy(&gspca_dev->vdev, &gspca_template, sizeof gspca_template);
-       gspca_dev->vdev.dev = &dev->dev;
+       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 */
@@ -1888,10 +1796,101 @@ void gspca_disconnect(struct usb_interface *intf)
 }
 EXPORT_SYMBOL(gspca_disconnect);
 
+/* -- cam driver utility functions -- */
+
+/* auto gain and exposure algorithm based on the knee algorithm described here:
+   http://ytse.tricolour.net/docs/LowLightOptimization.html
+
+   Returns 0 if no changes were made, 1 if the gain and or exposure settings
+   where changed. */
+int gspca_auto_gain_n_exposure(struct gspca_dev *gspca_dev, int avg_lum,
+       int desired_avg_lum, int deadzone, int gain_knee, int exposure_knee)
+{
+       int i, steps, gain, orig_gain, exposure, orig_exposure, autogain;
+       const struct ctrl *gain_ctrl = NULL;
+       const struct ctrl *exposure_ctrl = NULL;
+       const struct ctrl *autogain_ctrl = NULL;
+       int retval = 0;
+
+       for (i = 0; i < gspca_dev->sd_desc->nctrls; i++) {
+               if (gspca_dev->sd_desc->ctrls[i].qctrl.id == V4L2_CID_GAIN)
+                       gain_ctrl = &gspca_dev->sd_desc->ctrls[i];
+               if (gspca_dev->sd_desc->ctrls[i].qctrl.id == V4L2_CID_EXPOSURE)
+                       exposure_ctrl = &gspca_dev->sd_desc->ctrls[i];
+               if (gspca_dev->sd_desc->ctrls[i].qctrl.id == V4L2_CID_AUTOGAIN)
+                       autogain_ctrl = &gspca_dev->sd_desc->ctrls[i];
+       }
+       if (!gain_ctrl || !exposure_ctrl || !autogain_ctrl) {
+               PDEBUG(D_ERR, "Error: gspca_auto_gain_n_exposure called "
+                       "on cam without (auto)gain/exposure");
+               return 0;
+       }
+
+       if (gain_ctrl->get(gspca_dev, &gain) ||
+                       exposure_ctrl->get(gspca_dev, &exposure) ||
+                       autogain_ctrl->get(gspca_dev, &autogain) || !autogain)
+               return 0;
+
+       orig_gain = gain;
+       orig_exposure = exposure;
+
+       /* If we are of a multiple of deadzone, do multiple steps to reach the
+          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",
+               avg_lum, desired_avg_lum, steps);
+
+       for (i = 0; i < steps; i++) {
+               if (avg_lum > desired_avg_lum) {
+                       if (gain > gain_knee)
+                               gain--;
+                       else if (exposure > exposure_knee)
+                               exposure--;
+                       else if (gain > gain_ctrl->qctrl.default_value)
+                               gain--;
+                       else if (exposure > exposure_ctrl->qctrl.minimum)
+                               exposure--;
+                       else if (gain > gain_ctrl->qctrl.minimum)
+                               gain--;
+                       else
+                               break;
+               } else {
+                       if (gain < gain_ctrl->qctrl.default_value)
+                               gain++;
+                       else if (exposure < exposure_knee)
+                               exposure++;
+                       else if (gain < gain_knee)
+                               gain++;
+                       else if (exposure < exposure_ctrl->qctrl.maximum)
+                               exposure++;
+                       else if (gain < gain_ctrl->qctrl.maximum)
+                               gain++;
+                       else
+                               break;
+               }
+       }
+
+       if (gain != orig_gain) {
+               gain_ctrl->set(gspca_dev, gain);
+               retval = 1;
+       }
+       if (exposure != orig_exposure) {
+               exposure_ctrl->set(gspca_dev, exposure);
+               retval = 1;
+       }
+
+       return retval;
+}
+EXPORT_SYMBOL(gspca_auto_gain_n_exposure);
+
 /* -- module insert / remove -- */
 static int __init gspca_init(void)
 {
-       info("main v%s registered", version);
+       info("main v%d.%d.%d registered",
+               (DRIVER_VERSION_NUMBER >> 16) & 0xff,
+               (DRIVER_VERSION_NUMBER >> 8) & 0xff,
+               DRIVER_VERSION_NUMBER & 0xff);
        return 0;
 }
 static void __exit gspca_exit(void)