diff options
-rw-r--r-- | Documentation/video4linux/gspca.txt | 238 | ||||
-rw-r--r-- | drivers/media/video/Kconfig | 2 | ||||
-rw-r--r-- | drivers/media/video/gspca/Kconfig | 13 | ||||
-rw-r--r-- | drivers/media/video/gspca/Makefile | 4 | ||||
-rw-r--r-- | drivers/media/video/gspca/gspca.c | 1750 | ||||
-rw-r--r-- | drivers/media/video/gspca/gspca.h | 178 | ||||
-rw-r--r-- | drivers/media/video/gspca/jpeg.h | 301 | ||||
-rw-r--r-- | drivers/media/video/gspca/stk014.c | 562 |
8 files changed, 3048 insertions, 0 deletions
diff --git a/Documentation/video4linux/gspca.txt b/Documentation/video4linux/gspca.txt new file mode 100644 index 00000000000..9c404b56dbb --- /dev/null +++ b/Documentation/video4linux/gspca.txt @@ -0,0 +1,238 @@ +Here the list of the known working cameras with gspca. + +The modules are: + gspca_main main driver + gspca_xxxx subdriver module with xxxx as follows + +xxxx vend:prod +---- +conex 0572:0041 Creative Notebook cx11646 +etoms 102c:6151 Qcam Sangha CIF +etoms 102c:6251 Qcam xxxxxx VGA +mars 093a:050f Mars-Semi Pc-Camera +ov519 041e:4052 Creative Live! VISTA IM +ov519 041e:405f Creative Live! VISTA VF0330 +ov519 041e:4060 Creative Live! VISTA VF0350 +ov519 041e:4061 Creative Live! VISTA VF0400 +ov519 041e:4064 Creative Live! VISTA VF0420 +ov519 041e:4068 Creative Live! VISTA VF0470 +ov519 045e:028c Micro$oft xbox cam +ov519 054c:0154 Sonny toy4 +ov519 054c:0155 Sonny toy5 +ov519 05a9:0519 OmniVision +ov519 05a9:4519 OmniVision +ov519 05a9:8519 OmniVision +ov519 05a9:0530 OmniVision +pac207 041e:4028 Creative Webcam Vista Plus +pac207 093a:2460 PAC207 Qtec Webcam 100 +pac207 093a:2463 Philips spc200nc pac207 +pac207 093a:2464 Labtec Webcam 1200 +pac207 093a:2468 PAC207 +pac207 093a:2470 Genius GF112 +pac207 093a:2471 PAC207 Genius VideoCam ge111 +pac207 093a:2472 PAC207 Genius VideoCam ge110 +pac7311 093a:2600 PAC7311 Typhoon +pac7311 093a:2601 PAC7311 Phillips SPC610NC +pac7311 093a:2603 PAC7312 +pac7311 093a:2608 PAC7311 Trust WB-3300p +pac7311 093a:260e PAC7311 Gigaware VGA PC Camera, Trust WB-3350p, SIGMA cam 2350 +pac7311 093a:260f PAC7311 SnakeCam +sonixb 0c45:6001 Genius VideoCAM NB +sonixb 0c45:6005 Sweex Tas5110 +sonixb 0c45:6007 Sonix sn9c101 + Tas5110D +sonixb 0c45:6009 spcaCam@120 +sonixb 0c45:600d spcaCam@120 +sonixb 0c45:6011 MAX Webcam (Microdia - OV6650 - SN9C101G) +sonixb 0c45:6019 Generic Sonix OV7630 +sonixb 0c45:6024 Generic Sonix Tas5130c +sonixb 0c45:6025 Xcam Shanga +sonixb 0c45:6028 Sonix Btc Pc380 +sonixb 0c45:6029 spcaCam@150 +sonixb 0c45:602c Generic Sonix OV7630 +sonixb 0c45:602d LIC-200 LG +sonixb 0c45:602e Genius VideoCam Messenger +sonixj 0458:7025 Genius Eye 311Q +sonixj 045e:00f5 MicroSoft VX3000 +sonixj 045e:00f7 MicroSoft VX1000 +sonixj 0471:0327 Philips SPC 600 NC +sonixj 0471:0328 Philips SPC 700 NC +sonixj 0471:0330 Philips SPC 710NC +sonixj 0c45:6040 Speed NVC 350K +sonixj 0c45:607c Sonix sn9c102p Hv7131R +sonixj 0c45:60c0 Sangha Sn535 +sonixj 0c45:60ec SN9C105+MO4000 +sonixj 0c45:60fb Surfer NoName +sonixj 0c45:60fc LG-LIC300 +sonixj 0c45:612a Avant Camera +sonixj 0c45:612c Typhoon Rasy Cam 1.3MPix +sonixj 0c45:6130 Sonix Pccam +sonixj 0c45:6138 Sn9c120 Mo4000 +sonixj 0c45:613b Surfer SN-206 +sonixj 0c45:613c Sonix Pccam168 +spca500 040a:0300 Kodak EZ200 +spca500 041e:400a Creative PC-CAM 300 +spca500 046d:0890 Logitech QuickCam traveler +spca500 046d:0900 Logitech Inc. ClickSmart 310 +spca500 046d:0901 Logitech Inc. ClickSmart 510 +spca500 04a5:300c Benq DC1016 +spca500 04fc:7333 PalmPixDC85 +spca500 055f:c200 Mustek Gsmart 300 +spca500 055f:c220 Gsmart Mini +spca500 06bd:0404 Agfa CL20 +spca500 06be:0800 Optimedia +spca500 084d:0003 D-Link DSC-350 +spca500 08ca:0103 Aiptek PocketDV +spca500 2899:012c Toptro Industrial +spca500 8086:0630 Intel Pocket PC Camera +spca501 040a:0002 Kodak DVC-325 +spca501 0497:c001 Smile International +spca501 0506:00df 3Com HomeConnect Lite +spca501 0733:0401 Intel Create and Share +spca501 0733:0402 ViewQuest M318B +spca501 1776:501c Arowana 300K CMOS Camera +spca501 0000:0000 MystFromOri Unknow Camera +spca505 041e:401d Creative Webcam NX ULTRA +spca505 0733:0430 Intel PC Camera Pro +spca506 06e1:a190 ADS Instant VCD +spca506 0734:043b 3DeMon USB Capture aka +spca506 99fa:8988 Grandtec V.cap +spca506 99fa:8988 Grandtec V.cap +spca508 041e:4018 Creative Webcam Vista (PD1100) +spca508 0461:0815 Micro Innovation IC200 +spca508 0733:0110 ViewQuest VQ110 +spca508 0af9:0010 Hama USB Sightcam 100 +spca508 0af9:0011 Hama USB Sightcam 100 +spca508 8086:0110 Intel Easy PC Camera +spca561 041e:401a Creative Webcam Vista (PD1100) +spca561 041e:403b Creative Webcam Vista (VF0010) +spca561 0458:7004 Genius VideoCAM Express V2 +spca561 046d:0928 Logitech QC Express Etch2 +spca561 046d:0929 Labtec Webcam Elch2 +spca561 046d:092a Logitech QC for Notebook +spca561 046d:092b Labtec Webcam Plus +spca561 046d:092c Logitech QC chat Elch2 +spca561 046d:092d Logitech QC Elch2 +spca561 046d:092e Logitech QC Elch2 +spca561 046d:092f Logitech QC Elch2 +spca561 04fc:0561 Flexcam 100 +spca561 060b:a001 Maxell Compact Pc PM3 +spca561 10fd:7e50 FlyCam Usb 100 +spca561 abcd:cdee Petcam +stk014 05e1:0893 Syntek DV4000 +sunplus 041e:400b Creative PC-CAM 600 +sunplus 041e:4012 PC-Cam350 +sunplus 041e:4013 Creative Pccam750 +sunplus 0458:7006 Genius Dsc 1.3 Smart +sunplus 046d:0905 Logitech ClickSmart 820 +sunplus 046d:0960 Logitech ClickSmart 420 +sunplus 0471:0322 Philips DMVC1300K +sunplus 04a5:3003 Benq DC 1300 +sunplus 04a5:3008 Benq DC 1500 +sunplus 04a5:300a Benq DC3410 +sunplus 04f1:1001 JVC GC A50 +sunplus 04fc:500c Sunplus CA500C +sunplus 04fc:504a Aiptek Mini PenCam 1.3 +sunplus 04fc:504b Maxell MaxPocket LE 1.3 +sunplus 04fc:5330 Digitrex 2110 +sunplus 04fc:5360 Sunplus Generic +sunplus 04fc:ffff Pure DigitalDakota +sunplus 052b:1513 Megapix V4 +sunplus 0546:3155 Polaroid PDC3070 +sunplus 0546:3191 Polaroid Ion 80 +sunplus 0546:3273 Polaroid PDC2030 +sunplus 055f:c211 Kowa Bs888e Microcamera +sunplus 055f:c230 Mustek Digicam 330K +sunplus 055f:c232 Mustek MDC3500 +sunplus 055f:c360 Mustek DV4000 Mpeg4 +sunplus 055f:c420 Mustek gSmart Mini 2 +sunplus 055f:c430 Mustek Gsmart LCD 2 +sunplus 055f:c440 Mustek DV 3000 +sunplus 055f:c520 Mustek gSmart Mini 3 +sunplus 055f:c530 Mustek Gsmart LCD 3 +sunplus 055f:c540 Gsmart D30 +sunplus 055f:c630 Mustek MDC4000 +sunplus 055f:c650 Mustek MDC5500Z +sunplus 05da:1018 Digital Dream Enigma 1.3 +sunplus 06d6:0031 Trust 610 LCD PowerC@m Zoom +sunplus 0733:1311 Digital Dream Epsilon 1.3 +sunplus 0733:1314 Mercury 2.1MEG Deluxe Classic Cam +sunplus 0733:2211 Jenoptik jdc 21 LCD +sunplus 0733:2221 Mercury Digital Pro 3.1p +sunplus 0733:3261 Concord 3045 spca536a +sunplus 0733:3281 Cyberpix S550V +sunplus 08ca:0104 Aiptek PocketDVII 1.3 +sunplus 08ca:0106 Aiptek Pocket DV3100+ +sunplus 08ca:2008 Aiptek Mini PenCam 2 M +sunplus 08ca:2010 Aiptek PocketCam 3M +sunplus 08ca:2016 Aiptek PocketCam 2 Mega +sunplus 08ca:2018 Aiptek Pencam SD 2M +sunplus 08ca:2020 Aiptek Slim 3000F +sunplus 08ca:2022 Aiptek Slim 3200 +sunplus 08ca:2024 Aiptek DV3500 Mpeg4 +sunplus 08ca:2028 Aiptek PocketCam4M +sunplus 08ca:2040 Aiptek PocketDV4100M +sunplus 08ca:2042 Aiptek PocketDV5100 +sunplus 08ca:2060 Aiptek PocketDV5300 +sunplus 0d64:0303 Sunplus FashionCam DXG +tv8532 046d:0920 QC Express +tv8532 046d:0921 Labtec Webcam +tv8532 0545:808b Veo Stingray +tv8532 0545:8333 Veo Stingray +tv8532 0923:010f ICM532 cams +vc032x 046d:0892 Logitech Orbicam +vc032x 046d:0896 Logitech Orbicam +vc032x 0ac8:0321 Vimicro generic vc0321 +vc032x 0ac8:0323 Vimicro Vc0323 +vc032x 0ac8:0328 A4Tech PK-130MG +vc032x 0ac8:c001 Sony embedded vimicro +vc032x 0ac8:c002 Sony embedded vimicro +vc032x 17ef:4802 Lenovo Vc0323+MI1310_SOC +zc3xx 041e:041e Creative WebCam Live! +zc3xx 041e:4017 Creative Webcam Mobile PD1090 +zc3xx 041e:401c Creative NX +zc3xx 041e:401e Creative Nx Pro +zc3xx 041e:401f Creative Webcam Notebook PD1171 +zc3xx 041e:4029 Creative WebCam Vista Pro +zc3xx 041e:4034 Creative Instant P0620 +zc3xx 041e:4035 Creative Instant P0620D +zc3xx 041e:4036 Creative Live ! +zc3xx 041e:403a Creative Nx Pro 2 +zc3xx 041e:4051 Creative Live!Cam Notebook Pro (VF0250) +zc3xx 041e:4053 Creative Live!Cam Video IM +zc3xx 0458:7007 Genius VideoCam V2 +zc3xx 0458:700c Genius VideoCam V3 +zc3xx 0458:700f Genius VideoCam Web V2 +zc3xx 0461:0a00 MicroInnovation WebCam320 +zc3xx 046d:08a0 Logitech QC IM +zc3xx 046d:08a1 Logitech QC IM 0x08A1 +sound +zc3xx 046d:08a2 Labtec Webcam Pro +zc3xx 046d:08a3 Logitech QC Chat +zc3xx 046d:08a6 Logitech QCim +zc3xx 046d:08a7 Logitech QuickCam Image +zc3xx 046d:08a9 Logitech Notebook Deluxe +zc3xx 046d:08aa Labtec Webcam Notebook +zc3xx 046d:08ac Logitech QuickCam Cool +zc3xx 046d:08ad Logitech QCCommunicate STX +zc3xx 046d:08ae Logitech QuickCam for Notebooks +zc3xx 046d:08af Logitech QuickCam Cool +zc3xx 046d:08b9 Logitech QC IM ??? +zc3xx 046d:08d7 Logitech QCam STX +zc3xx 046d:08d9 Logitech QuickCam IM/Connect +zc3xx 046d:08d8 Logitech Notebook Deluxe +zc3xx 046d:08da Logitech QuickCam Messenger +zc3xx 046d:08dd Logitech QuickCam for Notebooks +zc3xx 0471:0325 Philips SPC 200 NC +zc3xx 0471:0326 Philips SPC 300 NC +zc3xx 0471:032d Philips spc210nc +zc3xx 0471:032e Philips spc315nc +zc3xx 055f:c005 Mustek Wcam300A +zc3xx 055f:d003 Mustek WCam300A +zc3xx 055f:d004 Mustek WCam300 AN +zc3xx 0698:2003 CTX M730V built in +zc3xx 0ac8:0302 Z-star Vimicro zc0302 +zc3xx 0ac8:301b Z-Star zc301b +zc3xx 0ac8:303b Vimicro 0x303b +zc3xx 0ac8:305b Z-star Vimicro zc0305b +zc3xx 0ac8:307b Ldlc VC302+Ov7620 +zc3xx 10fd:0128 Typhoon Webshot II USB 300k 0x0128 +zc3xx 10fd:8050 Typhoon Webshot II USB 300k diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 8fca90b75e7..a6fbee4d41b 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -801,6 +801,8 @@ config USB_VIDEO_CLASS For more information see: <http://linux-uvc.berlios.de/> +source "drivers/media/video/gspca/Kconfig" + source "drivers/media/video/pvrusb2/Kconfig" source "drivers/media/video/em28xx/Kconfig" diff --git a/drivers/media/video/gspca/Kconfig b/drivers/media/video/gspca/Kconfig new file mode 100644 index 00000000000..a04e413e125 --- /dev/null +++ b/drivers/media/video/gspca/Kconfig @@ -0,0 +1,13 @@ +config USB_GSPCA + tristate "USB GSPCA driver" + depends on VIDEO_V4L2 + ---help--- + Say Y here if you want support for various USB cameras. + + See <file:Documentation/video4linux/gspca.txt> for more info. + + This driver uses the Video For Linux API. You must say Y or M to + "Video For Linux" to use this driver. + + To compile this driver as modules, choose M here: the + modules will be called gspca_xxxx. diff --git a/drivers/media/video/gspca/Makefile b/drivers/media/video/gspca/Makefile new file mode 100644 index 00000000000..885d4454d8e --- /dev/null +++ b/drivers/media/video/gspca/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_GSPCA) += gspca_main.o gspca_stk014.o + +gspca_main-objs := gspca.o +gspca_stk014-objs := stk014.o diff --git a/drivers/media/video/gspca/gspca.c b/drivers/media/video/gspca/gspca.c new file mode 100644 index 00000000000..4fe082ff707 --- /dev/null +++ b/drivers/media/video/gspca/gspca.c @@ -0,0 +1,1750 @@ +/* + * Main USB camera driver + * + * V4L2 by Jean-Francois Moine <http://moinejf.free.fr> + * + * 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 + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define MODULE_NAME "gspca" + +#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 <asm/io.h> +#include <asm/page.h> +#include <asm/uaccess.h> +#include <linux/jiffies.h> + +#include "gspca.h" + +MODULE_AUTHOR("Jean-Francois Moine <http://moinejf.free.fr>"); +MODULE_DESCRIPTION("GSPCA USB Camera Driver"); +MODULE_LICENSE("GPL"); + +#define DRIVER_VERSION_NUMBER KERNEL_VERSION(0, 0, 26) +static const char version[] = "0.0.26"; + +static int video_nr = -1; + +static int comp_fac = 30; /* Buffer size ratio when compressed in % */ + +#ifdef GSPCA_DEBUG +int gspca_debug = D_ERR | D_PROBE; +EXPORT_SYMBOL(gspca_debug); + +static void PDEBUG_MODE(char *txt, __u32 pixfmt, int w, int h) +{ + if ((pixfmt >> 24) >= '0' && (pixfmt >> 24) <= 'z') { + PDEBUG(D_CONF|D_STREAM, "%s %c%c%c%c %dx%d", + txt, + pixfmt & 0xff, + (pixfmt >> 8) & 0xff, + (pixfmt >> 16) & 0xff, + pixfmt >> 24, + w, h); + } else { + PDEBUG(D_CONF|D_STREAM, "%s 0x%08x %dx%d", + txt, + pixfmt, + w, h); + } +} +#else +#define PDEBUG_MODE(txt, pixfmt, w, h) +#endif + +/* + * VMA operations. + */ +static void gspca_vm_open(struct vm_area_struct *vma) +{ + struct gspca_frame *frame = vma->vm_private_data; + + frame->vma_use_count++; + frame->v4l2_buf.flags |= V4L2_BUF_FLAG_MAPPED; +} + +static void gspca_vm_close(struct vm_area_struct *vma) +{ + struct gspca_frame *frame = vma->vm_private_data; + + if (--frame->vma_use_count <= 0) + frame->v4l2_buf.flags &= ~V4L2_BUF_FLAG_MAPPED; +} + +static struct vm_operations_struct gspca_vm_ops = { + .open = gspca_vm_open, + .close = gspca_vm_close, +}; + +/* + * ISOC message interrupt from the USB device + * + * Analyse each packet and call the subdriver for doing the copy + * to the frame buffer. + */ +static void isoc_irq(struct urb *urb) +{ + struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context; + struct gspca_frame *frame; + unsigned char *data; /* address of data in the iso message */ + int i, j, len, st; + cam_pkt_op pkt_scan; + + PDEBUG(D_PACK, "isoc irq"); + if (!gspca_dev->streaming) + return; + pkt_scan = gspca_dev->sd_desc->pkt_scan; + for (i = 0; i < urb->number_of_packets; i++) { + + /* check the availability of the frame buffer */ + j = gspca_dev->fr_i; + j = gspca_dev->fr_queue[j]; + frame = &gspca_dev->frame[j]; + if ((frame->v4l2_buf.flags + & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE)) + != V4L2_BUF_FLAG_QUEUED) { + gspca_dev->last_packet_type = DISCARD_PACKET; + break; + } + + /* check the packet status and length */ + len = urb->iso_frame_desc[i].actual_length; + st = urb->iso_frame_desc[i].status; + if (st) { + PDEBUG(D_ERR, "ISOC data error: [%d] len=%d, status=%d", + i, len, st); + gspca_dev->last_packet_type = DISCARD_PACKET; + continue; + } + if (len == 0) + continue; + + /* let the packet be analyzed by the subdriver */ + PDEBUG(D_PACK, "packet [%d] o:%d l:%d", + i, urb->iso_frame_desc[i].offset, len); + data = (unsigned char *) urb->transfer_buffer + + urb->iso_frame_desc[i].offset; + pkt_scan(gspca_dev, frame, data, len); + } + + /* resubmit the URB */ + urb->status = 0; + st = usb_submit_urb(urb, GFP_ATOMIC); + if (st < 0) + PDEBUG(D_ERR|D_PACK, "usb_submit_urb() ret %d", st); +} + +/* + * add data to the current frame + * + * This function is called by the subdrivers at interrupt level. + * 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. + * On LAST_PACKET, a new frame is returned. + */ +struct gspca_frame *gspca_frame_add(struct gspca_dev *gspca_dev, + int packet_type, + struct gspca_frame *frame, + unsigned char *data, + int len) +{ + int i, j; + + PDEBUG(D_PACK, "add t:%d l:%d %02x %02x %02x %02x...", + packet_type, len, data[0], data[1], data[2], data[3]); + + /* when start of a new frame, if the current frame buffer + * is not queued, discard the whole frame */ + if (packet_type == FIRST_PACKET) { + if ((frame->v4l2_buf.flags + & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE)) + != V4L2_BUF_FLAG_QUEUED) { + gspca_dev->last_packet_type = DISCARD_PACKET; + return frame; + } + frame->data_end = frame->data; + jiffies_to_timeval(get_jiffies_64(), + &frame->v4l2_buf.timestamp); + frame->v4l2_buf.sequence = ++gspca_dev->sequence; + } else if (gspca_dev->last_packet_type == DISCARD_PACKET) + return frame; + + /* append the packet in the frame buffer */ + if (len > 0) { + if (frame->data_end - frame->data + len + > frame->v4l2_buf.length) { + PDEBUG(D_ERR|D_PACK, "frame overflow %d > %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 + copy_to_user(frame->data_end, data, len); + frame->data_end += len; + } + } + gspca_dev->last_packet_type = packet_type; + + /* if last packet, wake the application and advance in the queue */ + if (packet_type == LAST_PACKET) { + frame->v4l2_buf.bytesused = frame->data_end - frame->data; + frame->v4l2_buf.flags &= ~V4L2_BUF_FLAG_QUEUED; + frame->v4l2_buf.flags |= V4L2_BUF_FLAG_DONE; + atomic_inc(&gspca_dev->nevent); + wake_up_interruptible(&gspca_dev->wq); /* event = new frame */ + i = gspca_dev->fr_i; + i = (i + 1) % gspca_dev->nframes; + PDEBUG(D_FRAM, "frame complete len:%d q:%d i:%d o:%d", + frame->v4l2_buf.bytesused, + gspca_dev->fr_q, + i, + gspca_dev->fr_o); + j = gspca_dev->fr_queue[i]; + frame = &gspca_dev->frame[j]; + gspca_dev->fr_i = i; + } + return frame; +} +EXPORT_SYMBOL(gspca_frame_add); + +static int gspca_is_compressed(__u32 format) +{ + switch (format) { + case V4L2_PIX_FMT_MJPEG: + case V4L2_PIX_FMT_JPEG: + return 1; + } + return 0; +} + +static void *rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + mem = vmalloc_32(size); + if (mem != 0) { + memset(mem, 0, size); + adr = (unsigned long) mem; + while ((long) size > 0) { + SetPageReserved(vmalloc_to_page((void *) adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + } + return mem; +} + +static void rvfree(void *mem, unsigned long size) +{ + unsigned long adr; + + if (!mem) + return; + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *) adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + vfree(mem); +} + +static int frame_alloc(struct gspca_dev *gspca_dev, + unsigned int count, + unsigned int frsz, + enum v4l2_memory memory) +{ + int i, ret = 0; + + PDEBUG(D_STREAM, "frame alloc frsz: %d", frsz); + if (gspca_dev->nframes != 0) { + PDEBUG(D_ERR|D_STREAM, "alloc frame already done"); + return -EBUSY; + } + if (count > GSPCA_MAX_FRAMES) + count = GSPCA_MAX_FRAMES; + /* if compressed, reduce the buffer size */ + if (gspca_is_compressed(gspca_dev->pixfmt)) + frsz = (frsz * comp_fac) / 100; + frsz = PAGE_ALIGN(frsz); + PDEBUG(D_STREAM, "new fr_sz: %d", frsz); + gspca_dev->frsz = frsz; + if (memory == V4L2_MEMORY_MMAP) { + 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++) { + gspca_dev->frame[i].v4l2_buf.index = i; + gspca_dev->frame[i].v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + gspca_dev->frame[i].v4l2_buf.flags = 0; + gspca_dev->frame[i].v4l2_buf.field = V4L2_FIELD_NONE; + gspca_dev->frame[i].v4l2_buf.length = frsz; + gspca_dev->frame[i].v4l2_buf.memory = memory; + if (memory == V4L2_MEMORY_MMAP) { + gspca_dev->frame[i].data + = gspca_dev->frame[i].data_end + = gspca_dev->frbuf + i * frsz; + gspca_dev->frame[i].v4l2_buf.m.offset = i * frsz; + } + gspca_dev->frame[i].v4l2_buf.flags = 0; /* buf in app space */ + } + 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); + return ret; +} + +static void frame_free(struct gspca_dev *gspca_dev) +{ + int i; + + PDEBUG(D_STREAM, "frame free"); + if (gspca_dev->frbuf != 0) { + rvfree(gspca_dev->frbuf, + gspca_dev->nframes * gspca_dev->frsz); + gspca_dev->frbuf = NULL; + for (i = 0; i < gspca_dev->nframes; i++) + gspca_dev->frame[i].data = NULL; + } + gspca_dev->nframes = 0; +} + +static int gspca_kill_transfer(struct gspca_dev *gspca_dev) +{ + struct urb *urb; + unsigned int i; + + PDEBUG(D_STREAM, "kill transfer"); + gspca_dev->streaming = 0; + for (i = 0; i < NURBS; ++i) { + urb = gspca_dev->pktbuf[i].urb; + if (urb == NULL) + continue; + + gspca_dev->pktbuf[i].urb = NULL; + usb_kill_urb(urb); + + /* urb->transfer_buffer_length is not touched by USB core, + * so we can use it here as the buffer length */ + if (gspca_dev->pktbuf[i].data) { + usb_buffer_free(gspca_dev->dev, + urb->transfer_buffer_length, + gspca_dev->pktbuf[i].data, + urb->transfer_dma); + gspca_dev->pktbuf[i].data = NULL; + } + usb_free_urb(urb); + } + return 0; +} + +/* + * search an input isochronous endpoint in an alternate setting + */ +static struct usb_host_endpoint *alt_isoc(struct usb_host_interface *alt, + __u8 epaddr) +{ + struct usb_host_endpoint *ep; + int i, attr; + + epaddr |= USB_DIR_IN; + for (i = 0; i < alt->desc.bNumEndpoints; i++) { + ep = &alt->endpoint[i]; + if (ep->desc.bEndpointAddress == epaddr) { + attr = ep->desc.bmAttributes + & USB_ENDPOINT_XFERTYPE_MASK; + if (attr == USB_ENDPOINT_XFER_ISOC) + return ep; + break; + } + } + return NULL; +} + +/* + * search an input isochronous endpoint + * + * The endpoint is defined by the subdriver. + * Use only the first isoc (some Zoran - 0x0572:0x0001 - have two such ep). + * This routine may be called many times when the bandwidth is too small + * (the bandwidth is checked on urb submit). + */ +struct usb_host_endpoint *get_isoc_ep(struct gspca_dev *gspca_dev) +{ + struct usb_interface *intf; + struct usb_host_endpoint *ep; + int i, ret; + + intf = usb_ifnum_to_if(gspca_dev->dev, gspca_dev->iface); + i = gspca_dev->alt; /* previous alt setting */ + while (--i > 0) { /* alt 0 is unusable */ + ep = alt_isoc(&intf->altsetting[i], gspca_dev->cam.epaddr); + if (ep) + break; + } + if (i <= 0) { + err("no ISOC endpoint found"); + return NULL; + } + PDEBUG(D_STREAM, "use ISOC alt %d ep 0x%02x", + i, ep->desc.bEndpointAddress); + ret = usb_set_interface(gspca_dev->dev, gspca_dev->iface, i); + if (ret < 0) { + err("set interface err %d", ret); + return NULL; + } + gspca_dev->alt = i; + return ep; +} + +/* + * create the isochronous URBs + */ +static int create_urbs(struct gspca_dev *gspca_dev, + struct usb_host_endpoint *ep) +{ + struct urb *urb; + int n, i, psize, npkt, bsize; + + /* calculate the packet size and the number of packets */ + /* the URB buffer size must be a power of 2 */ + psize = le16_to_cpu(ep->desc.wMaxPacketSize); + /* See paragraph 5.9 / table 5-11 of the usb 2.0 spec. */ + psize = (psize & 0x07ff) * (1 + ((psize >> 11) & 3)); + npkt = ISO_MAX_SIZE / psize; + if (npkt > ISO_MAX_PKT) + npkt = ISO_MAX_PKT; + bsize = psize * npkt; + for (n = ISO_MAX_SIZE; n > 0; n >>= 1) { + if (n & bsize) /* !! assume ISO_MAX_SIZE is a power of 2 */ + break; + } + if (n != 0) { + npkt = n / psize; + bsize = psize * npkt; + } + PDEBUG(D_STREAM, + "isoc %d pkts size %d (bsize:%d)", npkt, psize, bsize); + for (n = 0; n < NURBS; n++) { + urb = usb_alloc_urb(npkt, GFP_KERNEL); + if (!urb) { + err("usb_alloc_urb failed"); + return -ENOMEM; + } + gspca_dev->pktbuf[n].data = usb_buffer_alloc(gspca_dev->dev, + bsize, + GFP_KERNEL, + &urb->transfer_dma); + + if (gspca_dev->pktbuf[n].data == NULL) { + usb_free_urb(urb); + gspca_kill_transfer(gspca_dev); + err("usb_buffer_urb failed"); + return -ENOMEM; + } + gspca_dev->pktbuf[n].urb = urb; + urb->dev = gspca_dev->dev; + urb->context = gspca_dev; + urb->pipe = usb_rcvisocpipe(gspca_dev->dev, + ep->desc.bEndpointAddress); + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->interval = ep->desc.bInterval; + urb->transfer_buffer = gspca_dev->pktbuf[n].data; + urb->complete = isoc_irq; + urb->number_of_packets = npkt; + urb->transfer_buffer_length = bsize; + for (i = 0; i < npkt; i++) { + urb->iso_frame_desc[i].length = psize; + urb->iso_frame_desc[i].offset = psize * i; + } + } + return 0; +} + +/* + * start the USB transfer + */ +static int gspca_init_transfer(struct gspca_dev *gspca_dev) +{ + struct usb_interface *intf; + struct usb_host_endpoint *ep; + int n, ret; + + ret = mutex_lock_interruptible(&gspca_dev->usb_lock); + if (ret < 0) + return ret; + + /* set the max alternate setting and loop until urb submit succeeds */ + intf = usb_ifnum_to_if(gspca_dev->dev, gspca_dev->iface); + gspca_dev->alt = intf->num_altsetting; + for (;;) { + PDEBUG(D_STREAM, "init transfer nbalt %d", gspca_dev->alt); + ep = get_isoc_ep(gspca_dev); + if (ep == NULL) { + ret = -EIO; + goto out; + } + ret = create_urbs(gspca_dev, ep); + if (ret < 0) + goto out; + + /* start the cam */ + gspca_dev->sd_desc->start(gspca_dev); + gspca_dev->streaming = 1; + atomic_set(&gspca_dev->nevent, 0); + + /* submit the URBs */ + for (n = 0; n < NURBS; n++) { + ret = usb_submit_urb(gspca_dev->pktbuf[n].urb, + GFP_KERNEL); + if (ret < 0) { + PDEBUG(D_ERR|D_STREAM, + "usb_submit_urb [%d] err %d", n, ret); + gspca_kill_transfer(gspca_dev); + if (ret == -ENOSPC) + break; /* try the previous alt */ + goto out; + } + } + if (ret >= 0) + break; + } +out: + mutex_unlock(&gspca_dev->usb_lock); + return ret; +} + +static int gspca_set_alt0(struct gspca_dev *gspca_dev) +{ + int ret; + + ret = usb_set_interface(gspca_dev->dev, gspca_dev->iface, 0); + if (ret < 0) + PDEBUG(D_ERR|D_STREAM, "set interface 0 err %d", ret); + return ret; +} + +static void gspca_stream_off(struct gspca_dev *gspca_dev) +{ + mutex_lock_interruptible(&gspca_dev->usb_lock); + gspca_dev->streaming = 0; + if (gspca_dev->present) { + gspca_dev->sd_desc->stopN(gspca_dev); + gspca_kill_transfer(gspca_dev); + gspca_set_alt0(gspca_dev); + gspca_dev->sd_desc->stop0(gspca_dev); + PDEBUG(D_STREAM, "stream off OK"); + } else { + gspca_kill_transfer(gspca_dev); + atomic_inc(&gspca_dev->nevent); + wake_up_interruptible(&gspca_dev->wq); + PDEBUG(D_ERR|D_STREAM, "stream off no device ??"); + } + mutex_unlock(&gspca_dev->usb_lock); +} + +static int 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->width = gspca_dev->cam.cam_mode[i].width; + gspca_dev->height = gspca_dev->cam.cam_mode[i].height; + gspca_dev->pixfmt = gspca_dev->cam.cam_mode[i].pixfmt; + return 0; +} + +static int wxh_to_mode(struct gspca_dev *gspca_dev, + int width, int height) +{ + int i; + + for (i = gspca_dev->cam.nmodes - 1; --i >= 0; ) { + if (width > gspca_dev->cam.cam_mode[i].width) + break; + } + i++; + while (i < gspca_dev->cam.nmodes - 1 + && width == gspca_dev->cam.cam_mode[i + 1].width + && height < gspca_dev->cam.cam_mode[i + 1].height) + i++; + return i; +} + +static __u32 get_v4l2_depth(__u32 pixfmt) +{ + switch (pixfmt) { + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_RGB32: + return 32; + case V4L2_PIX_FMT_RGB24: + case V4L2_PIX_FMT_BGR24: + return 24; + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_YUYV: /* packed 4.2.2 */ + case V4L2_PIX_FMT_YYUV: + return 16; + case V4L2_PIX_FMT_YUV420: /* planar 4.2.0 */ + return 12; + case V4L2_PIX_FMT_MJPEG: + case V4L2_PIX_FMT_JPEG: + case V4L2_PIX_FMT_SBGGR8: /* Bayer */ + return 8; + } + PDEBUG(D_ERR|D_CONF, "Unknown pixel format %c%c%c%c", + pixfmt & 0xff, + (pixfmt >> 8) & 0xff, + (pixfmt >> 16) & 0xff, + pixfmt >> 24); + return -EINVAL; +} + +/* + * 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].pixfmt == pixfmt) + return modeD; + } + if (++modeU < gspca_dev->cam.nmodes) { + if (gspca_dev->cam.cam_mode[modeU].pixfmt == pixfmt) + return modeU; + } + } + return -EINVAL; +} + +static int vidioc_enum_fmt_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *fmtdesc) +{ + struct gspca_dev *gspca_dev = priv; + int i, j, index; + __u32 fmt_tb[8]; + + PDEBUG(D_CONF, "enum fmt cap"); + + /* give an index to each format */ + index = 0; + j = 0; + for (i = gspca_dev->cam.nmodes; --i >= 0; ) { + fmt_tb[index] = gspca_dev->cam.cam_mode[i].pixfmt; + 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 >= sizeof fmt_tb / sizeof fmt_tb[0]) + return -EINVAL; + } + } + if (i < 0) + return -EINVAL; /* no more format */ + + fmtdesc->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmtdesc->pixelformat = fmt_tb[index]; + if (gspca_is_compressed(fmt_tb[index])) + fmtdesc->flags = V4L2_FMT_FLAG_COMPRESSED; + fmtdesc->description[0] = fmtdesc->pixelformat & 0xff; + fmtdesc->description[1] = (fmtdesc->pixelformat >> 8) & 0xff; + fmtdesc->description[2] = (fmtdesc->pixelformat >> 16) & 0xff; + fmtdesc->description[3] = fmtdesc->pixelformat >> 24; + fmtdesc->description[4] = '\0'; + return 0; +} + +static int gspca_get_buff_size(struct gspca_dev *gspca_dev) +{ + unsigned int size; + + size = gspca_dev->width * gspca_dev->height + * get_v4l2_depth(gspca_dev->pixfmt) / 8; + if (!size) + return -ENOMEM; + return size; +} + +static int vidioc_g_fmt_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct gspca_dev *gspca_dev = priv; + + fmt->fmt.pix.width = gspca_dev->width; + fmt->fmt.pix.height = gspca_dev->height; + fmt->fmt.pix.pixelformat = gspca_dev->pixfmt; +#ifdef GSPCA_DEBUG + if (gspca_debug & D_CONF) { + PDEBUG_MODE("get fmt cap", + fmt->fmt.pix.pixelformat, + fmt->fmt.pix.width, + fmt->fmt.pix.height); + } +#endif + fmt->fmt.pix.field = V4L2_FIELD_NONE; + fmt->fmt.pix.bytesperline = get_v4l2_depth(fmt->fmt.pix.pixelformat) + * fmt->fmt.pix.width / 8; + fmt->fmt.pix.sizeimage = fmt->fmt.pix.bytesperline + * fmt->fmt.pix.height; +/* (should be in the subdriver) */ + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + fmt->fmt.pix.priv = 0; + return 0; +} + +static int try_fmt_cap(struct file *file, + void *priv, + struct v4l2_format *fmt) +{ + struct gspca_dev *gspca_dev = priv; + int w, h, mode, mode2, frsz; + + w = (int) fmt->fmt.pix.width; + h = (int) fmt->fmt.pix.height; +#ifdef GSPCA_DEBUG + if (gspca_debug & D_CONF) + PDEBUG_MODE("try fmt cap", fmt->fmt.pix.pixelformat, w, h); +#endif + /* search the closest mode for width and height */ + mode = wxh_to_mode(gspca_dev, w, h); + + /* OK if right palette */ + if (gspca_dev->cam.cam_mode[mode].pixfmt != 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; + else { + + /* no chance, return this mode */ + fmt->fmt.pix.pixelformat + = gspca_dev->cam.cam_mode[mode].pixfmt; +#ifdef GSPCA_DEBUG + if (gspca_debug & D_CONF) { + PDEBUG_MODE("new format", + fmt->fmt.pix.pixelformat, + gspca_dev->cam.cam_mode[mode].width, + gspca_dev->cam.cam_mode[mode].height); + } +#endif + } + } + fmt->fmt.pix.width = gspca_dev->cam.cam_mode[mode].width; + fmt->fmt.pix.height = gspca_dev->cam.cam_mode[mode].height; + fmt->fmt.pix.bytesperline = get_v4l2_depth(fmt->fmt.pix.pixelformat) + * fmt->fmt.pix.width / 8; + frsz = fmt->fmt.pix.bytesperline * fmt->fmt.pix.height; + if (gspca_is_compressed(fmt->fmt.pix.pixelformat)) + frsz = (frsz * comp_fac) / 100; + fmt->fmt.pix.sizeimage = frsz; + return mode; /* used when s_fmt */ +} + +static int vidioc_try_fmt_cap(struct file *file, + void *priv, + struct v4l2_format *fmt) +{ + int ret; + +/* mutex_lock_interruptible(&gspca_dev->queue_lock); */ + ret = try_fmt_cap(file, priv, fmt); +/* mutex_unlock(&gspc |