diff options
Diffstat (limited to 'drivers/media/video/gspca/gspca.c')
-rw-r--r-- | drivers/media/video/gspca/gspca.c | 259 |
1 files changed, 238 insertions, 21 deletions
diff --git a/drivers/media/video/gspca/gspca.c b/drivers/media/video/gspca/gspca.c index bd6214d4ab3..222af479150 100644 --- a/drivers/media/video/gspca/gspca.c +++ b/drivers/media/video/gspca/gspca.c @@ -3,6 +3,9 @@ * * Copyright (C) 2008-2009 Jean-Francois Moine (http://moinejf.free.fr) * + * Camera button input handling by Márton Németh + * Copyright (C) 2009-2010 Márton Németh <nm127@freemail.hu> + * * 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 @@ -37,6 +40,11 @@ #include "gspca.h" +#ifdef CONFIG_INPUT +#include <linux/input.h> +#include <linux/usb/input.h> +#endif + /* global values */ #define DEF_NURBS 3 /* default number of URBs */ #if DEF_NURBS > MAX_NURBS @@ -47,7 +55,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, 8, 0) +#define DRIVER_VERSION_NUMBER KERNEL_VERSION(2, 9, 0) #ifdef GSPCA_DEBUG int gspca_debug = D_ERR | D_PROBE; @@ -104,15 +112,185 @@ static const struct vm_operations_struct gspca_vm_ops = { .close = gspca_vm_close, }; +/* + * Input and interrupt endpoint handling functions + */ +#ifdef CONFIG_INPUT +static void int_irq(struct urb *urb) +{ + struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context; + int ret; + + ret = urb->status; + switch (ret) { + case 0: + if (gspca_dev->sd_desc->int_pkt_scan(gspca_dev, + urb->transfer_buffer, urb->actual_length) < 0) { + PDEBUG(D_ERR, "Unknown packet received"); + } + break; + + case -ENOENT: + case -ECONNRESET: + case -ENODEV: + case -ESHUTDOWN: + /* Stop is requested either by software or hardware is gone, + * keep the ret value non-zero and don't resubmit later. + */ + break; + + default: + PDEBUG(D_ERR, "URB error %i, resubmitting", urb->status); + urb->status = 0; + ret = 0; + } + + if (ret == 0) { + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + PDEBUG(D_ERR, "Resubmit URB failed with error %i", ret); + } +} + +static int gspca_input_connect(struct gspca_dev *dev) +{ + struct input_dev *input_dev; + int err = 0; + + dev->input_dev = NULL; + if (dev->sd_desc->int_pkt_scan || dev->sd_desc->other_input) { + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + usb_make_path(dev->dev, dev->phys, sizeof(dev->phys)); + strlcat(dev->phys, "/input0", sizeof(dev->phys)); + + input_dev->name = dev->sd_desc->name; + input_dev->phys = dev->phys; + + usb_to_input_id(dev->dev, &input_dev->id); + + input_dev->evbit[0] = BIT_MASK(EV_KEY); + input_dev->keybit[BIT_WORD(KEY_CAMERA)] = BIT_MASK(KEY_CAMERA); + input_dev->dev.parent = &dev->dev->dev; + + err = input_register_device(input_dev); + if (err) { + PDEBUG(D_ERR, "Input device registration failed " + "with error %i", err); + input_dev->dev.parent = NULL; + input_free_device(input_dev); + } else { + dev->input_dev = input_dev; + } + } + + return err; +} + +static int alloc_and_submit_int_urb(struct gspca_dev *gspca_dev, + struct usb_endpoint_descriptor *ep) +{ + unsigned int buffer_len; + int interval; + struct urb *urb; + struct usb_device *dev; + void *buffer = NULL; + int ret = -EINVAL; + + buffer_len = ep->wMaxPacketSize; + interval = ep->bInterval; + PDEBUG(D_PROBE, "found int in endpoint: 0x%x, " + "buffer_len=%u, interval=%u", + ep->bEndpointAddress, buffer_len, interval); + + dev = gspca_dev->dev; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + ret = -ENOMEM; + goto error; + } + + buffer = usb_buffer_alloc(dev, ep->wMaxPacketSize, + GFP_KERNEL, &urb->transfer_dma); + if (!buffer) { + ret = -ENOMEM; + goto error_buffer; + } + usb_fill_int_urb(urb, dev, + usb_rcvintpipe(dev, ep->bEndpointAddress), + buffer, buffer_len, + int_irq, (void *)gspca_dev, interval); + gspca_dev->int_urb = urb; + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret < 0) { + PDEBUG(D_ERR, "submit URB failed with error %i", ret); + goto error_submit; + } + return ret; + +error_submit: + usb_buffer_free(dev, + urb->transfer_buffer_length, + urb->transfer_buffer, + urb->transfer_dma); +error_buffer: + usb_free_urb(urb); +error: + return ret; +} + +static void gspca_input_create_urb(struct gspca_dev *gspca_dev) +{ + struct usb_interface *intf; + struct usb_host_interface *intf_desc; + struct usb_endpoint_descriptor *ep; + int i; + + if (gspca_dev->sd_desc->int_pkt_scan) { + intf = usb_ifnum_to_if(gspca_dev->dev, gspca_dev->iface); + intf_desc = intf->cur_altsetting; + for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) { + ep = &intf_desc->endpoint[i].desc; + if (usb_endpoint_dir_in(ep) && + usb_endpoint_xfer_int(ep)) { + + alloc_and_submit_int_urb(gspca_dev, ep); + break; + } + } + } +} + +static void gspca_input_destroy_urb(struct gspca_dev *gspca_dev) +{ + struct urb *urb; + + urb = gspca_dev->int_urb; + if (urb) { + gspca_dev->int_urb = NULL; + usb_kill_urb(urb); + usb_buffer_free(gspca_dev->dev, + urb->transfer_buffer_length, + urb->transfer_buffer, + urb->transfer_dma); + usb_free_urb(urb); + } +} +#else +#define gspca_input_connect(gspca_dev) 0 +#define gspca_input_create_urb(gspca_dev) +#define gspca_input_destroy_urb(gspca_dev) +#endif + /* 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]; + frame = gspca_dev->cur_frame; if ((frame->v4l2_buf.flags & BUF_ALL_FLAGS) != V4L2_BUF_FLAG_QUEUED) return NULL; @@ -486,11 +664,13 @@ static struct usb_host_endpoint *get_ep(struct gspca_dev *gspca_dev) i, ep->desc.bEndpointAddress); gspca_dev->alt = i; /* memorize the current alt setting */ if (gspca_dev->nbalt > 1) { + gspca_input_destroy_urb(gspca_dev); ret = usb_set_interface(gspca_dev->dev, gspca_dev->iface, i); if (ret < 0) { err("set alt %d err %d", i, ret); - return NULL; + ep = NULL; } + gspca_input_create_urb(gspca_dev); } return ep; } @@ -534,26 +714,22 @@ static int create_urbs(struct gspca_dev *gspca_dev, 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; } + gspca_dev->urb[n] = urb; urb->transfer_buffer = usb_buffer_alloc(gspca_dev->dev, bsize, GFP_KERNEL, &urb->transfer_dma); if (urb->transfer_buffer == NULL) { - usb_free_urb(urb); - err("usb_buffer_urb failed"); - destroy_urbs(gspca_dev); + err("usb_buffer_alloc failed"); return -ENOMEM; } - gspca_dev->urb[n] = urb; urb->dev = gspca_dev->dev; urb->context = gspca_dev; urb->transfer_buffer_length = bsize; @@ -585,6 +761,7 @@ static int create_urbs(struct gspca_dev *gspca_dev, static int gspca_init_transfer(struct gspca_dev *gspca_dev) { struct usb_host_endpoint *ep; + struct urb *urb; int n, ret; if (mutex_lock_interruptible(&gspca_dev->usb_lock)) @@ -595,6 +772,8 @@ static int gspca_init_transfer(struct gspca_dev *gspca_dev) goto out; } + gspca_dev->usb_err = 0; + /* set the higher alternate setting and * loop until urb submit succeeds */ if (gspca_dev->cam.reverse_alts) @@ -613,10 +792,15 @@ static int gspca_init_transfer(struct gspca_dev *gspca_dev) goto out; } for (;;) { - PDEBUG(D_STREAM, "init transfer alt %d", gspca_dev->alt); - ret = create_urbs(gspca_dev, ep); - if (ret < 0) - goto out; + if (!gspca_dev->cam.no_urb_create) { + PDEBUG(D_STREAM, "init transfer alt %d", + gspca_dev->alt); + ret = create_urbs(gspca_dev, ep); + if (ret < 0) { + destroy_urbs(gspca_dev); + goto out; + } + } /* clear the bulk endpoint */ if (gspca_dev->cam.bulk) @@ -636,8 +820,11 @@ static int gspca_init_transfer(struct gspca_dev *gspca_dev) break; /* submit the URBs */ - for (n = 0; n < gspca_dev->nurbs; n++) { - ret = usb_submit_urb(gspca_dev->urb[n], GFP_KERNEL); + for (n = 0; n < MAX_NURBS; n++) { + urb = gspca_dev->urb[n]; + if (urb == NULL) + break; + ret = usb_submit_urb(urb, GFP_KERNEL); if (ret < 0) break; } @@ -694,7 +881,9 @@ static void gspca_stream_off(struct gspca_dev *gspca_dev) if (gspca_dev->sd_desc->stopN) gspca_dev->sd_desc->stopN(gspca_dev); destroy_urbs(gspca_dev); + gspca_input_destroy_urb(gspca_dev); gspca_set_alt0(gspca_dev); + gspca_input_create_urb(gspca_dev); } /* always call stop0 to free the subdriver's resources */ @@ -2060,11 +2249,12 @@ int gspca_dev_probe(struct usb_interface *intf, PDEBUG(D_ERR, "Too many config"); return -ENODEV; } + + /* the USB video interface must be the first one */ interface = &intf->cur_altsetting->desc; - if (interface->bInterfaceNumber > 0) { - PDEBUG(D_ERR, "intf != 0"); + if (dev->config->desc.bNumInterfaces != 1 && + interface->bInterfaceNumber != 0) return -ENODEV; - } /* create the device */ if (dev_size < sizeof *gspca_dev) @@ -2096,6 +2286,10 @@ int gspca_dev_probe(struct usb_interface *intf, goto out; gspca_set_default_mode(gspca_dev); + ret = gspca_input_connect(gspca_dev); + if (ret) + goto out; + mutex_init(&gspca_dev->usb_lock); mutex_init(&gspca_dev->read_lock); mutex_init(&gspca_dev->queue_lock); @@ -2116,8 +2310,15 @@ int gspca_dev_probe(struct usb_interface *intf, usb_set_intfdata(intf, gspca_dev); PDEBUG(D_PROBE, "%s created", video_device_node_name(&gspca_dev->vdev)); + + gspca_input_create_urb(gspca_dev); + return 0; out: +#ifdef CONFIG_INPUT + if (gspca_dev->input_dev) + input_unregister_device(gspca_dev->input_dev); +#endif kfree(gspca_dev->usb_buf); kfree(gspca_dev); return ret; @@ -2133,6 +2334,9 @@ EXPORT_SYMBOL(gspca_dev_probe); void gspca_disconnect(struct usb_interface *intf) { struct gspca_dev *gspca_dev = usb_get_intfdata(intf); +#ifdef CONFIG_INPUT + struct input_dev *input_dev; +#endif PDEBUG(D_PROBE, "%s disconnect", video_device_node_name(&gspca_dev->vdev)); @@ -2144,6 +2348,15 @@ void gspca_disconnect(struct usb_interface *intf) wake_up_interruptible(&gspca_dev->wq); } +#ifdef CONFIG_INPUT + gspca_input_destroy_urb(gspca_dev); + input_dev = gspca_dev->input_dev; + if (input_dev) { + gspca_dev->input_dev = NULL; + input_unregister_device(input_dev); + } +#endif + /* the device is freed at exit of this function */ gspca_dev->dev = NULL; mutex_unlock(&gspca_dev->usb_lock); @@ -2169,6 +2382,7 @@ int gspca_suspend(struct usb_interface *intf, pm_message_t message) if (gspca_dev->sd_desc->stopN) gspca_dev->sd_desc->stopN(gspca_dev); destroy_urbs(gspca_dev); + gspca_input_destroy_urb(gspca_dev); gspca_set_alt0(gspca_dev); if (gspca_dev->sd_desc->stop0) gspca_dev->sd_desc->stop0(gspca_dev); @@ -2182,6 +2396,7 @@ int gspca_resume(struct usb_interface *intf) gspca_dev->frozen = 0; gspca_dev->sd_desc->init(gspca_dev); + gspca_input_create_urb(gspca_dev); if (gspca_dev->streaming) return gspca_init_transfer(gspca_dev); return 0; @@ -2205,6 +2420,8 @@ int gspca_auto_gain_n_exposure(struct gspca_dev *gspca_dev, int avg_lum, int retval = 0; for (i = 0; i < gspca_dev->sd_desc->nctrls; i++) { + if (gspca_dev->ctrl_dis & (1 << i)) + continue; 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) |