V4L/DVB (8482): videodev: move all ioctl callbacks to a new v4l2_ioctl_ops struct
[safe/jmp/linux-2.6] / drivers / media / video / gspca / gspca.c
index 77e5e4f..2a416cf 100644 (file)
@@ -32,6 +32,7 @@
 #include <asm/page.h>
 #include <linux/uaccess.h>
 #include <linux/jiffies.h>
+#include <media/v4l2-ioctl.h>
 
 #include "gspca.h"
 
@@ -42,8 +43,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, 1, 6)
-static const char version[] = "2.1.6";
+#define DRIVER_VERSION_NUMBER  KERNEL_VERSION(2, 2, 0)
 
 static int video_nr = -1;
 
@@ -168,7 +168,7 @@ 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);
@@ -209,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;
        }
 
@@ -216,7 +218,7 @@ 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;
@@ -399,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;
@@ -558,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 ??");
        }
 }
 
@@ -680,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);
@@ -816,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)
@@ -981,7 +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:
+       case GSPCA_MEMORY_READ:                 /* (internal call) */
        case V4L2_MEMORY_MMAP:
        case V4L2_MEMORY_USERPTR:
                break;
@@ -991,33 +985,46 @@ static int vidioc_reqbufs(struct file *file, void *priv,
        if (mutex_lock_interruptible(&gspca_dev->queue_lock))
                return -ERESTARTSYS;
 
-       /* only one file may do capture */
-       if ((gspca_dev->capt_file != NULL && gspca_dev->capt_file != file)
-           || gspca_dev->streaming) {
+       if (gspca_dev->memory != GSPCA_MEMORY_NO
+           && gspca_dev->memory != rb->memory) {
                ret = -EBUSY;
                goto out;
        }
 
-       if (rb->count == 0) {                   /* unrequest */
-               for (i = 0; i < gspca_dev->nframes; i++) {
-                       if (gspca_dev->frame[i].vma_use_count) {
-                               ret = -EBUSY;
-                               goto out;
-                       }
-               }
-               frame_free(gspca_dev);
-               gspca_dev->capt_file = NULL;
-       } else {
-               if (gspca_dev->nframes != 0) {
+       /* 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;
                        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;
-               }
+       }
+
+       /* stop streaming */
+       if (gspca_dev->streaming) {
+               mutex_lock(&gspca_dev->usb_lock);
+               gspca_stream_off(gspca_dev);
+               mutex_unlock(&gspca_dev->usb_lock);
+       }
+
+       /* free the previous allocated buffers, if any */
+       if (gspca_dev->nframes != 0) {
+               frame_free(gspca_dev);
+               gspca_dev->capt_file = NULL;
+       }
+       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);
@@ -1059,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)
@@ -1086,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;
@@ -1094,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;
@@ -1341,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;
 }
 
@@ -1364,14 +1374,17 @@ static int vidioc_dqbuf(struct file *file, void *priv,
                return -EINVAL;
        if (v4l2_buf->memory != gspca_dev->memory)
                return -EINVAL;
-       if (!gspca_dev->streaming)
+
+       /* 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;
-       if (gspca_dev->capt_file != file) {
-               ret = -EINVAL;
-               goto out;
-       }
 
-       /* only one read */
+       /* 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;
 
@@ -1416,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;
@@ -1492,9 +1504,6 @@ static int read_alloc(struct gspca_dev *gspca_dev,
                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);
@@ -1522,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)
@@ -1542,6 +1547,7 @@ static unsigned int dev_poll(struct file *file, poll_table *wait)
                goto out;
        }
 
+       /* 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)
@@ -1562,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) {
@@ -1613,7 +1619,7 @@ static ssize_t dev_read(struct file *file, char __user *data,
        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;
        }
@@ -1645,12 +1651,7 @@ static struct file_operations dev_fops = {
        .poll   = dev_poll,
 };
 
-static struct video_device gspca_template = {
-       .name = "gspca main driver",
-       .type = VID_TYPE_CAPTURE,
-       .fops = &dev_fops,
-       .release = dev_release,         /* mandatory */
-       .minor = -1,
+static const struct v4l2_ioctl_ops dev_ioctl_ops = {
        .vidioc_querycap        = vidioc_querycap,
        .vidioc_dqbuf           = vidioc_dqbuf,
        .vidioc_qbuf            = vidioc_qbuf,
@@ -1679,6 +1680,15 @@ static struct video_device gspca_template = {
 #endif
 };
 
+static struct video_device gspca_template = {
+       .name = "gspca main driver",
+       .type = VID_TYPE_CAPTURE,
+       .fops = &dev_fops,
+       .ioctl_ops = &dev_ioctl_ops,
+       .release = dev_release,         /* mandatory */
+       .minor = -1,
+};
+
 /*
  * probe and create a new gspca device
  *
@@ -1736,7 +1746,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 */
@@ -1790,10 +1800,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)