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:
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__ */