r5u870

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

commit 0d1f1ab2feb94f52025e6b52affda3db8bdb03f5
Author: alex <alex@022568fa-442e-4ef8-a3e8-54dcafdb011a>
Date:   Wed,  2 Jan 2008 05:24:44 +0000

Woops, small import screwup.


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

Diffstat:
ACOPYING | 340+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AChangeLog | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AKbuild | 2++
AMakefile | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AREADME | 179+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ar5u870_1810.fw | 0
Ar5u870_1830.fw | 0
Ar5u870_1832.fw | 0
Ar5u870_1833.fw | 0
Ar5u870_1834.fw | 0
Ar5u870_1835.fw | 0
Ar5u870_1836.fw | 0
Ar5u870_183a.fw | 0
Ar5u870_1870.fw | 0
Ar5u870_1870_1.fw | 0
Ar5u870_md.c | 3061+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ausbcam.c | 3484+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ausbcam.h | 721+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
18 files changed, 7944 insertions(+), 0 deletions(-)

diff --git a/COPYING b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog @@ -0,0 +1,91 @@ +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. diff --git a/Kbuild b/Kbuild @@ -0,0 +1,2 @@ +r5u870-objs := r5u870_md.o usbcam.o +obj-m := r5u870.o diff --git a/Makefile b/Makefile @@ -0,0 +1,66 @@ +# changelog : +#------------ +# naresh - 29-Mar-2007 : cyan_00391 +# fixed the code which detects the existing kernel module +# +# Notes: +# ----------- +# This clever makefile was shamelessly copied from the ivtv project. +# +# By default, the build is done against the running kernel version. +# to build against a different kernel version, set KVER +# +# make KVER=2.6.11-alpha +# +# Alternatively, set KDIR +# +# make KDIR=/usr/src/linux + +V ?= 0 +MDIR := extra + +KVER ?= $(shell uname -r) +KDIR ?= /lib/modules/$(KVER)/build +FWDIR ?= /lib/firmware + +# Old module name to detect and complain about when installing +OLD_MODULE_NM = ry5u870.ko + +FWFILES = r5u870_1830.fw r5u870_1832.fw r5u870_1833.fw r5u870_1834.fw r5u870_1835.fw r5u870_1836.fw r5u870_1870_1.fw r5u870_1870.fw r5u870_1810.fw r5u870_183a.fw + +ifneq ($(KERNELRELEASE),) +include $(src)/Kbuild +else + +all:: + $(MAKE) -C $(KDIR) M=$(CURDIR) V=$(V) modules + +install:: all + $(MAKE) INSTALL_MOD_PATH=$(DESTDIR) INSTALL_MOD_DIR=$(MDIR) \ + -C $(KDIR) M=$(CURDIR) modules_install + +clean:: + $(MAKE) -C $(KDIR) M=$(CURDIR) clean + rm -f Module.symvers + +endif + +install:: + install -m 0644 -o root -g root $(FWFILES) $(FWDIR) + /sbin/depmod -a + @if find /lib/modules -name $(OLD_MODULE_NM) | grep $(OLD_MODULE_NM) >/dev/null; then \ + echo; \ + echo "*** !!! WARNING !!! ***"; \ + echo "A prior version of this driver was detected, installed with a different file"; \ + echo "name. It is possible that a boot-time device detection component will choose"; \ + echo "to load the older version of this driver instead of the newly installed"; \ + echo "version."; \ + echo; \ + echo "Please consider deleting the following files:"; \ + echo; \ + find /lib/modules -name $(OLD_MODULE_NM); \ + echo; \ + echo "*** !!! WARNING !!! ***"; \ + echo; \ + printf "\\a"; sleep 1; printf "\\a"; sleep 1; printf "\\a"; \ + fi diff --git a/README b/README @@ -0,0 +1,179 @@ +Ricoh R5U870 Linux Driver +Version 0.10.1, 2008/1/2 + +Requirements +============ + +To build/install this driver, you must have a set of configuration and +interface headers, or the complete build directory, for your running kernel, +or the target kernel for which the driver is to be built. This should +include most files in the include/linux directory, and specifically +include/linux/autoconf.h and include/linux/version.h. + +The required interface headers are usually located at or symlinked from: +/lib/modules/<version>/build + +Your kernel must be 2.6.17 or newer. + + +Supported Hardware +================== + +This driver supports the following OEM webcams: + +05ca:1810 HP Pavilion Webcam - UVC +05ca:1830 Sony Visual Communication Camera VGP-VCC2 (for VAIO SZ) +05ca:1832 Sony Visual Communication Camera VGP-VCC3 (for VAIO UX) +05ca:1833 Sony Visual Communication Camera VGP-VCC2 (for VAIO AR1) +05ca:1834 Sony Visual Communication Camera VGP-VCC2 (for VAIO AR2) +05ca:1835 Sony Visual Communication Camera VGP-VCC5 (for VAIO SZ) +05ca:1836 Sony Visual Communication Camera VGP-VCC4 (for VAIO FE) +05ca:183a Sony Visual Communication Camera VGP-VCC7 (for VAIO SZ) +05ca:1870 HP Pavilion Webcam / HP Webcam 1000 + + +Installation Process +==================== + +To attempt to build against the running kernel: + +make + +To build against a specific kernel: + +make KDIR=/path/to/kernel + +To install the modules to the appropriate location: + +make install + +-or- + +make install KDIR=/path/to/kernel + +Installed modules will be automatically probed for supported devices by the +udev coldplug component at boot, and the driver should be automatically +loaded on subsequent reboots. + +NOTE: Previous releases of this driver have produced modules named +ry5u870.ko. With the current release, the module name was changed to +r5u870.ko. Ensure that any old versions are deleted after installing a +new version. + + +Loading the Driver +================== + +If you installed the driver, you can just run: + +modprobe r5u870 + +If you wish to load the driver without installing it, you must load the +prerequisite modules: + +modprobe videodev +modprobe video-buf +modprobe v4l1-compat +modprobe v4l2-common +modprobe compat_ioctl32 (on 64-bit platforms) + +You must also copy the microcode files (r5u870_*.fw) to /lib/firmware. + +Then you may load the module manually: + +insmod r5u870.ko + + +Driver Options +============== + +Below is a list of module parameters that may be used with the r5u870 module: + +dv1000 -- HP Webcam handling mode + HP has done it again and used ID 05ca:1870 for two distinct pieces + of hardware: the HP Webcam 1000, found in HP Pavilion dv1000 series + machines, and the HP Pavilion Webcam, found in other HP Pavilion + machines that don't have Microdia cameras, and don't have the + 05ca:1810 Ricoh UVC camera -- which is not actually any different + from the 05ca:1870 HP Pavilion webcam. These two devices have + different image sensors and require different microcode. + 0: Assume HP Pavilion Webcam + 1: Assume HP Webcam 1000 + 2: Check DMI product name field (DEFAULT) + +video_nr -- list of favored minor numbers + A list of video capture minor numbers (/dev/videoX) to try to + associate devices with, in order, before resorting to the first + available minor number. + +debug -- bit field integer + Set bits described in usbcam.h (USBCAM_DBG_XXX) to enable trace + messages. + +fixed_fbsize -- integer + Sets the size in bytes of fixed-length frame buffers. The default + value is 1MB. This is a compatibilty feature for buggy programs + that use the V4L1 VIDIOCGMBUF call to allocate a frame buffer + based on the current capture size, choose a larger capture size, + and then attempt to capture frames. + + +Changes from original version +============================= + + * 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. + +Bugs +==== + +Send bug reports to samr7@cs.washington.edu, and CC them to +hixon.alexander@mediati.org. + +Kopete 0.12 and earlier can be used with this driver, but colors may appear +distorted. This is a problem with Kopete's YUV decoder, and should be fixed +in a future version. Kopete may also set the gamma picture control to 0 +for no apparent reason, causing the image to appear too dark to Kopete and +to other applications that subsequently open the camera. + + +Copyright and License +===================== + +Copyright (C) 2007 Sam Revitch <samr7@cs.washington.edu> +Some bits copyright (c) 2008 Alexander Hixon <hixon.alexander@mediati.org> + +This driver is licensed to you under the terms of the GNU GPL v2. See +the included file 'COPYING'. + +The Makefile and Kbuild components are derived from the ivtv project. + +The files: + r5u870_1810.fw + r5u870_1830.fw + r5u870_1832.fw + r5u870_1833.fw + r5u870_1834.fw + r5u870_1835.fw + r5u870_1836.fw + r5u870_1870.fw + r5u870_1870_1.fw + +were derived from usbsnoop/sniffusb tracing of various Windows drivers, +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. + +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. diff --git a/r5u870_1810.fw b/r5u870_1810.fw Binary files differ. diff --git a/r5u870_1830.fw b/r5u870_1830.fw Binary files differ. diff --git a/r5u870_1832.fw b/r5u870_1832.fw Binary files differ. diff --git a/r5u870_1833.fw b/r5u870_1833.fw Binary files differ. diff --git a/r5u870_1834.fw b/r5u870_1834.fw Binary files differ. diff --git a/r5u870_1835.fw b/r5u870_1835.fw Binary files differ. diff --git a/r5u870_1836.fw b/r5u870_1836.fw Binary files differ. diff --git a/r5u870_183a.fw b/r5u870_183a.fw Binary files differ. diff --git a/r5u870_1870.fw b/r5u870_1870.fw Binary files differ. diff --git a/r5u870_1870_1.fw b/r5u870_1870_1.fw Binary files differ. diff --git a/r5u870_md.c b/r5u870_md.c @@ -0,0 +1,3061 @@ +/* + * 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> + * + * Cheers to Utz-Uwe Haus for getting the Sony VGP-VCC7 firmware extracted, + * and providing a patch against the r5u870 driver. + * + * 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.c b/usbcam.c @@ -0,0 +1,3484 @@ +/* + * USBCAM abstraction library for USB webcam drivers + * Version 0.1.1 + * + * 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); +} + + +/* + * 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); + void *vmalloc; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + vmalloc = framep->vbb.dma.vmalloc; +#else + struct videobuf_dmabuf *dma = videobuf_to_dma(&framep->vbb); + vmalloc = dma->vmalloc; +#endif + + usbcam_chklock(udp); + + if (!framep) + return -ENOENT; + + cf->uc_base = (u8 *) (framep->vmap_sof + ? framep->vmap_sof + : 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; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + dma = &framep->vbb.dma; +#else + dma = videobuf_to_dma(&framep->vbb); +#endif + + 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); + int res; + + struct videobuf_dmabuf *dma; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + dma = &framep->vbb.dma; +#else + dma = videobuf_to_dma(&framep->vbb); +#endif + + 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) + { + 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) { + 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 */ +#if 0 + 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; + } +#endif + + 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.h b/usbcam.h @@ -0,0 +1,721 @@ +/* + * 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 simplify 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__ + +#include <linux/usb.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/version.h> + +#include <linux/videodev2.h> +#include <media/v4l2-common.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) +#include <media/video-buf.h> +#else +#include <media/videobuf-dma-sg.h> +#endif + + +/* The actual per-minidriver structure is opaque */ +typedef struct usbcam_minidrv usbcam_minidrv_t; +struct usbcam_dev; + + +/* + * 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_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_ISOSTREAM, + + /* 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; +}; + + +/* + * The control structure is interpreted by the usbcam ioctl handler to + * answer QUERYCTRL/G_CTRL/S_CTRL/QUERYMENU requests. A minidriver may + * set the ud_ctrl_array field in usbcam_dev to point to an array of + * usbcam_ctrl structures, or larger structures that have a usbcam_ctrl + * as their 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 v4l2_queryctrl ctrl; + + const char **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_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_control *); +}; + +/* + * 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; + + /* + * ud_ctrl_array, ud_ctrl_array_elem_size, ud_ctrl_array_len: + * Supported control knobs with get/set callouts + * Protected by ud_ctrl_lock + * Modified only by minidriver, usually set by init callout + * Examined by default ioctl handlers for: + * QUERYCTRL, G_CTRL, S_CTRL, QUERYMENU + */ + const struct usbcam_ctrl *ud_ctrl_array; + int ud_ctrl_array_elem_size; + int ud_ctrl_array_len; + + /* + * 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_isostream_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 + * + */ +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_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); + + +/* + * 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. + */ +struct usbcam_curframe { + u8 *uc_base; + size_t uc_size; + enum v4l2_field uc_field; + struct timeval uc_timestamp; +}; + +extern int usbcam_curframe_get(struct usbcam_dev *udp, + struct usbcam_curframe *cf); +extern void usbcam_curframe_complete_detail(struct usbcam_dev *udp, + int is_error, + struct usbcam_curframe *cf); +#define usbcam_curframe_complete(UDP, ISERR) \ + usbcam_curframe_complete_detail(UDP, ISERR, NULL) + +extern void usbcam_curframe_abortall(struct usbcam_dev *udp); + +/* + * 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 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_workitem *wip, + usbcam_workfunc_t func); +extern int usbcam_work_queue(struct usbcam_dev *udp, + struct usbcam_workitem *wip); +extern int usbcam_work_cancel(struct usbcam_dev *udp, + struct usbcam_workitem *wip); + +/* + * 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); + + +/* + * Isochronous stream 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. + */ + +struct usbcam_isostream; + +struct usbcam_isostream_ops { + void (*packet_done)(struct usbcam_dev *, struct usbcam_isostream *, + void *pktdata, int pktlen, int pktstatus); + void (*submit_error)(struct usbcam_dev *, struct usbcam_isostream *, + int status); +}; + +struct usbcam_isostream { + struct usbcam_dev *iso_dev; + int iso_endpoint; + int iso_packet_len; + int iso_packet_count; + int iso_urb_interval; + struct usbcam_isostream_ops *iso_ops; + spinlock_t iso_lock; + int iso_streaming; + struct list_head iso_unused_list; + struct list_head iso_active_list; + struct list_head iso_complete_list; + int iso_resubmit_err; + struct usbcam_workitem iso_error_workitem; +}; + +extern 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); +extern void usbcam_isostream_cleanup(struct usbcam_isostream *); +extern int usbcam_isostream_start(struct usbcam_isostream *); +extern void usbcam_isostream_stop(struct usbcam_isostream *); + + +/* + * 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 +#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) */ + +#endif /* __KERNEL__ */ + +#endif /* __USBCAM_H__ */