r5u870

Ricoh R5U870 Linux Driver
git clone https://logand.com/git/r5u870.git/
Log | Files | Refs | README | LICENSE

commit 634557bf4dab7bf39eabc901184f3b8572bfb3d7
parent 81859663a98909f9217482f5205d2d7ab2bbfa90
Author: alex <alex@022568fa-442e-4ef8-a3e8-54dcafdb011a>
Date:   Wed, 16 Jan 2008 10:28:31 +0000

* Reformat changelog style, and added missing entries.
* Merge Sam Revitch's patch to video4linux-list with the code in trunk.
* Bump to version 0.11.0.
* Small README changes.
* Rename r5u870_md.c to r5u870.c


git-svn-id: http://svn.mediati.org/svn/r5u870/trunk@33 022568fa-442e-4ef8-a3e8-54dcafdb011a

Diffstat:
MChangeLog | 207+++++++++++++++++++++++++++++++++++++++++++------------------------------------
MKbuild | 3+--
MREADME | 33++++++++++++++++++++++++---------
Ar5u870.c | 2890+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dr5u870_md.c | 3061-------------------------------------------------------------------------------
Ausbcam/Makefile | 6++++++
Ausbcam/usbcam.c | 3477+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ausbcam/usbcam.h | 757+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ausbcam/usbcam_buf.c | 685+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ausbcam/usbcam_dev.c | 1109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ausbcam/usbcam_fops.c | 852+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ausbcam/usbcam_priv.h | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ausbcam/usbcam_skel.c | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ausbcam/usbcam_util.c | 1008+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14 files changed, 11243 insertions(+), 3166 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,94 +1,113 @@ -version 0.10.2, 2008/1/13 - Small minor bug fixes that were missed in the previous release. - -Version 0.10.1, 2008/1/2 - Add support for the 183a UVC camera. - - Complete V4L1 compatability; unbreaks functionatity with such - applications like GStreamer (and those that depend on the framework, - such as Cheese). - - Fix compiling against 2.6.24 kernels - changes to videobuf API. - -Version 0.10.0, 2007/4/7 - Add support for the 1810, 1835, and 1836 UVC cameras. - Add support for the 1833 non-UVC camera. - - Add basic support for UVC commands and UVC personalities of - various Ricoh webcams. - - Add DMI detection of HP Pavilion dv1000 systems. Most HP Pavilion - machines with the 05ca:1870 device have a 1.3MP camera. The - dv1000 machines with a device reporting that ID instead have a VGA - camera that requires different microcode. - - Fix solid blue frames on initial capture with VAIO UX webcams. - - Use a cleaner and more concise representation for vendor control - lists in r5u870_md. - - Add experimental auto-suspend support to usbcam and r5u870, but - leave it disabled. - - Update the usbcam work queue implementation, add some APIs. - - -Version 0.9.1, 2007/3/21 - Set all of the controls on device initialization, prior to starting - the first capture session. This was causing the first capture - session with certain devices to produce a solid blue image. - - Resolve build problems with vanilla 2.6.19 kernels. Thanks to - Naresh Kumar for pointing this out. - - Use the driver_info field of the usb_device_id structure to find - the model structure. - - Revise the usbcam device referencing model, along with some other - minor usbcam cleanups. - - -Version 0.9.0, 2007/3/6 - Change project name to r5u870, as the device called 'ry5u870' is - something Ricoh sells that doesn't seem to be a direct basis for - any of the supported OEM webcams. - - Change module name from ry5u870.ko to r5u870.ko, and changed the - install path to "extra" rather than "kernel/drivers/media/video". - - Add microcode, device IDs, and control descriptor tables for - HP Pavilion Webcam, Sony VGP-VCC3, and Sony VGP-VCC2 (VAIO AR). - - Introduce the webcam model structure, and hang lists of supported - resolutions, pixel formats, and controls off of it. - - Use the request_firmware() interface to load microcode, rather - than embedding the microcode in the driver binary. - - Clean up the debug, warning, and error printing macros. - - Overhaul the usbcam_curframe_* interfaces. Add a solid blue test - pattern generator for when a frame is dropped by the minidriver. - - Add a workaround for a v4l1_compat bug preventing VIDIOCSPICT - from working, present in kernels prior to 2.6.20. - - Add an application bug workaround for Ekiga/ptlib: zero the flags - field of the argument to VIDIOC_QBUF. - - Add an application bug workaround for motion: allocate fixed sized - buffers in VIDIOCGMBUF. - - Fixed issues with the compat_ioctl method for running 32-bit apps - on 64-bit platforms. - - Apparently Linus prefers struct X to X_t. Mass-update to match this - style for better hopes of getting it merged some day. - - -Version 0.8.1, 2007/2/15 - Fix trivial build problem with kernel 2.6.18. - - -Version 0.8.0, 2007/2/14 - Initial public release. +2008-01-16 Alexander Hixon <hixon.alexander@mediati.org> + * ChangeLog: Reformat changelog style, and added missing entries. + * KBuild, usbcam/usbcam_priv.h, usbcam/usbcam.h, usbcam/usbcam_fops.c, + usbcam/usbcam_buf.c, usbcam/usbcam_util.c, usbcam/usbcam_del.c, + usbcam/usbcam_skel.c, usbcam/usbcam.c, usbcam/Makefile: + + Seperate usbcam library out seperately. Part one + of two of the code merge from Sam Revitch's release of the r5u870 + driver on video4linux-list@redhat.com. + * r5u870_md.c, r5u870.c, Kbuild: Merged code changes for API change + within usbcam. Among other things, remove inflamatory things about HP + engineers, remove some debugging cruft, implement a control message + helper function, and other misc fixes. Also fixes a bug against Sam's + original release where memory might not get freed correctly. + * README, r5u870.c: Bump version to 0.11.0. + * README: Modified 'Changes from original version' section name and + contents, and added some more acknowledgements. + +2008-01-16 Alexander Hixon <hixon.alexander@mediati.org> + * recode-fw.scm: Added this file to easily extract firmware from Windows + drivers when needed. Thanks to Utz-Uwe Haus for this. + +2008-01-13 Alexander Hixon <hixon.alexander@mediati.org> + * README, Changelog, r5u870_md.c, usbcam.c, usbcam.h: Bump to 0.10.2. + * usbcam.c: Uncomment V4L2 control query ioctl. + +2008-01-12 Alexander Hixon <hixon.alexander@mediati.org> + * usbcam.c: Fix dereferencing of NULL pointers by checking to see if they + have a value before trying to get a memory address. + +2008-01-11 Alexander Hixon <hixon.alexander@mediati.org> + * r5u870_md.c: Change NULL to { } to hush NULL without definition warning. + * usbcam.c: Check results against videobuf operations for return values + less than 0, rather than if it != 0. + +2008-01-08 Alexander Hixon <hixon.alexander@mediati.org> + * r5u870_md.c: Add NULL to end of definitions list to remove warnings on + compile. + +2008-01-02 Alexander Hixon <hixon.alexander@mediati.org> + * r5u870_md.c, r5u870_183a.fw, README: Add support for the 183a UVC camera. + * r5u870_md.c: Complete V4L1 compatability; unbreaks functionatity with + such applications like GStreamer (and those that depend on the framework, + such as Cheese). + * r5u870_md.c, usbcam.h, usbcam.c: Fix compiling against 2.6.24 kernels - + changes to videobuf API. + * README: Initial release against original driver - 0.10.1. + +2007-04-07 Sam Revitch <samr7@cs.washington.edu> + * /dev/null: Add support for the 1810, 1835, and 1836 UVC cameras. + * /dev/null: Add support for the 1833 non-UVC camera. + * /dev/null: Add basic support for UVC commands and UVC personalities of + various Ricoh webcams. + * /dev/null: Add DMI detection of HP Pavilion dv1000 systems. Most HP + Pavilion machines with the 05ca:1870 device have a 1.3MP camera. The + dv1000 machines with a device reporting that ID instead have a VGA + camera that requires different microcode. + * /dev/null: Fix solid blue frames on initial capture with VAIO UX webcams. + * /dev/null: Use a cleaner and more concise representation for vendor + control lists in r5u870_md. + * /dev/null: Add experimental auto-suspend support to usbcam and r5u870, + but leave it disabled. + * /dev/null: Update the usbcam work queue implementation, add some APIs. + * README, r5u870_md.c, usbcam.c, usbcam.h: 0.10.0 release. + + +2007-03-21 Sam Revitch <samr7@cs.washington.edu> + * /dev/null: Set all of the controls on device initialization, prior to + starting the first capture session. This was causing the first capture + session with certain devices to produce a solid blue image. + * /dev/null: Resolve build problems with vanilla 2.6.19 kernels. Thanks + to Naresh Kumar for pointing this out. + * /dev/null: Use the driver_info field of the usb_device_id structure to + find the model structure. + * /dev/null: Revise the usbcam device referencing model, along with some + other minor usbcam cleanups. + * README, r5u870_md.c, usbcam.c, usbcam.h: 0.9.1 release. + + +2007-03-06 Sam Revitch <samr7@cs.washington.edu> + * /dev/null: Change project name to r5u870, as the device called 'ry5u870' + is something Ricoh sells that doesn't seem to be a direct basis for + any of the supported OEM webcams. + * /dev/null: Change module name from ry5u870.ko to r5u870.ko, and changed + the install path to "extra" rather than "kernel/drivers/media/video". + * /dev/null: Add microcode, device IDs, and control descriptor tables for + HP Pavilion Webcam, Sony VGP-VCC3, and Sony VGP-VCC2 (VAIO AR). + * /dev/null: Introduce the webcam model structure, and hang lists of + supported resolutions, pixel formats, and controls off of it. + * /dev/null: Use the request_firmware() interface to load microcode, rather + than embedding the microcode in the driver binary. + * /dev/null: Clean up the debug, warning, and error printing macros. + * /dev/null: Overhaul the usbcam_curframe_* interfaces. Add a solid blue + test pattern generator for when a frame is dropped by the minidriver. + * /dev/null: Add a workaround for a v4l1_compat bug preventing VIDIOCSPICT + from working, present in kernels prior to 2.6.20. + * /dev/null: Add an application bug workaround for Ekiga/ptlib: zero the + flags field of the argument to VIDIOC_QBUF. + * /dev/null: Add an application bug workaround for motion: allocate fixed + sized buffers in VIDIOCGMBUF. + * /dev/null: Fixed issues with the compat_ioctl method for running 32-bit + apps on 64-bit platforms. + * /dev/null: Apparently Linus prefers struct X to X_t. Mass-update to + match this style for better hopes of getting it merged some day. + * README, r5u870_md.c, usbcam.c, usbcam.h: 0.9.0 release. + +2007-02-15 Sam Revitch <samr7@cs.washington.edu> + * /dev/null: Fix trivial build problem with kernel 2.6.18. + * README, ry5u870_md.c, usbcam.c, usbcam.h: 0.8.1 release. + +2007-02-14 Sam Revitch <samr7@cs.washington.edu> + * /dev/null: Initial public release. + * README, ry5u870_md.c, usbcam.c, usbcam.h: 0.8.0 release. diff --git a/Kbuild b/Kbuild @@ -1,2 +1 @@ -r5u870-objs := r5u870_md.o usbcam.o -obj-m := r5u870.o +obj-m := usbcam/ r5u870.o diff --git a/README b/README @@ -1,5 +1,5 @@ Ricoh R5U870 Linux Driver -Version 0.10.2, 2008/1/13 +Version 0.11.0, 2008/1/16 Requirements ============ @@ -118,14 +118,17 @@ fixed_fbsize -- integer and then attempt to capture frames. -Changes from original version -============================= +Changes from original version (0.10.0) +====================================== * Properly implements V4L1 query ioctl functions. While version 1 has been obsoleted, it is still necessary to support it as a number of applications still use it, including GStreamer's v4lsrc element. * Support for VGP-VCC7, including supplied microcode. * Can compile against Linux kernels 2.6.24 or later. + * Uses a module approach to usbcam, and only require minidrivers to link + against a header file. This code was posted in an email to LKML by Chris + on May 1, 2007. Bugs ==== @@ -169,11 +172,23 @@ including some named Mvc25u870.sys, 5U870CAP.sys, and R5U870FLx86.sys. Acknowledgements ================ -Thanks to Geert Willems for extensive testing and bug reporting, for general -driver issues and issues specific to the HP Webcam. +Hude kudos to Sam Revitch for writing the driver. Cheers, mate. I owe you a +beer (or two). :) -Thanks to Benoît Canet for updating this driver to work with the Sony VAIO -AR webcam (05ca:1834). +Thanks to Albert Vilella for establishing an interest group for this type of +webcam, for early VAIO SZ testing, and generally collecting a good set of +resources for Linux on VAIO SZ laptops. -Thanks to Utz-Uwe Haus for getting the Sony VGP-VCC7 firmware extracted, -and providing a patch against the original r5u870 driver. +Thanks to Geert Willems for extensive testing of HP Webcam support, general +driver compatibility testing, and putting up with endless crazy requests for +more debug information. :-) + +Thanks to Mattia Dongili for taking usbsnoop traces of the VAIO UX50 webcam +driver on Windows, and testing initial support in the Linux driver. + +Thanks to Benoît Canet for updating this driver to work with the Sony VAIO AR +webcam (05ca:1834). + +Thanks to Utz-Uwe Haus for getting the Sony VGP-VCC7 firmware extracted, and +providing a patch against the original r5u870 driver, and providing the +recode-fw.scm script. diff --git a/r5u870.c b/r5u870.c @@ -0,0 +1,2890 @@ +/* + * Driver for Ricoh R5U870-based Custom OEM Webcams + * Copyright (c) 2007 Sam Revitch <samr7@cs.washington.edu> + * Copyright (c) 2008 Alexander Hixon <hixon.alexander@mediati.org> + * + * Check out README for additional credits. + * Version 0.11.0 + * + * This driver 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 driver 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 driver; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This driver supports certain custom OEM webcams based on the Ricoh + * R5U870 / Micro Vision M25U870 controller chip. These tend to be + * built in to certain models of laptop computers and claim USB vendor + * ID 0x05CA (Ricoh). + * + * All Ricoh webcams support common microcode upload, reset, and query + * version commands. + * + * Each distinct piece of hardware requires a different microcode blob, + * which is uploaded to the device the first time the device is probed + * after power-on. The microcode blob is tailored to the attached + * image sensor and the desired type of USB interface. Some microcode + * blobs are created to work with the Ricoh proprietary USB interface. + * Others behave as UVC devices, but use the common Ricoh vendor + * commands to set certain controls. Some devices, in particular the + * 1810 and 1870 HP Pavilion Webcam devices, appear to be identical in + * hardware, and differ only by USB descriptor tables, and the + * microcode blobs implement the different USB personalities. + * + * This driver supports basic UVC descriptor table parsing to determine + * available controls and resolutions, and enough UVC commands to + * support the UVC interface style of the Ricoh webcams. + */ + +#define USBCAM_DEBUG_DEFAULT 0 + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/firmware.h> +#include <linux/dmi.h> +#include <linux/ctype.h> + +#include "usbcam/usbcam.h" + + +#define USBCAM_DBG_R5U_INIT (USBCAM_DBGBIT_MD_START + 0) +#define USBCAM_DBG_R5U_FRAME (USBCAM_DBGBIT_MD_START + 1) +#define USBCAM_DBG_R5U_MDINTF (USBCAM_DBGBIT_MD_START + 2) +#define USBCAM_DBG_R5U_CTRL (USBCAM_DBGBIT_MD_START + 3) + + +#define r5u_info(RV, FMT...) usbcam_info((RV)->vh_parent, FMT) +#define r5u_err(RV, FMT...) usbcam_err((RV)->vh_parent, FMT) +#define r5u_dbg(RV, SUBSYS, FMT...) usbcam_dbg((RV)->vh_parent, SUBSYS, FMT) + +#define R5U870_VERSION KERNEL_VERSION(0,11,0) +#define R5U870_VERSION_EXTRA "halex" + + +struct r5u870_resolution { + int rw_width; + int rw_height; + int rw_reqbw; + int rw_interval; + int rw_frameidx; +}; + +struct r5u870_pix_fmt { + struct usbcam_pix_fmt base; + int rp_formatidx; + const struct r5u870_resolution *rp_restbl; + int rp_restbl_alloc; +}; + +struct r5u870_ctrl { + struct usbcam_ctrl base; + int reg; + u8 unit; + u8 info; + u8 size; + u8 is_auto; + int auto_offset; + int val_offset; + int value; +}; + +struct r5u870_model { + char *rm_name; + const char *rm_ucode_file; + const struct r5u870_resolution *rm_res; + const int *rm_wdm_ctrlids; + const struct r5u870_pix_fmt *rm_pixfmts; + int rm_npixfmts; + u16 rm_ucode_version; + unsigned int rm_uvc : 1, + rm_no_ctrl_reload : 1, + rm_no_first_auto_suppress : 1; +}; + +struct r5u870_ctx { + struct usbcam_dev *vh_parent; + struct usbcam_urbstream vh_iso; + + const struct r5u870_model *vh_model; + const struct r5u870_pix_fmt *vh_pixfmts; + int vh_npixfmts; + + int vh_dyn_pixfmts : 1, + vh_configured : 1, + vh_ctrl_reg_enable : 1, + vh_ctrl_sync : 1, + vh_ctrl_auto_suppress : 1; + + const struct r5u870_pix_fmt *vh_fmt; + const struct r5u870_resolution *vh_res; + + int vh_timeout; + int vh_firstframe; + int vh_emptypkts; + int vh_frame_accum; + + int vh_framebuf_offset; + + int vh_ctrl_ifnum; + int vh_iso_ep; + int vh_iso_ifnum; + int vh_iso_minpacket; + int vh_act_altsetting; + + int (*vh_set_fmt)(struct r5u870_ctx *, + const struct r5u870_pix_fmt *fmtp, + const struct r5u870_resolution *resp); + int (*vh_cap_stop)(struct r5u870_ctx *); + int (*vh_decide_pkt)(struct r5u870_ctx *, int st, int len, + const u8 *pktdata, int *start); + + /* Auto settings */ + int vh_auto_wb; + int vh_aec; + int vh_agc; +}; + +#define udp_r5u870(UDP) ((struct r5u870_ctx *)((UDP)->ud_minidrv_data)) +#define r5u870_dev(RV) ((RV)->vh_parent->ud_dev) + +/* + * USB control message with kmalloc'd buffer helper + */ + +static int r5u870_control_msg(struct r5u870_ctx *vhp, int write, int class, + u8 request, u16 value, u16 index, void *data, + u16 size) +{ + char *dbuf = NULL; + int res; + + if (size) { + dbuf = kmalloc(size, GFP_KERNEL); + if (!dbuf) + return -ENOMEM; + if (write) + memcpy(dbuf, data, size); + } + + res = usb_control_msg(r5u870_dev(vhp), + write + ? usb_sndctrlpipe(r5u870_dev(vhp), 0) + : usb_rcvctrlpipe(r5u870_dev(vhp), 0), + request, + (write ? USB_DIR_OUT : USB_DIR_IN) | + (class + ? (USB_TYPE_CLASS | USB_RECIP_INTERFACE) + : (USB_TYPE_VENDOR | USB_RECIP_DEVICE)), + value, index, dbuf, size, + vhp->vh_timeout); + + if (dbuf) { + if (!write) + memcpy(data, dbuf, size); + kfree(dbuf); + } + return res; +} + + +/* + * Controls + * + * For this driver, there is only one get function, which retrieves the + * value for the control stored in the r5u870_ctx structure. Each style + * of control -- WDM/UVC -- have separate set functions. + * + * There are query functions for setting the disabled flag on manual + * white balance controls when auto white balance is enabled. + */ + +enum { + V4L2_CID_R5U870_SHARPNESS = (V4L2_CID_PRIVATE_BASE + 0), + V4L2_CID_R5U870_GREEN_BALANCE, + V4L2_CID_R5U870_AUTOEXPOSURE, + V4L2_CID_R5U870_POWERLINE, + V4L2_CID_R5U870_BACKLIGHT, + V4L2_CID_R5U870_PRIVACY, + V4L2_CID_R5U870_NIGHT_MODE, +}; + +static int r5u870_get_ctrl(struct usbcam_dev *udp, + const struct usbcam_ctrl *basep, + struct v4l2_ext_control *c) +{ + struct r5u870_ctrl *ctrlp = container_of(basep, struct r5u870_ctrl, + base); + c->value = ctrlp->value; + return 0; +} + +static int r5u870_query_ctrl(struct usbcam_dev *udp, + const struct usbcam_ctrl *basep, + struct v4l2_queryctrl *c) +{ + struct r5u870_ctx *vhp = udp_r5u870(udp); + struct r5u870_ctrl *ctrlp = container_of(basep, struct r5u870_ctrl, + base); + int auto_ctrl = 0; + + if (ctrlp->auto_offset) { + auto_ctrl = *(int *) (((char *) vhp) + ctrlp->auto_offset); + if (auto_ctrl) + c->flags |= V4L2_CTRL_FLAG_INACTIVE; + } + return 0; +} + +static int r5u870_set_controls(struct r5u870_ctx *vhp, int dflt) +{ + const struct r5u870_ctrl *ctrlp; + struct v4l2_ext_control cv; + int res; + + res = 0; + list_for_each_entry(ctrlp, &vhp->vh_parent->ud_ctrl_list, + base.uc_links) { + cv.id = ctrlp->base.uc_v4l.id; + + if (dflt) + cv.value = ctrlp->base.uc_v4l.default_value; + else + cv.value = ctrlp->value; + + res = ctrlp->base.set_fn(vhp->vh_parent, + &ctrlp->base, + &cv); + if (res) + break; + } + + if (vhp->vh_ctrl_reg_enable && + !vhp->vh_ctrl_auto_suppress && + vhp->vh_model->rm_no_first_auto_suppress) + vhp->vh_ctrl_auto_suppress = 1; + + return res; +} + + +/* + * Microcode management and device initialization functions follow + */ + +static int r5u870_set_gen_reg(struct r5u870_ctx *vhp, + int cmd, int reg, int val) +{ + int res; + res = r5u870_control_msg(vhp, 1, 0, cmd, val, reg, NULL, 0); + if (res < 0) { + r5u_err(vhp, "set_gen_reg %04x/%04x/%04x failed: %d", + cmd, reg, val, res); + return res; + } + return 0; +} + +/* + * Send the power-up init sequence, in case it is needed. + */ +static int r5u870_microcode_upload(struct r5u870_ctx *vhp) +{ + const struct firmware *fws; + char *pgbuf; + const u8 *dptr; + int tolerance = 3; + int i, rem, addr, len, res = 0; + + pgbuf = (char *) kmalloc(64, GFP_KERNEL); + if (!pgbuf) + return -ENOMEM; + + r5u_dbg(vhp, R5U_INIT, "loading microcode file \"%s\"", + vhp->vh_model->rm_ucode_file); + + res = request_firmware(&fws, + vhp->vh_model->rm_ucode_file, + &vhp->vh_parent->ud_dev->dev); + + if (res) { + r5u_err(vhp, "Microcode file \"%s\" is missing", + vhp->vh_model->rm_ucode_file); + r5u_err(vhp, "Please see http://wiki.mediati.org/r5u870/Microcode"); + kfree(pgbuf); + return res; + } + + i = 0; + dptr = fws->data; + rem = fws->size; + while (rem) { + if (rem < 3) { + r5u_err(vhp, "Microcode file msg %d is incomplete", i); + res = -EINVAL; + break; + } + + len = dptr[0]; + addr = dptr[1] | (dptr[2] << 8); + dptr += 3; + rem -= 3; + + if ((rem < len) || (len > 64)) { + r5u_err(vhp, "Microcode file msg %d has bad length %d", + i, len); + res = -EINVAL; + break; + } + + /* + * The USB stack has issues with the initseq data if + * initseq points into the vmalloc arena. This is + * the case for microcode embedded in a module, or + * data loaded by request_firmware(). + * + * As a workaround, we memcpy() into a kmalloc page. + */ + memcpy(pgbuf, dptr, len); + dptr += len; + rem -= len; + + retry: + /* TODO: Maybe make this use r5u870_control_msg or similar? */ + res = usb_control_msg(r5u870_dev(vhp), + usb_sndctrlpipe(r5u870_dev(vhp), 0), + 0xa0, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + addr, 0, pgbuf, len, vhp->vh_timeout); + + if (res < 0) { + if (tolerance--) + goto retry; + r5u_err(vhp, "command a0[%d] failed: %d", + i, res); + break; + } + if (res != len) { + r5u_err(vhp, "command a0[%d] failed: %d (exp %d)", + i, res, len); + res = -EIO; + break; + } + + i++; + } + + release_firmware(fws); + kfree(pgbuf); + return res; +} + +static int r5u870_microcode_get_state(struct r5u870_ctx *vhp) +{ + char buf[1]; + int res; + r5u_dbg(vhp, R5U_INIT, "requesting microcode state"); + res = r5u870_control_msg(vhp, 0, 0, 0xa4, 0, 0, buf, 1); + if ((res != 1) || ((buf[0] != 0) && (buf[0] != 1))) { + r5u_err(vhp, "command 0xa4 failed: %d", res); + return res < 0 ? res : -EIO; + } + + r5u_dbg(vhp, R5U_INIT, "camera reports %s microcode state", + buf[0] ? "positive" : "negative"); + + return (buf[0] == 0) ? -ENOENT : 0; +} + +static int r5u870_microcode_get_ver(struct r5u870_ctx *vhp, int *verp) +{ + char buf[2]; + int res; + + r5u_dbg(vhp, R5U_INIT, "requesting microcode version"); + res = r5u870_control_msg(vhp, 0, 0, 0xc3, 0, 0x0e, buf, 2); + if (res != 2) { + r5u_err(vhp, "command 0xa3 failed: %d", res); + return res < 0 ? res : -EIO; + } + + res = le16_to_cpup((__le16 *) buf); + r5u_dbg(vhp, R5U_INIT, "camera reports version %04x", res); + *verp = res; + return 0; +} + +static int r5u870_microcode_enable(struct r5u870_ctx *vhp) +{ + char buf[1]; + int res; + + r5u_dbg(vhp, R5U_INIT, "enabling microcode"); + buf[0] = 1; + res = r5u870_control_msg(vhp, 1, 0, 0xa1, 0, 0, buf, 1); + if (res != 1) { + r5u_err(vhp, "command 0xa1 failed: %d", res); + return res < 0 ? res : -EIO; + } + + return 0; +} + +static int r5u870_microcode_reset(struct r5u870_ctx *vhp) +{ + int res; + r5u_dbg(vhp, R5U_INIT, "sending microcode reset command"); + msleep(100); /* The Windows driver waits 1sec */ + res = r5u870_set_gen_reg(vhp, 0xa6, 0, 0); + if (!res) + msleep(200); + return res; +} + +/* + * Initialize the camera after it is detected. + */ +static int r5u870_dev_init(struct r5u870_ctx *vhp) +{ + int mcver; + int res; + + if (!vhp->vh_model->rm_ucode_file) + return 0; + + res = r5u870_microcode_get_state(vhp); + if (res && (res != -ENOENT)) + return res; + + if (!res) { + res = r5u870_microcode_get_ver(vhp, &mcver); + if (res) + return res; + + if (mcver != vhp->vh_model->rm_ucode_version) { + res = r5u870_microcode_reset(vhp); + if (res) + return res; + res = -ENOENT; + } + } + + + if (res == -ENOENT) { + res = r5u870_microcode_upload(vhp); + if (res < 0) + return res; + + res = r5u870_microcode_enable(vhp); + if (res) + return res; + + res = r5u870_microcode_get_ver(vhp, &mcver); + if (res) + return res; + } + + if (mcver != vhp->vh_model->rm_ucode_version) + r5u_err(vhp, "Unexpected microcode version " + "(exp:%04x got:%04x)", + vhp->vh_model->rm_ucode_version, mcver); + + /* Halt capture in case it's running (broken driver?) */ + res = vhp->vh_cap_stop(vhp); + if (res < 0) + return res; + + return 0; +} + +/* + * WDM Device Registers are listed below. + * + * To set: use r5u870_set_reg_wdm(). + * + * No information is given about how to retrieve values from these + * registers. + * + * Note that some register IDs are overloaded. The Sony cam drivers + * seem to use 0x36 for backlight compensation and 0x37 for gain, + * whereas the HP drivers use 0x36 for color enable and 0x37 for frame + * rate. + */ +enum { + /* Brightness: [0,127] D:63 */ + R5U870_REG_BRIGHTNESS = 0x02, + + /* Contrast: [0,127] D:63 */ + R5U870_REG_CONTRAST = 0x04, + + /* Hue: [-180,180] D:0 (16-bit 2's complement) */ + R5U870_REG_HUE = 0x34, + + /* Saturation: [0,127] D:63 */ + R5U870_REG_SATURATION = 0x05, + + /* Sharpness: [0,127] D:63 */ + R5U870_REG_SHARPNESS = 0x03, + + /* Gamma correction: [1,500] D:100 */ + R5U870_REG_GAMMA = 0x35, + + /* Registers with unknown usage */ + R5U870_REG_COLOR_ENABLE = 0x36, + + /* White balance: [0,127] D:63 */ + R5U870_REG_WHITE_BALANCE = 0x1, + + /* Frame Rate: D:30 */ + R5U870_REG_FRAME_RATE = 0x37, + + /* Registers with unknown usage */ + R5U870_REG_BRIGHTNESS_EX = 0x20, + R5U870_REG_CONTRAST_EX = 0x21, + R5U870_REG_HUE_EX = 0x22, + R5U870_REG_SATURATION_EX = 0x23, + R5U870_REG_SHARPNESS_EX = 0x24, + R5U870_REG_GAMMA_EX = 0x25, + + /* White balance red value: [0,255] D:127 */ + R5U870_REG_WB_RED_EX = 0x26, + + /* White balance green value: [0,255] D:127 */ + R5U870_REG_WB_GREEN_EX = 0x27, + + /* White balance blue value: [0,255] D:127 */ + R5U870_REG_WB_BLUE_EX = 0x28, + + /* Auto white balance: [0,1] D:1 */ + R5U870_REG_WB_AUTO_EX = 0x29, + + /* Exposure Control: [0,255] D:255 */ + R5U870_REG_EXPOSURE_EX = 0x2a, + + /* Auto Exposure Control: [0,1] D:1 */ + R5U870_REG_AEC_EX = 0x2b, + + /* Gain: [0,127] D:63 */ + R5U870_REG_GAIN_EX = 0x2c, + + /* Auto Gain: [0,1] D:0 */ + R5U870_REG_AGC_EX = 0x2d, + + /* Light source flicker compensation: see R5U870_POWERLINE */ + R5U870_REG_POWERLINE_EX = 0x2e, + + /* Registers with unknown usage */ + R5U870_REG_SCENE_EX = 0x2f, + + /* Vertical flip: [0,1] D:0 */ + R5U870_REG_VFLIP_EX = 0x30, + + /* Horizontal flip: [0,1] D:0 */ + R5U870_REG_HFLIP_EX = 0x31, + + /* Blank image: [0,1] D:0 */ + R5U870_REG_PRIVACY_EX = 0x32, + + /* Night mode: [0,1] D:0 */ + R5U870_REG_NIGHT_MODE_EX = 0x33, + + /* Backlight compensation: [0,500] D:1 */ + R5U870_REG_BACKLIGHT_COMP = 0x36, + + /* Registers with unknown usage */ + R5U870_REG_GAIN = 0x37, + + /* Backlight compensation for 1834 device: [0,2] D:1 */ + R5U870_REG_BACKLIGHT_COMP_2 = 0x39, + + + /* Values for R5U870_REG_POWERLINE flicker compensation */ + R5U870_POWERLINE_OFF = 0, + R5U870_POWERLINE_50HZ = 1, + R5U870_POWERLINE_60HZ = 2, + + /* Number of empty packets between frames */ + R5U870_EMPTYPKT_FRAME_DELIM = 10, + + /* Number of empty packets before declaring the device dead (.5sec) */ + R5U870_EMPTYPKT_GIVE_UP = 4000, +}; + +static int r5u870_set_reg_wdm(struct r5u870_ctx *vhp, int reg, int val) +{ + return r5u870_set_gen_reg(vhp, 0xc2, reg, val); +} + +/* + * Set the frame size and data format. + * Do not call this function with the isochronous stream active. + */ +static int r5u870_set_fmt_wdm(struct r5u870_ctx *vhp, + const struct r5u870_pix_fmt *fmtp, + const struct r5u870_resolution *resp) +{ + int res; + + msleep(1); + res = r5u870_set_gen_reg(vhp, 0xc5, 2, fmtp->rp_formatidx); + if (res) + return res; + msleep(1); + res = r5u870_set_gen_reg(vhp, 0xc5, 0, resp->rw_width); + if (res) + return res; + msleep(1); + res = r5u870_set_gen_reg(vhp, 0xc5, 1, resp->rw_height); + if (res) + return res; + msleep(5); + return 0; +} + + +/* + * Turn frame grabbing on or off (WDM). + * This will also turn on or off the LED. + */ +static int r5u870_set_cap_state_wdm(struct r5u870_ctx *vhp, int val) +{ + return r5u870_set_gen_reg(vhp, 0xc4, val, 0); +} + +static int r5u870_cap_stop_wdm(struct r5u870_ctx *vhp) +{ + return r5u870_set_cap_state_wdm(vhp, 0); +} + +/* + * r5u870_decide_pkt_wdm + * + * Based on the size of an isochronous data packet, this function + * decides whether to copy the packet into the frame buffer and possibly + * complete the frame, or to discard both the packet and the frame. + * + * Returns: + * 0 Frame is done + * -EAGAIN Append packet to frame, frame is not done + * -EPIPE Discard frame and packet + * -EIO The device is nonresponsive, abort + */ +static int r5u870_decide_pkt_wdm(struct r5u870_ctx *vhp, int pktstatus, + int pktlen, const u8 *pktdata, int *start) +{ + int ret = -EAGAIN; + + *start = 0; + + if (pktstatus) { + /* Abort current frame */ + r5u_dbg(vhp, R5U_FRAME, "frame abort: packet status %d", + pktstatus); + vhp->vh_frame_accum = -1; + vhp->vh_emptypkts = 0; + ret = -EPIPE; + + } else if (!pktlen) { + if (++vhp->vh_emptypkts == R5U870_EMPTYPKT_FRAME_DELIM) { + if (vhp->vh_frame_accum == -1) { + /* Frame was previously aborted */ + ret = -EPIPE; + } else if (vhp->vh_frame_accum == + vhp->vh_parent->ud_format.sizeimage) { + /* Complete frame */ + ret = 0; + } else { + /* Not enough data in frame sequence */ + r5u_dbg(vhp, R5U_FRAME, "frame abort: " + "Frame seq too short (exp:%d got:%d)", + vhp->vh_parent->ud_format.sizeimage, + vhp->vh_frame_accum); + ret = -EPIPE; + } + + if (!vhp->vh_firstframe) { + /* Always reject the first frame */ + vhp->vh_firstframe = 1; + vhp->vh_frame_accum = -1; + ret = -EPIPE; + } else { + vhp->vh_frame_accum = 0; + } + } + + else if (vhp->vh_emptypkts >= R5U870_EMPTYPKT_GIVE_UP) { + r5u_dbg(vhp, R5U_FRAME, "%d empty packets, giving up", + vhp->vh_emptypkts); + ret = -EIO; + } + + } else { + vhp->vh_emptypkts = 0; + if (vhp->vh_frame_accum == -1) { + /* Frame was previously aborted */ + ret = -EPIPE; + } else if ((vhp->vh_frame_accum + pktlen) <= + vhp->vh_parent->ud_format.sizeimage) { + /* Append this data */ + vhp->vh_frame_accum += pktlen; + } else { + /* Oversized frame, abort */ + r5u_dbg(vhp, R5U_FRAME, "frame abort: " + "Frame seq too long"); + vhp->vh_frame_accum = -1; + ret = -EPIPE; + } + } + + return ret; +} + +static int r5u870_set_manual_ctrls_wdm(struct r5u870_ctx *vhp, int auto_offset) +{ + const struct r5u870_ctrl *ctrlp; + int val, res; + + res = 0; + list_for_each_entry(ctrlp, &vhp->vh_parent->ud_ctrl_list, + base.uc_links) { + if (ctrlp->auto_offset != auto_offset) + continue; + if (!vhp->vh_ctrl_reg_enable) { + vhp->vh_ctrl_sync = 0; + continue; + } + + val = ctrlp->value; + + r5u_dbg(vhp, R5U_CTRL, "control %s/wdm %02x <= %d [manual]", + ctrlp->base.uc_v4l.name, ctrlp->reg, val); + res = r5u870_set_reg_wdm(vhp, ctrlp->reg, val); + if (res) + break; + } + return res; +} + +static int r5u870_set_ctrl_wdm(struct usbcam_dev *udp, + const struct usbcam_ctrl *basep, + const struct v4l2_ext_control *c) +{ + struct r5u870_ctx *vhp = udp_r5u870(udp); + struct r5u870_ctrl *ctrlp = container_of(basep, struct r5u870_ctrl, + base); + int res = 0; + int auto_ctrl = 0; + + if (ctrlp->auto_offset) + auto_ctrl = *(int *) (((char *) vhp) + ctrlp->auto_offset); + + if (auto_ctrl && vhp->vh_ctrl_auto_suppress) { + r5u_dbg(vhp, R5U_CTRL, "control %s <= %d [auto suppress]", + ctrlp->base.uc_v4l.name, c->value); + + } else if (!vhp->vh_ctrl_reg_enable) { + r5u_dbg(vhp, R5U_CTRL, "control %s <= %d [capture off]", + ctrlp->base.uc_v4l.name, c->value); + vhp->vh_ctrl_sync = 0; + + } else { + r5u_dbg(vhp, R5U_CTRL, "control %s/wdm %02x <= %d", + ctrlp->base.uc_v4l.name, ctrlp->reg, c->value); + res = r5u870_set_reg_wdm(vhp, ctrlp->reg, c->value); + if (res) + return res; + } + + ctrlp->value = c->value; + + if (ctrlp->val_offset) + *(int *) (((char *) vhp) + ctrlp->val_offset) = c->value; + + if (ctrlp->is_auto && !c->value) + res = r5u870_set_manual_ctrls_wdm(vhp, ctrlp->val_offset); + + return res; +} + +/* + * WDM control templates follow + * + * Each device has an array of integer control IDs, which refer to an + * element in the control template array. When the device is detected, + * we build the control array out of the element list and the templates, + * by r5u870_wdm_add_ctrls(). + */ + +enum { + R5U870_WDM_CTRL_BRIGHTNESS, + R5U870_WDM_CTRL_CONTRAST, + R5U870_WDM_CTRL_SATURATION, + R5U870_WDM_CTRL_SHARPNESS, + R5U870_WDM_CTRL_HUE, + R5U870_WDM_CTRL_GAMMA, + R5U870_WDM_CTRL_BACKLIGHT_COMP_500, + R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1, + R5U870_WDM_CTRL_BACKLIGHT_COMP_X1834, + R5U870_WDM_CTRL_WB_RED, + R5U870_WDM_CTRL_WB_GREEN, + R5U870_WDM_CTRL_WB_BLUE, + R5U870_WDM_CTRL_WB_AUTO, + R5U870_WDM_CTRL_AUTO_EXPOSURE, + R5U870_WDM_CTRL_EXPOSURE, + R5U870_WDM_CTRL_AUTO_GAIN, + R5U870_WDM_CTRL_GAIN, + R5U870_WDM_CTRL_POWERLINE, + R5U870_WDM_CTRL_VFLIP, + R5U870_WDM_CTRL_HFLIP, + R5U870_WDM_CTRL_PRIVACY, + R5U870_WDM_CTRL_NIGHTMODE, + + R5U870_WDM_CTRL_LAST = 0xffff, +}; + +/* TODO: Merge these into V4L API. */ + +#define V4L2_CID_GREEN_BALANCE (V4L2_CID_BASE+24) +#define V4L2_CID_AUTOEXPOSURE (V4L2_CID_BASE+25) +#define V4L2_CID_POWER_LINE_FREQ (V4L2_CID_BASE+26) +#define V4L2_CID_BACKLIGHT_COMP (V4L2_CID_BASE+27) +#define V4L2_CID_PRIVACY (V4L2_CID_BASE+28) +#define V4L2_CID_NIGHT_MODE (V4L2_CID_BASE+29) +#define V4L2_CID_SHARPNESS (V4L2_CID_BASE+30) +#define V4L2_CID_LASTP1 (V4L2_CID_BASE+31) /* last CID + 1 */ + +static const char *r5u870_powerline_names[] = { "Off", "50Hz", "60Hz" }; + +static struct r5u870_ctrl r5u870_wdm_ctrls[] = { + + [R5U870_WDM_CTRL_BRIGHTNESS] = { + .base = { .uc_v4l = { .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 63, + .flags = V4L2_CTRL_FLAG_SLIDER }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_BRIGHTNESS, + }, + [R5U870_WDM_CTRL_CONTRAST] = { + .base = { .uc_v4l = { .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Contrast", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 63, + .flags = V4L2_CTRL_FLAG_SLIDER }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_CONTRAST, + }, + [R5U870_WDM_CTRL_SATURATION] = { + .base = { .uc_v4l = { .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 63, + .flags = V4L2_CTRL_FLAG_SLIDER }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_SATURATION, + }, + [R5U870_WDM_CTRL_SHARPNESS] = { + .base = { .uc_v4l = { .id = V4L2_CID_SHARPNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Sharpness", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 63, + .flags = V4L2_CTRL_FLAG_SLIDER }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_SHARPNESS, + }, + [R5U870_WDM_CTRL_HUE] = { + .base = { .uc_v4l = { .id = V4L2_CID_HUE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Hue", + .minimum = -180, + .maximum = 180, + .step = 1, + .default_value = 0, + .flags = V4L2_CTRL_FLAG_SLIDER }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_HUE, + }, + [R5U870_WDM_CTRL_GAMMA] = { + .base = { .uc_v4l = { .id = V4L2_CID_GAMMA, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gamma", + .minimum = 0, + .maximum = 500, + .step = 1, + .default_value = 100, + .flags = V4L2_CTRL_FLAG_SLIDER }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_GAMMA, + }, + [R5U870_WDM_CTRL_BACKLIGHT_COMP_500] = { + .base = { .uc_v4l = { .id = V4L2_CID_BACKLIGHT_COMP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Backlight Compensation", + .minimum = 0, + .maximum = 500, + .step = 1, + .default_value = 250, + .flags = V4L2_CTRL_FLAG_SLIDER }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_BACKLIGHT_COMP, + }, + [R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1] = { + .base = { .uc_v4l = { .id = V4L2_CID_BACKLIGHT_COMP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Backlight Compensation", + .minimum = 0, + .maximum = 500, + .step = 1, + .default_value = 1, + .flags = V4L2_CTRL_FLAG_SLIDER }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_BACKLIGHT_COMP, + }, + [R5U870_WDM_CTRL_BACKLIGHT_COMP_X1834] = { + .base = { .uc_v4l = { .id = V4L2_CID_BACKLIGHT_COMP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Backlight Compensation", + .minimum = 0, + .maximum = 2, + .step = 1, + .default_value = 1, + .flags = V4L2_CTRL_FLAG_SLIDER }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_BACKLIGHT_COMP_2, + }, + [R5U870_WDM_CTRL_WB_RED] = { + .base = { .uc_v4l = { .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "White Balance Red", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 127, + .flags = 0 }, + .query_fn = r5u870_query_ctrl, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_WB_RED_EX, + .auto_offset = offsetof(struct r5u870_ctx, vh_auto_wb) + }, + [R5U870_WDM_CTRL_WB_GREEN] = { + .base = { .uc_v4l = { .id = V4L2_CID_GREEN_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "White Balance Green", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 127, + .flags = 0 }, + .query_fn = r5u870_query_ctrl, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_WB_GREEN_EX, + .auto_offset = offsetof(struct r5u870_ctx, vh_auto_wb) + }, + [R5U870_WDM_CTRL_WB_BLUE] = { + .base = { .uc_v4l = { .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "White Balance Blue", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 127, + .flags = 0 }, + .query_fn = r5u870_query_ctrl, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_WB_BLUE_EX, + .auto_offset = offsetof(struct r5u870_ctx, vh_auto_wb) + }, + [R5U870_WDM_CTRL_WB_AUTO] = { + .base = { .uc_v4l = { .id = V4L2_CID_AUTO_WHITE_BALANCE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Auto White Balance", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + .flags = V4L2_CTRL_FLAG_UPDATE }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_WB_AUTO_EX, + .is_auto = 1, + .val_offset = offsetof(struct r5u870_ctx, vh_auto_wb) + }, + [R5U870_WDM_CTRL_AUTO_EXPOSURE] = { + .base = { .uc_v4l = { .id = V4L2_CID_AUTOEXPOSURE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Auto Exposure Control", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + .flags = 0 }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_AEC_EX, + .is_auto = 1, + .val_offset = offsetof(struct r5u870_ctx, vh_aec) + }, + [R5U870_WDM_CTRL_EXPOSURE] = { + .base = { .uc_v4l = { .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Exposure", + .minimum = 0, + .maximum = 511, + .step = 1, + .default_value = 255, + .flags = 0 }, + .query_fn = r5u870_query_ctrl, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_EXPOSURE_EX, + .auto_offset = offsetof(struct r5u870_ctx, vh_aec) + }, + [R5U870_WDM_CTRL_AUTO_GAIN] = { + .base = { .uc_v4l = { .id = V4L2_CID_AUTOGAIN, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Auto Gain Control", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + .flags = 0 }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_AGC_EX, + .is_auto = 1, + .val_offset = offsetof(struct r5u870_ctx, vh_agc) + }, + [R5U870_WDM_CTRL_GAIN] = { + .base = { .uc_v4l = { .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 63, + .flags = 0 }, + .query_fn = r5u870_query_ctrl, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_GAIN_EX, + .auto_offset = offsetof(struct r5u870_ctx, vh_agc) + }, + [R5U870_WDM_CTRL_POWERLINE] = { + .base = { .uc_v4l = { .id = V4L2_CID_POWER_LINE_FREQ, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Power Line Frequency", + .minimum = 0, + .maximum = 2, + .step = 1, + .default_value = 0, + .flags = 0 }, + .uc_menu_names = r5u870_powerline_names, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_POWERLINE_EX, + }, + [R5U870_WDM_CTRL_VFLIP] = { + .base = { .uc_v4l = { .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "V-Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0 }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_VFLIP_EX, + }, + [R5U870_WDM_CTRL_HFLIP] = { + .base = { .uc_v4l = { .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "H-Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0 }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_HFLIP_EX, + }, + [R5U870_WDM_CTRL_PRIVACY] = { + .base = { .uc_v4l = { .id = V4L2_CID_PRIVACY, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Privacy", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0 }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_PRIVACY_EX, + }, + [R5U870_WDM_CTRL_NIGHTMODE] = { + .base = { .uc_v4l = { .id = V4L2_CID_NIGHT_MODE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Night Mode", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0 }, + .get_fn = r5u870_get_ctrl, + .set_fn = r5u870_set_ctrl_wdm }, + .reg = R5U870_REG_NIGHT_MODE_EX, + }, +}; + +static int r5u870_wdm_add_ctrls(struct r5u870_ctx *vhp, const int *ctrlarray) +{ + struct r5u870_ctrl *ncp; + int i; + + for (i = 0; ctrlarray[i] != R5U870_WDM_CTRL_LAST; i++) { + ncp = (struct r5u870_ctrl *) + usbcam_ctrl_add_tmpl(vhp->vh_parent, + &r5u870_wdm_ctrls[ctrlarray[i]].base, + sizeof(*ncp)); + if (!ncp) + return -ENOMEM; + } + r5u_dbg(vhp, R5U_CTRL, "Added %d WDM controls", i); + return 0; +} + + + +/* + * UVC related code follows + */ + +enum { + UVC_SC_VIDEOCONTROL = 1, + UVC_SC_VIDEOSTREAMING = 1, + + UVC_VC_HEADER = 1, + UVC_VC_INPUT_TERMINAL = 2, + UVC_VC_OUTPUT_TERMINAL = 3, + UVC_VC_SELECTOR_UNIT = 4, + UVC_VC_PROCESSING_UNIT = 5, + UVC_VC_EXTENSION_UNIT = 6, + + UVC_VC_REQUEST_ERROR_CODE_CONTROL = 0x02, + + UVC_VS_INPUT_HEADER = 0x01, + UVC_VS_FORMAT_UNCOMPRESSED = 0x04, + UVC_VS_FRAME_UNCOMPRESSED = 0x05, + + UVC_SET_CUR = 0x01, + UVC_GET_CUR = 0x81, + UVC_GET_MIN = 0x82, + UVC_GET_MAX = 0x83, + UVC_GET_RES = 0x84, + UVC_GET_LEN = 0x85, + UVC_GET_INFO = 0x86, + UVC_GET_DEF = 0x87, + + UVC_PU_BACKLIGHT_COMPENSATION_CONTROL = 0x01, + UVC_PU_BRIGHTNESS_CONTROL = 0x02, + UVC_PU_CONTRAST_CONTROL = 0x03, + UVC_PU_POWER_LINE_FREQUENCY_CONTROL = 0x05, + UVC_PU_HUE_CONTROL = 0x06, + UVC_PU_SATURATION_CONTROL = 0x07, + UVC_PU_SHARPNESS_CONTROL = 0x08, + UVC_PU_GAMMA_CONTROL = 0x09, + + UVC_VS_PROBE_CONTROL = 0x01, + UVC_VS_COMMIT_CONTROL = 0x02, + +}; + +static int r5u870_uvc_req(struct r5u870_ctx *vhp, int cmd, + u8 valhi, u8 vallow, u8 idxhi, u8 idxlow, + u8 *buf, int len) +{ + int out, res, stres; + int tries = 5; + u8 stbuf[1]; + + out = (cmd == UVC_SET_CUR) ? 1 : 0; + +retry: + /* TODO: Base our other retry control message off this one. */ + res = r5u870_control_msg(vhp, out, 1, cmd, + (valhi << 8) | vallow, (idxhi << 8) | idxlow, + buf, len); + + if (res != -EPIPE) + //r5u_err(vhp, "res != -EPIPE."); + goto complete; + + stres = r5u870_control_msg(vhp, 0, 1, UVC_GET_CUR, + UVC_VC_REQUEST_ERROR_CODE_CONTROL << 8, + vhp->vh_ctrl_ifnum, + stbuf, sizeof(stbuf)); + + if (((stres == -EPIPE) && --tries) || + ((stres == 1) && (stbuf[0] == 1) && --tries)) { + msleep(5); + r5u_err(vhp, "uvc_req: retrying - EPIPE/stres error."); + goto retry; + } + + if (stres != 1) { + r5u_err(vhp, "uvc_req: status req failed: %d", stres); + goto complete; + + } else { + r5u_err(vhp, "uvc_req: status %d", stbuf[0]); + } + +complete: + if (res < 0) { + r5u_err(vhp, "uvc_req %02x/%02x%02x/%02x%02x failed: %d", + cmd, valhi, vallow, idxhi, idxlow, res); + } + + return res; +} + +static int r5u870_set_fmt_uvc(struct r5u870_ctx *vhp, + const struct r5u870_pix_fmt *fmtp, + const struct r5u870_resolution *resp) +{ + unsigned char buf[26]; + int res; + + memset(buf, 0, sizeof(buf)); + + buf[2] = fmtp->rp_formatidx; + buf[3] = resp->rw_frameidx; + *(__le32 *) &buf[4] = cpu_to_le32(resp->rw_interval); + + r5u_dbg(vhp, R5U_FRAME, + "set_format: fmtidx:%d frameidx:%d %dx%d ival:%d", + fmtp->rp_formatidx, resp->rw_frameidx, + resp->rw_width, resp->rw_height, resp->rw_interval); + + res = r5u870_uvc_req(vhp, UVC_SET_CUR, UVC_VS_PROBE_CONTROL, 0, + 0, vhp->vh_iso_ifnum, buf, sizeof(buf)); + if (res != sizeof(buf)) { + r5u_err(vhp, "%s: probe_control set_cur1: short write %d", + __FUNCTION__, res); + + return -EIO; + } + + res = r5u870_uvc_req(vhp, UVC_GET_CUR, UVC_VS_PROBE_CONTROL, 0, + 0, vhp->vh_iso_ifnum, buf, sizeof(buf)); + if (res != sizeof(buf)) { + r5u_err(vhp, "%s: probe_control get_cur: short read %d", + __FUNCTION__, res); + return -EIO; + } + + if (buf[2] != fmtp->rp_formatidx) { + r5u_err(vhp, "%s: probe_control get_cur: got fmt %d", + __FUNCTION__, buf[2]); + return -EIO; + } + + if (buf[3] != resp->rw_frameidx) { + r5u_err(vhp, "%s: probe_control get_cur: got frame idx %d", + __FUNCTION__, buf[3]); + return -EIO; + } + + res = r5u870_uvc_req(vhp, UVC_SET_CUR, UVC_VS_COMMIT_CONTROL, 0, + 0, vhp->vh_iso_ifnum, buf, sizeof(buf)); + if (res != sizeof(buf)) { + r5u_err(vhp, "%s: commit_control set_cur2: short write %d", + __FUNCTION__, res); + return -EIO; + } + + vhp->vh_iso_minpacket = le32_to_cpup((__le32 *) &buf[22]); + + return 0; +} + +static int r5u870_cap_stop_uvc(struct r5u870_ctx *vhp) +{ + /* UVC capture is controlled by changing the altsetting */ + return 0; +} + +static int r5u870_decide_pkt_uvc(struct r5u870_ctx *vhp, int pktstatus, + int pktlen, const u8 *pktdata, int *start) +{ + if (!pktlen) { + vhp->vh_emptypkts++; + if (vhp->vh_emptypkts >= R5U870_EMPTYPKT_GIVE_UP) { + r5u_err(vhp, "capture abort: too many empty pkts"); + return -EIO; + } + if (vhp->vh_frame_accum < 0) + return -EPIPE; + return -EAGAIN; + } + + vhp->vh_emptypkts = 0; + if (vhp->vh_frame_accum < 0) { + if (pktdata[1] & 2) + vhp->vh_frame_accum = 0; + return -EPIPE; + } + + if ((pktdata[0] < 2) || (pktdata[0] > pktlen)) { + r5u_err(vhp, "capture abort: hdrlen=%d pktlen=%d", + pktdata[0], pktlen); + return -EIO; + } + vhp->vh_frame_accum += pktlen - pktdata[0]; + if (vhp->vh_frame_accum > vhp->vh_parent->ud_format.sizeimage) { + r5u_err(vhp, "frame abort: accum=%d", vhp->vh_frame_accum); + vhp->vh_frame_accum = -1; + return -EPIPE; + } + + *start = pktdata[0]; + if (pktdata[1] & 2) { + if (vhp->vh_frame_accum < + vhp->vh_parent->ud_format.sizeimage) { + r5u_err(vhp, "warning: short frame (exp:%d got:%d)", + vhp->vh_parent->ud_format.sizeimage, + vhp->vh_frame_accum); + } + vhp->vh_frame_accum = 0; + return 0; + } + return -EAGAIN; +} + + +/* + * Known video format GUIDs and V4L pixel format translations + * + * Only uncompressed formats are supported, as Ricoh webcams are too + * minimal to do anything else. + */ + +static const struct r5u870_uvc_fmtinfo { + const char *fi_name; + int fi_v4l_id; + u8 fi_guid[16]; + +} r5u870_uvc_fmts[] = { + { .fi_name = "YUY2 4:2:2", + .fi_v4l_id = V4L2_PIX_FMT_YUYV, + .fi_guid = { 0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }, + { } +}; + +static int r5u870_uvc_add_resolution(struct r5u870_ctx *vhp, + struct r5u870_pix_fmt *fmtp, + int width, int height, int reqbw, + int frameidx, int interval) +{ + int i; + struct r5u870_resolution *resp; + + if (!width || !height) { + r5u_dbg(vhp, R5U_INIT, "invalid frame descriptor %d at %dx%d", + frameidx, width, height); + return -EINVAL; + } + + resp = NULL; + for (i = 0; i < fmtp->rp_restbl_alloc; i++) { + if (!fmtp->rp_restbl[i].rw_width) { + resp = (struct r5u870_resolution *) + &fmtp->rp_restbl[i]; + break; + } + } + + if (!resp) { + r5u_dbg(vhp, R5U_INIT, + "no space for frame descriptor %d at %dx%d", + frameidx, width, height); + return 0; + } + + resp->rw_width = width; + resp->rw_height = height; + resp->rw_frameidx = frameidx; + resp->rw_reqbw = reqbw; + resp->rw_interval = interval; + + r5u_dbg(vhp, R5U_INIT, "Found resolution %d: %dx%d ival %d (%d B/s)", + frameidx, width, height, interval, reqbw); + + return 0; +} + +static int r5u870_uvc_add_fmt(struct r5u870_ctx *vhp, const u8 *guid, + int fmtidx, int nresolutions, + struct r5u870_pix_fmt **new_fmt) +{ + const struct r5u870_uvc_fmtinfo *fip, *fmtarray = r5u870_uvc_fmts; + struct r5u870_pix_fmt *nfp, *fmtp; + int i; + + fip = NULL; + for (i = 0; fmtarray[i].fi_name != NULL; i++) { + if (!memcmp(fmtarray[i].fi_guid, guid, 16)) { + fip = &fmtarray[i]; + break; + } + } + + if (fip == NULL) { + r5u_dbg(vhp, R5U_INIT, "unknown format " + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x%02x%02x%02x%02x", + guid[0], guid[1], guid[2], guid[3], guid[4], guid[5], + guid[6], guid[7], guid[8], guid[9], guid[10], + guid[11], guid[12], guid[13], guid[14], guid[15]); + return -ENOENT; + } + + nfp = (struct r5u870_pix_fmt *) + kmalloc((1 + vhp->vh_npixfmts) * sizeof(*nfp), GFP_KERNEL); + if (!nfp) + return -ENOMEM; + if (vhp->vh_npixfmts) + memcpy(nfp, vhp->vh_pixfmts, vhp->vh_npixfmts * sizeof(*nfp)); + + fmtp = &nfp[vhp->vh_npixfmts]; + memset(fmtp, 0, sizeof(*fmtp)); + strlcpy(fmtp->base.description, + fip->fi_name, + sizeof(fmtp->base.description)); + fmtp->base.pixelformat = fip->fi_v4l_id; + fmtp->rp_formatidx = fmtidx; + fmtp->rp_restbl = (struct r5u870_resolution *) + kmalloc((1 + nresolutions) * sizeof(*fmtp->rp_restbl), + GFP_KERNEL); + if (!fmtp->rp_restbl) { + kfree(nfp); + return -ENOMEM; + } + + memset((char *)fmtp->rp_restbl, 0, + (1 + nresolutions) * sizeof(*fmtp->rp_restbl)); + fmtp->rp_restbl_alloc = nresolutions; + + if (vhp->vh_npixfmts && vhp->vh_dyn_pixfmts) + kfree(vhp->vh_pixfmts); + vhp->vh_pixfmts = nfp; + vhp->vh_npixfmts++; + + if (new_fmt) + (*new_fmt) = fmtp; + + r5u_dbg(vhp, R5U_INIT, "Found format %d: %c%c%c%c (%d frames)", + fmtidx, + fmtp->base.pixelformat & 0xff, + (fmtp->base.pixelformat >> 8) & 0xff, + (fmtp->base.pixelformat >> 16) & 0xff, + (fmtp->base.pixelformat >> 24) & 0xff, + nresolutions); + + return 0; +} + +static int r5u870_uvc_parse_vs(struct r5u870_ctx *vhp, int ifnum) +{ + struct usb_interface *intf; + struct usb_host_interface *aintf; + struct r5u870_pix_fmt *curfmt = NULL; + u8 *desc; + int dlen, rlen; + int wid, hgt, reqbw, ival; + int res; + + intf = usb_ifnum_to_if(r5u870_dev(vhp), ifnum); + if (!intf) + return -EINVAL; + + aintf = intf->cur_altsetting; + + for (desc = aintf->extra, rlen = aintf->extralen; + rlen > 2; + rlen -= desc[0], desc += desc[0]) { + + dlen = desc[0]; + if (dlen < 2) + return -EINVAL; + if (desc[1] != USB_DT_CS_INTERFACE) + continue; + if (dlen < 3) + return -EINVAL; + + switch (desc[2]) { + case UVC_VS_INPUT_HEADER: + if (dlen < 7) { + r5u_err(vhp, "VS_INPUT_HEADER too short " + "at %d bytes", dlen); + return -EINVAL; + } + vhp->vh_iso_ep = desc[6] & 0xf; + break; + + case UVC_VS_FORMAT_UNCOMPRESSED: + if (dlen < 21) { + r5u_err(vhp, "VS_FORMAT_UNCOMP too short " + "at %d bytes", dlen); + break; + } + res = r5u870_uvc_add_fmt(vhp, &desc[5], desc[3], + desc[4], &curfmt); + if (res) { + if (res != -ENOENT) + return res; + curfmt = NULL; + } + break; + + case UVC_VS_FRAME_UNCOMPRESSED: + if (dlen < 26) { + r5u_err(vhp, "VS_FRAME_UNCOMP too short " + "at %d bytes", dlen); + break; + } + if (!curfmt) { + r5u_dbg(vhp, R5U_INIT, "VS_FRAME_UNCOMP " + "not following VS_FORMAT_UNCOMP"); + break; + } + + wid = desc[5] | (desc[6] << 8); + hgt = desc[7] | (desc[8] << 8); + reqbw = desc[13] | (desc[14] << 8) | + (desc[15] << 16) | (desc[16] << 24); + reqbw = (reqbw + 7) / 8; + ival = le32_to_cpup((__le32 *) &desc[21]); + + res = r5u870_uvc_add_resolution(vhp, curfmt, wid, hgt, + reqbw, desc[3], ival); + if (res) + return res; + break; + } + } + + return 0; +} + + +/* + * Known UVC controls for processing units + * + * Not all types of UVC controls are supported. We stick to simple + * controls with one or two byte values, and don't do anything very + * complicated. + * + * We don't support camera unit controls, or multiple processing units + * with instances of the same control. We also don't check for + * redefined control IDs, and let usbcam do this for us. + */ + +struct r5u870_uvc_ctrlinfo { + const char *ci_name; + u32 ci_v4l_id; + int ci_v4l_type; + int ci_v4l_flags; + u8 ci_reg; + u8 ci_size; + u8 ci_bm_index; + int ci_min, ci_max, ci_def; + u8 ci_min_force, ci_max_force, ci_def_force; + const char **ci_menu_names; + int ci_val_offset; +}; + +static struct r5u870_uvc_ctrlinfo r5u870_uvc_proc_ctrls[] = { + { .ci_name = "Brightness", + .ci_v4l_id = V4L2_CID_BRIGHTNESS, + .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, + .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, + .ci_reg = UVC_PU_BRIGHTNESS_CONTROL, + .ci_size = 2, + .ci_bm_index = 0 }, + { .ci_name = "Contrast", + .ci_v4l_id = V4L2_CID_CONTRAST, + .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, + .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, + .ci_reg = UVC_PU_CONTRAST_CONTROL, + .ci_size = 2, + .ci_bm_index = 1 }, + { .ci_name = "Hue", + .ci_v4l_id = V4L2_CID_HUE, + .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, + .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, + .ci_reg = UVC_PU_HUE_CONTROL, + .ci_size = 2, + .ci_min = -180, .ci_min_force = 1, + .ci_max = 180, .ci_max_force = 1, + .ci_def = 0, .ci_def_force = 1, + .ci_bm_index = 2 }, + { .ci_name = "Saturation", + .ci_v4l_id = V4L2_CID_SATURATION, + .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, + .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, + .ci_reg = UVC_PU_SATURATION_CONTROL, + .ci_size = 2, + .ci_bm_index = 3 }, + { .ci_name = "Sharpness", + .ci_v4l_id = V4L2_CID_SHARPNESS, + .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, + .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, + .ci_reg = UVC_PU_SHARPNESS_CONTROL, + .ci_size = 2, + .ci_bm_index = 4 }, + { .ci_name = "Gamma", + .ci_v4l_id = V4L2_CID_GAMMA, + .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, + .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, + .ci_reg = UVC_PU_GAMMA_CONTROL, + .ci_size = 2, + .ci_bm_index = 5 }, + { .ci_name = "Backlight Compensation", + .ci_v4l_id = V4L2_CID_BACKLIGHT_COMP, + .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, + .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, + .ci_reg = UVC_PU_BACKLIGHT_COMPENSATION_CONTROL, + .ci_size = 2, + .ci_bm_index = 8 }, + { .ci_name = "Power Line Frequency", + .ci_v4l_id = V4L2_CID_POWER_LINE_FREQ, + .ci_v4l_type = V4L2_CTRL_TYPE_MENU, + .ci_reg = UVC_PU_POWER_LINE_FREQUENCY_CONTROL, + .ci_size = 1, + .ci_bm_index = 10, + .ci_min = 0, .ci_min_force = 1, + .ci_max = 2, .ci_max_force = 1, + .ci_menu_names = r5u870_powerline_names }, + { } +}; + +static int r5u870_uvc_ctrl_req(struct r5u870_ctx *vhp, + const struct r5u870_ctrl *ctrlp, + int req, int *value) +{ + u8 buf[4]; + int size, i, val, res; + + size = ctrlp->size; + if (req == UVC_GET_INFO) + size = 1; + + if (size > sizeof(buf)) { + r5u_err(vhp, "Control ID %d is too large, %d bytes", + ctrlp->reg, size); + return -EINVAL; + } + + memset(buf, 0, sizeof(buf)); + + if (req != UVC_SET_CUR) { + res = r5u870_uvc_req(vhp, req, ctrlp->reg, 0, + ctrlp->unit, vhp->vh_ctrl_ifnum, + buf, size); + if (res < 0) + return res; + if (res != size) { + r5u_err(vhp, "short read for UVC control %d", + ctrlp->reg); + return -EIO; + } + + val = 0; + switch (size) { + case 1: + val = buf[0]; + break; + case 2: + val = le16_to_cpu(*(__le16 *) buf); + break; + case 4: + val = le32_to_cpu(*(__le32 *) buf); + break; + default: + return -EINVAL; + } + + *value = val; + return 0; + } + + val = *value; + for (i = 0; i < size; i++, val >>= 8) + buf[i] = val & 0xff; + + res = r5u870_uvc_req(vhp, UVC_SET_CUR, ctrlp->reg, 0, + ctrlp->unit, vhp->vh_ctrl_ifnum, + buf, size); + if (res < 0) + return res; + if (res != size) { + r5u_err(vhp, "short write for UVC control %d", + ctrlp->reg); + return -EIO; + } + + return 0; +} + +static int r5u870_set_ctrl_uvc(struct usbcam_dev *udp, + const struct usbcam_ctrl *basep, + const struct v4l2_ext_control *c) +{ + struct r5u870_ctx *vhp = udp_r5u870(udp); + struct r5u870_ctrl *ctrlp = container_of(basep, struct r5u870_ctrl, + base); + int val; + int res = 0; + + if (!(ctrlp->info & 2)) { + r5u_dbg(vhp, R5U_CTRL, "control %s <= %d [set not supported]", + ctrlp->base.uc_v4l.name, c->value); + return -EINVAL; + } + + r5u_dbg(vhp, R5U_CTRL, "control %s/uvc %02x <= %d", + ctrlp->base.uc_v4l.name, ctrlp->reg, c->value); + + val = c->value; + res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_SET_CUR, &val); + if (res) + return res; + + ctrlp->value = c->value; + + return res; +} + +/* + * Initialize a r5u870_ctrl structure based on a UVC descriptor array, + * and a bit within a supported control bitmap. + */ +static int r5u870_uvc_init_ctrl(struct r5u870_ctx *vhp, + int unit, + const struct r5u870_uvc_ctrlinfo *ci_array, + int bit_offset) +{ + const struct r5u870_uvc_ctrlinfo *cip = NULL; + struct r5u870_ctrl *ctrlp; + int i, res, val; + + for (i = 0; ci_array[i].ci_name != NULL; i++) { + if (ci_array[i].ci_bm_index == bit_offset) { + cip = &ci_array[i]; + break; + } + } + + if (!cip) + return -ENOENT; + + ctrlp = (struct r5u870_ctrl *) usbcam_ctrl_alloc(sizeof(*ctrlp)); + if (!ctrlp) + return -ENOMEM; + + ctrlp->base.uc_v4l.id = cip->ci_v4l_id; + ctrlp->base.uc_v4l.type = cip->ci_v4l_type; + strlcpy(ctrlp->base.uc_v4l.name, + cip->ci_name, + sizeof(ctrlp->base.uc_v4l.name)); + ctrlp->base.uc_v4l.flags = cip->ci_v4l_flags; + ctrlp->base.uc_v4l.step = 1; + ctrlp->base.uc_menu_names = cip->ci_menu_names; + ctrlp->base.get_fn = r5u870_get_ctrl; + ctrlp->base.set_fn = r5u870_set_ctrl_uvc; + ctrlp->base.query_fn = r5u870_query_ctrl; + ctrlp->reg = cip->ci_reg; + ctrlp->unit = unit; + ctrlp->size = cip->ci_size; + ctrlp->is_auto = 0; + ctrlp->auto_offset = 0; + + res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_INFO, &val); + if (res) + goto failed; + ctrlp->info = val; + + if (cip->ci_min_force) { + ctrlp->base.uc_v4l.minimum = cip->ci_min; + } else { + res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_MIN, &val); + if (res < 0) + goto failed; + ctrlp->base.uc_v4l.minimum = val; + } + if (cip->ci_max_force) { + ctrlp->base.uc_v4l.maximum = cip->ci_max; + } else { + res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_MAX, &val); + if (res < 0) + goto failed; + ctrlp->base.uc_v4l.maximum = val; + } + + if (cip->ci_def_force) { + ctrlp->base.uc_v4l.default_value = cip->ci_def; + } else { + res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_DEF, &val); + if (res) + goto failed; + return res; + ctrlp->base.uc_v4l.default_value = val; + } + + res = usbcam_ctrl_add(vhp->vh_parent, &ctrlp->base); + if (res) + goto failed; + + r5u_dbg(vhp, R5U_INIT, "Found UVC control %s [%d,%d] def:%d info:%02x", + ctrlp->base.uc_v4l.name, ctrlp->base.uc_v4l.minimum, + ctrlp->base.uc_v4l.maximum, ctrlp->base.uc_v4l.default_value, + ctrlp->info); + return 0; + +failed: + usbcam_ctrl_free(ctrlp); + return res; +} + +static int r5u870_uvc_add_ctrls(struct r5u870_ctx *vhp, int unit, + const struct r5u870_uvc_ctrlinfo *ci_array, + const u8 *bits, int nbits) +{ + int i, res; + + for (i = 0; i < nbits; i++) { + if (!test_bit(i, (const unsigned long *) bits)) + continue; + res = r5u870_uvc_init_ctrl(vhp, unit, ci_array, i); + if (res == -ENOENT) { + r5u_dbg(vhp, R5U_INIT, + "unit %d ctrl %d not recognized", unit, i); + } + else if (res) + return res; + } + + return 0; +} + +static int r5u870_uvc_parse_vc(struct r5u870_ctx *vhp) +{ + struct usb_host_interface *aintf = + vhp->vh_parent->ud_intf->cur_altsetting; + u8 *desc; + int dlen, rlen, count; + int i, res; + + vhp->vh_ctrl_ifnum = aintf->desc.bInterfaceNumber; + + for (desc = aintf->extra, rlen = aintf->extralen; + rlen > 2; + rlen -= desc[0], desc += desc[0]) { + + dlen = desc[0]; + if (dlen < 2) + return -EINVAL; + if (desc[1] != USB_DT_CS_INTERFACE) + continue; + if (dlen < 3) + return -EINVAL; + + switch (desc[2]) { + case UVC_VC_HEADER: + count = (dlen < 12) ? 0 : desc[11]; + if (dlen < (12 + count)) { + r5u_err(vhp, "VC_HEADER too short at %d bytes", + dlen); + return -EINVAL; + } + + for (i = 0; i < count; i++) { + res = usbcam_claim_interface(vhp->vh_parent, + desc[12 + i]); + if (res) + r5u_err(vhp, "interface %d already " + "claimed", desc[12 + i]); + vhp->vh_iso_ifnum = desc[12 + i]; + + res = r5u870_uvc_parse_vs(vhp, desc[12 + i]); + if (res) + return res; + } + break; + + case UVC_VC_PROCESSING_UNIT: + count = (dlen < 8) ? 0 : desc[7]; + if (dlen < (8 + count)) { + r5u_err(vhp, "VC_PROCESSING_UNIT too short " + "at %d bytes", dlen); + return -EINVAL; + } + + res = r5u870_uvc_add_ctrls(vhp, desc[3], + r5u870_uvc_proc_ctrls, + &desc[8], desc[7] * 8); + if (res) + return res; + break; + } + } + + return 0; +} + + +static void r5u870_do_stop(struct r5u870_ctx *vhp) +{ + if (vhp->vh_parent->ud_capturing) { + vhp->vh_parent->ud_capturing = 0; + vhp->vh_ctrl_reg_enable = 0; + + usbcam_urbstream_stop(&vhp->vh_iso, 0); + usbcam_curframe_abortall(vhp->vh_parent); + + if (!vhp->vh_parent->ud_disconnected) { + usb_set_interface(r5u870_dev(vhp), + vhp->vh_iso_ifnum, + 0); + vhp->vh_cap_stop(vhp); + } + + usbcam_urbstream_cleanup(&vhp->vh_iso); + } +} + +/* + * As long as we are requested to capture, we keep the iso stream running. + * If we are explicitly requested to stop, we halt. + * If we run out of frame buffers to capture into, we halt. + */ +static void r5u870_iso_packet_done(struct usbcam_dev *udp, + struct usbcam_urbstream *usp, + const uint8_t *pktdata, + int pktlen, int pktstatus) +{ + struct r5u870_ctx *vhp = udp_r5u870(udp); + struct usbcam_curframe cf; + int res, start = 0; + + res = vhp->vh_decide_pkt(vhp, pktstatus, pktlen, + (u8 *) pktdata, &start); + switch (res) { + case -EPIPE: + if (vhp->vh_framebuf_offset) { + vhp->vh_framebuf_offset = 0; + usbcam_curframe_testpattern(udp); + usbcam_curframe_complete(udp); + } + break; + + case 0: + case -EAGAIN: + if (pktlen) { + if (usbcam_curframe_get(udp, &cf)) { + /* + * We have data, but there is no frame buffer + * queued to accept it, so we stop. + */ + r5u870_do_stop(vhp); + break; + } + + BUG_ON(pktlen - start + vhp->vh_framebuf_offset > + cf.uf_size); + + /* + * This is our one and only memcpy. + * It's kind of hard to get around doing this, as + * we cannot predict into which isochronous + * packets the camera will choose to return data. + */ + memcpy(cf.uf_base + vhp->vh_framebuf_offset, + pktdata + start, + pktlen - start); + vhp->vh_framebuf_offset += (pktlen - start); + } + + if (!res) { + vhp->vh_framebuf_offset = 0; + usbcam_curframe_complete(udp); + } + break; + + case -EIO: + r5u870_do_stop(vhp); + break; + + default: + BUG(); + } +} + +static void r5u870_iso_submit_error(struct usbcam_dev *udp, + struct usbcam_urbstream *usp, int status) +{ + struct r5u870_ctx *vhp = udp_r5u870(udp); + r5u_dbg(vhp, R5U_FRAME, "iso submit error: %d", status); + r5u870_do_stop(vhp); +} + +static struct usbcam_urbstream_ops r5u870_urb_data_ops = { + .packet_done = r5u870_iso_packet_done, + .submit_error = r5u870_iso_submit_error, +}; + + +static void r5u870_usbcam_release(struct usbcam_dev *udp) +{ + struct r5u870_ctx *vhp = udp_r5u870(udp); + int i; + + for (i = 0; i < vhp->vh_npixfmts; i++) { + if (vhp->vh_pixfmts[i].rp_restbl_alloc) + kfree(vhp->vh_pixfmts[i].rp_restbl); + } +} + + +static const struct r5u870_model *r5u870_find_model(int driver_info); +static int r5u870_usbcam_init(struct usbcam_dev *udp, + const struct usb_device_id *devid) +{ + struct r5u870_ctx *vhp; + int model_info; + int res; + + model_info = devid->driver_info; + + /* Initialize the private structure, don't use r5u_dbg() before this */ + vhp = udp_r5u870(udp); + memset(vhp, 0, sizeof(*vhp)); + vhp->vh_parent = udp; + vhp->vh_timeout = 1000; + + vhp->vh_ctrl_ifnum = -1; + vhp->vh_iso_ifnum = -1; + vhp->vh_iso_minpacket = -1; + + vhp->vh_model = r5u870_find_model(model_info); + + if (!vhp->vh_model) { + r5u_err(vhp, "no suitable model descriptor for %04x:%04x", + le16_to_cpu(r5u870_dev(vhp)->descriptor.idVendor), + le16_to_cpu(r5u870_dev(vhp)->descriptor.idProduct)); + return -ENODEV; + } + + vhp->vh_pixfmts = vhp->vh_model->rm_pixfmts; + vhp->vh_npixfmts = vhp->vh_model->rm_npixfmts; + + if (vhp->vh_model->rm_uvc) { + vhp->vh_set_fmt = r5u870_set_fmt_uvc; + vhp->vh_cap_stop = r5u870_cap_stop_uvc; + vhp->vh_decide_pkt = r5u870_decide_pkt_uvc; + } else { + vhp->vh_set_fmt = r5u870_set_fmt_wdm; + vhp->vh_cap_stop = r5u870_cap_stop_wdm; + vhp->vh_decide_pkt = r5u870_decide_pkt_wdm; + } + + r5u_info(vhp, "Detected %s", vhp->vh_model->rm_name); + snprintf(vhp->vh_parent->ud_vdev.name, + sizeof(vhp->vh_parent->ud_vdev.name), + "%s #%d", + vhp->vh_model->rm_name, vhp->vh_parent->ud_minidrv_id + 1); + + res = r5u870_dev_init(vhp); + if (res < 0) { + r5u_err(vhp, "initialization failed: %d", res); + goto out_failed; + } + + if (vhp->vh_model->rm_uvc) { + /* + * This appears to be a UVC VideoControl interface. + * Claim all of the associated VideoStreaming interfaces + * and configure the required endpoints and unit IDs + */ + res = r5u870_uvc_parse_vc(vhp); + if (res < 0) { + r5u_err(vhp, "UVC setup failed: %d", res); + goto out_failed; + } + + } else { + /* We are looking at a proprietary Ricoh interface */ + vhp->vh_iso_ifnum = + udp->ud_intf->altsetting->desc.bInterfaceNumber; + vhp->vh_iso_ep = 6; + } + + if (vhp->vh_model->rm_wdm_ctrlids) { + res = r5u870_wdm_add_ctrls(vhp, vhp->vh_model->rm_wdm_ctrlids); + if (res < 0) { + r5u_err(vhp, "Vendor control setup failed: %d", res); + goto out_failed; + } + } + + /* Configure the usbcam pixel format and control arrays */ + if (!vhp->vh_npixfmts) { + r5u_err(vhp, "No pixel formats detected"); + res = -ENODEV; + goto out_failed; + } + + udp->ud_fmt_array = &vhp->vh_pixfmts[0].base; + udp->ud_fmt_array_elem_size = sizeof(vhp->vh_pixfmts[0]); + udp->ud_fmt_array_len = vhp->vh_npixfmts; + + if (!vhp->vh_model->rm_no_first_auto_suppress) + vhp->vh_ctrl_auto_suppress = 1; + + usbcam_urbstream_init(&vhp->vh_iso, udp, vhp->vh_iso_ep); + + /* Set the default format */ + vhp->vh_fmt = &vhp->vh_pixfmts[0]; + vhp->vh_res = &vhp->vh_fmt->rp_restbl[0]; + udp->ud_format.width = vhp->vh_res->rw_width; + udp->ud_format.height = vhp->vh_res->rw_height; + udp->ud_format.pixelformat = vhp->vh_fmt->base.pixelformat; + udp->ud_format.field = V4L2_FIELD_INTERLACED; + udp->ud_format.bytesperline = udp->ud_format.width * 2; + udp->ud_format.sizeimage = (udp->ud_format.width * + udp->ud_format.height * 2); + udp->ud_format.colorspace = V4L2_COLORSPACE_SMPTE170M; + + /* Configure default values for all controls */ + res = r5u870_set_controls(vhp, 1); + if (res < 0) { + r5u_err(vhp, "set defaults failed: %d", res); + goto out_failed; + } + + return 0; + +out_failed: + r5u870_usbcam_release(udp); + return res; +} + +static void r5u870_usbcam_disconnect(struct usbcam_dev *udp) +{ + struct r5u870_ctx *vhp = udp_r5u870(udp); + r5u870_do_stop(vhp); +} + + +/* + * The power management stuff doesn't quite work yet, so we don't + * yet set the supports_autosuspend field in the ops structure. + */ + +static int r5u870_usbcam_suspend(struct usbcam_dev *udp, pm_message_t msg) +{ + struct r5u870_ctx *vhp = udp_r5u870(udp); + r5u870_do_stop(vhp); + return 0; +} + +static int r5u870_usbcam_resume(struct usbcam_dev *udp) +{ + struct r5u870_ctx *vhp = udp_r5u870(udp); + int res; + + res = r5u870_dev_init(vhp); + if (res < 0) { + r5u_err(vhp, "dev reinitialization failed: %d", res); + return res; + } + + vhp->vh_configured = 0; + + return 0; +} + +static int r5u870_try_format(struct usbcam_dev *udp, struct v4l2_pix_format *f, + const struct r5u870_pix_fmt **fmt_out, + const struct r5u870_resolution **res_out) +{ + struct r5u870_ctx *vhp = udp_r5u870(udp); + const struct r5u870_pix_fmt *fmt; + const struct r5u870_resolution *res, *restbl; + int i; + + fmt = NULL; + for (i = 0; i < vhp->vh_npixfmts; i++) { + if (vhp->vh_pixfmts[i].base.pixelformat == f->pixelformat) { + fmt = &vhp->vh_pixfmts[i]; + break; + } + } + if (fmt == NULL) + return -EINVAL; + + restbl = fmt->rp_restbl; + if (!restbl || !restbl[0].rw_width) { + r5u_err(vhp, "invalid resolution table"); + return -EINVAL; + } + + /* Find the most acceptable resolution */ + res = NULL; + for (i = 0; restbl[i].rw_width > 0; i++) { + if (!res) { + res = &restbl[i]; + } + else if (res->rw_width > f->width) { + if (restbl[i].rw_width < res->rw_width) + res = &restbl[i]; + } + else if (res->rw_height > f->height) { + if ((restbl[i].rw_width <= f->width) && + (restbl[i].rw_height < res->rw_height)) + res = &restbl[i]; + } + else if ((restbl[i].rw_width <= f->width) && + (restbl[i].rw_height <= f->height) && + ((restbl[i].rw_width > res->rw_width) || + ((restbl[i].rw_width == res->rw_width) && + (restbl[i].rw_height > res->rw_height)))) { + res = &restbl[i]; + } + } + + if ((f->width > 1) && (f->height > 1)) { + r5u_dbg(vhp, R5U_INIT, "pix_fmt width: %d height: %d", f->width, f->height); + r5u_dbg(vhp, R5U_INIT, "res width: %d height %d", res->rw_width, res->rw_height); + + if (((res->rw_width > f->width) || (res->rw_height > f->height))) { + r5u_dbg(vhp, R5U_INIT, "Bad size request. Returning -EINVAL."); + return -EINVAL; + } + } + + memset(f, 0, sizeof(*f)); + f->width = res->rw_width; + f->height = res->rw_height; + f->pixelformat = fmt->base.pixelformat; + f->bytesperline = f->width * 2; + f->sizeimage = f->width * f->height * 2; + f->field = V4L2_FIELD_INTERLACED; + f->colorspace = V4L2_COLORSPACE_SMPTE170M; + + r5u_dbg(vhp, R5U_INIT, "Settled on pixel format: %d (%s)", fmt->base.pixelformat, fmt->base.description); + + if (fmt_out) + (*fmt_out) = fmt; + if (res_out) + (*res_out) = res; + + return 0; +} + +static int r5u870_usbcam_try_format(struct usbcam_dev *udp, + struct v4l2_pix_format *f) +{ + return r5u870_try_format(udp, f, NULL, NULL); +} + +static int r5u870_usbcam_set_format(struct usbcam_dev *udp, + struct v4l2_pix_format *f) +{ + struct r5u870_ctx *vhp = udp_r5u870(udp); + const struct r5u870_pix_fmt *fmt_out; + const struct r5u870_resolution *res_out; + int res; + + res = r5u870_try_format(udp, f, &fmt_out, &res_out); + if (res) + return res; + + if ((udp->ud_format.width != f->width) || + (udp->ud_format.height != f->height) || + (udp->ud_format.pixelformat != f->pixelformat) || + (udp->ud_format.sizeimage != f->sizeimage)) + vhp->vh_configured = 0; + + udp->ud_format = *f; + vhp->vh_fmt = fmt_out; + vhp->vh_res = res_out; + return 0; +} + +static void r5u870_usbcam_cap_stop(struct usbcam_dev *udp) +{ + struct r5u870_ctx *vhp = udp_r5u870(udp); + r5u870_do_stop(vhp); +} + +static int r5u870_config_iso_ep(struct r5u870_ctx *vhp) +{ + int res; + + if (!vhp->vh_configured) { + vhp->vh_ctrl_sync = 0; + + res = usbcam_choose_altsetting(vhp->vh_parent, + vhp->vh_iso_ifnum, + usb_rcvisocpipe(r5u870_dev(vhp), + vhp->vh_iso_ep), + vhp->vh_res->rw_reqbw, + vhp->vh_iso_minpacket, -1, + &vhp->vh_act_altsetting); + if (res) { + r5u_err(vhp, "need %d B/s, no altsetting provides", + vhp->vh_res->rw_reqbw); + return res; + } + + r5u_dbg(vhp, R5U_FRAME, "using altsetting %d", + vhp->vh_act_altsetting); + } + + res = usb_set_interface(r5u870_dev(vhp), vhp->vh_iso_ifnum, + vhp->vh_act_altsetting); + if (res) { + r5u_err(vhp, "could not set altsetting: %d", res); + return res; + } + + return 0; +} + +static int r5u870_usbcam_cap_start(struct usbcam_dev *udp) +{ + struct r5u870_ctx *vhp = udp_r5u870(udp); + int res; + + if (vhp->vh_parent->ud_capturing) + return 0; + + if (!vhp->vh_model->rm_uvc) { + res = r5u870_config_iso_ep(vhp); + if (res) + return res; + } + + vhp->vh_ctrl_reg_enable = 1; + + if (!vhp->vh_configured) { + r5u_dbg(vhp, R5U_MDINTF, "setting initial control values"); + res = r5u870_set_controls(vhp, 0); + if (res) + goto out_set_idle; + + res = vhp->vh_set_fmt(vhp, vhp->vh_fmt, vhp->vh_res); + if (res) { + r5u_err(vhp, "could not configure capture: %d", res); + goto out_set_idle; + } + + if (vhp->vh_model->rm_no_ctrl_reload) + vhp->vh_ctrl_sync = 1; + } + + if (vhp->vh_model->rm_uvc) { + res = r5u870_config_iso_ep(vhp); + if (res) + goto out_set_idle; + } + + vhp->vh_configured = 1; + + res = usbcam_urbstream_config_iso(&vhp->vh_iso, + &r5u870_urb_data_ops, + 0, 0, 1, 0); + if (res < 0) { + r5u_err(vhp, "urbstream init failed: %d", res); + goto out_set_idle; + } + + udp->ud_capturing = 1; + + if (!vhp->vh_model->rm_uvc) { + res = r5u870_set_cap_state_wdm(vhp, 1); + if (res) + goto out_cleanup_urbstream; + } + + if (!vhp->vh_ctrl_sync) { + r5u_dbg(vhp, R5U_MDINTF, "reloading control values"); + + /* Reload the control values after changing res/format */ + res = r5u870_set_controls(vhp, 0); + if (res) { + r5u_err(vhp, "could not load control values: %d", res); + goto out_stop_capture; + } + + vhp->vh_ctrl_sync = 1; + } + + if (res) + goto out_failed; + + r5u_dbg(vhp, R5U_MDINTF, "starting capture"); + + vhp->vh_firstframe = 0; + vhp->vh_frame_accum = -1; + vhp->vh_framebuf_offset = 0; + vhp->vh_emptypkts = R5U870_EMPTYPKT_FRAME_DELIM - 1; + + res = usbcam_urbstream_start(&vhp->vh_iso); + if (res) + goto out_stop_capture; + + return 0; + +out_stop_capture: + (void) vhp->vh_cap_stop(vhp); +out_cleanup_urbstream: + usbcam_urbstream_cleanup(&vhp->vh_iso); +out_set_idle: + (void) usb_set_interface(r5u870_dev(vhp), vhp->vh_iso_ifnum, 0); +out_failed: + vhp->vh_ctrl_reg_enable = 0; + vhp->vh_parent->ud_capturing = 0; + return res; +} + +static struct usbcam_dev_ops r5u870_usbcam_dev_ops = { + .init = r5u870_usbcam_init, + .disconnect = r5u870_usbcam_disconnect, + .release = r5u870_usbcam_release, + .suspend = r5u870_usbcam_suspend, + .resume = r5u870_usbcam_resume, + .try_format = r5u870_usbcam_try_format, + .set_format = r5u870_usbcam_set_format, + .cap_start = r5u870_usbcam_cap_start, + .cap_stop = r5u870_usbcam_cap_stop, + + /* .supports_autosuspend = 1, */ +}; + + +/* + * Per-device hard coded vendor control lists follow + */ + +static const int r5u870_1830_ctrls[] = { + R5U870_WDM_CTRL_BRIGHTNESS, + R5U870_WDM_CTRL_CONTRAST, + R5U870_WDM_CTRL_SATURATION, + R5U870_WDM_CTRL_SHARPNESS, + R5U870_WDM_CTRL_HUE, + R5U870_WDM_CTRL_GAMMA, + R5U870_WDM_CTRL_BACKLIGHT_COMP_500, + R5U870_WDM_CTRL_WB_RED, + R5U870_WDM_CTRL_WB_GREEN, + R5U870_WDM_CTRL_WB_BLUE, + R5U870_WDM_CTRL_WB_AUTO, + R5U870_WDM_CTRL_GAIN, + R5U870_WDM_CTRL_POWERLINE, + R5U870_WDM_CTRL_VFLIP, + R5U870_WDM_CTRL_HFLIP, + R5U870_WDM_CTRL_PRIVACY, + R5U870_WDM_CTRL_NIGHTMODE, + R5U870_WDM_CTRL_LAST, +}; +static const int r5u870_1832_ctrls[] = { + R5U870_WDM_CTRL_BRIGHTNESS, + R5U870_WDM_CTRL_CONTRAST, + R5U870_WDM_CTRL_HUE, + R5U870_WDM_CTRL_SATURATION, + R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1, + R5U870_WDM_CTRL_POWERLINE, + R5U870_WDM_CTRL_VFLIP, + R5U870_WDM_CTRL_HFLIP, + R5U870_WDM_CTRL_PRIVACY, + R5U870_WDM_CTRL_NIGHTMODE, + R5U870_WDM_CTRL_LAST, +}; +static const int r5u870_1833_ctrls[] = { + R5U870_WDM_CTRL_BRIGHTNESS, + R5U870_WDM_CTRL_CONTRAST, + R5U870_WDM_CTRL_HUE, + R5U870_WDM_CTRL_SATURATION, + R5U870_WDM_CTRL_SHARPNESS, + R5U870_WDM_CTRL_GAMMA, + R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1, + R5U870_WDM_CTRL_WB_RED, + R5U870_WDM_CTRL_WB_GREEN, + R5U870_WDM_CTRL_WB_BLUE, + R5U870_WDM_CTRL_WB_AUTO, + R5U870_WDM_CTRL_POWERLINE, + R5U870_WDM_CTRL_VFLIP, + R5U870_WDM_CTRL_HFLIP, + R5U870_WDM_CTRL_PRIVACY, + R5U870_WDM_CTRL_NIGHTMODE, + R5U870_WDM_CTRL_LAST, +}; +static const int r5u870_1834_ctrls[] = { + R5U870_WDM_CTRL_BRIGHTNESS, + R5U870_WDM_CTRL_CONTRAST, + R5U870_WDM_CTRL_HUE, + R5U870_WDM_CTRL_SATURATION, + R5U870_WDM_CTRL_SHARPNESS, + R5U870_WDM_CTRL_GAMMA, + R5U870_WDM_CTRL_BACKLIGHT_COMP_X1834, + R5U870_WDM_CTRL_WB_AUTO, + R5U870_WDM_CTRL_WB_RED, + R5U870_WDM_CTRL_WB_BLUE, + R5U870_WDM_CTRL_AUTO_EXPOSURE, + R5U870_WDM_CTRL_EXPOSURE, + R5U870_WDM_CTRL_AUTO_GAIN, + R5U870_WDM_CTRL_GAIN, + R5U870_WDM_CTRL_POWERLINE, + R5U870_WDM_CTRL_VFLIP, + R5U870_WDM_CTRL_HFLIP, + R5U870_WDM_CTRL_PRIVACY, + R5U870_WDM_CTRL_NIGHTMODE, + R5U870_WDM_CTRL_LAST, +}; +static const int r5u870_1870_ctrls[] = { + R5U870_WDM_CTRL_BRIGHTNESS, + R5U870_WDM_CTRL_CONTRAST, + R5U870_WDM_CTRL_HUE, + R5U870_WDM_CTRL_SATURATION, + R5U870_WDM_CTRL_SHARPNESS, + R5U870_WDM_CTRL_GAMMA, + R5U870_WDM_CTRL_WB_AUTO, + R5U870_WDM_CTRL_WB_RED, + R5U870_WDM_CTRL_WB_BLUE, + R5U870_WDM_CTRL_AUTO_EXPOSURE, + R5U870_WDM_CTRL_EXPOSURE, + R5U870_WDM_CTRL_AUTO_GAIN, + R5U870_WDM_CTRL_GAIN, + R5U870_WDM_CTRL_POWERLINE, + R5U870_WDM_CTRL_VFLIP, + R5U870_WDM_CTRL_HFLIP, + R5U870_WDM_CTRL_PRIVACY, + R5U870_WDM_CTRL_NIGHTMODE, + R5U870_WDM_CTRL_LAST, +}; + +/* + * Even the UVC models do not express all of their controls in the UVC + * descriptor tables, and get sets of hard-coded vendor controls + */ +static const int r5u870_1835_ctrls[] = { + R5U870_WDM_CTRL_WB_RED, + R5U870_WDM_CTRL_WB_GREEN, + R5U870_WDM_CTRL_WB_BLUE, + R5U870_WDM_CTRL_WB_AUTO, + R5U870_WDM_CTRL_VFLIP, + R5U870_WDM_CTRL_HFLIP, + R5U870_WDM_CTRL_PRIVACY, + R5U870_WDM_CTRL_LAST, +}; +static const int r5u870_1810_1836_ctrls[] = { + R5U870_WDM_CTRL_WB_AUTO, + R5U870_WDM_CTRL_WB_RED, + R5U870_WDM_CTRL_WB_BLUE, + R5U870_WDM_CTRL_AUTO_EXPOSURE, + R5U870_WDM_CTRL_EXPOSURE, + R5U870_WDM_CTRL_AUTO_GAIN, + R5U870_WDM_CTRL_GAIN, + R5U870_WDM_CTRL_VFLIP, + R5U870_WDM_CTRL_HFLIP, + R5U870_WDM_CTRL_PRIVACY, + R5U870_WDM_CTRL_NIGHTMODE, + R5U870_WDM_CTRL_LAST, +}; +static const int r5u870_1810_183a_ctrls[] = { + R5U870_WDM_CTRL_WB_RED, + R5U870_WDM_CTRL_WB_GREEN, + R5U870_WDM_CTRL_WB_BLUE, + R5U870_WDM_CTRL_WB_AUTO, + R5U870_WDM_CTRL_VFLIP, + R5U870_WDM_CTRL_HFLIP, + R5U870_WDM_CTRL_PRIVACY, + R5U870_WDM_CTRL_NIGHTMODE, + R5U870_WDM_CTRL_LAST, +}; + + +/* + * Standard resolution table for non-UVC cameras, + * as UVC camera report back as to what resolutions + * they support. + * The Sony VGP-VCC2 Windows driver supports: + * 160x120, 176x144, 320x240, 352x288, 640x480 + * The HP driver also supports 1280x1024 + */ +static const struct r5u870_resolution r5u870_vga_wdm_res[] = { + { 160, 120, 1152000 }, + { 176, 144, 1520640 }, + { 320, 240, 4608000 }, + { 352, 288, 6082560 }, + { 640, 480, 18432000 }, + { } +}; +static const struct r5u870_resolution r5u870_sxga_wdm_res[] = { + { 160, 120, 1152000 }, + { 176, 144, 1520640 }, + { 320, 240, 4608000 }, + { 352, 288, 6082560 }, + { 640, 480, 18432000 }, + { 1280, 1024, 19660800 }, + { } +}; +static struct r5u870_pix_fmt r5u870_vga_wdm_pixfmts[] = { + { .base = { .description = "YUY2 4:2:2", + .pixelformat = V4L2_PIX_FMT_YUYV, + .flags = 0 }, + .rp_formatidx = 0, + .rp_restbl = r5u870_vga_wdm_res }, + + { .base = { .description = "UYVY 4:2:2", + .pixelformat = V4L2_PIX_FMT_UYVY, + .flags = 0 }, + .rp_formatidx = 1, + .rp_restbl = r5u870_vga_wdm_res }, +}; +static struct r5u870_pix_fmt r5u870_sxga_wdm_pixfmts[] = { + { .base = { .description = "YUY2 4:2:2", + .pixelformat = V4L2_PIX_FMT_YUYV, + .flags = 0 }, + .rp_formatidx = 0, + .rp_restbl = r5u870_sxga_wdm_res }, + + { .base = { .description = "UYVY 4:2:2", + .pixelformat = V4L2_PIX_FMT_UYVY, + .flags = 0 }, + .rp_formatidx = 1, + .rp_restbl = r5u870_sxga_wdm_res }, +}; + +enum { + R5U870_DI_INVALID, + R5U870_DI_VGP_VCC2_SZ, + R5U870_DI_VGP_VCC3, + R5U870_DI_VGP_VCC2_AR1, + R5U870_DI_VGP_VCC2_AR2, + R5U870_DI_VGP_VCC5, + R5U870_DI_VGP_VCC4, + R5U870_DI_VGP_VCC7, + R5U870_DI_HP_WEBCAM1K, + R5U870_DI_HP_PAVWC_WDM, + R5U870_DI_HP_PAVWC_UVC, + R5U870_DI_GENERIC_UVC, +}; + +static const struct r5u870_model r5u870_models[] = { + [R5U870_DI_VGP_VCC2_SZ] = { + .rm_name = "Sony VGP-VCC2 (VAIO SZ)", + .rm_ucode_file = "r5u870_1830.fw", + .rm_ucode_version = 0x0100, + .rm_wdm_ctrlids = r5u870_1830_ctrls, + .rm_pixfmts = r5u870_vga_wdm_pixfmts, + .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts), + }, + [R5U870_DI_VGP_VCC3] = { + .rm_name = "Sony VGP-VCC3", + .rm_ucode_file = "r5u870_1832.fw", + .rm_ucode_version = 0x0100, + .rm_wdm_ctrlids = r5u870_1832_ctrls, + .rm_pixfmts = r5u870_vga_wdm_pixfmts, + .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts), + .rm_no_ctrl_reload = 1, + }, + [R5U870_DI_VGP_VCC2_AR1] = { + .rm_name = "Sony VGP-VCC2 (VAIO AR1)", + .rm_ucode_file = "r5u870_1833.fw", + .rm_ucode_version = 0x0100, + .rm_wdm_ctrlids = r5u870_1833_ctrls, + .rm_pixfmts = r5u870_vga_wdm_pixfmts, + .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts), + }, + [R5U870_DI_VGP_VCC2_AR2] = { + .rm_name = "Sony VGP-VCC2 (VAIO AR)", + .rm_ucode_file = "r5u870_1834.fw", + .rm_ucode_version = 0x0111, + .rm_wdm_ctrlids = r5u870_1834_ctrls, + .rm_pixfmts = r5u870_vga_wdm_pixfmts, + .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts), + }, + [R5U870_DI_VGP_VCC5] = { + .rm_name = "Sony VGP-VCC5", + .rm_ucode_file = "r5u870_1835.fw", + .rm_ucode_version = 0x0107, + .rm_wdm_ctrlids = r5u870_1835_ctrls, + .rm_uvc = 1, + }, + [R5U870_DI_VGP_VCC4] = { + .rm_name = "Sony VGP-VCC4", + .rm_ucode_file = "r5u870_1836.fw", + .rm_ucode_version = 0x0115, + .rm_wdm_ctrlids = r5u870_1810_1836_ctrls, + .rm_uvc = 1, + }, + [R5U870_DI_VGP_VCC7] = { + .rm_name = "Sony VGP-VCC7 (VAIO SZ)", + .rm_ucode_file = "r5u870_183a.fw", + .rm_ucode_version = 0x0111, + .rm_wdm_ctrlids = r5u870_1810_183a_ctrls, + .rm_uvc = 1, + }, + [R5U870_DI_HP_WEBCAM1K] = { + .rm_name = "HP Webcam 1000", + .rm_ucode_file = "r5u870_1870_1.fw", + .rm_ucode_version = 0x0100, + .rm_wdm_ctrlids = r5u870_1870_ctrls, + .rm_pixfmts = r5u870_vga_wdm_pixfmts, + .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts), + }, + [R5U870_DI_HP_PAVWC_WDM] = { + .rm_name = "HP Pavilion Webcam", + .rm_ucode_file = "r5u870_1870.fw", + .rm_ucode_version = 0x0112, + .rm_wdm_ctrlids = r5u870_1870_ctrls, + .rm_pixfmts = r5u870_sxga_wdm_pixfmts, + .rm_npixfmts = ARRAY_SIZE(r5u870_sxga_wdm_pixfmts), + .rm_no_ctrl_reload = 1, + }, + [R5U870_DI_HP_PAVWC_UVC] = { + .rm_name = "HP Pavilion Webcam", + .rm_ucode_file = "r5u870_1810.fw", + .rm_ucode_version = 0x0115, + .rm_wdm_ctrlids = r5u870_1810_1836_ctrls, + .rm_uvc = 1, + .rm_no_ctrl_reload = 1, + }, + [R5U870_DI_GENERIC_UVC] = { + .rm_name = "Generic UVC Webcam", + .rm_uvc = 1, + }, +}; + +/* + * Someone clever at HP decid ed to use 05ca:1870 for two distinct devices. + * The Pavilion dv1xxx machines all seem to have the less common of the + * two. There is no known, working method to distinguish the devices + * using USB commands only. We resort to reading the model number out + * of DMI. + */ +static int dv1000 = 2; +module_param(dv1000, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(dv1000, "HP dv1000 detect mode (0=no,1=yes,2=DMI)"); + +static int r5u870_check_hp_dv1000(void) +{ + const char *prod_name; + if (!dv1000) + return 0; + if (dv1000 == 1) + return 1; + prod_name = dmi_get_system_info(DMI_PRODUCT_NAME); + if (!prod_name) + printk(KERN_INFO "r5u870: No DMI model found\n"); + else { + printk(KERN_INFO "r5u870: Found DMI model: \"%s\"\n", + prod_name); + if (!strncmp(prod_name, "HP Pavilion dv1000", 18) && + !isdigit(prod_name[18])) + return 1; + } + return 0; +} + +static const struct r5u870_model *r5u870_find_model(int driver_info) +{ + if (driver_info == R5U870_DI_HP_PAVWC_WDM) { + if (r5u870_check_hp_dv1000()) + driver_info = R5U870_DI_HP_WEBCAM1K; + } + if ((driver_info <= R5U870_DI_INVALID) || + (driver_info >= ARRAY_SIZE(r5u870_models))) + return NULL; + if (!r5u870_models[driver_info].rm_name) + return NULL; + return &r5u870_models[driver_info]; +} + + +#define R5U870_DEVICE_UVC(VID, PID, DINFO) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE \ + | USB_DEVICE_ID_MATCH_INT_INFO, \ + .idVendor = (VID), \ + .idProduct = (PID), \ + .bInterfaceClass = USB_CLASS_VIDEO, \ + .bInterfaceSubClass = 1, \ + .bInterfaceProtocol = 0, \ + .driver_info = (DINFO) + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x05CA, 0x1830), .driver_info = R5U870_DI_VGP_VCC2_SZ }, + { USB_DEVICE(0x05CA, 0x1832), .driver_info = R5U870_DI_VGP_VCC3 }, + { USB_DEVICE(0x05CA, 0x1833), .driver_info = R5U870_DI_VGP_VCC2_AR1 }, + { USB_DEVICE(0x05CA, 0x1834), .driver_info = R5U870_DI_VGP_VCC2_AR2 }, + { USB_DEVICE(0x05CA, 0x1870), .driver_info = R5U870_DI_HP_PAVWC_WDM }, + + { R5U870_DEVICE_UVC(0x05CA, 0x1810, R5U870_DI_HP_PAVWC_UVC) }, + { R5U870_DEVICE_UVC(0x05CA, 0x1835, R5U870_DI_VGP_VCC5) }, + { R5U870_DEVICE_UVC(0x05CA, 0x1836, R5U870_DI_VGP_VCC4) }, + { R5U870_DEVICE_UVC(0x05CA, 0x183a, R5U870_DI_VGP_VCC7) }, + { }, +}; + + +DEFINE_USBCAM_MINIDRV_MODULE(R5U870_VERSION, R5U870_VERSION_EXTRA, + &r5u870_usbcam_dev_ops, + sizeof(struct r5u870_ctx), + id_table) + +MODULE_DEVICE_TABLE(usb, id_table); +MODULE_DESCRIPTION("Driver for Ricoh R5U870-based Webcams"); +MODULE_AUTHOR("Sam Revitch <samr7@cs.washington.edu>"); +MODULE_LICENSE("GPL"); diff --git a/r5u870_md.c b/r5u870_md.c @@ -1,3061 +0,0 @@ -/* - * Driver for Ricoh R5U870-based Custom OEM Webcams - * Copyright (C) 2007 Sam Revitch <samr7@cs.washington.edu> - * Copyright (c) 2008 Alexander Hixon <hixon.alexander@mediati.org> - * - * Check out README for additional credits. - * - * This driver 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 driver 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 driver; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -/* - * This driver supports certain custom OEM webcams based on the Ricoh - * R5U870 / Micro Vision M25U870 controller chip. These tend to be - * built in to certain models of laptop computers and claim USB vendor - * ID 0x05CA (Ricoh). - * - * All Ricoh webcams support common microcode upload, reset, and query - * version commands. - * - * Each distinct piece of hardware requires a different microcode blob, - * which is uploaded to the device the first time the device is probed - * after power-on. The microcode blob is tailored to the attached - * image sensor and the desired type of USB interface. Some microcode - * blobs are created to work with the Ricoh proprietary USB interface. - * Others behave as UVC devices, but use the common Ricoh vendor - * commands to set certain controls. Some devices, in particular the - * 1810 and 1870 HP Pavilion Webcam devices, appear to be identical in - * hardware, and differ only by USB descriptor tables, and the - * microcode blobs implement the different USB personalities. - * - * This driver supports basic UVC descriptor table parsing to determine - * available controls and resolutions, and enough UVC commands to - * support the UVC interface style of the Ricoh webcams. - */ - -#define CONFIG_USBCAM_DEBUG -#define USBCAM_DEBUG_DEFAULT 0 - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/init.h> -#include <linux/usb.h> -#include <linux/firmware.h> -#include <linux/dmi.h> -#include <linux/ctype.h> -#include "usbcam.h" - - -#define USBCAM_DBG_R5U_INIT (USBCAM_DBGBIT_MD_START + 0) -#define USBCAM_DBG_R5U_FRAME (USBCAM_DBGBIT_MD_START + 1) -#define USBCAM_DBG_R5U_MDINTF (USBCAM_DBGBIT_MD_START + 2) -#define USBCAM_DBG_R5U_CTRL (USBCAM_DBGBIT_MD_START + 3) - - -#define r5u_info(RV, FMT...) usbcam_info((RV)->vh_parent, FMT) -#define r5u_err(RV, FMT...) usbcam_err((RV)->vh_parent, FMT) -#define r5u_dbg(RV, SUBSYS, FMT...) usbcam_dbg((RV)->vh_parent, SUBSYS, FMT) - -#define R5U870_VERSION KERNEL_VERSION(0,10,1) -#define R5U870_VERSION_EXTRA "halex" - - -struct r5u870_resolution { - int rw_width; - int rw_height; - int rw_reqbw; - int rw_interval; - int rw_frameidx; -}; - -struct r5u870_pix_fmt { - struct usbcam_pix_fmt base; - int rp_formatidx; - const struct r5u870_resolution *rp_restbl; - int rp_restbl_alloc; -}; - -struct r5u870_ctrl { - struct usbcam_ctrl base; - int reg; - u8 unit; - u8 info; - u8 size; - u8 is_auto; - int val_offset; - int auto_offset; -}; - -struct r5u870_model { - char *rm_name; - const char *rm_ucode_file; - const struct r5u870_resolution *rm_res; - const int *rm_wdm_ctrlids; - const struct r5u870_pix_fmt *rm_pixfmts; - int rm_npixfmts; - u16 rm_ucode_version; - unsigned int rm_uvc : 1, - rm_no_ctrl_reload : 1; -}; - -struct r5u870_test { - struct completion rt_completion; - int rt_status; -}; - -struct r5u870_ctx { - struct usbcam_dev *vh_parent; - struct usbcam_isostream vh_iso; - - const struct r5u870_model *vh_model; - const struct r5u870_pix_fmt *vh_pixfmts; - int vh_npixfmts; - const struct r5u870_ctrl *vh_ctrls; - int vh_nctrls; - - int vh_dyn_pixfmts : 1, - vh_dyn_ctrls : 1, - vh_configured : 1, - vh_ctrl_reg_enable : 1, - vh_ctrl_sync : 1; - - const struct r5u870_pix_fmt *vh_fmt; - const struct r5u870_resolution *vh_res; - - int vh_timeout; - int vh_firstframe; - int vh_emptypkts; - int vh_frame_accum; - - int vh_framebuf_offset; - - int vh_ctrl_ifnum; - int vh_iso_ep; - int vh_iso_ifnum; - int vh_iso_minpacket; - int vh_act_altsetting; - - struct r5u870_test *vh_test_info; - - int (*vh_set_fmt)(struct r5u870_ctx *, - const struct r5u870_pix_fmt *fmtp, - const struct r5u870_resolution *resp); - int (*vh_cap_stop)(struct r5u870_ctx *); - int (*vh_decide_pkt)(struct r5u870_ctx *, int st, int len, - const u8 *pktdata, int *start); - - /* Settings */ - int vh_brightness; - int vh_contrast; - int vh_hue; - int vh_saturation; - int vh_sharpness; - int vh_gamma; - int vh_backlight_comp; - int vh_frame_rate; - int vh_auto_wb; - int vh_wb_red; - int vh_wb_green; - int vh_wb_blue; - int vh_exposure; - int vh_aec; - int vh_gain; - int vh_agc; - int vh_powerline; - int vh_vflip; - int vh_hflip; - int vh_privacy; - int vh_nightmode; -}; - -#define udp_r5u870(UDP) ((struct r5u870_ctx *)((UDP)->ud_minidrv_data)) -#define r5u870_dev(RV) ((RV)->vh_parent->ud_dev) - - -/* - * Controls - * - * For this driver, there is only one get function, which retrieves the - * value for the control stored in the r5u870_ctx structure. Each style - * of control -- WDM/UVC -- have separate set functions. - * - * There are query functions for setting the disabled flag on manual - * white balance controls when auto white balance is enabled. - */ - -enum { - V4L2_CID_R5U870_SHARPNESS = (V4L2_CID_PRIVATE_BASE + 0), - V4L2_CID_R5U870_GREEN_BALANCE, - V4L2_CID_R5U870_AUTOEXPOSURE, - V4L2_CID_R5U870_POWERLINE, - V4L2_CID_R5U870_BACKLIGHT, - V4L2_CID_R5U870_PRIVACY, - V4L2_CID_R5U870_NIGHT_MODE, -}; - -static int r5u870_get_ctrl(struct usbcam_dev *udp, - const struct usbcam_ctrl *basep, - struct v4l2_control *c) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - struct r5u870_ctrl *ctlp = container_of(basep, struct r5u870_ctrl, - base); - - c->value = *(int *) (((char *) vhp) + ctlp->val_offset); - return 0; -} - -static int r5u870_query_ctrl(struct usbcam_dev *udp, - const struct usbcam_ctrl *basep, - struct v4l2_queryctrl *c) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - struct r5u870_ctrl *ctlp = container_of(basep, struct r5u870_ctrl, - base); - int auto_ctrl = 0; - - if (ctlp->auto_offset) { - auto_ctrl = *(int *) (((char *) vhp) + ctlp->auto_offset); - if (auto_ctrl) - c->flags |= V4L2_CTRL_FLAG_INACTIVE; - } - return 0; -} - -static int r5u870_set_controls(struct r5u870_ctx *vhp, int dflt) -{ - const struct r5u870_ctrl *ctrlp; - struct v4l2_control cv; - int i, res; - - res = 0; - ctrlp = vhp->vh_ctrls; - for (i = 0; i < vhp->vh_nctrls; i++) { - cv.id = ctrlp[i].base.ctrl.id; - - if (dflt) - cv.value = ctrlp[i].base.ctrl.default_value; - else - cv.value = *(int *) (((char *) vhp) + - ctrlp[i].val_offset); - - res = ctrlp[i].base.set_fn(vhp->vh_parent, - &ctrlp[i].base, - &cv); - if (res) - break; - } - - return res; -} - - -/* - * Microcode management and device initialization functions follow - */ - -static int r5u870_set_gen_reg(struct r5u870_ctx *vhp, - int cmd, int reg, int val) -{ - int res; - res = usb_control_msg(r5u870_dev(vhp), - usb_sndctrlpipe(r5u870_dev(vhp), 0), - cmd, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - val, - reg, - NULL, 0, - vhp->vh_timeout); - if (res < 0) { - r5u_err(vhp, "set_gen_reg %04x/%04x/%04x failed: %d", - cmd, reg, val, res); - return res; - } - return 0; -} - -/* - * Send the power-up init sequence, in case it is needed. - */ -static int r5u870_microcode_upload(struct r5u870_ctx *vhp) -{ - const struct firmware *fws; - char *pgbuf; - const u8 *dptr; - int tolerance = 3; - int i, rem, addr, len, res = 0; - - pgbuf = (char *) __get_free_page(GFP_KERNEL); - if (!pgbuf) - return -ENOMEM; - - r5u_dbg(vhp, R5U_INIT, "loading microcode file \"%s\"", - vhp->vh_model->rm_ucode_file); - - res = request_firmware(&fws, - vhp->vh_model->rm_ucode_file, - &vhp->vh_parent->ud_dev->dev); - - if (res) { - r5u_err(vhp, "Microcode file \"%s\" is missing", - vhp->vh_model->rm_ucode_file); - free_page((unsigned long)pgbuf); - return res; - } - - i = 0; - dptr = fws->data; - rem = fws->size; - while (rem) { - if (rem < 3) { - r5u_err(vhp, "Microcode file msg %d is incomplete", i); - res = -EINVAL; - break; - } - - len = dptr[0]; - addr = dptr[1] | (dptr[2] << 8); - dptr += 3; - rem -= 3; - - if ((rem < len) || (len > 64)) { - r5u_err(vhp, "Microcode file msg %d has bad length %d", - i, len); - res = -EINVAL; - break; - } - - /* - * The USB stack has issues with the initseq data if - * initseq points into the vmalloc arena. This is - * the case for microcode embedded in a module, or - * data loaded by request_firmware(). - * - * As a workaround, we memcpy() into a kmalloc page. - */ - memcpy(pgbuf, dptr, len); - dptr += len; - rem -= len; - - retry: - res = usb_control_msg(r5u870_dev(vhp), - usb_sndctrlpipe(r5u870_dev(vhp), 0), - 0xa0, - USB_DIR_OUT | USB_TYPE_VENDOR | - USB_RECIP_DEVICE, - addr, 0, pgbuf, len, vhp->vh_timeout); - - if (res < 0) { - if (tolerance--) - goto retry; - r5u_err(vhp, "command a0[%d] failed: %d", - i, res); - break; - } - if (res != len) { - r5u_err(vhp, "command a0[%d] failed: %d (exp %d)", - i, res, len); - res = -EIO; - break; - } - - i++; - } - - release_firmware(fws); - free_page((unsigned long) pgbuf); - return res; -} - -static int r5u870_microcode_get_state(struct r5u870_ctx *vhp) -{ - char buf[1]; - int res; - r5u_dbg(vhp, R5U_INIT, "requesting microcode state"); - res = usb_control_msg(r5u870_dev(vhp), - usb_rcvctrlpipe(r5u870_dev(vhp), 0), - 0xa4, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0, - 0, - buf, 1, - vhp->vh_timeout); - if ((res != 1) || ((buf[0] != 0) && (buf[0] != 1))) { - r5u_err(vhp, "command 0xa4 failed: %d", res); - return res < 0 ? res : -EIO; - } - - r5u_dbg(vhp, R5U_INIT, "camera reports %s microcode state", - buf[0] ? "positive" : "negative"); - - return (buf[0] == 0) ? -ENOENT : 0; -} - -static int r5u870_microcode_get_ver(struct r5u870_ctx *vhp, int *verp) -{ - char buf[2]; - int res; - - r5u_dbg(vhp, R5U_INIT, "requesting microcode version"); - res = usb_control_msg(r5u870_dev(vhp), - usb_rcvctrlpipe(r5u870_dev(vhp), 0), - 0xc3, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0, - 0x0e, - buf, 2, - vhp->vh_timeout); - if (res != 2) { - r5u_err(vhp, "command 0xa3 failed: %d", res); - return res < 0 ? res : -EIO; - } - - res = le16_to_cpup((__le16 *) buf); - r5u_dbg(vhp, R5U_INIT, "camera reports version %04x", res); - *verp = res; - return 0; -} - -static int r5u870_microcode_enable(struct r5u870_ctx *vhp) -{ - char buf[1]; - int res; - - r5u_dbg(vhp, R5U_INIT, "enabling microcode"); - buf[0] = 1; - res = usb_control_msg(r5u870_dev(vhp), - usb_sndctrlpipe(r5u870_dev(vhp), 0), - 0xa1, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0, - 0, - buf, 1, - vhp->vh_timeout); - if (res != 1) { - r5u_err(vhp, "command 0xa1 failed: %d", res); - return res < 0 ? res : -EIO; - } - - return 0; -} - -static int r5u870_microcode_reset(struct r5u870_ctx *vhp) -{ - int res; - r5u_dbg(vhp, R5U_INIT, "sending microcode reset command"); - msleep(100); /* The Windows driver waits 1sec */ - res = r5u870_set_gen_reg(vhp, 0xa6, 0, 0); - if (!res) - msleep(200); - return res; -} - -static int __attribute__((unused)) r5u870_dev_test_234(struct r5u870_ctx *vhp) -{ - char buf[10]; - int res, tries = 0; - - while (1) { - buf[0] = 2; - buf[1] = 3; - buf[2] = 4; - res = usb_control_msg(r5u870_dev(vhp), - usb_sndctrlpipe(r5u870_dev(vhp), 0), - 0xd0, - USB_DIR_OUT |USB_TYPE_VENDOR | - USB_RECIP_DEVICE, - 0, - 0, - buf, 3, - vhp->vh_timeout); - if ((res < 0) || (res != 3)) { - r5u_err(vhp, "command d0[set] failed: %d", res); - /* return res < 0 ? res : -EIO; */ - } - - buf[0] = 0; - buf[1] = 0; - buf[2] = 0; - res = usb_control_msg(r5u870_dev(vhp), - usb_rcvctrlpipe(r5u870_dev(vhp), 0), - 0xd0, - USB_DIR_IN | USB_TYPE_VENDOR | - USB_RECIP_DEVICE, - 0, - 1, - buf, - sizeof(buf), - vhp->vh_timeout); - if ((res < 0) || (res != 3)) { - r5u_err(vhp, "command d0[get] failed: %d", res); - /* return res < 0 ? res : -EIO; */ - } - - if ((buf[0] == 2) && (buf[1] == 3) && (buf[2] == 4)) { - break; - } - - tries++; - if (tries > 1) { - r5u_err(vhp, "command d0: too many retries"); - /* return -EIO; */ - break; - } - } - - return 0; -} - -/* - * Initialize the camera after it is detected. - */ -static int r5u870_dev_init(struct r5u870_ctx *vhp) -{ - int mcver; - int res; - - if (!vhp->vh_model->rm_ucode_file) - return 0; - - res = r5u870_microcode_get_state(vhp); - if (res && (res != -ENOENT)) - return res; - - if (!res) { - res = r5u870_microcode_get_ver(vhp, &mcver); - if (res) - return res; - - if (mcver != vhp->vh_model->rm_ucode_version) { - res = r5u870_microcode_reset(vhp); - if (res) - return res; - res = -ENOENT; - } - } - - - if (res == -ENOENT) { - res = r5u870_microcode_upload(vhp); - if (res < 0) - return res; - - res = r5u870_microcode_enable(vhp); - if (res) - return res; - - res = r5u870_microcode_get_ver(vhp, &mcver); - if (res) - return res; - } - - if (mcver != vhp->vh_model->rm_ucode_version) - r5u_err(vhp, "Unexpected microcode version " - "(exp:%04x got:%04x)", - vhp->vh_model->rm_ucode_version, mcver); - - /* (void) r5u870_dev_test_234(vhp); */ - - /* Halt capture in case it's running (broken driver?) */ - res = vhp->vh_cap_stop(vhp); - if (res < 0) - return res; - - return 0; -} - -/* - * WDM Device Registers are listed below. - * - * To set: use r5u870_set_reg_wdm(). - * - * No information is given about how to retrieve values from these - * registers. - * - * Note that some register IDs are overloaded. The Sony cam drivers - * seem to use 0x36 for backlight compensation and 0x37 for gain, - * whereas the HP drivers use 0x36 for color enable and 0x37 for frame - * rate. - */ -enum { - /* Brightness: [0,127] D:63 */ - R5U870_REG_BRIGHTNESS = 0x02, - - /* Contrast: [0,127] D:63 */ - R5U870_REG_CONTRAST = 0x04, - - /* Hue: [-180,180] D:0 (16-bit 2's complement) */ - R5U870_REG_HUE = 0x34, - - /* Saturation: [0,127] D:63 */ - R5U870_REG_SATURATION = 0x05, - - /* Sharpness: [0,127] D:63 */ - R5U870_REG_SHARPNESS = 0x03, - - /* Gamma correction: [1,500] D:100 */ - R5U870_REG_GAMMA = 0x35, - - /* Registers with unknown usage */ - R5U870_REG_COLOR_ENABLE = 0x36, - - /* White balance: [0,127] D:63 */ - R5U870_REG_WHITE_BALANCE = 0x1, - - /* Frame Rate: D:30 */ - R5U870_REG_FRAME_RATE = 0x37, - - /* Registers with unknown usage */ - R5U870_REG_BRIGHTNESS_EX = 0x20, - R5U870_REG_CONTRAST_EX = 0x21, - R5U870_REG_HUE_EX = 0x22, - R5U870_REG_SATURATION_EX = 0x23, - R5U870_REG_SHARPNESS_EX = 0x24, - R5U870_REG_GAMMA_EX = 0x25, - - /* White balance red value: [0,255] D:127 */ - R5U870_REG_WB_RED_EX = 0x26, - - /* White balance green value: [0,255] D:127 */ - R5U870_REG_WB_GREEN_EX = 0x27, - - /* White balance blue value: [0,255] D:127 */ - R5U870_REG_WB_BLUE_EX = 0x28, - - /* Auto white balance: [0,1] D:1 */ - R5U870_REG_WB_AUTO_EX = 0x29, - - /* Exposure Control: [0,255] D:255 */ - R5U870_REG_EXPOSURE_EX = 0x2a, - - /* Auto Exposure Control: [0,1] D:1 */ - R5U870_REG_AEC_EX = 0x2b, - - /* Gain: [0,127] D:63 */ - R5U870_REG_GAIN_EX = 0x2c, - - /* Auto Gain: [0,1] D:0 */ - R5U870_REG_AGC_EX = 0x2d, - - /* Light source flicker compensation: see R5U870_POWERLINE */ - R5U870_REG_POWERLINE_EX = 0x2e, - - /* Registers with unknown usage */ - R5U870_REG_SCENE_EX = 0x2f, - - /* Vertical flip: [0,1] D:0 */ - R5U870_REG_VFLIP_EX = 0x30, - - /* Horizontal flip: [0,1] D:0 */ - R5U870_REG_HFLIP_EX = 0x31, - - /* Blank image: [0,1] D:0 */ - R5U870_REG_PRIVACY_EX = 0x32, - - /* Night mode: [0,1] D:0 */ - R5U870_REG_NIGHT_MODE_EX = 0x33, - - /* Backlight compensation: [0,500] D:1 */ - R5U870_REG_BACKLIGHT_COMP = 0x36, - - /* Registers with unknown usage */ - R5U870_REG_GAIN = 0x37, - - /* Backlight compensation for 1834 device: [0,2] D:1 */ - R5U870_REG_BACKLIGHT_COMP_2 = 0x39, - - - /* Values for R5U870_REG_POWERLINE flicker compensation */ - R5U870_POWERLINE_OFF = 0, - R5U870_POWERLINE_50HZ = 1, - R5U870_POWERLINE_60HZ = 2, - - /* Number of empty packets between frames */ - R5U870_EMPTYPKT_FRAME_DELIM = 10, - - /* Number of empty packets before declaring the device dead (.5sec) */ - R5U870_EMPTYPKT_GIVE_UP = 4000, -}; - -static int r5u870_set_reg_wdm(struct r5u870_ctx *vhp, int reg, int val) -{ - return r5u870_set_gen_reg(vhp, 0xc2, reg, val); -} - -/* - * Set the frame size and data format. - * Do not call this function with the isochronous stream active. - */ -static int r5u870_set_fmt_wdm(struct r5u870_ctx *vhp, - const struct r5u870_pix_fmt *fmtp, - const struct r5u870_resolution *resp) -{ - int res; - - msleep(1); - res = r5u870_set_gen_reg(vhp, 0xc5, 2, fmtp->rp_formatidx); - if (res) - return res; - msleep(1); - res = r5u870_set_gen_reg(vhp, 0xc5, 0, resp->rw_width); - if (res) - return res; - msleep(1); - res = r5u870_set_gen_reg(vhp, 0xc5, 1, resp->rw_height); - if (res) - return res; - msleep(5); - return 0; -} - - -/* - * Turn frame grabbing on or off (WDM). - * This will also turn on or off the LED. - */ -static int r5u870_set_cap_state_wdm(struct r5u870_ctx *vhp, int val) -{ - return r5u870_set_gen_reg(vhp, 0xc4, val, 0); -} - -static int r5u870_cap_stop_wdm(struct r5u870_ctx *vhp) -{ - return r5u870_set_cap_state_wdm(vhp, 0); -} - -/* - * r5u870_decide_pkt_wdm - * - * Based on the size of an isochronous data packet, this function - * decides whether to copy the packet into the frame buffer and possibly - * complete the frame, or to discard both the packet and the frame. - * - * Returns: - * 0 Frame is done - * -EAGAIN Append packet to frame, frame is not done - * -EPIPE Discard frame and packet - * -EIO The device is nonresponsive, abort - */ -static int r5u870_decide_pkt_wdm(struct r5u870_ctx *vhp, int pktstatus, - int pktlen, const u8 *pktdata, int *start) -{ - int ret = -EAGAIN; - - *start = 0; - - if (pktstatus) { - /* Abort current frame */ - r5u_dbg(vhp, R5U_FRAME, "frame abort: packet status %d", - pktstatus); - vhp->vh_frame_accum = -1; - vhp->vh_emptypkts = 0; - ret = -EPIPE; - - } else if (!pktlen) { - if (++vhp->vh_emptypkts == R5U870_EMPTYPKT_FRAME_DELIM) { - if (vhp->vh_frame_accum == -1) { - /* Frame was previously aborted */ - ret = -EPIPE; - } else if (vhp->vh_frame_accum == - vhp->vh_parent->ud_format.sizeimage) { - /* Complete frame */ - ret = 0; - } else { - /* Not enough data in frame sequence */ - r5u_dbg(vhp, R5U_FRAME, "frame abort: " - "Frame seq too short (exp:%d got:%d)", - vhp->vh_parent->ud_format.sizeimage, - vhp->vh_frame_accum); - ret = -EPIPE; - } - - if (!vhp->vh_firstframe) { - /* Always reject the first frame */ - vhp->vh_firstframe = 1; - vhp->vh_frame_accum = -1; - ret = -EPIPE; - } else { - vhp->vh_frame_accum = 0; - } - } - - else if (vhp->vh_emptypkts >= R5U870_EMPTYPKT_GIVE_UP) { - r5u_dbg(vhp, R5U_FRAME, "%d empty packets, giving up", - vhp->vh_emptypkts); - ret = -EIO; - } - - } else { - vhp->vh_emptypkts = 0; - if (vhp->vh_frame_accum == -1) { - /* Frame was previously aborted */ - ret = -EPIPE; - } else if ((vhp->vh_frame_accum + pktlen) <= - vhp->vh_parent->ud_format.sizeimage) { - /* Append this data */ - vhp->vh_frame_accum += pktlen; - } else { - /* Oversized frame, abort */ - r5u_dbg(vhp, R5U_FRAME, "frame abort: " - "Frame seq too long"); - vhp->vh_frame_accum = -1; - ret = -EPIPE; - } - } - - return ret; -} - -static int r5u870_set_manual_ctrls_wdm(struct r5u870_ctx *vhp, int auto_offset) -{ - const struct r5u870_ctrl *ctrlp; - int i, val, res; - - res = 0; - ctrlp = vhp->vh_ctrls; - for (i = 0; i < vhp->vh_nctrls; i++) { - if (ctrlp[i].auto_offset != auto_offset) - continue; - if (!vhp->vh_ctrl_reg_enable) { - vhp->vh_ctrl_sync = 0; - continue; - } - - val = *(int *) (((char *) vhp) + ctrlp[i].val_offset); - - r5u_dbg(vhp, R5U_CTRL, "control %s/wdm %02x <= %d [manual]", - ctrlp[i].base.ctrl.name, ctrlp[i].reg, val); - res = r5u870_set_reg_wdm(vhp, ctrlp[i].reg, val); - if (res) - break; - } - return res; -} - -static int r5u870_set_ctrl_wdm(struct usbcam_dev *udp, - const struct usbcam_ctrl *basep, - const struct v4l2_control *c) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - struct r5u870_ctrl *ctlp = container_of(basep, struct r5u870_ctrl, - base); - int res = 0; - int auto_ctrl = 0; - - if (ctlp->auto_offset) - auto_ctrl = *(int *) (((char *) vhp) + ctlp->auto_offset); - - if (auto_ctrl) { - r5u_dbg(vhp, R5U_CTRL, "control %s <= %d [auto suppress]", - ctlp->base.ctrl.name, c->value); - - } else if (!vhp->vh_ctrl_reg_enable) { - r5u_dbg(vhp, R5U_CTRL, "control %s <= %d [capture off]", - ctlp->base.ctrl.name, c->value); - vhp->vh_ctrl_sync = 0; - - } else { - r5u_dbg(vhp, R5U_CTRL, "control %s/wdm %02x <= %d", - ctlp->base.ctrl.name, ctlp->reg, c->value); - res = r5u870_set_reg_wdm(vhp, ctlp->reg, c->value); - if (res) - return res; - } - - *(int *) (((char *) vhp) + ctlp->val_offset) = c->value; - - if (ctlp->is_auto && !c->value) - res = r5u870_set_manual_ctrls_wdm(vhp, ctlp->val_offset); - - return res; -} - -/* - * WDM control templates follow - * - * Each device has an array of integer control IDs, which refer to an - * element in the control template array. When the device is detected, - * we build the control array out of the element list and the templates, - * by r5u870_wdm_add_ctrls(). - */ - -enum { - R5U870_WDM_CTRL_BRIGHTNESS, - R5U870_WDM_CTRL_CONTRAST, - R5U870_WDM_CTRL_SATURATION, - R5U870_WDM_CTRL_SHARPNESS, - R5U870_WDM_CTRL_HUE, - R5U870_WDM_CTRL_GAMMA, - R5U870_WDM_CTRL_BACKLIGHT_COMP_500, - R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1, - R5U870_WDM_CTRL_BACKLIGHT_COMP_X1834, - R5U870_WDM_CTRL_WB_RED, - R5U870_WDM_CTRL_WB_GREEN, - R5U870_WDM_CTRL_WB_BLUE, - R5U870_WDM_CTRL_WB_AUTO, - R5U870_WDM_CTRL_AUTO_EXPOSURE, - R5U870_WDM_CTRL_EXPOSURE, - R5U870_WDM_CTRL_AUTO_GAIN, - R5U870_WDM_CTRL_GAIN, - R5U870_WDM_CTRL_POWERLINE, - R5U870_WDM_CTRL_VFLIP, - R5U870_WDM_CTRL_HFLIP, - R5U870_WDM_CTRL_PRIVACY, - R5U870_WDM_CTRL_NIGHTMODE, - - R5U870_WDM_CTRL_LAST = 0xffff, -}; - -static const char *r5u870_powerline_names[] = { "Off", "50Hz", "60Hz" }; - -static struct r5u870_ctrl r5u870_wdm_ctrls[] = { - - [R5U870_WDM_CTRL_BRIGHTNESS] = { - .base = { .ctrl = { .id = V4L2_CID_BRIGHTNESS, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Brightness", - .minimum = 0, - .maximum = 127, - .step = 1, - .default_value = 63, - .flags = V4L2_CTRL_FLAG_SLIDER }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_BRIGHTNESS, - .val_offset = offsetof(struct r5u870_ctx, vh_brightness) - }, - [R5U870_WDM_CTRL_CONTRAST] = { - .base = { .ctrl = { .id = V4L2_CID_CONTRAST, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Contrast", - .minimum = 0, - .maximum = 127, - .step = 1, - .default_value = 63, - .flags = V4L2_CTRL_FLAG_SLIDER }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_CONTRAST, - .val_offset = offsetof(struct r5u870_ctx, vh_contrast) - }, - [R5U870_WDM_CTRL_SATURATION] = { - .base = { .ctrl = { .id = V4L2_CID_SATURATION, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Saturation", - .minimum = 0, - .maximum = 127, - .step = 1, - .default_value = 63, - .flags = V4L2_CTRL_FLAG_SLIDER }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_SATURATION, - .val_offset = offsetof(struct r5u870_ctx, vh_saturation) - }, - - [R5U870_WDM_CTRL_SHARPNESS] = { - .base = { .ctrl = { .id = V4L2_CID_R5U870_SHARPNESS, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Sharpness", - .minimum = 0, - .maximum = 127, - .step = 1, - .default_value = 63, - .flags = V4L2_CTRL_FLAG_SLIDER }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_SHARPNESS, - .val_offset = offsetof(struct r5u870_ctx, vh_sharpness) - }, - [R5U870_WDM_CTRL_HUE] = { - .base = { .ctrl = { .id = V4L2_CID_HUE, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Hue", - .minimum = -180, - .maximum = 180, - .step = 1, - .default_value = 0, - .flags = V4L2_CTRL_FLAG_SLIDER }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_HUE, - .val_offset = offsetof(struct r5u870_ctx, vh_hue) - }, - [R5U870_WDM_CTRL_GAMMA] = { - .base = { .ctrl = { .id = V4L2_CID_GAMMA, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Gamma", - .minimum = 0, - .maximum = 500, - .step = 1, - .default_value = 100, - .flags = V4L2_CTRL_FLAG_SLIDER }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_GAMMA, - .val_offset = offsetof(struct r5u870_ctx, vh_gamma) - }, - [R5U870_WDM_CTRL_BACKLIGHT_COMP_500] = { - .base = { .ctrl = { .id = V4L2_CID_R5U870_BACKLIGHT, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Backlight Compensation", - .minimum = 0, - .maximum = 500, - .step = 1, - .default_value = 250, - .flags = V4L2_CTRL_FLAG_SLIDER }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_BACKLIGHT_COMP, - .val_offset = offsetof(struct r5u870_ctx, vh_backlight_comp) - }, - [R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1] = { - .base = { .ctrl = { .id = V4L2_CID_R5U870_BACKLIGHT, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Backlight Compensation", - .minimum = 0, - .maximum = 500, - .step = 1, - .default_value = 1, - .flags = V4L2_CTRL_FLAG_SLIDER }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_BACKLIGHT_COMP, - .val_offset = offsetof(struct r5u870_ctx, vh_backlight_comp) - }, - [R5U870_WDM_CTRL_BACKLIGHT_COMP_X1834] = { - .base = { .ctrl = { .id = V4L2_CID_R5U870_BACKLIGHT, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Backlight Compensation", - .minimum = 0, - .maximum = 2, - .step = 1, - .default_value = 1, - .flags = V4L2_CTRL_FLAG_SLIDER }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_BACKLIGHT_COMP_2, - .val_offset = offsetof(struct r5u870_ctx, vh_backlight_comp) - }, - [R5U870_WDM_CTRL_WB_RED] = { - .base = { .ctrl = { .id = V4L2_CID_RED_BALANCE, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "White Balance Red", - .minimum = 0, - .maximum = 255, - .step = 1, - .default_value = 127, - .flags = 0 }, - .query_fn = r5u870_query_ctrl, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_WB_RED_EX, - .val_offset = offsetof(struct r5u870_ctx, vh_wb_red), - .auto_offset = offsetof(struct r5u870_ctx, vh_auto_wb) - }, - [R5U870_WDM_CTRL_WB_GREEN] = { - .base = { .ctrl = { .id = V4L2_CID_R5U870_GREEN_BALANCE, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "White Balance Green", - .minimum = 0, - .maximum = 255, - .step = 1, - .default_value = 127, - .flags = 0 }, - .query_fn = r5u870_query_ctrl, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_WB_GREEN_EX, - .val_offset = offsetof(struct r5u870_ctx, vh_wb_green), - .auto_offset = offsetof(struct r5u870_ctx, vh_auto_wb) - }, - [R5U870_WDM_CTRL_WB_BLUE] = { - .base = { .ctrl = { .id = V4L2_CID_BLUE_BALANCE, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "White Balance Blue", - .minimum = 0, - .maximum = 255, - .step = 1, - .default_value = 127, - .flags = 0 }, - .query_fn = r5u870_query_ctrl, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_WB_BLUE_EX, - .val_offset = offsetof(struct r5u870_ctx, vh_wb_blue), - .auto_offset = offsetof(struct r5u870_ctx, vh_auto_wb) - }, - [R5U870_WDM_CTRL_WB_AUTO] = { - .base = { .ctrl = { .id = V4L2_CID_AUTO_WHITE_BALANCE, - .type = V4L2_CTRL_TYPE_BOOLEAN, - .name = "Auto White Balance", - .minimum = 0, - .maximum = 1, - .step = 1, - .default_value = 1, - .flags = V4L2_CTRL_FLAG_UPDATE }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_WB_AUTO_EX, - .is_auto = 1, - .val_offset = offsetof(struct r5u870_ctx, vh_auto_wb) - }, - [R5U870_WDM_CTRL_AUTO_EXPOSURE] = { - .base = { .ctrl = { .id = V4L2_CID_R5U870_AUTOEXPOSURE, - .type = V4L2_CTRL_TYPE_BOOLEAN, - .name = "Auto Exposure Control", - .minimum = 0, - .maximum = 1, - .step = 1, - .default_value = 1, - .flags = 0 }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_AEC_EX, - .is_auto = 1, - .val_offset = offsetof(struct r5u870_ctx, vh_aec) - }, - [R5U870_WDM_CTRL_EXPOSURE] = { - .base = { .ctrl = { .id = V4L2_CID_EXPOSURE, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Exposure", - .minimum = 0, - .maximum = 511, - .step = 1, - .default_value = 255, - .flags = 0 }, - .query_fn = r5u870_query_ctrl, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_EXPOSURE_EX, - .val_offset = offsetof(struct r5u870_ctx, vh_exposure), - .auto_offset = offsetof(struct r5u870_ctx, vh_aec) - }, - [R5U870_WDM_CTRL_AUTO_GAIN] = { - .base = { .ctrl = { .id = V4L2_CID_AUTOGAIN, - .type = V4L2_CTRL_TYPE_BOOLEAN, - .name = "Auto Gain Control", - .minimum = 0, - .maximum = 1, - .step = 1, - .default_value = 1, - .flags = 0 }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_AGC_EX, - .is_auto = 1, - .val_offset = offsetof(struct r5u870_ctx, vh_agc) - }, - [R5U870_WDM_CTRL_GAIN] = { - .base = { .ctrl = { .id = V4L2_CID_GAIN, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Gain", - .minimum = 0, - .maximum = 127, - .step = 1, - .default_value = 63, - .flags = 0 }, - .query_fn = r5u870_query_ctrl, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_GAIN_EX, - .val_offset = offsetof(struct r5u870_ctx, vh_gain), - .auto_offset = offsetof(struct r5u870_ctx, vh_aec) - }, - [R5U870_WDM_CTRL_POWERLINE] = { - .base = { .ctrl = { .id = V4L2_CID_R5U870_POWERLINE, - .type = V4L2_CTRL_TYPE_MENU, - .name = "Power Line Frequency", - .minimum = 0, - .maximum = 2, - .step = 1, - .default_value = 0, - .flags = 0 }, - .menu_names = r5u870_powerline_names, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_POWERLINE_EX, - .val_offset = offsetof(struct r5u870_ctx, vh_powerline) - }, - [R5U870_WDM_CTRL_VFLIP] = { - .base = { .ctrl = { .id = V4L2_CID_VFLIP, - .type = V4L2_CTRL_TYPE_BOOLEAN, - .name = "V-Flip", - .minimum = 0, - .maximum = 1, - .step = 1, - .default_value = 0, - .flags = 0 }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_VFLIP_EX, - .val_offset = offsetof(struct r5u870_ctx, vh_vflip) - }, - [R5U870_WDM_CTRL_HFLIP] = { - .base = { .ctrl = { .id = V4L2_CID_HFLIP, - .type = V4L2_CTRL_TYPE_BOOLEAN, - .name = "H-Flip", - .minimum = 0, - .maximum = 1, - .step = 1, - .default_value = 0, - .flags = 0 }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_HFLIP_EX, - .val_offset = offsetof(struct r5u870_ctx, vh_hflip) - }, - [R5U870_WDM_CTRL_PRIVACY] = { - .base = { .ctrl = { .id = V4L2_CID_R5U870_PRIVACY, - .type = V4L2_CTRL_TYPE_BOOLEAN, - .name = "Privacy", - .minimum = 0, - .maximum = 1, - .step = 1, - .default_value = 0, - .flags = 0 }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_PRIVACY_EX, - .val_offset = offsetof(struct r5u870_ctx, vh_privacy) - }, - [R5U870_WDM_CTRL_NIGHTMODE] = { - .base = { .ctrl = { .id = V4L2_CID_R5U870_NIGHT_MODE, - .type = V4L2_CTRL_TYPE_BOOLEAN, - .name = "Night Mode", - .minimum = 0, - .maximum = 1, - .step = 1, - .default_value = 0, - .flags = 0 }, - .get_fn = r5u870_get_ctrl, - .set_fn = r5u870_set_ctrl_wdm }, - .reg = R5U870_REG_NIGHT_MODE_EX, - .val_offset = offsetof(struct r5u870_ctx, vh_nightmode) - }, -}; - -static int r5u870_wdm_add_ctrls(struct r5u870_ctx *vhp, const int *ctrlarray) -{ - struct r5u870_ctrl *ncp; - int curctrl, i, nctrls; - - /* Count the number of controls */ - for (nctrls = 0; - ctrlarray[nctrls] != R5U870_WDM_CTRL_LAST; - nctrls++); - - if (!nctrls) - return 0; - - /* Allocate a new control array */ - ncp = (struct r5u870_ctrl *) kmalloc((vhp->vh_nctrls + nctrls) * - sizeof(*ncp), - GFP_KERNEL); - if (!ncp) - return -ENOMEM; - - if (vhp->vh_nctrls) - memcpy(ncp, vhp->vh_ctrls, vhp->vh_nctrls * sizeof(*ncp)); - memset(&ncp[vhp->vh_nctrls], 0, nctrls * sizeof(*ncp)); - - curctrl = vhp->vh_nctrls; - for (i = 0; i < nctrls; i++, curctrl++) - ncp[curctrl] = r5u870_wdm_ctrls[ctrlarray[i]]; - - if (vhp->vh_nctrls && vhp->vh_dyn_ctrls) - kfree(vhp->vh_ctrls); - vhp->vh_ctrls = ncp; - vhp->vh_nctrls = curctrl; - vhp->vh_dyn_ctrls = 1; - - r5u_dbg(vhp, R5U_CTRL, "Added %d WDM controls", nctrls); - - return 0; -} - - - -/* - * UVC related code follows - */ - -enum { - UVC_SC_VIDEOCONTROL = 1, - UVC_SC_VIDEOSTREAMING = 1, - - UVC_VC_HEADER = 1, - UVC_VC_INPUT_TERMINAL = 2, - UVC_VC_OUTPUT_TERMINAL = 3, - UVC_VC_SELECTOR_UNIT = 4, - UVC_VC_PROCESSING_UNIT = 5, - UVC_VC_EXTENSION_UNIT = 6, - - UVC_VC_REQUEST_ERROR_CODE_CONTROL = 0x02, - - UVC_VS_INPUT_HEADER = 0x01, - UVC_VS_FORMAT_UNCOMPRESSED = 0x04, - UVC_VS_FRAME_UNCOMPRESSED = 0x05, - - UVC_SET_CUR = 0x01, - UVC_GET_CUR = 0x81, - UVC_GET_MIN = 0x82, - UVC_GET_MAX = 0x83, - UVC_GET_RES = 0x84, - UVC_GET_LEN = 0x85, - UVC_GET_INFO = 0x86, - UVC_GET_DEF = 0x87, - - UVC_PU_BACKLIGHT_COMPENSATION_CONTROL = 0x01, - UVC_PU_BRIGHTNESS_CONTROL = 0x02, - UVC_PU_CONTRAST_CONTROL = 0x03, - UVC_PU_POWER_LINE_FREQUENCY_CONTROL = 0x05, - UVC_PU_HUE_CONTROL = 0x06, - UVC_PU_SATURATION_CONTROL = 0x07, - UVC_PU_SHARPNESS_CONTROL = 0x08, - UVC_PU_GAMMA_CONTROL = 0x09, - - UVC_VS_PROBE_CONTROL = 0x01, - UVC_VS_COMMIT_CONTROL = 0x02, - -}; - -static int r5u870_uvc_req(struct r5u870_ctx *vhp, int cmd, - int valhi, int vallow, int idxhi, int idxlow, - u8 *buf, int len) -{ - int out, res, stres; - int tries = 5; - u8 stbuf[1]; - - out = (cmd == UVC_SET_CUR) ? 1 : 0; - -retry: - res = usb_control_msg(r5u870_dev(vhp), - out - ? usb_sndctrlpipe(r5u870_dev(vhp), 0) - : usb_rcvctrlpipe(r5u870_dev(vhp), 0), - cmd, - (out ? USB_DIR_OUT : USB_DIR_IN) | - USB_TYPE_CLASS | USB_RECIP_INTERFACE, - ((valhi & 0xff) << 8) | (vallow & 0xff), - ((idxhi & 0xff) << 8) | (idxlow & 0xff), - buf, len, - vhp->vh_timeout); - - if (res != -EPIPE) - goto complete; - - stres = usb_control_msg(r5u870_dev(vhp), - usb_rcvctrlpipe(r5u870_dev(vhp), 0), - UVC_GET_CUR, - USB_DIR_IN | USB_TYPE_CLASS | - USB_RECIP_INTERFACE, - UVC_VC_REQUEST_ERROR_CODE_CONTROL << 8, - vhp->vh_ctrl_ifnum, - stbuf, sizeof(stbuf), - vhp->vh_timeout); - - if (((stres == -EPIPE) && --tries) || - ((stres == 1) && (stbuf[0] == 1) && --tries)) { - msleep(5); - goto retry; - } - - if (stres != 1) { - r5u_err(vhp, "uvc_req: status req failed: %d", stres); - goto complete; - - } else { - r5u_err(vhp, "uvc_req: status %d", stbuf[0]); - } - -complete: - if (res < 0) { - r5u_err(vhp, "uvc_req %02x/%02x%02x/%02x%02x failed: %d", - cmd, valhi, vallow, idxhi, idxlow, res); - } - - return res; -} - -static int r5u870_set_fmt_uvc(struct r5u870_ctx *vhp, - const struct r5u870_pix_fmt *fmtp, - const struct r5u870_resolution *resp) -{ - unsigned char buf[26]; - int res; - - memset(buf, 0, sizeof(buf)); - - buf[2] = fmtp->rp_formatidx; - buf[3] = resp->rw_frameidx; - *(__le32 *) &buf[4] = cpu_to_le32(resp->rw_interval); - - r5u_err(vhp, "set_format: fmtidx:%d frameidx:%d %dx%d ival:%d", - fmtp->rp_formatidx, resp->rw_frameidx, - resp->rw_width, resp->rw_height, resp->rw_interval); - - res = r5u870_uvc_req(vhp, UVC_SET_CUR, UVC_VS_PROBE_CONTROL, 0, - 0, vhp->vh_iso_ifnum, buf, sizeof(buf)); - if (res != sizeof(buf)) { - r5u_err(vhp, "%s: probe_control set_cur: short write %d", - __FUNCTION__, res); - - return -EIO; - } - - res = r5u870_uvc_req(vhp, UVC_GET_CUR, UVC_VS_PROBE_CONTROL, 0, - 0, vhp->vh_iso_ifnum, buf, sizeof(buf)); - if (res != sizeof(buf)) { - r5u_err(vhp, "%s: probe_control get_cur: short read %d", - __FUNCTION__, res); - return -EIO; - } - - if (buf[2] != fmtp->rp_formatidx) { - r5u_err(vhp, "%s: probe_control get_cur: got fmt %d", - __FUNCTION__, buf[2]); - return -EIO; - } - - if (buf[3] != resp->rw_frameidx) { - r5u_err(vhp, "%s: probe_control get_cur: got frame idx %d", - __FUNCTION__, buf[3]); - return -EIO; - } - - res = r5u870_uvc_req(vhp, UVC_SET_CUR, UVC_VS_COMMIT_CONTROL, 0, - 0, vhp->vh_iso_ifnum, buf, sizeof(buf)); - if (res != sizeof(buf)) { - r5u_err(vhp, "%s: commit_control set_cur: short write %d", - __FUNCTION__, res); - return -EIO; - } - - vhp->vh_iso_minpacket = le32_to_cpup((__le32 *) &buf[22]); - - return 0; -} - -static int r5u870_cap_stop_uvc(struct r5u870_ctx *vhp) -{ - /* UVC capture is controlled by changing the altsetting */ - return 0; -} - -static int r5u870_decide_pkt_uvc(struct r5u870_ctx *vhp, int pktstatus, - int pktlen, const u8 *pktdata, int *start) -{ - if (!pktlen) { - vhp->vh_emptypkts++; - if (vhp->vh_emptypkts >= R5U870_EMPTYPKT_GIVE_UP) { - r5u_err(vhp, "capture abort: too many empty pkts"); - return -EIO; - } - if (vhp->vh_frame_accum < 0) - return -EPIPE; - return -EAGAIN; - } - - vhp->vh_emptypkts = 0; - if (vhp->vh_frame_accum < 0) { - if (pktdata[1] & 2) - vhp->vh_frame_accum = 0; - return -EPIPE; - } - - if ((pktdata[0] < 2) || (pktdata[0] > pktlen)) { - r5u_err(vhp, "capture abort: hdrlen=%d pktlen=%d", - pktdata[0], pktlen); - return -EIO; - } - vhp->vh_frame_accum += pktlen - pktdata[0]; - if (vhp->vh_frame_accum > vhp->vh_parent->ud_format.sizeimage) { - r5u_err(vhp, "frame abort: accum=%d", vhp->vh_frame_accum); - vhp->vh_frame_accum = -1; - return -EPIPE; - } - - *start = pktdata[0]; - if (pktdata[1] & 2) { - if (vhp->vh_frame_accum < - vhp->vh_parent->ud_format.sizeimage) { - r5u_err(vhp, "warning: short frame (exp:%d got:%d)", - vhp->vh_parent->ud_format.sizeimage, - vhp->vh_frame_accum); - } - vhp->vh_frame_accum = 0; - return 0; - } - return -EAGAIN; -} - - -/* - * Known video format GUIDs and V4L pixel format translations - * - * Only uncompressed formats are supported, as Ricoh webcams are too - * minimal to do anything else. - */ - -static const struct r5u870_uvc_fmtinfo { - const char *fi_name; - int fi_v4l_id; - u8 fi_guid[16]; - -} r5u870_uvc_fmts[] = { - { .fi_name = "YUY2 4:2:2", - .fi_v4l_id = V4L2_PIX_FMT_YUYV, - .fi_guid = { 0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00, - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }, - { } -}; - -static int r5u870_uvc_add_resolution(struct r5u870_ctx *vhp, - struct r5u870_pix_fmt *fmtp, - int width, int height, int reqbw, - int frameidx, int interval) -{ - int i; - struct r5u870_resolution *resp; - - if (!width || !height) { - r5u_dbg(vhp, R5U_INIT, "invalid frame descriptor %d at %dx%d", - frameidx, width, height); - return -EINVAL; - } - - resp = NULL; - for (i = 0; i < fmtp->rp_restbl_alloc; i++) { - if (!fmtp->rp_restbl[i].rw_width) { - resp = (struct r5u870_resolution *) - &fmtp->rp_restbl[i]; - break; - } - } - - if (!resp) { - r5u_dbg(vhp, R5U_INIT, - "no space for frame descriptor %d at %dx%d", - frameidx, width, height); - return 0; - } - - resp->rw_width = width; - resp->rw_height = height; - resp->rw_frameidx = frameidx; - resp->rw_reqbw = reqbw; - resp->rw_interval = interval; - - r5u_dbg(vhp, R5U_INIT, "Found resolution %d: %dx%d ival %d (%d B/s)", - frameidx, width, height, interval, reqbw); - - return 0; -} - -static int r5u870_uvc_add_fmt(struct r5u870_ctx *vhp, const u8 *guid, - int fmtidx, int nresolutions, - struct r5u870_pix_fmt **new_fmt) -{ - const struct r5u870_uvc_fmtinfo *fip, *fmtarray = r5u870_uvc_fmts; - struct r5u870_pix_fmt *nfp, *fmtp; - int i; - - fip = NULL; - for (i = 0; fmtarray[i].fi_name != NULL; i++) { - if (!memcmp(fmtarray[i].fi_guid, guid, 16)) { - fip = &fmtarray[i]; - break; - } - } - - if (fip == NULL) { - r5u_dbg(vhp, R5U_INIT, "unknown format " - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-" - "%02x%02x%02x%02x%02x%02x", - guid[0], guid[1], guid[2], guid[3], guid[4], guid[5], - guid[6], guid[7], guid[8], guid[9], guid[10], - guid[11], guid[12], guid[13], guid[14], guid[15]); - return -ENOENT; - } - - nfp = (struct r5u870_pix_fmt *) - kmalloc((1 + vhp->vh_npixfmts) * sizeof(*nfp), GFP_KERNEL); - if (!nfp) - return -ENOMEM; - if (vhp->vh_npixfmts) - memcpy(nfp, vhp->vh_pixfmts, vhp->vh_npixfmts * sizeof(*nfp)); - - fmtp = &nfp[vhp->vh_npixfmts]; - memset(fmtp, 0, sizeof(*fmtp)); - strlcpy(fmtp->base.description, - fip->fi_name, - sizeof(fmtp->base.description)); - fmtp->base.pixelformat = fip->fi_v4l_id; - fmtp->rp_formatidx = fmtidx; - fmtp->rp_restbl = (struct r5u870_resolution *) - kmalloc((1 + nresolutions) * sizeof(*fmtp->rp_restbl), - GFP_KERNEL); - if (!fmtp->rp_restbl) { - kfree(nfp); - return -ENOMEM; - } - - memset((char *)fmtp->rp_restbl, 0, - (1 + nresolutions) * sizeof(*fmtp->rp_restbl)); - fmtp->rp_restbl_alloc = nresolutions; - - if (vhp->vh_npixfmts && vhp->vh_dyn_pixfmts) - kfree(vhp->vh_pixfmts); - vhp->vh_pixfmts = nfp; - vhp->vh_npixfmts++; - - if (new_fmt) - (*new_fmt) = fmtp; - - r5u_dbg(vhp, R5U_INIT, "Found format %d: %c%c%c%c (%d frames)", - fmtidx, - fmtp->base.pixelformat & 0xff, - (fmtp->base.pixelformat >> 8) & 0xff, - (fmtp->base.pixelformat >> 16) & 0xff, - (fmtp->base.pixelformat >> 24) & 0xff, - nresolutions); - - return 0; -} - -static int r5u870_uvc_parse_vs(struct r5u870_ctx *vhp, int ifnum) -{ - struct usb_interface *intf; - struct usb_host_interface *aintf; - struct r5u870_pix_fmt *curfmt = NULL; - u8 *desc; - int dlen, rlen; - int wid, hgt, reqbw, ival; - int res; - - intf = usb_ifnum_to_if(r5u870_dev(vhp), ifnum); - if (!intf) - return -EINVAL; - - aintf = intf->cur_altsetting; - - for (desc = aintf->extra, rlen = aintf->extralen; - rlen > 2; - rlen -= desc[0], desc += desc[0]) { - - dlen = desc[0]; - if (dlen < 2) - return -EINVAL; - if (desc[1] != USB_DT_CS_INTERFACE) - continue; - if (dlen < 3) - return -EINVAL; - - switch (desc[2]) { - case UVC_VS_INPUT_HEADER: - if (dlen < 7) { - r5u_err(vhp, "VS_INPUT_HEADER too short " - "at %d bytes", dlen); - return -EINVAL; - } - vhp->vh_iso_ep = desc[6]; - break; - - case UVC_VS_FORMAT_UNCOMPRESSED: - if (dlen < 21) { - r5u_err(vhp, "VS_FORMAT_UNCOMP too short " - "at %d bytes", dlen); - break; - } - res = r5u870_uvc_add_fmt(vhp, &desc[5], desc[3], - desc[4], &curfmt); - if (res) { - if (res != -ENOENT) - return res; - curfmt = NULL; - } - break; - - case UVC_VS_FRAME_UNCOMPRESSED: - if (dlen < 26) { - r5u_err(vhp, "VS_FRAME_UNCOMP too short " - "at %d bytes", dlen); - break; - } - if (!curfmt) { - r5u_dbg(vhp, R5U_INIT, "VS_FRAME_UNCOMP " - "not following VS_FORMAT_UNCOMP"); - break; - } - - wid = desc[5] | (desc[6] << 8); - hgt = desc[7] | (desc[8] << 8); - reqbw = desc[13] | (desc[14] << 8) | - (desc[15] << 16) | (desc[16] << 24); - reqbw = (reqbw + 7) / 8; - ival = le32_to_cpup((__le32 *) &desc[21]); - - res = r5u870_uvc_add_resolution(vhp, curfmt, wid, hgt, - reqbw, desc[3], ival); - if (res) - return res; - break; - } - } - - return 0; -} - - -/* - * Known UVC controls for processing units - * - * Not all types of UVC controls are supported. We stick to simple - * controls with one or two byte values, and don't do anything very - * complicated. - * - * We don't support camera unit controls, or multiple processing units - * with instances of the same control. We also don't check for - * redefined control IDs, and let usbcam do this for us. - */ - -struct r5u870_uvc_ctrlinfo { - const char *ci_name; - int ci_v4l_id; - int ci_v4l_type; - int ci_v4l_flags; - int ci_reg; - int ci_size; - int ci_bm_index; - int ci_min, ci_max, ci_def; - u8 ci_min_force, ci_max_force, ci_def_force; - const char **ci_menu_names; - int ci_val_offset; -}; - -static struct r5u870_uvc_ctrlinfo r5u870_uvc_proc_ctrls[] = { - { .ci_name = "Brightness", - .ci_v4l_id = V4L2_CID_BRIGHTNESS, - .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, - .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, - .ci_reg = UVC_PU_BRIGHTNESS_CONTROL, - .ci_size = 2, - .ci_bm_index = 0, - .ci_val_offset = offsetof(struct r5u870_ctx, vh_brightness) }, - { .ci_name = "Contrast", - .ci_v4l_id = V4L2_CID_CONTRAST, - .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, - .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, - .ci_reg = UVC_PU_CONTRAST_CONTROL, - .ci_size = 2, - .ci_bm_index = 1, - .ci_val_offset = offsetof(struct r5u870_ctx, vh_contrast) }, - { .ci_name = "Hue", - .ci_v4l_id = V4L2_CID_HUE, - .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, - .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, - .ci_reg = UVC_PU_HUE_CONTROL, - .ci_size = 2, - .ci_min = -180, .ci_min_force = 1, - .ci_max = 180, .ci_max_force = 1, - .ci_def = 0, .ci_def_force = 1, - .ci_bm_index = 2, - .ci_val_offset = offsetof(struct r5u870_ctx, vh_hue) }, - { .ci_name = "Saturation", - .ci_v4l_id = V4L2_CID_SATURATION, - .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, - .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, - .ci_reg = UVC_PU_SATURATION_CONTROL, - .ci_size = 2, - .ci_bm_index = 3, - .ci_val_offset = offsetof(struct r5u870_ctx, vh_saturation) }, - { .ci_name = "Sharpness", - .ci_v4l_id = V4L2_CID_R5U870_SHARPNESS, - .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, - .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, - .ci_reg = UVC_PU_SHARPNESS_CONTROL, - .ci_size = 2, - .ci_bm_index = 4, - .ci_val_offset = offsetof(struct r5u870_ctx, vh_sharpness) }, - { .ci_name = "Gamma", - .ci_v4l_id = V4L2_CID_GAMMA, - .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, - .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, - .ci_reg = UVC_PU_GAMMA_CONTROL, - .ci_size = 2, - .ci_bm_index = 5, - .ci_val_offset = offsetof(struct r5u870_ctx, vh_gamma) }, - { .ci_name = "Backlight Compensation", - .ci_v4l_id = V4L2_CID_R5U870_BACKLIGHT, - .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER, - .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER, - .ci_reg = UVC_PU_BACKLIGHT_COMPENSATION_CONTROL, - .ci_size = 2, - .ci_bm_index = 8, - .ci_val_offset = offsetof(struct r5u870_ctx, vh_backlight_comp) }, - { .ci_name = "Power Line Frequency", - .ci_v4l_id = V4L2_CID_R5U870_POWERLINE, - .ci_v4l_type = V4L2_CTRL_TYPE_MENU, - .ci_reg = UVC_PU_POWER_LINE_FREQUENCY_CONTROL, - .ci_size = 1, - .ci_bm_index = 10, - .ci_min = 0, .ci_min_force = 1, - .ci_max = 2, .ci_max_force = 1, - .ci_menu_names = r5u870_powerline_names, - .ci_val_offset = offsetof(struct r5u870_ctx, vh_powerline) }, - { } -}; - -static int r5u870_uvc_ctrl_req(struct r5u870_ctx *vhp, - const struct r5u870_ctrl *ctrlp, - int req, int *value) -{ - u8 buf[4]; - int size, i, val, res; - - size = ctrlp->size; - if (req == UVC_GET_INFO) - size = 1; - - if (size > sizeof(buf)) { - r5u_err(vhp, "Control ID %d is too large, %d bytes", - ctrlp->reg, size); - return -EINVAL; - } - - memset(buf, 0, sizeof(buf)); - - if (req != UVC_SET_CUR) { - res = r5u870_uvc_req(vhp, req, ctrlp->reg, 0, - ctrlp->unit, vhp->vh_ctrl_ifnum, - buf, size); - if (res < 0) - return res; - if (res != size) { - r5u_err(vhp, "short read for UVC control %d", - ctrlp->reg); - return -EIO; - } - - val = 0; - switch (size) { - case 1: - val = buf[0]; - break; - case 2: - val = le16_to_cpu(*(__le16 *) buf); - break; - case 4: - val = le32_to_cpu(*(__le32 *) buf); - break; - default: - return -EINVAL; - } - - *value = val; - return 0; - } - - val = *value; - for (i = 0; i < size; i++, val >>= 8) - buf[i] = val & 0xff; - - res = r5u870_uvc_req(vhp, UVC_SET_CUR, ctrlp->reg, 0, - ctrlp->unit, vhp->vh_ctrl_ifnum, - buf, size); - if (res < 0) - return res; - if (res != size) { - r5u_err(vhp, "short write for UVC control %d", - ctrlp->reg); - return -EIO; - } - - return 0; -} - -static int r5u870_set_ctrl_uvc(struct usbcam_dev *udp, - const struct usbcam_ctrl *basep, - const struct v4l2_control *c) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - struct r5u870_ctrl *ctlp = container_of(basep, struct r5u870_ctrl, - base); - int val; - int res = 0; - - if (!(ctlp->info & 2)) { - r5u_dbg(vhp, R5U_CTRL, "control %s <= %d [set not supported]", - ctlp->base.ctrl.name, c->value); - return -EINVAL; - } - - r5u_dbg(vhp, R5U_CTRL, "control %s/uvc %02x <= %d", - ctlp->base.ctrl.name, ctlp->reg, c->value); - - val = c->value; - res = r5u870_uvc_ctrl_req(vhp, ctlp, UVC_SET_CUR, &val); - if (res) - return res; - - *(int *) (((char *) vhp) + ctlp->val_offset) = c->value; - - return res; -} - -/* - * Initialize a r5u870_ctrl structure based on a UVC descriptor array, - * and a bit within a supported control bitmap. - */ -static int r5u870_uvc_init_ctrl(struct r5u870_ctx *vhp, - struct r5u870_ctrl *ctrlp, - int unit, - const struct r5u870_uvc_ctrlinfo *ci_array, - int bit_offset) -{ - const struct r5u870_uvc_ctrlinfo *cip = NULL; - int i, res, val; - - for (i = 0; ci_array[i].ci_name != NULL; i++) { - if (ci_array[i].ci_bm_index == bit_offset) { - cip = &ci_array[i]; - break; - } - } - - if (!cip) - return -ENOENT; - if (!ctrlp) - return 0; - - ctrlp->base.ctrl.id = cip->ci_v4l_id; - ctrlp->base.ctrl.type = cip->ci_v4l_type; - strlcpy(ctrlp->base.ctrl.name, - cip->ci_name, - sizeof(ctrlp->base.ctrl.name)); - ctrlp->base.ctrl.flags = cip->ci_v4l_flags; - ctrlp->base.ctrl.step = 1; - ctrlp->base.menu_names = cip->ci_menu_names; - ctrlp->base.get_fn = r5u870_get_ctrl; - ctrlp->base.set_fn = r5u870_set_ctrl_uvc; - ctrlp->base.query_fn = r5u870_query_ctrl; - ctrlp->reg = cip->ci_reg; - ctrlp->unit = unit; - ctrlp->size = cip->ci_size; - ctrlp->is_auto = 0; - ctrlp->val_offset = cip->ci_val_offset; - ctrlp->auto_offset = 0; - - res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_INFO, &val); - if (res) - return res; - ctrlp->info = val; - - if (cip->ci_min_force) { - ctrlp->base.ctrl.minimum = cip->ci_min; - } else { - res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_MIN, &val); - if (res < 0) - return res; - ctrlp->base.ctrl.minimum = val; - } - if (cip->ci_max_force) { - ctrlp->base.ctrl.maximum = cip->ci_max; - } else { - res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_MAX, &val); - if (res < 0) - return res; - ctrlp->base.ctrl.maximum = val; - } - - if (cip->ci_def_force) { - ctrlp->base.ctrl.default_value = cip->ci_def; - } else { - res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_DEF, &val); - if (res) - return res; - ctrlp->base.ctrl.default_value = val; - } - - r5u_dbg(vhp, R5U_INIT, "Found UVC control %s [%d,%d] def:%d info:%02x", - ctrlp->base.ctrl.name, ctrlp->base.ctrl.minimum, - ctrlp->base.ctrl.maximum, ctrlp->base.ctrl.default_value, - ctrlp->info); - return 0; -} - -static int r5u870_uvc_add_ctrls(struct r5u870_ctx *vhp, int unit, - const struct r5u870_uvc_ctrlinfo *ci_array, - const u8 *bits, int nbits) -{ - struct r5u870_ctrl *ncp; - int nctrls = 0; - int i, res, curctrl; - - /* Count the known, usable controls on this descriptor unit */ - for (i = 0; i < nbits; i++) { - if (!test_bit(i, (const unsigned long *) bits)) - continue; - res = r5u870_uvc_init_ctrl(vhp, NULL, unit, ci_array, i); - if (res == -ENOENT) { - r5u_dbg(vhp, R5U_INIT, - "unit %d ctrl %d not recognized", unit, i); - } - else if (res) - return res; - - else - nctrls++; - } - - if (!nctrls) - return 0; - - /* Allocate a new control array */ - ncp = (struct r5u870_ctrl *) kmalloc((vhp->vh_nctrls + nctrls) * - sizeof(*ncp), - GFP_KERNEL); - if (!ncp) - return -ENOMEM; - - if (vhp->vh_nctrls) - memcpy(ncp, vhp->vh_ctrls, vhp->vh_nctrls * sizeof(*ncp)); - memset(&ncp[vhp->vh_nctrls], 0, nctrls * sizeof(*ncp)); - - /* Fill out the new control entries */ - curctrl = vhp->vh_nctrls; - for (i = 0; i < nbits; i++) { - if (!test_bit(i, (const unsigned long *) bits)) - continue; - - res = r5u870_uvc_init_ctrl(vhp, &ncp[curctrl], - unit, ci_array, i); - if (!res) { - BUG_ON(curctrl > (vhp->vh_nctrls + nctrls)); - curctrl++; - } - else if (res != -ENOENT) { - kfree(ncp); - return res; - } - } - - BUG_ON(curctrl != (vhp->vh_nctrls + nctrls)); - - if (vhp->vh_nctrls && vhp->vh_dyn_ctrls) - kfree(vhp->vh_ctrls); - vhp->vh_ctrls = ncp; - vhp->vh_nctrls = curctrl; - vhp->vh_dyn_ctrls = 1; - return 0; -} - -static int r5u870_uvc_parse_vc(struct r5u870_ctx *vhp) -{ - struct usb_host_interface *aintf = - vhp->vh_parent->ud_intf->cur_altsetting; - u8 *desc; - int dlen, rlen, count; - int i, res; - - vhp->vh_ctrl_ifnum = aintf->desc.bInterfaceNumber; - - for (desc = aintf->extra, rlen = aintf->extralen; - rlen > 2; - rlen -= desc[0], desc += desc[0]) { - - dlen = desc[0]; - if (dlen < 2) - return -EINVAL; - if (desc[1] != USB_DT_CS_INTERFACE) - continue; - if (dlen < 3) - return -EINVAL; - - switch (desc[2]) { - case UVC_VC_HEADER: - count = (dlen < 12) ? 0 : desc[11]; - if (dlen < (12 + count)) { - r5u_err(vhp, "VC_HEADER too short at %d bytes", - dlen); - return -EINVAL; - } - - for (i = 0; i < count; i++) { - res = usbcam_claim_interface(vhp->vh_parent, - desc[12 + i]); - if (res) - r5u_err(vhp, "interface %d already " - "claimed", desc[12 + i]); - vhp->vh_iso_ifnum = desc[12 + i]; - - res = r5u870_uvc_parse_vs(vhp, desc[12 + i]); - if (res) - return res; - } - break; - - case UVC_VC_PROCESSING_UNIT: - count = (dlen < 8) ? 0 : desc[7]; - if (dlen < (8 + count)) { - r5u_err(vhp, "VC_PROCESSING_UNIT too short " - "at %d bytes", dlen); - return -EINVAL; - } - - res = r5u870_uvc_add_ctrls(vhp, desc[3], - r5u870_uvc_proc_ctrls, - &desc[8], desc[7] * 8); - if (res) - return res; - break; - } - } - - return 0; -} - - -static void r5u870_do_stop(struct r5u870_ctx *vhp) -{ - if (vhp->vh_parent->ud_capturing) { - vhp->vh_parent->ud_capturing = 0; - vhp->vh_ctrl_reg_enable = 0; - - usbcam_isostream_stop(&vhp->vh_iso); - usbcam_curframe_abortall(vhp->vh_parent); - - if (!vhp->vh_parent->ud_disconnected) { - usb_set_interface(r5u870_dev(vhp), - vhp->vh_iso_ifnum, - 0); - vhp->vh_cap_stop(vhp); - } - - usbcam_isostream_cleanup(&vhp->vh_iso); - } - - if (vhp->vh_test_info) - complete(&vhp->vh_test_info->rt_completion); -} - -/* - * As long as we are requested to capture, we keep the iso stream running. - * If we are explicitly requested to stop, we halt. - * If we run out of frame buffers to capture into, we halt. - */ -static void r5u870_iso_packet_done(struct usbcam_dev *udp, - struct usbcam_isostream *isop, - void *pktdata, int pktlen, int pktstatus) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - struct usbcam_curframe cf; - int res, start = 0; - - res = vhp->vh_decide_pkt(vhp, pktstatus, pktlen, - (u8 *) pktdata, &start); - if (vhp->vh_test_info) { - if (res == -EIO) - r5u870_do_stop(vhp); - else if (pktlen && (res != -EPIPE)) { - vhp->vh_test_info->rt_status = 0; - r5u870_do_stop(vhp); - } - return; - } - - switch (res) { - case -EPIPE: - if (vhp->vh_framebuf_offset) { - vhp->vh_framebuf_offset = 0; - usbcam_curframe_complete(udp, 1); - } - break; - - case 0: - case -EAGAIN: - if (pktlen) { - if (usbcam_curframe_get(udp, &cf)) { - /* - * We have data, but there is no frame buffer - * queued to accept it, so we stop. - */ - r5u870_do_stop(vhp); - break; - } - - BUG_ON(pktlen - start + vhp->vh_framebuf_offset > - cf.uc_size); - - /* - * This is our one and only memcpy. - * It's kind of hard to get around doing this, as - * we cannot predict into which isochronous - * packets the camera will choose to return data. - */ - memcpy(cf.uc_base + vhp->vh_framebuf_offset, - pktdata + start, - pktlen - start); - vhp->vh_framebuf_offset += (pktlen - start); - } - - if (!res) { - vhp->vh_framebuf_offset = 0; - usbcam_curframe_complete(udp, 0); - } - break; - - case -EIO: - r5u870_do_stop(vhp); - break; - - default: - BUG(); - } -} - -static void r5u870_iso_submit_error(struct usbcam_dev *udp, - struct usbcam_isostream *isop, int status) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - r5u_dbg(vhp, R5U_FRAME, "iso submit error: %d", status); - r5u870_do_stop(vhp); -} - -static struct usbcam_isostream_ops r5u870_iso_data_ops = { - .packet_done = r5u870_iso_packet_done, - .submit_error = r5u870_iso_submit_error, -}; - - -static void r5u870_usbcam_release(struct usbcam_dev *udp) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - int i; - - for (i = 0; i < vhp->vh_npixfmts; i++) { - if (vhp->vh_pixfmts[i].rp_restbl_alloc) - kfree(vhp->vh_pixfmts[i].rp_restbl); - } - - if (vhp->vh_dyn_pixfmts) - kfree(vhp->vh_pixfmts); - vhp->vh_dyn_pixfmts = 0; - vhp->vh_pixfmts = NULL; - vhp->vh_npixfmts = 0; - - if (vhp->vh_dyn_ctrls) - kfree(vhp->vh_ctrls); - vhp->vh_ctrls = NULL; - vhp->vh_nctrls = 0; - vhp->vh_dyn_ctrls = 0; -} - - -static const struct r5u870_model *r5u870_find_model(int driver_info); -static int r5u870_usbcam_init(struct usbcam_dev *udp, - const struct usb_device_id *devid) -{ - struct r5u870_ctx *vhp; - int model_info; - int res; - - model_info = devid->driver_info; - - /* Initialize the private structure, don't use r5u_dbg() before this */ - vhp = udp_r5u870(udp); - memset(vhp, 0, sizeof(*vhp)); - vhp->vh_parent = udp; - vhp->vh_timeout = 1000; - - vhp->vh_ctrl_ifnum = -1; - vhp->vh_iso_ifnum = -1; - vhp->vh_iso_minpacket = -1; - - vhp->vh_model = r5u870_find_model(model_info); - - if (!vhp->vh_model) { - r5u_err(vhp, "no suitable model descriptor for %04x:%04x", - le16_to_cpu(r5u870_dev(vhp)->descriptor.idVendor), - le16_to_cpu(r5u870_dev(vhp)->descriptor.idProduct)); - return -ENODEV; - } - - vhp->vh_pixfmts = vhp->vh_model->rm_pixfmts; - vhp->vh_npixfmts = vhp->vh_model->rm_npixfmts; - - if (vhp->vh_model->rm_uvc) { - vhp->vh_set_fmt = r5u870_set_fmt_uvc; - vhp->vh_cap_stop = r5u870_cap_stop_uvc; - vhp->vh_decide_pkt = r5u870_decide_pkt_uvc; - } else { - vhp->vh_set_fmt = r5u870_set_fmt_wdm; - vhp->vh_cap_stop = r5u870_cap_stop_wdm; - vhp->vh_decide_pkt = r5u870_decide_pkt_wdm; - } - - r5u_info(vhp, "Detected %s", vhp->vh_model->rm_name); - snprintf(vhp->vh_parent->ud_vdev.name, - sizeof(vhp->vh_parent->ud_vdev.name), - "%s #%d", - vhp->vh_model->rm_name, vhp->vh_parent->ud_minidrv_id + 1); - - res = r5u870_dev_init(vhp); - if (res < 0) { - r5u_err(vhp, "initialization failed: %d", res); - goto out_failed; - } - - if (vhp->vh_model->rm_uvc) { - /* - * This appears to be a UVC VideoControl interface. - * Claim all of the associated VideoStreaming interfaces - * and configure the required endpoints and unit IDs - */ - res = r5u870_uvc_parse_vc(vhp); - if (res < 0) { - r5u_err(vhp, "UVC setup failed: %d", res); - goto out_failed; - } - - } else { - /* We are looking at a proprietary Ricoh interface */ - vhp->vh_iso_ifnum = - udp->ud_intf->altsetting->desc.bInterfaceNumber; - vhp->vh_iso_ep = 6; - } - - if (vhp->vh_model->rm_wdm_ctrlids) { - res = r5u870_wdm_add_ctrls(vhp, vhp->vh_model->rm_wdm_ctrlids); - if (res < 0) { - r5u_err(vhp, "Vendor control setup failed: %d", res); - goto out_failed; - } - } - - /* Configure the usbcam pixel format and control arrays */ - if (!vhp->vh_npixfmts) { - r5u_err(vhp, "No pixel formats detected"); - res = -ENODEV; - goto out_failed; - } - - udp->ud_fmt_array = &vhp->vh_pixfmts[0].base; - udp->ud_fmt_array_elem_size = sizeof(vhp->vh_pixfmts[0]); - udp->ud_fmt_array_len = vhp->vh_npixfmts; - - if (vhp->vh_nctrls) { - udp->ud_ctrl_array = &vhp->vh_ctrls[0].base; - udp->ud_ctrl_array_len = vhp->vh_nctrls; - udp->ud_ctrl_array_elem_size = sizeof(*vhp->vh_ctrls); - } - - /* Set the default format */ - vhp->vh_fmt = &vhp->vh_pixfmts[0]; - vhp->vh_res = &vhp->vh_fmt->rp_restbl[0]; - udp->ud_format.width = vhp->vh_res->rw_width; - udp->ud_format.height = vhp->vh_res->rw_height; - udp->ud_format.pixelformat = vhp->vh_fmt->base.pixelformat; - udp->ud_format.field = V4L2_FIELD_INTERLACED; - udp->ud_format.bytesperline = udp->ud_format.width * 2; - udp->ud_format.sizeimage = (udp->ud_format.width * - udp->ud_format.height * 2); - udp->ud_format.colorspace = V4L2_COLORSPACE_SMPTE170M; - - /* Configure default values for all controls */ - res = r5u870_set_controls(vhp, 1); - if (res < 0) { - r5u_err(vhp, "set defaults failed: %d", res); - goto out_failed; - } - - return 0; - -out_failed: - r5u870_usbcam_release(udp); - return res; -} - -static void r5u870_usbcam_disconnect(struct usbcam_dev *udp) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - r5u870_do_stop(vhp); -} - - -/* - * The power management stuff doesn't quite work yet, so we don't - * yet set the supports_autosuspend field in the ops structure. - */ - -static int r5u870_usbcam_suspend(struct usbcam_dev *udp, pm_message_t msg) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - r5u870_do_stop(vhp); - return 0; -} - -static int r5u870_usbcam_resume(struct usbcam_dev *udp) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - int res; - - res = r5u870_dev_init(vhp); - if (res < 0) { - r5u_err(vhp, "dev reinitialization failed: %d", res); - return res; - } - - vhp->vh_configured = 0; - - return 0; -} - -static int r5u870_try_format(struct usbcam_dev *udp, struct v4l2_pix_format *f, - const struct r5u870_pix_fmt **fmt_out, - const struct r5u870_resolution **res_out) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - const struct r5u870_pix_fmt *fmt; - const struct r5u870_resolution *res, *restbl; - int i; - - fmt = NULL; - for (i = 0; i < vhp->vh_npixfmts; i++) { - if (vhp->vh_pixfmts[i].base.pixelformat == f->pixelformat) { - fmt = &vhp->vh_pixfmts[i]; - break; - } - } - if (fmt == NULL) - return -EINVAL; - - restbl = fmt->rp_restbl; - if (!restbl || !restbl[0].rw_width) { - r5u_err(vhp, "invalid resolution table"); - return -EINVAL; - } - - /* Find the most acceptable resolution */ - res = NULL; - for (i = 0; restbl[i].rw_width > 0; i++) { - if (!res) { - res = &restbl[i]; - } - else if (res->rw_width > f->width) { - if (restbl[i].rw_width < res->rw_width) - res = &restbl[i]; - } - else if (res->rw_height > f->height) { - if ((restbl[i].rw_width <= f->width) && - (restbl[i].rw_height < res->rw_height)) - res = &restbl[i]; - } - else if ((restbl[i].rw_width <= f->width) && - (restbl[i].rw_height <= f->height) && - ((restbl[i].rw_width > res->rw_width) || - ((restbl[i].rw_width == res->rw_width) && - (restbl[i].rw_height > res->rw_height)))) { - res = &restbl[i]; - } - } - - if ((f->width > 1) && (f->height > 1)) { - r5u_dbg(vhp, R5U_INIT, "pix_fmt width: %d height: %d", f->width, f->height); - r5u_dbg(vhp, R5U_INIT, "res width: %d height %d", res->rw_width, res->rw_height); - - if (((res->rw_width > f->width) || (res->rw_height > f->height))) { - r5u_dbg(vhp, R5U_INIT, "Bad size request. Returning -EINVAL."); - return -EINVAL; - } - } - - memset(f, 0, sizeof(*f)); - f->width = res->rw_width; - f->height = res->rw_height; - f->pixelformat = fmt->base.pixelformat; - f->bytesperline = f->width * 2; - f->sizeimage = f->width * f->height * 2; - f->field = V4L2_FIELD_INTERLACED; - f->colorspace = V4L2_COLORSPACE_SMPTE170M; - - r5u_dbg(vhp, R5U_INIT, "Settled on pixel format: %d (%s)", fmt->base.pixelformat, fmt->base.description); - - if (fmt_out) - (*fmt_out) = fmt; - if (res_out) - (*res_out) = res; - - return 0; -} - -static int r5u870_usbcam_try_format(struct usbcam_dev *udp, - struct v4l2_pix_format *f) -{ - return r5u870_try_format(udp, f, NULL, NULL); -} - -static int r5u870_usbcam_set_format(struct usbcam_dev *udp, - struct v4l2_pix_format *f) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - const struct r5u870_pix_fmt *fmt_out; - const struct r5u870_resolution *res_out; - int res; - - res = r5u870_try_format(udp, f, &fmt_out, &res_out); - if (res) - return res; - - if ((udp->ud_format.width != f->width) || - (udp->ud_format.height != f->height) || - (udp->ud_format.pixelformat != f->pixelformat) || - (udp->ud_format.sizeimage != f->sizeimage)) - vhp->vh_configured = 0; - - udp->ud_format = *f; - vhp->vh_fmt = fmt_out; - vhp->vh_res = res_out; - return 0; -} - -static void r5u870_usbcam_cap_stop(struct usbcam_dev *udp) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - r5u870_do_stop(vhp); -} - -static int r5u870_config_iso_ep(struct r5u870_ctx *vhp) -{ - int res; - - if (!vhp->vh_configured) { - vhp->vh_ctrl_sync = 0; - - res = usbcam_choose_altsetting(vhp->vh_parent, - vhp->vh_iso_ifnum, - usb_rcvisocpipe(r5u870_dev(vhp), - vhp->vh_iso_ep), - vhp->vh_res->rw_reqbw, - vhp->vh_iso_minpacket, -1, - &vhp->vh_act_altsetting); - if (res) { - r5u_err(vhp, "need %d B/s, no altsetting provides", - vhp->vh_res->rw_reqbw); - return res; - } - - r5u_dbg(vhp, R5U_FRAME, "using altsetting %d", - vhp->vh_act_altsetting); - } - - res = usb_set_interface(r5u870_dev(vhp), vhp->vh_iso_ifnum, - vhp->vh_act_altsetting); - if (res) { - r5u_err(vhp, "could not set altsetting: %d", res); - return res; - } - - return 0; -} - -static int r5u870_usbcam_cap_start(struct usbcam_dev *udp) -{ - struct r5u870_ctx *vhp = udp_r5u870(udp); - int res; - - if (vhp->vh_parent->ud_capturing) - return 0; - - if (!vhp->vh_model->rm_uvc) { - res = r5u870_config_iso_ep(vhp); - if (res) - return res; - } - - vhp->vh_ctrl_reg_enable = 1; - - if (!vhp->vh_configured) { - r5u_dbg(vhp, R5U_MDINTF, "setting initial control values"); - res = r5u870_set_controls(vhp, 0); - if (res) - goto out_set_idle; - - res = vhp->vh_set_fmt(vhp, vhp->vh_fmt, vhp->vh_res); - if (res) { - r5u_err(vhp, "could not configure capture: %d", res); - goto out_set_idle; - } - - if (vhp->vh_model->rm_no_ctrl_reload) - vhp->vh_ctrl_sync = 1; - } - - if (vhp->vh_model->rm_uvc) { - res = r5u870_config_iso_ep(vhp); - if (res) - goto out_set_idle; - } - - vhp->vh_configured = 1; - - res = usbcam_isostream_init(&vhp->vh_iso, vhp->vh_parent, - vhp->vh_iso_ep, - &r5u870_iso_data_ops, - 0, 0, 1, 0); - if (res < 0) { - r5u_err(vhp, "isostream init failed: %d", res); - goto out_set_idle; - } - - udp->ud_capturing = 1; - - if (!vhp->vh_model->rm_uvc) { - res = r5u870_set_cap_state_wdm(vhp, 1); - if (res) - goto out_cleanup_isostream; - } - - if (!vhp->vh_ctrl_sync) { - r5u_dbg(vhp, R5U_MDINTF, "reloading control values"); - - /* Reload the control values after changing res/format */ - res = r5u870_set_controls(vhp, 0); - if (res) { - r5u_err(vhp, "could not load control values: %d", res); - goto out_stop_capture; - } - - vhp->vh_ctrl_sync = 1; - } - - if (res) - goto out_failed; - - r5u_dbg(vhp, R5U_MDINTF, "starting capture"); - - vhp->vh_firstframe = 0; - vhp->vh_frame_accum = -1; - vhp->vh_framebuf_offset = 0; - vhp->vh_emptypkts = R5U870_EMPTYPKT_FRAME_DELIM - 1; - - res = usbcam_isostream_start(&vhp->vh_iso); - if (res) - goto out_stop_capture; - - return 0; - -out_stop_capture: - (void) vhp->vh_cap_stop(vhp); -out_cleanup_isostream: - usbcam_isostream_cleanup(&vhp->vh_iso); -out_set_idle: - (void) usb_set_interface(r5u870_dev(vhp), vhp->vh_iso_ifnum, 0); -out_failed: - vhp->vh_ctrl_reg_enable = 0; - vhp->vh_parent->ud_capturing = 0; - return res; -} - -static struct usbcam_dev_ops r5u870_usbcam_dev_ops = { - .init = r5u870_usbcam_init, - .disconnect = r5u870_usbcam_disconnect, - .release = r5u870_usbcam_release, - .suspend = r5u870_usbcam_suspend, - .resume = r5u870_usbcam_resume, - .try_format = r5u870_usbcam_try_format, - .set_format = r5u870_usbcam_set_format, - .cap_start = r5u870_usbcam_cap_start, - .cap_stop = r5u870_usbcam_cap_stop, - - /* .supports_autosuspend = 1, */ -}; - - -/* - * Per-device hard coded vendor control lists follow - */ - -static const int r5u870_1830_ctrls[] = { - R5U870_WDM_CTRL_BRIGHTNESS, - R5U870_WDM_CTRL_CONTRAST, - R5U870_WDM_CTRL_SATURATION, - R5U870_WDM_CTRL_SHARPNESS, - R5U870_WDM_CTRL_HUE, - R5U870_WDM_CTRL_GAMMA, - R5U870_WDM_CTRL_BACKLIGHT_COMP_500, - R5U870_WDM_CTRL_WB_RED, - R5U870_WDM_CTRL_WB_GREEN, - R5U870_WDM_CTRL_WB_BLUE, - R5U870_WDM_CTRL_WB_AUTO, - R5U870_WDM_CTRL_GAIN, - R5U870_WDM_CTRL_POWERLINE, - R5U870_WDM_CTRL_VFLIP, - R5U870_WDM_CTRL_HFLIP, - R5U870_WDM_CTRL_PRIVACY, - R5U870_WDM_CTRL_NIGHTMODE, - R5U870_WDM_CTRL_LAST, -}; -static const int r5u870_1832_ctrls[] = { - R5U870_WDM_CTRL_BRIGHTNESS, - R5U870_WDM_CTRL_CONTRAST, - R5U870_WDM_CTRL_HUE, - R5U870_WDM_CTRL_SATURATION, - R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1, - R5U870_WDM_CTRL_POWERLINE, - R5U870_WDM_CTRL_VFLIP, - R5U870_WDM_CTRL_HFLIP, - R5U870_WDM_CTRL_PRIVACY, - R5U870_WDM_CTRL_NIGHTMODE, - R5U870_WDM_CTRL_LAST, -}; -static const int r5u870_1833_ctrls[] = { - R5U870_WDM_CTRL_BRIGHTNESS, - R5U870_WDM_CTRL_CONTRAST, - R5U870_WDM_CTRL_HUE, - R5U870_WDM_CTRL_SATURATION, - R5U870_WDM_CTRL_SHARPNESS, - R5U870_WDM_CTRL_GAMMA, - R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1, - R5U870_WDM_CTRL_WB_RED, - R5U870_WDM_CTRL_WB_GREEN, - R5U870_WDM_CTRL_WB_BLUE, - R5U870_WDM_CTRL_WB_AUTO, - R5U870_WDM_CTRL_POWERLINE, - R5U870_WDM_CTRL_VFLIP, - R5U870_WDM_CTRL_HFLIP, - R5U870_WDM_CTRL_PRIVACY, - R5U870_WDM_CTRL_NIGHTMODE, - R5U870_WDM_CTRL_LAST, -}; -static const int r5u870_1834_ctrls[] = { - R5U870_WDM_CTRL_BRIGHTNESS, - R5U870_WDM_CTRL_CONTRAST, - R5U870_WDM_CTRL_HUE, - R5U870_WDM_CTRL_SATURATION, - R5U870_WDM_CTRL_SHARPNESS, - R5U870_WDM_CTRL_GAMMA, - R5U870_WDM_CTRL_BACKLIGHT_COMP_X1834, - R5U870_WDM_CTRL_WB_AUTO, - R5U870_WDM_CTRL_WB_RED, - R5U870_WDM_CTRL_WB_BLUE, - R5U870_WDM_CTRL_AUTO_EXPOSURE, - R5U870_WDM_CTRL_EXPOSURE, - R5U870_WDM_CTRL_AUTO_GAIN, - R5U870_WDM_CTRL_GAIN, - R5U870_WDM_CTRL_POWERLINE, - R5U870_WDM_CTRL_VFLIP, - R5U870_WDM_CTRL_HFLIP, - R5U870_WDM_CTRL_PRIVACY, - R5U870_WDM_CTRL_NIGHTMODE, - R5U870_WDM_CTRL_LAST, -}; -static const int r5u870_1870_ctrls[] = { - R5U870_WDM_CTRL_BRIGHTNESS, - R5U870_WDM_CTRL_CONTRAST, - R5U870_WDM_CTRL_HUE, - R5U870_WDM_CTRL_SATURATION, - R5U870_WDM_CTRL_SHARPNESS, - R5U870_WDM_CTRL_GAMMA, - R5U870_WDM_CTRL_WB_AUTO, - R5U870_WDM_CTRL_WB_RED, - R5U870_WDM_CTRL_WB_BLUE, - R5U870_WDM_CTRL_AUTO_EXPOSURE, - R5U870_WDM_CTRL_EXPOSURE, - R5U870_WDM_CTRL_AUTO_GAIN, - R5U870_WDM_CTRL_GAIN, - R5U870_WDM_CTRL_POWERLINE, - R5U870_WDM_CTRL_VFLIP, - R5U870_WDM_CTRL_HFLIP, - R5U870_WDM_CTRL_PRIVACY, - R5U870_WDM_CTRL_NIGHTMODE, - R5U870_WDM_CTRL_LAST, -}; - -/* - * Even the UVC models do not express all of their controls in the UVC - * descriptor tables, and get sets of hard-coded vendor controls - */ -static const int r5u870_1835_ctrls[] = { - R5U870_WDM_CTRL_WB_RED, - R5U870_WDM_CTRL_WB_GREEN, - R5U870_WDM_CTRL_WB_BLUE, - R5U870_WDM_CTRL_WB_AUTO, - R5U870_WDM_CTRL_VFLIP, - R5U870_WDM_CTRL_HFLIP, - R5U870_WDM_CTRL_PRIVACY, - R5U870_WDM_CTRL_LAST, -}; -static const int r5u870_1810_1836_ctrls[] = { - R5U870_WDM_CTRL_WB_AUTO, - R5U870_WDM_CTRL_WB_RED, - R5U870_WDM_CTRL_WB_BLUE, - R5U870_WDM_CTRL_AUTO_EXPOSURE, - R5U870_WDM_CTRL_EXPOSURE, - R5U870_WDM_CTRL_AUTO_GAIN, - R5U870_WDM_CTRL_GAIN, - R5U870_WDM_CTRL_VFLIP, - R5U870_WDM_CTRL_HFLIP, - R5U870_WDM_CTRL_PRIVACY, - R5U870_WDM_CTRL_NIGHTMODE, - R5U870_WDM_CTRL_LAST, -}; -static const int r5u870_1810_183a_ctrls[] = { - /* these are the ones not automatically discovered in uvc controls */ - R5U870_WDM_CTRL_WB_RED, - R5U870_WDM_CTRL_WB_GREEN, - R5U870_WDM_CTRL_WB_BLUE, - R5U870_WDM_CTRL_WB_AUTO, - R5U870_WDM_CTRL_VFLIP, - R5U870_WDM_CTRL_HFLIP, - R5U870_WDM_CTRL_PRIVACY, - R5U870_WDM_CTRL_NIGHTMODE, - R5U870_WDM_CTRL_LAST, -}; - - -/* - * Standard resolution table for non-UVC cameras, - * as UVC camera report back as to what resolutions - * they support. - * The Sony VGP-VCC2 Windows driver supports: - * 160x120, 176x144, 320x240, 352x288, 640x480 - * The HP driver also supports 1280x1024 - */ -static const struct r5u870_resolution r5u870_vga_wdm_res[] = { - { 160, 120, 1152000 }, - { 176, 144, 1520640 }, - { 320, 240, 4608000 }, - { 352, 288, 6082560 }, - { 640, 480, 18432000 }, - { } -}; -static const struct r5u870_resolution r5u870_sxga_wdm_res[] = { - { 160, 120, 1152000 }, - { 176, 144, 1520640 }, - { 320, 240, 4608000 }, - { 352, 288, 6082560 }, - { 640, 480, 18432000 }, - { 1280, 1024, 19660800 }, - { } -}; -static struct r5u870_pix_fmt r5u870_vga_wdm_pixfmts[] = { - { .base = { .description = "YUY2 4:2:2", - .pixelformat = V4L2_PIX_FMT_YUYV, - .flags = 0 }, - .rp_formatidx = 0, - .rp_restbl = r5u870_vga_wdm_res }, - - { .base = { .description = "UYVY 4:2:2", - .pixelformat = V4L2_PIX_FMT_UYVY, - .flags = 0 }, - .rp_formatidx = 1, - .rp_restbl = r5u870_vga_wdm_res }, -}; -static struct r5u870_pix_fmt r5u870_sxga_wdm_pixfmts[] = { - { .base = { .description = "YUY2 4:2:2", - .pixelformat = V4L2_PIX_FMT_YUYV, - .flags = 0 }, - .rp_formatidx = 0, - .rp_restbl = r5u870_sxga_wdm_res }, - - { .base = { .description = "UYVY 4:2:2", - .pixelformat = V4L2_PIX_FMT_UYVY, - .flags = 0 }, - .rp_formatidx = 1, - .rp_restbl = r5u870_sxga_wdm_res }, -}; - -enum { - R5U870_DI_INVALID, - R5U870_DI_VGP_VCC2_SZ, - R5U870_DI_VGP_VCC3, - R5U870_DI_VGP_VCC2_AR1, - R5U870_DI_VGP_VCC2_AR2, - R5U870_DI_VGP_VCC5, - R5U870_DI_VGP_VCC4, - R5U870_DI_VGP_VCC7, - R5U870_DI_HP_WEBCAM1K, - R5U870_DI_HP_PAVWC_WDM, - R5U870_DI_HP_PAVWC_UVC, - R5U870_DI_GENERIC_UVC, -}; - -static const struct r5u870_model r5u870_models[] = { - [R5U870_DI_VGP_VCC2_SZ] = { - .rm_name = "Sony VGP-VCC2 (VAIO SZ)", - .rm_ucode_file = "r5u870_1830.fw", - .rm_ucode_version = 0x0100, - .rm_wdm_ctrlids = r5u870_1830_ctrls, - .rm_pixfmts = r5u870_vga_wdm_pixfmts, - .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts), - }, - [R5U870_DI_VGP_VCC3] = { - .rm_name = "Sony VGP-VCC3", - .rm_ucode_file = "r5u870_1832.fw", - .rm_ucode_version = 0x0100, - .rm_wdm_ctrlids = r5u870_1832_ctrls, - .rm_pixfmts = r5u870_vga_wdm_pixfmts, - .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts), - .rm_no_ctrl_reload = 1, - }, - [R5U870_DI_VGP_VCC2_AR1] = { - .rm_name = "Sony VGP-VCC2 (VAIO AR1)", - .rm_ucode_file = "r5u870_1833.fw", - .rm_ucode_version = 0x0100, - .rm_wdm_ctrlids = r5u870_1833_ctrls, - .rm_pixfmts = r5u870_vga_wdm_pixfmts, - .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts), - }, - [R5U870_DI_VGP_VCC2_AR2] = { - .rm_name = "Sony VGP-VCC2 (VAIO AR)", - .rm_ucode_file = "r5u870_1834.fw", - .rm_ucode_version = 0x0111, - .rm_wdm_ctrlids = r5u870_1834_ctrls, - .rm_pixfmts = r5u870_vga_wdm_pixfmts, - .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts), - }, - [R5U870_DI_VGP_VCC5] = { - .rm_name = "Sony VGP-VCC5", - .rm_ucode_file = "r5u870_1835.fw", - .rm_ucode_version = 0x0107, - .rm_wdm_ctrlids = r5u870_1835_ctrls, - .rm_uvc = 1, - }, - [R5U870_DI_VGP_VCC4] = { - .rm_name = "Sony VGP-VCC4", - .rm_ucode_file = "r5u870_1836.fw", - .rm_ucode_version = 0x0115, - .rm_wdm_ctrlids = r5u870_1810_1836_ctrls, - .rm_uvc = 1, - }, - [R5U870_DI_VGP_VCC7] = { - .rm_name = "Sony VGP-VCC7 (VAIO SZ)", - .rm_ucode_file = "r5u870_183a.fw", - .rm_ucode_version = 0x0111, - .rm_wdm_ctrlids = r5u870_1810_183a_ctrls, - .rm_uvc = 1, - }, - [R5U870_DI_HP_WEBCAM1K] = { - .rm_name = "HP Webcam 1000", - .rm_ucode_file = "r5u870_1870_1.fw", - .rm_ucode_version = 0x0100, - .rm_wdm_ctrlids = r5u870_1870_ctrls, - .rm_pixfmts = r5u870_vga_wdm_pixfmts, - .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts), - }, - [R5U870_DI_HP_PAVWC_WDM] = { - .rm_name = "HP Pavilion Webcam", - .rm_ucode_file = "r5u870_1870.fw", - .rm_ucode_version = 0x0112, - .rm_wdm_ctrlids = r5u870_1870_ctrls, - .rm_pixfmts = r5u870_sxga_wdm_pixfmts, - .rm_npixfmts = ARRAY_SIZE(r5u870_sxga_wdm_pixfmts), - }, - [R5U870_DI_HP_PAVWC_UVC] = { - .rm_name = "HP Pavilion Webcam", - .rm_ucode_file = "r5u870_1810.fw", - .rm_ucode_version = 0x0115, - .rm_wdm_ctrlids = r5u870_1810_1836_ctrls, - .rm_uvc = 1, - }, - [R5U870_DI_GENERIC_UVC] = { - .rm_name = "Generic UVC Webcam", - .rm_uvc = 1, - }, -}; - -/* - * Some idiot at HP decided to use 05ca:1870 for two distinct devices. - * The Pavilion dv1xxx machines all seem to have the less common of the - * two. There is no known, working method to distinguish the devices - * using USB commands only. We resort to reading the model number out - * of DMI. - */ -static int dv1000 = 2; -module_param(dv1000, int, S_IRUGO|S_IWUSR); -MODULE_PARM_DESC(dv1000, "HP dv1000 detect mode (0=no,1=yes,2=DMI)"); - -static int r5u870_check_hp_dv1000(void) -{ - const char *prod_name; - if (!dv1000) - return 0; - if (dv1000 == 1) - return 1; - prod_name = dmi_get_system_info(DMI_PRODUCT_NAME); - if (!prod_name) - printk(KERN_INFO "r5u870: No DMI model found\n"); - else { - printk(KERN_INFO "r5u870: Found DMI model: \"%s\"\n", - prod_name); - if (!strncmp(prod_name, "HP Pavilion dv1000", 18) && - !isdigit(prod_name[18])) - return 1; - } - return 0; -} - -static const struct r5u870_model *r5u870_find_model(int driver_info) -{ - if (driver_info == R5U870_DI_HP_PAVWC_WDM) { - if (r5u870_check_hp_dv1000()) - driver_info = R5U870_DI_HP_WEBCAM1K; - } - if ((driver_info <= R5U870_DI_INVALID) || - (driver_info >= ARRAY_SIZE(r5u870_models))) - return NULL; - if (!r5u870_models[driver_info].rm_name) - return NULL; - return &r5u870_models[driver_info]; -} - - -#define R5U870_DEVICE_UVC(VID, PID, DINFO) \ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE \ - | USB_DEVICE_ID_MATCH_INT_INFO, \ - .idVendor = (VID), \ - .idProduct = (PID), \ - .bInterfaceClass = USB_CLASS_VIDEO, \ - .bInterfaceSubClass = 1, \ - .bInterfaceProtocol = 0, \ - .driver_info = (DINFO) - -static const struct usb_device_id id_table[] = { - { USB_DEVICE(0x05CA, 0x1830), .driver_info = R5U870_DI_VGP_VCC2_SZ }, - { USB_DEVICE(0x05CA, 0x1832), .driver_info = R5U870_DI_VGP_VCC3 }, - { USB_DEVICE(0x05CA, 0x1833), .driver_info = R5U870_DI_VGP_VCC2_AR1 }, - { USB_DEVICE(0x05CA, 0x1834), .driver_info = R5U870_DI_VGP_VCC2_AR2 }, - { USB_DEVICE(0x05CA, 0x1870), .driver_info = R5U870_DI_HP_PAVWC_WDM }, - - { R5U870_DEVICE_UVC(0x05CA, 0x1810, R5U870_DI_HP_PAVWC_UVC) }, - { R5U870_DEVICE_UVC(0x05CA, 0x1835, R5U870_DI_VGP_VCC5) }, - { R5U870_DEVICE_UVC(0x05CA, 0x1836, R5U870_DI_VGP_VCC4) }, - { R5U870_DEVICE_UVC(0x05CA, 0x183a, R5U870_DI_VGP_VCC7) }, - { }, -}; - - -DEFINE_USBCAM_MINIDRV_MODULE(R5U870_VERSION, R5U870_VERSION_EXTRA, - &r5u870_usbcam_dev_ops, - sizeof(struct r5u870_ctx), - id_table) - -MODULE_DEVICE_TABLE(usb, id_table); -MODULE_DESCRIPTION("Driver for Ricoh R5U870-based Webcams"); -MODULE_AUTHOR("Sam Revitch <samr7@cs.washington.edu>"); -MODULE_LICENSE("GPL"); diff --git a/usbcam/Makefile b/usbcam/Makefile @@ -0,0 +1,6 @@ +usbcam-objs := usbcam_dev.o \ + usbcam_fops.o \ + usbcam_buf.o \ + usbcam_util.o + +obj-m += usbcam.o diff --git a/usbcam/usbcam.c b/usbcam/usbcam.c @@ -0,0 +1,3477 @@ +/* + * USBCAM abstraction library for USB webcam drivers + * Version 0.10.2 + * + * Copyright (C) 2007 Sam Revitch <samr7@cs.washington.edu> + * Copyright (c) 2008 Alexander Hixon <hixon.alexander@mediati.org> + * + * 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, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * TODO LIST: + * - Add debug tracing to more ioctl paths + * - Provide a cleaner mechanism for alerting minidrvers of URB + * underflows in the isostream component. + */ + +#define CONFIG_USBCAM_DEBUG + +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/smp_lock.h> +#include <linux/vmalloc.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/videodev.h> + +#include "usbcam.h" + +/* If building integrated with a minidriver, don't export symbols */ +#undef EXPORT_SYMBOL +#define EXPORT_SYMBOL(X) + +#define assert usbcam_assert + +#define usbcam_drvname(MDP) ((MDP)->um_owner->name) + +#if defined(CONFIG_USBCAM_DEBUG) +#define usbcam_dbgm(MD, SUBSYS, FMT, ARG...) do { \ + if ((MD)->um_debug && \ + *(MD)->um_debug & (1UL << USBCAM_DBG_ ## SUBSYS)) \ + printk(KERN_INFO "%s: " FMT "\n", \ + (MD)->um_modname, ## ARG); \ +} while (0) +#else +#define usbcam_dbgm(MD, SUBSYS, FMT, ARG...) +#endif /* defined(CONFIG_USBCAM_DEBUG) */ + +#define usbcam_minidrv_op_present(UDP, CB) \ + ((UDP)->ud_minidrv->um_ops->CB ? 1 : 0) +#define usbcam_minidrv_op(UDP, CB, ARGS...) \ + ((UDP)->ud_minidrv->um_ops->CB((UDP), ## ARGS)) + + +/* + * Private data structure definitions + */ + +/* + * This structure represents a registered minidriver + */ +struct usbcam_minidrv { + struct kref um_kref; + struct module *um_owner; + const char *um_modname; + int *um_debug; + int um_version; + int um_dev_count; + struct list_head um_dev_list; + int um_dev_privsize; + struct usb_driver um_usbdrv; + struct mutex um_lock; + const struct usbcam_dev_ops *um_ops; + struct video_device um_videodev_template; + struct file_operations um_v4l_fops; + const int *um_video_nr_array; + int um_video_nr_array_len; +}; + +/* + * The frame structure is generally managed by the video-buf module + * and represents some chunk of memory that the video4linux client + * requested as a frame buffer. It might be vmalloc()'d, or it might + * be mapped from a user address space. In either case, usbcam + * guarantees a contiguous kernel mapping accessible to the minidriver. + * + * The primary reason to use video-buf in usbcam is for its + * implementation of buffer mapping methods and "zero-copy" kernel-user + * data movement. The V4L2 API is quite rich, and it's much easier to + * use video-buf than to create a private full-featured implementation, + * and much more desirable to use video-buf than to limp along with a + * substandard implementation. The video-buf module isn't specifically + * used for DMA functionality, as most USB devices, with the possible + * exception of those employing bulk transfers, are unsuitable for + * direct frame buffer DMA. + * + * Minidrivers can access details of the current frame using + * usbcam_curframe_get(), and can signal completion of the current + * frame with usbcam_curframe_complete(). It is up to the minidriver + * to fill in the frame buffer. + */ +struct usbcam_frame { + struct videobuf_buffer vbb; + struct list_head cap_links; + void *vmap_base; + void *vmap_sof; +}; + +/* + * This structure represents an open file handle and the frame + * buffers associated with that client + */ +struct usbcam_fh { + struct usbcam_dev *uf_dev; + int uf_flags; + struct videobuf_queue uf_vbq; +}; + +#define USBCAM_FH_USE_FIXED_FB 0x00000001 + + +/* + * APPBUG: Some applications expect VIDIOCGMBUF to provide a buffer + * large enough to accommodate whatever image format they choose in the + * future. We enable fixed size buffer mode from VIDIOCGMBUF, and + * disable it from VIDIOC_REQBUFS. + */ +static int fixed_fbsize = 1024 * 1024; +module_param(fixed_fbsize, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(fixed_fbsize, "Size in bytes of fixed-length framebuffers"); + + +/* + * Frame capture handling helpers follow + */ + +static inline struct usbcam_frame * +usbcam_capture_curframe(struct usbcam_dev *udp) +{ + return list_empty(&udp->ud_frame_cap_queue) + ? NULL + : list_entry(udp->ud_frame_cap_queue.next, + struct usbcam_frame, cap_links); +} + +static void usbcam_capture_abortall(struct usbcam_dev *udp) +{ + struct usbcam_frame *framep; + + /* Abort all frames on the capture queue */ + while (1) { + framep = usbcam_capture_curframe(udp); + if (!framep) + break; + usbcam_dbg(udp, VIDEOBUF, "completing frame %d STATE_ERROR", + framep->vbb.i); + list_del_init(&framep->cap_links); + framep->vbb.state = STATE_ERROR; + wake_up_all(&framep->vbb.done); + } +} + +static inline void usbcam_capture_complete_frame(struct usbcam_dev *udp, + struct usbcam_frame *framep, + int is_error) +{ + usbcam_chklock(udp); + usbcam_dbg(udp, VIDEOBUF, "completing frame %d/%p %s", framep->vbb.i, + framep, is_error ? "STATE_ERROR" : "STATE_DONE"); + list_del_init(&framep->cap_links); + framep->vbb.state = is_error ? STATE_ERROR : STATE_DONE; + wake_up_all(&framep->vbb.done); +} + +static int usbcam_capture_start(struct usbcam_dev *udp) +{ + int res; + + if (udp->ud_capturing) { + usbcam_warn(udp, "%s: already capturing", __FUNCTION__); + return 0; + } + + if (list_empty(&udp->ud_frame_cap_queue)) { + usbcam_warn(udp, "%s: no frames queued to capture", + __FUNCTION__); + return -ENOENT; + } + + if (udp->ud_disconnected) { + /* + * We can't let any frames through if the device has + * been disconnected + */ + usbcam_capture_abortall(udp); + return -ENODEV; + } + + usbcam_dbg(udp, CAPTURE, "invoking minidriver cap_start"); + + res = usbcam_minidrv_op(udp, cap_start); + if (res) { + usbcam_dbg(udp, CAPTURE, + "%s: could not start capture for %s: %d", + __FUNCTION__, usbcam_drvname(udp->ud_minidrv), res); + + if (udp->ud_capturing) { + usbcam_warn(udp, + "%s: minidriver left ud_capturing set\n", + __FUNCTION__); + } + + usbcam_capture_abortall(udp); + return res; + } + + if (!udp->ud_capturing && usbcam_capture_curframe(udp)) { + usbcam_warn(udp, "%s: minidriver failed to set ud_capturing!", + __FUNCTION__); + } else { + usbcam_dbg(udp, CAPTURE, "minidriver capture started"); + } + + return 0; +} + +static void usbcam_capture_stop(struct usbcam_dev *udp) +{ + if (udp->ud_capturing) { + usbcam_dbg(udp, CAPTURE, "invoking minidriver cap_stop"); + usbcam_minidrv_op(udp, cap_stop); + + if (udp->ud_capturing) { + usbcam_warn(udp, "%s: minidriver failed to clear " + "ud_capturing!", __FUNCTION__); + } else { + usbcam_dbg(udp, CAPTURE, "minidriver capture stopped"); + } + } +} + +static void usbcam_capture_stop_nondestructive(struct usbcam_dev *udp) +{ + /* + * Only stop capturing if no frames are queued. + * + * We allow and encourage the minidriver to continue + * capturing in the last requested format, and have it + * stop autonomously when it receives its first data + * for the next frame but finds no frame available. + * This expedites the process for situations such as + * S_FMT which cannot tolerate capture being in progress. + */ + if (udp->ud_capturing && list_empty(&udp->ud_frame_cap_queue)) + usbcam_capture_stop(udp); +} + +static inline struct videobuf_dmabuf* usbframe_get_dmabuf(struct videobuf_buffer *buf) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + return buf->dma; +#else + return videobuf_to_dma(buf); +#endif +} + + +/* + * External APIs for minidriver access to the frame queue + */ + +int usbcam_curframe_get(struct usbcam_dev *udp, struct usbcam_curframe *cf) +{ + struct usbcam_frame *framep = usbcam_capture_curframe(udp); + struct videobuf_dmabuf *dma; + + usbcam_chklock(udp); + + if (!framep || !&framep->vbb) + return -ENOENT; + + dma = usbframe_get_dmabuf(&framep->vbb); + + cf->uc_base = (u8 *) (framep->vmap_sof + ? framep->vmap_sof + : dma->vmalloc); + cf->uc_size = framep->vbb.size; + cf->uc_field = framep->vbb.field; + memset(&cf->uc_timestamp, 0, sizeof(cf->uc_timestamp)); + + return 0; +} +EXPORT_SYMBOL(usbcam_curframe_get); + +void usbcam_curframe_complete_detail(struct usbcam_dev *udp, int is_error, + struct usbcam_curframe *cf) +{ + struct usbcam_frame *framep; + + usbcam_chklock(udp); + + framep = usbcam_capture_curframe(udp); + if (!framep) { + usbcam_warn(udp, "%s: no current frame!", __FUNCTION__); + return; + } + + if (framep->vbb.state != STATE_ACTIVE) { + usbcam_err(udp, "%s: current frame is in unexpected state %d", + __FUNCTION__, framep->vbb.state); + } + + if (cf && !is_error) { + framep->vbb.size = cf->uc_size; + framep->vbb.field = cf->uc_field; + framep->vbb.ts = cf->uc_timestamp; + + if (framep->vbb.bsize < cf->uc_size) { + usbcam_warn(udp, "%s: minidriver supplied " + "excessive size %zu", + __FUNCTION__, cf->uc_size); + framep->vbb.size = framep->vbb.bsize; + } + } + + else if (is_error && usbcam_curframe_testpattern(udp)) { + /* + * No test pattern available. + * Punt: just memset the frame buffer to zero. + */ + const char tp[1] = { 0 }; + usbcam_curframe_fill(udp, 0, tp, 1, udp->ud_format.sizeimage); + } + + usbcam_capture_complete_frame(udp, framep, 0); +} +EXPORT_SYMBOL(usbcam_curframe_complete_detail); + +void usbcam_curframe_abortall(struct usbcam_dev *udp) +{ + usbcam_dbg(udp, CAPTURE, "minidriver aborting all frames"); + + usbcam_chklock(udp); + + if (udp->ud_capturing) { + usbcam_warn(udp, "%s: minidriver left ud_capturing set", + __FUNCTION__); + } + usbcam_capture_abortall(udp); +} +EXPORT_SYMBOL(usbcam_curframe_abortall); + + +/* + * Test pattern code + */ + +void usbcam_curframe_fill(struct usbcam_dev *udp, size_t offset, + const void *pattern, int patlen, int nrecs) +{ + struct usbcam_curframe cf; + size_t end; + + usbcam_chklock(udp); + + if (usbcam_curframe_get(udp, &cf)) { + usbcam_warn(udp, "%s: no current frame", __FUNCTION__); + return; + } + + end = offset + (patlen * nrecs); + if (end > cf.uc_size) { + usbcam_warn(udp, "%s(offs=%zu patlen=%d nrecs=%d) would " + "write past end of %zu byte framebuffer", + __FUNCTION__, + offset, patlen, nrecs, cf.uc_size); + if (offset > cf.uc_size) + nrecs = 0; + else + nrecs = (cf.uc_size - offset) / patlen; + } + else if (end > udp->ud_format.sizeimage) + usbcam_warn(udp, "%s(offs=%zu patlen=%d nrecs=%d) writing " + "beyond %u-byte sizeimage", __FUNCTION__, + offset, patlen, nrecs, udp->ud_format.sizeimage); + + if (!nrecs) + return; + + if (patlen == 1) { + memset(cf.uc_base + offset, *(char *)pattern, nrecs); + return; + } + + while (nrecs--) { + memcpy(cf.uc_base + offset, pattern, patlen); + offset += patlen; + } +} +EXPORT_SYMBOL(usbcam_curframe_fill); + +void usbcam_curframe_fill_lines(struct usbcam_dev *udp, + const char *pat, int patlen, + int pixperpat) +{ + int line, nrecperline, stride; + size_t offset = 0; + + nrecperline = (udp->ud_format.width + pixperpat - 1) / pixperpat; + stride = udp->ud_format.bytesperline + ? udp->ud_format.bytesperline + : (nrecperline * patlen); + + if ((patlen * nrecperline) == stride) { + usbcam_curframe_fill(udp, 0, pat, patlen, + nrecperline * udp->ud_format.height); + return; + } + + for (line = 0; line < udp->ud_format.height; line++) { + usbcam_curframe_fill(udp, offset, pat, patlen, nrecperline); + offset += stride; + } +} + +void usbcam_curframe_fill_interleaved(struct usbcam_dev *udp, + const char *pat_even, + const char *pat_odd, + int patlen, int pixperpat) +{ + int line, nrecperline, stride; + size_t offset = 0; + + nrecperline = (udp->ud_format.width + pixperpat - 1) / pixperpat; + stride = udp->ud_format.bytesperline + ? udp->ud_format.bytesperline + : (nrecperline * patlen); + + for (line = 0; line < udp->ud_format.height; line++) { + usbcam_curframe_fill(udp, offset, + (line & 1) ? pat_odd : pat_even, + patlen, nrecperline); + offset += stride; + } +} + +void usbcam_curframe_fill_planar(struct usbcam_dev *udp, + const char *pat0, int pat0len, int pixperpat0, + const char *pat1, int pat1len, int pixperpat1, + const char *pat2, int pat2len, int pixperpat2) +{ + int nrecperline; + size_t offset = 0; + + if (pat0 && pat0len) { + nrecperline = ((udp->ud_format.width + pixperpat0 - 1) / + pixperpat0); + usbcam_curframe_fill(udp, offset, pat0, pat0len, + nrecperline * udp->ud_format.height); + offset += (nrecperline * udp->ud_format.height * pat0len); + } + if (pat1 && pat1len) { + nrecperline = ((udp->ud_format.width + pixperpat1 - 1) / + pixperpat1); + usbcam_curframe_fill(udp, offset, pat1, pat1len, + nrecperline * udp->ud_format.height); + offset += (nrecperline * udp->ud_format.height * pat1len); + } + if (pat2 && pat2len) { + nrecperline = ((udp->ud_format.width + pixperpat2 - 1) / + pixperpat2); + usbcam_curframe_fill(udp, offset, pat2, pat2len, + nrecperline * udp->ud_format.height); + offset += (nrecperline * udp->ud_format.height * pat2len); + } +} + +/* + * The goal is to be able to come up with a solid blue image in all + * basic uncompressed formats. No JPEG or compressed formats, at least + * not yet. + */ +int usbcam_curframe_testpattern(struct usbcam_dev *udp) +{ + usbcam_chklock(udp); + +#define DO_FILL(PIXPERPAT, TP...) { \ + const char tp[] = {TP}; \ + usbcam_curframe_fill_lines(udp, tp, sizeof(tp), PIXPERPAT); \ +} + switch (udp->ud_format.pixelformat) { + case V4L2_PIX_FMT_RGB332: + DO_FILL(1, 0x03) + return 0; + case V4L2_PIX_FMT_RGB555: + case V4L2_PIX_FMT_RGB565: + DO_FILL(1, 0x1f, 0x00) + return 0; + case V4L2_PIX_FMT_RGB555X: + case V4L2_PIX_FMT_RGB565X: + DO_FILL(1, 0x00, 0x1f) + return 0; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) + case V4L2_PIX_FMT_RGB444: + DO_FILL(1, 0x00, 0x0f) + return 0; +#endif + case V4L2_PIX_FMT_BGR24: + DO_FILL(1, 0xff, 0x00, 0x00); + return 0; + case V4L2_PIX_FMT_RGB24: + DO_FILL(1, 0x00, 0x00, 0xff); + return 0; + case V4L2_PIX_FMT_BGR32: + DO_FILL(1, 0xff, 0x00, 0x00, 0x00); + return 0; + case V4L2_PIX_FMT_RGB32: + DO_FILL(1, 0x00, 0x00, 0xff, 0x00); + return 0; + case V4L2_PIX_FMT_GREY: + DO_FILL(1, 0x1f); + return 0; + case V4L2_PIX_FMT_YUYV: + DO_FILL(2, 0x29, 0xf0, 0x29, 0x6e); + return 0; + case V4L2_PIX_FMT_UYVY: + DO_FILL(2, 0xf0, 0x29, 0x6e, 0x29); + return 0; + case V4L2_PIX_FMT_YYUV: + DO_FILL(2, 0x29, 0x29, 0xf0, 0x6e); + return 0; + case V4L2_PIX_FMT_Y41P: + DO_FILL(8, + 0xf0, 0x29, 0x6e, 0x29, 0xf0, 0x29, 0x6e, 0x29, + 0x29, 0x29, 0x29, 0x29); + return 0; +#undef DO_FILL + + case V4L2_PIX_FMT_SBGGR8: { + const char tp0[] = { 0xff, 0x00 }, tp1[] = { 0x00, 0x00 }; + usbcam_curframe_fill_interleaved(udp, tp0, tp1, 2, 2); + return 0; + } + case V4L2_PIX_FMT_YVU410: { + const char tp0[] = {0x29}, tp1[] = {0x6e}, tp2[] = {0xf0}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 16, + tp2, sizeof(tp2), 16); + return 0; + } + case V4L2_PIX_FMT_YUV410: { + const char tp0[] = {0x29}, tp1[] = {0xf0}, tp2[] = {0x6e}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 16, + tp2, sizeof(tp2), 16); + return 0; + } + case V4L2_PIX_FMT_YVU420: { + const char tp0[] = {0x29}, tp1[] = {0x6e}, tp2[] = {0xf0}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 4, + tp2, sizeof(tp2), 4); + return 0; + } + case V4L2_PIX_FMT_YUV411P: + case V4L2_PIX_FMT_YUV420: { + const char tp0[] = {0x29}, tp1[] = {0xf0}, tp2[] = {0x6e}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 4, + tp2, sizeof(tp2), 4); + return 0; + } + case V4L2_PIX_FMT_YUV422P: { + const char tp0[] = {0x29}, tp1[] = {0xf0}, tp2[] = {0x6e}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 2, + tp2, sizeof(tp2), 2); + return 0; + } + case V4L2_PIX_FMT_NV12: { + const char tp0[] = {0x29}, tp1[] = {0xf0, 0x6e}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 4, + NULL, 0, 0); + return 0; + } + case V4L2_PIX_FMT_NV21: { + const char tp0[] = {0x29}, tp1[] = {0x6e, 0xf0}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 4, + NULL, 0, 0); + return 0; + } + } + + return -EINVAL; +} +EXPORT_SYMBOL(usbcam_curframe_testpattern); + + +/* + * video-buf interfaces for managing frame buffers + */ + +static int usbcam_videobuf_setup(struct videobuf_queue *vq, + unsigned int *count, unsigned int *size) +{ + struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, uf_vbq); + struct usbcam_dev *udp = ufp->uf_dev; + + usbcam_lock(udp); + + /* APPBUG: possibly request larger buffers than necessary */ + if ((ufp->uf_flags & USBCAM_FH_USE_FIXED_FB) && + (fixed_fbsize > udp->ud_format.sizeimage)) + *size = fixed_fbsize; + else + *size = udp->ud_format.sizeimage; + + if (!*count) + *count = 2; + + usbcam_dbg(udp, VIDEOBUF, "videobuf setup: size=%u count=%u", + *size, *count); + + usbcam_unlock(udp); + return 0; +} + +static void usbcam_videobuf_free(struct videobuf_queue *vq, + struct usbcam_frame *framep) +{ + struct videobuf_dmabuf *dma = usbframe_get_dmabuf(&framep->vbb); + + videobuf_waiton(&framep->vbb, 0, 0); + videobuf_dma_unmap(vq, dma); + videobuf_dma_free(dma); + if (framep->vbb.state != STATE_NEEDS_INIT) { + if (framep->vmap_base) { + vunmap(framep->vmap_base); + framep->vmap_base = NULL; + framep->vmap_sof = NULL; + } + assert(list_empty(&framep->cap_links)); + framep->vbb.state = STATE_NEEDS_INIT; + } +} + +static int usbcam_videobuf_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, uf_vbq); + struct usbcam_dev *udp = ufp->uf_dev; + struct usbcam_frame *framep = + container_of(vb, struct usbcam_frame, vbb); + struct videobuf_dmabuf *dma = usbframe_get_dmabuf(&framep->vbb); + int res; + + + framep->vbb.size = udp->ud_format.sizeimage; + if (framep->vbb.baddr && (framep->vbb.bsize < framep->vbb.size)) { + usbcam_warn(udp, "process %s requested capture of a frame " + "larger than its", current->comm); + usbcam_warn(udp, "allocated frame buffer, fix it!"); + return -EINVAL; + } + + if (framep->vbb.state == STATE_NEEDS_INIT) { + /* + * This is the place where we initialize the rest of + * the usbcam_frame structure. + */ + INIT_LIST_HEAD(&framep->cap_links); + framep->vmap_base = NULL; + framep->vmap_sof = NULL; + + usbcam_dbg(udp, VIDEOBUF, + "preparing frame %d/%p", framep->vbb.i, framep); + + /* We also lock down the memory that was allocated for it */ + res = videobuf_iolock(vq, &framep->vbb, NULL); + if (res) + goto fail; + + /* If there's no kernel mapping, we must create one */ + if (!dma->vmalloc) { + framep->vmap_base = vmap(dma->pages, + dma->nr_pages, + VM_MAP, + PAGE_KERNEL); + if (!framep->vmap_base) { + res = -ENOMEM; + goto fail; + } + + framep->vmap_sof = + ((char *)framep->vmap_base) + + dma->offset; + } + } + + framep->vbb.field = field; + framep->vbb.state = STATE_PREPARED; + return 0; + +fail: + usbcam_warn(udp, "videobuf_prepare failed."); + usbcam_videobuf_free(vq, framep); + return res; +} + +static void usbcam_videobuf_queue(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, uf_vbq); + struct usbcam_dev *udp = ufp->uf_dev; + struct usbcam_frame *framep = + container_of(vb, struct usbcam_frame, vbb); + int was_empty = 0; + + assert(framep->vbb.state != STATE_NEEDS_INIT); + + usbcam_lock(udp); + + if (list_empty(&udp->ud_frame_cap_queue)) + was_empty = 1; + + usbcam_dbg(udp, VIDEOBUF, "queueing frame %d/%p", + framep->vbb.i, framep); + + /* + * We always set buffers to STATE_ACTIVE to prevent them from + * being manipulated / dequeued by the videobuf code. + */ + list_add_tail(&framep->cap_links, &udp->ud_frame_cap_queue); + framep->vbb.state = STATE_ACTIVE; + + if (was_empty && !udp->ud_capturing) { + (void) usbcam_capture_start(udp); + } + + usbcam_unlock(udp); +} + +static void usbcam_videobuf_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, uf_vbq); + struct usbcam_dev *udp = ufp->uf_dev; + struct usbcam_frame *framep = + container_of(vb, struct usbcam_frame, vbb); + int stopped_capture = 0; + + usbcam_lock(udp); + + if ((framep->vbb.state != STATE_NEEDS_INIT) && + !list_empty(&framep->cap_links)) { + + usbcam_dbg(udp, VIDEOBUF, + "aborting frame %d/%p", framep->vbb.i, framep); + + /* + * An active frame is being shot down here, most + * likely by videobuf_queue_cancel. + */ + assert(framep->vbb.state == STATE_ACTIVE); + + if (udp->ud_capturing && + !list_empty(&udp->ud_frame_cap_queue) && + (framep == usbcam_capture_curframe(udp))) { + /* + * The current frame has been user-aborted. + * We will stop capturing. The minidriver may complete + * it with an error, or may leave it alone, in which + * case we will complete it with an error. + */ + usbcam_dbg(udp, VIDEOBUF, + "current frame aborted, stopping capture"); + + usbcam_capture_stop(udp); + stopped_capture = 1; + } + + if (!list_empty(&framep->cap_links)) + usbcam_capture_complete_frame(udp, framep, 1); + + /* + * Ideally, if we stopped capturing, and there are frames + * still in the queue, we would restart. + * + * In reality, we only take this code path if all frames + * from the owning file handle are aborted, and restarting + * would pointlessly slow down this process. + */ + } + + usbcam_unlock(udp); + usbcam_videobuf_free(vq, framep); +} + +static struct videobuf_queue_ops usbcam_videobuf_qops = { + .buf_setup = usbcam_videobuf_setup, + .buf_prepare = usbcam_videobuf_prepare, + .buf_queue = usbcam_videobuf_queue, + .buf_release = usbcam_videobuf_release, +}; + + +/* + * Reference Counting Notes + * + * Each usbcam_minidrv gets: + * - One reference for being in the registered state + * - One reference for each outstanding usbcam_dev + * + * Each usbcam_dev gets: + * - One reference for having its V4L minor registered and not released + * - One reference for having its underlying USB device not disconnected + * - One reference for each open file handle + */ + +static void usbcam_minidrv_release(struct kref *kref) +{ + struct usbcam_minidrv *minidrv = + container_of(kref, struct usbcam_minidrv, um_kref); + + assert(!minidrv->um_dev_count); + usbcam_dbgm(minidrv, DEV_STATE, "%s: destroying minidrvier", + __FUNCTION__); + kfree(minidrv); +} + +/* + */ +static void usbcam_work_stop(struct usbcam_dev *udp); +static inline void usbcam_work_maybe_stop(struct usbcam_dev *udp) +{ + if (!udp->ud_work_refs) + usbcam_work_stop(udp); +} + +static void usbcam_dev_free(struct kref *kref) +{ + struct usbcam_dev *udp = + container_of(kref, struct usbcam_dev, ud_kref); + + if (udp->ud_work_refs) { + usbcam_lock(udp); + usbcam_warn(udp, "%s: work queue has %d leaked refs", + __FUNCTION__, udp->ud_work_refs); + while (udp->ud_work_refs) + usbcam_work_unref(udp); + usbcam_unlock(udp); + } + + usbcam_work_maybe_stop(udp); + assert(!udp->ud_work_thread); + + usb_put_intf(udp->ud_intf); + udp->ud_intf = NULL; + + usb_put_dev(udp->ud_dev); + udp->ud_dev = NULL; + + mutex_lock(&udp->ud_minidrv->um_lock); + + assert(!list_empty(&udp->ud_drv_links)); + assert(udp->ud_minidrv->um_dev_count > 0); + + list_del_init(&udp->ud_drv_links); + udp->ud_minidrv->um_dev_count--; + + mutex_unlock(&udp->ud_minidrv->um_lock); + + kref_put(&udp->ud_minidrv->um_kref, usbcam_minidrv_release); + kfree(udp); +} + +static void usbcam_dev_release(struct kref *kref) +{ + struct usbcam_dev *udp = + container_of(kref, struct usbcam_dev, ud_kref); + + usbcam_dbg(udp, DEV_STATE, "%s: destroying device", __FUNCTION__); + + if (usbcam_minidrv_op_present(udp, release)) { + usbcam_lock(udp); + usbcam_minidrv_op(udp, release); + usbcam_unlock(udp); + } + usbcam_dev_free(kref); +} + +void usbcam_put(struct usbcam_dev *udp) +{ + kref_put(&udp->ud_kref, usbcam_dev_release); +} +EXPORT_SYMBOL(usbcam_put); + + +/* + * Work item crap + */ + +enum { + USBCAM_WORKSTATE_DEAD, + USBCAM_WORKSTATE_IDLE, + USBCAM_WORKSTATE_MUTEX_WAIT, + USBCAM_WORKSTATE_RUNNING, +}; + +DECLARE_WAIT_QUEUE_HEAD(usbcam_work_idle_wait); + +static int usbcam_work_thread(void *arg) +{ + struct usbcam_dev *udp = (struct usbcam_dev *) arg; + struct usbcam_workitem *wip; + sigset_t wakesigs; + unsigned long flags; + usbcam_workfunc_t fn; + int res; + + current->flags |= PF_NOFREEZE; + set_user_nice(current, -5); + + sigemptyset(&wakesigs); + sigaddset(&wakesigs, SIGUSR1); + + while (1) { + /* Wait for something to appear on the work queue */ + spin_lock_irqsave(&udp->ud_work_lock, flags); + udp->ud_work_lockwait = 0; + if (list_empty(&udp->ud_work_queue)) { + if (kthread_should_stop()) { + spin_unlock_irqrestore(&udp->ud_work_lock, + flags); + break; + } + + set_current_state(TASK_INTERRUPTIBLE); + wake_up_all(&usbcam_work_idle_wait); + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + schedule(); + spin_lock_irqsave(&udp->ud_work_lock, flags); + } + udp->ud_work_lockwait = 1; + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + + /* Enable the mutex wait cancelation signal */ + sigprocmask(SIG_UNBLOCK, &wakesigs, NULL); + + /* Re-check the queue, wait if it's still nonempty */ + res = -EINTR; + if (!list_empty(&udp->ud_work_queue)) { + res = mutex_lock_interruptible(&udp->ud_lock); + } + + /* Disable the mutex wait cancelation signal */ + sigprocmask(SIG_BLOCK, &wakesigs, NULL); + flush_signals(current); + + if (res) + continue; + + wip = NULL; + spin_lock_irqsave(&udp->ud_work_lock, flags); + udp->ud_work_lockwait = 0; + if (!list_empty(&udp->ud_work_queue)) { + wip = container_of(udp->ud_work_queue.next, + struct usbcam_workitem, + uw_links); + list_del_init(&wip->uw_links); + } + + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + + if (wip) { + fn = wip->uw_func; + fn(wip); + } + + usbcam_unlock(udp); + } + + return 0; +} + +void usbcam_work_init(struct usbcam_workitem *wip, usbcam_workfunc_t func) +{ + INIT_LIST_HEAD(&wip->uw_links); + wip->uw_dev = NULL; + wip->uw_func = func; +} +EXPORT_SYMBOL(usbcam_work_init); + +int usbcam_work_queue(struct usbcam_dev *udp, struct usbcam_workitem *wip) +{ + unsigned long flags; + int res; + + spin_lock_irqsave(&udp->ud_work_lock, flags); + if (!list_empty(&wip->uw_links)) { + res = -EALREADY; + assert(wip->uw_dev == udp); + } else if (udp->ud_work_refs) { + res = 0; + wip->uw_dev = udp; + list_add_tail(&wip->uw_links, &udp->ud_work_queue); + if (udp->ud_work_queue.next == &wip->uw_links) + wake_up_process(udp->ud_work_thread); + } else { + res = -EBUSY; + } + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + + return res; +} +EXPORT_SYMBOL(usbcam_work_queue); + +int usbcam_work_cancel(struct usbcam_dev *udp, struct usbcam_workitem *wip) +{ + unsigned long flags; + int res, wakeit = 0; + + usbcam_chklock(udp); + + res = -ENOENT; + spin_lock_irqsave(&udp->ud_work_lock, flags); + if (!list_empty(&wip->uw_links)) { + res = 0; + assert(wip->uw_dev == udp); + if ((udp->ud_work_queue.next == &wip->uw_links) && + udp->ud_work_lockwait) + wakeit = 1; + list_del_init(&wip->uw_links); + if (wakeit) + force_sig(SIGUSR1, udp->ud_work_thread); + } + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + + return res; +} +EXPORT_SYMBOL(usbcam_work_cancel); + +int usbcam_work_ref(struct usbcam_dev *udp) +{ + struct task_struct *kt_new; + unsigned long flags; + + usbcam_chklock(udp); + + /* + * We adjust this value under the spinlock to synchronize with + * usbcam_work_queue(). + */ + spin_lock_irqsave(&udp->ud_work_lock, flags); + udp->ud_work_refs++; + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + + if (!udp->ud_work_thread) { + kt_new = kthread_create(usbcam_work_thread, udp, + udp->ud_dev_name); + if (!kt_new) { + usbcam_err(udp, "%s: could not create worker thread", + __FUNCTION__); + return -ENOMEM; + } + + spin_lock_irqsave(&udp->ud_work_lock, flags); + udp->ud_work_thread = kt_new; + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + } + + return 0; +} +EXPORT_SYMBOL(usbcam_work_ref); + +void usbcam_work_unref(struct usbcam_dev *udp) +{ + unsigned long flags; + + usbcam_chklock(udp); + + if (!udp->ud_work_refs) { + usbcam_warn(udp, "%s: work queue has zero refs", __FUNCTION__); + return; + } + + spin_lock_irqsave(&udp->ud_work_lock, flags); + udp->ud_work_refs--; + spin_unlock_irqrestore(&udp->ud_work_lock, flags); +} +EXPORT_SYMBOL(usbcam_work_unref); + +void usbcam_work_runqueue(struct usbcam_dev *udp) +{ + struct usbcam_workitem *wip; + unsigned long flags; + usbcam_workfunc_t fn; + + usbcam_chklock(udp); + + spin_lock_irqsave(&udp->ud_work_lock, flags); + while (!list_empty(&udp->ud_work_queue)) { + wip = container_of(udp->ud_work_queue.next, + struct usbcam_workitem, + uw_links); + list_del_init(&wip->uw_links); + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + + fn = wip->uw_func; + fn(wip); + + spin_lock_irqsave(&udp->ud_work_lock, flags); + } + spin_unlock_irqrestore(&udp->ud_work_lock, flags); +} +EXPORT_SYMBOL(usbcam_work_runqueue); + + +static void usbcam_work_stop(struct usbcam_dev *udp) +{ + struct task_struct *kt_stop = NULL; + unsigned long flags; + + usbcam_lock(udp); + + if (!udp->ud_work_refs) { + /* Prevent further tasks from being queued */ + spin_lock_irqsave(&udp->ud_work_lock, flags); + kt_stop = udp->ud_work_thread; + udp->ud_work_thread = NULL; + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + } + + usbcam_unlock(udp); + + if (kt_stop) { + /* + * Wait for the queue to empty out, then stop the + * thread. It might be easier to just call + * usbcam_work_flush() and execute the remaining + * tasks synchronously in the current thread. + */ + wait_event(usbcam_work_idle_wait, + list_empty(&udp->ud_work_queue)); + kthread_stop(kt_stop); + } +} + + + +/* + * V4L file_operations callout implementations + */ + +static int usbcam_v4l_open(struct inode *inode, struct file *filp) +{ + struct usbcam_dev *udp; + struct usbcam_fh *ufp; + int autopm_ref = 0; + int work_ref = 0; + int res = 0; + + /* The usbcam_dev is referenced by the videodev at this point */ + udp = container_of(video_devdata(filp), struct usbcam_dev, ud_vdev); + + ufp = (struct usbcam_fh *) kzalloc(sizeof(*ufp), GFP_KERNEL); + if (!ufp) + return -ENOMEM; + + ufp->uf_dev = udp; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + videobuf_queue_init(&ufp->uf_vbq, + &usbcam_videobuf_qops, + NULL, + NULL, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct usbcam_frame), ufp); +#else + videobuf_queue_pci_init(&ufp->uf_vbq, + &usbcam_videobuf_qops, + NULL, + NULL, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct usbcam_frame), ufp); +#endif + + mutex_lock(&udp->ud_open_lock); + + if (!udp->ud_user_refs) { + res = usb_autopm_get_interface(udp->ud_intf); + if (res) + goto bail_nolock; + autopm_ref = 1; + } + + usbcam_lock(udp); + + if (udp->ud_disconnected) { + usbcam_warn(udp, "udp is disconnected; bailing"); + res = -ENODEV; + goto bail; + } + + if (!udp->ud_user_refs) { + if (!udp->ud_minidrv->um_ops->no_workref_on_open) { + res = usbcam_work_ref(udp); + if (res) + goto bail; + work_ref = 1; + } + + if (usbcam_minidrv_op_present(udp, open)) { + res = usbcam_minidrv_op(udp, open); + if (res) { + /* Can release/reacquire device lock */ + if (work_ref) + usbcam_work_unref(udp); + assert(!udp->ud_user_refs); + goto bail; + } + } + + /* Transfer the autopm reference */ + assert(autopm_ref); + autopm_ref = 0; + } + + udp->ud_user_refs++; + filp->private_data = ufp; + usbcam_get(udp); + +bail: + usbcam_unlock(udp); +bail_nolock: + mutex_unlock(&udp->ud_open_lock); + if (res) + kfree(ufp); + if (autopm_ref) + usb_autopm_put_interface(udp->ud_intf); + usbcam_work_maybe_stop(udp); + return res; +} + +static int usbcam_v4l_release(struct inode *inode, struct file *filp) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data; + struct usbcam_dev *udp = ufp->uf_dev; + int autopm_ref = 0; + + videobuf_mmap_free(&ufp->uf_vbq); + + mutex_lock(&udp->ud_open_lock); + usbcam_lock(udp); + + assert(udp->ud_user_refs); + if (udp->ud_excl_owner == filp) + udp->ud_excl_owner = NULL; + kfree(ufp); + filp->private_data = NULL; + + if (!--udp->ud_user_refs) { + usbcam_capture_stop(udp); + + if (usbcam_minidrv_op_present(udp, close)) + usbcam_minidrv_op(udp, close); + if (!udp->ud_minidrv->um_ops->no_workref_on_open) + usbcam_work_unref(udp); + autopm_ref = 1; + } + + usbcam_unlock(udp); + mutex_unlock(&udp->ud_open_lock); + if (autopm_ref) + usb_autopm_put_interface(udp->ud_intf); + usbcam_work_maybe_stop(udp); + usbcam_put(udp); + return 0; +} + +static ssize_t usbcam_v4l_read(struct file *filp, char __user *data, + size_t count, loff_t *ppos) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data; + int res; + + res = videobuf_read_one(&ufp->uf_vbq, data, count, ppos, + (filp->f_flags & O_NONBLOCK) ? 1 : 0); + usbcam_work_maybe_stop(ufp->uf_dev); + return res; +} + +static unsigned int usbcam_v4l_poll(struct file *filp, + struct poll_table_struct *wait) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data; + + return videobuf_poll_stream(filp, &ufp->uf_vbq, wait); +} + +static int usbcam_v4l_mmap(struct file *filp, struct vm_area_struct * vma) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data; + + return videobuf_mmap_mapper(&ufp->uf_vbq, vma); +} + +static void usbcam_dbg_v4l2_buffer_res(struct usbcam_dev *udp, int res, + void *arg, const char *prefix) +{ + struct v4l2_buffer *b __attribute__((unused)) = + (struct v4l2_buffer *) arg; + + if (res) { + usbcam_dbg(udp, IOCTL_BUF, "%s res:%d", prefix, res); + return; + } + + usbcam_dbg(udp, IOCTL_BUF, "%s out: index=%d type=%d bytesused=%d " + "flags=0x%x field=%d memory=%d m=0x%lx length=%d", + prefix, b->index, b->type, b->bytesused, + b->flags, b->field, b->memory, b->m.userptr, b->length); +} + +static void usbcam_dbg_v4l2_pix_format(struct usbcam_dev *udp, + struct v4l2_pix_format *f, + const char *prefix) +{ + __u32 pixfmt = f->pixelformat; + if (!pixfmt) + pixfmt = 0x3f3f3f3f; + usbcam_dbg(udp, IOCTL_FMT, "%s wid=%d hgt=%d fmt=%.4s field=%d " + "bpl=%d size=%d cs=%d", prefix, + f->width, f->height, (char *) &pixfmt, f->field, + f->bytesperline, f->sizeimage, f->colorspace); +} + +static void usbcam_dbg_v4l2_pix_format_res(struct usbcam_dev *udp, int res, + struct v4l2_pix_format *f, + const char *prefix) +{ + if (res) { + usbcam_dbg(udp, IOCTL_FMT, "%s %d", prefix, res); + return; + } + usbcam_dbg_v4l2_pix_format(udp, f, prefix); +} + +static int usbcam_v4l_int_ioctl(struct inode *inodep, struct file *filp, + unsigned int cmd, void *arg) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data; + struct usbcam_dev *udp = ufp->uf_dev; + int droplock = 0, res; + +#ifdef CONFIG_VIDEO_V4L1_COMPAT + /* V4L1 API -- Handle this before v4l_compat_translate_ioctl(). */ + if (cmd == VIDIOCGMBUF) { + struct v4l2_requestbuffers req; + struct video_mbuf *p = (struct video_mbuf *) arg; + unsigned int i; + + /* + * APPBUG: motion keeps the first mmap, yet requests + * larger capture sizes. + */ + usbcam_lock(udp); + ufp->uf_flags |= USBCAM_FH_USE_FIXED_FB; + usbcam_unlock(udp); + + req.type = ufp->uf_vbq.type; + req.count = 2; + req.memory = V4L2_MEMORY_MMAP; + res = videobuf_reqbufs(&ufp->uf_vbq, &req); + if (res == -EBUSY) + { + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOCGMBUF reqbufs failed: device was busy" + " - closing and trying again."); + + res = videobuf_streamoff(&ufp->uf_vbq); + if (res < 0) + { + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOCGMBUF reqbufs failed:" + "couldn't free previous buffer."); + return -EBUSY; + } + else + { + // we freed previous reqbuf OK. + usbcam_lock(udp); + ufp->uf_flags |= USBCAM_FH_USE_FIXED_FB; + usbcam_unlock(udp); + + req.type = ufp->uf_vbq.type; + req.count = 2; + req.memory = V4L2_MEMORY_MMAP; + res = videobuf_reqbufs(&ufp->uf_vbq, &req); + } + } + else if (res < 0) { + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOCGMBUF reqbufs failed: %d", res); + return res; + } + + p->frames = req.count; + p->size = 0; + for (i = 0; i < p->frames; i++) { + p->offsets[i] = ufp->uf_vbq.bufs[i]->boff; + p->size += ufp->uf_vbq.bufs[i]->bsize; + } + + usbcam_dbg(udp, IOCTL_BUF, "VIDIOCGMBUF frames=%d size=%d", + p->frames, p->size); + return 0; + } + else if (cmd == VIDIOCGCAP) { + struct video_capability *cap = (struct video_capability *) arg; + + usbcam_lock(udp); + + strlcpy(cap->name, udp->ud_vdev.name, sizeof(cap->name)); + cap->type = VID_TYPE_CAPTURE; + cap->audios = 0; + cap->channels = 1; /* only one input source, the camera */ + + cap->maxwidth = udp->ud_format.width; + cap->maxheight = udp->ud_format.height; + + /* + * We lie, here. These values normally return 640x480, which is + * actually the maximum, not the minimum. Minimum is usually + * 160x120. It's sort of useful to lie since lots of software + * just stick with the minimum - we want higher res for the + * user where possible. + */ + + cap->minwidth = udp->ud_format.width; + cap->minheight = udp->ud_format.height; + + usbcam_unlock(udp); + return 0; + } + else if (cmd == VIDIOCGFBUF) { + struct video_buffer *buf = (struct video_buffer *) arg; + + usbcam_lock(udp); + buf->base = NULL; /* no physical frame buffer access */ + buf->height = udp->ud_format.height; + buf->width = udp->ud_format.width; + + // graciously stolen from drivers/media/video/v4l1-compat.c + // and modified slightly. + switch (udp->ud_format.pixelformat) { + case V4L2_PIX_FMT_RGB332: + buf->depth = 8; + break; + case V4L2_PIX_FMT_RGB555: + buf->depth = 15; + break; + case V4L2_PIX_FMT_RGB565: + buf->depth = 16; + break; + case V4L2_PIX_FMT_BGR24: + buf->depth = 24; + break; + case V4L2_PIX_FMT_BGR32: + buf->depth = 32; + break; + default: + buf->depth = 0; + } + + if (udp->ud_format.bytesperline) { + buf->bytesperline = udp->ud_format.bytesperline; + + /* typically comes out at 16 bit depth as non-rgb */ + if (!buf->depth && buf->width) + buf->depth = ((udp->ud_format.bytesperline<<3) + + (buf->width-1) ) + /buf->width; + } else { + buf->bytesperline = + (buf->width * buf->depth + 7) & 7; + buf->bytesperline >>= 3; + } + + usbcam_unlock(udp); + return 0; + } +#endif + /* If the command is any other V4L1 API, pass it to the translator */ + if (_IOC_TYPE(cmd) == 'v') + { + return v4l_compat_translate_ioctl(inodep, filp, cmd, arg, + usbcam_v4l_int_ioctl); + } + + /* + * Pass 1: filter out ioctls that we don't want to give + * the minidriver a chance to handle. + * These tend to be related to frame management, which is + * none of the minidriver's business, and we do not allow the + * minidriver to intercept. + */ + + switch (cmd) { + case VIDIOC_REQBUFS: { + struct v4l2_requestbuffers *r = + (struct v4l2_requestbuffers *) arg; + /* APPBUG: disable USE_FIXED_FB if we enter this path */ + usbcam_lock(udp); + ufp->uf_flags &= ~(USBCAM_FH_USE_FIXED_FB); + usbcam_unlock(udp); + + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOC_REQBUFS count=%d type=%d memory=%d", + r->count, r->type, r->memory); + res = videobuf_reqbufs(&ufp->uf_vbq, r); + usbcam_dbg(udp, IOCTL_BUF, "REQBUFS result=%d", res); + usbcam_work_maybe_stop(udp); + return res; + } + case VIDIOC_QUERYBUF: { + struct v4l2_buffer *b = (struct v4l2_buffer *) arg; + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOC_QUERYBUF in: index=%d type=%d", + b->index, b->type); + res = videobuf_querybuf(&ufp->uf_vbq, b); + usbcam_dbg_v4l2_buffer_res(udp, res, b, "VIDIOC_QUERYBUF"); + usbcam_work_maybe_stop(udp); + return res; + } + case VIDIOC_QBUF: { + struct v4l2_buffer *b = (struct v4l2_buffer *) arg; + /* + * APPBUG: ptlib / Ekiga has an issue with zeroing the + * flags field before calling QBUF. + * + * Minidriver support for fast input switching is + * unavailable for the time being. + */ + b->flags = 0; + + usbcam_dbg(udp, IOCTL_BUF, "VIDIOC_QBUF in: index=%d type=%d", + b->index, b->type); + res = videobuf_qbuf(&ufp->uf_vbq, b); + usbcam_dbg_v4l2_buffer_res(udp, res, b, "VIDIOC_QBUF"); + usbcam_work_maybe_stop(udp); + return res; + } + case VIDIOC_DQBUF: { + struct v4l2_buffer *b = (struct v4l2_buffer *) arg; + res = videobuf_dqbuf(&ufp->uf_vbq, b, + (filp->f_flags & O_NONBLOCK) ? 1 : 0); + usbcam_dbg_v4l2_buffer_res(udp, res, b, "VIDIOC_DQBUF"); + usbcam_work_maybe_stop(udp); + return res; + } + case VIDIOC_STREAMON: { + enum v4l2_buf_type f = *(int *) arg; + + if (f != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOC_STREAMON: invalid buf type %d", f); + return -EINVAL; + } + if (!udp->ud_excl_owner) { + usbcam_lock(udp); + if (!udp->ud_excl_owner) + udp->ud_excl_owner = filp; + usbcam_unlock(udp); + } + if (udp->ud_excl_owner != filp) { + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOC_STREAMON: not exclusive owner"); + return -EBUSY; + } + res = videobuf_streamon(&ufp->uf_vbq); + usbcam_dbg(udp, IOCTL_BUF, "VIDIOC_STREAMON: res:%d", res); + usbcam_work_maybe_stop(udp); + return res; + } + case VIDIOC_STREAMOFF: { + enum v4l2_buf_type f = *(int *) arg; + + if (f != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOC_STREAMOFF: invalid buf type %d", f); + return -EINVAL; + } + res = videobuf_streamoff(&ufp->uf_vbq); + usbcam_dbg(udp, IOCTL_BUF, "VIDIOC_STREAMOFF: res:%d", res); + usbcam_work_maybe_stop(udp); + return res; + } + } + + /* Pass the ioctl to the minidriver, see if it takes responsibility */ + + if (usbcam_minidrv_op_present(udp, ioctl)) { + if (!udp->ud_minidrv->um_ops->unlocked_ioctl) { + usbcam_lock(udp); + droplock = 1; + } + res = usbcam_minidrv_op(udp, ioctl, cmd, arg); + if (droplock) + usbcam_unlock(udp); + + usbcam_work_maybe_stop(udp); + + if (res != -ENOIOCTLCMD) + return res; + } + + /* + * Pass 2: If the minidriver doesn't handle the ioctl, do something + * default. The minidriver can override all of this stuff by + * intercepting. + */ + + switch (cmd) { + + /* DEFAULT CAPABILITIES / DEVICE NAME / DRIVER NAME / BUS INFO */ + case VIDIOC_QUERYCAP: { + struct v4l2_capability *cap = (struct v4l2_capability *) arg; + + usbcam_lock(udp); + strlcpy(cap->driver, + usbcam_drvname(udp->ud_minidrv), + sizeof(cap->driver)); + strlcpy(cap->card, udp->ud_vdev.name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "usb:%s", udp->ud_dev->dev.bus_id); + cap->version = udp->ud_minidrv->um_version; + cap->capabilities = (V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING); + usbcam_unlock(udp); + return 0; + } + + /* DEFAULT FORMAT HANDLING - USE MINDIRIVER CALLOUTS */ + case VIDIOC_ENUM_FMT: { + struct v4l2_fmtdesc *f = (struct v4l2_fmtdesc *) arg; + struct usbcam_pix_fmt *pf; + + usbcam_lock(udp); + + res = -EINVAL; + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + goto enum_fmt_done; + if (f->index >= udp->ud_fmt_array_len) + goto enum_fmt_done; + + res = 0; + pf = (struct usbcam_pix_fmt *) + &(((u8 *) udp->ud_fmt_array) + [f->index * udp->ud_fmt_array_elem_size]); + f->flags = pf->flags; + f->pixelformat = pf->pixelformat; + strlcpy(f->description, + pf->description, + sizeof(f->description)); + + enum_fmt_done: + usbcam_unlock(udp); + return res; + } + + case VIDIOC_G_FMT: { + struct v4l2_format *f = (struct v4l2_format *) arg; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_G_FMT: invalid buf type %d" + "(wasn't of type VIDEO_CAPTURE: %d)", + f->type, V4L2_BUF_TYPE_VIDEO_CAPTURE); + return -EINVAL; + } + + usbcam_lock(udp); + f->fmt.pix = udp->ud_format; + usbcam_unlock(udp); + usbcam_dbg_v4l2_pix_format(udp, &f->fmt.pix, + "VIDIOC_G_FMT: res:"); + return 0; + } + + case VIDIOC_S_FMT: { + struct v4l2_format *f = (struct v4l2_format *) arg; + + usbcam_lock(udp); + + usbcam_dbg_v4l2_pix_format(udp, &f->fmt.pix, + "VIDIOC_S_FMT: param:"); + + if (udp->ud_disconnected) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_S_FMT: device disconnected"); + res = -EIO; + goto s_fmt_done; + } + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_S_FMT: invalid buf type %d", + f->type); + res = -EINVAL; + goto s_fmt_done; + } + if (!udp->ud_excl_owner) + udp->ud_excl_owner = filp; + if (!memcmp(&f->fmt.pix, &udp->ud_format, + sizeof(f->fmt.pix))) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_S_FMT: nothing to do"); + res = 0; + goto s_fmt_done; + } + if (!usbcam_minidrv_op_present(udp, set_format)) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_S_FMT: no minidriver op"); + res = -EINVAL; + goto s_fmt_done; + } + if (udp->ud_excl_owner != filp) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_S_FMT: not exclusive owner"); + res = -EBUSY; + goto s_fmt_done; + } + usbcam_capture_stop_nondestructive(udp); + if (udp->ud_capturing) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_S_FMT: capture in progress"); + res = -EBUSY; + goto s_fmt_done; + } + res = usbcam_minidrv_op(udp, set_format, &f->fmt.pix); + usbcam_dbg_v4l2_pix_format_res(udp, res, &f->fmt.pix, + "VIDIOC_S_FMT: res:"); + + s_fmt_done: + usbcam_unlock(udp); + return res; + } + + case VIDIOC_TRY_FMT: { + struct v4l2_format *f = (struct v4l2_format *) arg; + + usbcam_lock(udp); + + usbcam_dbg_v4l2_pix_format(udp, &f->fmt.pix, + "VIDIOC_TRY_FMT: param:"); + + if (udp->ud_disconnected) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_TRY_FMT: device disconnected"); + res = -EIO; + goto s_fmt_done; + } + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_TRY_FMT: invalid buf type %d", + f->type); + res = -EINVAL; + goto try_fmt_done; + } + if (!usbcam_minidrv_op_present(udp, try_format)) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_TRY_FMT: no minidriver op"); + res = -EINVAL; + goto try_fmt_done; + } + + res = usbcam_minidrv_op(udp, try_format, &f->fmt.pix); + usbcam_dbg_v4l2_pix_format_res(udp, res, &f->fmt.pix, + "VIDIOC_TRY_FMT: res:"); + + try_fmt_done: + usbcam_unlock(udp); + usbcam_work_maybe_stop(udp); + return res; + } + + /* DEFAULT CONTROL HANDLING - USE MINIDRIVER ARRAY / CALLOUTS */ + case VIDIOC_QUERYCTRL: { + struct v4l2_queryctrl *a = (struct v4l2_queryctrl *) arg; + const struct usbcam_ctrl *ctrlp, *resp; + unsigned int targ, highest_id = 0; + int i, res; + + usbcam_lock(udp); + droplock = 1; + targ = a->id; + + resp = NULL; + + if (targ & V4L2_CTRL_FLAG_NEXT_CTRL) { + /* + * Find the control with the least ID greater than or + * equal to a->id + */ + targ &= ~V4L2_CTRL_FLAG_NEXT_CTRL; + for (i = 0, ctrlp = udp->ud_ctrl_array; + i < udp->ud_ctrl_array_len; + i++, ctrlp = (struct usbcam_ctrl *) + (((u8 *) ctrlp) + + udp->ud_ctrl_array_elem_size)) { + if (ctrlp->ctrl.id <= targ) { + if (!resp || + (ctrlp->ctrl.id < resp->ctrl.id)) + resp = ctrlp; + } + } + + } else { + /* Find an exact match */ + for (i = 0, ctrlp = udp->ud_ctrl_array; + i < udp->ud_ctrl_array_len; + i++, ctrlp = (struct usbcam_ctrl *) + (((u8 *) ctrlp) + + udp->ud_ctrl_array_elem_size)) { + if (ctrlp->ctrl.id == targ) { + resp = ctrlp; + break; + } + else if ((ctrlp->ctrl.id >= + V4L2_CID_PRIVATE_BASE) && + (ctrlp->ctrl.id < + (V4L2_CID_PRIVATE_BASE + 1024)) && + (ctrlp->ctrl.id > highest_id)) { + highest_id = ctrlp->ctrl.id; + } + } + } + + if (!resp && !highest_id) + res = -EINVAL; + + else if (!resp) { + /* + * Deal with the private control enumeration + * nonsense that the CTRL_FLAG_NEXT_CTRL thing + * fixes. + */ + memset(a, 0, sizeof(*a)); + a->id = targ; + a->type = V4L2_CTRL_TYPE_INTEGER; + strlcpy(a->name, "Disabled", sizeof(a->name)); + a->flags = V4L2_CTRL_FLAG_DISABLED; + res = 0; + + } else { + *a = resp->ctrl; + res = 0; + + /* + * If a query function was provided, call it to + * postprocess the response structure, e.g. to set + * flags. + */ + if (resp->query_fn) { + if (udp->ud_minidrv->um_ops->unlocked_ctrl) { + usbcam_unlock(udp); + droplock = 0; + } + res = resp->query_fn(udp, resp, a); + } + } + + if (droplock) + usbcam_unlock(udp); + usbcam_work_maybe_stop(udp); + return res; + } + + case VIDIOC_G_CTRL: { + struct v4l2_control *a = (struct v4l2_control *) arg; + const struct usbcam_ctrl *ctrlp, *resp; + int i, res; + + usbcam_lock(udp); + droplock = 1; + + if (udp->ud_disconnected) { + usbcam_unlock(udp); + return -EIO; + } + + resp = NULL; + for (i = 0, ctrlp = udp->ud_ctrl_array; + i < udp->ud_ctrl_array_len; + i++, ctrlp = (struct usbcam_ctrl *) + (((u8 *) ctrlp) + + udp->ud_ctrl_array_elem_size)) { + if (ctrlp->ctrl.id == a->id) { + resp = ctrlp; + break; + } + } + + if (!resp || (resp->ctrl.type == V4L2_CTRL_TYPE_BUTTON)) + res = -EINVAL; + else { + if (udp->ud_minidrv->um_ops->unlocked_ctrl) { + usbcam_unlock(udp); + droplock = 0; + } + res = resp->get_fn(udp, resp, a); + } + + if (droplock) + usbcam_unlock(udp); + usbcam_work_maybe_stop(udp); + return res; + } + + case VIDIOC_S_CTRL: { + struct v4l2_control *a = (struct v4l2_control *) arg; + const struct usbcam_ctrl *ctrlp, *resp; + int i, res; + + usbcam_lock(udp); + droplock = 1; + + if (udp->ud_disconnected) { + usbcam_unlock(udp); + return -EIO; + } + + resp = NULL; + for (i = 0, ctrlp = udp->ud_ctrl_array; + i < udp->ud_ctrl_array_len; + i++, ctrlp = (struct usbcam_ctrl *) + (((u8 *) ctrlp) + + udp->ud_ctrl_array_elem_size)) { + if (ctrlp->ctrl.id == a->id) { + resp = ctrlp; + break; + } + } + + if (!resp) { + res = -EINVAL; + } else if (!resp->set_fn) { + /* Read-only control */ + res = -EBUSY; + } else if ((resp->ctrl.type != V4L2_CTRL_TYPE_BUTTON) && + ((a->value < resp->ctrl.minimum) || + (a->value > resp->ctrl.maximum))) { + res = -ERANGE; + } else { + if (udp->ud_minidrv->um_ops->unlocked_ctrl) { + usbcam_unlock(udp); + droplock = 0; + } + res = resp->set_fn(udp, resp, a); + } + + if (droplock) + usbcam_unlock(udp); + usbcam_work_maybe_stop(udp); + return res; + } + + case VIDIOC_QUERYMENU: { + struct v4l2_querymenu *a = (struct v4l2_querymenu *) arg; + const struct usbcam_ctrl *ctrlp, *resp; + int i, res; + + usbcam_lock(udp); + + resp = NULL; + for (i = 0, ctrlp = udp->ud_ctrl_array; + i < udp->ud_ctrl_array_len; + i++, ctrlp = (struct usbcam_ctrl *) + (((u8 *) ctrlp) + + udp->ud_ctrl_array_elem_size)) { + if (ctrlp->ctrl.id == a->id) { + resp = ctrlp; + break; + } + } + + if (!resp || + (resp->ctrl.type != V4L2_CTRL_TYPE_MENU) || + (a->index > resp->ctrl.maximum)) { + res = -EINVAL; + goto querymenu_done; + } + + strlcpy(a->name, resp->menu_names[a->index], sizeof(a->name)); + res = 0; + + querymenu_done: + usbcam_unlock(udp); + return res; + } + + /* DEFAULT INPUT HANDLING -- There is one input called "Camera" */ + case VIDIOC_ENUMINPUT: { + const struct v4l2_input dfl_input = { + .name = "Camera", + .type = V4L2_INPUT_TYPE_CAMERA, + }; + struct v4l2_input *inp = (struct v4l2_input *) arg; + + if (inp->index > 0) + return -EINVAL; + *inp = dfl_input; + return 0; + } + + case VIDIOCGCHAN: { + const struct video_channel dfl_input = { + .name = "Camera", + .type = VIDEO_TYPE_CAMERA + }; + struct video_channel *inp = (struct video_channel *) arg; + + if (inp->channel > 0) + return -EINVAL; + *inp = dfl_input; + return 0; + } + + case VIDIOC_G_INPUT: { + unsigned int *i = (unsigned int *) arg; + *i = 0; + return 0; + } + + case VIDIOC_S_INPUT: { + unsigned int *i = (unsigned int *) arg; + if (*i != 0) + return -EINVAL; + return 0; + } + + /* DEFAULT VIDEO STANDARD HANDLING */ + case VIDIOC_ENUMSTD: { + struct v4l2_standard *s = (struct v4l2_standard *) arg; + if (s->index > 0) + return -EINVAL; + v4l2_video_std_construct(s, V4L2_STD_NTSC_M, "NTSC-M"); + return 0; + } + + case VIDIOC_G_STD: { + v4l2_std_id *std = (v4l2_std_id *) arg; + *std = V4L2_STD_NTSC_M; + return 0; + } + + case VIDIOC_S_STD: + return 0; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + /* Workaround for v4l1_compat bug in older kernels */ + case VIDIOC_G_FBUF: { + struct v4l2_framebuffer *f = (struct v4l2_framebuffer *) arg; + memset(f, 0, sizeof(*f)); + return -ENOIOCTLCMD; + } +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + } + + /* video_usercopy() will convert this to -EINVAL. */ + return -ENOIOCTLCMD; +} + +static int usbcam_v4l_ioctl(struct inode *inodep, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inodep, filp, cmd, arg, usbcam_v4l_int_ioctl); +} + + +/* + * The template file_operations structure + * + * Each usbcam_minidrv_t contains its own copy of this, which + * is associated with the video4linux device created for that + * minidriver. + * + * In general, copies will differ only in the .owner field, which + * will refer to the minidriver module, not usbcam. + */ + +static struct file_operations usbcam_v4l_fops_template = { + .owner = THIS_MODULE, + .open = usbcam_v4l_open, + .release = usbcam_v4l_release, + .read = usbcam_v4l_read, + .poll = usbcam_v4l_poll, + .mmap = usbcam_v4l_mmap, + .ioctl = usbcam_v4l_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = v4l_compat_ioctl32, +#endif + .llseek = no_llseek, +}; + + +/* + * V4L2 videodev callout implementations + */ + +static void usbcam_videodev_release(struct video_device *vfd) +{ + struct usbcam_dev *udp = container_of(vfd, struct usbcam_dev, ud_vdev); + + usbcam_warn(udp, "releasing videodev device."); + + usbcam_lock(udp); + + assert(!udp->ud_videodev_released); + udp->ud_videodev_released = 1; + + usbcam_unlock(udp); + usbcam_put(udp); +} + +static struct video_device usbcam_videodev_template = { + .name = "usbcam-unknown", + .type = VFL_TYPE_GRABBER, + .type2 = VID_TYPE_CAPTURE, + .minor = -1, + .release = usbcam_videodev_release, +}; + + +/* + * USB subsystem operation implementations + */ + +static int usbcam_check_ctrl_array(struct usbcam_dev *udp, + const struct usbcam_ctrl *ctrlp, + int elem_size, int count) +{ + const struct usbcam_ctrl *startp = ctrlp, *xctrlp; + int i, j; + int errors = 0; + + for (i = 0; i < count; + i++, ctrlp = (struct usbcam_ctrl *) + (((u8 *) ctrlp) + elem_size)) { + /* Verify that the ID isn't already registered */ + for (j = 0, xctrlp = startp; + j < i; + j++, xctrlp = (struct usbcam_ctrl *) + (((u8 *) xctrlp) + elem_size)) { + if (ctrlp->ctrl.id == xctrlp->ctrl.id) { + usbcam_warn(udp, "control %d is registered " + "more than once", + ctrlp->ctrl.id); + errors++; + break; + } + } + + /* Check minimum, maximum, step, and default */ + switch (ctrlp->ctrl.type) { + case V4L2_CTRL_TYPE_INTEGER: + if (ctrlp->ctrl.minimum > ctrlp->ctrl.maximum) { + usbcam_warn(udp, "control %d has " + "minimum > maximum", + ctrlp->ctrl.id); + errors++; + } + break; + + case V4L2_CTRL_TYPE_BOOLEAN: + if (ctrlp->ctrl.minimum) { + usbcam_warn(udp, "control %d is BOOLEAN " + "and has minimum != 0", + ctrlp->ctrl.id); + errors++; + } + if (ctrlp->ctrl.maximum != 1) { + usbcam_warn(udp, "control %d is BOOLEAN " + "and has maximum != 1", + ctrlp->ctrl.id); + errors++; + } + if (ctrlp->ctrl.step != 1) { + usbcam_warn(udp, "control %d is BOOLEAN " + "and has step != 1", + ctrlp->ctrl.id); + errors++; + } + break; + + case V4L2_CTRL_TYPE_MENU: + if (ctrlp->ctrl.minimum) { + usbcam_warn(udp, "control %d is MENU and has " + "minimum != 0", + ctrlp->ctrl.id); + errors++; + } + if (!ctrlp->ctrl.maximum) { + usbcam_warn(udp, "control %d is MENU and has " + "maximum == 0", + ctrlp->ctrl.id); + errors++; + } + if (ctrlp->ctrl.step != 1) { + usbcam_warn(udp, "control %d is MENU and has " + "step != 1", + ctrlp->ctrl.id); + errors++; + } + if (!ctrlp->menu_names) { + usbcam_warn(udp, "control %d is MENU and has " + "NULL menu_names", + ctrlp->ctrl.id); + errors++; + break; + } + break; + + case V4L2_CTRL_TYPE_BUTTON: + if (ctrlp->ctrl.minimum) { + usbcam_warn(udp, "control %d is BUTTON " + "and has minimum != 0", + ctrlp->ctrl.id); + errors++; + } + if (ctrlp->ctrl.maximum) { + usbcam_warn(udp, "control %d is BUTTON " + "and has maximum != 0", + ctrlp->ctrl.id); + errors++; + } + if (ctrlp->ctrl.step) { + usbcam_warn(udp, "control %d is BUTTON " + "and has step != 0", + ctrlp->ctrl.id); + errors++; + } + break; + + default: + usbcam_warn(udp, "control %d is of invalid type %d", + ctrlp->ctrl.id, ctrlp->ctrl.type); + errors++; + continue; + } + + /* Check the range */ + if ((ctrlp->ctrl.default_value < ctrlp->ctrl.minimum) || + (ctrlp->ctrl.default_value > ctrlp->ctrl.maximum)) { + usbcam_warn(udp, "control %d default out of range", + ctrlp->ctrl.id); + errors++; + } + + /* Check the get_fn callout */ + if (ctrlp->ctrl.type == V4L2_CTRL_TYPE_BUTTON) { + if (ctrlp->get_fn) { + usbcam_warn(udp, "control %d is BUTTON " + "and has a get_fn callout", + ctrlp->ctrl.id); + errors++; + } + } else { + if (!ctrlp->get_fn) { + usbcam_warn(udp, "control %d has no " + "get_fn callout", + ctrlp->ctrl.id); + errors++; + } + } + + if ((ctrlp->ctrl.type == V4L2_CTRL_TYPE_BUTTON) && + !ctrlp->set_fn) { + usbcam_warn(udp, "control %d has no set_fn callout", + ctrlp->ctrl.id); + errors++; + } + } + + return errors; +} + +struct usbcam_claimed_interface { + struct list_head uc_links; + struct usb_interface *uc_intf; +}; + +static int usbcam_usb_probe(struct usb_interface *intf, + const struct usb_device_id *devid) +{ + struct usb_driver *drvp; + struct usbcam_dev *udp = NULL, *udpx; + usbcam_minidrv_t *minidrv; + struct usb_device *dev; + struct list_head *listp; + struct usbcam_claimed_interface *cip; + int minidrv_init_failed = 0; + int res, i; + + /* Locate the mini-driver */ + dev = interface_to_usbdev(intf); + drvp = to_usb_driver(intf->dev.driver); + minidrv = container_of(drvp, usbcam_minidrv_t, um_usbdrv); + + /* Allocate and initialize a device structure */ + udp = (struct usbcam_dev *) kzalloc(sizeof(*udp) + + minidrv->um_dev_privsize, + GFP_KERNEL); + if (!udp) + return -ENOMEM; + + mutex_init(&udp->ud_open_lock); + mutex_init(&udp->ud_lock); + spin_lock_init(&udp->ud_work_lock); + INIT_LIST_HEAD(&udp->ud_work_queue); + udp->ud_minidrv = minidrv; + udp->ud_dev = usb_get_dev(dev); + udp->ud_intf = usb_get_intf(intf); + udp->ud_debug = minidrv->um_debug; + INIT_LIST_HEAD(&udp->ud_drv_links); + kref_init(&udp->ud_kref); + + INIT_LIST_HEAD(&udp->ud_interface_list); + INIT_LIST_HEAD(&udp->ud_frame_cap_queue); + + if (minidrv->um_dev_privsize) { + udp->ud_minidrv_data = &udp[1]; + } + + /* Set up the video4linux structure */ + udp->ud_vdev = minidrv->um_videodev_template; + + /* Add the device to the minidriver's list of active devices */ + usbcam_lock(udp); + + mutex_lock(&minidrv->um_lock); + + /* Inefficiently find an unused ID in the device list */ + i = 0; + udpx = NULL; + list_for_each(listp, &minidrv->um_dev_list) { + udpx = list_entry(listp, struct usbcam_dev, ud_drv_links); + if (udpx->ud_minidrv_id < 0) { + udpx = NULL; + continue; + } + if (udpx->ud_minidrv_id != i) + break; + udpx = NULL; + i++; + } + + udp->ud_minidrv_id = i; + if (udpx) { + list_add_tail(&udp->ud_drv_links, &udpx->ud_drv_links); + } else { + list_add_tail(&udp->ud_drv_links, &minidrv->um_dev_list); + } + + minidrv->um_dev_count++; + kref_get(&minidrv->um_kref); + + snprintf(udp->ud_dev_name, sizeof(udp->ud_dev_name), + "%s-%d", usbcam_drvname(udp->ud_minidrv), + udp->ud_minidrv_id); + + snprintf(udp->ud_vdev.name, sizeof(udp->ud_vdev.name), + "%s USB Camera #%d", + usbcam_drvname(minidrv), udp->ud_minidrv_id + 1); + + + mutex_unlock(&minidrv->um_lock); + + /* Invoke the minidriver initialization callout */ + udp->ud_initializing = 1; + res = usbcam_minidrv_op(udp, init, devid); + udp->ud_initializing = 0; + if (res) { + usbcam_dbg(udp, DEV_STATE, "minidriver init failed: %d", res); + minidrv_init_failed = 1; + goto out_nodisconn; + } + + /* Complain if the device isn't filled out correctly */ + if (!udp->ud_format.width || !udp->ud_format.height) { + usbcam_warn(udp, "minidriver did not set default size"); + res = -EINVAL; + goto out; + } + if (!udp->ud_format.pixelformat) { + usbcam_warn(udp, "minidriver did not set default pixelformat"); + res = -EINVAL; + goto out; + } + + /* Check the control array */ + if (udp->ud_ctrl_array_len && + usbcam_check_ctrl_array(udp, + udp->ud_ctrl_array, + udp->ud_ctrl_array_elem_size, + udp->ud_ctrl_array_len)) { + usbcam_warn(udp, "minidriver supplied ctrl_array with errors"); + res = -EINVAL; + goto out; + } + + usb_set_intfdata(intf, udp); + usbcam_unlock(udp); + + /* + * Register the device with video4linux + * + * BUG: video_register_device() may or may not call back + * into usbcam_videodev_release(), depending on how it fails, + * and if it does call back, its callback may be latent. + * + * We will assume no callback on failure. + */ + + if (udp->ud_vdev.minor != -1) { + /* Minidriver has indicated its preference for a minor */ + res = video_register_device(&udp->ud_vdev, VFL_TYPE_GRABBER, + -1); + if (!res) + goto video_registered; + } + + for (i = 0; i < minidrv->um_video_nr_array_len; i++) { + res = video_register_device(&udp->ud_vdev, VFL_TYPE_GRABBER, + minidrv->um_video_nr_array[i]); + if (!res) + goto video_registered; + } + + res = video_register_device(&udp->ud_vdev, VFL_TYPE_GRABBER, -1); + if (res) { + usbcam_err(udp, "%s: video_register_device failed", + __FUNCTION__); + usbcam_lock(udp); + assert(!udp->ud_videodev_released); + udp->ud_videodev_released = 1; + goto out; + } + +video_registered: + usbcam_get(udp); + /* + * There should now be at least two references on udp: + * One for the primary USB interface in the non-disconnected state + * One for the videodev stuff + * One for each additional claimed interface + */ + + usbcam_dbg(udp, DEV_STATE, "registered as video%d", + udp->ud_vdev.minor); + + usbcam_work_maybe_stop(udp); + return 0; + +out: + assert(!udp->ud_disconnected); + udp->ud_disconnected = 1; + if (usbcam_minidrv_op_present(udp, disconnect)) + usbcam_minidrv_op(udp, disconnect); + +out_nodisconn: + while (!list_empty(&udp->ud_interface_list)) { + cip = list_entry(udp->ud_interface_list.next, + struct usbcam_claimed_interface, + uc_links); + list_del_init(&cip->uc_links); + usb_set_intfdata(cip->uc_intf, NULL); + usb_driver_release_interface(&minidrv->um_usbdrv, + cip->uc_intf); + usb_put_intf(cip->uc_intf); + kfree(cip); + usbcam_put(udp); + } + + usbcam_unlock(udp); + + if (minidrv_init_failed) + kref_put(&udp->ud_kref, usbcam_dev_free); + else + usbcam_put(udp); + return res; +} + +static void usbcam_usb_disconnect(struct usb_interface *intf) +{ + struct usbcam_dev *udp = (struct usbcam_dev *) usb_get_intfdata(intf); + struct usbcam_claimed_interface *iterp, *cip; + int put_intf = 0; + int put_udp = 0; + + if (!udp) + return; + + usbcam_lock(udp); + if (!udp->ud_disconnected) { + udp->ud_disconnected = 1; + usbcam_unlock(udp); + + usbcam_dbg(udp, DEV_STATE, "disconnected"); + video_unregister_device(&udp->ud_vdev); + + usbcam_dbg(udp, DEV_STATE, "unregistered from video%d", + udp->ud_vdev.minor); + + usbcam_lock(udp); + if (usbcam_minidrv_op_present(udp, disconnect)) + usbcam_minidrv_op(udp, disconnect); + + usbcam_capture_stop(udp); + } + + if (intf == udp->ud_intf) { + assert(!udp->ud_disconnected_primary); + udp->ud_disconnected_primary = 1; + put_udp = 1; + + } else { + cip = NULL; + list_for_each_entry(iterp, &udp->ud_interface_list, uc_links) { + if (iterp->uc_intf == intf) { + cip = iterp; + break; + } + } + + if (cip) { + list_del_init(&cip->uc_links); + kfree(cip); + put_intf = 1; + put_udp = 1; + } else { + usbcam_err(udp, "interface %p is not claimed", intf); + } + } + + usb_set_intfdata(intf, NULL); + usbcam_unlock(udp); + + usbcam_work_maybe_stop(udp); + + if (put_intf) + usb_put_intf(intf); + if (put_udp) + usbcam_put(udp); +} + +#if defined(CONFIG_PM) +static int usbcam_usb_suspend(struct usb_interface *intf, pm_message_t msg) +{ + struct usbcam_dev *udp = (struct usbcam_dev *) usb_get_intfdata(intf); + int relock = 0, res = 0; + if (!udp) { + printk(KERN_WARNING "%s: no associated device\n", + __FUNCTION__); + return 0; + } + + usbcam_lock(udp); + if ((intf != udp->ud_intf) || udp->ud_suspended) { + /* Do nothing */ + } else if (usbcam_minidrv_op_present(udp, suspend)) { + usbcam_dbg(udp, DEV_STATE, "invoking minidriver suspend"); + udp->ud_suspended = 1; + if (udp->ud_minidrv->um_ops->unlocked_pm) { + usbcam_unlock(udp); + relock = 1; + } + + res = usbcam_minidrv_op(udp, suspend, msg); + + if (relock) + usbcam_lock(udp); + if (res) + udp->ud_suspended = 0; + } else { + usbcam_dbg(udp, DEV_STATE, "no minidriver suspend method"); + udp->ud_suspended = 1; + } + + usbcam_unlock(udp); + usbcam_work_maybe_stop(udp); + return res; +} + +static int usbcam_usb_resume(struct usb_interface *intf) +{ + struct usbcam_dev *udp = (struct usbcam_dev *) usb_get_intfdata(intf); + int relock = 0, res = 0; + if (!udp) { + printk(KERN_WARNING "%s: no associated device\n", + __FUNCTION__); + return 0; + } + + usbcam_lock(udp); + if ((intf != udp->ud_intf) || !udp->ud_suspended) { + /* Nothing to do! */ + } else if (usbcam_minidrv_op_present(udp, resume)) { + usbcam_dbg(udp, DEV_STATE, "invoking minidriver resume"); + if (udp->ud_minidrv->um_ops->unlocked_pm) { + usbcam_unlock(udp); + relock = 1; + } + res = usbcam_minidrv_op(udp, resume); + if (relock) + usbcam_lock(udp); + } else + usbcam_dbg(udp, DEV_STATE, "no minidriver resume method"); + + if (!res) + udp->ud_suspended = 0; + + usbcam_unlock(udp); + usbcam_work_maybe_stop(udp); + return res; +} +#endif /* defined(CONFIG_PM) */ + + +static const struct usb_driver usbcam_usb_driver_template = { + .name = "usbcam minidriver", + .probe = usbcam_usb_probe, + .disconnect = usbcam_usb_disconnect, +#if defined(CONFIG_PM) + .suspend = usbcam_usb_suspend, + .resume = usbcam_usb_resume, +#endif +}; + + +/* + * Minidriver registration/unregistration + */ + +int usbcam_register_mod(usbcam_minidrv_t **driverpp, + int minidrv_version, const char *minidrv_verx, + const struct usbcam_dev_ops *ops, + const int dev_priv_size, + const struct usb_device_id *id_table, + const int *video_nrs, int video_nrs_len, + int *debug, struct module *md, const char *modname) +{ + usbcam_minidrv_t *minidrv; + int res; + + printk(KERN_INFO "usbcam: registering driver %s %d.%d.%d%s\n", + modname, + (minidrv_version >> 16) & 0xff, + (minidrv_version >> 8) & 0xff, + minidrv_version & 0xff, + minidrv_verx ? minidrv_verx : ""); + + minidrv = (usbcam_minidrv_t *) kzalloc(sizeof(*minidrv), GFP_KERNEL); + if (!minidrv) { + err("%s: Failed to allocate usbcam_minidrv_t", __FUNCTION__); + return -ENOMEM; + } + + kref_init(&minidrv->um_kref); + minidrv->um_owner = md; + minidrv->um_modname = modname; + minidrv->um_version = minidrv_version; + minidrv->um_debug = debug; + minidrv->um_dev_privsize = dev_priv_size; + INIT_LIST_HEAD(&minidrv->um_dev_list); + mutex_init(&minidrv->um_lock); + minidrv->um_video_nr_array = video_nrs; + minidrv->um_video_nr_array_len = video_nrs_len; + + minidrv->um_ops = ops; + + minidrv->um_usbdrv = usbcam_usb_driver_template; + minidrv->um_usbdrv.name = usbcam_drvname(minidrv); + minidrv->um_usbdrv.id_table = id_table; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19) + minidrv->um_usbdrv.supports_autosuspend = + minidrv->um_ops->supports_autosuspend; +#endif + + /* + * We have a separate fops per minidriver structure so that + * module reference counting works without egregious hacks. + */ + minidrv->um_v4l_fops = usbcam_v4l_fops_template; + minidrv->um_v4l_fops.owner = minidrv->um_owner; + + minidrv->um_videodev_template = usbcam_videodev_template; + minidrv->um_videodev_template.fops = &minidrv->um_v4l_fops; + + *driverpp = minidrv; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21) + res = usb_register_driver(&minidrv->um_usbdrv, minidrv->um_owner, + minidrv->um_modname); +#else + res = usb_register_driver(&minidrv->um_usbdrv, minidrv->um_owner); +#endif + if (res) { + kref_put(&minidrv->um_kref, usbcam_minidrv_release); + *driverpp = NULL; + } + + usbcam_dbgm(minidrv, DEV_STATE, "registered minidriver"); + + return res; +} +EXPORT_SYMBOL(usbcam_register_mod); + +void usbcam_unregister(usbcam_minidrv_t *minidrv) +{ + usbcam_dbgm(minidrv, DEV_STATE, "unregistering minidriver"); + + usb_deregister(&minidrv->um_usbdrv); + + if (minidrv->um_dev_count) { + err("%s: %d \"%s\" devices remain", + __FUNCTION__, minidrv->um_dev_count, + usbcam_drvname(minidrv)); + } + + kref_put(&minidrv->um_kref, usbcam_minidrv_release); +} +EXPORT_SYMBOL(usbcam_unregister); + + +int usbcam_claim_interface(struct usbcam_dev *udp, int ifnum) +{ + struct usb_interface *intf; + struct usbcam_claimed_interface *cip; + int res; + + usbcam_chklock(udp); + + if (!udp->ud_initializing) { + usbcam_warn(udp, "%s may only be called from minidriver init", + __FUNCTION__); + return -EINVAL; + } + + intf = usb_ifnum_to_if(udp->ud_dev, ifnum); + if (!intf) { + usbcam_warn(udp, "%s: interface %d does not exist", + __FUNCTION__, ifnum); + return -ENODEV; + } + + res = usb_driver_claim_interface(&udp->ud_minidrv->um_usbdrv, + intf, NULL); + + if (!res) { + cip = kmalloc(sizeof(*cip), GFP_KERNEL); + if (!cip) { + usb_driver_release_interface(&udp->ud_minidrv-> + um_usbdrv, intf); + return -ENOMEM; + } + + INIT_LIST_HEAD(&cip->uc_links); + cip->uc_intf = usb_get_intf(intf); + usb_set_intfdata(intf, udp); + usbcam_get(udp); + list_add_tail(&cip->uc_links, &udp->ud_interface_list); + } + + return res; +} +EXPORT_SYMBOL(usbcam_claim_interface); + + +/* + * Traverse the alternate setting list and find one that provides + * the least bandwidth that satisfies the minimum requirement. + */ +int usbcam_choose_altsetting(struct usbcam_dev *udp, int ifnum, + int pipe, int bytes_per_sec_min, + int pkt_min, int pkt_max, + int *altsetting_nr) +{ + struct usb_interface *intf; + const struct usb_host_interface *aintf; + const struct usb_endpoint_descriptor *epd = NULL; + int i, j; + + int wmp, bw; + int best_alt = -1, best_alt_bw = 0; + + usbcam_chklock(udp); + + if (udp->ud_disconnected) { + usbcam_warn(udp, "%s: device is disconnected", __FUNCTION__); + return -ENODEV; + } + + if (ifnum < 0) + ifnum = udp->ud_intf->cur_altsetting->desc.bInterfaceNumber; + + intf = usb_ifnum_to_if(udp->ud_dev, ifnum); + if (!intf) { + usbcam_warn(udp, "%s: interface %d does not exist", + __FUNCTION__, ifnum); + return -ENODEV; + } + + if ((bytes_per_sec_min >= 0) && + !usb_pipeisoc(pipe) && !usb_pipeint(pipe)) { + usbcam_warn(udp, "%s: minidriver specified bytes_per_sec_min " + "on non-iso non-int pipe", __FUNCTION__); + } + + for (i = 0; i < intf->num_altsetting; i++) { + + aintf = &intf->altsetting[i]; + for (j = 0; j < aintf->desc.bNumEndpoints; j++) { + epd = &aintf->endpoint[j].desc; + if ((epd->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK) == + usb_pipeendpoint(pipe)) + break; + } + + if (j == aintf->desc.bNumEndpoints) { + /* No endpoint 6 in this descriptor, huh?? */ + usbcam_dbg(udp, ALTSETTING, + "altsetting %d has no EP%d", + i, usb_pipeendpoint(pipe)); + continue; + } + + if (((usb_pipetype(pipe) == PIPE_ISOCHRONOUS) && + !usb_endpoint_xfer_isoc(epd)) || + ((usb_pipetype(pipe) == PIPE_INTERRUPT) && + !usb_endpoint_xfer_int(epd)) || + (usb_pipein(pipe) && !usb_endpoint_dir_in(epd)) || + (!usb_pipein(pipe) && usb_endpoint_dir_in(epd))) { + /* Something is horribly wrong */ + usbcam_dbg(udp, ALTSETTING, + "altsetting %d has unexpected EP%d", + i, usb_pipeendpoint(pipe)); + continue; + } + + bw = 0; + wmp = le16_to_cpu(epd->wMaxPacketSize); + + /* Bandwidth only applies to iso & int pipes */ + if (usb_pipeisoc(pipe) || usb_pipeint(pipe)) { + if (udp->ud_dev->speed == USB_SPEED_HIGH) { + /* 8 uframes per regular frame */ + bw = 8000; + + /* high bandwidth endpoint? */ + wmp = ((wmp & 0x7ff) * + (((wmp >> 11) & 0x3) + 1)); + } else { + bw = 1000; + wmp &= 0x7ff; + } + + bw *= wmp; + + /* Divide by interval / frame skippage */ + bw = bw / (1 << (epd->bInterval - 1)); + + usbcam_dbg(udp, ALTSETTING, + "altsetting %d provides %d B/s bandwidth", + i, bw); + + /* Check the bandwidth */ + if (bw < bytes_per_sec_min) + continue; + + } else + wmp &= 0x7ff; + + /* Check the packet size */ + if (((pkt_min >= 0) && (wmp < pkt_min)) || + ((pkt_max >= 0) && (wmp > pkt_max))) + continue; + + if ((best_alt < 0) || (bw < best_alt_bw)) { + best_alt = i; + best_alt_bw = bw; + } + } + + if (best_alt == -1) + return -ENODEV; + + *altsetting_nr = best_alt; + return 0; +} +EXPORT_SYMBOL(usbcam_choose_altsetting); + + +/* + * DMA buffer helper routines + */ +static int usbcam_urb_allocbuf(struct usbcam_dev *udp, struct urb *urbp, + size_t nbytes) +{ + urbp->transfer_buffer = usb_buffer_alloc(udp->ud_dev, + nbytes, + GFP_KERNEL, + &urbp->transfer_dma); + if (!urbp->transfer_buffer) + return -ENOMEM; + + urbp->dev = udp->ud_dev; + urbp->transfer_buffer_length = nbytes; + urbp->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + return 0; +} + +static inline void usbcam_urb_freebuf(struct urb *urbp) +{ + usb_buffer_free(urbp->dev, + urbp->transfer_buffer_length, + urbp->transfer_buffer, + urbp->transfer_dma); +} + + +/* + * Isochronous stream helper functions + * This depends on the other usbcam stuff, but they don't depend on it, + * and it should be considered an extension sub-library. + */ + +/* Default isostream parameters */ +#define USBCAM_DFL_ISO_URBS 8 +#define USBCAM_DFL_ISO_URB_PKTS 32 + +/* + * This structure represents one Isoc request - URB and buffer + */ +struct usbcam_isobuf { + struct list_head ib_links; + struct usbcam_isostream *ib_isostream; + struct urb *ib_urb; + struct task_struct **ib_worker; + struct usbcam_workitem ib_workitem; +}; + +/* isop->iso_lock must be held on entry */ +static void usbcam_isostream_resubmit(struct usbcam_isostream *isop, + struct usbcam_isobuf *ibp) +{ + int res; + + list_del(&ibp->ib_links); + list_add(&ibp->ib_links, &isop->iso_active_list); + + res = usb_submit_urb(ibp->ib_urb, GFP_ATOMIC); + if (res == -EL2NSYNC) + res = usb_submit_urb(ibp->ib_urb, GFP_ATOMIC); + if (res) { + usbcam_dbg(isop->iso_dev, ISOSTREAM, + "iso resubmit %p failed: %d", ibp, res); + isop->iso_resubmit_err = res; + + list_del(&ibp->ib_links); + list_add(&ibp->ib_links, &isop->iso_unused_list); + + (void) usbcam_work_queue(isop->iso_dev, + &isop->iso_error_workitem); + } +} + +static void usbcam_isostream_urb_process(struct usbcam_workitem *work) +{ + struct usbcam_isobuf *ibp = container_of(work, struct usbcam_isobuf, + ib_workitem); + struct usbcam_isostream *isop = ibp->ib_isostream; + struct task_struct *me = current; + long flags; + int i; + + usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso processing %p", ibp); + + assert(!ibp->ib_worker); + ibp->ib_worker = &me; + + for (i = 0; i < ibp->ib_urb->number_of_packets; i++) { + char *buf = (((char *) ibp->ib_urb->transfer_buffer) + + ibp->ib_urb->iso_frame_desc[i].offset); + int len = ibp->ib_urb->iso_frame_desc[i].actual_length; + int status = ibp->ib_urb->iso_frame_desc[i].status; + + ibp->ib_urb->iso_frame_desc[i].actual_length = 0; + ibp->ib_urb->iso_frame_desc[i].status = 0; + + isop->iso_ops->packet_done(isop->iso_dev, + isop, buf, len, status); + if (!me) + return; + } + + assert(ibp->ib_worker == &me); + ibp->ib_worker = NULL; + + spin_lock_irqsave(&isop->iso_lock, flags); + + if (isop->iso_streaming && (isop->iso_resubmit_err == -ENOSPC)) { + /* Try to limp along with iso underflows */ + + usbcam_isostream_resubmit(isop, ibp); + + if ((isop->iso_active_list.next != + isop->iso_active_list.prev) && + (isop->iso_resubmit_err == -ENOSPC)) + isop->iso_resubmit_err = 0; + + } else { + list_del(&ibp->ib_links); + list_add(&ibp->ib_links, &isop->iso_unused_list); + } + + spin_unlock_irqrestore(&isop->iso_lock, flags); +} + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19) +static void usbcam_isostream_urb_complete(struct urb *urb) +#else +static void usbcam_isostream_urb_complete(struct urb *urb, + struct pt_regs *unused) +#endif +{ + struct usbcam_isobuf *ibp = (struct usbcam_isobuf *) urb->context; + struct usbcam_isostream *isop = ibp->ib_isostream; + long flags; + int res; + + usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso urb complete: %p", ibp); + + spin_lock_irqsave(&isop->iso_lock, flags); + + if (list_empty(&ibp->ib_links)) { + /* We are being singled out for cancelation, do nothing */ + usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso canceled, ignoring"); + goto done; + } + + else if (!isop->iso_streaming) { + /* Quietly and politely move this URB to the unused list */ + list_del(&ibp->ib_links); + list_add(&ibp->ib_links, &isop->iso_unused_list); + usbcam_dbg(isop->iso_dev, ISOSTREAM, + "not resubmitting, streaming off"); + goto done; + } + + /* Move to the done queue, submit a new URB, wake */ + list_del(&ibp->ib_links); + list_add_tail(&ibp->ib_links, &isop->iso_complete_list); + + res = usbcam_work_queue(isop->iso_dev, &ibp->ib_workitem); + if (res) { + assert(res == -EBUSY); + list_del(&ibp->ib_links); + list_add_tail(&ibp->ib_links, &isop->iso_unused_list); + goto done; + } + + if (isop->iso_resubmit_err && + (isop->iso_resubmit_err != -ENOSPC)) { + /* Nothing to do here... */ + usbcam_dbg(isop->iso_dev, ISOSTREAM, + "not resubmitting, pending error"); + goto done; + } + + if (list_empty(&isop->iso_unused_list)) { + isop->iso_resubmit_err = -ENOSPC; + usbcam_dbg(isop->iso_dev, ISOSTREAM, + "not resubmitting, no URBs available"); + goto done; + } + + ibp = list_entry(isop->iso_unused_list.next, + struct usbcam_isobuf, ib_links); + + usbcam_isostream_resubmit(isop, ibp); + +done: + spin_unlock_irqrestore(&isop->iso_lock, flags); +} + +static void usbcam_isostream_freebuf(struct usbcam_isobuf *ibp) +{ + assert(list_empty(&ibp->ib_links)); + + usbcam_urb_freebuf(ibp->ib_urb); + usb_free_urb(ibp->ib_urb); + kfree(ibp); +} + +static struct usbcam_isobuf * +usbcam_isostream_allocbuf(struct usbcam_isostream *isop) +{ + struct usbcam_isobuf *ibp; + size_t nbytes; + int i; + + ibp = kzalloc(sizeof(*ibp), GFP_KERNEL); + if (!ibp) + return NULL; + + INIT_LIST_HEAD(&ibp->ib_links); + usbcam_work_init(&ibp->ib_workitem, usbcam_isostream_urb_process); + ibp->ib_isostream = isop; + + ibp->ib_urb = usb_alloc_urb(isop->iso_packet_count, GFP_KERNEL); + if (!ibp->ib_urb) { + kfree(ibp); + return NULL; + } + + nbytes = (isop->iso_packet_len * isop->iso_packet_count); + + if (usbcam_urb_allocbuf(isop->iso_dev, ibp->ib_urb, nbytes)) { + usb_free_urb(ibp->ib_urb); + kfree(ibp); + return NULL; + } + + ibp->ib_urb->context = ibp; + ibp->ib_urb->number_of_packets = isop->iso_packet_count; + + + for (i = 0; i < isop->iso_packet_count; i++) { + ibp->ib_urb->iso_frame_desc[i].offset = + (i * isop->iso_packet_len); + ibp->ib_urb->iso_frame_desc[i].length = isop->iso_packet_len; + ibp->ib_urb->iso_frame_desc[i].actual_length = 0; + ibp->ib_urb->iso_frame_desc[i].status = 0; + } + + return ibp; +} + +static void usbcam_isostream_freebufs(struct list_head *head) +{ + struct usbcam_isobuf *ibp; + while (!list_empty(head)) { + ibp = list_entry(head->next, struct usbcam_isobuf, ib_links); + list_del_init(&ibp->ib_links); + usbcam_isostream_freebuf(ibp); + } +} + +static int usbcam_isostream_allocbufs(struct usbcam_isostream *isop, + struct list_head *head, int count) +{ + struct usbcam_isobuf *ibp; + struct list_head new_bufs; + + INIT_LIST_HEAD(&new_bufs); + + while (count--) { + ibp = usbcam_isostream_allocbuf(isop); + if (!ibp) { + usbcam_isostream_freebufs(&new_bufs); + return -ENOMEM; + } + list_add_tail(&ibp->ib_links, &new_bufs); + } + + list_splice(&new_bufs, head); + return 0; +} + +static void usbcam_isostream_error(struct usbcam_workitem *work) +{ + struct usbcam_isostream *isop = container_of(work, + struct usbcam_isostream, + iso_error_workitem); + unsigned long flags; + int sts; + + spin_lock_irqsave(&isop->iso_lock, flags); + sts = isop->iso_resubmit_err; + isop->iso_resubmit_err = 0; + spin_unlock_irqrestore(&isop->iso_lock, flags); + + if (sts && isop->iso_ops && isop->iso_ops->submit_error) + isop->iso_ops->submit_error(isop->iso_dev, isop, sts); +} + +int usbcam_isostream_init(struct usbcam_isostream *isop, + struct usbcam_dev *udp, + int ep, struct usbcam_isostream_ops *ops, + int pktcount, int nurbs, int interval, int pktlen) +{ + int res; + + if (!interval) { + /* FIXME: find the appropriate interval for the endpoint */ + return -EINVAL; + } + + if (!pktlen) { + /* Choose a packet length based on the current altsetting */ + pktlen = usb_maxpacket(udp->ud_dev, + usb_rcvisocpipe(udp->ud_dev, ep), 0); + if (!pktlen) { + usbcam_dbg(udp, ISOSTREAM, + "current altsetting for ep%d has " + "maxpacket=0", ep); + return -EINVAL; + } + if (udp->ud_dev->speed == USB_SPEED_HIGH) + pktlen = (pktlen & 0x7ff) * + (((pktlen >> 11) & 0x3) + 1); + else + pktlen &= 0x7ff; + + usbcam_dbg(udp, ISOSTREAM, "isostream using pktlen %d", + pktlen); + } + + if (!pktcount) + pktcount = USBCAM_DFL_ISO_URB_PKTS; + if (!nurbs) + nurbs = USBCAM_DFL_ISO_URBS; + if (nurbs < 2) { + usbcam_warn(udp, "%s: at least two iso URBs are required", + __FUNCTION__); + nurbs = 2; + } + + memset(isop, 0, sizeof(*isop)); + isop->iso_dev = udp; + isop->iso_endpoint = ep; + isop->iso_packet_len = pktlen; + isop->iso_packet_count = pktcount; + isop->iso_urb_interval = interval; + isop->iso_ops = ops; + spin_lock_init(&isop->iso_lock); + INIT_LIST_HEAD(&isop->iso_unused_list); + INIT_LIST_HEAD(&isop->iso_active_list); + INIT_LIST_HEAD(&isop->iso_complete_list); + usbcam_work_init(&isop->iso_error_workitem, usbcam_isostream_error); + + res = usbcam_isostream_allocbufs(isop, &isop->iso_unused_list, nurbs); + return res; +} +EXPORT_SYMBOL(usbcam_isostream_init); + +void usbcam_isostream_stop(struct usbcam_isostream *isop) +{ + long flags; + struct usbcam_isobuf *ibp, *prev; + int res; + + usbcam_chklock(isop->iso_dev); + + usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso stream stopping"); + spin_lock_irqsave(&isop->iso_lock, flags); + + if (isop->iso_streaming) { + isop->iso_streaming = 0; + + /* Cancel all in-flight requests */ + while (!list_empty(&isop->iso_active_list)) { + ibp = list_entry(isop->iso_active_list.prev, + struct usbcam_isobuf, ib_links); + list_del_init(&ibp->ib_links); + spin_unlock_irqrestore(&isop->iso_lock, flags); + usb_kill_urb(ibp->ib_urb); + spin_lock_irqsave(&isop->iso_lock, flags); + list_add(&ibp->ib_links, &isop->iso_unused_list); + } + + /* Cancel all done queue work items */ + list_for_each_entry_safe(ibp, prev, + &isop->iso_complete_list, + ib_links) { + + res = usbcam_work_cancel(isop->iso_dev, + &ibp->ib_workitem); + + if (!ibp->ib_worker) + assert(!res); + else { + assert(res); + assert(*ibp->ib_worker == current); + *ibp->ib_worker = NULL; + ibp->ib_worker = NULL; + } + + list_del(&ibp->ib_links); + list_add_tail(&ibp->ib_links, &isop->iso_unused_list); + } + + /* Cancel the error work item */ + (void) usbcam_work_cancel(isop->iso_dev, + &isop->iso_error_workitem); + } + + /* Clear the resubmission error code */ + isop->iso_resubmit_err = 0; + + spin_unlock_irqrestore(&isop->iso_lock, flags); + usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso stream stopped"); +} +EXPORT_SYMBOL(usbcam_isostream_stop); + +int usbcam_isostream_start(struct usbcam_isostream *isop) +{ + long flags; + struct usbcam_dev *udp; + struct usbcam_isobuf *ibp; + int submitted = 0, res; + + udp = isop->iso_dev; + + usbcam_chklock(udp); + + spin_lock_irqsave(&isop->iso_lock, flags); + if (isop->iso_streaming) { + spin_unlock_irqrestore(&isop->iso_lock, flags); + usbcam_warn(udp, "%s: already streaming", __FUNCTION__); + return -EEXIST; + } + + if (list_empty(&isop->iso_unused_list) || + (isop->iso_unused_list.next == isop->iso_unused_list.prev)) { + spin_unlock_irqrestore(&isop->iso_lock, flags); + usbcam_warn(udp, "%s: not enough unused iso URBs", + __FUNCTION__); + return -ENOENT; + } + + usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso stream starting"); + isop->iso_streaming = 1; + + /* + * Fill out generic details of each URB. + * The interval and endpoints can be changed after init, but + * the packet size and URB count cannot. + */ + list_for_each_entry(ibp, &isop->iso_unused_list, ib_links) { + ibp->ib_urb->pipe = usb_rcvisocpipe(ibp->ib_urb->dev, + isop->iso_endpoint); + ibp->ib_urb->interval = isop->iso_urb_interval; + ibp->ib_urb->transfer_flags |= URB_ISO_ASAP; + ibp->ib_urb->complete = usbcam_isostream_urb_complete; + ibp->ib_urb->start_frame = 0; + } + + /* + * Submit two (2) URBs. + */ + while (1) { + assert(!list_empty(&isop->iso_unused_list)); + ibp = list_entry(isop->iso_unused_list.next, + struct usbcam_isobuf, ib_links); + + list_del(&ibp->ib_links); + list_add_tail(&ibp->ib_links, &isop->iso_active_list); + + spin_unlock_irqrestore(&isop->iso_lock, flags); + + usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso submit %p", ibp); + res = usb_submit_urb(ibp->ib_urb, GFP_KERNEL); + if (res == -EL2NSYNC) + res = usb_submit_urb(ibp->ib_urb, GFP_KERNEL); + + if (res) { + usbcam_dbg(isop->iso_dev, ISOSTREAM, + "%s: ISO URB submit failed: %d", + __FUNCTION__, res); + usbcam_isostream_stop(isop); + return res; + } + + if (++submitted == 2) + break; + + spin_lock_irqsave(&isop->iso_lock, flags); + } + + usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso stream started"); + return 0; + +} +EXPORT_SYMBOL(usbcam_isostream_start); + +void usbcam_isostream_cleanup(struct usbcam_isostream *isop) +{ + usbcam_isostream_stop(isop); + usbcam_isostream_freebufs(&isop->iso_unused_list); + assert(list_empty(&isop->iso_active_list)); + assert(list_empty(&isop->iso_complete_list)); +} +EXPORT_SYMBOL(usbcam_isostream_cleanup); + + +#ifdef usbcam_hexdump +#undef usbcam_hexdump +#endif +#define dumpable_char(X) (((X) >= ' ') && ((X) <= '~')) +void usbcam_hexdump(struct usbcam_dev *udp, const u8 *buf, size_t len) +{ + const int bpl = 16; + const int cend_max = ((bpl * 4) + 1); + static const char n2x[16] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + char x, outbuf[cend_max + 1]; + unsigned int cstart, cend, xpos, cpos, offset; + + offset = 0; + cstart = (3 * bpl) + 1; + cend = cstart + bpl; + outbuf[cend] = '\0'; + goto beginning; + + while (len) { + x = *buf++; + outbuf[xpos++] = n2x[(x >> 4) & 0xf]; + outbuf[xpos++] = n2x[x & 0xf]; + outbuf[cpos+cstart] = dumpable_char(x) ? x : '.'; + cpos++; + xpos++; + len--; + + if (!len || (cpos == bpl)) { + printk(KERN_DEBUG "%s: %08x %s\n", + udp->ud_dev_name, offset, outbuf); + offset += bpl; + + beginning: + memset(outbuf, ' ', cend); + cpos = 0; + xpos = 0; + } + } +} +EXPORT_SYMBOL(usbcam_hexdump); + + +MODULE_DESCRIPTION("Abstraction Library for USB Webcam Drivers"); +MODULE_AUTHOR("Sam Revitch <samr7@cs.washington.edu>, " + "Alexander Hixon <hixon.alexander@mediati.org>"); +MODULE_LICENSE("GPL"); diff --git a/usbcam/usbcam.h b/usbcam/usbcam.h @@ -0,0 +1,757 @@ +/* + * USBCAM abstraction library for USB webcam drivers + * Version 0.1.1 + * + * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu> + * + * 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, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This library is intended to ease the process of creating drivers + * for simpler USB webcam devices. It handles most V4L interactions, and + * all USB driver entry points. It provides a minidriver callback API + * for handling most common tasks. + */ + +#ifndef __USBCAM_H__ +#define __USBCAM_H__ + +#ifdef __KERNEL__ + +#define CONFIG_USB_USBCAM_DEBUG + +#include <linux/usb.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/videodev.h> +#include <media/v4l2-common.h> + +/* The actual per-minidriver structure is opaque */ +typedef struct usbcam_minidrv usbcam_minidrv_t; +struct usbcam_dev; +struct usbcam_curframe; + + +/* + * Log file and debug infrastructure + */ +#define usbcam_info(UDP, FMT, ARG...) do { \ + printk(KERN_INFO "%s: " FMT "\n", \ + (UDP)->ud_dev_name, ## ARG); \ +} while (0) +#define usbcam_warn(UDP, FMT, ARG...) do { \ + printk(KERN_WARNING "%s: " FMT "\n", \ + (UDP)->ud_dev_name, ## ARG); \ +} while (0) +#define usbcam_err(UDP, FMT, ARG...) do { \ + printk(KERN_ERR "%s: " FMT "\n", \ + (UDP)->ud_dev_name, ## ARG); \ +} while (0) + + +#if defined(CONFIG_USB_USBCAM_DEBUG) +#define usbcam_dbg(UDP, SUBSYS, FMT, ARG...) do { \ + if ((UDP)->ud_debug && \ + (*(UDP)->ud_debug & (1UL << USBCAM_DBG_ ## SUBSYS))) \ + printk(KERN_DEBUG "%s: " FMT "\n", \ + (UDP)->ud_dev_name, ## ARG); \ +} while (0) +#define usbcam_assert(expr) \ +do { \ + if (!(expr)) { \ + printk(KERN_ERR "%s:%d: assertion \"" # expr "\" " \ + "failed", __FILE__, __LINE__); \ + dump_stack(); \ + } \ +} while (0) +extern void usbcam_hexdump(struct usbcam_dev *udp, const u8 *buf, size_t len); +#else +#define usbcam_dbg(UDP, SUBSYS, FMT, ARG...) +#define usbcam_assert(expr) +#define usbcam_hexdump(udp, buf, len) +#endif + +/* + * Debug subsystem bit values + * + * The usbcam_dev.ud_debug debug subsystem mask is a pointer to a + * per-minidriver integer, which is usually a module parameter and can + * be manipulated via sysfs. + */ +enum { + USBCAM_DBG_VIDEOBUF = 0, + USBCAM_DBG_CAPTURE, + USBCAM_DBG_IOCTL_BUF, + USBCAM_DBG_IOCTL_FMT, + USBCAM_DBG_IOCTL_MISC, + USBCAM_DBG_DEV_STATE, + USBCAM_DBG_ALTSETTING, + USBCAM_DBG_URBSTREAM, + + /* First bit available to minidrivers */ + USBCAM_DBGBIT_MD_START = 8, +}; + + + +/* + * The usbcam_pix_fmt structure is used to describe a pixel format natively + * supported by the driver to V4L clients. A minidriver may set the + * ud_fmt_array member of usbcam_dev to point to an array of these + * structures, and the array will be used to service the ENUMFMT ioctl, + * as long as the minidriver does not intercept that ioctl. + */ +struct usbcam_pix_fmt { + char description[32]; + unsigned int pixelformat; + unsigned int flags; +}; + + +/* + * usbcam_dev: The per-device structure representing: + * (1) A USB device/interface + * (2) A registered V4L device + * + * Commented fields are of interest to minidrivers. + * Uncommented fields should be considered opaque. + */ + +struct usbcam_dev { + /* + * ud_vdev is the video4linux device structure. + * The minidriver may be interested in a few fields: + * ud_vdev.name: The device name displayed by applications + */ + struct video_device ud_vdev; + + /* + * ud_dev, ud_intf: The associated USB device/primary interface + * These members are read-only to all except when set during + * usbcam_dev structure initialization. + */ + struct usb_device *ud_dev; + struct usb_interface *ud_intf; + + usbcam_minidrv_t *ud_minidrv; + + /* + * ud_minidrv_data: Minidriver private data + * During structure initialization, if a minidriver structure + * size was specified to usbcam_register(), that many bytes of + * memory are allocated, the allocation is assigned here, and + * the original allocation is automatically freed with the + * usbcam_dev structure. Otherwise this member is initialized + * to NULL. + * The minidriver may set whatever policies it wants with how + * this field is managed. + */ + void *ud_minidrv_data; + + struct list_head ud_drv_links; + + /* + * ud_minidrv_id: The device's unique number among all devices + * belonging to the minidriver. Set prior to the minidriver's + * init handler being called. Read-only at all other times. + */ + int ud_minidrv_id; + + /* + * ud_lock: the mutex protecting most of this structure and all + * minidriver entry points. + */ + struct mutex ud_lock; + + /* + * ud_format: Currently configured size/format + * Protected by ud_lock + * Modified only by minidriver + * Examined by ioctl handler and framebuffer allocator + */ + struct v4l2_pix_format ud_format; + + /* + * ud_fmt_array, ud_fmt_array_elem_size, ud_fmt_array_len: + * Supported pixel formats for enumeration + * Protected by ud_lock + * Modified only by minidriver, usually set by init callout + * Examined by default ioctl handler for ENUMFMT + */ + const struct usbcam_pix_fmt *ud_fmt_array; + int ud_fmt_array_elem_size; + int ud_fmt_array_len; + + struct list_head ud_ctrl_list; + + /* + * ud_capturing: Minidriver capture-in-progress flag + * Protected by ud_lock + * Modified only by minidriver, e.g. cap_start, cap_stop + * Examined by framebuffer interface and S_FMT ioctl handler + */ + unsigned int ud_capturing : 1, + + /* + * ud_disconnected: Set if the underlying USB device has been + * disconnected, just prior to invoking the minidriver disconnect + * callout, if one is provided. This field should be considered + * read-only to minidrivers. + */ + ud_disconnected : 1, + + ud_initializing : 1, + ud_suspended : 1, + ud_disconnected_primary : 1, + ud_videodev_released : 1; + + struct kref ud_kref; + struct list_head ud_interface_list; + + struct list_head ud_frame_cap_queue; + + int *ud_debug; + + int ud_work_refs; + spinlock_t ud_work_lock; + int ud_work_lockwait; + struct task_struct *ud_work_thread; + struct list_head ud_work_queue; + + struct mutex ud_open_lock; + int ud_user_refs; + unsigned int ud_autopm_ref : 1; + struct file *ud_excl_owner; + + /* + * ud_dev_name: Name of device as used for worker thread names and + * debug messages. The minidriver may set this field in its init + * callout, but should consider it read-only after that point. + */ + char ud_dev_name[32]; +}; + + +/* + * Per-device reference counting helpers + * + * When the last reference is released, the minidriver's release + * callout will be invoked. + * + * usbcam_get() may be called from any context. + * usbcam_put() can sleep, and may not be called while holding the device + * lock (see below). + */ +#define usbcam_get(UDP) kref_get(&(UDP)->ud_kref) +extern void usbcam_put(struct usbcam_dev *udp); + + +/* + * Per-Device locking helpers + * + * The minidriver callouts, which are described below in usbcam_dev_ops + * and usbcam_urbstream_ops, are all invoked with the device lock held. + * Also, all usbcam work queue callouts are invoked with the device lock + * held. + * + * Minidrivers that must be entered through paths other than those + * described above may need to examine or update data structures + * protected by the device lock, and may do so using the lock + * acquire/release macros. For example, minidrivers that use + * procfs/sysfs file operations, or completion callouts unsuitable for + * the usbcam work queue will need this. + * + * Minidrivers may release and reacquire the lock inside of certain + * usbcam minidriver callouts (see + */ +#define usbcam_lock(UDP) mutex_lock(&(UDP)->ud_lock) +#define usbcam_unlock(UDP) mutex_unlock(&(UDP)->ud_lock) +#define usbcam_chklock(UDP) usbcam_assert(mutex_is_locked(&(UDP)->ud_lock)) + + +/* + * MINIDRIVER CALLOUTS + * + * Callouts invoked at various stages of the lifetime of a usbcam_dev + * device. + * + * REQUIRED: init, cap_start, cap_stop. + * + * All other callouts are optional. + * + * By default, all callouts are invoked with the device lock held. + * + * The device lock is not intended to be held for long periods of time. + * Some operations may run for a long time, and may need to sleep + * waiting for an operation to complete on the device. If these + * operations need to be able to run at the same time as capture, the + * minidriver must ensure that it does not hold the device lock while + * waiting for such long operations to complete. + * + * To support operating without the device lock, there are flags + * in the ops structure to selectively disable this behavior for the + * ioctl callout, control callouts, and power management callouts. + * The minidriver will be responsible for acquiring and releasing the + * device lock, and re-verifying the device state upon reacquisition. + * + * Certain callouts are explicitly restricted from releasing and + * reacquiring the device lock. These include: + * + * disconnect, open, close, try_format, set_format, cap_start, cap_stop, + * testpattern + * + */ +struct usbcam_dev_ops { + int unlocked_ioctl : 1, + unlocked_ctrl : 1, + unlocked_pm : 1, + supports_autosuspend : 1, + no_workref_on_open : 1; + + /* init: invoked when a matching USB device is discovered */ + int (*init)(struct usbcam_dev *udp, const struct usb_device_id *); + + /* suspend/resume: invoked from power management paths */ + int (*suspend)(struct usbcam_dev *udp, pm_message_t message); + int (*resume)(struct usbcam_dev *udp); + + /* disconnect: invoked when a device has been disconnected */ + void (*disconnect)(struct usbcam_dev *udp); + + /* release: invoked when a usbcam_dev is about to be freed */ + void (*release)(struct usbcam_dev *udp); + + /* open: invoked on first user open of the device */ + int (*open)(struct usbcam_dev *udp); + + /* close: invoked on last user close of the device */ + void (*close)(struct usbcam_dev *udp); + + /* try_format: invoked to negotiate capture format */ + int (*try_format)(struct usbcam_dev *udp, + struct v4l2_pix_format *fmt_in_out); + + /* set_format: invoked when the capture format is being set */ + int (*set_format)(struct usbcam_dev *udp, + struct v4l2_pix_format *fmt_in_out); + + /* ioctl: ioctl call interception for most commands */ + int (*ioctl)(struct usbcam_dev *udp, int cmd, void *arg); + + /* cap_start: invoked when capture should be initiated */ + int (*cap_start)(struct usbcam_dev *udp); + + /* cap_stop: invoked when capture should be halted */ + void (*cap_stop)(struct usbcam_dev *udp); +}; + + +/* + * Minidrivers may register themselves using usbcam_register_mod(), + * although the recommended method is to use the usbcam_register() + * macro instead. + * + * The driver should keep a usbcam_minidrv structure pointer as a + * handle to the usbcam registration, as it will need it later when it + * needs to unregister itself. usbcam_register_mod() accepts a pointer + * to this structure, and fills it in on success. + * + * The minidriver must report a primary version number in KERNEL_VERSION + * format, which is used by the default VIDIOC_QUERYCAP ioctl handler. + * The minidriver may optionally report an extra version string. + * + * usbcam_register_mod() will cause usbcam to register a new USB driver + * with the given device ID table, so that it may be called when + * matching devices are discovered. + * + * When a matching device is detected, usbcam will: + * -> Allocate a usbcam_dev structure, including a minidriver-specific + * portion of a given size, attached to ud_minidrv_data + * -> Invoke the ->init() callout for the minidriver + * -> If successful, register a new video4linux device and + * start accepting V4L API requests on it + * + * usbcam_register() depends on variables defined by the + * DEFINE_USBCAM_MODPARAMS macro to function correctly. This defines + * a set of static variables and marks them as module parameters. These + * include: + * -> video_nr: Favored video4linux minor numbers + * -> debug: A pointer to the debug level variable + */ + +extern int usbcam_register_mod(usbcam_minidrv_t **driverpp, + int mdrv_version, const char *mdrv_verx, + const struct usbcam_dev_ops *ops, + const int dev_priv_size, + const struct usb_device_id *id_table, + const int *video_nrs, int video_nrs_len, + int *debug, struct module *md, + const char *modname); + +/* + * usbcam_unregister() will remove a minidriver registration with the + * USB subsystem, and will prepare the minidriver structure to be + * freed. It is safe to call this API in paths other than minidriver + * module unload, as long as the minidriver is registered. + */ +extern void usbcam_unregister(usbcam_minidrv_t *driverp); + +#define DEFINE_USBCAM_MODPARAMS_BASE \ + static int video_nr_len = 0; \ + static int video_nr[8]; \ + module_param_array(video_nr, int, &video_nr_len, S_IRUGO|S_IWUSR); \ + MODULE_PARM_DESC(video_nr, "=n[,n...] Force /dev/videoX IDs"); + +#if defined(CONFIG_USB_USBCAM_DEBUG) +#ifndef USBCAM_DEBUG_DEFAULT +#define USBCAM_DEBUG_DEFAULT 0x0020 +#endif +#define DEFINE_USBCAM_MODPARAMS \ + DEFINE_USBCAM_MODPARAMS_BASE \ + static int debug = USBCAM_DEBUG_DEFAULT; \ + module_param(debug, int, S_IRUGO|S_IWUSR); \ + MODULE_PARM_DESC(debug, "Enable debug trace messages"); +#define usbcam_register(DRVPP, MDV, VERX, CB, STRUCTSIZE, IDTBL) \ + usbcam_register_mod((DRVPP), (MDV), (VERX), (CB), (STRUCTSIZE), \ + (IDTBL), video_nr, video_nr_len, &debug, \ + THIS_MODULE, KBUILD_MODNAME) +#else +#define DEFINE_USBCAM_MODPARAMS \ + DEFINE_USBCAM_MODPARAMS_BASE +#define usbcam_register(DRVPP, MDV, VERX, CB, STRUCTSIZE, IDTBL) \ + usbcam_register_mod((DRVPP), (MDV), (VERX), (CB), (STRUCTSIZE), \ + (IDTBL), video_nr, video_nr_len, NULL, \ + THIS_MODULE, KBUILD_MODNAME) +#endif + + +/* + * For minidrivers that do not need to do anything other than register + * and unregister themselves as usbcam minidrivers when their modules + * are loaded and unloaded, the DEFINE_USBCAM_MINIDRV_MODULE macro + * is offered. + */ +#define DEFINE_USBCAM_MINIDRV_MODULE(VER, VERX, OPS, STRUCTSIZE, IDTBL) \ + DEFINE_USBCAM_MODPARAMS \ + static usbcam_minidrv_t *usbcam_minidrv_driver; \ + static int __init usbcam_minidrv_init(void) \ + { \ + return usbcam_register(&usbcam_minidrv_driver, \ + (VER), (VERX), (OPS), \ + (STRUCTSIZE), (IDTBL)); \ + } \ + static void __exit usbcam_minidrv_exit(void) \ + { \ + usbcam_unregister(usbcam_minidrv_driver); \ + } \ + module_init(usbcam_minidrv_init); \ + module_exit(usbcam_minidrv_exit); + + +/* + * Function for claiming additional interfaces + * + * This may only be called from the minidriver init callout + * + * For devices with multiple interfaces that pertain to a single driver, + * a separate usbcam_dev would ordinarily be created for each interface, + * and the init callout would be invoked. With this function, + * additional pertinent interfaces can be bound to the base usbcam_dev. + * + * Additional claimed interfaces will be automatically released at + * disconnect time, or in the event of a failure from the init callout. + * + * If the subject interface is already claimed by another USB driver, + * this function will fail with -EBUSY. + */ +extern int usbcam_claim_interface(struct usbcam_dev *udp, int ifnum); + + +/* + * Alternate setting choosing helper function + * + * This function assists in choosing an alternate setting based on + * overall bandwidth and packet size requirements for a given pipe. + */ +extern int usbcam_choose_altsetting(struct usbcam_dev *udp, int ifnum, + int pipe, int bytes_per_sec_min, + int pkt_min, int pkt_max, + int *altsetting_nr); + + +/* + * CONTROL MANAGEMENT + * + * The control structure is interpreted by the usbcam ioctl handler to + * answer QUERYCTRL/G_CTRL/S_CTRL/QUERYMENU requests. A minidriver may + * define template controls, and add instances of them per device using + * usbcam_ctrl_add(). Template controls may include additional fields, + * as long as struct usbcam_ctrl is the first member. + * + * This mode of handling control-related ioctls is only used if the + * minidriver does not intercept and handle the appropriate ioctl + * commands in its ioctl callout. + */ +struct usbcam_ctrl { + struct list_head uc_links; + struct v4l2_queryctrl uc_v4l; + + const char **uc_menu_names; + + /* Retrieves details about the control dynamically, may be NULL */ + int (*query_fn)(struct usbcam_dev *, const struct usbcam_ctrl *, + struct v4l2_queryctrl *); + + /* Retrieves the current value of the control */ + int (*get_fn)(struct usbcam_dev *, const struct usbcam_ctrl *, + struct v4l2_ext_control *); + + /* If set_fn is not set, the control is considered read-only. */ + int (*set_fn)(struct usbcam_dev *, const struct usbcam_ctrl *, + const struct v4l2_ext_control *); + + /* Called when a control is being freed, not normally needed */ + void (*release_fn)(struct usbcam_dev *, struct usbcam_ctrl *); +}; + +extern struct usbcam_ctrl *usbcam_ctrl_find(struct usbcam_dev *udp, u32 ctlid); + +/* Recommended API: Add a control based on a template */ +extern struct usbcam_ctrl *usbcam_ctrl_add_tmpl(struct usbcam_dev *udp, + const struct usbcam_ctrl *tplp, + size_t real_size); + +/* Less recommended APIs: allocate, configure, try to add, free on failure */ +extern struct usbcam_ctrl *usbcam_ctrl_alloc(size_t real_size); +#define usbcam_ctrl_free(CTRLP) kfree(CTRLP) +extern int usbcam_ctrl_add(struct usbcam_dev *udp, + struct usbcam_ctrl *ctrlp); + + +/* + * CURRENT FRAME BUFFER ACCESS + * + * Minidrivers need to get the address to which the current frame + * buffer is mapped, and the allocated size of the frame buffer in + * order to do their job. The below functions facilitate that. + * + * usbcam_curframe_complete() will cause the current frame buffer to + * be returned to the client application with a successful status. + * The next queued frame buffer will become the current frame buffer. + * + * usbcam_curframe_abortall() will cause all pending frame buffers + * to be aborted and returned to the client application with -EIO. + */ +struct usbcam_curframe { + u8 *uf_base; + size_t uf_size; + enum v4l2_field uf_field; + struct timeval uf_timestamp; +}; + +extern int usbcam_curframe_get(struct usbcam_dev *udp, + struct usbcam_curframe *cf); +extern void usbcam_curframe_complete_detail(struct usbcam_dev *udp, + struct usbcam_curframe *cf); +#define usbcam_curframe_complete(UDP) \ + usbcam_curframe_complete_detail(UDP, NULL) + +extern void usbcam_curframe_abortall(struct usbcam_dev *udp); + +/* + * FRAME FILLERS AND TEST PATTERNS + * + * usbcam_curframe_fill() is a glorified memset: it fills the current + * frame buffer, starting at offs, with nrecs instances of the repeating + * byte sequence pattern/patlen. + * + * It does range checking and will issue printk warnings if the frame + * buffer or the ud_format.sizeimage is overshot. + */ +extern void usbcam_curframe_fill(struct usbcam_dev *udp, size_t offset, + const void *pattern, int patlen, int nrecs); + +/* + * usbcam_curframe_testpattern() attempts to fill the current frame + * buffer with a blue test pattern. It paves over any partial frame + * data. If you wish to display part of an incomplete or interrupted + * frame, don't use this function. It is used by default to fill in + * frames that are completed with is_error = 1. + * + * This function understands many formats, but there will always be + * exceptions. To keep the testpattern mechanism consistent in this + * situation, this function will always defer to the 'testpattern' + * function in the minidriver's usbcam_dev_ops, if one is given, + * before attempting to fill the frame itself. + * + * This function is aware of bytesperline and padded line formats. + */ +extern int usbcam_curframe_testpattern(struct usbcam_dev *udp); + + +/* + * WORK ITEMS AND THE PER-DEVICE WORKER THREAD + * + * So long as a usbcam video4linux device is open, there is a worker + * thread available to execute deferred minidriver tasks. The worker + * thread becomes available prior to the open callout issued to the + * minidriver, and the last close of the device will block after the + * minidriver close callout finishes, waiting for the work queue to + * drain. + * + * A work item may be queued from any context, including interrupt + * contexts and URB completion callouts. + * + * Work item callouts are invoked in the context of the worker thread + * with the device mutex held. When a work item is queued, when the + * worker thread is next idle, it will wait for the device mutex, and + * will start the work item once it acquires the device mutex. Until + * that point, the work item may be canceled and dequeued using the + * usbcam_work_cancel() API, which may only be called while holding + * the device mutex. + * + * It was decided not to use the core kernel work queue + * implementation for the usbcam work queue because: + * 1. Work item cancelation is seen as a useful feature + * 2. Multithreading is not seen as a useful feature + */ + +struct usbcam_workitem; +typedef void (*usbcam_workfunc_t)(struct usbcam_workitem *work); + +struct usbcam_workitem { + struct list_head uw_links; + struct usbcam_dev *uw_dev; + usbcam_workfunc_t uw_func; +}; + +extern void usbcam_work_init(struct usbcam_dev *udp, + struct usbcam_workitem *wip, + usbcam_workfunc_t func); +extern int usbcam_work_queue(struct usbcam_workitem *wip); +extern int usbcam_work_cancel(struct usbcam_workitem *wip); + +struct usbcam_delayedwork { + struct usbcam_workitem dw_work; + struct timer_list dw_timer; +}; + +extern void usbcam_delayedwork_init(struct usbcam_dev *udp, + struct usbcam_delayedwork *dwp, + usbcam_workfunc_t func); +extern void usbcam_delayedwork_queue(struct usbcam_delayedwork *dwp, + unsigned int timeout_ms); +extern int usbcam_delayedwork_cancel(struct usbcam_delayedwork *dwp); + + +/* + * usbcam_work_ref() and usbcam_work_unref() are for managing the start + * and stop of the worker thread. + */ +extern int usbcam_work_ref(struct usbcam_dev *udp); +extern void usbcam_work_unref(struct usbcam_dev *udp); + +/* + * usbcam_work_runqueue() will run work queue items in the current context + * until the work queue becomes empty. + */ +extern void usbcam_work_runqueue(struct usbcam_dev *udp); + + +/* + * Streaming URB helper structure + * + * Minidrivers create any number of these, assign appropriate endpoints, + * and invoke start. Completed packets are automatically processed in the + * worker thread. + * + * This infrastructure may only be used when a device is opened, as the + * worker thread is shut down at other times. + */ + +extern struct urb *usbcam_urb_alloc(struct usbcam_dev *udp, int pipe, + int pktsize, int npkts, int alloc_buf); +extern void usbcam_urb_free(struct urb *urbp, int free_buf); + + +struct usbcam_urbstream; + +struct usbcam_urbstream_ops { + void (*packet_done)(struct usbcam_dev *, struct usbcam_urbstream *, + const u8 *pktdata, int pktlen, int pktstatus); + void (*submit_error)(struct usbcam_dev *, struct usbcam_urbstream *, + int status); +}; + +struct usbcam_urbstream { + struct usbcam_dev *us_dev; + char us_endpoint; + char us_streaming; + char us_active_goal; + char us_active_count; + const struct usbcam_urbstream_ops *us_ops; + spinlock_t us_lock; + int us_timeout_ticks; + int us_resubmit_err; + struct list_head us_unused_list; + struct list_head us_active_list; + struct list_head us_complete_list; + struct completion us_active_empty; + struct usbcam_workitem us_error_workitem; +}; + +extern void usbcam_urbstream_init(struct usbcam_urbstream *usp, + struct usbcam_dev *udp, int ep); + +extern int usbcam_urbstream_start(struct usbcam_urbstream *); +extern void usbcam_urbstream_stop(struct usbcam_urbstream *, int wait); +extern void usbcam_urbstream_cleanup(struct usbcam_urbstream *usp); +extern int usbcam_urbstream_submit_one(struct usbcam_urbstream *usp); + +extern int usbcam_urbstream_config_iso(struct usbcam_urbstream *usp, + const struct usbcam_urbstream_ops *ops, + int pktcount, int nreqs, int interval, + int pktlen); + +extern int usbcam_urbstream_config_bulk(struct usbcam_urbstream *usp, + const struct usbcam_urbstream_ops *ops, + int nreqs, int reqlen, int maxpkt, + int timeout_ms); + + +/* + * Backward compatibility crap for slightly older 2.6 series kernels + */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +#if !defined(V4L2_CTRL_FLAG_NEXT_CTRL) +#define V4L2_CTRL_FLAG_NEXT_CTRL 0 +#endif +#if !defined(V4L2_CTRL_FLAG_SLIDER) +#define V4L2_CTRL_FLAG_SLIDER 0 +#endif +#if !defined(V4L2_CTRL_FLAG_INACTIVE) +#define V4L2_CTRL_FLAG_INACTIVE 0 +#endif +#if !defined(V4L2_CTRL_FLAG_UPDATE) +#define V4L2_CTRL_FLAG_UPDATE 0 +#endif +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) */ + +#endif /* __KERNEL__ */ + +#endif /* __USBCAM_H__ */ diff --git a/usbcam/usbcam_buf.c b/usbcam/usbcam_buf.c @@ -0,0 +1,685 @@ +/* + * USBCAM abstraction library for USB webcam drivers + * + * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu> + * + * 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, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "usbcam_priv.h" + +/* + * This file contains videobuf frame buffer interface routines, and + * current frame access functions. + */ + +/* + * APPBUG: Some applications expect VIDIOCGMBUF to provide a buffer + * large enough to accommodate whatever image format they choose in the + * future. We enable fixed size buffer mode from VIDIOCGMBUF, and + * disable it from VIDIOC_REQBUFS. + */ +static int fixed_fbsize = 1024 * 1024; +module_param(fixed_fbsize, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(fixed_fbsize, "Size in bytes of fixed-length framebuffers"); + + +/* + * Frame capture handling helpers follow + */ + +static inline struct usbcam_frame * +usbcam_capture_curframe(struct usbcam_dev *udp) +{ + return list_empty(&udp->ud_frame_cap_queue) + ? NULL + : list_entry(udp->ud_frame_cap_queue.next, + struct usbcam_frame, cap_links); +} + +static void usbcam_capture_abortall(struct usbcam_dev *udp) +{ + struct usbcam_frame *framep; + + /* Abort all frames on the capture queue */ + while (1) { + framep = usbcam_capture_curframe(udp); + if (!framep) + break; + usbcam_dbg(udp, VIDEOBUF, "completing frame %d STATE_ERROR", + framep->vbb.i); + list_del_init(&framep->cap_links); + framep->vbb.state = STATE_ERROR; + wake_up_all(&framep->vbb.done); + } +} + +static inline void usbcam_capture_complete_frame(struct usbcam_dev *udp, + struct usbcam_frame *framep, + int is_error) +{ + usbcam_chklock(udp); + usbcam_dbg(udp, VIDEOBUF, "completing frame %d/%p %s", framep->vbb.i, + framep, is_error ? "STATE_ERROR" : "STATE_DONE"); + list_del_init(&framep->cap_links); + framep->vbb.state = is_error ? STATE_ERROR : STATE_DONE; + wake_up_all(&framep->vbb.done); +} + +static int usbcam_capture_start(struct usbcam_dev *udp) +{ + int res; + + if (udp->ud_capturing) { + usbcam_warn(udp, "%s: already capturing", __FUNCTION__); + return 0; + } + + if (list_empty(&udp->ud_frame_cap_queue)) { + usbcam_warn(udp, "%s: no frames queued to capture", + __FUNCTION__); + return -ENOENT; + } + + if (udp->ud_disconnected) { + /* + * We can't let any frames through if the device has + * been disconnected + */ + usbcam_capture_abortall(udp); + return -ENODEV; + } + + usbcam_dbg(udp, CAPTURE, "invoking minidriver cap_start"); + + res = usbcam_minidrv_op(udp, cap_start); + if (res) { + usbcam_dbg(udp, CAPTURE, + "%s: could not start capture for %s: %d", + __FUNCTION__, usbcam_drvname(udp->ud_minidrv), res); + + if (udp->ud_capturing) { + usbcam_warn(udp, + "%s: minidriver left ud_capturing set\n", + __FUNCTION__); + } + + usbcam_capture_abortall(udp); + return res; + } + + if (!udp->ud_capturing && usbcam_capture_curframe(udp)) { + usbcam_warn(udp, "%s: minidriver failed to set ud_capturing!", + __FUNCTION__); + } else { + usbcam_dbg(udp, CAPTURE, "minidriver capture started"); + } + + return 0; +} + +void usbcam_capture_stop(struct usbcam_dev *udp) +{ + if (udp->ud_capturing) { + usbcam_dbg(udp, CAPTURE, "invoking minidriver cap_stop"); + usbcam_minidrv_op(udp, cap_stop); + + if (udp->ud_capturing) { + usbcam_warn(udp, "%s: minidriver failed to clear " + "ud_capturing!", __FUNCTION__); + } else { + usbcam_dbg(udp, CAPTURE, "minidriver capture stopped"); + } + } +} + +static inline struct videobuf_dmabuf* usbframe_get_dmabuf(struct videobuf_buffer *buf) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + return buf->dma; +#else + return videobuf_to_dma(buf); +#endif +} + +/* + * External APIs for minidriver access to the frame queue + */ + +int usbcam_curframe_get(struct usbcam_dev *udp, struct usbcam_curframe *cf) +{ + struct usbcam_frame *framep = usbcam_capture_curframe(udp); + struct videobuf_dmabuf *dma; + + usbcam_chklock(udp); + + if (!framep) + return -ENOENT; + + dma = usbframe_get_dmabuf(&framep->vbb); + + cf->uf_base = (u8 *) (framep->vmap_sof + ? framep->vmap_sof + : dma->vmalloc); + cf->uf_size = framep->vbb.size; + cf->uf_field = framep->vbb.field; + memset(&cf->uf_timestamp, 0, sizeof(cf->uf_timestamp)); + + return 0; +} +USBCAM_EXPORT_SYMBOL(usbcam_curframe_get); + +void usbcam_curframe_complete_detail(struct usbcam_dev *udp, + struct usbcam_curframe *cf) +{ + struct usbcam_frame *framep; + + usbcam_chklock(udp); + + framep = usbcam_capture_curframe(udp); + if (!framep) { + usbcam_warn(udp, "%s: no current frame!", __FUNCTION__); + return; + } + + if (framep->vbb.state != STATE_ACTIVE) { + usbcam_err(udp, "%s: current frame is in unexpected state %d", + __FUNCTION__, framep->vbb.state); + } + + if (cf) { + framep->vbb.size = cf->uf_size; + framep->vbb.field = cf->uf_field; + framep->vbb.ts = cf->uf_timestamp; + + if (framep->vbb.bsize < cf->uf_size) { + usbcam_warn(udp, "%s: minidriver supplied " + "excessive size %zu", + __FUNCTION__, cf->uf_size); + framep->vbb.size = framep->vbb.bsize; + } + } + + usbcam_capture_complete_frame(udp, framep, 0); +} +USBCAM_EXPORT_SYMBOL(usbcam_curframe_complete_detail); + +void usbcam_curframe_abortall(struct usbcam_dev *udp) +{ + usbcam_dbg(udp, CAPTURE, "minidriver aborting all frames"); + + usbcam_chklock(udp); + + if (udp->ud_capturing) { + usbcam_warn(udp, "%s: minidriver left ud_capturing set", + __FUNCTION__); + } + usbcam_capture_abortall(udp); +} +USBCAM_EXPORT_SYMBOL(usbcam_curframe_abortall); + + +/* + * Test pattern code + */ + +void usbcam_curframe_fill(struct usbcam_dev *udp, size_t offset, + const void *pattern, int patlen, int nrecs) +{ + struct usbcam_curframe cf; + size_t end; + + usbcam_chklock(udp); + + if (usbcam_curframe_get(udp, &cf)) { + usbcam_warn(udp, "%s: no current frame", __FUNCTION__); + return; + } + + end = offset + (patlen * nrecs); + if (end > cf.uf_size) { + usbcam_warn(udp, "%s(offs=%zu patlen=%d nrecs=%d) would " + "write past end of %zu byte framebuffer", + __FUNCTION__, + offset, patlen, nrecs, cf.uf_size); + if (offset > cf.uf_size) + nrecs = 0; + else + nrecs = (cf.uf_size - offset) / patlen; + } + else if (end > udp->ud_format.sizeimage) + usbcam_warn(udp, "%s(offs=%zu patlen=%d nrecs=%d) writing " + "beyond %u-byte sizeimage", __FUNCTION__, + offset, patlen, nrecs, udp->ud_format.sizeimage); + + if (!nrecs) + return; + + if (patlen == 1) { + memset(cf.uf_base + offset, *(char *)pattern, nrecs); + return; + } + + while (nrecs--) { + memcpy(cf.uf_base + offset, pattern, patlen); + offset += patlen; + } +} +USBCAM_EXPORT_SYMBOL(usbcam_curframe_fill); + +void usbcam_curframe_fill_lines(struct usbcam_dev *udp, + const char *pat, int patlen, + int pixperpat) +{ + int line, nrecperline, stride; + size_t offset = 0; + + nrecperline = (udp->ud_format.width + pixperpat - 1) / pixperpat; + stride = udp->ud_format.bytesperline + ? udp->ud_format.bytesperline + : (nrecperline * patlen); + + if ((patlen * nrecperline) == stride) { + usbcam_curframe_fill(udp, 0, pat, patlen, + nrecperline * udp->ud_format.height); + return; + } + + for (line = 0; line < udp->ud_format.height; line++) { + usbcam_curframe_fill(udp, offset, pat, patlen, nrecperline); + offset += stride; + } +} + +void usbcam_curframe_fill_interleaved(struct usbcam_dev *udp, + const char *pat_even, + const char *pat_odd, + int patlen, int pixperpat) +{ + int line, nrecperline, stride; + size_t offset = 0; + + nrecperline = (udp->ud_format.width + pixperpat - 1) / pixperpat; + stride = udp->ud_format.bytesperline + ? udp->ud_format.bytesperline + : (nrecperline * patlen); + + for (line = 0; line < udp->ud_format.height; line++) { + usbcam_curframe_fill(udp, offset, + (line & 1) ? pat_odd : pat_even, + patlen, nrecperline); + offset += stride; + } +} + +void usbcam_curframe_fill_planar(struct usbcam_dev *udp, + const char *pat0, int pat0len, int pixperpat0, + const char *pat1, int pat1len, int pixperpat1, + const char *pat2, int pat2len, int pixperpat2) +{ + int nrecperline; + size_t offset = 0; + + if (pat0 && pat0len) { + nrecperline = ((udp->ud_format.width + pixperpat0 - 1) / + pixperpat0); + usbcam_curframe_fill(udp, offset, pat0, pat0len, + nrecperline * udp->ud_format.height); + offset += (nrecperline * udp->ud_format.height * pat0len); + } + if (pat1 && pat1len) { + nrecperline = ((udp->ud_format.width + pixperpat1 - 1) / + pixperpat1); + usbcam_curframe_fill(udp, offset, pat1, pat1len, + nrecperline * udp->ud_format.height); + offset += (nrecperline * udp->ud_format.height * pat1len); + } + if (pat2 && pat2len) { + nrecperline = ((udp->ud_format.width + pixperpat2 - 1) / + pixperpat2); + usbcam_curframe_fill(udp, offset, pat2, pat2len, + nrecperline * udp->ud_format.height); + offset += (nrecperline * udp->ud_format.height * pat2len); + } +} + +/* + * The goal is to be able to come up with a solid blue image in all + * basic uncompressed formats. No JPEG or compressed formats, at least + * not yet. + */ +int usbcam_curframe_testpattern(struct usbcam_dev *udp) +{ + usbcam_chklock(udp); + +#define DO_FILL(PIXPERPAT, TP...) { \ + const char tp[] = {TP}; \ + usbcam_curframe_fill_lines(udp, tp, sizeof(tp), PIXPERPAT); \ +} + switch (udp->ud_format.pixelformat) { + case V4L2_PIX_FMT_RGB332: + DO_FILL(1, 0x03) + return 0; + case V4L2_PIX_FMT_RGB555: + case V4L2_PIX_FMT_RGB565: + DO_FILL(1, 0x1f, 0x00) + return 0; + case V4L2_PIX_FMT_RGB555X: + case V4L2_PIX_FMT_RGB565X: + DO_FILL(1, 0x00, 0x1f) + return 0; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) + case V4L2_PIX_FMT_RGB444: + DO_FILL(1, 0x00, 0x0f) + return 0; +#endif + case V4L2_PIX_FMT_BGR24: + DO_FILL(1, 0xff, 0x00, 0x00); + return 0; + case V4L2_PIX_FMT_RGB24: + DO_FILL(1, 0x00, 0x00, 0xff); + return 0; + case V4L2_PIX_FMT_BGR32: + DO_FILL(1, 0xff, 0x00, 0x00, 0x00); + return 0; + case V4L2_PIX_FMT_RGB32: + DO_FILL(1, 0x00, 0x00, 0xff, 0x00); + return 0; + case V4L2_PIX_FMT_GREY: + DO_FILL(1, 0x1f); + return 0; + case V4L2_PIX_FMT_YUYV: + DO_FILL(2, 0x29, 0xf0, 0x29, 0x6e); + return 0; + case V4L2_PIX_FMT_UYVY: + DO_FILL(2, 0xf0, 0x29, 0x6e, 0x29); + return 0; + case V4L2_PIX_FMT_YYUV: + DO_FILL(2, 0x29, 0x29, 0xf0, 0x6e); + return 0; + case V4L2_PIX_FMT_Y41P: + DO_FILL(8, + 0xf0, 0x29, 0x6e, 0x29, 0xf0, 0x29, 0x6e, 0x29, + 0x29, 0x29, 0x29, 0x29); + return 0; +#undef DO_FILL + + case V4L2_PIX_FMT_SBGGR8: { + const char tp0[] = { 0xff, 0x00 }, tp1[] = { 0x00, 0x00 }; + usbcam_curframe_fill_interleaved(udp, tp0, tp1, 2, 2); + return 0; + } + case V4L2_PIX_FMT_YVU410: { + const char tp0[] = {0x29}, tp1[] = {0x6e}, tp2[] = {0xf0}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 16, + tp2, sizeof(tp2), 16); + return 0; + } + case V4L2_PIX_FMT_YUV410: { + const char tp0[] = {0x29}, tp1[] = {0xf0}, tp2[] = {0x6e}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 16, + tp2, sizeof(tp2), 16); + return 0; + } + case V4L2_PIX_FMT_YVU420: { + const char tp0[] = {0x29}, tp1[] = {0x6e}, tp2[] = {0xf0}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 4, + tp2, sizeof(tp2), 4); + return 0; + } + case V4L2_PIX_FMT_YUV411P: + case V4L2_PIX_FMT_YUV420: { + const char tp0[] = {0x29}, tp1[] = {0xf0}, tp2[] = {0x6e}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 4, + tp2, sizeof(tp2), 4); + return 0; + } + case V4L2_PIX_FMT_YUV422P: { + const char tp0[] = {0x29}, tp1[] = {0xf0}, tp2[] = {0x6e}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 2, + tp2, sizeof(tp2), 2); + return 0; + } + case V4L2_PIX_FMT_NV12: { + const char tp0[] = {0x29}, tp1[] = {0xf0, 0x6e}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 4, + NULL, 0, 0); + return 0; + } + case V4L2_PIX_FMT_NV21: { + const char tp0[] = {0x29}, tp1[] = {0x6e, 0xf0}; + usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1, + tp1, sizeof(tp1), 4, + NULL, 0, 0); + return 0; + } + } + + return -EINVAL; +} +USBCAM_EXPORT_SYMBOL(usbcam_curframe_testpattern); + + +/* + * video-buf interfaces for managing frame buffers + * The device mutex is not held on entry to _any_ of these functions. + */ + +static int usbcam_videobuf_setup(struct videobuf_queue *vq, + unsigned int *count, unsigned int *size) +{ + struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, ufh_vbq); + struct usbcam_dev *udp = ufp->ufh_dev; + + usbcam_lock(udp); + + /* APPBUG: possibly request larger buffers than necessary */ + if ((ufp->ufh_flags & USBCAM_FH_USE_FIXED_FB) && + (fixed_fbsize > udp->ud_format.sizeimage)) + *size = fixed_fbsize; + else + *size = udp->ud_format.sizeimage; + + if (!*count) + *count = 2; + + usbcam_dbg(udp, VIDEOBUF, "videobuf setup: size=%u count=%u", + *size, *count); + + usbcam_unlock(udp); + return 0; +} + +static void usbcam_videobuf_free(struct videobuf_queue *vq, + struct usbcam_frame *framep) +{ + struct videobuf_dmabuf *dma = usbframe_get_dmabuf(&framep->vbb); + + videobuf_waiton(&framep->vbb, 0, 0); + videobuf_dma_unmap(vq, dma); + videobuf_dma_free(dma); + if (framep->vbb.state != STATE_NEEDS_INIT) { + if (framep->vmap_base) { + vunmap(framep->vmap_base); + framep->vmap_base = NULL; + framep->vmap_sof = NULL; + } + assert(list_empty(&framep->cap_links)); + framep->vbb.state = STATE_NEEDS_INIT; + } +} + +static int usbcam_videobuf_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, ufh_vbq); + struct usbcam_dev *udp = ufp->ufh_dev; + struct usbcam_frame *framep = + container_of(vb, struct usbcam_frame, vbb); + struct videobuf_dmabuf *dma = usbframe_get_dmabuf(&framep->vbb); + int res; + + framep->vbb.size = udp->ud_format.sizeimage; + if (framep->vbb.baddr && (framep->vbb.bsize < framep->vbb.size)) { + usbcam_warn(udp, "process %s requested capture of a frame " + "larger than its", current->comm); + usbcam_warn(udp, "allocated frame buffer, fix it!"); + return -EINVAL; + } + + if (framep->vbb.state == STATE_NEEDS_INIT) { + /* + * This is the place where we initialize the rest of + * the usbcam_frame structure. + */ + INIT_LIST_HEAD(&framep->cap_links); + framep->vmap_base = NULL; + framep->vmap_sof = NULL; + + usbcam_dbg(udp, VIDEOBUF, + "preparing frame %d/%p", framep->vbb.i, framep); + + /* We also lock down the memory that was allocated for it */ + res = videobuf_iolock(vq, &framep->vbb, NULL); + if (res) + goto fail; + + /* If there's no kernel mapping, we must create one */ + if (!dma->vmalloc) { + framep->vmap_base = vmap(dma->pages, + dma->nr_pages, + VM_MAP, + PAGE_KERNEL); + if (!framep->vmap_base) { + res = -ENOMEM; + goto fail; + } + + framep->vmap_sof = + ((char *)framep->vmap_base) + + dma->offset; + } + } + + framep->vbb.field = field; + framep->vbb.state = STATE_PREPARED; + return 0; + +fail: + usbcam_videobuf_free(vq, framep); + return res; +} + +static void usbcam_videobuf_queue(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, ufh_vbq); + struct usbcam_dev *udp = ufp->ufh_dev; + struct usbcam_frame *framep = + container_of(vb, struct usbcam_frame, vbb); + int was_empty = 0; + + assert(framep->vbb.state != STATE_NEEDS_INIT); + + usbcam_lock(udp); + + if (list_empty(&udp->ud_frame_cap_queue)) + was_empty = 1; + + usbcam_dbg(udp, VIDEOBUF, "queueing frame %d/%p", + framep->vbb.i, framep); + + /* + * We always set buffers to STATE_ACTIVE to prevent them from + * being manipulated / dequeued by the videobuf code. + */ + list_add_tail(&framep->cap_links, &udp->ud_frame_cap_queue); + framep->vbb.state = STATE_ACTIVE; + + if (was_empty && !udp->ud_capturing) + (void) usbcam_capture_start(udp); + + usbcam_unlock(udp); +} + +static void usbcam_videobuf_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, ufh_vbq); + struct usbcam_dev *udp = ufp->ufh_dev; + struct usbcam_frame *framep = + container_of(vb, struct usbcam_frame, vbb); + int stopped_capture = 0; + + usbcam_lock(udp); + + if ((framep->vbb.state != STATE_NEEDS_INIT) && + !list_empty(&framep->cap_links)) { + + usbcam_dbg(udp, VIDEOBUF, + "aborting frame %d/%p", framep->vbb.i, framep); + + /* + * An active frame is being shot down here, most + * likely by videobuf_queue_cancel. + */ + assert(framep->vbb.state == STATE_ACTIVE); + + if (udp->ud_capturing && + !list_empty(&udp->ud_frame_cap_queue) && + (framep == usbcam_capture_curframe(udp))) { + /* + * The current frame has been user-aborted. + * We will stop capturing. The minidriver may complete + * it with an error, or may leave it alone, in which + * case we will complete it with an error. + */ + usbcam_dbg(udp, VIDEOBUF, + "current frame aborted, stopping capture"); + + usbcam_capture_stop(udp); + stopped_capture = 1; + } + + if (!list_empty(&framep->cap_links)) + usbcam_capture_complete_frame(udp, framep, 1); + + /* + * Ideally, if we stopped capturing, and there are frames + * still in the queue, we would restart. + * + * In reality, we only take this code path if all frames + * from the owning file handle are aborted, and restarting + * would pointlessly slow down this process. + */ + } + + usbcam_unlock(udp); + usbcam_videobuf_free(vq, framep); +} + +struct videobuf_queue_ops usbcam_videobuf_qops = { + .buf_setup = usbcam_videobuf_setup, + .buf_prepare = usbcam_videobuf_prepare, + .buf_queue = usbcam_videobuf_queue, + .buf_release = usbcam_videobuf_release, +}; diff --git a/usbcam/usbcam_dev.c b/usbcam/usbcam_dev.c @@ -0,0 +1,1109 @@ +/* + * USBCAM abstraction library for USB webcam drivers + * + * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu> + * + * 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, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "usbcam_priv.h" + +/* + * This file contains: + * - Minidriver registration / deregistration handlers. + * - Device and minidriver reference counting functions + * - USB subsystem callouts + */ + +/* + * Reference Counting Notes + * + * Each usbcam_minidrv gets: + * - One reference for being in the registered state + * - One reference for each outstanding usbcam_dev + * + * Each usbcam_dev gets: + * - One reference for having its V4L minor registered and not released + * - One reference for having its underlying USB device not disconnected + * - One reference for each open file handle + */ + +static void usbcam_minidrv_release(struct kref *kref) +{ + struct usbcam_minidrv *minidrv = + container_of(kref, struct usbcam_minidrv, um_kref); + + assert(!minidrv->um_dev_count); + usbcam_dbgm(minidrv, DEV_STATE, "%s: destroying minidrvier", + __FUNCTION__); + kfree(minidrv); +} + + +static void usbcam_dev_free(struct kref *kref) +{ + struct usbcam_dev *udp = + container_of(kref, struct usbcam_dev, ud_kref); + + if (udp->ud_work_refs) { + usbcam_lock(udp); + usbcam_warn(udp, "%s: work queue has %d leaked refs", + __FUNCTION__, udp->ud_work_refs); + while (udp->ud_work_refs) + usbcam_work_unref(udp); + usbcam_unlock(udp); + } + + usbcam_work_maybe_stop(udp); + assert(!udp->ud_work_thread); + + usb_put_intf(udp->ud_intf); + udp->ud_intf = NULL; + + usb_put_dev(udp->ud_dev); + udp->ud_dev = NULL; + + mutex_lock(&udp->ud_minidrv->um_lock); + + assert(!list_empty(&udp->ud_drv_links)); + assert(udp->ud_minidrv->um_dev_count > 0); + + list_del_init(&udp->ud_drv_links); + udp->ud_minidrv->um_dev_count--; + + mutex_unlock(&udp->ud_minidrv->um_lock); + + kref_put(&udp->ud_minidrv->um_kref, usbcam_minidrv_release); + kfree(udp); +} + +static void usbcam_dev_release(struct kref *kref) +{ + struct usbcam_dev *udp = + container_of(kref, struct usbcam_dev, ud_kref); + + usbcam_dbg(udp, DEV_STATE, "%s: destroying device", __FUNCTION__); + + if (usbcam_minidrv_op_present(udp, release)) { + usbcam_lock(udp); + usbcam_ctrl_releaseall(udp); + usbcam_minidrv_op(udp, release); + usbcam_unlock(udp); + } + usbcam_dev_free(kref); +} + +void usbcam_put(struct usbcam_dev *udp) +{ + kref_put(&udp->ud_kref, usbcam_dev_release); +} +USBCAM_EXPORT_SYMBOL(usbcam_put); + +/* + * V4L2 videodev callout implementations + */ + +static void usbcam_videodev_release(struct video_device *vfd) +{ + struct usbcam_dev *udp = container_of(vfd, struct usbcam_dev, ud_vdev); + + usbcam_lock(udp); + + assert(!udp->ud_videodev_released); + udp->ud_videodev_released = 1; + + usbcam_unlock(udp); + usbcam_put(udp); +} + + +/* + * USB subsystem operation implementations + */ + +struct usbcam_claimed_interface { + struct list_head ui_links; + struct usb_interface *ui_intf; +}; + +static int usbcam_usb_probe(struct usb_interface *intf, + const struct usb_device_id *devid) +{ + struct usb_driver *drvp; + struct usbcam_dev *udp = NULL, *udpx; + usbcam_minidrv_t *minidrv; + struct usb_device *dev; + struct list_head *listp; + struct usbcam_claimed_interface *cip; + int minidrv_init_failed = 0; + int res, i; + + /* Locate the mini-driver */ + dev = interface_to_usbdev(intf); + drvp = to_usb_driver(intf->dev.driver); + minidrv = container_of(drvp, usbcam_minidrv_t, um_usbdrv); + + /* Allocate and initialize a device structure */ + udp = (struct usbcam_dev *) kzalloc(sizeof(*udp) + + minidrv->um_dev_privsize, + GFP_KERNEL); + if (!udp) + return -ENOMEM; + + INIT_LIST_HEAD(&udp->ud_ctrl_list); + mutex_init(&udp->ud_open_lock); + mutex_init(&udp->ud_lock); + spin_lock_init(&udp->ud_work_lock); + INIT_LIST_HEAD(&udp->ud_work_queue); + udp->ud_minidrv = minidrv; + udp->ud_dev = usb_get_dev(dev); + udp->ud_intf = usb_get_intf(intf); + udp->ud_debug = minidrv->um_debug; + INIT_LIST_HEAD(&udp->ud_drv_links); + kref_init(&udp->ud_kref); + + INIT_LIST_HEAD(&udp->ud_interface_list); + INIT_LIST_HEAD(&udp->ud_frame_cap_queue); + + if (minidrv->um_dev_privsize) + udp->ud_minidrv_data = &udp[1]; + + /* Set up the video4linux structure */ + udp->ud_vdev = minidrv->um_videodev_template; + udp->ud_vdev.release = usbcam_videodev_release; + + /* Add the device to the minidriver's list of active devices */ + usbcam_lock(udp); + + mutex_lock(&minidrv->um_lock); + + /* Inefficiently find an unused ID in the device list */ + i = 0; + udpx = NULL; + list_for_each(listp, &minidrv->um_dev_list) { + udpx = list_entry(listp, struct usbcam_dev, ud_drv_links); + if (udpx->ud_minidrv_id < 0) { + udpx = NULL; + continue; + } + if (udpx->ud_minidrv_id != i) + break; + udpx = NULL; + i++; + } + + udp->ud_minidrv_id = i; + if (udpx) { + list_add_tail(&udp->ud_drv_links, &udpx->ud_drv_links); + } else { + list_add_tail(&udp->ud_drv_links, &minidrv->um_dev_list); + } + + minidrv->um_dev_count++; + kref_get(&minidrv->um_kref); + + snprintf(udp->ud_dev_name, sizeof(udp->ud_dev_name), + "%s-%d", usbcam_drvname(udp->ud_minidrv), + udp->ud_minidrv_id); + + snprintf(udp->ud_vdev.name, sizeof(udp->ud_vdev.name), + "%s USB Camera #%d", + usbcam_drvname(minidrv), udp->ud_minidrv_id + 1); + + + mutex_unlock(&minidrv->um_lock); + + /* Invoke the minidriver initialization callout */ + udp->ud_initializing = 1; + res = usbcam_minidrv_op(udp, init, devid); + udp->ud_initializing = 0; + if (res) { + usbcam_dbg(udp, DEV_STATE, "minidriver init failed: %d", res); + minidrv_init_failed = 1; + goto out_nodisconn; + } + + /* Complain if the device isn't filled out correctly */ + if (!udp->ud_format.width || !udp->ud_format.height) { + usbcam_warn(udp, "minidriver did not set default size"); + res = -EINVAL; + goto out; + } + if (!udp->ud_format.pixelformat) { + usbcam_warn(udp, "minidriver did not set default pixelformat"); + res = -EINVAL; + goto out; + } + + usb_set_intfdata(intf, udp); + usbcam_unlock(udp); + + /* + * Register the device with video4linux + * + * BUG: video_register_device() may or may not call back + * into usbcam_videodev_release(), depending on how it fails, + * and if it does call back, its callback may be latent. + * + * We will assume no callback on failure. + */ + + if (udp->ud_vdev.minor != -1) { + /* Minidriver has indicated its preference for a minor */ + res = video_register_device(&udp->ud_vdev, VFL_TYPE_GRABBER, + -1); + if (!res) + goto video_registered; + } + + for (i = 0; i < minidrv->um_video_nr_array_len; i++) { + res = video_register_device(&udp->ud_vdev, VFL_TYPE_GRABBER, + minidrv->um_video_nr_array[i]); + if (!res) + goto video_registered; + } + + res = video_register_device(&udp->ud_vdev, VFL_TYPE_GRABBER, -1); + if (res) { + usbcam_err(udp, "%s: video_register_device failed", + __FUNCTION__); + usbcam_lock(udp); + assert(!udp->ud_videodev_released); + udp->ud_videodev_released = 1; + goto out; + } + +video_registered: + usbcam_get(udp); + /* + * There should now be at least two references on udp: + * One for the primary USB interface in the non-disconnected state + * One for the videodev stuff + * One for each additional claimed interface + */ + + usbcam_info(udp, "registered as video%d", udp->ud_vdev.minor); + + usbcam_work_maybe_stop(udp); + return 0; + +out: + assert(!udp->ud_disconnected); + udp->ud_disconnected = 1; + if (usbcam_minidrv_op_present(udp, disconnect)) + usbcam_minidrv_op(udp, disconnect); + +out_nodisconn: + while (!list_empty(&udp->ud_interface_list)) { + cip = list_entry(udp->ud_interface_list.next, + struct usbcam_claimed_interface, + ui_links); + list_del_init(&cip->ui_links); + usb_set_intfdata(cip->ui_intf, NULL); + usb_driver_release_interface(&minidrv->um_usbdrv, + cip->ui_intf); + usb_put_intf(cip->ui_intf); + kfree(cip); + usbcam_put(udp); + } + + usbcam_unlock(udp); + + if (minidrv_init_failed) + kref_put(&udp->ud_kref, usbcam_dev_free); + else + usbcam_put(udp); + return res; +} + +static void usbcam_usb_disconnect(struct usb_interface *intf) +{ + struct usbcam_dev *udp = (struct usbcam_dev *) usb_get_intfdata(intf); + struct usbcam_claimed_interface *iterp, *cip; + int put_intf = 0; + int put_udp = 0; + + if (!udp) + return; + + usbcam_lock(udp); + if (!udp->ud_disconnected) { + udp->ud_disconnected = 1; + usbcam_unlock(udp); + + usbcam_dbg(udp, DEV_STATE, "disconnected"); + video_unregister_device(&udp->ud_vdev); + + usbcam_dbg(udp, DEV_STATE, "unregistered from video%d", + udp->ud_vdev.minor); + + usbcam_lock(udp); + if (usbcam_minidrv_op_present(udp, disconnect)) + usbcam_minidrv_op(udp, disconnect); + + usbcam_capture_stop(udp); + } + + if (intf == udp->ud_intf) { + assert(!udp->ud_disconnected_primary); + udp->ud_disconnected_primary = 1; + put_udp = 1; + + } else { + cip = NULL; + list_for_each_entry(iterp, &udp->ud_interface_list, ui_links) { + if (iterp->ui_intf == intf) { + cip = iterp; + break; + } + } + + if (cip) { + list_del_init(&cip->ui_links); + kfree(cip); + put_intf = 1; + put_udp = 1; + } else { + usbcam_err(udp, "interface %p is not claimed", intf); + } + } + + usb_set_intfdata(intf, NULL); + usbcam_unlock(udp); + + usbcam_work_maybe_stop(udp); + + if (put_intf) + usb_put_intf(intf); + if (put_udp) + usbcam_put(udp); +} + +#if defined(CONFIG_PM) +static int usbcam_usb_suspend(struct usb_interface *intf, pm_message_t msg) +{ + struct usbcam_dev *udp = (struct usbcam_dev *) usb_get_intfdata(intf); + int relock = 0, res = 0; + if (!udp) { + printk(KERN_WARNING "%s: no associated device\n", + __FUNCTION__); + return 0; + } + + usbcam_lock(udp); + if ((intf != udp->ud_intf) || udp->ud_suspended) { + /* Do nothing */ + } else if (usbcam_minidrv_op_present(udp, suspend)) { + usbcam_dbg(udp, DEV_STATE, "invoking minidriver suspend"); + udp->ud_suspended = 1; + if (udp->ud_minidrv->um_ops->unlocked_pm) { + usbcam_unlock(udp); + relock = 1; + } + + res = usbcam_minidrv_op(udp, suspend, msg); + + if (relock) + usbcam_lock(udp); + if (res) + udp->ud_suspended = 0; + } else { + usbcam_dbg(udp, DEV_STATE, "no minidriver suspend method"); + udp->ud_suspended = 1; + } + + usbcam_unlock(udp); + usbcam_work_maybe_stop(udp); + return res; +} + +static int usbcam_usb_resume(struct usb_interface *intf) +{ + struct usbcam_dev *udp = (struct usbcam_dev *) usb_get_intfdata(intf); + int relock = 0, res = 0; + if (!udp) { + printk(KERN_WARNING "%s: no associated device\n", + __FUNCTION__); + return 0; + } + + usbcam_lock(udp); + if ((intf != udp->ud_intf) || !udp->ud_suspended) { + /* Nothing to do! */ + } else if (usbcam_minidrv_op_present(udp, resume)) { + usbcam_dbg(udp, DEV_STATE, "invoking minidriver resume"); + if (udp->ud_minidrv->um_ops->unlocked_pm) { + usbcam_unlock(udp); + relock = 1; + } + res = usbcam_minidrv_op(udp, resume); + if (relock) + usbcam_lock(udp); + } else + usbcam_dbg(udp, DEV_STATE, "no minidriver resume method"); + + if (!res) + udp->ud_suspended = 0; + + usbcam_unlock(udp); + usbcam_work_maybe_stop(udp); + return res; +} +#endif /* defined(CONFIG_PM) */ + + +static const struct usb_driver usbcam_usb_driver_template = { + .name = "usbcam minidriver", + .probe = usbcam_usb_probe, + .disconnect = usbcam_usb_disconnect, +#if defined(CONFIG_PM) + .suspend = usbcam_usb_suspend, + .resume = usbcam_usb_resume, +#endif +}; + + +/* + * Minidriver registration/unregistration + */ + +int usbcam_register_mod(usbcam_minidrv_t **driverpp, + int minidrv_version, const char *minidrv_verx, + const struct usbcam_dev_ops *ops, + const int dev_priv_size, + const struct usb_device_id *id_table, + const int *video_nrs, int video_nrs_len, + int *debug, struct module *md, const char *modname) +{ + usbcam_minidrv_t *minidrv; + int res; + + printk(KERN_INFO "usbcam: registering driver %s %d.%d.%d%s\n", + modname, + (minidrv_version >> 16) & 0xff, + (minidrv_version >> 8) & 0xff, + minidrv_version & 0xff, + minidrv_verx ? minidrv_verx : ""); + + minidrv = (usbcam_minidrv_t *) kzalloc(sizeof(*minidrv), GFP_KERNEL); + if (!minidrv) { + err("%s: Failed to allocate usbcam_minidrv_t", __FUNCTION__); + return -ENOMEM; + } + + kref_init(&minidrv->um_kref); + minidrv->um_owner = md; + minidrv->um_modname = modname; + minidrv->um_version = minidrv_version; + minidrv->um_debug = debug; + minidrv->um_dev_privsize = dev_priv_size; + INIT_LIST_HEAD(&minidrv->um_dev_list); + mutex_init(&minidrv->um_lock); + minidrv->um_video_nr_array = video_nrs; + minidrv->um_video_nr_array_len = video_nrs_len; + + minidrv->um_ops = ops; + + minidrv->um_usbdrv = usbcam_usb_driver_template; + minidrv->um_usbdrv.name = usbcam_drvname(minidrv); + minidrv->um_usbdrv.id_table = id_table; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19) + minidrv->um_usbdrv.supports_autosuspend = + minidrv->um_ops->supports_autosuspend; +#endif + + /* + * We have a separate fops per minidriver structure so that + * module reference counting works without egregious hacks. + */ + minidrv->um_v4l_fops = usbcam_v4l_fops_template; + minidrv->um_v4l_fops.owner = minidrv->um_owner; + + minidrv->um_videodev_template = usbcam_videodev_template; + minidrv->um_videodev_template.fops = &minidrv->um_v4l_fops; + + *driverpp = minidrv; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21) + res = usb_register_driver(&minidrv->um_usbdrv, minidrv->um_owner, + minidrv->um_modname); +#else + res = usb_register_driver(&minidrv->um_usbdrv, minidrv->um_owner); +#endif + if (res) { + kref_put(&minidrv->um_kref, usbcam_minidrv_release); + *driverpp = NULL; + } + + usbcam_dbgm(minidrv, DEV_STATE, "registered minidriver"); + + return res; +} +USBCAM_EXPORT_SYMBOL(usbcam_register_mod); + +void usbcam_unregister(usbcam_minidrv_t *minidrv) +{ + usbcam_dbgm(minidrv, DEV_STATE, "unregistering minidriver"); + + usb_deregister(&minidrv->um_usbdrv); + + if (minidrv->um_dev_count) { + /* + * This can happen if minidrivers unregister prior to + * module_exit(), but is usually bad. + */ + err("%s: %d \"%s\" devices remain", + __FUNCTION__, minidrv->um_dev_count, + usbcam_drvname(minidrv)); + } + + kref_put(&minidrv->um_kref, usbcam_minidrv_release); +} +USBCAM_EXPORT_SYMBOL(usbcam_unregister); + + +int usbcam_claim_interface(struct usbcam_dev *udp, int ifnum) +{ + struct usb_interface *intf; + struct usbcam_claimed_interface *cip; + int res; + + usbcam_chklock(udp); + + if (!udp->ud_initializing) { + usbcam_warn(udp, "%s may only be called from minidriver init", + __FUNCTION__); + return -EINVAL; + } + + intf = usb_ifnum_to_if(udp->ud_dev, ifnum); + if (!intf) { + usbcam_warn(udp, "%s: interface %d does not exist", + __FUNCTION__, ifnum); + return -ENODEV; + } + + res = usb_driver_claim_interface(&udp->ud_minidrv->um_usbdrv, + intf, NULL); + + if (!res) { + cip = kmalloc(sizeof(*cip), GFP_KERNEL); + if (!cip) { + usb_driver_release_interface(&udp->ud_minidrv-> + um_usbdrv, intf); + return -ENOMEM; + } + + INIT_LIST_HEAD(&cip->ui_links); + cip->ui_intf = usb_get_intf(intf); + usb_set_intfdata(intf, udp); + usbcam_get(udp); + list_add_tail(&cip->ui_links, &udp->ud_interface_list); + } + + return res; +} +USBCAM_EXPORT_SYMBOL(usbcam_claim_interface); + + +/* + * Work queue implementation + */ + +static DECLARE_WAIT_QUEUE_HEAD(usbcam_work_idle_wait); + +static int usbcam_work_thread(void *arg) +{ + struct usbcam_dev *udp = (struct usbcam_dev *) arg; + struct usbcam_workitem *wip; + sigset_t wakesigs; + unsigned long flags; + usbcam_workfunc_t fn; + int res; + + current->flags |= PF_NOFREEZE; + set_user_nice(current, -5); + + sigemptyset(&wakesigs); + sigaddset(&wakesigs, SIGUSR1); + + while (1) { + /* Wait for something to appear on the work queue */ + spin_lock_irqsave(&udp->ud_work_lock, flags); + udp->ud_work_lockwait = 0; + if (list_empty(&udp->ud_work_queue)) { + if (kthread_should_stop()) { + spin_unlock_irqrestore(&udp->ud_work_lock, + flags); + break; + } + + set_current_state(TASK_INTERRUPTIBLE); + wake_up_all(&usbcam_work_idle_wait); + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + schedule(); + spin_lock_irqsave(&udp->ud_work_lock, flags); + } + udp->ud_work_lockwait = 1; + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + + /* Enable the mutex wait cancelation signal */ + sigprocmask(SIG_UNBLOCK, &wakesigs, NULL); + + /* Re-check the queue, wait if it's still nonempty */ + res = -EINTR; + if (!list_empty(&udp->ud_work_queue)) + res = mutex_lock_interruptible(&udp->ud_lock); + + /* Disable the mutex wait cancelation signal */ + sigprocmask(SIG_BLOCK, &wakesigs, NULL); + flush_signals(current); + + if (res) + continue; + + wip = NULL; + spin_lock_irqsave(&udp->ud_work_lock, flags); + udp->ud_work_lockwait = 0; + if (!list_empty(&udp->ud_work_queue)) { + wip = container_of(udp->ud_work_queue.next, + struct usbcam_workitem, + uw_links); + list_del_init(&wip->uw_links); + } + + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + + if (wip) { + fn = wip->uw_func; + fn(wip); + } + + usbcam_unlock(udp); + } + + return 0; +} + +void usbcam_work_init(struct usbcam_dev *udp, struct usbcam_workitem *wip, + usbcam_workfunc_t func) +{ + INIT_LIST_HEAD(&wip->uw_links); + wip->uw_dev = udp; + wip->uw_func = func; +} +USBCAM_EXPORT_SYMBOL(usbcam_work_init); + +int usbcam_work_queue(struct usbcam_workitem *wip) +{ + struct usbcam_dev *udp = wip->uw_dev; + unsigned long flags; + int res; + + assert(udp != NULL); + + spin_lock_irqsave(&udp->ud_work_lock, flags); + if (!list_empty(&wip->uw_links)) { + res = -EALREADY; + assert(wip->uw_dev == udp); + } else if (udp->ud_work_refs) { + res = 0; + wip->uw_dev = udp; + list_add_tail(&wip->uw_links, &udp->ud_work_queue); + if (udp->ud_work_queue.next == &wip->uw_links) + wake_up_process(udp->ud_work_thread); + } else { + res = -EBUSY; + } + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + + return res; +} +USBCAM_EXPORT_SYMBOL(usbcam_work_queue); + +int usbcam_work_cancel(struct usbcam_workitem *wip) +{ + struct usbcam_dev *udp = wip->uw_dev; + unsigned long flags; + int res, wakeit = 0; + + assert(udp != NULL); + usbcam_chklock(udp); + + res = -ENOENT; + spin_lock_irqsave(&udp->ud_work_lock, flags); + if (!list_empty(&wip->uw_links)) { + res = 0; + assert(wip->uw_dev == udp); + if ((udp->ud_work_queue.next == &wip->uw_links) && + udp->ud_work_lockwait) + wakeit = 1; + list_del_init(&wip->uw_links); + if (wakeit) + force_sig(SIGUSR1, udp->ud_work_thread); + } + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + + return res; +} +USBCAM_EXPORT_SYMBOL(usbcam_work_cancel); + +int usbcam_work_ref(struct usbcam_dev *udp) +{ + struct task_struct *kt_new; + unsigned long flags; + + usbcam_chklock(udp); + + /* + * We adjust this value under the spinlock to synchronize with + * usbcam_work_queue(). + */ + spin_lock_irqsave(&udp->ud_work_lock, flags); + udp->ud_work_refs++; + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + + if (!udp->ud_work_thread) { + kt_new = kthread_create(usbcam_work_thread, udp, + udp->ud_dev_name); + if (!kt_new) { + usbcam_err(udp, "%s: could not create worker thread", + __FUNCTION__); + return -ENOMEM; + } + + spin_lock_irqsave(&udp->ud_work_lock, flags); + udp->ud_work_thread = kt_new; + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + } + + return 0; +} +USBCAM_EXPORT_SYMBOL(usbcam_work_ref); + +void usbcam_work_unref(struct usbcam_dev *udp) +{ + unsigned long flags; + + usbcam_chklock(udp); + + if (!udp->ud_work_refs) { + usbcam_warn(udp, "%s: work queue has zero refs", __FUNCTION__); + return; + } + + spin_lock_irqsave(&udp->ud_work_lock, flags); + udp->ud_work_refs--; + spin_unlock_irqrestore(&udp->ud_work_lock, flags); +} +USBCAM_EXPORT_SYMBOL(usbcam_work_unref); + +void usbcam_work_runqueue(struct usbcam_dev *udp) +{ + struct usbcam_workitem *wip; + unsigned long flags; + usbcam_workfunc_t fn; + + usbcam_chklock(udp); + + spin_lock_irqsave(&udp->ud_work_lock, flags); + while (!list_empty(&udp->ud_work_queue)) { + wip = container_of(udp->ud_work_queue.next, + struct usbcam_workitem, + uw_links); + list_del_init(&wip->uw_links); + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + + fn = wip->uw_func; + fn(wip); + + spin_lock_irqsave(&udp->ud_work_lock, flags); + } + spin_unlock_irqrestore(&udp->ud_work_lock, flags); +} +USBCAM_EXPORT_SYMBOL(usbcam_work_runqueue); + + +void usbcam_work_stop(struct usbcam_dev *udp) +{ + struct task_struct *kt_stop = NULL; + unsigned long flags; + + usbcam_lock(udp); + + if (!udp->ud_work_refs) { + /* Prevent further tasks from being queued */ + spin_lock_irqsave(&udp->ud_work_lock, flags); + kt_stop = udp->ud_work_thread; + udp->ud_work_thread = NULL; + spin_unlock_irqrestore(&udp->ud_work_lock, flags); + } + + usbcam_unlock(udp); + + if (kt_stop) { + /* + * Wait for the queue to empty out, then stop the + * thread. It might be easier to just call + * usbcam_work_flush() and execute the remaining + * tasks synchronously in the current thread. + */ + wait_event(usbcam_work_idle_wait, + list_empty(&udp->ud_work_queue)); + kthread_stop(kt_stop); + } +} + + +static void usbcam_delayedwork_timeout(unsigned long data) +{ + struct usbcam_delayedwork *dwp = (struct usbcam_delayedwork *) data; + int res; + res = usbcam_work_queue(&dwp->dw_work); + if (res) + usbcam_warn(dwp->dw_work.uw_dev, + "delayed work item submit failed: %d", res); +} + +void usbcam_delayedwork_init(struct usbcam_dev *udp, + struct usbcam_delayedwork *dwp, + usbcam_workfunc_t func) +{ + usbcam_work_init(udp, &dwp->dw_work, func); + setup_timer(&dwp->dw_timer, + usbcam_delayedwork_timeout, + (unsigned long) dwp); +} +USBCAM_EXPORT_SYMBOL(usbcam_delayedwork_init); + +void usbcam_delayedwork_queue(struct usbcam_delayedwork *dwp, + unsigned int timeout_ms) +{ + dwp->dw_timer.expires = jiffies + ((timeout_ms * HZ) / 1000); + add_timer(&dwp->dw_timer); +} +USBCAM_EXPORT_SYMBOL(usbcam_delayedwork_queue); + +int usbcam_delayedwork_cancel(struct usbcam_delayedwork *dwp) +{ + if (timer_pending(&dwp->dw_timer) && del_timer_sync(&dwp->dw_timer)) + return 0; + return usbcam_work_cancel(&dwp->dw_work); +} +USBCAM_EXPORT_SYMBOL(usbcam_delayedwork_cancel); + + +/* + * Control related stuff + */ + +struct usbcam_ctrl *usbcam_ctrl_find(struct usbcam_dev *udp, u32 ctlid) +{ + struct usbcam_ctrl *ctrlp; + + usbcam_chklock(udp); + list_for_each_entry(ctrlp, &udp->ud_ctrl_list, uc_links) { + if (ctrlp->uc_v4l.id == ctlid) + return ctrlp; + } + return NULL; +} +USBCAM_EXPORT_SYMBOL(usbcam_ctrl_find); + +int usbcam_ctrl_add(struct usbcam_dev *udp, struct usbcam_ctrl *ctrlp) +{ + int errors = 0; + struct usbcam_ctrl *xctrlp; + + usbcam_chklock(udp); + + /* Verify that the ID isn't already registered */ + xctrlp = usbcam_ctrl_find(udp, ctrlp->uc_v4l.id); + if (xctrlp) { + usbcam_warn(udp, "control \"%s\" id=%d already defined", + ctrlp->uc_v4l.name, ctrlp->uc_v4l.id); + errors++; + } + + /* Check minimum, maximum, step, and default */ + switch (ctrlp->uc_v4l.type) { + case V4L2_CTRL_TYPE_INTEGER: + if (ctrlp->uc_v4l.minimum > ctrlp->uc_v4l.maximum) { + usbcam_warn(udp, "control \"%s\" has " + "minimum > maximum", + ctrlp->uc_v4l.name); + errors++; + } + break; + + case V4L2_CTRL_TYPE_BOOLEAN: + break; + + case V4L2_CTRL_TYPE_MENU: + if (ctrlp->uc_v4l.minimum) { + usbcam_warn(udp, "control \"%s\" is MENU and has " + "minimum != 0", + ctrlp->uc_v4l.name); + errors++; + } + if (!ctrlp->uc_v4l.maximum) { + usbcam_warn(udp, "control \"%s\" is MENU and has " + "maximum == 0", + ctrlp->uc_v4l.name); + errors++; + } + if (!ctrlp->uc_menu_names) { + usbcam_warn(udp, "control \"%s\" is MENU and has " + "NULL menu_names", + ctrlp->uc_v4l.name); + errors++; + break; + } + break; + + case V4L2_CTRL_TYPE_BUTTON: + if (ctrlp->uc_v4l.minimum) { + usbcam_warn(udp, "control \"%s\" is BUTTON " + "and has minimum != 0", + ctrlp->uc_v4l.name); + errors++; + } + if (ctrlp->uc_v4l.maximum) { + usbcam_warn(udp, "control \"%s\" is BUTTON " + "and has maximum != 0", + ctrlp->uc_v4l.name); + errors++; + } + if (ctrlp->uc_v4l.step) { + usbcam_warn(udp, "control \"%s\" is BUTTON " + "and has step != 0", + ctrlp->uc_v4l.name); + errors++; + } + break; + + default: + usbcam_warn(udp, "control \"%s\" is of " + "invalid type %d", + ctrlp->uc_v4l.name, + ctrlp->uc_v4l.type); + errors++; + } + + /* Check the range */ + if (ctrlp->uc_v4l.type == V4L2_CTRL_TYPE_BOOLEAN) { + if ((ctrlp->uc_v4l.default_value != 0) && + (ctrlp->uc_v4l.default_value != 1)) { + usbcam_warn(udp, "control \"%s\" is BOOLEAN " + "and default value is %d", + ctrlp->uc_v4l.name, + ctrlp->uc_v4l.default_value); + errors++; + } + } + + else if ((ctrlp->uc_v4l.default_value < + ctrlp->uc_v4l.minimum) || + (ctrlp->uc_v4l.default_value > + ctrlp->uc_v4l.maximum)) { + usbcam_warn(udp, "control \"%s\" default out of range", + ctrlp->uc_v4l.name); + errors++; + } + + /* Check the get_fn callout */ + if (ctrlp->uc_v4l.type == V4L2_CTRL_TYPE_BUTTON) { + if (ctrlp->get_fn) { + usbcam_warn(udp, "control \"%s\" is BUTTON " + "and has a get_fn callout", + ctrlp->uc_v4l.name); + errors++; + } + if (!ctrlp->set_fn) { + usbcam_warn(udp, "control \"%s\" is BUTTON " + "and has no set_fn callout", + ctrlp->uc_v4l.name); + errors++; + } + } + + if (errors) + return -EINVAL; + + list_add_tail(&ctrlp->uc_links, &udp->ud_ctrl_list); + return 0; +} +USBCAM_EXPORT_SYMBOL(usbcam_ctrl_add); + +struct usbcam_ctrl *usbcam_ctrl_alloc(size_t real_size) +{ + struct usbcam_ctrl *ctrlp; + + if (!real_size) + real_size = sizeof(*ctrlp); + if (real_size < sizeof(*ctrlp)) { + printk(KERN_WARNING "Minidriver set control size %zd, " + "must be at least %zd\n", real_size, sizeof(*ctrlp)); + return NULL; + } + + ctrlp = kzalloc(real_size, GFP_KERNEL); + if (!ctrlp) + return NULL; + + INIT_LIST_HEAD(&ctrlp->uc_links); + return ctrlp; +} +USBCAM_EXPORT_SYMBOL(usbcam_ctrl_alloc); + +struct usbcam_ctrl *usbcam_ctrl_add_tmpl(struct usbcam_dev *udp, + const struct usbcam_ctrl *tmplp, + size_t real_size) +{ + struct usbcam_ctrl *ctrlp; + + ctrlp = usbcam_ctrl_alloc(real_size); + if (!ctrlp) + return NULL; + + memcpy(ctrlp, tmplp, real_size); + INIT_LIST_HEAD(&ctrlp->uc_links); + + if (usbcam_ctrl_add(udp, ctrlp)) { + usbcam_ctrl_free(ctrlp); + return NULL; + } + + return ctrlp; +} +USBCAM_EXPORT_SYMBOL(usbcam_ctrl_add_tmpl); + +void usbcam_ctrl_releaseall(struct usbcam_dev *udp) +{ + struct usbcam_ctrl *ctrlp; + while (!list_empty(&udp->ud_ctrl_list)) { + ctrlp = list_entry(udp->ud_ctrl_list.next, + struct usbcam_ctrl, + uc_links); + list_del_init(&ctrlp->uc_links); + if (ctrlp->release_fn) + ctrlp->release_fn(udp, ctrlp); + kfree(ctrlp); + } +} + + +MODULE_DESCRIPTION("Abstraction Library for USB Webcam Drivers"); +MODULE_AUTHOR("Sam Revitch <samr7 cs washington edu>"); +MODULE_LICENSE("GPL"); diff --git a/usbcam/usbcam_fops.c b/usbcam/usbcam_fops.c @@ -0,0 +1,852 @@ +/* + * USBCAM abstraction library for USB webcam drivers + * Version 0.1.1 + * + * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu> + * + * 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, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "usbcam_priv.h" + +/* + * This file contains the file_operations implementation + * + * TODO LIST: + * - Add debug tracing to more ioctl paths + */ + +/* + * V4L file_operations callout implementations + */ + +static int usbcam_v4l_open(struct inode *inode, struct file *filp) +{ + struct usbcam_dev *udp; + struct usbcam_fh *ufp; + int autopm_ref = 0; + int work_ref = 0; + int res = 0; + + /* The usbcam_dev is referenced by the videodev at this point */ + udp = container_of(video_devdata(filp), struct usbcam_dev, ud_vdev); + + ufp = (struct usbcam_fh *) kzalloc(sizeof(*ufp), GFP_KERNEL); + if (!ufp) + return -ENOMEM; + + ufp->ufh_dev = udp; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + videobuf_queue_init(&ufp->ufh_vbq, + &usbcam_videobuf_qops, + NULL, + NULL, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct usbcam_frame), ufp); +#else + videobuf_queue_pci_init(&ufp->ufh_vbq, + &usbcam_videobuf_qops, + NULL, + NULL, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct usbcam_frame), ufp); +#endif + + mutex_lock(&udp->ud_open_lock); + + if (!udp->ud_user_refs) { + res = usb_autopm_get_interface(udp->ud_intf); + if (res) + goto bail_nolock; + autopm_ref = 1; + } + + usbcam_lock(udp); + + if (udp->ud_disconnected) { + res = -ENODEV; + goto bail; + } + + if (!udp->ud_user_refs) { + if (!udp->ud_minidrv->um_ops->no_workref_on_open) { + res = usbcam_work_ref(udp); + if (res) + goto bail; + work_ref = 1; + } + + if (usbcam_minidrv_op_present(udp, open)) { + res = usbcam_minidrv_op(udp, open); + if (res) { + if (work_ref) + usbcam_work_unref(udp); + assert(!udp->ud_user_refs); + goto bail; + } + } + + /* Transfer the autopm reference */ + assert(autopm_ref); + autopm_ref = 0; + } + + udp->ud_user_refs++; + filp->private_data = ufp; + usbcam_get(udp); + +bail: + usbcam_unlock(udp); +bail_nolock: + mutex_unlock(&udp->ud_open_lock); + if (res) + kfree(ufp); + if (autopm_ref) + usb_autopm_put_interface(udp->ud_intf); + usbcam_work_maybe_stop(udp); + return res; +} + +static int usbcam_v4l_release(struct inode *inode, struct file *filp) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data; + struct usbcam_dev *udp = ufp->ufh_dev; + int autopm_ref = 0; + + videobuf_mmap_free(&ufp->ufh_vbq); + + mutex_lock(&udp->ud_open_lock); + usbcam_lock(udp); + + assert(udp->ud_user_refs); + if (udp->ud_excl_owner == filp) + udp->ud_excl_owner = NULL; + kfree(ufp); + filp->private_data = NULL; + + if (!--udp->ud_user_refs) { + usbcam_capture_stop(udp); + + if (usbcam_minidrv_op_present(udp, close)) + usbcam_minidrv_op(udp, close); + if (!udp->ud_minidrv->um_ops->no_workref_on_open) + usbcam_work_unref(udp); + autopm_ref = 1; + } + + usbcam_unlock(udp); + mutex_unlock(&udp->ud_open_lock); + if (autopm_ref) + usb_autopm_put_interface(udp->ud_intf); + usbcam_work_maybe_stop(udp); + usbcam_put(udp); + return 0; +} + +static ssize_t usbcam_v4l_read(struct file *filp, char __user *data, + size_t count, loff_t *ppos) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data; + ssize_t res; + + res = videobuf_read_one(&ufp->ufh_vbq, data, count, ppos, + (filp->f_flags & O_NONBLOCK) ? 1 : 0); + usbcam_work_maybe_stop(ufp->ufh_dev); + return res; +} + +static unsigned int usbcam_v4l_poll(struct file *filp, + struct poll_table_struct *wait) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data; + + return videobuf_poll_stream(filp, &ufp->ufh_vbq, wait); +} + +static int usbcam_v4l_mmap(struct file *filp, struct vm_area_struct * vma) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data; + + return videobuf_mmap_mapper(&ufp->ufh_vbq, vma); +} + +/* + * The template file_operations structure + * + * Each usbcam_minidrv_t contains its own copy of this, which + * is associated with the video4linux device created for that + * minidriver. + * + * In general, copies will differ only in the .owner field, which + * will refer to the minidriver module, not usbcam. + */ + +struct file_operations usbcam_v4l_fops_template = { + .owner = THIS_MODULE, + .open = usbcam_v4l_open, + .release = usbcam_v4l_release, + .read = usbcam_v4l_read, + .poll = usbcam_v4l_poll, + .mmap = usbcam_v4l_mmap, + .ioctl = video_ioctl2, +#ifdef CONFIG_COMPAT + .compat_ioctl = v4l_compat_ioctl32, +#endif + .llseek = no_llseek, +}; + + +static void usbcam_dbg_v4l2_buffer_res(struct usbcam_dev *udp, int res, + void *arg, const char *prefix) +{ + struct v4l2_buffer *b __attribute__((unused)) = + (struct v4l2_buffer *) arg; + + if (res) { + usbcam_dbg(udp, IOCTL_BUF, "%s res:%d", prefix, res); + return; + } + + usbcam_dbg(udp, IOCTL_BUF, "%s out: index=%d type=%d bytesused=%d " + "flags=0x%x field=%d memory=%d m=0x%lx length=%d", + prefix, b->index, b->type, b->bytesused, + b->flags, b->field, b->memory, b->m.userptr, b->length); +} + +static void usbcam_dbg_v4l2_pix_format(struct usbcam_dev *udp, + struct v4l2_pix_format *f, + const char *prefix) +{ + __u32 pixfmt = f->pixelformat; + if (!pixfmt) + pixfmt = 0x3f3f3f3f; + usbcam_dbg(udp, IOCTL_FMT, "%s wid=%d hgt=%d fmt=%.4s field=%d " + "bpl=%d size=%d cs=%d", prefix, + f->width, f->height, (char *) &pixfmt, f->field, + f->bytesperline, f->sizeimage, f->colorspace); +} + +static void usbcam_dbg_v4l2_pix_format_res(struct usbcam_dev *udp, int res, + struct v4l2_pix_format *f, + const char *prefix) +{ + if (res) { + usbcam_dbg(udp, IOCTL_FMT, "%s %d", prefix, res); + return; + } + usbcam_dbg_v4l2_pix_format(udp, f, prefix); +} + +static int usbcam_v4l_vidiocgmbuf(struct file *filp, void *fh, + struct video_mbuf *p) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + struct v4l2_requestbuffers req; + unsigned int i; + int res; + + /* + * APPBUG: motion keeps the first mmap, yet requests + * larger capture sizes. + */ + usbcam_lock(udp); + ufp->ufh_flags |= USBCAM_FH_USE_FIXED_FB; + usbcam_unlock(udp); + + req.type = ufp->ufh_vbq.type; + req.count = 2; + req.memory = V4L2_MEMORY_MMAP; + res = videobuf_reqbufs(&ufp->ufh_vbq, &req); + if (res) { + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOCGMBUF reqbufs failed: %d", res); + return res; + } + + p->frames = req.count; + p->size = 0; + for (i = 0; i < p->frames; i++) { + p->offsets[i] = ufp->ufh_vbq.bufs[i]->boff; + p->size += ufp->ufh_vbq.bufs[i]->bsize; + } + + usbcam_dbg(udp, IOCTL_BUF, "VIDIOCGMBUF frames=%d size=%d", + p->frames, p->size); + return 0; +} + +static int usbcam_v4l_vidioc_reqbufs(struct file *filp, void *fh, + struct v4l2_requestbuffers *r) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + int res; + + /* APPBUG: disable USE_FIXED_FB if we enter this path */ + usbcam_lock(udp); + ufp->ufh_flags &= ~(USBCAM_FH_USE_FIXED_FB); + usbcam_unlock(udp); + + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOC_REQBUFS count=%d type=%d memory=%d", + r->count, r->type, r->memory); + res = videobuf_reqbufs(&ufp->ufh_vbq, r); + usbcam_dbg(udp, IOCTL_BUF, "REQBUFS result=%d", res); + usbcam_work_maybe_stop(udp); + return res; +} + +static int usbcam_v4l_vidioc_querybuf(struct file *filp, void *fh, + struct v4l2_buffer *b) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + int res; + + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOC_QUERYBUF in: index=%d type=%d", + b->index, b->type); + res = videobuf_querybuf(&ufp->ufh_vbq, b); + usbcam_dbg_v4l2_buffer_res(udp, res, b, "VIDIOC_QUERYBUF"); + usbcam_work_maybe_stop(udp); + return res; +} + +static int usbcam_v4l_vidioc_qbuf(struct file *filp, void *fh, + struct v4l2_buffer *b) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + int res; + + /* + * APPBUG: ptlib / Ekiga has an issue with zeroing the + * flags field before calling QBUF. + * + * Minidriver support for fast input switching is + * unavailable for the time being. + */ + b->flags = 0; + + usbcam_dbg(udp, IOCTL_BUF, "VIDIOC_QBUF in: index=%d type=%d", + b->index, b->type); + res = videobuf_qbuf(&ufp->ufh_vbq, b); + usbcam_dbg_v4l2_buffer_res(udp, res, b, "VIDIOC_QBUF"); + usbcam_work_maybe_stop(udp); + return res; +} + +static int usbcam_v4l_vidioc_dqbuf(struct file *filp, void *fh, + struct v4l2_buffer *b) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + int res; + + res = videobuf_dqbuf(&ufp->ufh_vbq, b, + (filp->f_flags & O_NONBLOCK) ? 1 : 0); + usbcam_dbg_v4l2_buffer_res(udp, res, b, "VIDIOC_DQBUF"); + usbcam_work_maybe_stop(udp); + return res; +} + +static int usbcam_v4l_vidioc_streamon(struct file *filp, void *fh, + enum v4l2_buf_type f) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + int res; + + if (f != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOC_STREAMON: invalid buf type %d", f); + return -EINVAL; + } + if (!udp->ud_excl_owner) { + usbcam_lock(udp); + if (!udp->ud_excl_owner) + udp->ud_excl_owner = filp; + usbcam_unlock(udp); + } + if (udp->ud_excl_owner != filp) { + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOC_STREAMON: not exclusive owner"); + return -EBUSY; + } + res = videobuf_streamon(&ufp->ufh_vbq); + usbcam_dbg(udp, IOCTL_BUF, "VIDIOC_STREAMON: res:%d", res); + usbcam_work_maybe_stop(udp); + return res; +} + +static int usbcam_v4l_vidioc_streamoff(struct file *filp, void *fh, + enum v4l2_buf_type f) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + int res; + + if (f != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + usbcam_dbg(udp, IOCTL_BUF, + "VIDIOC_STREAMOFF: invalid buf type %d", f); + return -EINVAL; + } + res = videobuf_streamoff(&ufp->ufh_vbq); + usbcam_dbg(udp, IOCTL_BUF, "VIDIOC_STREAMOFF: res:%d", res); + usbcam_work_maybe_stop(udp); + return res; +} + + +/* DEFAULT CAPABILITIES / DEVICE NAME / DRIVER NAME / BUS INFO */ +static int usbcam_v4l_vidioc_querycap(struct file *filp, void *fh, + struct v4l2_capability *cap) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + + usbcam_lock(udp); + strlcpy(cap->driver, + usbcam_drvname(udp->ud_minidrv), + sizeof(cap->driver)); + strlcpy(cap->card, udp->ud_vdev.name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "usb:%s", udp->ud_dev->dev.bus_id); + cap->version = udp->ud_minidrv->um_version; + cap->capabilities = (V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING); + usbcam_unlock(udp); + return 0; +} + +/* DEFAULT FORMAT HANDLING - USE MINDIRIVER CALLOUTS */ +static int usbcam_v4l_vidioc_enum_fmt_cap(struct file *filp, void *fh, + struct v4l2_fmtdesc *f) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + struct usbcam_pix_fmt *pf; + int res; + + usbcam_lock(udp); + + res = -EINVAL; + if (f->index >= udp->ud_fmt_array_len) + goto enum_fmt_done; + + res = 0; + pf = (struct usbcam_pix_fmt *) + &(((u8 *) udp->ud_fmt_array) + [f->index * udp->ud_fmt_array_elem_size]); + f->flags = pf->flags; + f->pixelformat = pf->pixelformat; + strlcpy(f->description, + pf->description, + sizeof(f->description)); + +enum_fmt_done: + usbcam_unlock(udp); + return res; +} + +static int usbcam_v4l_vidioc_g_fmt_cap(struct file *filp, void *fh, + struct v4l2_format *f) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + + usbcam_lock(udp); + f->fmt.pix = udp->ud_format; + usbcam_unlock(udp); + usbcam_dbg_v4l2_pix_format(udp, &f->fmt.pix, "VIDIOC_G_FMT: res:"); + return 0; +} + +static int usbcam_v4l_vidioc_s_fmt_cap(struct file *filp, void *fh, + struct v4l2_format *f) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + int res; + + usbcam_lock(udp); + + usbcam_dbg_v4l2_pix_format(udp, &f->fmt.pix, + "VIDIOC_S_FMT: param:"); + + if (udp->ud_disconnected) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_S_FMT: device disconnected"); + res = -EIO; + goto s_fmt_done; + } + if (!udp->ud_excl_owner) + udp->ud_excl_owner = filp; + if (!memcmp(&f->fmt.pix, &udp->ud_format, sizeof(f->fmt.pix))) { + usbcam_dbg(udp, IOCTL_FMT, "VIDIOC_S_FMT: nothing to do"); + res = 0; + goto s_fmt_done; + } + if (!usbcam_minidrv_op_present(udp, set_format)) { + usbcam_dbg(udp, IOCTL_FMT, "VIDIOC_S_FMT: no minidriver op"); + res = -EINVAL; + goto s_fmt_done; + } + if (udp->ud_excl_owner != filp) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_S_FMT: not exclusive owner"); + res = -EBUSY; + goto s_fmt_done; + } + usbcam_capture_stop_nondestructive(udp); + if (udp->ud_capturing) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_S_FMT: capture in progress"); + res = -EBUSY; + goto s_fmt_done; + } + res = usbcam_minidrv_op(udp, set_format, &f->fmt.pix); + usbcam_dbg_v4l2_pix_format_res(udp, res, &f->fmt.pix, + "VIDIOC_S_FMT: res:"); + +s_fmt_done: + usbcam_unlock(udp); + return res; +} + +static int usbcam_v4l_vidioc_try_fmt_cap(struct file *filp, void *fh, + struct v4l2_format *f) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + int res; + + usbcam_lock(udp); + + usbcam_dbg_v4l2_pix_format(udp, &f->fmt.pix, + "VIDIOC_TRY_FMT: param:"); + + if (udp->ud_disconnected) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_TRY_FMT: device disconnected"); + res = -EIO; + goto try_fmt_done; + } + if (!usbcam_minidrv_op_present(udp, try_format)) { + usbcam_dbg(udp, IOCTL_FMT, + "VIDIOC_TRY_FMT: no minidriver op"); + res = -EINVAL; + goto try_fmt_done; + } + + res = usbcam_minidrv_op(udp, try_format, &f->fmt.pix); + usbcam_dbg_v4l2_pix_format_res(udp, res, &f->fmt.pix, + "VIDIOC_TRY_FMT: res:"); + +try_fmt_done: + usbcam_unlock(udp); + usbcam_work_maybe_stop(udp); + return res; +} + +/* DEFAULT CONTROL HANDLING - USE MINIDRIVER ARRAY / CALLOUTS */ +static int usbcam_v4l_vidioc_queryctrl(struct file *filp, void *fh, + struct v4l2_queryctrl *a) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + const struct usbcam_ctrl *ctrlp, *resp; + int droplock; + u32 targ; + int higher_ids = 0, res = -EINVAL; + + usbcam_lock(udp); + droplock = 1; + targ = a->id; + + resp = NULL; + + if (targ & V4L2_CTRL_FLAG_NEXT_CTRL) { + /* + * Find the control with the least ID greater than or + * equal to a->id + */ + targ &= ~V4L2_CTRL_FLAG_NEXT_CTRL; + list_for_each_entry(ctrlp, &udp->ud_ctrl_list, uc_links) { + if (ctrlp->uc_v4l.id <= targ) { + if (!resp || (ctrlp->uc_v4l.id < + resp->uc_v4l.id)) + resp = ctrlp; + } + } + + } else { + /* Find an exact match */ + list_for_each_entry(ctrlp, &udp->ud_ctrl_list, uc_links) { + if (ctrlp->uc_v4l.id == targ) { + resp = ctrlp; + break; + } + else if ((ctrlp->uc_v4l.id >= + V4L2_CID_PRIVATE_BASE) && + (ctrlp->uc_v4l.id < + (V4L2_CID_PRIVATE_BASE + 1024)) && + (targ >= V4L2_CID_PRIVATE_BASE) && + (targ < ctrlp->uc_v4l.id)) { + higher_ids = 1; + } + } + + if (!resp && higher_ids) { + /* + * Deal with the private control enumeration + * nonsense that the CTRL_FLAG_NEXT_CTRL thing + * fixes. + */ + memset(a, 0, sizeof(*a)); + a->id = targ; + a->type = V4L2_CTRL_TYPE_INTEGER; + strlcpy(a->name, "Disabled", sizeof(a->name)); + a->flags = V4L2_CTRL_FLAG_DISABLED; + res = 0; + } + } + + if (resp) { + *a = resp->uc_v4l; + res = 0; + + /* Fill in required values for certain types */ + switch (a->type) { + case V4L2_CTRL_TYPE_BOOLEAN: + a->minimum = 0; + a->maximum = 1; + a->step = 1; + break; + case V4L2_CTRL_TYPE_MENU: + a->step = 1; + break; + default: + break; + } + + /* + * If a query function was provided, call it to + * postprocess the response structure, e.g. to set + * flags. + */ + if (resp->query_fn) { + if (udp->ud_minidrv->um_ops->unlocked_ctrl) { + usbcam_unlock(udp); + droplock = 0; + } + res = resp->query_fn(udp, resp, a); + } + } + + if (droplock) + usbcam_unlock(udp); + usbcam_work_maybe_stop(udp); + return res; +} + +static int usbcam_v4l_vidioc_g_ctrl(struct file *filp, void *fh, + struct v4l2_control *a) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + const struct usbcam_ctrl *resp; + struct v4l2_ext_control ec; + int droplock; + int res; + + usbcam_lock(udp); + droplock = 1; + + if (udp->ud_disconnected) { + usbcam_unlock(udp); + return -EIO; + } + + resp = usbcam_ctrl_find(udp, a->id); + if (!resp || + (resp->uc_v4l.type == V4L2_CTRL_TYPE_BUTTON) || + !resp->get_fn) + res = -EINVAL; + else { + if (udp->ud_minidrv->um_ops->unlocked_ctrl) { + usbcam_unlock(udp); + droplock = 0; + } + memset(&ec, 0, sizeof(ec)); + ec.id = a->id; + ec.value = a->value; + res = resp->get_fn(udp, resp, &ec); + memset(a, 0, sizeof(*a)); + a->id = ec.id; + a->value = ec.value; + } + + if (droplock) + usbcam_unlock(udp); + usbcam_work_maybe_stop(udp); + return res; +} + +static int usbcam_v4l_vidioc_s_ctrl(struct file *filp, void *fh, + struct v4l2_control *a) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + const struct usbcam_ctrl *resp; + struct v4l2_ext_control ec; + int droplock; + int res; + + usbcam_lock(udp); + droplock = 1; + + if (udp->ud_disconnected) { + usbcam_unlock(udp); + return -EIO; + } + + resp = usbcam_ctrl_find(udp, a->id); + if (!resp) { + res = -EINVAL; + } else if (!resp->set_fn) { + /* Read-only control */ + res = -EBUSY; + } else if ((resp->uc_v4l.type != V4L2_CTRL_TYPE_BUTTON) && + ((a->value < resp->uc_v4l.minimum) || + (a->value > resp->uc_v4l.maximum))) { + res = -ERANGE; + } else { + if (udp->ud_minidrv->um_ops->unlocked_ctrl) { + usbcam_unlock(udp); + droplock = 0; + } + memset(&ec, 0, sizeof(ec)); + ec.id = a->id; + ec.value = a->value; + res = resp->set_fn(udp, resp, &ec); + memset(a, 0, sizeof(*a)); + a->id = ec.id; + a->value = ec.value; + } + + if (droplock) + usbcam_unlock(udp); + usbcam_work_maybe_stop(udp); + return res; +} + +static int usbcam_v4l_vidioc_querymenu(struct file *filp, void *fh, + struct v4l2_querymenu *a) +{ + struct usbcam_fh *ufp = (struct usbcam_fh *) fh; + struct usbcam_dev *udp = ufp->ufh_dev; + const struct usbcam_ctrl *resp; + int res; + + usbcam_lock(udp); + + resp = usbcam_ctrl_find(udp, a->id); + + if (!resp || + (resp->uc_v4l.type != V4L2_CTRL_TYPE_MENU) || + (a->index > resp->uc_v4l.maximum)) { + res = -EINVAL; + goto querymenu_done; + } + + strlcpy(a->name, + resp->uc_menu_names[a->index], + sizeof(a->name)); + res = 0; + +querymenu_done: + usbcam_unlock(udp); + return res; +} + +/* DEFAULT INPUT HANDLING -- There is one input called "Camera" */ +static int usbcam_v4l_vidioc_enum_input(struct file *filp, void *fh, + struct v4l2_input *inp) +{ + const struct v4l2_input dfl_input = { + .name = "Camera", + .type = V4L2_INPUT_TYPE_CAMERA, + }; + + if (inp->index > 0) + return -EINVAL; + *inp = dfl_input; + return 0; +} + +static int usbcam_v4l_vidioc_g_input(struct file *filp, void *fh, + unsigned int *i) +{ + *i = 0; + return 0; +} + +static int usbcam_v4l_vidioc_s_input(struct file *filp, void *fh, + unsigned int i) +{ + if (i != 0) + return -EINVAL; + return 0; +} + +/* + * The template video_device structure + * + * Each usbcam_dev contains its own copy of this. The minidriver is + * free to install its own handlers for each interface, although it + * should take care not to screw up the frame buffer handling. + */ + +struct video_device usbcam_videodev_template = { + .name = "usbcam-unknown", + .type = VFL_TYPE_GRABBER, + .type2 = VID_TYPE_CAPTURE, + .minor = -1, + + .vidioc_querycap = usbcam_v4l_vidioc_querycap, + .vidioc_enum_fmt_cap = usbcam_v4l_vidioc_enum_fmt_cap, + .vidioc_g_fmt_cap = usbcam_v4l_vidioc_g_fmt_cap, + .vidioc_s_fmt_cap = usbcam_v4l_vidioc_s_fmt_cap, + .vidioc_try_fmt_cap = usbcam_v4l_vidioc_try_fmt_cap, + .vidioc_reqbufs = usbcam_v4l_vidioc_reqbufs, + .vidioc_querybuf = usbcam_v4l_vidioc_querybuf, + .vidioc_qbuf = usbcam_v4l_vidioc_qbuf, + .vidioc_dqbuf = usbcam_v4l_vidioc_dqbuf, + .vidiocgmbuf = usbcam_v4l_vidiocgmbuf, + .vidioc_enum_input = usbcam_v4l_vidioc_enum_input, + .vidioc_streamon = usbcam_v4l_vidioc_streamon, + .vidioc_streamoff = usbcam_v4l_vidioc_streamoff, + .vidioc_g_input = usbcam_v4l_vidioc_g_input, + .vidioc_s_input = usbcam_v4l_vidioc_s_input, + .vidioc_queryctrl = usbcam_v4l_vidioc_queryctrl, + .vidioc_g_ctrl = usbcam_v4l_vidioc_g_ctrl, + .vidioc_s_ctrl = usbcam_v4l_vidioc_s_ctrl, + .vidioc_querymenu = usbcam_v4l_vidioc_querymenu, +}; diff --git a/usbcam/usbcam_priv.h b/usbcam/usbcam_priv.h @@ -0,0 +1,202 @@ +/* + * USBCAM abstraction library for USB webcam drivers + * + * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu> + * + * 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, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This header file is private and internal to usbcam. + * DO NOT INCLUDE THIS HEADER FILE IN MINIDRIVERS. USE usbcam.h INSTEAD. + */ + +#if !defined(__USBCAM_PRIV_H__) +#define __USBCAM_PRIV_H__ + +#define CONFIG_USB_USBCAM_DEBUG + +#include "usbcam.h" + +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/smp_lock.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> +#include <linux/timer.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/videodev.h> +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) +#include <media/video-buf.h> +#else +#include <media/videobuf-dma-sg.h> +#endif + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +#define usb_endpoint_xfer_bulk(EPD) \ + (((EPD)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == \ + USB_ENDPOINT_XFER_BULK) +#define usb_endpoint_xfer_int(EPD) \ + (((EPD)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == \ + USB_ENDPOINT_XFER_INT) +#define usb_endpoint_xfer_isoc(EPD) \ + (((EPD)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == \ + USB_ENDPOINT_XFER_ISOC) +#define usb_endpoint_dir_in(EPD) \ + (((EPD)->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) +#define usb_autopm_get_interface(X) 0 +#define usb_autopm_put_interface(X) +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) */ + + +/* + * When creating a minidriver that is not part of the core kernel, it + * may be desired to use a specific version of usbcam. In this case, + * usbcam's symbols should be available only to the minidriver with + * which it is linked, and its symbols should not be exported. + */ +#define USBCAM_EXPORT_SYMBOL(X) EXPORT_SYMBOL(X) + +#define assert usbcam_assert + +#define usbcam_drvname(MDP) ((MDP)->um_owner->name) + +#if defined(CONFIG_USB_USBCAM_DEBUG) +#define usbcam_dbgm(MD, SUBSYS, FMT, ARG...) do { \ + if ((MD)->um_debug && \ + *(MD)->um_debug & (1UL << USBCAM_DBG_ ## SUBSYS)) \ + printk(KERN_DEBUG "%s: " FMT "\n", \ + (MD)->um_modname, ## ARG); \ +} while (0) +#else +#define usbcam_dbgm(MD, SUBSYS, FMT, ARG...) +#endif /* defined(CONFIG_USB_USBCAM_DEBUG) */ + +#define usbcam_minidrv_op_present(UDP, CB) \ + ((UDP)->ud_minidrv->um_ops->CB ? 1 : 0) +#define usbcam_minidrv_op(UDP, CB, ARGS...) \ + ((UDP)->ud_minidrv->um_ops->CB((UDP), ## ARGS)) + + +/* + * Private data structure definitions + */ + +/* + * This structure represents a registered minidriver + */ +struct usbcam_minidrv { + struct kref um_kref; + struct module *um_owner; + const char *um_modname; + int *um_debug; + int um_version; + int um_dev_count; + struct list_head um_dev_list; + int um_dev_privsize; + struct usb_driver um_usbdrv; + struct mutex um_lock; + const struct usbcam_dev_ops *um_ops; + struct video_device um_videodev_template; + struct file_operations um_v4l_fops; + const int *um_video_nr_array; + int um_video_nr_array_len; +}; + +/* + * The frame structure is generally managed by the video-buf module + * and represents some chunk of memory that the video4linux client + * requested as a frame buffer. It might be vmalloc()'d, or it might + * be mapped from a user address space. In either case, usbcam + * guarantees a contiguous kernel mapping accessible to the minidriver. + * + * The primary reason to use video-buf in usbcam is for its + * implementation of buffer mapping methods and "zero-copy" kernel-user + * data movement. The V4L2 API is quite rich, and it's much easier to + * use video-buf than to create a private full-featured implementation, + * and much more desirable to use video-buf than to limp along with a + * substandard implementation. The video-buf module isn't specifically + * used for DMA functionality, as most USB devices, with the possible + * exception of those employing bulk transfers, are unsuitable for + * direct frame buffer DMA. + * + * Minidrivers can access details of the current frame using + * usbcam_curframe_get(), and can signal completion of the current + * frame with usbcam_curframe_complete(). It is up to the minidriver + * to fill in the frame buffer. + */ +struct usbcam_frame { + struct videobuf_buffer vbb; + struct list_head cap_links; + void *vmap_base; + void *vmap_sof; +}; + +/* + * This structure represents an open file handle and the frame + * buffers associated with that client + */ +struct usbcam_fh { + struct usbcam_dev *ufh_dev; + int ufh_flags; + struct videobuf_queue ufh_vbq; +}; + +#define USBCAM_FH_USE_FIXED_FB 0x00000001 + +extern void usbcam_work_stop(struct usbcam_dev *udp); +extern void usbcam_capture_stop(struct usbcam_dev *udp); +extern void usbcam_ctrl_releaseall(struct usbcam_dev *udp); + +/* + * The below maybe_stop function allows usbcam to ensure that any kernel + * threads it creates are completely stopped and not executing any + * usbcam code at module unload time. + * + * This is ugly but it remains unclear how better to do it. + */ +static inline void usbcam_work_maybe_stop(struct usbcam_dev *udp) +{ + if (!udp->ud_work_refs) + usbcam_work_stop(udp); +} + +/* + * Only stop capturing if no frames are queued. + * + * We allow and encourage the minidriver to continue capturing in the + * last requested format, and have it stop autonomously when it receives + * its first data for the next frame but finds no frame available. This + * expedites the process for situations such as S_FMT which cannot + * tolerate capture being in progress. + */ +static inline void usbcam_capture_stop_nondestructive(struct usbcam_dev *udp) +{ + if (udp->ud_capturing && list_empty(&udp->ud_frame_cap_queue)) + usbcam_capture_stop(udp); +} + +extern struct videobuf_queue_ops usbcam_videobuf_qops; +extern struct file_operations usbcam_v4l_fops_template; +extern struct video_device usbcam_videodev_template; + +#endif /* !defined(__USBCAM_PRIV_H__) */ diff --git a/usbcam/usbcam_skel.c b/usbcam/usbcam_skel.c @@ -0,0 +1,119 @@ +/* + * USBCAM abstraction library for USB webcam drivers + * + * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu> + * + * This driver 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 driver 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 driver; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This is a usbcam skeleton driver intended to be a starting point for + * minidriver development. + */ + +#define USBCAM_DEBUG_DEFAULT 0xffff + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb.h> +#include "usbcam.h" + + +#define USBCAM_DBG_SKEL_INIT (USBCAM_DBGBIT_MD_START + 0) +#define USBCAM_DBG_SKEL_FRAME (USBCAM_DBGBIT_MD_START + 1) +#define USBCAM_DBG_SKEL_MDINTF (USBCAM_DBGBIT_MD_START + 2) +#define USBCAM_DBG_SKEL_CTRL (USBCAM_DBGBIT_MD_START + 3) + + +#define skel_info(RV, FMT...) usbcam_info((RV)->cs_parent, FMT) +#define skel_err(RV, FMT...) usbcam_err((RV)->cs_parent, FMT) +#define skel_dbg(RV, SUBSYS, FMT...) usbcam_dbg((RV)->cs_parent, SUBSYS, FMT) + +#define SKEL_VERSION KERNEL_VERSION(1,0,0) +#define SKEL_VERSION_EXTRA "" + + +/* + * struct skel_dev is the per-device private structure for this + * minidriver. We store all the details of the current state of the + * camera in here, other than those explicitly shared with usbcam. + */ +struct skel_dev { + struct usbcam_dev *cs_parent; +}; + +#define udp_skel(UDP) ((struct skel_dev *)((UDP)->ud_minidrv_data)) + +static int skel_usbcam_init(struct usbcam_dev *udp, + const struct usb_device_id *devid) +{ + struct skel_dev *cdp = udp_skel(udp); + cdp->cs_parent = udp; + + /* Probe/initialize the device */ + + return 0; +} + +static void skel_usbcam_disconnect(struct usbcam_dev *udp) +{ +} + +static int skel_usbcam_try_format(struct usbcam_dev *udp, + struct v4l2_pix_format *f) +{ + return -EINVAL; +} + +static int skel_usbcam_set_format(struct usbcam_dev *udp, + struct v4l2_pix_format *f) +{ + return -EINVAL; +} + +static int skel_usbcam_cap_start(struct usbcam_dev *udp) +{ + return -EINVAL; +} + +static void skel_usbcam_cap_stop(struct usbcam_dev *udp) +{ +} + + +static struct usbcam_dev_ops skel_usbcam_dev_ops = { + .init = skel_usbcam_init, + .disconnect = skel_usbcam_disconnect, + .try_format = skel_usbcam_try_format, + .set_format = skel_usbcam_set_format, + .cap_start = skel_usbcam_cap_start, + .cap_stop = skel_usbcam_cap_stop, +}; + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0xfff0, 0xfff0), .driver_info = 0 }, + { } +}; + +DEFINE_USBCAM_MINIDRV_MODULE(SKEL_VERSION, + SKEL_VERSION_EXTRA, + &skel_usbcam_dev_ops, + sizeof(struct skel_dev), + id_table) + +MODULE_DESCRIPTION("Driver for Skeleton Webcam"); +MODULE_AUTHOR("You <you yourdomain com>"); +MODULE_LICENSE("GPL"); diff --git a/usbcam/usbcam_util.c b/usbcam/usbcam_util.c @@ -0,0 +1,1008 @@ +/* + * USBCAM abstraction library for USB webcam drivers + * + * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu> + * + * 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, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "usbcam_priv.h" + +/* + * This file contains utility routines, including: + * - usbcam_choose_altsetting() for selecting int/isoc altsettings + * - The usbcam_urbstream accessory module + * - usbcam_hexdump() buffer dumping utility function + * + * TODO: + * - Find some way to alert minidrivers of URB shortages + */ + +/* + * Traverse the alternate setting list and find one that provides + * the least bandwidth that satisfies the minimum requirement. + */ +int usbcam_choose_altsetting(struct usbcam_dev *udp, int ifnum, + int pipe, int bytes_per_sec_min, + int pkt_min, int pkt_max, + int *altsetting_nr) +{ + struct usb_interface *intf; + const struct usb_host_interface *aintf; + const struct usb_endpoint_descriptor *epd = NULL; + int i, j; + + int wmp, bw; + int best_alt = -1, best_alt_bw = 0; + + usbcam_chklock(udp); + + if (udp->ud_disconnected) { + usbcam_warn(udp, "%s: device is disconnected", __FUNCTION__); + return -ENODEV; + } + + if (ifnum < 0) + ifnum = udp->ud_intf->cur_altsetting->desc.bInterfaceNumber; + + intf = usb_ifnum_to_if(udp->ud_dev, ifnum); + if (!intf) { + usbcam_warn(udp, "%s: interface %d does not exist", + __FUNCTION__, ifnum); + return -ENODEV; + } + + if ((bytes_per_sec_min >= 0) && + !usb_pipeisoc(pipe) && !usb_pipeint(pipe)) { + usbcam_warn(udp, "%s: minidriver specified bytes_per_sec_min " + "on non-iso non-int pipe", __FUNCTION__); + } + + for (i = 0; i < intf->num_altsetting; i++) { + + aintf = &intf->altsetting[i]; + for (j = 0; j < aintf->desc.bNumEndpoints; j++) { + epd = &aintf->endpoint[j].desc; + if ((epd->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK) == + usb_pipeendpoint(pipe)) + break; + } + + if (j == aintf->desc.bNumEndpoints) { + /* Desired endpoint not present in this descriptor */ + usbcam_dbg(udp, ALTSETTING, + "altsetting %d has no EP%d", + i, usb_pipeendpoint(pipe)); + continue; + } + + if (((usb_pipetype(pipe) == PIPE_ISOCHRONOUS) && + !usb_endpoint_xfer_isoc(epd)) || + ((usb_pipetype(pipe) == PIPE_INTERRUPT) && + !usb_endpoint_xfer_int(epd)) || + (usb_pipein(pipe) && !usb_endpoint_dir_in(epd)) || + (!usb_pipein(pipe) && usb_endpoint_dir_in(epd))) { + /* Something is horribly wrong */ + usbcam_dbg(udp, ALTSETTING, + "altsetting %d has unexpected EP%d", + i, usb_pipeendpoint(pipe)); + continue; + } + + bw = 0; + wmp = le16_to_cpu(epd->wMaxPacketSize); + + /* Bandwidth only applies to iso & int pipes */ + if (usb_pipeisoc(pipe) || usb_pipeint(pipe)) { + if (udp->ud_dev->speed == USB_SPEED_HIGH) { + /* 8 uframes per regular frame */ + bw = 8000; + + /* high bandwidth endpoint? */ + wmp = ((wmp & 0x7ff) * + (((wmp >> 11) & 0x3) + 1)); + } else { + bw = 1000; + wmp &= 0x7ff; + } + + bw *= wmp; + + /* Divide by interval / frame skippage */ + bw = bw / (1 << (epd->bInterval - 1)); + + usbcam_dbg(udp, ALTSETTING, + "altsetting %d provides %d B/s bandwidth", + i, bw); + + /* Check the bandwidth */ + if (bw < bytes_per_sec_min) + continue; + + } else + wmp &= 0x7ff; + + /* Check the packet size */ + if (((pkt_min >= 0) && (wmp < pkt_min)) || + ((pkt_max >= 0) && (wmp > pkt_max))) + continue; + + if ((best_alt < 0) || (bw < best_alt_bw)) { + best_alt = i; + best_alt_bw = bw; + } + } + + if (best_alt == -1) + return -ENODEV; + + *altsetting_nr = best_alt; + return 0; +} +USBCAM_EXPORT_SYMBOL(usbcam_choose_altsetting); + + +/* + * DMA buffer helper routines + */ +static int usbcam_urb_allocbuf(struct urb *urbp, size_t nbytes) +{ + urbp->transfer_buffer = usb_buffer_alloc(urbp->dev, + nbytes, + GFP_KERNEL, + &urbp->transfer_dma); + if (!urbp->transfer_buffer) + return -ENOMEM; + + urbp->transfer_buffer_length = nbytes; + urbp->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + return 0; +} + +static inline void usbcam_urb_freebuf(struct urb *urbp) +{ + usb_buffer_free(urbp->dev, + urbp->transfer_buffer_length, + urbp->transfer_buffer, + urbp->transfer_dma); +} + +struct urb *usbcam_urb_alloc(struct usbcam_dev *udp, int pipe, + int pktsize, int npkts, int alloc_buf) +{ + struct urb *urbp; + int nbytes, i; + + if (npkts) { + if (!usb_pipeisoc(pipe)) { + usbcam_err(udp, "npkts=%d but !pipeisoc", npkts); + return NULL; + } + } else if (usb_pipeisoc(pipe)) { + usbcam_err(udp, "npkts=0 but pipeisoc"); + return NULL; + } + + + urbp = usb_alloc_urb(npkts, GFP_KERNEL); + if (!urbp) + return NULL; + + urbp->dev = udp->ud_dev; + urbp->pipe = pipe; + urbp->number_of_packets = npkts; + urbp->transfer_buffer = NULL; + urbp->transfer_buffer_length = 0; + + if (alloc_buf) { + nbytes = pktsize; + if (npkts) + nbytes *= npkts; + + if (usbcam_urb_allocbuf(urbp, nbytes)) { + usb_free_urb(urbp); + return NULL; + } + } + + if (npkts) + urbp->transfer_flags |= URB_ISO_ASAP; + + for (i = 0; i < npkts; i++) { + urbp->iso_frame_desc[i].offset = (i * pktsize); + urbp->iso_frame_desc[i].length = pktsize; + urbp->iso_frame_desc[i].actual_length = 0; + urbp->iso_frame_desc[i].status = 0; + } + + return urbp; +} +USBCAM_EXPORT_SYMBOL(usbcam_urb_alloc); + +void usbcam_urb_free(struct urb *urbp, int free_buf) +{ + if (free_buf) + usbcam_urb_freebuf(urbp); + usb_free_urb(urbp); +} +USBCAM_EXPORT_SYMBOL(usbcam_urb_free); + + +/* + * Streaming request helper functions + * This depends on the other usbcam stuff, but they don't depend on it, + * and it should be considered an extension sub-library. + */ + +/* Default parameters for config_iso and config_bulk */ +#define USBCAM_DFL_ISO_REQS 8 +#define USBCAM_DFL_ISO_URB_PKTS 32 + +#define USBCAM_DFL_BULK_REQS 8 + + +/* + * This structure represents a set of USB requests - URBs and buffers + */ +struct usbcam_urbinfo { + struct list_head ib_links; + struct usbcam_urbstream *ib_urbstream; + struct task_struct **ib_worker; + struct usbcam_workitem ib_workitem; + struct timer_list ib_timeout; + unsigned short ib_nurbs; + unsigned short ib_cururb; + struct urb *ib_urbs[0]; +}; + +/* usp->us_lock must be held on entry */ +static void usbcam_urbstream_resubmit(struct usbcam_urbstream *usp, + struct usbcam_urbinfo *ibp) +{ + int res; + + if (!ibp->ib_cururb) { + list_del(&ibp->ib_links); + list_add(&ibp->ib_links, &usp->us_active_list); + usp->us_active_count++; + } + + res = usb_submit_urb(ibp->ib_urbs[ibp->ib_cururb], GFP_ATOMIC); + if (res == -EL2NSYNC) + res = usb_submit_urb(ibp->ib_urbs[ibp->ib_cururb], GFP_ATOMIC); + + if (res) { + usbcam_dbg(usp->us_dev, URBSTREAM, + "urbstream[%d] resubmit %p/%d failed: %d", + usp->us_endpoint, ibp, ibp->ib_cururb, res); + usp->us_resubmit_err = res; + + ibp->ib_cururb = 0; + list_del(&ibp->ib_links); + list_add(&ibp->ib_links, &usp->us_unused_list); + if (!--usp->us_active_count) + complete(&usp->us_active_empty); + + (void) usbcam_work_queue(&usp->us_error_workitem); + } + + else if (usp->us_timeout_ticks) { + ibp->ib_timeout.expires = jiffies + usp->us_timeout_ticks; + add_timer(&ibp->ib_timeout); + } +} + +/* usp->us_lock NOT held on entry */ +static int usbcam_urbstream_submit_unused(struct usbcam_urbstream *usp) +{ + struct usbcam_urbinfo *ibp = NULL; + long flags; + int res; + + spin_lock_irqsave(&usp->us_lock, flags); + + if (!list_empty(&usp->us_unused_list)) { + ibp = list_entry(usp->us_unused_list.next, + struct usbcam_urbinfo, ib_links); + list_del(&ibp->ib_links); + list_add_tail(&ibp->ib_links, &usp->us_active_list); + usp->us_active_count++; + ibp->ib_cururb = 0; + } + + spin_unlock_irqrestore(&usp->us_lock, flags); + + if (!ibp) + return -ENOENT; + + usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] urb submit %p/%d", + usp->us_endpoint, ibp, ibp->ib_cururb); + res = usb_submit_urb(ibp->ib_urbs[ibp->ib_cururb], GFP_KERNEL); + if (res == -EL2NSYNC) + res = usb_submit_urb(ibp->ib_urbs[ibp->ib_cururb], GFP_KERNEL); + + if (!res && usp->us_timeout_ticks) { + ibp->ib_timeout.expires = jiffies + usp->us_timeout_ticks; + add_timer(&ibp->ib_timeout); + } + + if (res) { + usbcam_dbg(usp->us_dev, URBSTREAM, "%s: URB submit failed: %d", + __FUNCTION__, res); + spin_lock_irqsave(&usp->us_lock, flags); + list_del(&ibp->ib_links); + list_add_tail(&ibp->ib_links, &usp->us_unused_list); + assert(usp->us_active_count > 0); + usp->us_active_count--; + spin_unlock_irqrestore(&usp->us_lock, flags); + } + + return res; +} + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19) +static void usbcam_urbstream_urb_complete(struct urb *urb) +#else +static void usbcam_urbstream_urb_complete(struct urb *urb, + struct pt_regs *unused) +#endif +{ + struct usbcam_urbinfo *ibp = (struct usbcam_urbinfo *) urb->context; + struct usbcam_urbstream *usp = ibp->ib_urbstream; + long flags; + int timer_fired = 0, res; + + assert(ibp->ib_urbs[ibp->ib_cururb] == urb); + + if (usp->us_timeout_ticks) + timer_fired = !del_timer_sync(&ibp->ib_timeout); + + usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] urb complete: %p/%d", + usp->us_endpoint, ibp, ibp->ib_cururb); + + spin_lock_irqsave(&usp->us_lock, flags); + + if (list_empty(&ibp->ib_links)) { + /* We are being singled out for cancelation, do nothing */ + usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] " + "request canceled, ignoring", usp->us_endpoint); + goto done; + } + + ibp->ib_cururb++; + if (!urb->status) { + if (ibp->ib_cururb != ibp->ib_nurbs) { + usbcam_urbstream_resubmit(usp, ibp); + goto done; + } + } + + /* Move to the done queue, submit a new URB, wake */ + list_del(&ibp->ib_links); + list_add_tail(&ibp->ib_links, &usp->us_complete_list); + assert(usp->us_active_count > 0); + usp->us_active_count--; + + if ((urb->status == -ECONNRESET) && timer_fired) { + usbcam_dbg(usp->us_dev, URBSTREAM, + "urbstream[%d] urb %p/%d timed out", + usp->us_endpoint, ibp, ibp->ib_cururb); + urb->status = -ETIMEDOUT; + } + + res = usbcam_work_queue(&ibp->ib_workitem); + if (res) { + assert(res == -EBUSY); + list_del(&ibp->ib_links); + list_add_tail(&ibp->ib_links, &usp->us_unused_list); + goto done; + } + + if (usp->us_resubmit_err) { + usbcam_dbg(usp->us_dev, URBSTREAM, + "urbstream[%d] not resubmitting, pending error", + usp->us_endpoint); + goto done; + } + + /* Is the active count sufficient? */ + if (usp->us_active_count >= usp->us_active_goal) + goto done; + + /* Should we automatically submit an URB from the unused list? */ + if (usp->us_streaming) { + if (list_empty(&usp->us_unused_list)) { + usbcam_dbg(usp->us_dev, URBSTREAM, + "urbstream[%d] not resubmitting, " + "URBs exhausted", + usp->us_endpoint); + goto done; + } + + ibp = list_entry(usp->us_unused_list.next, + struct usbcam_urbinfo, ib_links); + + ibp->ib_cururb = 0; + usbcam_urbstream_resubmit(usp, ibp); + } + +done: + if (!usp->us_active_count) + complete(&usp->us_active_empty); + spin_unlock_irqrestore(&usp->us_lock, flags); +} + +static void usbcam_urbstream_urb_timeout(unsigned long data) +{ + struct usbcam_urbinfo *ibp = (struct usbcam_urbinfo *) data; + usb_unlink_urb(ibp->ib_urbs[ibp->ib_cururb]); +} + +static void usbcam_urbstream_req_done(struct usbcam_urbinfo *ibp) +{ + struct usbcam_urbstream *usp; + long flags; + + usp = ibp->ib_urbstream; + spin_lock_irqsave(&usp->us_lock, flags); + + if (usp->us_streaming && + (usp->us_active_count < usp->us_active_goal)) { + /* Try to limp along with underflows */ + ibp->ib_cururb = 0; + usbcam_urbstream_resubmit(usp, ibp); + + } else { + list_del(&ibp->ib_links); + list_add(&ibp->ib_links, &usp->us_unused_list); + } + + spin_unlock_irqrestore(&usp->us_lock, flags); +} + +static void usbcam_urbstream_freereq(struct usbcam_urbinfo *ibp) +{ + int i; + assert(list_empty(&ibp->ib_links)); + for (i = 0; i < ibp->ib_nurbs; i++) + usbcam_urb_free(ibp->ib_urbs[i], 1); + kfree(ibp); +} + +static struct usbcam_urbinfo * +usbcam_urbstream_allocreq(struct usbcam_urbstream *usp, int pipe, int ival, + int pktlen, int npkts, int alloc) +{ + struct usbcam_urbinfo *ibp; + int sizeremain, nurbs, cpktlen; + int i; + + usbcam_chklock(usp->us_dev); + + sizeremain = 0; + nurbs = 1; + if (usb_pipebulk(pipe)) { + sizeremain = npkts; + npkts = 0; + nurbs = (sizeremain + pktlen - 1) / pktlen; + } + + ibp = kzalloc(sizeof(*ibp) + (nurbs * sizeof(struct urb *)), + GFP_KERNEL); + if (!ibp) + return NULL; + + INIT_LIST_HEAD(&ibp->ib_links); + ibp->ib_urbstream = usp; + ibp->ib_nurbs = nurbs; + + for (i = 0; i < nurbs; i++) { + cpktlen = pktlen; + if (sizeremain && (cpktlen > sizeremain)) + cpktlen = sizeremain; + ibp->ib_urbs[i] = usbcam_urb_alloc(usp->us_dev, pipe, cpktlen, + npkts, alloc); + + if (!ibp->ib_urbs[i]) + break; + + ibp->ib_urbs[i]->complete = usbcam_urbstream_urb_complete; + ibp->ib_urbs[i]->interval = ival; + ibp->ib_urbs[i]->context = ibp; + + + if (sizeremain) + sizeremain -= cpktlen; + } + + if (i < nurbs) { + while (i--) + usbcam_urb_free(ibp->ib_urbs[i], alloc); + kfree(ibp); + return NULL; + } + + setup_timer(&ibp->ib_timeout, + usbcam_urbstream_urb_timeout, + (unsigned long) ibp); + + return ibp; +} + +static void usbcam_urbstream_freereqs(struct list_head *head) +{ + struct usbcam_urbinfo *ibp; + while (!list_empty(head)) { + ibp = list_entry(head->next, struct usbcam_urbinfo, ib_links); + list_del_init(&ibp->ib_links); + usbcam_urbstream_freereq(ibp); + } +} + +static void usbcam_urbstream_error(struct usbcam_workitem *work) +{ + struct usbcam_urbstream *usp = container_of(work, + struct usbcam_urbstream, + us_error_workitem); + unsigned long flags; + int sts; + + spin_lock_irqsave(&usp->us_lock, flags); + sts = usp->us_resubmit_err; + usp->us_resubmit_err = 0; + spin_unlock_irqrestore(&usp->us_lock, flags); + + if (sts && usp->us_ops && usp->us_ops->submit_error) + usp->us_ops->submit_error(usp->us_dev, usp, sts); +} + +void usbcam_urbstream_init(struct usbcam_urbstream *usp, + struct usbcam_dev *udp, int ep) +{ + memset(usp, 0, sizeof(*usp)); + usp->us_dev = udp; + usp->us_endpoint = ep; + spin_lock_init(&usp->us_lock); + INIT_LIST_HEAD(&usp->us_unused_list); + INIT_LIST_HEAD(&usp->us_active_list); + INIT_LIST_HEAD(&usp->us_complete_list); + init_completion(&usp->us_active_empty); + usbcam_work_init(usp->us_dev, + &usp->us_error_workitem, + usbcam_urbstream_error); +} +USBCAM_EXPORT_SYMBOL(usbcam_urbstream_init); + +void usbcam_urbstream_stop(struct usbcam_urbstream *usp, int wait) +{ + long flags; + struct usbcam_urbinfo *ibp, *prev; + int res; + + usbcam_chklock(usp->us_dev); + + usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] stopping", + usp->us_endpoint); + spin_lock_irqsave(&usp->us_lock, flags); + + if (usp->us_streaming) + usp->us_streaming = 0; + + if (!wait) { + /* + * Cancel all in-flight requests without waiting + */ + while (!list_empty(&usp->us_active_list)) { + ibp = list_entry(usp->us_active_list.prev, + struct usbcam_urbinfo, ib_links); + list_del_init(&ibp->ib_links); + assert(usp->us_active_count > 0); + usp->us_active_count--; + spin_unlock_irqrestore(&usp->us_lock, flags); + usb_kill_urb(ibp->ib_urbs[ibp->ib_cururb]); + spin_lock_irqsave(&usp->us_lock, flags); + list_add(&ibp->ib_links, &usp->us_unused_list); + } + } else { + /* + * Wait for all in-flight request groups to complete + * Do so while holding the device mutex, which is a bit ugly + */ + while (!list_empty(&usp->us_active_list)) { + init_completion(&usp->us_active_empty); + spin_unlock_irqrestore(&usp->us_lock, flags); + wait_for_completion(&usp->us_active_empty); + spin_lock_irqsave(&usp->us_lock, flags); + } + } + + + /* Cancel all completed request groups with queued work items */ + list_for_each_entry_safe(ibp, prev, + &usp->us_complete_list, + ib_links) { + + res = usbcam_work_cancel(&ibp->ib_workitem); + + if (!ibp->ib_worker) + assert(!res); + else { + assert(res); + assert(*ibp->ib_worker == current); + *ibp->ib_worker = NULL; + ibp->ib_worker = NULL; + } + + list_del(&ibp->ib_links); + list_add_tail(&ibp->ib_links, &usp->us_unused_list); + } + + /* Cancel the error work item */ + (void) usbcam_work_cancel(&usp->us_error_workitem); + + /* Clear the resubmission error code */ + usp->us_resubmit_err = 0; + + spin_unlock_irqrestore(&usp->us_lock, flags); + usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] stopped", + usp->us_endpoint); +} +USBCAM_EXPORT_SYMBOL(usbcam_urbstream_stop); + +int usbcam_urbstream_start(struct usbcam_urbstream *usp) +{ + struct usbcam_dev *udp; + long flags; + int submitted = 0, res; + + udp = usp->us_dev; + + usbcam_chklock(udp); + + spin_lock_irqsave(&usp->us_lock, flags); + if (usp->us_streaming) { + spin_unlock_irqrestore(&usp->us_lock, flags); + usbcam_warn(udp, "%s: urbstream[%d] already streaming", + __FUNCTION__, usp->us_endpoint); + return -EEXIST; + } + + if (list_empty(&usp->us_unused_list)) { + usbcam_warn(udp, "%s urbstream[%d] no unused URBs", + __FUNCTION__, usp->us_endpoint); + return -ENOENT; + } + + usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] starting", + usp->us_endpoint); + usp->us_streaming = 1; + + spin_unlock_irqrestore(&usp->us_lock, flags); + + /* Submit initial URB(s) */ + while (1) { + res = usbcam_urbstream_submit_unused(usp); + assert(res != -ENOENT); + if (res) { + usbcam_urbstream_stop(usp, 0); + return res; + } + + if (++submitted == usp->us_active_goal) + break; + } + + usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] started", + usp->us_endpoint); + return 0; + +} +USBCAM_EXPORT_SYMBOL(usbcam_urbstream_start); + +int usbcam_urbstream_submit_one(struct usbcam_urbstream *usp) +{ + usbcam_chklock(usp->us_dev); + assert(!usp->us_streaming); + return usbcam_urbstream_submit_unused(usp); +} +USBCAM_EXPORT_SYMBOL(usbcam_urbstream_submit_one); + +void usbcam_urbstream_cleanup(struct usbcam_urbstream *usp) +{ + usbcam_urbstream_stop(usp, 0); + usbcam_urbstream_freereqs(&usp->us_unused_list); + assert(list_empty(&usp->us_active_list)); + assert(list_empty(&usp->us_complete_list)); +} +USBCAM_EXPORT_SYMBOL(usbcam_urbstream_cleanup); + + +static void usbcam_urbstream_iso_process(struct usbcam_workitem *work) +{ + struct usbcam_urbinfo *ibp = container_of(work, struct usbcam_urbinfo, + ib_workitem); + struct usbcam_urbstream *usp = ibp->ib_urbstream; + struct task_struct *me = current; + struct urb *urbp; + int i; + + usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] processing %p", + usp->us_endpoint, ibp); + + assert(!ibp->ib_worker); + ibp->ib_worker = &me; + + urbp = ibp->ib_urbs[0]; + for (i = 0; i < urbp->number_of_packets; i++) { + char *buf = (((char *) urbp->transfer_buffer) + + urbp->iso_frame_desc[i].offset); + int len = urbp->iso_frame_desc[i].actual_length; + int status = urbp->iso_frame_desc[i].status; + + urbp->iso_frame_desc[i].actual_length = 0; + urbp->iso_frame_desc[i].status = 0; + + usp->us_ops->packet_done(usp->us_dev, + usp, buf, len, status); + if (!me) + return; + } + + assert(ibp->ib_worker == &me); + ibp->ib_worker = NULL; + + usbcam_urbstream_req_done(ibp); +} + +static int usbcam_urbstream_allocreqs_isorcv(struct usbcam_urbstream *usp, + struct list_head *head, int count, + int ival, int pktcount, + int pktlen) +{ + struct usbcam_urbinfo *ibp; + struct list_head new_bufs; + int pipe; + + pipe = usb_rcvisocpipe(usp->us_dev->ud_dev, usp->us_endpoint); + + INIT_LIST_HEAD(&new_bufs); + + while (count--) { + ibp = usbcam_urbstream_allocreq(usp, pipe, ival, + pktlen, pktcount, 1); + if (!ibp) { + usbcam_urbstream_freereqs(&new_bufs); + return -ENOMEM; + } + + usbcam_work_init(usp->us_dev, + &ibp->ib_workitem, + usbcam_urbstream_iso_process); + list_add_tail(&ibp->ib_links, &new_bufs); + } + + list_splice(&new_bufs, head); + return 0; +} + +int usbcam_urbstream_config_iso(struct usbcam_urbstream *usp, + const struct usbcam_urbstream_ops *ops, + int pktcount, int nreqs, int interval, + int pktlen) +{ + int res; + + usbcam_urbstream_cleanup(usp); + + usp->us_active_goal = 2; + usp->us_ops = ops; + + if (!interval) { + /* FIXME: find the appropriate interval for the endpoint */ + return -EINVAL; + } + + if (!pktlen) { + /* Choose a packet length based on the current altsetting */ + pktlen = usb_maxpacket(usp->us_dev->ud_dev, + usb_rcvisocpipe(usp->us_dev->ud_dev, + usp->us_endpoint), 0); + if (!pktlen) { + usbcam_dbg(usp->us_dev, URBSTREAM, + "urbstream[%d]: current altsetting has " + "maxpacket=0", usp->us_endpoint); + return -EINVAL; + } + if (usp->us_dev->ud_dev->speed == USB_SPEED_HIGH) + pktlen = (pktlen & 0x7ff) * + (((pktlen >> 11) & 0x3) + 1); + else + pktlen &= 0x7ff; + + usbcam_dbg(usp->us_dev, URBSTREAM, + "urbstream[%d] using pktlen %d", + usp->us_endpoint, pktlen); + } + + if (!pktcount) + pktcount = USBCAM_DFL_ISO_URB_PKTS; + if (!nreqs) + nreqs = USBCAM_DFL_ISO_REQS; + if (nreqs < usp->us_active_goal) { + usbcam_warn(usp->us_dev, "%s urbstream[%d]: at least %d reqs " + "are required", __FUNCTION__, usp->us_endpoint, + usp->us_active_goal); + nreqs = usp->us_active_goal; + } + + usp->us_timeout_ticks = 0; + + res = usbcam_urbstream_allocreqs_isorcv(usp, &usp->us_unused_list, + nreqs, interval, pktcount, + pktlen); + return res; +} +USBCAM_EXPORT_SYMBOL(usbcam_urbstream_config_iso); + + +static void usbcam_urbstream_bulk_process(struct usbcam_workitem *work) +{ + struct usbcam_urbinfo *ibp = container_of(work, struct usbcam_urbinfo, + ib_workitem); + struct usbcam_urbstream *usp = ibp->ib_urbstream; + struct task_struct *me = current; + struct urb *urbp; + int len, status; + int i; + + usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] processing %p", + usp->us_endpoint, ibp); + + assert(!ibp->ib_worker); + ibp->ib_worker = &me; + + for (i = 0; i < ibp->ib_cururb; i++) { + urbp = ibp->ib_urbs[i]; + len = urbp->actual_length; + status = urbp->status; + + urbp->actual_length = 0; + urbp->status = 0; + + usp->us_ops->packet_done(usp->us_dev, usp, + urbp->transfer_buffer, len, status); + if (!me) + return; + } + + assert(ibp->ib_worker == &me); + ibp->ib_worker = NULL; + + usbcam_urbstream_req_done(ibp); +} + +static int usbcam_urbstream_allocreqs_bulkrcv(struct usbcam_urbstream *usp, + struct list_head *head, + int count, int reqlen, + int maxpkt) +{ + struct usbcam_urbinfo *ibp; + struct list_head new_bufs; + int pipe; + + pipe = usb_rcvbulkpipe(usp->us_dev->ud_dev, usp->us_endpoint); + + INIT_LIST_HEAD(&new_bufs); + + while (count--) { + ibp = usbcam_urbstream_allocreq(usp, pipe, 0, + maxpkt, reqlen, 1); + if (!ibp) { + usbcam_urbstream_freereqs(&new_bufs); + return -ENOMEM; + } + + usbcam_work_init(usp->us_dev, + &ibp->ib_workitem, + usbcam_urbstream_bulk_process); + list_add_tail(&ibp->ib_links, &new_bufs); + } + + list_splice(&new_bufs, head); + return 0; +} + +int usbcam_urbstream_config_bulk(struct usbcam_urbstream *usp, + const struct usbcam_urbstream_ops *ops, + int nreqs, int reqlen, int maxpkt, + int timeout_ms) +{ + int res; + + usbcam_urbstream_cleanup(usp); + + usp->us_active_goal = 1; + usp->us_ops = ops; + + if (!maxpkt) + maxpkt = 64 * 1024; + if (reqlen < 0) { + usbcam_dbg(usp->us_dev, URBSTREAM, + "urbstream[%d]: packet length must be >=0", + usp->us_endpoint); + return -EINVAL; + } + + if (!nreqs) + nreqs = USBCAM_DFL_BULK_REQS; + if (nreqs < usp->us_active_goal) { + usbcam_warn(usp->us_dev, "%s urbstream[%d]: at least %d URBs " + "are required", __FUNCTION__, usp->us_endpoint, + usp->us_active_goal); + nreqs = usp->us_active_goal; + } + + usp->us_timeout_ticks = (timeout_ms * HZ) / 1000; + + res = usbcam_urbstream_allocreqs_bulkrcv(usp, &usp->us_unused_list, + nreqs, reqlen, maxpkt); + return res; +} +USBCAM_EXPORT_SYMBOL(usbcam_urbstream_config_bulk); + + +#ifdef usbcam_hexdump +#undef usbcam_hexdump +#endif +#define dumpable_char(X) (((X) >= ' ') && ((X) <= '~')) +void usbcam_hexdump(struct usbcam_dev *udp, const u8 *buf, size_t len) +{ + const int bpl = 16; + const int cend_max = ((bpl * 4) + 1); + static const char n2x[16] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + char x, outbuf[cend_max + 1]; + unsigned int cstart, cend, xpos, cpos, offset; + + offset = 0; + cstart = (3 * bpl) + 1; + cend = cstart + bpl; + outbuf[cend] = '\0'; + goto beginning; + + while (len) { + x = *buf++; + outbuf[xpos++] = n2x[(x >> 4) & 0xf]; + outbuf[xpos++] = n2x[x & 0xf]; + outbuf[cpos+cstart] = dumpable_char(x) ? x : '.'; + cpos++; + xpos++; + len--; + + if (!len || (cpos == bpl)) { + printk(KERN_DEBUG "%s: %08x %s\n", + udp->ud_dev_name, offset, outbuf); + offset += bpl; + + beginning: + memset(outbuf, ' ', cend); + cpos = 0; + xpos = 0; + } + } +} +USBCAM_EXPORT_SYMBOL(usbcam_hexdump);