diff options
Diffstat (limited to 'drivers/media/usb/gspca/gspca.c')
-rw-r--r-- | drivers/media/usb/gspca/gspca.c | 1721 |
1 files changed, 1721 insertions, 0 deletions
diff --git a/drivers/media/usb/gspca/gspca.c b/drivers/media/usb/gspca/gspca.c new file mode 100644 index 0000000000..770714c342 --- /dev/null +++ b/drivers/media/usb/gspca/gspca.c @@ -0,0 +1,1721 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Main USB camera driver + * + * Copyright (C) 2008-2011 Jean-François 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> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define GSPCA_VERSION "2.14.0" + +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/vmalloc.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/pagemap.h> +#include <linux/io.h> +#include <asm/page.h> +#include <linux/uaccess.h> +#include <linux/ktime.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> + +#include "gspca.h" + +#if IS_ENABLED(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 +#error "DEF_NURBS too big" +#endif + +MODULE_AUTHOR("Jean-François Moine <http://moinejf.free.fr>"); +MODULE_DESCRIPTION("GSPCA USB Camera Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(GSPCA_VERSION); + +int gspca_debug; +EXPORT_SYMBOL(gspca_debug); + +static void PDEBUG_MODE(struct gspca_dev *gspca_dev, int debug, char *txt, + __u32 pixfmt, int w, int h) +{ + if ((pixfmt >> 24) >= '0' && (pixfmt >> 24) <= 'z') { + gspca_dbg(gspca_dev, debug, "%s %c%c%c%c %dx%d\n", + txt, + pixfmt & 0xff, + (pixfmt >> 8) & 0xff, + (pixfmt >> 16) & 0xff, + pixfmt >> 24, + w, h); + } else { + gspca_dbg(gspca_dev, debug, "%s 0x%08x %dx%d\n", + txt, + pixfmt, + w, h); + } +} + +/* specific memory types - !! should be different from V4L2_MEMORY_xxx */ +#define GSPCA_MEMORY_NO 0 /* V4L2_MEMORY_xxx starts from 1 */ +#define GSPCA_MEMORY_READ 7 + +/* + * Input and interrupt endpoint handling functions + */ +#if IS_ENABLED(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) { + gspca_err(gspca_dev, "Unknown packet received\n"); + } + 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: + gspca_err(gspca_dev, "URB error %i, resubmitting\n", + urb->status); + urb->status = 0; + ret = 0; + } + + if (ret == 0) { + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + pr_err("Resubmit URB failed with error %i\n", 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) { + pr_err("Input device registration failed with error %i\n", + 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 = le16_to_cpu(ep->wMaxPacketSize); + interval = ep->bInterval; + gspca_dbg(gspca_dev, D_CONF, "found int in endpoint: 0x%x, buffer_len=%u, interval=%u\n", + ep->bEndpointAddress, buffer_len, interval); + + dev = gspca_dev->dev; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + ret = -ENOMEM; + goto error; + } + + buffer = usb_alloc_coherent(dev, buffer_len, + 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); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret < 0) { + gspca_err(gspca_dev, "submit int URB failed with error %i\n", + ret); + goto error_submit; + } + gspca_dev->int_urb = urb; + return ret; + +error_submit: + usb_free_coherent(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_free_coherent(gspca_dev->dev, + urb->transfer_buffer_length, + urb->transfer_buffer, + urb->transfer_dma); + usb_free_urb(urb); + } +} +#else +static inline void gspca_input_destroy_urb(struct gspca_dev *gspca_dev) +{ +} + +static inline void gspca_input_create_urb(struct gspca_dev *gspca_dev) +{ +} + +static inline int gspca_input_connect(struct gspca_dev *dev) +{ + return 0; +} +#endif + +/* + * fill a video frame from an URB and resubmit + */ +static void fill_frame(struct gspca_dev *gspca_dev, + struct urb *urb) +{ + u8 *data; /* address of data in the iso message */ + int i, len, st; + cam_pkt_op pkt_scan; + + if (urb->status != 0) { + if (urb->status == -ESHUTDOWN) + return; /* disconnection */ +#ifdef CONFIG_PM + if (gspca_dev->frozen) + return; +#endif + gspca_err(gspca_dev, "urb status: %d\n", urb->status); + urb->status = 0; + goto resubmit; + } + pkt_scan = gspca_dev->sd_desc->pkt_scan; + for (i = 0; i < urb->number_of_packets; i++) { + len = urb->iso_frame_desc[i].actual_length; + + /* check the packet status and length */ + st = urb->iso_frame_desc[i].status; + if (st) { + gspca_dbg(gspca_dev, D_PACK, "ISOC data error: [%d] len=%d, status=%d\n", + i, len, st); + gspca_dev->last_packet_type = DISCARD_PACKET; + continue; + } + if (len == 0) { + if (gspca_dev->empty_packet == 0) + gspca_dev->empty_packet = 1; + continue; + } + + /* let the packet be analyzed by the subdriver */ + gspca_dbg(gspca_dev, D_PACK, "packet [%d] o:%d l:%d\n", + i, urb->iso_frame_desc[i].offset, len); + data = (u8 *) urb->transfer_buffer + + urb->iso_frame_desc[i].offset; + pkt_scan(gspca_dev, data, len); + } + +resubmit: + if (!gspca_dev->streaming) + return; + /* resubmit the URB */ + st = usb_submit_urb(urb, GFP_ATOMIC); + if (st < 0) + pr_err("usb_submit_urb() ret %d\n", st); +} + +/* + * ISOC message interrupt from the USB device + * + * Analyse each packet and call the subdriver for copy to the frame buffer. + */ +static void isoc_irq(struct urb *urb) +{ + struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context; + + gspca_dbg(gspca_dev, D_PACK, "isoc irq\n"); + if (!gspca_dev->streaming) + return; + fill_frame(gspca_dev, 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; + int st; + + gspca_dbg(gspca_dev, D_PACK, "bulk irq\n"); + if (!gspca_dev->streaming) + return; + switch (urb->status) { + case 0: + break; + case -ESHUTDOWN: + return; /* disconnection */ + default: +#ifdef CONFIG_PM + if (gspca_dev->frozen) + return; +#endif + gspca_err(gspca_dev, "urb status: %d\n", urb->status); + urb->status = 0; + goto resubmit; + } + + gspca_dbg(gspca_dev, D_PACK, "packet l:%d\n", urb->actual_length); + gspca_dev->sd_desc->pkt_scan(gspca_dev, + urb->transfer_buffer, + urb->actual_length); + +resubmit: + if (!gspca_dev->streaming) + return; + /* resubmit the URB */ + if (gspca_dev->cam.bulk_nurbs != 0) { + st = usb_submit_urb(urb, GFP_ATOMIC); + if (st < 0) + pr_err("usb_submit_urb() ret %d\n", st); + } +} + +/* + * add data to the current frame + * + * 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 + * - one LAST_PACKET + * DISCARD_PACKET invalidates the whole frame. + */ +void gspca_frame_add(struct gspca_dev *gspca_dev, + enum gspca_packet_type packet_type, + const u8 *data, + int len) +{ + struct gspca_buffer *buf; + unsigned long flags; + + gspca_dbg(gspca_dev, D_PACK, "add t:%d l:%d\n", packet_type, len); + + spin_lock_irqsave(&gspca_dev->qlock, flags); + buf = list_first_entry_or_null(&gspca_dev->buf_list, + typeof(*buf), list); + spin_unlock_irqrestore(&gspca_dev->qlock, flags); + + if (packet_type == FIRST_PACKET) { + /* if there is no queued buffer, discard the whole frame */ + if (!buf) { + gspca_dev->last_packet_type = DISCARD_PACKET; + gspca_dev->sequence++; + return; + } + gspca_dev->image = vb2_plane_vaddr(&buf->vb.vb2_buf, 0); + gspca_dev->image_len = 0; + } else { + switch (gspca_dev->last_packet_type) { + case DISCARD_PACKET: + if (packet_type == LAST_PACKET) { + gspca_dev->last_packet_type = packet_type; + gspca_dev->image = NULL; + gspca_dev->image_len = 0; + } + return; + case LAST_PACKET: + return; + } + } + + /* append the packet to the frame buffer */ + if (len > 0) { + if (gspca_dev->image_len + len > PAGE_ALIGN(gspca_dev->pixfmt.sizeimage)) { + gspca_err(gspca_dev, "frame overflow %d > %d\n", + gspca_dev->image_len + len, + PAGE_ALIGN(gspca_dev->pixfmt.sizeimage)); + packet_type = DISCARD_PACKET; + } else { +/* !! image is NULL only when last pkt is LAST or DISCARD + if (gspca_dev->image == NULL) { + pr_err("gspca_frame_add() image == NULL\n"); + return; + } + */ + memcpy(gspca_dev->image + gspca_dev->image_len, + data, len); + gspca_dev->image_len += len; + } + } + gspca_dev->last_packet_type = packet_type; + + /* if last packet, invalidate packet concatenation until + * next first packet, wake up the application and advance + * in the queue */ + if (packet_type == LAST_PACKET) { + if (gspca_dev->image_len > gspca_dev->pixfmt.sizeimage) + gspca_dev->image_len = gspca_dev->pixfmt.sizeimage; + spin_lock_irqsave(&gspca_dev->qlock, flags); + list_del(&buf->list); + spin_unlock_irqrestore(&gspca_dev->qlock, flags); + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, + gspca_dev->image_len); + buf->vb.sequence = gspca_dev->sequence++; + buf->vb.field = V4L2_FIELD_NONE; + gspca_dbg(gspca_dev, D_FRAM, "frame complete len:%d\n", + gspca_dev->image_len); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + gspca_dev->image = NULL; + gspca_dev->image_len = 0; + } +} +EXPORT_SYMBOL(gspca_frame_add); + +static void destroy_urbs(struct gspca_dev *gspca_dev) +{ + struct urb *urb; + unsigned int i; + + gspca_dbg(gspca_dev, D_STREAM, "kill transfer\n"); + + /* Killing all URBs guarantee that no URB completion + * handler is running. Therefore, there shouldn't + * be anyone trying to access gspca_dev->urb[i] + */ + for (i = 0; i < MAX_NURBS; i++) + usb_kill_urb(gspca_dev->urb[i]); + + gspca_dbg(gspca_dev, D_STREAM, "releasing urbs\n"); + for (i = 0; i < MAX_NURBS; i++) { + urb = gspca_dev->urb[i]; + if (!urb) + continue; + gspca_dev->urb[i] = NULL; + usb_free_coherent(gspca_dev->dev, + urb->transfer_buffer_length, + urb->transfer_buffer, + urb->transfer_dma); + usb_free_urb(urb); + } +} + +static int gspca_set_alt0(struct gspca_dev *gspca_dev) +{ + int ret; + + if (gspca_dev->alt == 0) + return 0; + ret = usb_set_interface(gspca_dev->dev, gspca_dev->iface, 0); + if (ret < 0) + pr_err("set alt 0 err %d\n", ret); + return ret; +} + +/* + * look for an input transfer endpoint in an alternate setting. + * + * If xfer_ep is invalid, return the first valid ep found, otherwise + * look for exactly the ep with address equal to xfer_ep. + */ +static struct usb_host_endpoint *alt_xfer(struct usb_host_interface *alt, + int xfer, int xfer_ep) +{ + struct usb_host_endpoint *ep; + int i, attr; + + for (i = 0; i < alt->desc.bNumEndpoints; i++) { + ep = &alt->endpoint[i]; + attr = ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + if (attr == xfer + && ep->desc.wMaxPacketSize != 0 + && usb_endpoint_dir_in(&ep->desc) + && (xfer_ep < 0 || ep->desc.bEndpointAddress == xfer_ep)) + return ep; + } + return NULL; +} + +/* compute the minimum bandwidth for the current transfer */ +static u32 which_bandwidth(struct gspca_dev *gspca_dev) +{ + u32 bandwidth; + + /* get the (max) image size */ + bandwidth = gspca_dev->pixfmt.sizeimage; + + /* if the image is compressed, estimate its mean size */ + if (!gspca_dev->cam.needs_full_bandwidth && + bandwidth < gspca_dev->pixfmt.width * + gspca_dev->pixfmt.height) + bandwidth = bandwidth * 3 / 8; /* 0.375 */ + + /* estimate the frame rate */ + if (gspca_dev->sd_desc->get_streamparm) { + struct v4l2_streamparm parm; + + gspca_dev->sd_desc->get_streamparm(gspca_dev, &parm); + bandwidth *= parm.parm.capture.timeperframe.denominator; + bandwidth /= parm.parm.capture.timeperframe.numerator; + } else { + + /* don't hope more than 15 fps with USB 1.1 and + * image resolution >= 640x480 */ + if (gspca_dev->pixfmt.width >= 640 + && gspca_dev->dev->speed == USB_SPEED_FULL) + bandwidth *= 15; /* 15 fps */ + else + bandwidth *= 30; /* 30 fps */ + } + + gspca_dbg(gspca_dev, D_STREAM, "min bandwidth: %d\n", bandwidth); + return bandwidth; +} + +/* endpoint table */ +#define MAX_ALT 16 +struct ep_tb_s { + u32 alt; + u32 bandwidth; +}; + +/* + * build the table of the endpoints + * and compute the minimum bandwidth for the image transfer + */ +static int build_isoc_ep_tb(struct gspca_dev *gspca_dev, + struct usb_interface *intf, + struct ep_tb_s *ep_tb) +{ + struct usb_host_endpoint *ep; + int i, j, nbalt, psize, found; + u32 bandwidth, last_bw; + + nbalt = intf->num_altsetting; + if (nbalt > MAX_ALT) + nbalt = MAX_ALT; /* fixme: should warn */ + + /* build the endpoint table */ + i = 0; + last_bw = 0; + for (;;) { + ep_tb->bandwidth = 2000 * 2000 * 120; + found = 0; + for (j = 0; j < nbalt; j++) { + ep = alt_xfer(&intf->altsetting[j], + USB_ENDPOINT_XFER_ISOC, + gspca_dev->xfer_ep); + if (ep == NULL) + continue; + if (ep->desc.bInterval == 0) { + pr_err("alt %d iso endp with 0 interval\n", j); + continue; + } + psize = le16_to_cpu(ep->desc.wMaxPacketSize); + psize = (psize & 0x07ff) * (1 + ((psize >> 11) & 3)); + bandwidth = psize * 1000; + if (gspca_dev->dev->speed == USB_SPEED_HIGH + || gspca_dev->dev->speed >= USB_SPEED_SUPER) + bandwidth *= 8; + bandwidth /= 1 << (ep->desc.bInterval - 1); + if (bandwidth <= last_bw) + continue; + if (bandwidth < ep_tb->bandwidth) { + ep_tb->bandwidth = bandwidth; + ep_tb->alt = j; + found = 1; + } + } + if (!found) + break; + gspca_dbg(gspca_dev, D_STREAM, "alt %d bandwidth %d\n", + ep_tb->alt, ep_tb->bandwidth); + last_bw = ep_tb->bandwidth; + i++; + ep_tb++; + } + + /* + * If the camera: + * has a usb audio class interface (a built in usb mic); and + * is a usb 1 full speed device; and + * uses the max full speed iso bandwidth; and + * and has more than 1 alt setting + * then skip the highest alt setting to spare bandwidth for the mic + */ + if (gspca_dev->audio && + gspca_dev->dev->speed == USB_SPEED_FULL && + last_bw >= 1000000 && + i > 1) { + gspca_dbg(gspca_dev, D_STREAM, "dev has usb audio, skipping highest alt\n"); + i--; + ep_tb--; + } + + /* get the requested bandwidth and start at the highest atlsetting */ + bandwidth = which_bandwidth(gspca_dev); + ep_tb--; + while (i > 1) { + ep_tb--; + if (ep_tb->bandwidth < bandwidth) + break; + i--; + } + return i; +} + +/* + * create the URBs for image transfer + */ +static int create_urbs(struct gspca_dev *gspca_dev, + struct usb_host_endpoint *ep) +{ + struct urb *urb; + int n, nurbs, i, psize, npkt, bsize; + + /* calculate the packet size and the number of packets */ + psize = le16_to_cpu(ep->desc.wMaxPacketSize); + + if (!gspca_dev->cam.bulk) { /* isoc */ + + /* See paragraph 5.9 / table 5-11 of the usb 2.0 spec. */ + if (gspca_dev->pkt_size == 0) + psize = (psize & 0x07ff) * (1 + ((psize >> 11) & 3)); + else + psize = gspca_dev->pkt_size; + npkt = gspca_dev->cam.npkt; + if (npkt == 0) + npkt = 32; /* default value */ + bsize = psize * npkt; + gspca_dbg(gspca_dev, D_STREAM, + "isoc %d pkts size %d = bsize:%d\n", + npkt, psize, bsize); + nurbs = DEF_NURBS; + } else { /* bulk */ + npkt = 0; + bsize = gspca_dev->cam.bulk_size; + if (bsize == 0) + bsize = psize; + gspca_dbg(gspca_dev, D_STREAM, "bulk bsize:%d\n", bsize); + if (gspca_dev->cam.bulk_nurbs != 0) + nurbs = gspca_dev->cam.bulk_nurbs; + else + nurbs = 1; + } + + for (n = 0; n < nurbs; n++) { + urb = usb_alloc_urb(npkt, GFP_KERNEL); + if (!urb) + return -ENOMEM; + gspca_dev->urb[n] = urb; + urb->transfer_buffer = usb_alloc_coherent(gspca_dev->dev, + bsize, + GFP_KERNEL, + &urb->transfer_dma); + + if (urb->transfer_buffer == NULL) { + pr_err("usb_alloc_coherent failed\n"); + return -ENOMEM; + } + urb->dev = gspca_dev->dev; + urb->context = gspca_dev; + urb->transfer_buffer_length = bsize; + 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 = 1 << (ep->desc.bInterval - 1); + 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; +} + +/* 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 = false; + gspca_dev->usb_err = 0; + 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->present) + gspca_input_create_urb(gspca_dev); + if (gspca_dev->sd_desc->stop0) + gspca_dev->sd_desc->stop0(gspca_dev); + gspca_dbg(gspca_dev, D_STREAM, "stream off OK\n"); +} + +/* + * start the USB transfer + */ +static int gspca_init_transfer(struct gspca_dev *gspca_dev) +{ + struct usb_interface *intf; + struct usb_host_endpoint *ep; + struct urb *urb; + struct ep_tb_s ep_tb[MAX_ALT]; + int n, ret, xfer, alt, alt_idx; + + /* reset the streaming variables */ + gspca_dev->image = NULL; + gspca_dev->image_len = 0; + gspca_dev->last_packet_type = DISCARD_PACKET; + + gspca_dev->usb_err = 0; + + /* do the specific subdriver stuff before endpoint selection */ + intf = usb_ifnum_to_if(gspca_dev->dev, gspca_dev->iface); + gspca_dev->alt = gspca_dev->cam.bulk ? intf->num_altsetting : 0; + if (gspca_dev->sd_desc->isoc_init) { + ret = gspca_dev->sd_desc->isoc_init(gspca_dev); + if (ret < 0) + return ret; + } + xfer = gspca_dev->cam.bulk ? USB_ENDPOINT_XFER_BULK + : USB_ENDPOINT_XFER_ISOC; + + /* if bulk or the subdriver forced an altsetting, get the endpoint */ + if (gspca_dev->alt != 0) { + gspca_dev->alt--; /* (previous version compatibility) */ + ep = alt_xfer(&intf->altsetting[gspca_dev->alt], xfer, + gspca_dev->xfer_ep); + if (ep == NULL) { + pr_err("bad altsetting %d\n", gspca_dev->alt); + return -EIO; + } + ep_tb[0].alt = gspca_dev->alt; + alt_idx = 1; + } else { + /* else, compute the minimum bandwidth + * and build the endpoint table */ + alt_idx = build_isoc_ep_tb(gspca_dev, intf, ep_tb); + if (alt_idx <= 0) { + pr_err("no transfer endpoint found\n"); + return -EIO; + } + } + + /* set the highest alternate setting and + * loop until urb submit succeeds */ + gspca_input_destroy_urb(gspca_dev); + + gspca_dev->alt = ep_tb[--alt_idx].alt; + alt = -1; + for (;;) { + if (alt != gspca_dev->alt) { + alt = gspca_dev->alt; + if (intf->num_altsetting > 1) { + ret = usb_set_interface(gspca_dev->dev, + gspca_dev->iface, + alt); + if (ret < 0) { + if (ret == -ENOSPC) + goto retry; /*fixme: ugly*/ + pr_err("set alt %d err %d\n", alt, ret); + goto out; + } + } + } + if (!gspca_dev->cam.no_urb_create) { + gspca_dbg(gspca_dev, D_STREAM, "init transfer alt %d\n", + alt); + ret = create_urbs(gspca_dev, + alt_xfer(&intf->altsetting[alt], xfer, + gspca_dev->xfer_ep)); + if (ret < 0) { + destroy_urbs(gspca_dev); + goto out; + } + } + + /* clear the bulk endpoint */ + if (gspca_dev->cam.bulk) + usb_clear_halt(gspca_dev->dev, + gspca_dev->urb[0]->pipe); + + /* start the cam */ + ret = gspca_dev->sd_desc->start(gspca_dev); + if (ret < 0) { + destroy_urbs(gspca_dev); + goto out; + } + v4l2_ctrl_handler_setup(gspca_dev->vdev.ctrl_handler); + gspca_dev->streaming = true; + + /* some bulk transfers are started by the subdriver */ + if (gspca_dev->cam.bulk && gspca_dev->cam.bulk_nurbs == 0) + break; + + /* submit the URBs */ + 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; + } + if (ret >= 0) + break; /* transfer is started */ + + /* something when wrong + * stop the webcam and free the transfer resources */ + gspca_stream_off(gspca_dev); + if (ret != -ENOSPC) { + pr_err("usb_submit_urb alt %d err %d\n", + gspca_dev->alt, ret); + goto out; + } + + /* the bandwidth is not wide enough + * negotiate or try a lower alternate setting */ +retry: + gspca_err(gspca_dev, "alt %d - bandwidth not wide enough, trying again\n", + alt); + msleep(20); /* wait for kill complete */ + if (gspca_dev->sd_desc->isoc_nego) { + ret = gspca_dev->sd_desc->isoc_nego(gspca_dev); + if (ret < 0) + goto out; + } else { + if (alt_idx <= 0) { + pr_err("no transfer endpoint found\n"); + ret = -EIO; + goto out; + } + gspca_dev->alt = ep_tb[--alt_idx].alt; + } + } +out: + gspca_input_create_urb(gspca_dev); + return ret; +} + +static void gspca_set_default_mode(struct gspca_dev *gspca_dev) +{ + int i; + + i = gspca_dev->cam.nmodes - 1; /* take the highest mode */ + gspca_dev->curr_mode = i; + gspca_dev->pixfmt = gspca_dev->cam.cam_mode[i]; + + /* does nothing if ctrl_handler == NULL */ + v4l2_ctrl_handler_setup(gspca_dev->vdev.ctrl_handler); +} + +static int wxh_to_mode(struct gspca_dev *gspca_dev, + int width, int height, u32 pixelformat) +{ + int i; + + for (i = 0; i < gspca_dev->cam.nmodes; i++) { + if (width == gspca_dev->cam.cam_mode[i].width + && height == gspca_dev->cam.cam_mode[i].height + && pixelformat == gspca_dev->cam.cam_mode[i].pixelformat) + return i; + } + return -EINVAL; +} + +static int wxh_to_nearest_mode(struct gspca_dev *gspca_dev, + int width, int height, u32 pixelformat) +{ + int i; + + for (i = gspca_dev->cam.nmodes; --i >= 0; ) { + if (width >= gspca_dev->cam.cam_mode[i].width + && height >= gspca_dev->cam.cam_mode[i].height + && pixelformat == gspca_dev->cam.cam_mode[i].pixelformat) + return i; + } + for (i = gspca_dev->cam.nmodes; --i > 0; ) { + if (width >= gspca_dev->cam.cam_mode[i].width + && height >= gspca_dev->cam.cam_mode[i].height) + break; + } + return i; +} + +/* + * search a mode with the right pixel format + */ +static int gspca_get_mode(struct gspca_dev *gspca_dev, + int mode, + int pixfmt) +{ + int modeU, modeD; + + modeU = modeD = mode; + while ((modeU < gspca_dev->cam.nmodes) || modeD >= 0) { + if (--modeD >= 0) { + if (gspca_dev->cam.cam_mode[modeD].pixelformat + == pixfmt) + return modeD; + } + if (++modeU < gspca_dev->cam.nmodes) { + if (gspca_dev->cam.cam_mode[modeU].pixelformat + == pixfmt) + return modeU; + } + } + return -EINVAL; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vidioc_g_chip_info(struct file *file, void *priv, + struct v4l2_dbg_chip_info *chip) +{ + struct gspca_dev *gspca_dev = video_drvdata(file); + + gspca_dev->usb_err = 0; + if (gspca_dev->sd_desc->get_chip_info) + return gspca_dev->sd_desc->get_chip_info(gspca_dev, chip); + return chip->match.addr ? -EINVAL : 0; +} + +static int vidioc_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct gspca_dev *gspca_dev = video_drvdata(file); + + gspca_dev->usb_err = 0; + return gspca_dev->sd_desc->get_register(gspca_dev, reg); +} + +static int vidioc_s_register(struct file *file, void *priv, + const struct v4l2_dbg_register *reg) +{ + struct gspca_dev *gspca_dev = video_drvdata(file); + + gspca_dev->usb_err = 0; + return gspca_dev->sd_desc->set_register(gspca_dev, reg); +} +#endif + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *fmtdesc) +{ + struct gspca_dev *gspca_dev = video_drvdata(file); + int i, j, index; + __u32 fmt_tb[8]; + + /* give an index to each format */ + index = 0; + for (i = gspca_dev->cam.nmodes; --i >= 0; ) { + fmt_tb[index] = gspca_dev->cam.cam_mode[i].pixelformat; + j = 0; + for (;;) { + if (fmt_tb[j] == fmt_tb[index]) + break; + j++; + } + if (j == index) { + if (fmtdesc->index == index) + break; /* new format */ + index++; + if (index >= ARRAY_SIZE(fmt_tb)) + return -EINVAL; + } + } + if (i < 0) + return -EINVAL; /* no more format */ + + fmtdesc->pixelformat = fmt_tb[index]; + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *_priv, + struct v4l2_format *fmt) +{ + struct gspca_dev *gspca_dev = video_drvdata(file); + u32 priv = fmt->fmt.pix.priv; + + fmt->fmt.pix = gspca_dev->pixfmt; + /* some drivers use priv internally, so keep the original value */ + fmt->fmt.pix.priv = priv; + return 0; +} + +static int try_fmt_vid_cap(struct gspca_dev *gspca_dev, + struct v4l2_format *fmt) +{ + int w, h, mode, mode2; + + w = fmt->fmt.pix.width; + h = fmt->fmt.pix.height; + + PDEBUG_MODE(gspca_dev, D_CONF, "try fmt cap", + fmt->fmt.pix.pixelformat, w, h); + + /* search the nearest mode for width and height */ + mode = wxh_to_nearest_mode(gspca_dev, w, h, fmt->fmt.pix.pixelformat); + + /* OK if right palette */ + if (gspca_dev->cam.cam_mode[mode].pixelformat + != fmt->fmt.pix.pixelformat) { + + /* else, search the closest mode with the same pixel format */ + mode2 = gspca_get_mode(gspca_dev, mode, + fmt->fmt.pix.pixelformat); + if (mode2 >= 0) + mode = mode2; + } + fmt->fmt.pix = gspca_dev->cam.cam_mode[mode]; + if (gspca_dev->sd_desc->try_fmt) { + /* pass original resolution to subdriver try_fmt */ + fmt->fmt.pix.width = w; + fmt->fmt.pix.height = h; + gspca_dev->sd_desc->try_fmt(gspca_dev, fmt); + } + return mode; /* used when s_fmt */ +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *_priv, + struct v4l2_format *fmt) +{ + struct gspca_dev *gspca_dev = video_drvdata(file); + u32 priv = fmt->fmt.pix.priv; + + if (try_fmt_vid_cap(gspca_dev, fmt) < 0) + return -EINVAL; + /* some drivers use priv internally, so keep the original value */ + fmt->fmt.pix.priv = priv; + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *_priv, + struct v4l2_format *fmt) +{ + struct gspca_dev *gspca_dev = video_drvdata(file); + u32 priv = fmt->fmt.pix.priv; + int mode; + + if (vb2_is_busy(&gspca_dev->queue)) + return -EBUSY; + + mode = try_fmt_vid_cap(gspca_dev, fmt); + if (mode < 0) + return -EINVAL; + + gspca_dev->curr_mode = mode; + if (gspca_dev->sd_desc->try_fmt) + /* subdriver try_fmt can modify format parameters */ + gspca_dev->pixfmt = fmt->fmt.pix; + else + gspca_dev->pixfmt = gspca_dev->cam.cam_mode[mode]; + /* some drivers use priv internally, so keep the original value */ + fmt->fmt.pix.priv = priv; + return 0; +} + +static int vidioc_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct gspca_dev *gspca_dev = video_drvdata(file); + int i; + __u32 index = 0; + + if (gspca_dev->sd_desc->enum_framesizes) + return gspca_dev->sd_desc->enum_framesizes(gspca_dev, fsize); + + for (i = 0; i < gspca_dev->cam.nmodes; i++) { + if (fsize->pixel_format != + gspca_dev->cam.cam_mode[i].pixelformat) + continue; + + if (fsize->index == index) { + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = + gspca_dev->cam.cam_mode[i].width; + fsize->discrete.height = + gspca_dev->cam.cam_mode[i].height; + return 0; + } + index++; + } + + return -EINVAL; +} + +static int vidioc_enum_frameintervals(struct file *filp, void *priv, + struct v4l2_frmivalenum *fival) +{ + struct gspca_dev *gspca_dev = video_drvdata(filp); + int mode; + __u32 i; + + mode = wxh_to_mode(gspca_dev, fival->width, fival->height, + fival->pixel_format); + if (mode < 0) + return -EINVAL; + + if (gspca_dev->cam.mode_framerates == NULL || + gspca_dev->cam.mode_framerates[mode].nrates == 0) + return -EINVAL; + + if (fival->pixel_format != + gspca_dev->cam.cam_mode[mode].pixelformat) + return -EINVAL; + + for (i = 0; i < gspca_dev->cam.mode_framerates[mode].nrates; i++) { + if (fival->index == i) { + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete.numerator = 1; + fival->discrete.denominator = + gspca_dev->cam.mode_framerates[mode].rates[i]; + return 0; + } + } + + return -EINVAL; +} + +static void gspca_release(struct v4l2_device *v4l2_device) +{ + struct gspca_dev *gspca_dev = + container_of(v4l2_device, struct gspca_dev, v4l2_dev); + + v4l2_ctrl_handler_free(gspca_dev->vdev.ctrl_handler); + v4l2_device_unregister(&gspca_dev->v4l2_dev); + kfree(gspca_dev->usb_buf); + kfree(gspca_dev); +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct gspca_dev *gspca_dev = video_drvdata(file); + + strscpy((char *)cap->driver, gspca_dev->sd_desc->name, + sizeof(cap->driver)); + if (gspca_dev->dev->product != NULL) { + strscpy((char *)cap->card, gspca_dev->dev->product, + sizeof(cap->card)); + } else { + snprintf((char *) cap->card, sizeof cap->card, + "USB Camera (%04x:%04x)", + le16_to_cpu(gspca_dev->dev->descriptor.idVendor), + le16_to_cpu(gspca_dev->dev->descriptor.idProduct)); + } + usb_make_path(gspca_dev->dev, (char *) cap->bus_info, + sizeof(cap->bus_info)); + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct gspca_dev *gspca_dev = video_drvdata(file); + + if (input->index != 0) + return -EINVAL; + input->type = V4L2_INPUT_TYPE_CAMERA; + input->status = gspca_dev->cam.input_flags; + strscpy(input->name, gspca_dev->sd_desc->name, + sizeof input->name); + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i > 0) + return -EINVAL; + return 0; +} + +static int vidioc_g_jpegcomp(struct file *file, void *priv, + struct v4l2_jpegcompression *jpegcomp) +{ + struct gspca_dev *gspca_dev = video_drvdata(file); + + gspca_dev->usb_err = 0; + return gspca_dev->sd_desc->get_jcomp(gspca_dev, jpegcomp); +} + +static int vidioc_s_jpegcomp(struct file *file, void *priv, + const struct v4l2_jpegcompression *jpegcomp) +{ + struct gspca_dev *gspca_dev = video_drvdata(file); + + gspca_dev->usb_err = 0; + return gspca_dev->sd_desc->set_jcomp(gspca_dev, jpegcomp); +} + +static int vidioc_g_parm(struct file *filp, void *priv, + struct v4l2_streamparm *parm) +{ + struct gspca_dev *gspca_dev = video_drvdata(filp); + + parm->parm.capture.readbuffers = gspca_dev->queue.min_buffers_needed; + + if (!gspca_dev->sd_desc->get_streamparm) + return 0; + + parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + gspca_dev->usb_err = 0; + gspca_dev->sd_desc->get_streamparm(gspca_dev, parm); + return gspca_dev->usb_err; +} + +static int vidioc_s_parm(struct file *filp, void *priv, + struct v4l2_streamparm *parm) +{ + struct gspca_dev *gspca_dev = video_drvdata(filp); + + parm->parm.capture.readbuffers = gspca_dev->queue.min_buffers_needed; + + if (!gspca_dev->sd_desc->set_streamparm) { + parm->parm.capture.capability = 0; + return 0; + } + + parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + gspca_dev->usb_err = 0; + gspca_dev->sd_desc->set_streamparm(gspca_dev, parm); + return gspca_dev->usb_err; +} + +static int gspca_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct gspca_dev *gspca_dev = vb2_get_drv_priv(vq); + unsigned int size = PAGE_ALIGN(gspca_dev->pixfmt.sizeimage); + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + *nplanes = 1; + sizes[0] = size; + return 0; +} + +static int gspca_buffer_prepare(struct vb2_buffer *vb) +{ + struct gspca_dev *gspca_dev = vb2_get_drv_priv(vb->vb2_queue); + unsigned long size = PAGE_ALIGN(gspca_dev->pixfmt.sizeimage); + + if (vb2_plane_size(vb, 0) < size) { + gspca_err(gspca_dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + return 0; +} + +static void gspca_buffer_finish(struct vb2_buffer *vb) +{ + struct gspca_dev *gspca_dev = vb2_get_drv_priv(vb->vb2_queue); + + if (!gspca_dev->sd_desc->dq_callback) + return; + + gspca_dev->usb_err = 0; + if (gspca_dev->present) + gspca_dev->sd_desc->dq_callback(gspca_dev); +} + +static void gspca_buffer_queue(struct vb2_buffer *vb) +{ + struct gspca_dev *gspca_dev = vb2_get_drv_priv(vb->vb2_queue); + struct gspca_buffer *buf = to_gspca_buffer(vb); + unsigned long flags; + + spin_lock_irqsave(&gspca_dev->qlock, flags); + list_add_tail(&buf->list, &gspca_dev->buf_list); + spin_unlock_irqrestore(&gspca_dev->qlock, flags); +} + +static void gspca_return_all_buffers(struct gspca_dev *gspca_dev, + enum vb2_buffer_state state) +{ + struct gspca_buffer *buf, *node; + unsigned long flags; + + spin_lock_irqsave(&gspca_dev->qlock, flags); + list_for_each_entry_safe(buf, node, &gspca_dev->buf_list, list) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->list); + } + spin_unlock_irqrestore(&gspca_dev->qlock, flags); +} + +static int gspca_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct gspca_dev *gspca_dev = vb2_get_drv_priv(vq); + int ret; + + gspca_dev->sequence = 0; + + ret = gspca_init_transfer(gspca_dev); + if (ret) + gspca_return_all_buffers(gspca_dev, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void gspca_stop_streaming(struct vb2_queue *vq) +{ + struct gspca_dev *gspca_dev = vb2_get_drv_priv(vq); + + gspca_stream_off(gspca_dev); + + /* Release all active buffers */ + gspca_return_all_buffers(gspca_dev, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops gspca_qops = { + .queue_setup = gspca_queue_setup, + .buf_prepare = gspca_buffer_prepare, + .buf_finish = gspca_buffer_finish, + .buf_queue = gspca_buffer_queue, + .start_streaming = gspca_start_streaming, + .stop_streaming = gspca_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static const struct v4l2_file_operations dev_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +static const struct v4l2_ioctl_ops dev_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_g_jpegcomp = vidioc_g_jpegcomp, + .vidioc_s_jpegcomp = vidioc_s_jpegcomp, + .vidioc_g_parm = vidioc_g_parm, + .vidioc_s_parm = vidioc_s_parm, + .vidioc_enum_framesizes = vidioc_enum_framesizes, + .vidioc_enum_frameintervals = vidioc_enum_frameintervals, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_chip_info = vidioc_g_chip_info, + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct video_device gspca_template = { + .name = "gspca main driver", + .fops = &dev_fops, + .ioctl_ops = &dev_ioctl_ops, + .release = video_device_release_empty, /* We use v4l2_dev.release */ +}; + +/* + * probe and create a new gspca device + * + * This function must be called by the sub-driver when it is + * called for probing a new device. + */ +int gspca_dev_probe2(struct usb_interface *intf, + const struct usb_device_id *id, + const struct sd_desc *sd_desc, + int dev_size, + struct module *module) +{ + struct gspca_dev *gspca_dev; + struct usb_device *dev = interface_to_usbdev(intf); + struct vb2_queue *q; + int ret; + + pr_info("%s-" GSPCA_VERSION " probing %04x:%04x\n", + sd_desc->name, id->idVendor, id->idProduct); + + /* create the device */ + if (dev_size < sizeof *gspca_dev) + dev_size = sizeof *gspca_dev; + gspca_dev = kzalloc(dev_size, GFP_KERNEL); + if (!gspca_dev) { + pr_err("couldn't kzalloc gspca struct\n"); + return -ENOMEM; + } + gspca_dev->usb_buf = kzalloc(USB_BUF_SZ, GFP_KERNEL); + if (!gspca_dev->usb_buf) { + pr_err("out of memory\n"); + ret = -ENOMEM; + goto out; + } + gspca_dev->dev = dev; + gspca_dev->iface = intf->cur_altsetting->desc.bInterfaceNumber; + gspca_dev->xfer_ep = -1; + + /* check if any audio device */ + if (dev->actconfig->desc.bNumInterfaces != 1) { + int i; + struct usb_interface *intf2; + + for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { + intf2 = dev->actconfig->interface[i]; + if (intf2 != NULL + && intf2->altsetting != NULL + && intf2->altsetting->desc.bInterfaceClass == + USB_CLASS_AUDIO) { + gspca_dev->audio = 1; + break; + } + } + } + + gspca_dev->v4l2_dev.release = gspca_release; + ret = v4l2_device_register(&intf->dev, &gspca_dev->v4l2_dev); + if (ret) + goto out; + gspca_dev->present = true; + gspca_dev->sd_desc = sd_desc; + gspca_dev->empty_packet = -1; /* don't check the empty packets */ + gspca_dev->vdev = gspca_template; + gspca_dev->vdev.v4l2_dev = &gspca_dev->v4l2_dev; + gspca_dev->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; + video_set_drvdata(&gspca_dev->vdev, gspca_dev); + gspca_dev->module = module; + + mutex_init(&gspca_dev->usb_lock); + gspca_dev->vdev.lock = &gspca_dev->usb_lock; + init_waitqueue_head(&gspca_dev->wq); + + /* Initialize the vb2 queue */ + q = &gspca_dev->queue; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ; + q->drv_priv = gspca_dev; + q->buf_struct_size = sizeof(struct gspca_buffer); + q->ops = &gspca_qops; + q->mem_ops = &vb2_vmalloc_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 2; + q->lock = &gspca_dev->usb_lock; + ret = vb2_queue_init(q); + if (ret) + goto out; + gspca_dev->vdev.queue = q; + + INIT_LIST_HEAD(&gspca_dev->buf_list); + spin_lock_init(&gspca_dev->qlock); + + /* configure the subdriver and initialize the USB device */ + ret = sd_desc->config(gspca_dev, id); + if (ret < 0) + goto out; + ret = sd_desc->init(gspca_dev); + if (ret < 0) + goto out; + if (sd_desc->init_controls) + ret = sd_desc->init_controls(gspca_dev); + if (ret < 0) + goto out; + gspca_set_default_mode(gspca_dev); + + ret = gspca_input_connect(gspca_dev); + if (ret) + goto out; + +#ifdef CONFIG_VIDEO_ADV_DEBUG + if (!gspca_dev->sd_desc->get_register) + v4l2_disable_ioctl(&gspca_dev->vdev, VIDIOC_DBG_G_REGISTER); + if (!gspca_dev->sd_desc->set_register) + v4l2_disable_ioctl(&gspca_dev->vdev, VIDIOC_DBG_S_REGISTER); +#endif + if (!gspca_dev->sd_desc->get_jcomp) + v4l2_disable_ioctl(&gspca_dev->vdev, VIDIOC_G_JPEGCOMP); + if (!gspca_dev->sd_desc->set_jcomp) + v4l2_disable_ioctl(&gspca_dev->vdev, VIDIOC_S_JPEGCOMP); + + /* init video stuff */ + ret = video_register_device(&gspca_dev->vdev, + VFL_TYPE_VIDEO, + -1); + if (ret < 0) { + pr_err("video_register_device err %d\n", ret); + goto out; + } + + usb_set_intfdata(intf, gspca_dev); + gspca_dbg(gspca_dev, D_PROBE, "%s created\n", + video_device_node_name(&gspca_dev->vdev)); + + gspca_input_create_urb(gspca_dev); + + return 0; +out: +#if IS_ENABLED(CONFIG_INPUT) + if (gspca_dev->input_dev) + input_unregister_device(gspca_dev->input_dev); +#endif + v4l2_ctrl_handler_free(gspca_dev->vdev.ctrl_handler); + v4l2_device_unregister(&gspca_dev->v4l2_dev); + if (sd_desc->probe_error) + sd_desc->probe_error(gspca_dev); + kfree(gspca_dev->usb_buf); + kfree(gspca_dev); + return ret; +} +EXPORT_SYMBOL(gspca_dev_probe2); + +/* same function as the previous one, but check the interface */ +int gspca_dev_probe(struct usb_interface *intf, + const struct usb_device_id *id, + const struct sd_desc *sd_desc, + int dev_size, + struct module *module) +{ + struct usb_device *dev = interface_to_usbdev(intf); + + /* we don't handle multi-config cameras */ + if (dev->descriptor.bNumConfigurations != 1) { + pr_err("%04x:%04x too many config\n", + id->idVendor, id->idProduct); + return -ENODEV; + } + + /* the USB video interface must be the first one */ + if (dev->actconfig->desc.bNumInterfaces != 1 + && intf->cur_altsetting->desc.bInterfaceNumber != 0) + return -ENODEV; + + return gspca_dev_probe2(intf, id, sd_desc, dev_size, module); +} +EXPORT_SYMBOL(gspca_dev_probe); + +/* + * USB disconnection + * + * This function must be called by the sub-driver + * when the device disconnects, after the specific resources are freed. + */ +void gspca_disconnect(struct usb_interface *intf) +{ + struct gspca_dev *gspca_dev = usb_get_intfdata(intf); +#if IS_ENABLED(CONFIG_INPUT) + struct input_dev *input_dev; +#endif + + gspca_dbg(gspca_dev, D_PROBE, "%s disconnect\n", + video_device_node_name(&gspca_dev->vdev)); + + mutex_lock(&gspca_dev->usb_lock); + gspca_dev->present = false; + destroy_urbs(gspca_dev); + gspca_input_destroy_urb(gspca_dev); + + vb2_queue_error(&gspca_dev->queue); + +#if IS_ENABLED(CONFIG_INPUT) + input_dev = gspca_dev->input_dev; + if (input_dev) { + gspca_dev->input_dev = NULL; + input_unregister_device(input_dev); + } +#endif + + v4l2_device_disconnect(&gspca_dev->v4l2_dev); + video_unregister_device(&gspca_dev->vdev); + + mutex_unlock(&gspca_dev->usb_lock); + + /* (this will call gspca_release() immediately or on last close) */ + v4l2_device_put(&gspca_dev->v4l2_dev); +} +EXPORT_SYMBOL(gspca_disconnect); + +#ifdef CONFIG_PM +int gspca_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct gspca_dev *gspca_dev = usb_get_intfdata(intf); + + gspca_input_destroy_urb(gspca_dev); + + if (!vb2_start_streaming_called(&gspca_dev->queue)) + return 0; + + mutex_lock(&gspca_dev->usb_lock); + gspca_dev->frozen = 1; /* avoid urb error messages */ + gspca_dev->usb_err = 0; + if (gspca_dev->sd_desc->stopN) + gspca_dev->sd_desc->stopN(gspca_dev); + destroy_urbs(gspca_dev); + gspca_set_alt0(gspca_dev); + if (gspca_dev->sd_desc->stop0) + gspca_dev->sd_desc->stop0(gspca_dev); + mutex_unlock(&gspca_dev->usb_lock); + + return 0; +} +EXPORT_SYMBOL(gspca_suspend); + +int gspca_resume(struct usb_interface *intf) +{ + struct gspca_dev *gspca_dev = usb_get_intfdata(intf); + int streaming, ret = 0; + + mutex_lock(&gspca_dev->usb_lock); + gspca_dev->frozen = 0; + gspca_dev->usb_err = 0; + gspca_dev->sd_desc->init(gspca_dev); + /* + * Most subdrivers send all ctrl values on sd_start and thus + * only write to the device registers on s_ctrl when streaming -> + * Clear streaming to avoid setting all ctrls twice. + */ + streaming = vb2_start_streaming_called(&gspca_dev->queue); + if (streaming) + ret = gspca_init_transfer(gspca_dev); + else + gspca_input_create_urb(gspca_dev); + mutex_unlock(&gspca_dev->usb_lock); + + return ret; +} +EXPORT_SYMBOL(gspca_resume); +#endif + +/* -- module insert / remove -- */ +static int __init gspca_init(void) +{ + pr_info("v" GSPCA_VERSION " registered\n"); + return 0; +} +static void __exit gspca_exit(void) +{ +} + +module_init(gspca_init); +module_exit(gspca_exit); + +module_param_named(debug, gspca_debug, int, 0644); +MODULE_PARM_DESC(debug, + "1:probe 2:config 3:stream 4:frame 5:packet 6:usbi 7:usbo"); |