commit 634557bf4dab7bf39eabc901184f3b8572bfb3d7
parent 81859663a98909f9217482f5205d2d7ab2bbfa90
Author: alex <alex@022568fa-442e-4ef8-a3e8-54dcafdb011a>
Date: Wed, 16 Jan 2008 10:28:31 +0000
* Reformat changelog style, and added missing entries.
* Merge Sam Revitch's patch to video4linux-list with the code in trunk.
* Bump to version 0.11.0.
* Small README changes.
* Rename r5u870_md.c to r5u870.c
git-svn-id: http://svn.mediati.org/svn/r5u870/trunk@33 022568fa-442e-4ef8-a3e8-54dcafdb011a
Diffstat:
M | ChangeLog | | | 207 | +++++++++++++++++++++++++++++++++++++++++++------------------------------------ |
M | Kbuild | | | 3 | +-- |
M | README | | | 33 | ++++++++++++++++++++++++--------- |
A | r5u870.c | | | 2890 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | r5u870_md.c | | | 3061 | ------------------------------------------------------------------------------- |
A | usbcam/Makefile | | | 6 | ++++++ |
A | usbcam/usbcam.c | | | 3477 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | usbcam/usbcam.h | | | 757 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | usbcam/usbcam_buf.c | | | 685 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | usbcam/usbcam_dev.c | | | 1109 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | usbcam/usbcam_fops.c | | | 852 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | usbcam/usbcam_priv.h | | | 202 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | usbcam/usbcam_skel.c | | | 119 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | usbcam/usbcam_util.c | | | 1008 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
14 files changed, 11243 insertions(+), 3166 deletions(-)
diff --git a/ChangeLog b/ChangeLog
@@ -1,94 +1,113 @@
-version 0.10.2, 2008/1/13
- Small minor bug fixes that were missed in the previous release.
-
-Version 0.10.1, 2008/1/2
- Add support for the 183a UVC camera.
-
- Complete V4L1 compatability; unbreaks functionatity with such
- applications like GStreamer (and those that depend on the framework,
- such as Cheese).
-
- Fix compiling against 2.6.24 kernels - changes to videobuf API.
-
-Version 0.10.0, 2007/4/7
- Add support for the 1810, 1835, and 1836 UVC cameras.
- Add support for the 1833 non-UVC camera.
-
- Add basic support for UVC commands and UVC personalities of
- various Ricoh webcams.
-
- Add DMI detection of HP Pavilion dv1000 systems. Most HP Pavilion
- machines with the 05ca:1870 device have a 1.3MP camera. The
- dv1000 machines with a device reporting that ID instead have a VGA
- camera that requires different microcode.
-
- Fix solid blue frames on initial capture with VAIO UX webcams.
-
- Use a cleaner and more concise representation for vendor control
- lists in r5u870_md.
-
- Add experimental auto-suspend support to usbcam and r5u870, but
- leave it disabled.
-
- Update the usbcam work queue implementation, add some APIs.
-
-
-Version 0.9.1, 2007/3/21
- Set all of the controls on device initialization, prior to starting
- the first capture session. This was causing the first capture
- session with certain devices to produce a solid blue image.
-
- Resolve build problems with vanilla 2.6.19 kernels. Thanks to
- Naresh Kumar for pointing this out.
-
- Use the driver_info field of the usb_device_id structure to find
- the model structure.
-
- Revise the usbcam device referencing model, along with some other
- minor usbcam cleanups.
-
-
-Version 0.9.0, 2007/3/6
- Change project name to r5u870, as the device called 'ry5u870' is
- something Ricoh sells that doesn't seem to be a direct basis for
- any of the supported OEM webcams.
-
- Change module name from ry5u870.ko to r5u870.ko, and changed the
- install path to "extra" rather than "kernel/drivers/media/video".
-
- Add microcode, device IDs, and control descriptor tables for
- HP Pavilion Webcam, Sony VGP-VCC3, and Sony VGP-VCC2 (VAIO AR).
-
- Introduce the webcam model structure, and hang lists of supported
- resolutions, pixel formats, and controls off of it.
-
- Use the request_firmware() interface to load microcode, rather
- than embedding the microcode in the driver binary.
-
- Clean up the debug, warning, and error printing macros.
-
- Overhaul the usbcam_curframe_* interfaces. Add a solid blue test
- pattern generator for when a frame is dropped by the minidriver.
-
- Add a workaround for a v4l1_compat bug preventing VIDIOCSPICT
- from working, present in kernels prior to 2.6.20.
-
- Add an application bug workaround for Ekiga/ptlib: zero the flags
- field of the argument to VIDIOC_QBUF.
-
- Add an application bug workaround for motion: allocate fixed sized
- buffers in VIDIOCGMBUF.
-
- Fixed issues with the compat_ioctl method for running 32-bit apps
- on 64-bit platforms.
-
- Apparently Linus prefers struct X to X_t. Mass-update to match this
- style for better hopes of getting it merged some day.
-
-
-Version 0.8.1, 2007/2/15
- Fix trivial build problem with kernel 2.6.18.
-
-
-Version 0.8.0, 2007/2/14
- Initial public release.
+2008-01-16 Alexander Hixon <hixon.alexander@mediati.org>
+ * ChangeLog: Reformat changelog style, and added missing entries.
+ * KBuild, usbcam/usbcam_priv.h, usbcam/usbcam.h, usbcam/usbcam_fops.c,
+ usbcam/usbcam_buf.c, usbcam/usbcam_util.c, usbcam/usbcam_del.c,
+ usbcam/usbcam_skel.c, usbcam/usbcam.c, usbcam/Makefile:
+
+ Seperate usbcam library out seperately. Part one
+ of two of the code merge from Sam Revitch's release of the r5u870
+ driver on video4linux-list@redhat.com.
+ * r5u870_md.c, r5u870.c, Kbuild: Merged code changes for API change
+ within usbcam. Among other things, remove inflamatory things about HP
+ engineers, remove some debugging cruft, implement a control message
+ helper function, and other misc fixes. Also fixes a bug against Sam's
+ original release where memory might not get freed correctly.
+ * README, r5u870.c: Bump version to 0.11.0.
+ * README: Modified 'Changes from original version' section name and
+ contents, and added some more acknowledgements.
+
+2008-01-16 Alexander Hixon <hixon.alexander@mediati.org>
+ * recode-fw.scm: Added this file to easily extract firmware from Windows
+ drivers when needed. Thanks to Utz-Uwe Haus for this.
+
+2008-01-13 Alexander Hixon <hixon.alexander@mediati.org>
+ * README, Changelog, r5u870_md.c, usbcam.c, usbcam.h: Bump to 0.10.2.
+ * usbcam.c: Uncomment V4L2 control query ioctl.
+
+2008-01-12 Alexander Hixon <hixon.alexander@mediati.org>
+ * usbcam.c: Fix dereferencing of NULL pointers by checking to see if they
+ have a value before trying to get a memory address.
+
+2008-01-11 Alexander Hixon <hixon.alexander@mediati.org>
+ * r5u870_md.c: Change NULL to { } to hush NULL without definition warning.
+ * usbcam.c: Check results against videobuf operations for return values
+ less than 0, rather than if it != 0.
+
+2008-01-08 Alexander Hixon <hixon.alexander@mediati.org>
+ * r5u870_md.c: Add NULL to end of definitions list to remove warnings on
+ compile.
+
+2008-01-02 Alexander Hixon <hixon.alexander@mediati.org>
+ * r5u870_md.c, r5u870_183a.fw, README: Add support for the 183a UVC camera.
+ * r5u870_md.c: Complete V4L1 compatability; unbreaks functionatity with
+ such applications like GStreamer (and those that depend on the framework,
+ such as Cheese).
+ * r5u870_md.c, usbcam.h, usbcam.c: Fix compiling against 2.6.24 kernels -
+ changes to videobuf API.
+ * README: Initial release against original driver - 0.10.1.
+
+2007-04-07 Sam Revitch <samr7@cs.washington.edu>
+ * /dev/null: Add support for the 1810, 1835, and 1836 UVC cameras.
+ * /dev/null: Add support for the 1833 non-UVC camera.
+ * /dev/null: Add basic support for UVC commands and UVC personalities of
+ various Ricoh webcams.
+ * /dev/null: Add DMI detection of HP Pavilion dv1000 systems. Most HP
+ Pavilion machines with the 05ca:1870 device have a 1.3MP camera. The
+ dv1000 machines with a device reporting that ID instead have a VGA
+ camera that requires different microcode.
+ * /dev/null: Fix solid blue frames on initial capture with VAIO UX webcams.
+ * /dev/null: Use a cleaner and more concise representation for vendor
+ control lists in r5u870_md.
+ * /dev/null: Add experimental auto-suspend support to usbcam and r5u870,
+ but leave it disabled.
+ * /dev/null: Update the usbcam work queue implementation, add some APIs.
+ * README, r5u870_md.c, usbcam.c, usbcam.h: 0.10.0 release.
+
+
+2007-03-21 Sam Revitch <samr7@cs.washington.edu>
+ * /dev/null: Set all of the controls on device initialization, prior to
+ starting the first capture session. This was causing the first capture
+ session with certain devices to produce a solid blue image.
+ * /dev/null: Resolve build problems with vanilla 2.6.19 kernels. Thanks
+ to Naresh Kumar for pointing this out.
+ * /dev/null: Use the driver_info field of the usb_device_id structure to
+ find the model structure.
+ * /dev/null: Revise the usbcam device referencing model, along with some
+ other minor usbcam cleanups.
+ * README, r5u870_md.c, usbcam.c, usbcam.h: 0.9.1 release.
+
+
+2007-03-06 Sam Revitch <samr7@cs.washington.edu>
+ * /dev/null: Change project name to r5u870, as the device called 'ry5u870'
+ is something Ricoh sells that doesn't seem to be a direct basis for
+ any of the supported OEM webcams.
+ * /dev/null: Change module name from ry5u870.ko to r5u870.ko, and changed
+ the install path to "extra" rather than "kernel/drivers/media/video".
+ * /dev/null: Add microcode, device IDs, and control descriptor tables for
+ HP Pavilion Webcam, Sony VGP-VCC3, and Sony VGP-VCC2 (VAIO AR).
+ * /dev/null: Introduce the webcam model structure, and hang lists of
+ supported resolutions, pixel formats, and controls off of it.
+ * /dev/null: Use the request_firmware() interface to load microcode, rather
+ than embedding the microcode in the driver binary.
+ * /dev/null: Clean up the debug, warning, and error printing macros.
+ * /dev/null: Overhaul the usbcam_curframe_* interfaces. Add a solid blue
+ test pattern generator for when a frame is dropped by the minidriver.
+ * /dev/null: Add a workaround for a v4l1_compat bug preventing VIDIOCSPICT
+ from working, present in kernels prior to 2.6.20.
+ * /dev/null: Add an application bug workaround for Ekiga/ptlib: zero the
+ flags field of the argument to VIDIOC_QBUF.
+ * /dev/null: Add an application bug workaround for motion: allocate fixed
+ sized buffers in VIDIOCGMBUF.
+ * /dev/null: Fixed issues with the compat_ioctl method for running 32-bit
+ apps on 64-bit platforms.
+ * /dev/null: Apparently Linus prefers struct X to X_t. Mass-update to
+ match this style for better hopes of getting it merged some day.
+ * README, r5u870_md.c, usbcam.c, usbcam.h: 0.9.0 release.
+
+2007-02-15 Sam Revitch <samr7@cs.washington.edu>
+ * /dev/null: Fix trivial build problem with kernel 2.6.18.
+ * README, ry5u870_md.c, usbcam.c, usbcam.h: 0.8.1 release.
+
+2007-02-14 Sam Revitch <samr7@cs.washington.edu>
+ * /dev/null: Initial public release.
+ * README, ry5u870_md.c, usbcam.c, usbcam.h: 0.8.0 release.
diff --git a/Kbuild b/Kbuild
@@ -1,2 +1 @@
-r5u870-objs := r5u870_md.o usbcam.o
-obj-m := r5u870.o
+obj-m := usbcam/ r5u870.o
diff --git a/README b/README
@@ -1,5 +1,5 @@
Ricoh R5U870 Linux Driver
-Version 0.10.2, 2008/1/13
+Version 0.11.0, 2008/1/16
Requirements
============
@@ -118,14 +118,17 @@ fixed_fbsize -- integer
and then attempt to capture frames.
-Changes from original version
-=============================
+Changes from original version (0.10.0)
+======================================
* Properly implements V4L1 query ioctl functions. While version 1 has been
obsoleted, it is still necessary to support it as a number of applications
still use it, including GStreamer's v4lsrc element.
* Support for VGP-VCC7, including supplied microcode.
* Can compile against Linux kernels 2.6.24 or later.
+ * Uses a module approach to usbcam, and only require minidrivers to link
+ against a header file. This code was posted in an email to LKML by Chris
+ on May 1, 2007.
Bugs
====
@@ -169,11 +172,23 @@ including some named Mvc25u870.sys, 5U870CAP.sys, and R5U870FLx86.sys.
Acknowledgements
================
-Thanks to Geert Willems for extensive testing and bug reporting, for general
-driver issues and issues specific to the HP Webcam.
+Hude kudos to Sam Revitch for writing the driver. Cheers, mate. I owe you a
+beer (or two). :)
-Thanks to Benoît Canet for updating this driver to work with the Sony VAIO
-AR webcam (05ca:1834).
+Thanks to Albert Vilella for establishing an interest group for this type of
+webcam, for early VAIO SZ testing, and generally collecting a good set of
+resources for Linux on VAIO SZ laptops.
-Thanks to Utz-Uwe Haus for getting the Sony VGP-VCC7 firmware extracted,
-and providing a patch against the original r5u870 driver.
+Thanks to Geert Willems for extensive testing of HP Webcam support, general
+driver compatibility testing, and putting up with endless crazy requests for
+more debug information. :-)
+
+Thanks to Mattia Dongili for taking usbsnoop traces of the VAIO UX50 webcam
+driver on Windows, and testing initial support in the Linux driver.
+
+Thanks to Benoît Canet for updating this driver to work with the Sony VAIO AR
+webcam (05ca:1834).
+
+Thanks to Utz-Uwe Haus for getting the Sony VGP-VCC7 firmware extracted, and
+providing a patch against the original r5u870 driver, and providing the
+recode-fw.scm script.
diff --git a/r5u870.c b/r5u870.c
@@ -0,0 +1,2890 @@
+/*
+ * Driver for Ricoh R5U870-based Custom OEM Webcams
+ * Copyright (c) 2007 Sam Revitch <samr7@cs.washington.edu>
+ * Copyright (c) 2008 Alexander Hixon <hixon.alexander@mediati.org>
+ *
+ * Check out README for additional credits.
+ * Version 0.11.0
+ *
+ * This driver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This driver supports certain custom OEM webcams based on the Ricoh
+ * R5U870 / Micro Vision M25U870 controller chip. These tend to be
+ * built in to certain models of laptop computers and claim USB vendor
+ * ID 0x05CA (Ricoh).
+ *
+ * All Ricoh webcams support common microcode upload, reset, and query
+ * version commands.
+ *
+ * Each distinct piece of hardware requires a different microcode blob,
+ * which is uploaded to the device the first time the device is probed
+ * after power-on. The microcode blob is tailored to the attached
+ * image sensor and the desired type of USB interface. Some microcode
+ * blobs are created to work with the Ricoh proprietary USB interface.
+ * Others behave as UVC devices, but use the common Ricoh vendor
+ * commands to set certain controls. Some devices, in particular the
+ * 1810 and 1870 HP Pavilion Webcam devices, appear to be identical in
+ * hardware, and differ only by USB descriptor tables, and the
+ * microcode blobs implement the different USB personalities.
+ *
+ * This driver supports basic UVC descriptor table parsing to determine
+ * available controls and resolutions, and enough UVC commands to
+ * support the UVC interface style of the Ricoh webcams.
+ */
+
+#define USBCAM_DEBUG_DEFAULT 0
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <linux/dmi.h>
+#include <linux/ctype.h>
+
+#include "usbcam/usbcam.h"
+
+
+#define USBCAM_DBG_R5U_INIT (USBCAM_DBGBIT_MD_START + 0)
+#define USBCAM_DBG_R5U_FRAME (USBCAM_DBGBIT_MD_START + 1)
+#define USBCAM_DBG_R5U_MDINTF (USBCAM_DBGBIT_MD_START + 2)
+#define USBCAM_DBG_R5U_CTRL (USBCAM_DBGBIT_MD_START + 3)
+
+
+#define r5u_info(RV, FMT...) usbcam_info((RV)->vh_parent, FMT)
+#define r5u_err(RV, FMT...) usbcam_err((RV)->vh_parent, FMT)
+#define r5u_dbg(RV, SUBSYS, FMT...) usbcam_dbg((RV)->vh_parent, SUBSYS, FMT)
+
+#define R5U870_VERSION KERNEL_VERSION(0,11,0)
+#define R5U870_VERSION_EXTRA "halex"
+
+
+struct r5u870_resolution {
+ int rw_width;
+ int rw_height;
+ int rw_reqbw;
+ int rw_interval;
+ int rw_frameidx;
+};
+
+struct r5u870_pix_fmt {
+ struct usbcam_pix_fmt base;
+ int rp_formatidx;
+ const struct r5u870_resolution *rp_restbl;
+ int rp_restbl_alloc;
+};
+
+struct r5u870_ctrl {
+ struct usbcam_ctrl base;
+ int reg;
+ u8 unit;
+ u8 info;
+ u8 size;
+ u8 is_auto;
+ int auto_offset;
+ int val_offset;
+ int value;
+};
+
+struct r5u870_model {
+ char *rm_name;
+ const char *rm_ucode_file;
+ const struct r5u870_resolution *rm_res;
+ const int *rm_wdm_ctrlids;
+ const struct r5u870_pix_fmt *rm_pixfmts;
+ int rm_npixfmts;
+ u16 rm_ucode_version;
+ unsigned int rm_uvc : 1,
+ rm_no_ctrl_reload : 1,
+ rm_no_first_auto_suppress : 1;
+};
+
+struct r5u870_ctx {
+ struct usbcam_dev *vh_parent;
+ struct usbcam_urbstream vh_iso;
+
+ const struct r5u870_model *vh_model;
+ const struct r5u870_pix_fmt *vh_pixfmts;
+ int vh_npixfmts;
+
+ int vh_dyn_pixfmts : 1,
+ vh_configured : 1,
+ vh_ctrl_reg_enable : 1,
+ vh_ctrl_sync : 1,
+ vh_ctrl_auto_suppress : 1;
+
+ const struct r5u870_pix_fmt *vh_fmt;
+ const struct r5u870_resolution *vh_res;
+
+ int vh_timeout;
+ int vh_firstframe;
+ int vh_emptypkts;
+ int vh_frame_accum;
+
+ int vh_framebuf_offset;
+
+ int vh_ctrl_ifnum;
+ int vh_iso_ep;
+ int vh_iso_ifnum;
+ int vh_iso_minpacket;
+ int vh_act_altsetting;
+
+ int (*vh_set_fmt)(struct r5u870_ctx *,
+ const struct r5u870_pix_fmt *fmtp,
+ const struct r5u870_resolution *resp);
+ int (*vh_cap_stop)(struct r5u870_ctx *);
+ int (*vh_decide_pkt)(struct r5u870_ctx *, int st, int len,
+ const u8 *pktdata, int *start);
+
+ /* Auto settings */
+ int vh_auto_wb;
+ int vh_aec;
+ int vh_agc;
+};
+
+#define udp_r5u870(UDP) ((struct r5u870_ctx *)((UDP)->ud_minidrv_data))
+#define r5u870_dev(RV) ((RV)->vh_parent->ud_dev)
+
+/*
+ * USB control message with kmalloc'd buffer helper
+ */
+
+static int r5u870_control_msg(struct r5u870_ctx *vhp, int write, int class,
+ u8 request, u16 value, u16 index, void *data,
+ u16 size)
+{
+ char *dbuf = NULL;
+ int res;
+
+ if (size) {
+ dbuf = kmalloc(size, GFP_KERNEL);
+ if (!dbuf)
+ return -ENOMEM;
+ if (write)
+ memcpy(dbuf, data, size);
+ }
+
+ res = usb_control_msg(r5u870_dev(vhp),
+ write
+ ? usb_sndctrlpipe(r5u870_dev(vhp), 0)
+ : usb_rcvctrlpipe(r5u870_dev(vhp), 0),
+ request,
+ (write ? USB_DIR_OUT : USB_DIR_IN) |
+ (class
+ ? (USB_TYPE_CLASS | USB_RECIP_INTERFACE)
+ : (USB_TYPE_VENDOR | USB_RECIP_DEVICE)),
+ value, index, dbuf, size,
+ vhp->vh_timeout);
+
+ if (dbuf) {
+ if (!write)
+ memcpy(data, dbuf, size);
+ kfree(dbuf);
+ }
+ return res;
+}
+
+
+/*
+ * Controls
+ *
+ * For this driver, there is only one get function, which retrieves the
+ * value for the control stored in the r5u870_ctx structure. Each style
+ * of control -- WDM/UVC -- have separate set functions.
+ *
+ * There are query functions for setting the disabled flag on manual
+ * white balance controls when auto white balance is enabled.
+ */
+
+enum {
+ V4L2_CID_R5U870_SHARPNESS = (V4L2_CID_PRIVATE_BASE + 0),
+ V4L2_CID_R5U870_GREEN_BALANCE,
+ V4L2_CID_R5U870_AUTOEXPOSURE,
+ V4L2_CID_R5U870_POWERLINE,
+ V4L2_CID_R5U870_BACKLIGHT,
+ V4L2_CID_R5U870_PRIVACY,
+ V4L2_CID_R5U870_NIGHT_MODE,
+};
+
+static int r5u870_get_ctrl(struct usbcam_dev *udp,
+ const struct usbcam_ctrl *basep,
+ struct v4l2_ext_control *c)
+{
+ struct r5u870_ctrl *ctrlp = container_of(basep, struct r5u870_ctrl,
+ base);
+ c->value = ctrlp->value;
+ return 0;
+}
+
+static int r5u870_query_ctrl(struct usbcam_dev *udp,
+ const struct usbcam_ctrl *basep,
+ struct v4l2_queryctrl *c)
+{
+ struct r5u870_ctx *vhp = udp_r5u870(udp);
+ struct r5u870_ctrl *ctrlp = container_of(basep, struct r5u870_ctrl,
+ base);
+ int auto_ctrl = 0;
+
+ if (ctrlp->auto_offset) {
+ auto_ctrl = *(int *) (((char *) vhp) + ctrlp->auto_offset);
+ if (auto_ctrl)
+ c->flags |= V4L2_CTRL_FLAG_INACTIVE;
+ }
+ return 0;
+}
+
+static int r5u870_set_controls(struct r5u870_ctx *vhp, int dflt)
+{
+ const struct r5u870_ctrl *ctrlp;
+ struct v4l2_ext_control cv;
+ int res;
+
+ res = 0;
+ list_for_each_entry(ctrlp, &vhp->vh_parent->ud_ctrl_list,
+ base.uc_links) {
+ cv.id = ctrlp->base.uc_v4l.id;
+
+ if (dflt)
+ cv.value = ctrlp->base.uc_v4l.default_value;
+ else
+ cv.value = ctrlp->value;
+
+ res = ctrlp->base.set_fn(vhp->vh_parent,
+ &ctrlp->base,
+ &cv);
+ if (res)
+ break;
+ }
+
+ if (vhp->vh_ctrl_reg_enable &&
+ !vhp->vh_ctrl_auto_suppress &&
+ vhp->vh_model->rm_no_first_auto_suppress)
+ vhp->vh_ctrl_auto_suppress = 1;
+
+ return res;
+}
+
+
+/*
+ * Microcode management and device initialization functions follow
+ */
+
+static int r5u870_set_gen_reg(struct r5u870_ctx *vhp,
+ int cmd, int reg, int val)
+{
+ int res;
+ res = r5u870_control_msg(vhp, 1, 0, cmd, val, reg, NULL, 0);
+ if (res < 0) {
+ r5u_err(vhp, "set_gen_reg %04x/%04x/%04x failed: %d",
+ cmd, reg, val, res);
+ return res;
+ }
+ return 0;
+}
+
+/*
+ * Send the power-up init sequence, in case it is needed.
+ */
+static int r5u870_microcode_upload(struct r5u870_ctx *vhp)
+{
+ const struct firmware *fws;
+ char *pgbuf;
+ const u8 *dptr;
+ int tolerance = 3;
+ int i, rem, addr, len, res = 0;
+
+ pgbuf = (char *) kmalloc(64, GFP_KERNEL);
+ if (!pgbuf)
+ return -ENOMEM;
+
+ r5u_dbg(vhp, R5U_INIT, "loading microcode file \"%s\"",
+ vhp->vh_model->rm_ucode_file);
+
+ res = request_firmware(&fws,
+ vhp->vh_model->rm_ucode_file,
+ &vhp->vh_parent->ud_dev->dev);
+
+ if (res) {
+ r5u_err(vhp, "Microcode file \"%s\" is missing",
+ vhp->vh_model->rm_ucode_file);
+ r5u_err(vhp, "Please see http://wiki.mediati.org/r5u870/Microcode");
+ kfree(pgbuf);
+ return res;
+ }
+
+ i = 0;
+ dptr = fws->data;
+ rem = fws->size;
+ while (rem) {
+ if (rem < 3) {
+ r5u_err(vhp, "Microcode file msg %d is incomplete", i);
+ res = -EINVAL;
+ break;
+ }
+
+ len = dptr[0];
+ addr = dptr[1] | (dptr[2] << 8);
+ dptr += 3;
+ rem -= 3;
+
+ if ((rem < len) || (len > 64)) {
+ r5u_err(vhp, "Microcode file msg %d has bad length %d",
+ i, len);
+ res = -EINVAL;
+ break;
+ }
+
+ /*
+ * The USB stack has issues with the initseq data if
+ * initseq points into the vmalloc arena. This is
+ * the case for microcode embedded in a module, or
+ * data loaded by request_firmware().
+ *
+ * As a workaround, we memcpy() into a kmalloc page.
+ */
+ memcpy(pgbuf, dptr, len);
+ dptr += len;
+ rem -= len;
+
+ retry:
+ /* TODO: Maybe make this use r5u870_control_msg or similar? */
+ res = usb_control_msg(r5u870_dev(vhp),
+ usb_sndctrlpipe(r5u870_dev(vhp), 0),
+ 0xa0,
+ USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE,
+ addr, 0, pgbuf, len, vhp->vh_timeout);
+
+ if (res < 0) {
+ if (tolerance--)
+ goto retry;
+ r5u_err(vhp, "command a0[%d] failed: %d",
+ i, res);
+ break;
+ }
+ if (res != len) {
+ r5u_err(vhp, "command a0[%d] failed: %d (exp %d)",
+ i, res, len);
+ res = -EIO;
+ break;
+ }
+
+ i++;
+ }
+
+ release_firmware(fws);
+ kfree(pgbuf);
+ return res;
+}
+
+static int r5u870_microcode_get_state(struct r5u870_ctx *vhp)
+{
+ char buf[1];
+ int res;
+ r5u_dbg(vhp, R5U_INIT, "requesting microcode state");
+ res = r5u870_control_msg(vhp, 0, 0, 0xa4, 0, 0, buf, 1);
+ if ((res != 1) || ((buf[0] != 0) && (buf[0] != 1))) {
+ r5u_err(vhp, "command 0xa4 failed: %d", res);
+ return res < 0 ? res : -EIO;
+ }
+
+ r5u_dbg(vhp, R5U_INIT, "camera reports %s microcode state",
+ buf[0] ? "positive" : "negative");
+
+ return (buf[0] == 0) ? -ENOENT : 0;
+}
+
+static int r5u870_microcode_get_ver(struct r5u870_ctx *vhp, int *verp)
+{
+ char buf[2];
+ int res;
+
+ r5u_dbg(vhp, R5U_INIT, "requesting microcode version");
+ res = r5u870_control_msg(vhp, 0, 0, 0xc3, 0, 0x0e, buf, 2);
+ if (res != 2) {
+ r5u_err(vhp, "command 0xa3 failed: %d", res);
+ return res < 0 ? res : -EIO;
+ }
+
+ res = le16_to_cpup((__le16 *) buf);
+ r5u_dbg(vhp, R5U_INIT, "camera reports version %04x", res);
+ *verp = res;
+ return 0;
+}
+
+static int r5u870_microcode_enable(struct r5u870_ctx *vhp)
+{
+ char buf[1];
+ int res;
+
+ r5u_dbg(vhp, R5U_INIT, "enabling microcode");
+ buf[0] = 1;
+ res = r5u870_control_msg(vhp, 1, 0, 0xa1, 0, 0, buf, 1);
+ if (res != 1) {
+ r5u_err(vhp, "command 0xa1 failed: %d", res);
+ return res < 0 ? res : -EIO;
+ }
+
+ return 0;
+}
+
+static int r5u870_microcode_reset(struct r5u870_ctx *vhp)
+{
+ int res;
+ r5u_dbg(vhp, R5U_INIT, "sending microcode reset command");
+ msleep(100); /* The Windows driver waits 1sec */
+ res = r5u870_set_gen_reg(vhp, 0xa6, 0, 0);
+ if (!res)
+ msleep(200);
+ return res;
+}
+
+/*
+ * Initialize the camera after it is detected.
+ */
+static int r5u870_dev_init(struct r5u870_ctx *vhp)
+{
+ int mcver;
+ int res;
+
+ if (!vhp->vh_model->rm_ucode_file)
+ return 0;
+
+ res = r5u870_microcode_get_state(vhp);
+ if (res && (res != -ENOENT))
+ return res;
+
+ if (!res) {
+ res = r5u870_microcode_get_ver(vhp, &mcver);
+ if (res)
+ return res;
+
+ if (mcver != vhp->vh_model->rm_ucode_version) {
+ res = r5u870_microcode_reset(vhp);
+ if (res)
+ return res;
+ res = -ENOENT;
+ }
+ }
+
+
+ if (res == -ENOENT) {
+ res = r5u870_microcode_upload(vhp);
+ if (res < 0)
+ return res;
+
+ res = r5u870_microcode_enable(vhp);
+ if (res)
+ return res;
+
+ res = r5u870_microcode_get_ver(vhp, &mcver);
+ if (res)
+ return res;
+ }
+
+ if (mcver != vhp->vh_model->rm_ucode_version)
+ r5u_err(vhp, "Unexpected microcode version "
+ "(exp:%04x got:%04x)",
+ vhp->vh_model->rm_ucode_version, mcver);
+
+ /* Halt capture in case it's running (broken driver?) */
+ res = vhp->vh_cap_stop(vhp);
+ if (res < 0)
+ return res;
+
+ return 0;
+}
+
+/*
+ * WDM Device Registers are listed below.
+ *
+ * To set: use r5u870_set_reg_wdm().
+ *
+ * No information is given about how to retrieve values from these
+ * registers.
+ *
+ * Note that some register IDs are overloaded. The Sony cam drivers
+ * seem to use 0x36 for backlight compensation and 0x37 for gain,
+ * whereas the HP drivers use 0x36 for color enable and 0x37 for frame
+ * rate.
+ */
+enum {
+ /* Brightness: [0,127] D:63 */
+ R5U870_REG_BRIGHTNESS = 0x02,
+
+ /* Contrast: [0,127] D:63 */
+ R5U870_REG_CONTRAST = 0x04,
+
+ /* Hue: [-180,180] D:0 (16-bit 2's complement) */
+ R5U870_REG_HUE = 0x34,
+
+ /* Saturation: [0,127] D:63 */
+ R5U870_REG_SATURATION = 0x05,
+
+ /* Sharpness: [0,127] D:63 */
+ R5U870_REG_SHARPNESS = 0x03,
+
+ /* Gamma correction: [1,500] D:100 */
+ R5U870_REG_GAMMA = 0x35,
+
+ /* Registers with unknown usage */
+ R5U870_REG_COLOR_ENABLE = 0x36,
+
+ /* White balance: [0,127] D:63 */
+ R5U870_REG_WHITE_BALANCE = 0x1,
+
+ /* Frame Rate: D:30 */
+ R5U870_REG_FRAME_RATE = 0x37,
+
+ /* Registers with unknown usage */
+ R5U870_REG_BRIGHTNESS_EX = 0x20,
+ R5U870_REG_CONTRAST_EX = 0x21,
+ R5U870_REG_HUE_EX = 0x22,
+ R5U870_REG_SATURATION_EX = 0x23,
+ R5U870_REG_SHARPNESS_EX = 0x24,
+ R5U870_REG_GAMMA_EX = 0x25,
+
+ /* White balance red value: [0,255] D:127 */
+ R5U870_REG_WB_RED_EX = 0x26,
+
+ /* White balance green value: [0,255] D:127 */
+ R5U870_REG_WB_GREEN_EX = 0x27,
+
+ /* White balance blue value: [0,255] D:127 */
+ R5U870_REG_WB_BLUE_EX = 0x28,
+
+ /* Auto white balance: [0,1] D:1 */
+ R5U870_REG_WB_AUTO_EX = 0x29,
+
+ /* Exposure Control: [0,255] D:255 */
+ R5U870_REG_EXPOSURE_EX = 0x2a,
+
+ /* Auto Exposure Control: [0,1] D:1 */
+ R5U870_REG_AEC_EX = 0x2b,
+
+ /* Gain: [0,127] D:63 */
+ R5U870_REG_GAIN_EX = 0x2c,
+
+ /* Auto Gain: [0,1] D:0 */
+ R5U870_REG_AGC_EX = 0x2d,
+
+ /* Light source flicker compensation: see R5U870_POWERLINE */
+ R5U870_REG_POWERLINE_EX = 0x2e,
+
+ /* Registers with unknown usage */
+ R5U870_REG_SCENE_EX = 0x2f,
+
+ /* Vertical flip: [0,1] D:0 */
+ R5U870_REG_VFLIP_EX = 0x30,
+
+ /* Horizontal flip: [0,1] D:0 */
+ R5U870_REG_HFLIP_EX = 0x31,
+
+ /* Blank image: [0,1] D:0 */
+ R5U870_REG_PRIVACY_EX = 0x32,
+
+ /* Night mode: [0,1] D:0 */
+ R5U870_REG_NIGHT_MODE_EX = 0x33,
+
+ /* Backlight compensation: [0,500] D:1 */
+ R5U870_REG_BACKLIGHT_COMP = 0x36,
+
+ /* Registers with unknown usage */
+ R5U870_REG_GAIN = 0x37,
+
+ /* Backlight compensation for 1834 device: [0,2] D:1 */
+ R5U870_REG_BACKLIGHT_COMP_2 = 0x39,
+
+
+ /* Values for R5U870_REG_POWERLINE flicker compensation */
+ R5U870_POWERLINE_OFF = 0,
+ R5U870_POWERLINE_50HZ = 1,
+ R5U870_POWERLINE_60HZ = 2,
+
+ /* Number of empty packets between frames */
+ R5U870_EMPTYPKT_FRAME_DELIM = 10,
+
+ /* Number of empty packets before declaring the device dead (.5sec) */
+ R5U870_EMPTYPKT_GIVE_UP = 4000,
+};
+
+static int r5u870_set_reg_wdm(struct r5u870_ctx *vhp, int reg, int val)
+{
+ return r5u870_set_gen_reg(vhp, 0xc2, reg, val);
+}
+
+/*
+ * Set the frame size and data format.
+ * Do not call this function with the isochronous stream active.
+ */
+static int r5u870_set_fmt_wdm(struct r5u870_ctx *vhp,
+ const struct r5u870_pix_fmt *fmtp,
+ const struct r5u870_resolution *resp)
+{
+ int res;
+
+ msleep(1);
+ res = r5u870_set_gen_reg(vhp, 0xc5, 2, fmtp->rp_formatidx);
+ if (res)
+ return res;
+ msleep(1);
+ res = r5u870_set_gen_reg(vhp, 0xc5, 0, resp->rw_width);
+ if (res)
+ return res;
+ msleep(1);
+ res = r5u870_set_gen_reg(vhp, 0xc5, 1, resp->rw_height);
+ if (res)
+ return res;
+ msleep(5);
+ return 0;
+}
+
+
+/*
+ * Turn frame grabbing on or off (WDM).
+ * This will also turn on or off the LED.
+ */
+static int r5u870_set_cap_state_wdm(struct r5u870_ctx *vhp, int val)
+{
+ return r5u870_set_gen_reg(vhp, 0xc4, val, 0);
+}
+
+static int r5u870_cap_stop_wdm(struct r5u870_ctx *vhp)
+{
+ return r5u870_set_cap_state_wdm(vhp, 0);
+}
+
+/*
+ * r5u870_decide_pkt_wdm
+ *
+ * Based on the size of an isochronous data packet, this function
+ * decides whether to copy the packet into the frame buffer and possibly
+ * complete the frame, or to discard both the packet and the frame.
+ *
+ * Returns:
+ * 0 Frame is done
+ * -EAGAIN Append packet to frame, frame is not done
+ * -EPIPE Discard frame and packet
+ * -EIO The device is nonresponsive, abort
+ */
+static int r5u870_decide_pkt_wdm(struct r5u870_ctx *vhp, int pktstatus,
+ int pktlen, const u8 *pktdata, int *start)
+{
+ int ret = -EAGAIN;
+
+ *start = 0;
+
+ if (pktstatus) {
+ /* Abort current frame */
+ r5u_dbg(vhp, R5U_FRAME, "frame abort: packet status %d",
+ pktstatus);
+ vhp->vh_frame_accum = -1;
+ vhp->vh_emptypkts = 0;
+ ret = -EPIPE;
+
+ } else if (!pktlen) {
+ if (++vhp->vh_emptypkts == R5U870_EMPTYPKT_FRAME_DELIM) {
+ if (vhp->vh_frame_accum == -1) {
+ /* Frame was previously aborted */
+ ret = -EPIPE;
+ } else if (vhp->vh_frame_accum ==
+ vhp->vh_parent->ud_format.sizeimage) {
+ /* Complete frame */
+ ret = 0;
+ } else {
+ /* Not enough data in frame sequence */
+ r5u_dbg(vhp, R5U_FRAME, "frame abort: "
+ "Frame seq too short (exp:%d got:%d)",
+ vhp->vh_parent->ud_format.sizeimage,
+ vhp->vh_frame_accum);
+ ret = -EPIPE;
+ }
+
+ if (!vhp->vh_firstframe) {
+ /* Always reject the first frame */
+ vhp->vh_firstframe = 1;
+ vhp->vh_frame_accum = -1;
+ ret = -EPIPE;
+ } else {
+ vhp->vh_frame_accum = 0;
+ }
+ }
+
+ else if (vhp->vh_emptypkts >= R5U870_EMPTYPKT_GIVE_UP) {
+ r5u_dbg(vhp, R5U_FRAME, "%d empty packets, giving up",
+ vhp->vh_emptypkts);
+ ret = -EIO;
+ }
+
+ } else {
+ vhp->vh_emptypkts = 0;
+ if (vhp->vh_frame_accum == -1) {
+ /* Frame was previously aborted */
+ ret = -EPIPE;
+ } else if ((vhp->vh_frame_accum + pktlen) <=
+ vhp->vh_parent->ud_format.sizeimage) {
+ /* Append this data */
+ vhp->vh_frame_accum += pktlen;
+ } else {
+ /* Oversized frame, abort */
+ r5u_dbg(vhp, R5U_FRAME, "frame abort: "
+ "Frame seq too long");
+ vhp->vh_frame_accum = -1;
+ ret = -EPIPE;
+ }
+ }
+
+ return ret;
+}
+
+static int r5u870_set_manual_ctrls_wdm(struct r5u870_ctx *vhp, int auto_offset)
+{
+ const struct r5u870_ctrl *ctrlp;
+ int val, res;
+
+ res = 0;
+ list_for_each_entry(ctrlp, &vhp->vh_parent->ud_ctrl_list,
+ base.uc_links) {
+ if (ctrlp->auto_offset != auto_offset)
+ continue;
+ if (!vhp->vh_ctrl_reg_enable) {
+ vhp->vh_ctrl_sync = 0;
+ continue;
+ }
+
+ val = ctrlp->value;
+
+ r5u_dbg(vhp, R5U_CTRL, "control %s/wdm %02x <= %d [manual]",
+ ctrlp->base.uc_v4l.name, ctrlp->reg, val);
+ res = r5u870_set_reg_wdm(vhp, ctrlp->reg, val);
+ if (res)
+ break;
+ }
+ return res;
+}
+
+static int r5u870_set_ctrl_wdm(struct usbcam_dev *udp,
+ const struct usbcam_ctrl *basep,
+ const struct v4l2_ext_control *c)
+{
+ struct r5u870_ctx *vhp = udp_r5u870(udp);
+ struct r5u870_ctrl *ctrlp = container_of(basep, struct r5u870_ctrl,
+ base);
+ int res = 0;
+ int auto_ctrl = 0;
+
+ if (ctrlp->auto_offset)
+ auto_ctrl = *(int *) (((char *) vhp) + ctrlp->auto_offset);
+
+ if (auto_ctrl && vhp->vh_ctrl_auto_suppress) {
+ r5u_dbg(vhp, R5U_CTRL, "control %s <= %d [auto suppress]",
+ ctrlp->base.uc_v4l.name, c->value);
+
+ } else if (!vhp->vh_ctrl_reg_enable) {
+ r5u_dbg(vhp, R5U_CTRL, "control %s <= %d [capture off]",
+ ctrlp->base.uc_v4l.name, c->value);
+ vhp->vh_ctrl_sync = 0;
+
+ } else {
+ r5u_dbg(vhp, R5U_CTRL, "control %s/wdm %02x <= %d",
+ ctrlp->base.uc_v4l.name, ctrlp->reg, c->value);
+ res = r5u870_set_reg_wdm(vhp, ctrlp->reg, c->value);
+ if (res)
+ return res;
+ }
+
+ ctrlp->value = c->value;
+
+ if (ctrlp->val_offset)
+ *(int *) (((char *) vhp) + ctrlp->val_offset) = c->value;
+
+ if (ctrlp->is_auto && !c->value)
+ res = r5u870_set_manual_ctrls_wdm(vhp, ctrlp->val_offset);
+
+ return res;
+}
+
+/*
+ * WDM control templates follow
+ *
+ * Each device has an array of integer control IDs, which refer to an
+ * element in the control template array. When the device is detected,
+ * we build the control array out of the element list and the templates,
+ * by r5u870_wdm_add_ctrls().
+ */
+
+enum {
+ R5U870_WDM_CTRL_BRIGHTNESS,
+ R5U870_WDM_CTRL_CONTRAST,
+ R5U870_WDM_CTRL_SATURATION,
+ R5U870_WDM_CTRL_SHARPNESS,
+ R5U870_WDM_CTRL_HUE,
+ R5U870_WDM_CTRL_GAMMA,
+ R5U870_WDM_CTRL_BACKLIGHT_COMP_500,
+ R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1,
+ R5U870_WDM_CTRL_BACKLIGHT_COMP_X1834,
+ R5U870_WDM_CTRL_WB_RED,
+ R5U870_WDM_CTRL_WB_GREEN,
+ R5U870_WDM_CTRL_WB_BLUE,
+ R5U870_WDM_CTRL_WB_AUTO,
+ R5U870_WDM_CTRL_AUTO_EXPOSURE,
+ R5U870_WDM_CTRL_EXPOSURE,
+ R5U870_WDM_CTRL_AUTO_GAIN,
+ R5U870_WDM_CTRL_GAIN,
+ R5U870_WDM_CTRL_POWERLINE,
+ R5U870_WDM_CTRL_VFLIP,
+ R5U870_WDM_CTRL_HFLIP,
+ R5U870_WDM_CTRL_PRIVACY,
+ R5U870_WDM_CTRL_NIGHTMODE,
+
+ R5U870_WDM_CTRL_LAST = 0xffff,
+};
+
+/* TODO: Merge these into V4L API. */
+
+#define V4L2_CID_GREEN_BALANCE (V4L2_CID_BASE+24)
+#define V4L2_CID_AUTOEXPOSURE (V4L2_CID_BASE+25)
+#define V4L2_CID_POWER_LINE_FREQ (V4L2_CID_BASE+26)
+#define V4L2_CID_BACKLIGHT_COMP (V4L2_CID_BASE+27)
+#define V4L2_CID_PRIVACY (V4L2_CID_BASE+28)
+#define V4L2_CID_NIGHT_MODE (V4L2_CID_BASE+29)
+#define V4L2_CID_SHARPNESS (V4L2_CID_BASE+30)
+#define V4L2_CID_LASTP1 (V4L2_CID_BASE+31) /* last CID + 1 */
+
+static const char *r5u870_powerline_names[] = { "Off", "50Hz", "60Hz" };
+
+static struct r5u870_ctrl r5u870_wdm_ctrls[] = {
+
+ [R5U870_WDM_CTRL_BRIGHTNESS] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_BRIGHTNESS,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Brightness",
+ .minimum = 0,
+ .maximum = 127,
+ .step = 1,
+ .default_value = 63,
+ .flags = V4L2_CTRL_FLAG_SLIDER },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_BRIGHTNESS,
+ },
+ [R5U870_WDM_CTRL_CONTRAST] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_CONTRAST,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Contrast",
+ .minimum = 0,
+ .maximum = 127,
+ .step = 1,
+ .default_value = 63,
+ .flags = V4L2_CTRL_FLAG_SLIDER },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_CONTRAST,
+ },
+ [R5U870_WDM_CTRL_SATURATION] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_SATURATION,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Saturation",
+ .minimum = 0,
+ .maximum = 127,
+ .step = 1,
+ .default_value = 63,
+ .flags = V4L2_CTRL_FLAG_SLIDER },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_SATURATION,
+ },
+ [R5U870_WDM_CTRL_SHARPNESS] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_SHARPNESS,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Sharpness",
+ .minimum = 0,
+ .maximum = 127,
+ .step = 1,
+ .default_value = 63,
+ .flags = V4L2_CTRL_FLAG_SLIDER },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_SHARPNESS,
+ },
+ [R5U870_WDM_CTRL_HUE] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_HUE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Hue",
+ .minimum = -180,
+ .maximum = 180,
+ .step = 1,
+ .default_value = 0,
+ .flags = V4L2_CTRL_FLAG_SLIDER },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_HUE,
+ },
+ [R5U870_WDM_CTRL_GAMMA] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_GAMMA,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Gamma",
+ .minimum = 0,
+ .maximum = 500,
+ .step = 1,
+ .default_value = 100,
+ .flags = V4L2_CTRL_FLAG_SLIDER },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_GAMMA,
+ },
+ [R5U870_WDM_CTRL_BACKLIGHT_COMP_500] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_BACKLIGHT_COMP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Backlight Compensation",
+ .minimum = 0,
+ .maximum = 500,
+ .step = 1,
+ .default_value = 250,
+ .flags = V4L2_CTRL_FLAG_SLIDER },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_BACKLIGHT_COMP,
+ },
+ [R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_BACKLIGHT_COMP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Backlight Compensation",
+ .minimum = 0,
+ .maximum = 500,
+ .step = 1,
+ .default_value = 1,
+ .flags = V4L2_CTRL_FLAG_SLIDER },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_BACKLIGHT_COMP,
+ },
+ [R5U870_WDM_CTRL_BACKLIGHT_COMP_X1834] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_BACKLIGHT_COMP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Backlight Compensation",
+ .minimum = 0,
+ .maximum = 2,
+ .step = 1,
+ .default_value = 1,
+ .flags = V4L2_CTRL_FLAG_SLIDER },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_BACKLIGHT_COMP_2,
+ },
+ [R5U870_WDM_CTRL_WB_RED] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_RED_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "White Balance Red",
+ .minimum = 0,
+ .maximum = 255,
+ .step = 1,
+ .default_value = 127,
+ .flags = 0 },
+ .query_fn = r5u870_query_ctrl,
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_WB_RED_EX,
+ .auto_offset = offsetof(struct r5u870_ctx, vh_auto_wb)
+ },
+ [R5U870_WDM_CTRL_WB_GREEN] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_GREEN_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "White Balance Green",
+ .minimum = 0,
+ .maximum = 255,
+ .step = 1,
+ .default_value = 127,
+ .flags = 0 },
+ .query_fn = r5u870_query_ctrl,
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_WB_GREEN_EX,
+ .auto_offset = offsetof(struct r5u870_ctx, vh_auto_wb)
+ },
+ [R5U870_WDM_CTRL_WB_BLUE] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_BLUE_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "White Balance Blue",
+ .minimum = 0,
+ .maximum = 255,
+ .step = 1,
+ .default_value = 127,
+ .flags = 0 },
+ .query_fn = r5u870_query_ctrl,
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_WB_BLUE_EX,
+ .auto_offset = offsetof(struct r5u870_ctx, vh_auto_wb)
+ },
+ [R5U870_WDM_CTRL_WB_AUTO] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_AUTO_WHITE_BALANCE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Auto White Balance",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 1,
+ .flags = V4L2_CTRL_FLAG_UPDATE },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_WB_AUTO_EX,
+ .is_auto = 1,
+ .val_offset = offsetof(struct r5u870_ctx, vh_auto_wb)
+ },
+ [R5U870_WDM_CTRL_AUTO_EXPOSURE] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_AUTOEXPOSURE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Auto Exposure Control",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 1,
+ .flags = 0 },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_AEC_EX,
+ .is_auto = 1,
+ .val_offset = offsetof(struct r5u870_ctx, vh_aec)
+ },
+ [R5U870_WDM_CTRL_EXPOSURE] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_EXPOSURE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Exposure",
+ .minimum = 0,
+ .maximum = 511,
+ .step = 1,
+ .default_value = 255,
+ .flags = 0 },
+ .query_fn = r5u870_query_ctrl,
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_EXPOSURE_EX,
+ .auto_offset = offsetof(struct r5u870_ctx, vh_aec)
+ },
+ [R5U870_WDM_CTRL_AUTO_GAIN] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_AUTOGAIN,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Auto Gain Control",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 1,
+ .flags = 0 },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_AGC_EX,
+ .is_auto = 1,
+ .val_offset = offsetof(struct r5u870_ctx, vh_agc)
+ },
+ [R5U870_WDM_CTRL_GAIN] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_GAIN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Gain",
+ .minimum = 0,
+ .maximum = 127,
+ .step = 1,
+ .default_value = 63,
+ .flags = 0 },
+ .query_fn = r5u870_query_ctrl,
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_GAIN_EX,
+ .auto_offset = offsetof(struct r5u870_ctx, vh_agc)
+ },
+ [R5U870_WDM_CTRL_POWERLINE] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_POWER_LINE_FREQ,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .name = "Power Line Frequency",
+ .minimum = 0,
+ .maximum = 2,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0 },
+ .uc_menu_names = r5u870_powerline_names,
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_POWERLINE_EX,
+ },
+ [R5U870_WDM_CTRL_VFLIP] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_VFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "V-Flip",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0 },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_VFLIP_EX,
+ },
+ [R5U870_WDM_CTRL_HFLIP] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_HFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "H-Flip",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0 },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_HFLIP_EX,
+ },
+ [R5U870_WDM_CTRL_PRIVACY] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_PRIVACY,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Privacy",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0 },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_PRIVACY_EX,
+ },
+ [R5U870_WDM_CTRL_NIGHTMODE] = {
+ .base = { .uc_v4l = { .id = V4L2_CID_NIGHT_MODE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Night Mode",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0 },
+ .get_fn = r5u870_get_ctrl,
+ .set_fn = r5u870_set_ctrl_wdm },
+ .reg = R5U870_REG_NIGHT_MODE_EX,
+ },
+};
+
+static int r5u870_wdm_add_ctrls(struct r5u870_ctx *vhp, const int *ctrlarray)
+{
+ struct r5u870_ctrl *ncp;
+ int i;
+
+ for (i = 0; ctrlarray[i] != R5U870_WDM_CTRL_LAST; i++) {
+ ncp = (struct r5u870_ctrl *)
+ usbcam_ctrl_add_tmpl(vhp->vh_parent,
+ &r5u870_wdm_ctrls[ctrlarray[i]].base,
+ sizeof(*ncp));
+ if (!ncp)
+ return -ENOMEM;
+ }
+ r5u_dbg(vhp, R5U_CTRL, "Added %d WDM controls", i);
+ return 0;
+}
+
+
+
+/*
+ * UVC related code follows
+ */
+
+enum {
+ UVC_SC_VIDEOCONTROL = 1,
+ UVC_SC_VIDEOSTREAMING = 1,
+
+ UVC_VC_HEADER = 1,
+ UVC_VC_INPUT_TERMINAL = 2,
+ UVC_VC_OUTPUT_TERMINAL = 3,
+ UVC_VC_SELECTOR_UNIT = 4,
+ UVC_VC_PROCESSING_UNIT = 5,
+ UVC_VC_EXTENSION_UNIT = 6,
+
+ UVC_VC_REQUEST_ERROR_CODE_CONTROL = 0x02,
+
+ UVC_VS_INPUT_HEADER = 0x01,
+ UVC_VS_FORMAT_UNCOMPRESSED = 0x04,
+ UVC_VS_FRAME_UNCOMPRESSED = 0x05,
+
+ UVC_SET_CUR = 0x01,
+ UVC_GET_CUR = 0x81,
+ UVC_GET_MIN = 0x82,
+ UVC_GET_MAX = 0x83,
+ UVC_GET_RES = 0x84,
+ UVC_GET_LEN = 0x85,
+ UVC_GET_INFO = 0x86,
+ UVC_GET_DEF = 0x87,
+
+ UVC_PU_BACKLIGHT_COMPENSATION_CONTROL = 0x01,
+ UVC_PU_BRIGHTNESS_CONTROL = 0x02,
+ UVC_PU_CONTRAST_CONTROL = 0x03,
+ UVC_PU_POWER_LINE_FREQUENCY_CONTROL = 0x05,
+ UVC_PU_HUE_CONTROL = 0x06,
+ UVC_PU_SATURATION_CONTROL = 0x07,
+ UVC_PU_SHARPNESS_CONTROL = 0x08,
+ UVC_PU_GAMMA_CONTROL = 0x09,
+
+ UVC_VS_PROBE_CONTROL = 0x01,
+ UVC_VS_COMMIT_CONTROL = 0x02,
+
+};
+
+static int r5u870_uvc_req(struct r5u870_ctx *vhp, int cmd,
+ u8 valhi, u8 vallow, u8 idxhi, u8 idxlow,
+ u8 *buf, int len)
+{
+ int out, res, stres;
+ int tries = 5;
+ u8 stbuf[1];
+
+ out = (cmd == UVC_SET_CUR) ? 1 : 0;
+
+retry:
+ /* TODO: Base our other retry control message off this one. */
+ res = r5u870_control_msg(vhp, out, 1, cmd,
+ (valhi << 8) | vallow, (idxhi << 8) | idxlow,
+ buf, len);
+
+ if (res != -EPIPE)
+ //r5u_err(vhp, "res != -EPIPE.");
+ goto complete;
+
+ stres = r5u870_control_msg(vhp, 0, 1, UVC_GET_CUR,
+ UVC_VC_REQUEST_ERROR_CODE_CONTROL << 8,
+ vhp->vh_ctrl_ifnum,
+ stbuf, sizeof(stbuf));
+
+ if (((stres == -EPIPE) && --tries) ||
+ ((stres == 1) && (stbuf[0] == 1) && --tries)) {
+ msleep(5);
+ r5u_err(vhp, "uvc_req: retrying - EPIPE/stres error.");
+ goto retry;
+ }
+
+ if (stres != 1) {
+ r5u_err(vhp, "uvc_req: status req failed: %d", stres);
+ goto complete;
+
+ } else {
+ r5u_err(vhp, "uvc_req: status %d", stbuf[0]);
+ }
+
+complete:
+ if (res < 0) {
+ r5u_err(vhp, "uvc_req %02x/%02x%02x/%02x%02x failed: %d",
+ cmd, valhi, vallow, idxhi, idxlow, res);
+ }
+
+ return res;
+}
+
+static int r5u870_set_fmt_uvc(struct r5u870_ctx *vhp,
+ const struct r5u870_pix_fmt *fmtp,
+ const struct r5u870_resolution *resp)
+{
+ unsigned char buf[26];
+ int res;
+
+ memset(buf, 0, sizeof(buf));
+
+ buf[2] = fmtp->rp_formatidx;
+ buf[3] = resp->rw_frameidx;
+ *(__le32 *) &buf[4] = cpu_to_le32(resp->rw_interval);
+
+ r5u_dbg(vhp, R5U_FRAME,
+ "set_format: fmtidx:%d frameidx:%d %dx%d ival:%d",
+ fmtp->rp_formatidx, resp->rw_frameidx,
+ resp->rw_width, resp->rw_height, resp->rw_interval);
+
+ res = r5u870_uvc_req(vhp, UVC_SET_CUR, UVC_VS_PROBE_CONTROL, 0,
+ 0, vhp->vh_iso_ifnum, buf, sizeof(buf));
+ if (res != sizeof(buf)) {
+ r5u_err(vhp, "%s: probe_control set_cur1: short write %d",
+ __FUNCTION__, res);
+
+ return -EIO;
+ }
+
+ res = r5u870_uvc_req(vhp, UVC_GET_CUR, UVC_VS_PROBE_CONTROL, 0,
+ 0, vhp->vh_iso_ifnum, buf, sizeof(buf));
+ if (res != sizeof(buf)) {
+ r5u_err(vhp, "%s: probe_control get_cur: short read %d",
+ __FUNCTION__, res);
+ return -EIO;
+ }
+
+ if (buf[2] != fmtp->rp_formatidx) {
+ r5u_err(vhp, "%s: probe_control get_cur: got fmt %d",
+ __FUNCTION__, buf[2]);
+ return -EIO;
+ }
+
+ if (buf[3] != resp->rw_frameidx) {
+ r5u_err(vhp, "%s: probe_control get_cur: got frame idx %d",
+ __FUNCTION__, buf[3]);
+ return -EIO;
+ }
+
+ res = r5u870_uvc_req(vhp, UVC_SET_CUR, UVC_VS_COMMIT_CONTROL, 0,
+ 0, vhp->vh_iso_ifnum, buf, sizeof(buf));
+ if (res != sizeof(buf)) {
+ r5u_err(vhp, "%s: commit_control set_cur2: short write %d",
+ __FUNCTION__, res);
+ return -EIO;
+ }
+
+ vhp->vh_iso_minpacket = le32_to_cpup((__le32 *) &buf[22]);
+
+ return 0;
+}
+
+static int r5u870_cap_stop_uvc(struct r5u870_ctx *vhp)
+{
+ /* UVC capture is controlled by changing the altsetting */
+ return 0;
+}
+
+static int r5u870_decide_pkt_uvc(struct r5u870_ctx *vhp, int pktstatus,
+ int pktlen, const u8 *pktdata, int *start)
+{
+ if (!pktlen) {
+ vhp->vh_emptypkts++;
+ if (vhp->vh_emptypkts >= R5U870_EMPTYPKT_GIVE_UP) {
+ r5u_err(vhp, "capture abort: too many empty pkts");
+ return -EIO;
+ }
+ if (vhp->vh_frame_accum < 0)
+ return -EPIPE;
+ return -EAGAIN;
+ }
+
+ vhp->vh_emptypkts = 0;
+ if (vhp->vh_frame_accum < 0) {
+ if (pktdata[1] & 2)
+ vhp->vh_frame_accum = 0;
+ return -EPIPE;
+ }
+
+ if ((pktdata[0] < 2) || (pktdata[0] > pktlen)) {
+ r5u_err(vhp, "capture abort: hdrlen=%d pktlen=%d",
+ pktdata[0], pktlen);
+ return -EIO;
+ }
+ vhp->vh_frame_accum += pktlen - pktdata[0];
+ if (vhp->vh_frame_accum > vhp->vh_parent->ud_format.sizeimage) {
+ r5u_err(vhp, "frame abort: accum=%d", vhp->vh_frame_accum);
+ vhp->vh_frame_accum = -1;
+ return -EPIPE;
+ }
+
+ *start = pktdata[0];
+ if (pktdata[1] & 2) {
+ if (vhp->vh_frame_accum <
+ vhp->vh_parent->ud_format.sizeimage) {
+ r5u_err(vhp, "warning: short frame (exp:%d got:%d)",
+ vhp->vh_parent->ud_format.sizeimage,
+ vhp->vh_frame_accum);
+ }
+ vhp->vh_frame_accum = 0;
+ return 0;
+ }
+ return -EAGAIN;
+}
+
+
+/*
+ * Known video format GUIDs and V4L pixel format translations
+ *
+ * Only uncompressed formats are supported, as Ricoh webcams are too
+ * minimal to do anything else.
+ */
+
+static const struct r5u870_uvc_fmtinfo {
+ const char *fi_name;
+ int fi_v4l_id;
+ u8 fi_guid[16];
+
+} r5u870_uvc_fmts[] = {
+ { .fi_name = "YUY2 4:2:2",
+ .fi_v4l_id = V4L2_PIX_FMT_YUYV,
+ .fi_guid = { 0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } },
+ { }
+};
+
+static int r5u870_uvc_add_resolution(struct r5u870_ctx *vhp,
+ struct r5u870_pix_fmt *fmtp,
+ int width, int height, int reqbw,
+ int frameidx, int interval)
+{
+ int i;
+ struct r5u870_resolution *resp;
+
+ if (!width || !height) {
+ r5u_dbg(vhp, R5U_INIT, "invalid frame descriptor %d at %dx%d",
+ frameidx, width, height);
+ return -EINVAL;
+ }
+
+ resp = NULL;
+ for (i = 0; i < fmtp->rp_restbl_alloc; i++) {
+ if (!fmtp->rp_restbl[i].rw_width) {
+ resp = (struct r5u870_resolution *)
+ &fmtp->rp_restbl[i];
+ break;
+ }
+ }
+
+ if (!resp) {
+ r5u_dbg(vhp, R5U_INIT,
+ "no space for frame descriptor %d at %dx%d",
+ frameidx, width, height);
+ return 0;
+ }
+
+ resp->rw_width = width;
+ resp->rw_height = height;
+ resp->rw_frameidx = frameidx;
+ resp->rw_reqbw = reqbw;
+ resp->rw_interval = interval;
+
+ r5u_dbg(vhp, R5U_INIT, "Found resolution %d: %dx%d ival %d (%d B/s)",
+ frameidx, width, height, interval, reqbw);
+
+ return 0;
+}
+
+static int r5u870_uvc_add_fmt(struct r5u870_ctx *vhp, const u8 *guid,
+ int fmtidx, int nresolutions,
+ struct r5u870_pix_fmt **new_fmt)
+{
+ const struct r5u870_uvc_fmtinfo *fip, *fmtarray = r5u870_uvc_fmts;
+ struct r5u870_pix_fmt *nfp, *fmtp;
+ int i;
+
+ fip = NULL;
+ for (i = 0; fmtarray[i].fi_name != NULL; i++) {
+ if (!memcmp(fmtarray[i].fi_guid, guid, 16)) {
+ fip = &fmtarray[i];
+ break;
+ }
+ }
+
+ if (fip == NULL) {
+ r5u_dbg(vhp, R5U_INIT, "unknown format "
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
+ "%02x%02x%02x%02x%02x%02x",
+ guid[0], guid[1], guid[2], guid[3], guid[4], guid[5],
+ guid[6], guid[7], guid[8], guid[9], guid[10],
+ guid[11], guid[12], guid[13], guid[14], guid[15]);
+ return -ENOENT;
+ }
+
+ nfp = (struct r5u870_pix_fmt *)
+ kmalloc((1 + vhp->vh_npixfmts) * sizeof(*nfp), GFP_KERNEL);
+ if (!nfp)
+ return -ENOMEM;
+ if (vhp->vh_npixfmts)
+ memcpy(nfp, vhp->vh_pixfmts, vhp->vh_npixfmts * sizeof(*nfp));
+
+ fmtp = &nfp[vhp->vh_npixfmts];
+ memset(fmtp, 0, sizeof(*fmtp));
+ strlcpy(fmtp->base.description,
+ fip->fi_name,
+ sizeof(fmtp->base.description));
+ fmtp->base.pixelformat = fip->fi_v4l_id;
+ fmtp->rp_formatidx = fmtidx;
+ fmtp->rp_restbl = (struct r5u870_resolution *)
+ kmalloc((1 + nresolutions) * sizeof(*fmtp->rp_restbl),
+ GFP_KERNEL);
+ if (!fmtp->rp_restbl) {
+ kfree(nfp);
+ return -ENOMEM;
+ }
+
+ memset((char *)fmtp->rp_restbl, 0,
+ (1 + nresolutions) * sizeof(*fmtp->rp_restbl));
+ fmtp->rp_restbl_alloc = nresolutions;
+
+ if (vhp->vh_npixfmts && vhp->vh_dyn_pixfmts)
+ kfree(vhp->vh_pixfmts);
+ vhp->vh_pixfmts = nfp;
+ vhp->vh_npixfmts++;
+
+ if (new_fmt)
+ (*new_fmt) = fmtp;
+
+ r5u_dbg(vhp, R5U_INIT, "Found format %d: %c%c%c%c (%d frames)",
+ fmtidx,
+ fmtp->base.pixelformat & 0xff,
+ (fmtp->base.pixelformat >> 8) & 0xff,
+ (fmtp->base.pixelformat >> 16) & 0xff,
+ (fmtp->base.pixelformat >> 24) & 0xff,
+ nresolutions);
+
+ return 0;
+}
+
+static int r5u870_uvc_parse_vs(struct r5u870_ctx *vhp, int ifnum)
+{
+ struct usb_interface *intf;
+ struct usb_host_interface *aintf;
+ struct r5u870_pix_fmt *curfmt = NULL;
+ u8 *desc;
+ int dlen, rlen;
+ int wid, hgt, reqbw, ival;
+ int res;
+
+ intf = usb_ifnum_to_if(r5u870_dev(vhp), ifnum);
+ if (!intf)
+ return -EINVAL;
+
+ aintf = intf->cur_altsetting;
+
+ for (desc = aintf->extra, rlen = aintf->extralen;
+ rlen > 2;
+ rlen -= desc[0], desc += desc[0]) {
+
+ dlen = desc[0];
+ if (dlen < 2)
+ return -EINVAL;
+ if (desc[1] != USB_DT_CS_INTERFACE)
+ continue;
+ if (dlen < 3)
+ return -EINVAL;
+
+ switch (desc[2]) {
+ case UVC_VS_INPUT_HEADER:
+ if (dlen < 7) {
+ r5u_err(vhp, "VS_INPUT_HEADER too short "
+ "at %d bytes", dlen);
+ return -EINVAL;
+ }
+ vhp->vh_iso_ep = desc[6] & 0xf;
+ break;
+
+ case UVC_VS_FORMAT_UNCOMPRESSED:
+ if (dlen < 21) {
+ r5u_err(vhp, "VS_FORMAT_UNCOMP too short "
+ "at %d bytes", dlen);
+ break;
+ }
+ res = r5u870_uvc_add_fmt(vhp, &desc[5], desc[3],
+ desc[4], &curfmt);
+ if (res) {
+ if (res != -ENOENT)
+ return res;
+ curfmt = NULL;
+ }
+ break;
+
+ case UVC_VS_FRAME_UNCOMPRESSED:
+ if (dlen < 26) {
+ r5u_err(vhp, "VS_FRAME_UNCOMP too short "
+ "at %d bytes", dlen);
+ break;
+ }
+ if (!curfmt) {
+ r5u_dbg(vhp, R5U_INIT, "VS_FRAME_UNCOMP "
+ "not following VS_FORMAT_UNCOMP");
+ break;
+ }
+
+ wid = desc[5] | (desc[6] << 8);
+ hgt = desc[7] | (desc[8] << 8);
+ reqbw = desc[13] | (desc[14] << 8) |
+ (desc[15] << 16) | (desc[16] << 24);
+ reqbw = (reqbw + 7) / 8;
+ ival = le32_to_cpup((__le32 *) &desc[21]);
+
+ res = r5u870_uvc_add_resolution(vhp, curfmt, wid, hgt,
+ reqbw, desc[3], ival);
+ if (res)
+ return res;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Known UVC controls for processing units
+ *
+ * Not all types of UVC controls are supported. We stick to simple
+ * controls with one or two byte values, and don't do anything very
+ * complicated.
+ *
+ * We don't support camera unit controls, or multiple processing units
+ * with instances of the same control. We also don't check for
+ * redefined control IDs, and let usbcam do this for us.
+ */
+
+struct r5u870_uvc_ctrlinfo {
+ const char *ci_name;
+ u32 ci_v4l_id;
+ int ci_v4l_type;
+ int ci_v4l_flags;
+ u8 ci_reg;
+ u8 ci_size;
+ u8 ci_bm_index;
+ int ci_min, ci_max, ci_def;
+ u8 ci_min_force, ci_max_force, ci_def_force;
+ const char **ci_menu_names;
+ int ci_val_offset;
+};
+
+static struct r5u870_uvc_ctrlinfo r5u870_uvc_proc_ctrls[] = {
+ { .ci_name = "Brightness",
+ .ci_v4l_id = V4L2_CID_BRIGHTNESS,
+ .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
+ .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
+ .ci_reg = UVC_PU_BRIGHTNESS_CONTROL,
+ .ci_size = 2,
+ .ci_bm_index = 0 },
+ { .ci_name = "Contrast",
+ .ci_v4l_id = V4L2_CID_CONTRAST,
+ .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
+ .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
+ .ci_reg = UVC_PU_CONTRAST_CONTROL,
+ .ci_size = 2,
+ .ci_bm_index = 1 },
+ { .ci_name = "Hue",
+ .ci_v4l_id = V4L2_CID_HUE,
+ .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
+ .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
+ .ci_reg = UVC_PU_HUE_CONTROL,
+ .ci_size = 2,
+ .ci_min = -180, .ci_min_force = 1,
+ .ci_max = 180, .ci_max_force = 1,
+ .ci_def = 0, .ci_def_force = 1,
+ .ci_bm_index = 2 },
+ { .ci_name = "Saturation",
+ .ci_v4l_id = V4L2_CID_SATURATION,
+ .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
+ .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
+ .ci_reg = UVC_PU_SATURATION_CONTROL,
+ .ci_size = 2,
+ .ci_bm_index = 3 },
+ { .ci_name = "Sharpness",
+ .ci_v4l_id = V4L2_CID_SHARPNESS,
+ .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
+ .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
+ .ci_reg = UVC_PU_SHARPNESS_CONTROL,
+ .ci_size = 2,
+ .ci_bm_index = 4 },
+ { .ci_name = "Gamma",
+ .ci_v4l_id = V4L2_CID_GAMMA,
+ .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
+ .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
+ .ci_reg = UVC_PU_GAMMA_CONTROL,
+ .ci_size = 2,
+ .ci_bm_index = 5 },
+ { .ci_name = "Backlight Compensation",
+ .ci_v4l_id = V4L2_CID_BACKLIGHT_COMP,
+ .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
+ .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
+ .ci_reg = UVC_PU_BACKLIGHT_COMPENSATION_CONTROL,
+ .ci_size = 2,
+ .ci_bm_index = 8 },
+ { .ci_name = "Power Line Frequency",
+ .ci_v4l_id = V4L2_CID_POWER_LINE_FREQ,
+ .ci_v4l_type = V4L2_CTRL_TYPE_MENU,
+ .ci_reg = UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
+ .ci_size = 1,
+ .ci_bm_index = 10,
+ .ci_min = 0, .ci_min_force = 1,
+ .ci_max = 2, .ci_max_force = 1,
+ .ci_menu_names = r5u870_powerline_names },
+ { }
+};
+
+static int r5u870_uvc_ctrl_req(struct r5u870_ctx *vhp,
+ const struct r5u870_ctrl *ctrlp,
+ int req, int *value)
+{
+ u8 buf[4];
+ int size, i, val, res;
+
+ size = ctrlp->size;
+ if (req == UVC_GET_INFO)
+ size = 1;
+
+ if (size > sizeof(buf)) {
+ r5u_err(vhp, "Control ID %d is too large, %d bytes",
+ ctrlp->reg, size);
+ return -EINVAL;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ if (req != UVC_SET_CUR) {
+ res = r5u870_uvc_req(vhp, req, ctrlp->reg, 0,
+ ctrlp->unit, vhp->vh_ctrl_ifnum,
+ buf, size);
+ if (res < 0)
+ return res;
+ if (res != size) {
+ r5u_err(vhp, "short read for UVC control %d",
+ ctrlp->reg);
+ return -EIO;
+ }
+
+ val = 0;
+ switch (size) {
+ case 1:
+ val = buf[0];
+ break;
+ case 2:
+ val = le16_to_cpu(*(__le16 *) buf);
+ break;
+ case 4:
+ val = le32_to_cpu(*(__le32 *) buf);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *value = val;
+ return 0;
+ }
+
+ val = *value;
+ for (i = 0; i < size; i++, val >>= 8)
+ buf[i] = val & 0xff;
+
+ res = r5u870_uvc_req(vhp, UVC_SET_CUR, ctrlp->reg, 0,
+ ctrlp->unit, vhp->vh_ctrl_ifnum,
+ buf, size);
+ if (res < 0)
+ return res;
+ if (res != size) {
+ r5u_err(vhp, "short write for UVC control %d",
+ ctrlp->reg);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int r5u870_set_ctrl_uvc(struct usbcam_dev *udp,
+ const struct usbcam_ctrl *basep,
+ const struct v4l2_ext_control *c)
+{
+ struct r5u870_ctx *vhp = udp_r5u870(udp);
+ struct r5u870_ctrl *ctrlp = container_of(basep, struct r5u870_ctrl,
+ base);
+ int val;
+ int res = 0;
+
+ if (!(ctrlp->info & 2)) {
+ r5u_dbg(vhp, R5U_CTRL, "control %s <= %d [set not supported]",
+ ctrlp->base.uc_v4l.name, c->value);
+ return -EINVAL;
+ }
+
+ r5u_dbg(vhp, R5U_CTRL, "control %s/uvc %02x <= %d",
+ ctrlp->base.uc_v4l.name, ctrlp->reg, c->value);
+
+ val = c->value;
+ res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_SET_CUR, &val);
+ if (res)
+ return res;
+
+ ctrlp->value = c->value;
+
+ return res;
+}
+
+/*
+ * Initialize a r5u870_ctrl structure based on a UVC descriptor array,
+ * and a bit within a supported control bitmap.
+ */
+static int r5u870_uvc_init_ctrl(struct r5u870_ctx *vhp,
+ int unit,
+ const struct r5u870_uvc_ctrlinfo *ci_array,
+ int bit_offset)
+{
+ const struct r5u870_uvc_ctrlinfo *cip = NULL;
+ struct r5u870_ctrl *ctrlp;
+ int i, res, val;
+
+ for (i = 0; ci_array[i].ci_name != NULL; i++) {
+ if (ci_array[i].ci_bm_index == bit_offset) {
+ cip = &ci_array[i];
+ break;
+ }
+ }
+
+ if (!cip)
+ return -ENOENT;
+
+ ctrlp = (struct r5u870_ctrl *) usbcam_ctrl_alloc(sizeof(*ctrlp));
+ if (!ctrlp)
+ return -ENOMEM;
+
+ ctrlp->base.uc_v4l.id = cip->ci_v4l_id;
+ ctrlp->base.uc_v4l.type = cip->ci_v4l_type;
+ strlcpy(ctrlp->base.uc_v4l.name,
+ cip->ci_name,
+ sizeof(ctrlp->base.uc_v4l.name));
+ ctrlp->base.uc_v4l.flags = cip->ci_v4l_flags;
+ ctrlp->base.uc_v4l.step = 1;
+ ctrlp->base.uc_menu_names = cip->ci_menu_names;
+ ctrlp->base.get_fn = r5u870_get_ctrl;
+ ctrlp->base.set_fn = r5u870_set_ctrl_uvc;
+ ctrlp->base.query_fn = r5u870_query_ctrl;
+ ctrlp->reg = cip->ci_reg;
+ ctrlp->unit = unit;
+ ctrlp->size = cip->ci_size;
+ ctrlp->is_auto = 0;
+ ctrlp->auto_offset = 0;
+
+ res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_INFO, &val);
+ if (res)
+ goto failed;
+ ctrlp->info = val;
+
+ if (cip->ci_min_force) {
+ ctrlp->base.uc_v4l.minimum = cip->ci_min;
+ } else {
+ res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_MIN, &val);
+ if (res < 0)
+ goto failed;
+ ctrlp->base.uc_v4l.minimum = val;
+ }
+ if (cip->ci_max_force) {
+ ctrlp->base.uc_v4l.maximum = cip->ci_max;
+ } else {
+ res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_MAX, &val);
+ if (res < 0)
+ goto failed;
+ ctrlp->base.uc_v4l.maximum = val;
+ }
+
+ if (cip->ci_def_force) {
+ ctrlp->base.uc_v4l.default_value = cip->ci_def;
+ } else {
+ res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_DEF, &val);
+ if (res)
+ goto failed;
+ return res;
+ ctrlp->base.uc_v4l.default_value = val;
+ }
+
+ res = usbcam_ctrl_add(vhp->vh_parent, &ctrlp->base);
+ if (res)
+ goto failed;
+
+ r5u_dbg(vhp, R5U_INIT, "Found UVC control %s [%d,%d] def:%d info:%02x",
+ ctrlp->base.uc_v4l.name, ctrlp->base.uc_v4l.minimum,
+ ctrlp->base.uc_v4l.maximum, ctrlp->base.uc_v4l.default_value,
+ ctrlp->info);
+ return 0;
+
+failed:
+ usbcam_ctrl_free(ctrlp);
+ return res;
+}
+
+static int r5u870_uvc_add_ctrls(struct r5u870_ctx *vhp, int unit,
+ const struct r5u870_uvc_ctrlinfo *ci_array,
+ const u8 *bits, int nbits)
+{
+ int i, res;
+
+ for (i = 0; i < nbits; i++) {
+ if (!test_bit(i, (const unsigned long *) bits))
+ continue;
+ res = r5u870_uvc_init_ctrl(vhp, unit, ci_array, i);
+ if (res == -ENOENT) {
+ r5u_dbg(vhp, R5U_INIT,
+ "unit %d ctrl %d not recognized", unit, i);
+ }
+ else if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int r5u870_uvc_parse_vc(struct r5u870_ctx *vhp)
+{
+ struct usb_host_interface *aintf =
+ vhp->vh_parent->ud_intf->cur_altsetting;
+ u8 *desc;
+ int dlen, rlen, count;
+ int i, res;
+
+ vhp->vh_ctrl_ifnum = aintf->desc.bInterfaceNumber;
+
+ for (desc = aintf->extra, rlen = aintf->extralen;
+ rlen > 2;
+ rlen -= desc[0], desc += desc[0]) {
+
+ dlen = desc[0];
+ if (dlen < 2)
+ return -EINVAL;
+ if (desc[1] != USB_DT_CS_INTERFACE)
+ continue;
+ if (dlen < 3)
+ return -EINVAL;
+
+ switch (desc[2]) {
+ case UVC_VC_HEADER:
+ count = (dlen < 12) ? 0 : desc[11];
+ if (dlen < (12 + count)) {
+ r5u_err(vhp, "VC_HEADER too short at %d bytes",
+ dlen);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < count; i++) {
+ res = usbcam_claim_interface(vhp->vh_parent,
+ desc[12 + i]);
+ if (res)
+ r5u_err(vhp, "interface %d already "
+ "claimed", desc[12 + i]);
+ vhp->vh_iso_ifnum = desc[12 + i];
+
+ res = r5u870_uvc_parse_vs(vhp, desc[12 + i]);
+ if (res)
+ return res;
+ }
+ break;
+
+ case UVC_VC_PROCESSING_UNIT:
+ count = (dlen < 8) ? 0 : desc[7];
+ if (dlen < (8 + count)) {
+ r5u_err(vhp, "VC_PROCESSING_UNIT too short "
+ "at %d bytes", dlen);
+ return -EINVAL;
+ }
+
+ res = r5u870_uvc_add_ctrls(vhp, desc[3],
+ r5u870_uvc_proc_ctrls,
+ &desc[8], desc[7] * 8);
+ if (res)
+ return res;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+static void r5u870_do_stop(struct r5u870_ctx *vhp)
+{
+ if (vhp->vh_parent->ud_capturing) {
+ vhp->vh_parent->ud_capturing = 0;
+ vhp->vh_ctrl_reg_enable = 0;
+
+ usbcam_urbstream_stop(&vhp->vh_iso, 0);
+ usbcam_curframe_abortall(vhp->vh_parent);
+
+ if (!vhp->vh_parent->ud_disconnected) {
+ usb_set_interface(r5u870_dev(vhp),
+ vhp->vh_iso_ifnum,
+ 0);
+ vhp->vh_cap_stop(vhp);
+ }
+
+ usbcam_urbstream_cleanup(&vhp->vh_iso);
+ }
+}
+
+/*
+ * As long as we are requested to capture, we keep the iso stream running.
+ * If we are explicitly requested to stop, we halt.
+ * If we run out of frame buffers to capture into, we halt.
+ */
+static void r5u870_iso_packet_done(struct usbcam_dev *udp,
+ struct usbcam_urbstream *usp,
+ const uint8_t *pktdata,
+ int pktlen, int pktstatus)
+{
+ struct r5u870_ctx *vhp = udp_r5u870(udp);
+ struct usbcam_curframe cf;
+ int res, start = 0;
+
+ res = vhp->vh_decide_pkt(vhp, pktstatus, pktlen,
+ (u8 *) pktdata, &start);
+ switch (res) {
+ case -EPIPE:
+ if (vhp->vh_framebuf_offset) {
+ vhp->vh_framebuf_offset = 0;
+ usbcam_curframe_testpattern(udp);
+ usbcam_curframe_complete(udp);
+ }
+ break;
+
+ case 0:
+ case -EAGAIN:
+ if (pktlen) {
+ if (usbcam_curframe_get(udp, &cf)) {
+ /*
+ * We have data, but there is no frame buffer
+ * queued to accept it, so we stop.
+ */
+ r5u870_do_stop(vhp);
+ break;
+ }
+
+ BUG_ON(pktlen - start + vhp->vh_framebuf_offset >
+ cf.uf_size);
+
+ /*
+ * This is our one and only memcpy.
+ * It's kind of hard to get around doing this, as
+ * we cannot predict into which isochronous
+ * packets the camera will choose to return data.
+ */
+ memcpy(cf.uf_base + vhp->vh_framebuf_offset,
+ pktdata + start,
+ pktlen - start);
+ vhp->vh_framebuf_offset += (pktlen - start);
+ }
+
+ if (!res) {
+ vhp->vh_framebuf_offset = 0;
+ usbcam_curframe_complete(udp);
+ }
+ break;
+
+ case -EIO:
+ r5u870_do_stop(vhp);
+ break;
+
+ default:
+ BUG();
+ }
+}
+
+static void r5u870_iso_submit_error(struct usbcam_dev *udp,
+ struct usbcam_urbstream *usp, int status)
+{
+ struct r5u870_ctx *vhp = udp_r5u870(udp);
+ r5u_dbg(vhp, R5U_FRAME, "iso submit error: %d", status);
+ r5u870_do_stop(vhp);
+}
+
+static struct usbcam_urbstream_ops r5u870_urb_data_ops = {
+ .packet_done = r5u870_iso_packet_done,
+ .submit_error = r5u870_iso_submit_error,
+};
+
+
+static void r5u870_usbcam_release(struct usbcam_dev *udp)
+{
+ struct r5u870_ctx *vhp = udp_r5u870(udp);
+ int i;
+
+ for (i = 0; i < vhp->vh_npixfmts; i++) {
+ if (vhp->vh_pixfmts[i].rp_restbl_alloc)
+ kfree(vhp->vh_pixfmts[i].rp_restbl);
+ }
+}
+
+
+static const struct r5u870_model *r5u870_find_model(int driver_info);
+static int r5u870_usbcam_init(struct usbcam_dev *udp,
+ const struct usb_device_id *devid)
+{
+ struct r5u870_ctx *vhp;
+ int model_info;
+ int res;
+
+ model_info = devid->driver_info;
+
+ /* Initialize the private structure, don't use r5u_dbg() before this */
+ vhp = udp_r5u870(udp);
+ memset(vhp, 0, sizeof(*vhp));
+ vhp->vh_parent = udp;
+ vhp->vh_timeout = 1000;
+
+ vhp->vh_ctrl_ifnum = -1;
+ vhp->vh_iso_ifnum = -1;
+ vhp->vh_iso_minpacket = -1;
+
+ vhp->vh_model = r5u870_find_model(model_info);
+
+ if (!vhp->vh_model) {
+ r5u_err(vhp, "no suitable model descriptor for %04x:%04x",
+ le16_to_cpu(r5u870_dev(vhp)->descriptor.idVendor),
+ le16_to_cpu(r5u870_dev(vhp)->descriptor.idProduct));
+ return -ENODEV;
+ }
+
+ vhp->vh_pixfmts = vhp->vh_model->rm_pixfmts;
+ vhp->vh_npixfmts = vhp->vh_model->rm_npixfmts;
+
+ if (vhp->vh_model->rm_uvc) {
+ vhp->vh_set_fmt = r5u870_set_fmt_uvc;
+ vhp->vh_cap_stop = r5u870_cap_stop_uvc;
+ vhp->vh_decide_pkt = r5u870_decide_pkt_uvc;
+ } else {
+ vhp->vh_set_fmt = r5u870_set_fmt_wdm;
+ vhp->vh_cap_stop = r5u870_cap_stop_wdm;
+ vhp->vh_decide_pkt = r5u870_decide_pkt_wdm;
+ }
+
+ r5u_info(vhp, "Detected %s", vhp->vh_model->rm_name);
+ snprintf(vhp->vh_parent->ud_vdev.name,
+ sizeof(vhp->vh_parent->ud_vdev.name),
+ "%s #%d",
+ vhp->vh_model->rm_name, vhp->vh_parent->ud_minidrv_id + 1);
+
+ res = r5u870_dev_init(vhp);
+ if (res < 0) {
+ r5u_err(vhp, "initialization failed: %d", res);
+ goto out_failed;
+ }
+
+ if (vhp->vh_model->rm_uvc) {
+ /*
+ * This appears to be a UVC VideoControl interface.
+ * Claim all of the associated VideoStreaming interfaces
+ * and configure the required endpoints and unit IDs
+ */
+ res = r5u870_uvc_parse_vc(vhp);
+ if (res < 0) {
+ r5u_err(vhp, "UVC setup failed: %d", res);
+ goto out_failed;
+ }
+
+ } else {
+ /* We are looking at a proprietary Ricoh interface */
+ vhp->vh_iso_ifnum =
+ udp->ud_intf->altsetting->desc.bInterfaceNumber;
+ vhp->vh_iso_ep = 6;
+ }
+
+ if (vhp->vh_model->rm_wdm_ctrlids) {
+ res = r5u870_wdm_add_ctrls(vhp, vhp->vh_model->rm_wdm_ctrlids);
+ if (res < 0) {
+ r5u_err(vhp, "Vendor control setup failed: %d", res);
+ goto out_failed;
+ }
+ }
+
+ /* Configure the usbcam pixel format and control arrays */
+ if (!vhp->vh_npixfmts) {
+ r5u_err(vhp, "No pixel formats detected");
+ res = -ENODEV;
+ goto out_failed;
+ }
+
+ udp->ud_fmt_array = &vhp->vh_pixfmts[0].base;
+ udp->ud_fmt_array_elem_size = sizeof(vhp->vh_pixfmts[0]);
+ udp->ud_fmt_array_len = vhp->vh_npixfmts;
+
+ if (!vhp->vh_model->rm_no_first_auto_suppress)
+ vhp->vh_ctrl_auto_suppress = 1;
+
+ usbcam_urbstream_init(&vhp->vh_iso, udp, vhp->vh_iso_ep);
+
+ /* Set the default format */
+ vhp->vh_fmt = &vhp->vh_pixfmts[0];
+ vhp->vh_res = &vhp->vh_fmt->rp_restbl[0];
+ udp->ud_format.width = vhp->vh_res->rw_width;
+ udp->ud_format.height = vhp->vh_res->rw_height;
+ udp->ud_format.pixelformat = vhp->vh_fmt->base.pixelformat;
+ udp->ud_format.field = V4L2_FIELD_INTERLACED;
+ udp->ud_format.bytesperline = udp->ud_format.width * 2;
+ udp->ud_format.sizeimage = (udp->ud_format.width *
+ udp->ud_format.height * 2);
+ udp->ud_format.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+ /* Configure default values for all controls */
+ res = r5u870_set_controls(vhp, 1);
+ if (res < 0) {
+ r5u_err(vhp, "set defaults failed: %d", res);
+ goto out_failed;
+ }
+
+ return 0;
+
+out_failed:
+ r5u870_usbcam_release(udp);
+ return res;
+}
+
+static void r5u870_usbcam_disconnect(struct usbcam_dev *udp)
+{
+ struct r5u870_ctx *vhp = udp_r5u870(udp);
+ r5u870_do_stop(vhp);
+}
+
+
+/*
+ * The power management stuff doesn't quite work yet, so we don't
+ * yet set the supports_autosuspend field in the ops structure.
+ */
+
+static int r5u870_usbcam_suspend(struct usbcam_dev *udp, pm_message_t msg)
+{
+ struct r5u870_ctx *vhp = udp_r5u870(udp);
+ r5u870_do_stop(vhp);
+ return 0;
+}
+
+static int r5u870_usbcam_resume(struct usbcam_dev *udp)
+{
+ struct r5u870_ctx *vhp = udp_r5u870(udp);
+ int res;
+
+ res = r5u870_dev_init(vhp);
+ if (res < 0) {
+ r5u_err(vhp, "dev reinitialization failed: %d", res);
+ return res;
+ }
+
+ vhp->vh_configured = 0;
+
+ return 0;
+}
+
+static int r5u870_try_format(struct usbcam_dev *udp, struct v4l2_pix_format *f,
+ const struct r5u870_pix_fmt **fmt_out,
+ const struct r5u870_resolution **res_out)
+{
+ struct r5u870_ctx *vhp = udp_r5u870(udp);
+ const struct r5u870_pix_fmt *fmt;
+ const struct r5u870_resolution *res, *restbl;
+ int i;
+
+ fmt = NULL;
+ for (i = 0; i < vhp->vh_npixfmts; i++) {
+ if (vhp->vh_pixfmts[i].base.pixelformat == f->pixelformat) {
+ fmt = &vhp->vh_pixfmts[i];
+ break;
+ }
+ }
+ if (fmt == NULL)
+ return -EINVAL;
+
+ restbl = fmt->rp_restbl;
+ if (!restbl || !restbl[0].rw_width) {
+ r5u_err(vhp, "invalid resolution table");
+ return -EINVAL;
+ }
+
+ /* Find the most acceptable resolution */
+ res = NULL;
+ for (i = 0; restbl[i].rw_width > 0; i++) {
+ if (!res) {
+ res = &restbl[i];
+ }
+ else if (res->rw_width > f->width) {
+ if (restbl[i].rw_width < res->rw_width)
+ res = &restbl[i];
+ }
+ else if (res->rw_height > f->height) {
+ if ((restbl[i].rw_width <= f->width) &&
+ (restbl[i].rw_height < res->rw_height))
+ res = &restbl[i];
+ }
+ else if ((restbl[i].rw_width <= f->width) &&
+ (restbl[i].rw_height <= f->height) &&
+ ((restbl[i].rw_width > res->rw_width) ||
+ ((restbl[i].rw_width == res->rw_width) &&
+ (restbl[i].rw_height > res->rw_height)))) {
+ res = &restbl[i];
+ }
+ }
+
+ if ((f->width > 1) && (f->height > 1)) {
+ r5u_dbg(vhp, R5U_INIT, "pix_fmt width: %d height: %d", f->width, f->height);
+ r5u_dbg(vhp, R5U_INIT, "res width: %d height %d", res->rw_width, res->rw_height);
+
+ if (((res->rw_width > f->width) || (res->rw_height > f->height))) {
+ r5u_dbg(vhp, R5U_INIT, "Bad size request. Returning -EINVAL.");
+ return -EINVAL;
+ }
+ }
+
+ memset(f, 0, sizeof(*f));
+ f->width = res->rw_width;
+ f->height = res->rw_height;
+ f->pixelformat = fmt->base.pixelformat;
+ f->bytesperline = f->width * 2;
+ f->sizeimage = f->width * f->height * 2;
+ f->field = V4L2_FIELD_INTERLACED;
+ f->colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+ r5u_dbg(vhp, R5U_INIT, "Settled on pixel format: %d (%s)", fmt->base.pixelformat, fmt->base.description);
+
+ if (fmt_out)
+ (*fmt_out) = fmt;
+ if (res_out)
+ (*res_out) = res;
+
+ return 0;
+}
+
+static int r5u870_usbcam_try_format(struct usbcam_dev *udp,
+ struct v4l2_pix_format *f)
+{
+ return r5u870_try_format(udp, f, NULL, NULL);
+}
+
+static int r5u870_usbcam_set_format(struct usbcam_dev *udp,
+ struct v4l2_pix_format *f)
+{
+ struct r5u870_ctx *vhp = udp_r5u870(udp);
+ const struct r5u870_pix_fmt *fmt_out;
+ const struct r5u870_resolution *res_out;
+ int res;
+
+ res = r5u870_try_format(udp, f, &fmt_out, &res_out);
+ if (res)
+ return res;
+
+ if ((udp->ud_format.width != f->width) ||
+ (udp->ud_format.height != f->height) ||
+ (udp->ud_format.pixelformat != f->pixelformat) ||
+ (udp->ud_format.sizeimage != f->sizeimage))
+ vhp->vh_configured = 0;
+
+ udp->ud_format = *f;
+ vhp->vh_fmt = fmt_out;
+ vhp->vh_res = res_out;
+ return 0;
+}
+
+static void r5u870_usbcam_cap_stop(struct usbcam_dev *udp)
+{
+ struct r5u870_ctx *vhp = udp_r5u870(udp);
+ r5u870_do_stop(vhp);
+}
+
+static int r5u870_config_iso_ep(struct r5u870_ctx *vhp)
+{
+ int res;
+
+ if (!vhp->vh_configured) {
+ vhp->vh_ctrl_sync = 0;
+
+ res = usbcam_choose_altsetting(vhp->vh_parent,
+ vhp->vh_iso_ifnum,
+ usb_rcvisocpipe(r5u870_dev(vhp),
+ vhp->vh_iso_ep),
+ vhp->vh_res->rw_reqbw,
+ vhp->vh_iso_minpacket, -1,
+ &vhp->vh_act_altsetting);
+ if (res) {
+ r5u_err(vhp, "need %d B/s, no altsetting provides",
+ vhp->vh_res->rw_reqbw);
+ return res;
+ }
+
+ r5u_dbg(vhp, R5U_FRAME, "using altsetting %d",
+ vhp->vh_act_altsetting);
+ }
+
+ res = usb_set_interface(r5u870_dev(vhp), vhp->vh_iso_ifnum,
+ vhp->vh_act_altsetting);
+ if (res) {
+ r5u_err(vhp, "could not set altsetting: %d", res);
+ return res;
+ }
+
+ return 0;
+}
+
+static int r5u870_usbcam_cap_start(struct usbcam_dev *udp)
+{
+ struct r5u870_ctx *vhp = udp_r5u870(udp);
+ int res;
+
+ if (vhp->vh_parent->ud_capturing)
+ return 0;
+
+ if (!vhp->vh_model->rm_uvc) {
+ res = r5u870_config_iso_ep(vhp);
+ if (res)
+ return res;
+ }
+
+ vhp->vh_ctrl_reg_enable = 1;
+
+ if (!vhp->vh_configured) {
+ r5u_dbg(vhp, R5U_MDINTF, "setting initial control values");
+ res = r5u870_set_controls(vhp, 0);
+ if (res)
+ goto out_set_idle;
+
+ res = vhp->vh_set_fmt(vhp, vhp->vh_fmt, vhp->vh_res);
+ if (res) {
+ r5u_err(vhp, "could not configure capture: %d", res);
+ goto out_set_idle;
+ }
+
+ if (vhp->vh_model->rm_no_ctrl_reload)
+ vhp->vh_ctrl_sync = 1;
+ }
+
+ if (vhp->vh_model->rm_uvc) {
+ res = r5u870_config_iso_ep(vhp);
+ if (res)
+ goto out_set_idle;
+ }
+
+ vhp->vh_configured = 1;
+
+ res = usbcam_urbstream_config_iso(&vhp->vh_iso,
+ &r5u870_urb_data_ops,
+ 0, 0, 1, 0);
+ if (res < 0) {
+ r5u_err(vhp, "urbstream init failed: %d", res);
+ goto out_set_idle;
+ }
+
+ udp->ud_capturing = 1;
+
+ if (!vhp->vh_model->rm_uvc) {
+ res = r5u870_set_cap_state_wdm(vhp, 1);
+ if (res)
+ goto out_cleanup_urbstream;
+ }
+
+ if (!vhp->vh_ctrl_sync) {
+ r5u_dbg(vhp, R5U_MDINTF, "reloading control values");
+
+ /* Reload the control values after changing res/format */
+ res = r5u870_set_controls(vhp, 0);
+ if (res) {
+ r5u_err(vhp, "could not load control values: %d", res);
+ goto out_stop_capture;
+ }
+
+ vhp->vh_ctrl_sync = 1;
+ }
+
+ if (res)
+ goto out_failed;
+
+ r5u_dbg(vhp, R5U_MDINTF, "starting capture");
+
+ vhp->vh_firstframe = 0;
+ vhp->vh_frame_accum = -1;
+ vhp->vh_framebuf_offset = 0;
+ vhp->vh_emptypkts = R5U870_EMPTYPKT_FRAME_DELIM - 1;
+
+ res = usbcam_urbstream_start(&vhp->vh_iso);
+ if (res)
+ goto out_stop_capture;
+
+ return 0;
+
+out_stop_capture:
+ (void) vhp->vh_cap_stop(vhp);
+out_cleanup_urbstream:
+ usbcam_urbstream_cleanup(&vhp->vh_iso);
+out_set_idle:
+ (void) usb_set_interface(r5u870_dev(vhp), vhp->vh_iso_ifnum, 0);
+out_failed:
+ vhp->vh_ctrl_reg_enable = 0;
+ vhp->vh_parent->ud_capturing = 0;
+ return res;
+}
+
+static struct usbcam_dev_ops r5u870_usbcam_dev_ops = {
+ .init = r5u870_usbcam_init,
+ .disconnect = r5u870_usbcam_disconnect,
+ .release = r5u870_usbcam_release,
+ .suspend = r5u870_usbcam_suspend,
+ .resume = r5u870_usbcam_resume,
+ .try_format = r5u870_usbcam_try_format,
+ .set_format = r5u870_usbcam_set_format,
+ .cap_start = r5u870_usbcam_cap_start,
+ .cap_stop = r5u870_usbcam_cap_stop,
+
+ /* .supports_autosuspend = 1, */
+};
+
+
+/*
+ * Per-device hard coded vendor control lists follow
+ */
+
+static const int r5u870_1830_ctrls[] = {
+ R5U870_WDM_CTRL_BRIGHTNESS,
+ R5U870_WDM_CTRL_CONTRAST,
+ R5U870_WDM_CTRL_SATURATION,
+ R5U870_WDM_CTRL_SHARPNESS,
+ R5U870_WDM_CTRL_HUE,
+ R5U870_WDM_CTRL_GAMMA,
+ R5U870_WDM_CTRL_BACKLIGHT_COMP_500,
+ R5U870_WDM_CTRL_WB_RED,
+ R5U870_WDM_CTRL_WB_GREEN,
+ R5U870_WDM_CTRL_WB_BLUE,
+ R5U870_WDM_CTRL_WB_AUTO,
+ R5U870_WDM_CTRL_GAIN,
+ R5U870_WDM_CTRL_POWERLINE,
+ R5U870_WDM_CTRL_VFLIP,
+ R5U870_WDM_CTRL_HFLIP,
+ R5U870_WDM_CTRL_PRIVACY,
+ R5U870_WDM_CTRL_NIGHTMODE,
+ R5U870_WDM_CTRL_LAST,
+};
+static const int r5u870_1832_ctrls[] = {
+ R5U870_WDM_CTRL_BRIGHTNESS,
+ R5U870_WDM_CTRL_CONTRAST,
+ R5U870_WDM_CTRL_HUE,
+ R5U870_WDM_CTRL_SATURATION,
+ R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1,
+ R5U870_WDM_CTRL_POWERLINE,
+ R5U870_WDM_CTRL_VFLIP,
+ R5U870_WDM_CTRL_HFLIP,
+ R5U870_WDM_CTRL_PRIVACY,
+ R5U870_WDM_CTRL_NIGHTMODE,
+ R5U870_WDM_CTRL_LAST,
+};
+static const int r5u870_1833_ctrls[] = {
+ R5U870_WDM_CTRL_BRIGHTNESS,
+ R5U870_WDM_CTRL_CONTRAST,
+ R5U870_WDM_CTRL_HUE,
+ R5U870_WDM_CTRL_SATURATION,
+ R5U870_WDM_CTRL_SHARPNESS,
+ R5U870_WDM_CTRL_GAMMA,
+ R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1,
+ R5U870_WDM_CTRL_WB_RED,
+ R5U870_WDM_CTRL_WB_GREEN,
+ R5U870_WDM_CTRL_WB_BLUE,
+ R5U870_WDM_CTRL_WB_AUTO,
+ R5U870_WDM_CTRL_POWERLINE,
+ R5U870_WDM_CTRL_VFLIP,
+ R5U870_WDM_CTRL_HFLIP,
+ R5U870_WDM_CTRL_PRIVACY,
+ R5U870_WDM_CTRL_NIGHTMODE,
+ R5U870_WDM_CTRL_LAST,
+};
+static const int r5u870_1834_ctrls[] = {
+ R5U870_WDM_CTRL_BRIGHTNESS,
+ R5U870_WDM_CTRL_CONTRAST,
+ R5U870_WDM_CTRL_HUE,
+ R5U870_WDM_CTRL_SATURATION,
+ R5U870_WDM_CTRL_SHARPNESS,
+ R5U870_WDM_CTRL_GAMMA,
+ R5U870_WDM_CTRL_BACKLIGHT_COMP_X1834,
+ R5U870_WDM_CTRL_WB_AUTO,
+ R5U870_WDM_CTRL_WB_RED,
+ R5U870_WDM_CTRL_WB_BLUE,
+ R5U870_WDM_CTRL_AUTO_EXPOSURE,
+ R5U870_WDM_CTRL_EXPOSURE,
+ R5U870_WDM_CTRL_AUTO_GAIN,
+ R5U870_WDM_CTRL_GAIN,
+ R5U870_WDM_CTRL_POWERLINE,
+ R5U870_WDM_CTRL_VFLIP,
+ R5U870_WDM_CTRL_HFLIP,
+ R5U870_WDM_CTRL_PRIVACY,
+ R5U870_WDM_CTRL_NIGHTMODE,
+ R5U870_WDM_CTRL_LAST,
+};
+static const int r5u870_1870_ctrls[] = {
+ R5U870_WDM_CTRL_BRIGHTNESS,
+ R5U870_WDM_CTRL_CONTRAST,
+ R5U870_WDM_CTRL_HUE,
+ R5U870_WDM_CTRL_SATURATION,
+ R5U870_WDM_CTRL_SHARPNESS,
+ R5U870_WDM_CTRL_GAMMA,
+ R5U870_WDM_CTRL_WB_AUTO,
+ R5U870_WDM_CTRL_WB_RED,
+ R5U870_WDM_CTRL_WB_BLUE,
+ R5U870_WDM_CTRL_AUTO_EXPOSURE,
+ R5U870_WDM_CTRL_EXPOSURE,
+ R5U870_WDM_CTRL_AUTO_GAIN,
+ R5U870_WDM_CTRL_GAIN,
+ R5U870_WDM_CTRL_POWERLINE,
+ R5U870_WDM_CTRL_VFLIP,
+ R5U870_WDM_CTRL_HFLIP,
+ R5U870_WDM_CTRL_PRIVACY,
+ R5U870_WDM_CTRL_NIGHTMODE,
+ R5U870_WDM_CTRL_LAST,
+};
+
+/*
+ * Even the UVC models do not express all of their controls in the UVC
+ * descriptor tables, and get sets of hard-coded vendor controls
+ */
+static const int r5u870_1835_ctrls[] = {
+ R5U870_WDM_CTRL_WB_RED,
+ R5U870_WDM_CTRL_WB_GREEN,
+ R5U870_WDM_CTRL_WB_BLUE,
+ R5U870_WDM_CTRL_WB_AUTO,
+ R5U870_WDM_CTRL_VFLIP,
+ R5U870_WDM_CTRL_HFLIP,
+ R5U870_WDM_CTRL_PRIVACY,
+ R5U870_WDM_CTRL_LAST,
+};
+static const int r5u870_1810_1836_ctrls[] = {
+ R5U870_WDM_CTRL_WB_AUTO,
+ R5U870_WDM_CTRL_WB_RED,
+ R5U870_WDM_CTRL_WB_BLUE,
+ R5U870_WDM_CTRL_AUTO_EXPOSURE,
+ R5U870_WDM_CTRL_EXPOSURE,
+ R5U870_WDM_CTRL_AUTO_GAIN,
+ R5U870_WDM_CTRL_GAIN,
+ R5U870_WDM_CTRL_VFLIP,
+ R5U870_WDM_CTRL_HFLIP,
+ R5U870_WDM_CTRL_PRIVACY,
+ R5U870_WDM_CTRL_NIGHTMODE,
+ R5U870_WDM_CTRL_LAST,
+};
+static const int r5u870_1810_183a_ctrls[] = {
+ R5U870_WDM_CTRL_WB_RED,
+ R5U870_WDM_CTRL_WB_GREEN,
+ R5U870_WDM_CTRL_WB_BLUE,
+ R5U870_WDM_CTRL_WB_AUTO,
+ R5U870_WDM_CTRL_VFLIP,
+ R5U870_WDM_CTRL_HFLIP,
+ R5U870_WDM_CTRL_PRIVACY,
+ R5U870_WDM_CTRL_NIGHTMODE,
+ R5U870_WDM_CTRL_LAST,
+};
+
+
+/*
+ * Standard resolution table for non-UVC cameras,
+ * as UVC camera report back as to what resolutions
+ * they support.
+ * The Sony VGP-VCC2 Windows driver supports:
+ * 160x120, 176x144, 320x240, 352x288, 640x480
+ * The HP driver also supports 1280x1024
+ */
+static const struct r5u870_resolution r5u870_vga_wdm_res[] = {
+ { 160, 120, 1152000 },
+ { 176, 144, 1520640 },
+ { 320, 240, 4608000 },
+ { 352, 288, 6082560 },
+ { 640, 480, 18432000 },
+ { }
+};
+static const struct r5u870_resolution r5u870_sxga_wdm_res[] = {
+ { 160, 120, 1152000 },
+ { 176, 144, 1520640 },
+ { 320, 240, 4608000 },
+ { 352, 288, 6082560 },
+ { 640, 480, 18432000 },
+ { 1280, 1024, 19660800 },
+ { }
+};
+static struct r5u870_pix_fmt r5u870_vga_wdm_pixfmts[] = {
+ { .base = { .description = "YUY2 4:2:2",
+ .pixelformat = V4L2_PIX_FMT_YUYV,
+ .flags = 0 },
+ .rp_formatidx = 0,
+ .rp_restbl = r5u870_vga_wdm_res },
+
+ { .base = { .description = "UYVY 4:2:2",
+ .pixelformat = V4L2_PIX_FMT_UYVY,
+ .flags = 0 },
+ .rp_formatidx = 1,
+ .rp_restbl = r5u870_vga_wdm_res },
+};
+static struct r5u870_pix_fmt r5u870_sxga_wdm_pixfmts[] = {
+ { .base = { .description = "YUY2 4:2:2",
+ .pixelformat = V4L2_PIX_FMT_YUYV,
+ .flags = 0 },
+ .rp_formatidx = 0,
+ .rp_restbl = r5u870_sxga_wdm_res },
+
+ { .base = { .description = "UYVY 4:2:2",
+ .pixelformat = V4L2_PIX_FMT_UYVY,
+ .flags = 0 },
+ .rp_formatidx = 1,
+ .rp_restbl = r5u870_sxga_wdm_res },
+};
+
+enum {
+ R5U870_DI_INVALID,
+ R5U870_DI_VGP_VCC2_SZ,
+ R5U870_DI_VGP_VCC3,
+ R5U870_DI_VGP_VCC2_AR1,
+ R5U870_DI_VGP_VCC2_AR2,
+ R5U870_DI_VGP_VCC5,
+ R5U870_DI_VGP_VCC4,
+ R5U870_DI_VGP_VCC7,
+ R5U870_DI_HP_WEBCAM1K,
+ R5U870_DI_HP_PAVWC_WDM,
+ R5U870_DI_HP_PAVWC_UVC,
+ R5U870_DI_GENERIC_UVC,
+};
+
+static const struct r5u870_model r5u870_models[] = {
+ [R5U870_DI_VGP_VCC2_SZ] = {
+ .rm_name = "Sony VGP-VCC2 (VAIO SZ)",
+ .rm_ucode_file = "r5u870_1830.fw",
+ .rm_ucode_version = 0x0100,
+ .rm_wdm_ctrlids = r5u870_1830_ctrls,
+ .rm_pixfmts = r5u870_vga_wdm_pixfmts,
+ .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts),
+ },
+ [R5U870_DI_VGP_VCC3] = {
+ .rm_name = "Sony VGP-VCC3",
+ .rm_ucode_file = "r5u870_1832.fw",
+ .rm_ucode_version = 0x0100,
+ .rm_wdm_ctrlids = r5u870_1832_ctrls,
+ .rm_pixfmts = r5u870_vga_wdm_pixfmts,
+ .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts),
+ .rm_no_ctrl_reload = 1,
+ },
+ [R5U870_DI_VGP_VCC2_AR1] = {
+ .rm_name = "Sony VGP-VCC2 (VAIO AR1)",
+ .rm_ucode_file = "r5u870_1833.fw",
+ .rm_ucode_version = 0x0100,
+ .rm_wdm_ctrlids = r5u870_1833_ctrls,
+ .rm_pixfmts = r5u870_vga_wdm_pixfmts,
+ .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts),
+ },
+ [R5U870_DI_VGP_VCC2_AR2] = {
+ .rm_name = "Sony VGP-VCC2 (VAIO AR)",
+ .rm_ucode_file = "r5u870_1834.fw",
+ .rm_ucode_version = 0x0111,
+ .rm_wdm_ctrlids = r5u870_1834_ctrls,
+ .rm_pixfmts = r5u870_vga_wdm_pixfmts,
+ .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts),
+ },
+ [R5U870_DI_VGP_VCC5] = {
+ .rm_name = "Sony VGP-VCC5",
+ .rm_ucode_file = "r5u870_1835.fw",
+ .rm_ucode_version = 0x0107,
+ .rm_wdm_ctrlids = r5u870_1835_ctrls,
+ .rm_uvc = 1,
+ },
+ [R5U870_DI_VGP_VCC4] = {
+ .rm_name = "Sony VGP-VCC4",
+ .rm_ucode_file = "r5u870_1836.fw",
+ .rm_ucode_version = 0x0115,
+ .rm_wdm_ctrlids = r5u870_1810_1836_ctrls,
+ .rm_uvc = 1,
+ },
+ [R5U870_DI_VGP_VCC7] = {
+ .rm_name = "Sony VGP-VCC7 (VAIO SZ)",
+ .rm_ucode_file = "r5u870_183a.fw",
+ .rm_ucode_version = 0x0111,
+ .rm_wdm_ctrlids = r5u870_1810_183a_ctrls,
+ .rm_uvc = 1,
+ },
+ [R5U870_DI_HP_WEBCAM1K] = {
+ .rm_name = "HP Webcam 1000",
+ .rm_ucode_file = "r5u870_1870_1.fw",
+ .rm_ucode_version = 0x0100,
+ .rm_wdm_ctrlids = r5u870_1870_ctrls,
+ .rm_pixfmts = r5u870_vga_wdm_pixfmts,
+ .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts),
+ },
+ [R5U870_DI_HP_PAVWC_WDM] = {
+ .rm_name = "HP Pavilion Webcam",
+ .rm_ucode_file = "r5u870_1870.fw",
+ .rm_ucode_version = 0x0112,
+ .rm_wdm_ctrlids = r5u870_1870_ctrls,
+ .rm_pixfmts = r5u870_sxga_wdm_pixfmts,
+ .rm_npixfmts = ARRAY_SIZE(r5u870_sxga_wdm_pixfmts),
+ .rm_no_ctrl_reload = 1,
+ },
+ [R5U870_DI_HP_PAVWC_UVC] = {
+ .rm_name = "HP Pavilion Webcam",
+ .rm_ucode_file = "r5u870_1810.fw",
+ .rm_ucode_version = 0x0115,
+ .rm_wdm_ctrlids = r5u870_1810_1836_ctrls,
+ .rm_uvc = 1,
+ .rm_no_ctrl_reload = 1,
+ },
+ [R5U870_DI_GENERIC_UVC] = {
+ .rm_name = "Generic UVC Webcam",
+ .rm_uvc = 1,
+ },
+};
+
+/*
+ * Someone clever at HP decid ed to use 05ca:1870 for two distinct devices.
+ * The Pavilion dv1xxx machines all seem to have the less common of the
+ * two. There is no known, working method to distinguish the devices
+ * using USB commands only. We resort to reading the model number out
+ * of DMI.
+ */
+static int dv1000 = 2;
+module_param(dv1000, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(dv1000, "HP dv1000 detect mode (0=no,1=yes,2=DMI)");
+
+static int r5u870_check_hp_dv1000(void)
+{
+ const char *prod_name;
+ if (!dv1000)
+ return 0;
+ if (dv1000 == 1)
+ return 1;
+ prod_name = dmi_get_system_info(DMI_PRODUCT_NAME);
+ if (!prod_name)
+ printk(KERN_INFO "r5u870: No DMI model found\n");
+ else {
+ printk(KERN_INFO "r5u870: Found DMI model: \"%s\"\n",
+ prod_name);
+ if (!strncmp(prod_name, "HP Pavilion dv1000", 18) &&
+ !isdigit(prod_name[18]))
+ return 1;
+ }
+ return 0;
+}
+
+static const struct r5u870_model *r5u870_find_model(int driver_info)
+{
+ if (driver_info == R5U870_DI_HP_PAVWC_WDM) {
+ if (r5u870_check_hp_dv1000())
+ driver_info = R5U870_DI_HP_WEBCAM1K;
+ }
+ if ((driver_info <= R5U870_DI_INVALID) ||
+ (driver_info >= ARRAY_SIZE(r5u870_models)))
+ return NULL;
+ if (!r5u870_models[driver_info].rm_name)
+ return NULL;
+ return &r5u870_models[driver_info];
+}
+
+
+#define R5U870_DEVICE_UVC(VID, PID, DINFO) \
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE \
+ | USB_DEVICE_ID_MATCH_INT_INFO, \
+ .idVendor = (VID), \
+ .idProduct = (PID), \
+ .bInterfaceClass = USB_CLASS_VIDEO, \
+ .bInterfaceSubClass = 1, \
+ .bInterfaceProtocol = 0, \
+ .driver_info = (DINFO)
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x05CA, 0x1830), .driver_info = R5U870_DI_VGP_VCC2_SZ },
+ { USB_DEVICE(0x05CA, 0x1832), .driver_info = R5U870_DI_VGP_VCC3 },
+ { USB_DEVICE(0x05CA, 0x1833), .driver_info = R5U870_DI_VGP_VCC2_AR1 },
+ { USB_DEVICE(0x05CA, 0x1834), .driver_info = R5U870_DI_VGP_VCC2_AR2 },
+ { USB_DEVICE(0x05CA, 0x1870), .driver_info = R5U870_DI_HP_PAVWC_WDM },
+
+ { R5U870_DEVICE_UVC(0x05CA, 0x1810, R5U870_DI_HP_PAVWC_UVC) },
+ { R5U870_DEVICE_UVC(0x05CA, 0x1835, R5U870_DI_VGP_VCC5) },
+ { R5U870_DEVICE_UVC(0x05CA, 0x1836, R5U870_DI_VGP_VCC4) },
+ { R5U870_DEVICE_UVC(0x05CA, 0x183a, R5U870_DI_VGP_VCC7) },
+ { },
+};
+
+
+DEFINE_USBCAM_MINIDRV_MODULE(R5U870_VERSION, R5U870_VERSION_EXTRA,
+ &r5u870_usbcam_dev_ops,
+ sizeof(struct r5u870_ctx),
+ id_table)
+
+MODULE_DEVICE_TABLE(usb, id_table);
+MODULE_DESCRIPTION("Driver for Ricoh R5U870-based Webcams");
+MODULE_AUTHOR("Sam Revitch <samr7@cs.washington.edu>");
+MODULE_LICENSE("GPL");
diff --git a/r5u870_md.c b/r5u870_md.c
@@ -1,3061 +0,0 @@
-/*
- * Driver for Ricoh R5U870-based Custom OEM Webcams
- * Copyright (C) 2007 Sam Revitch <samr7@cs.washington.edu>
- * Copyright (c) 2008 Alexander Hixon <hixon.alexander@mediati.org>
- *
- * Check out README for additional credits.
- *
- * This driver is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This driver is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this driver; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-/*
- * This driver supports certain custom OEM webcams based on the Ricoh
- * R5U870 / Micro Vision M25U870 controller chip. These tend to be
- * built in to certain models of laptop computers and claim USB vendor
- * ID 0x05CA (Ricoh).
- *
- * All Ricoh webcams support common microcode upload, reset, and query
- * version commands.
- *
- * Each distinct piece of hardware requires a different microcode blob,
- * which is uploaded to the device the first time the device is probed
- * after power-on. The microcode blob is tailored to the attached
- * image sensor and the desired type of USB interface. Some microcode
- * blobs are created to work with the Ricoh proprietary USB interface.
- * Others behave as UVC devices, but use the common Ricoh vendor
- * commands to set certain controls. Some devices, in particular the
- * 1810 and 1870 HP Pavilion Webcam devices, appear to be identical in
- * hardware, and differ only by USB descriptor tables, and the
- * microcode blobs implement the different USB personalities.
- *
- * This driver supports basic UVC descriptor table parsing to determine
- * available controls and resolutions, and enough UVC commands to
- * support the UVC interface style of the Ricoh webcams.
- */
-
-#define CONFIG_USBCAM_DEBUG
-#define USBCAM_DEBUG_DEFAULT 0
-
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/usb.h>
-#include <linux/firmware.h>
-#include <linux/dmi.h>
-#include <linux/ctype.h>
-#include "usbcam.h"
-
-
-#define USBCAM_DBG_R5U_INIT (USBCAM_DBGBIT_MD_START + 0)
-#define USBCAM_DBG_R5U_FRAME (USBCAM_DBGBIT_MD_START + 1)
-#define USBCAM_DBG_R5U_MDINTF (USBCAM_DBGBIT_MD_START + 2)
-#define USBCAM_DBG_R5U_CTRL (USBCAM_DBGBIT_MD_START + 3)
-
-
-#define r5u_info(RV, FMT...) usbcam_info((RV)->vh_parent, FMT)
-#define r5u_err(RV, FMT...) usbcam_err((RV)->vh_parent, FMT)
-#define r5u_dbg(RV, SUBSYS, FMT...) usbcam_dbg((RV)->vh_parent, SUBSYS, FMT)
-
-#define R5U870_VERSION KERNEL_VERSION(0,10,1)
-#define R5U870_VERSION_EXTRA "halex"
-
-
-struct r5u870_resolution {
- int rw_width;
- int rw_height;
- int rw_reqbw;
- int rw_interval;
- int rw_frameidx;
-};
-
-struct r5u870_pix_fmt {
- struct usbcam_pix_fmt base;
- int rp_formatidx;
- const struct r5u870_resolution *rp_restbl;
- int rp_restbl_alloc;
-};
-
-struct r5u870_ctrl {
- struct usbcam_ctrl base;
- int reg;
- u8 unit;
- u8 info;
- u8 size;
- u8 is_auto;
- int val_offset;
- int auto_offset;
-};
-
-struct r5u870_model {
- char *rm_name;
- const char *rm_ucode_file;
- const struct r5u870_resolution *rm_res;
- const int *rm_wdm_ctrlids;
- const struct r5u870_pix_fmt *rm_pixfmts;
- int rm_npixfmts;
- u16 rm_ucode_version;
- unsigned int rm_uvc : 1,
- rm_no_ctrl_reload : 1;
-};
-
-struct r5u870_test {
- struct completion rt_completion;
- int rt_status;
-};
-
-struct r5u870_ctx {
- struct usbcam_dev *vh_parent;
- struct usbcam_isostream vh_iso;
-
- const struct r5u870_model *vh_model;
- const struct r5u870_pix_fmt *vh_pixfmts;
- int vh_npixfmts;
- const struct r5u870_ctrl *vh_ctrls;
- int vh_nctrls;
-
- int vh_dyn_pixfmts : 1,
- vh_dyn_ctrls : 1,
- vh_configured : 1,
- vh_ctrl_reg_enable : 1,
- vh_ctrl_sync : 1;
-
- const struct r5u870_pix_fmt *vh_fmt;
- const struct r5u870_resolution *vh_res;
-
- int vh_timeout;
- int vh_firstframe;
- int vh_emptypkts;
- int vh_frame_accum;
-
- int vh_framebuf_offset;
-
- int vh_ctrl_ifnum;
- int vh_iso_ep;
- int vh_iso_ifnum;
- int vh_iso_minpacket;
- int vh_act_altsetting;
-
- struct r5u870_test *vh_test_info;
-
- int (*vh_set_fmt)(struct r5u870_ctx *,
- const struct r5u870_pix_fmt *fmtp,
- const struct r5u870_resolution *resp);
- int (*vh_cap_stop)(struct r5u870_ctx *);
- int (*vh_decide_pkt)(struct r5u870_ctx *, int st, int len,
- const u8 *pktdata, int *start);
-
- /* Settings */
- int vh_brightness;
- int vh_contrast;
- int vh_hue;
- int vh_saturation;
- int vh_sharpness;
- int vh_gamma;
- int vh_backlight_comp;
- int vh_frame_rate;
- int vh_auto_wb;
- int vh_wb_red;
- int vh_wb_green;
- int vh_wb_blue;
- int vh_exposure;
- int vh_aec;
- int vh_gain;
- int vh_agc;
- int vh_powerline;
- int vh_vflip;
- int vh_hflip;
- int vh_privacy;
- int vh_nightmode;
-};
-
-#define udp_r5u870(UDP) ((struct r5u870_ctx *)((UDP)->ud_minidrv_data))
-#define r5u870_dev(RV) ((RV)->vh_parent->ud_dev)
-
-
-/*
- * Controls
- *
- * For this driver, there is only one get function, which retrieves the
- * value for the control stored in the r5u870_ctx structure. Each style
- * of control -- WDM/UVC -- have separate set functions.
- *
- * There are query functions for setting the disabled flag on manual
- * white balance controls when auto white balance is enabled.
- */
-
-enum {
- V4L2_CID_R5U870_SHARPNESS = (V4L2_CID_PRIVATE_BASE + 0),
- V4L2_CID_R5U870_GREEN_BALANCE,
- V4L2_CID_R5U870_AUTOEXPOSURE,
- V4L2_CID_R5U870_POWERLINE,
- V4L2_CID_R5U870_BACKLIGHT,
- V4L2_CID_R5U870_PRIVACY,
- V4L2_CID_R5U870_NIGHT_MODE,
-};
-
-static int r5u870_get_ctrl(struct usbcam_dev *udp,
- const struct usbcam_ctrl *basep,
- struct v4l2_control *c)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- struct r5u870_ctrl *ctlp = container_of(basep, struct r5u870_ctrl,
- base);
-
- c->value = *(int *) (((char *) vhp) + ctlp->val_offset);
- return 0;
-}
-
-static int r5u870_query_ctrl(struct usbcam_dev *udp,
- const struct usbcam_ctrl *basep,
- struct v4l2_queryctrl *c)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- struct r5u870_ctrl *ctlp = container_of(basep, struct r5u870_ctrl,
- base);
- int auto_ctrl = 0;
-
- if (ctlp->auto_offset) {
- auto_ctrl = *(int *) (((char *) vhp) + ctlp->auto_offset);
- if (auto_ctrl)
- c->flags |= V4L2_CTRL_FLAG_INACTIVE;
- }
- return 0;
-}
-
-static int r5u870_set_controls(struct r5u870_ctx *vhp, int dflt)
-{
- const struct r5u870_ctrl *ctrlp;
- struct v4l2_control cv;
- int i, res;
-
- res = 0;
- ctrlp = vhp->vh_ctrls;
- for (i = 0; i < vhp->vh_nctrls; i++) {
- cv.id = ctrlp[i].base.ctrl.id;
-
- if (dflt)
- cv.value = ctrlp[i].base.ctrl.default_value;
- else
- cv.value = *(int *) (((char *) vhp) +
- ctrlp[i].val_offset);
-
- res = ctrlp[i].base.set_fn(vhp->vh_parent,
- &ctrlp[i].base,
- &cv);
- if (res)
- break;
- }
-
- return res;
-}
-
-
-/*
- * Microcode management and device initialization functions follow
- */
-
-static int r5u870_set_gen_reg(struct r5u870_ctx *vhp,
- int cmd, int reg, int val)
-{
- int res;
- res = usb_control_msg(r5u870_dev(vhp),
- usb_sndctrlpipe(r5u870_dev(vhp), 0),
- cmd,
- USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- val,
- reg,
- NULL, 0,
- vhp->vh_timeout);
- if (res < 0) {
- r5u_err(vhp, "set_gen_reg %04x/%04x/%04x failed: %d",
- cmd, reg, val, res);
- return res;
- }
- return 0;
-}
-
-/*
- * Send the power-up init sequence, in case it is needed.
- */
-static int r5u870_microcode_upload(struct r5u870_ctx *vhp)
-{
- const struct firmware *fws;
- char *pgbuf;
- const u8 *dptr;
- int tolerance = 3;
- int i, rem, addr, len, res = 0;
-
- pgbuf = (char *) __get_free_page(GFP_KERNEL);
- if (!pgbuf)
- return -ENOMEM;
-
- r5u_dbg(vhp, R5U_INIT, "loading microcode file \"%s\"",
- vhp->vh_model->rm_ucode_file);
-
- res = request_firmware(&fws,
- vhp->vh_model->rm_ucode_file,
- &vhp->vh_parent->ud_dev->dev);
-
- if (res) {
- r5u_err(vhp, "Microcode file \"%s\" is missing",
- vhp->vh_model->rm_ucode_file);
- free_page((unsigned long)pgbuf);
- return res;
- }
-
- i = 0;
- dptr = fws->data;
- rem = fws->size;
- while (rem) {
- if (rem < 3) {
- r5u_err(vhp, "Microcode file msg %d is incomplete", i);
- res = -EINVAL;
- break;
- }
-
- len = dptr[0];
- addr = dptr[1] | (dptr[2] << 8);
- dptr += 3;
- rem -= 3;
-
- if ((rem < len) || (len > 64)) {
- r5u_err(vhp, "Microcode file msg %d has bad length %d",
- i, len);
- res = -EINVAL;
- break;
- }
-
- /*
- * The USB stack has issues with the initseq data if
- * initseq points into the vmalloc arena. This is
- * the case for microcode embedded in a module, or
- * data loaded by request_firmware().
- *
- * As a workaround, we memcpy() into a kmalloc page.
- */
- memcpy(pgbuf, dptr, len);
- dptr += len;
- rem -= len;
-
- retry:
- res = usb_control_msg(r5u870_dev(vhp),
- usb_sndctrlpipe(r5u870_dev(vhp), 0),
- 0xa0,
- USB_DIR_OUT | USB_TYPE_VENDOR |
- USB_RECIP_DEVICE,
- addr, 0, pgbuf, len, vhp->vh_timeout);
-
- if (res < 0) {
- if (tolerance--)
- goto retry;
- r5u_err(vhp, "command a0[%d] failed: %d",
- i, res);
- break;
- }
- if (res != len) {
- r5u_err(vhp, "command a0[%d] failed: %d (exp %d)",
- i, res, len);
- res = -EIO;
- break;
- }
-
- i++;
- }
-
- release_firmware(fws);
- free_page((unsigned long) pgbuf);
- return res;
-}
-
-static int r5u870_microcode_get_state(struct r5u870_ctx *vhp)
-{
- char buf[1];
- int res;
- r5u_dbg(vhp, R5U_INIT, "requesting microcode state");
- res = usb_control_msg(r5u870_dev(vhp),
- usb_rcvctrlpipe(r5u870_dev(vhp), 0),
- 0xa4,
- USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- 0,
- 0,
- buf, 1,
- vhp->vh_timeout);
- if ((res != 1) || ((buf[0] != 0) && (buf[0] != 1))) {
- r5u_err(vhp, "command 0xa4 failed: %d", res);
- return res < 0 ? res : -EIO;
- }
-
- r5u_dbg(vhp, R5U_INIT, "camera reports %s microcode state",
- buf[0] ? "positive" : "negative");
-
- return (buf[0] == 0) ? -ENOENT : 0;
-}
-
-static int r5u870_microcode_get_ver(struct r5u870_ctx *vhp, int *verp)
-{
- char buf[2];
- int res;
-
- r5u_dbg(vhp, R5U_INIT, "requesting microcode version");
- res = usb_control_msg(r5u870_dev(vhp),
- usb_rcvctrlpipe(r5u870_dev(vhp), 0),
- 0xc3,
- USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- 0,
- 0x0e,
- buf, 2,
- vhp->vh_timeout);
- if (res != 2) {
- r5u_err(vhp, "command 0xa3 failed: %d", res);
- return res < 0 ? res : -EIO;
- }
-
- res = le16_to_cpup((__le16 *) buf);
- r5u_dbg(vhp, R5U_INIT, "camera reports version %04x", res);
- *verp = res;
- return 0;
-}
-
-static int r5u870_microcode_enable(struct r5u870_ctx *vhp)
-{
- char buf[1];
- int res;
-
- r5u_dbg(vhp, R5U_INIT, "enabling microcode");
- buf[0] = 1;
- res = usb_control_msg(r5u870_dev(vhp),
- usb_sndctrlpipe(r5u870_dev(vhp), 0),
- 0xa1,
- USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- 0,
- 0,
- buf, 1,
- vhp->vh_timeout);
- if (res != 1) {
- r5u_err(vhp, "command 0xa1 failed: %d", res);
- return res < 0 ? res : -EIO;
- }
-
- return 0;
-}
-
-static int r5u870_microcode_reset(struct r5u870_ctx *vhp)
-{
- int res;
- r5u_dbg(vhp, R5U_INIT, "sending microcode reset command");
- msleep(100); /* The Windows driver waits 1sec */
- res = r5u870_set_gen_reg(vhp, 0xa6, 0, 0);
- if (!res)
- msleep(200);
- return res;
-}
-
-static int __attribute__((unused)) r5u870_dev_test_234(struct r5u870_ctx *vhp)
-{
- char buf[10];
- int res, tries = 0;
-
- while (1) {
- buf[0] = 2;
- buf[1] = 3;
- buf[2] = 4;
- res = usb_control_msg(r5u870_dev(vhp),
- usb_sndctrlpipe(r5u870_dev(vhp), 0),
- 0xd0,
- USB_DIR_OUT |USB_TYPE_VENDOR |
- USB_RECIP_DEVICE,
- 0,
- 0,
- buf, 3,
- vhp->vh_timeout);
- if ((res < 0) || (res != 3)) {
- r5u_err(vhp, "command d0[set] failed: %d", res);
- /* return res < 0 ? res : -EIO; */
- }
-
- buf[0] = 0;
- buf[1] = 0;
- buf[2] = 0;
- res = usb_control_msg(r5u870_dev(vhp),
- usb_rcvctrlpipe(r5u870_dev(vhp), 0),
- 0xd0,
- USB_DIR_IN | USB_TYPE_VENDOR |
- USB_RECIP_DEVICE,
- 0,
- 1,
- buf,
- sizeof(buf),
- vhp->vh_timeout);
- if ((res < 0) || (res != 3)) {
- r5u_err(vhp, "command d0[get] failed: %d", res);
- /* return res < 0 ? res : -EIO; */
- }
-
- if ((buf[0] == 2) && (buf[1] == 3) && (buf[2] == 4)) {
- break;
- }
-
- tries++;
- if (tries > 1) {
- r5u_err(vhp, "command d0: too many retries");
- /* return -EIO; */
- break;
- }
- }
-
- return 0;
-}
-
-/*
- * Initialize the camera after it is detected.
- */
-static int r5u870_dev_init(struct r5u870_ctx *vhp)
-{
- int mcver;
- int res;
-
- if (!vhp->vh_model->rm_ucode_file)
- return 0;
-
- res = r5u870_microcode_get_state(vhp);
- if (res && (res != -ENOENT))
- return res;
-
- if (!res) {
- res = r5u870_microcode_get_ver(vhp, &mcver);
- if (res)
- return res;
-
- if (mcver != vhp->vh_model->rm_ucode_version) {
- res = r5u870_microcode_reset(vhp);
- if (res)
- return res;
- res = -ENOENT;
- }
- }
-
-
- if (res == -ENOENT) {
- res = r5u870_microcode_upload(vhp);
- if (res < 0)
- return res;
-
- res = r5u870_microcode_enable(vhp);
- if (res)
- return res;
-
- res = r5u870_microcode_get_ver(vhp, &mcver);
- if (res)
- return res;
- }
-
- if (mcver != vhp->vh_model->rm_ucode_version)
- r5u_err(vhp, "Unexpected microcode version "
- "(exp:%04x got:%04x)",
- vhp->vh_model->rm_ucode_version, mcver);
-
- /* (void) r5u870_dev_test_234(vhp); */
-
- /* Halt capture in case it's running (broken driver?) */
- res = vhp->vh_cap_stop(vhp);
- if (res < 0)
- return res;
-
- return 0;
-}
-
-/*
- * WDM Device Registers are listed below.
- *
- * To set: use r5u870_set_reg_wdm().
- *
- * No information is given about how to retrieve values from these
- * registers.
- *
- * Note that some register IDs are overloaded. The Sony cam drivers
- * seem to use 0x36 for backlight compensation and 0x37 for gain,
- * whereas the HP drivers use 0x36 for color enable and 0x37 for frame
- * rate.
- */
-enum {
- /* Brightness: [0,127] D:63 */
- R5U870_REG_BRIGHTNESS = 0x02,
-
- /* Contrast: [0,127] D:63 */
- R5U870_REG_CONTRAST = 0x04,
-
- /* Hue: [-180,180] D:0 (16-bit 2's complement) */
- R5U870_REG_HUE = 0x34,
-
- /* Saturation: [0,127] D:63 */
- R5U870_REG_SATURATION = 0x05,
-
- /* Sharpness: [0,127] D:63 */
- R5U870_REG_SHARPNESS = 0x03,
-
- /* Gamma correction: [1,500] D:100 */
- R5U870_REG_GAMMA = 0x35,
-
- /* Registers with unknown usage */
- R5U870_REG_COLOR_ENABLE = 0x36,
-
- /* White balance: [0,127] D:63 */
- R5U870_REG_WHITE_BALANCE = 0x1,
-
- /* Frame Rate: D:30 */
- R5U870_REG_FRAME_RATE = 0x37,
-
- /* Registers with unknown usage */
- R5U870_REG_BRIGHTNESS_EX = 0x20,
- R5U870_REG_CONTRAST_EX = 0x21,
- R5U870_REG_HUE_EX = 0x22,
- R5U870_REG_SATURATION_EX = 0x23,
- R5U870_REG_SHARPNESS_EX = 0x24,
- R5U870_REG_GAMMA_EX = 0x25,
-
- /* White balance red value: [0,255] D:127 */
- R5U870_REG_WB_RED_EX = 0x26,
-
- /* White balance green value: [0,255] D:127 */
- R5U870_REG_WB_GREEN_EX = 0x27,
-
- /* White balance blue value: [0,255] D:127 */
- R5U870_REG_WB_BLUE_EX = 0x28,
-
- /* Auto white balance: [0,1] D:1 */
- R5U870_REG_WB_AUTO_EX = 0x29,
-
- /* Exposure Control: [0,255] D:255 */
- R5U870_REG_EXPOSURE_EX = 0x2a,
-
- /* Auto Exposure Control: [0,1] D:1 */
- R5U870_REG_AEC_EX = 0x2b,
-
- /* Gain: [0,127] D:63 */
- R5U870_REG_GAIN_EX = 0x2c,
-
- /* Auto Gain: [0,1] D:0 */
- R5U870_REG_AGC_EX = 0x2d,
-
- /* Light source flicker compensation: see R5U870_POWERLINE */
- R5U870_REG_POWERLINE_EX = 0x2e,
-
- /* Registers with unknown usage */
- R5U870_REG_SCENE_EX = 0x2f,
-
- /* Vertical flip: [0,1] D:0 */
- R5U870_REG_VFLIP_EX = 0x30,
-
- /* Horizontal flip: [0,1] D:0 */
- R5U870_REG_HFLIP_EX = 0x31,
-
- /* Blank image: [0,1] D:0 */
- R5U870_REG_PRIVACY_EX = 0x32,
-
- /* Night mode: [0,1] D:0 */
- R5U870_REG_NIGHT_MODE_EX = 0x33,
-
- /* Backlight compensation: [0,500] D:1 */
- R5U870_REG_BACKLIGHT_COMP = 0x36,
-
- /* Registers with unknown usage */
- R5U870_REG_GAIN = 0x37,
-
- /* Backlight compensation for 1834 device: [0,2] D:1 */
- R5U870_REG_BACKLIGHT_COMP_2 = 0x39,
-
-
- /* Values for R5U870_REG_POWERLINE flicker compensation */
- R5U870_POWERLINE_OFF = 0,
- R5U870_POWERLINE_50HZ = 1,
- R5U870_POWERLINE_60HZ = 2,
-
- /* Number of empty packets between frames */
- R5U870_EMPTYPKT_FRAME_DELIM = 10,
-
- /* Number of empty packets before declaring the device dead (.5sec) */
- R5U870_EMPTYPKT_GIVE_UP = 4000,
-};
-
-static int r5u870_set_reg_wdm(struct r5u870_ctx *vhp, int reg, int val)
-{
- return r5u870_set_gen_reg(vhp, 0xc2, reg, val);
-}
-
-/*
- * Set the frame size and data format.
- * Do not call this function with the isochronous stream active.
- */
-static int r5u870_set_fmt_wdm(struct r5u870_ctx *vhp,
- const struct r5u870_pix_fmt *fmtp,
- const struct r5u870_resolution *resp)
-{
- int res;
-
- msleep(1);
- res = r5u870_set_gen_reg(vhp, 0xc5, 2, fmtp->rp_formatidx);
- if (res)
- return res;
- msleep(1);
- res = r5u870_set_gen_reg(vhp, 0xc5, 0, resp->rw_width);
- if (res)
- return res;
- msleep(1);
- res = r5u870_set_gen_reg(vhp, 0xc5, 1, resp->rw_height);
- if (res)
- return res;
- msleep(5);
- return 0;
-}
-
-
-/*
- * Turn frame grabbing on or off (WDM).
- * This will also turn on or off the LED.
- */
-static int r5u870_set_cap_state_wdm(struct r5u870_ctx *vhp, int val)
-{
- return r5u870_set_gen_reg(vhp, 0xc4, val, 0);
-}
-
-static int r5u870_cap_stop_wdm(struct r5u870_ctx *vhp)
-{
- return r5u870_set_cap_state_wdm(vhp, 0);
-}
-
-/*
- * r5u870_decide_pkt_wdm
- *
- * Based on the size of an isochronous data packet, this function
- * decides whether to copy the packet into the frame buffer and possibly
- * complete the frame, or to discard both the packet and the frame.
- *
- * Returns:
- * 0 Frame is done
- * -EAGAIN Append packet to frame, frame is not done
- * -EPIPE Discard frame and packet
- * -EIO The device is nonresponsive, abort
- */
-static int r5u870_decide_pkt_wdm(struct r5u870_ctx *vhp, int pktstatus,
- int pktlen, const u8 *pktdata, int *start)
-{
- int ret = -EAGAIN;
-
- *start = 0;
-
- if (pktstatus) {
- /* Abort current frame */
- r5u_dbg(vhp, R5U_FRAME, "frame abort: packet status %d",
- pktstatus);
- vhp->vh_frame_accum = -1;
- vhp->vh_emptypkts = 0;
- ret = -EPIPE;
-
- } else if (!pktlen) {
- if (++vhp->vh_emptypkts == R5U870_EMPTYPKT_FRAME_DELIM) {
- if (vhp->vh_frame_accum == -1) {
- /* Frame was previously aborted */
- ret = -EPIPE;
- } else if (vhp->vh_frame_accum ==
- vhp->vh_parent->ud_format.sizeimage) {
- /* Complete frame */
- ret = 0;
- } else {
- /* Not enough data in frame sequence */
- r5u_dbg(vhp, R5U_FRAME, "frame abort: "
- "Frame seq too short (exp:%d got:%d)",
- vhp->vh_parent->ud_format.sizeimage,
- vhp->vh_frame_accum);
- ret = -EPIPE;
- }
-
- if (!vhp->vh_firstframe) {
- /* Always reject the first frame */
- vhp->vh_firstframe = 1;
- vhp->vh_frame_accum = -1;
- ret = -EPIPE;
- } else {
- vhp->vh_frame_accum = 0;
- }
- }
-
- else if (vhp->vh_emptypkts >= R5U870_EMPTYPKT_GIVE_UP) {
- r5u_dbg(vhp, R5U_FRAME, "%d empty packets, giving up",
- vhp->vh_emptypkts);
- ret = -EIO;
- }
-
- } else {
- vhp->vh_emptypkts = 0;
- if (vhp->vh_frame_accum == -1) {
- /* Frame was previously aborted */
- ret = -EPIPE;
- } else if ((vhp->vh_frame_accum + pktlen) <=
- vhp->vh_parent->ud_format.sizeimage) {
- /* Append this data */
- vhp->vh_frame_accum += pktlen;
- } else {
- /* Oversized frame, abort */
- r5u_dbg(vhp, R5U_FRAME, "frame abort: "
- "Frame seq too long");
- vhp->vh_frame_accum = -1;
- ret = -EPIPE;
- }
- }
-
- return ret;
-}
-
-static int r5u870_set_manual_ctrls_wdm(struct r5u870_ctx *vhp, int auto_offset)
-{
- const struct r5u870_ctrl *ctrlp;
- int i, val, res;
-
- res = 0;
- ctrlp = vhp->vh_ctrls;
- for (i = 0; i < vhp->vh_nctrls; i++) {
- if (ctrlp[i].auto_offset != auto_offset)
- continue;
- if (!vhp->vh_ctrl_reg_enable) {
- vhp->vh_ctrl_sync = 0;
- continue;
- }
-
- val = *(int *) (((char *) vhp) + ctrlp[i].val_offset);
-
- r5u_dbg(vhp, R5U_CTRL, "control %s/wdm %02x <= %d [manual]",
- ctrlp[i].base.ctrl.name, ctrlp[i].reg, val);
- res = r5u870_set_reg_wdm(vhp, ctrlp[i].reg, val);
- if (res)
- break;
- }
- return res;
-}
-
-static int r5u870_set_ctrl_wdm(struct usbcam_dev *udp,
- const struct usbcam_ctrl *basep,
- const struct v4l2_control *c)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- struct r5u870_ctrl *ctlp = container_of(basep, struct r5u870_ctrl,
- base);
- int res = 0;
- int auto_ctrl = 0;
-
- if (ctlp->auto_offset)
- auto_ctrl = *(int *) (((char *) vhp) + ctlp->auto_offset);
-
- if (auto_ctrl) {
- r5u_dbg(vhp, R5U_CTRL, "control %s <= %d [auto suppress]",
- ctlp->base.ctrl.name, c->value);
-
- } else if (!vhp->vh_ctrl_reg_enable) {
- r5u_dbg(vhp, R5U_CTRL, "control %s <= %d [capture off]",
- ctlp->base.ctrl.name, c->value);
- vhp->vh_ctrl_sync = 0;
-
- } else {
- r5u_dbg(vhp, R5U_CTRL, "control %s/wdm %02x <= %d",
- ctlp->base.ctrl.name, ctlp->reg, c->value);
- res = r5u870_set_reg_wdm(vhp, ctlp->reg, c->value);
- if (res)
- return res;
- }
-
- *(int *) (((char *) vhp) + ctlp->val_offset) = c->value;
-
- if (ctlp->is_auto && !c->value)
- res = r5u870_set_manual_ctrls_wdm(vhp, ctlp->val_offset);
-
- return res;
-}
-
-/*
- * WDM control templates follow
- *
- * Each device has an array of integer control IDs, which refer to an
- * element in the control template array. When the device is detected,
- * we build the control array out of the element list and the templates,
- * by r5u870_wdm_add_ctrls().
- */
-
-enum {
- R5U870_WDM_CTRL_BRIGHTNESS,
- R5U870_WDM_CTRL_CONTRAST,
- R5U870_WDM_CTRL_SATURATION,
- R5U870_WDM_CTRL_SHARPNESS,
- R5U870_WDM_CTRL_HUE,
- R5U870_WDM_CTRL_GAMMA,
- R5U870_WDM_CTRL_BACKLIGHT_COMP_500,
- R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1,
- R5U870_WDM_CTRL_BACKLIGHT_COMP_X1834,
- R5U870_WDM_CTRL_WB_RED,
- R5U870_WDM_CTRL_WB_GREEN,
- R5U870_WDM_CTRL_WB_BLUE,
- R5U870_WDM_CTRL_WB_AUTO,
- R5U870_WDM_CTRL_AUTO_EXPOSURE,
- R5U870_WDM_CTRL_EXPOSURE,
- R5U870_WDM_CTRL_AUTO_GAIN,
- R5U870_WDM_CTRL_GAIN,
- R5U870_WDM_CTRL_POWERLINE,
- R5U870_WDM_CTRL_VFLIP,
- R5U870_WDM_CTRL_HFLIP,
- R5U870_WDM_CTRL_PRIVACY,
- R5U870_WDM_CTRL_NIGHTMODE,
-
- R5U870_WDM_CTRL_LAST = 0xffff,
-};
-
-static const char *r5u870_powerline_names[] = { "Off", "50Hz", "60Hz" };
-
-static struct r5u870_ctrl r5u870_wdm_ctrls[] = {
-
- [R5U870_WDM_CTRL_BRIGHTNESS] = {
- .base = { .ctrl = { .id = V4L2_CID_BRIGHTNESS,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "Brightness",
- .minimum = 0,
- .maximum = 127,
- .step = 1,
- .default_value = 63,
- .flags = V4L2_CTRL_FLAG_SLIDER },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_BRIGHTNESS,
- .val_offset = offsetof(struct r5u870_ctx, vh_brightness)
- },
- [R5U870_WDM_CTRL_CONTRAST] = {
- .base = { .ctrl = { .id = V4L2_CID_CONTRAST,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "Contrast",
- .minimum = 0,
- .maximum = 127,
- .step = 1,
- .default_value = 63,
- .flags = V4L2_CTRL_FLAG_SLIDER },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_CONTRAST,
- .val_offset = offsetof(struct r5u870_ctx, vh_contrast)
- },
- [R5U870_WDM_CTRL_SATURATION] = {
- .base = { .ctrl = { .id = V4L2_CID_SATURATION,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "Saturation",
- .minimum = 0,
- .maximum = 127,
- .step = 1,
- .default_value = 63,
- .flags = V4L2_CTRL_FLAG_SLIDER },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_SATURATION,
- .val_offset = offsetof(struct r5u870_ctx, vh_saturation)
- },
-
- [R5U870_WDM_CTRL_SHARPNESS] = {
- .base = { .ctrl = { .id = V4L2_CID_R5U870_SHARPNESS,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "Sharpness",
- .minimum = 0,
- .maximum = 127,
- .step = 1,
- .default_value = 63,
- .flags = V4L2_CTRL_FLAG_SLIDER },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_SHARPNESS,
- .val_offset = offsetof(struct r5u870_ctx, vh_sharpness)
- },
- [R5U870_WDM_CTRL_HUE] = {
- .base = { .ctrl = { .id = V4L2_CID_HUE,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "Hue",
- .minimum = -180,
- .maximum = 180,
- .step = 1,
- .default_value = 0,
- .flags = V4L2_CTRL_FLAG_SLIDER },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_HUE,
- .val_offset = offsetof(struct r5u870_ctx, vh_hue)
- },
- [R5U870_WDM_CTRL_GAMMA] = {
- .base = { .ctrl = { .id = V4L2_CID_GAMMA,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "Gamma",
- .minimum = 0,
- .maximum = 500,
- .step = 1,
- .default_value = 100,
- .flags = V4L2_CTRL_FLAG_SLIDER },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_GAMMA,
- .val_offset = offsetof(struct r5u870_ctx, vh_gamma)
- },
- [R5U870_WDM_CTRL_BACKLIGHT_COMP_500] = {
- .base = { .ctrl = { .id = V4L2_CID_R5U870_BACKLIGHT,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "Backlight Compensation",
- .minimum = 0,
- .maximum = 500,
- .step = 1,
- .default_value = 250,
- .flags = V4L2_CTRL_FLAG_SLIDER },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_BACKLIGHT_COMP,
- .val_offset = offsetof(struct r5u870_ctx, vh_backlight_comp)
- },
- [R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1] = {
- .base = { .ctrl = { .id = V4L2_CID_R5U870_BACKLIGHT,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "Backlight Compensation",
- .minimum = 0,
- .maximum = 500,
- .step = 1,
- .default_value = 1,
- .flags = V4L2_CTRL_FLAG_SLIDER },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_BACKLIGHT_COMP,
- .val_offset = offsetof(struct r5u870_ctx, vh_backlight_comp)
- },
- [R5U870_WDM_CTRL_BACKLIGHT_COMP_X1834] = {
- .base = { .ctrl = { .id = V4L2_CID_R5U870_BACKLIGHT,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "Backlight Compensation",
- .minimum = 0,
- .maximum = 2,
- .step = 1,
- .default_value = 1,
- .flags = V4L2_CTRL_FLAG_SLIDER },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_BACKLIGHT_COMP_2,
- .val_offset = offsetof(struct r5u870_ctx, vh_backlight_comp)
- },
- [R5U870_WDM_CTRL_WB_RED] = {
- .base = { .ctrl = { .id = V4L2_CID_RED_BALANCE,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "White Balance Red",
- .minimum = 0,
- .maximum = 255,
- .step = 1,
- .default_value = 127,
- .flags = 0 },
- .query_fn = r5u870_query_ctrl,
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_WB_RED_EX,
- .val_offset = offsetof(struct r5u870_ctx, vh_wb_red),
- .auto_offset = offsetof(struct r5u870_ctx, vh_auto_wb)
- },
- [R5U870_WDM_CTRL_WB_GREEN] = {
- .base = { .ctrl = { .id = V4L2_CID_R5U870_GREEN_BALANCE,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "White Balance Green",
- .minimum = 0,
- .maximum = 255,
- .step = 1,
- .default_value = 127,
- .flags = 0 },
- .query_fn = r5u870_query_ctrl,
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_WB_GREEN_EX,
- .val_offset = offsetof(struct r5u870_ctx, vh_wb_green),
- .auto_offset = offsetof(struct r5u870_ctx, vh_auto_wb)
- },
- [R5U870_WDM_CTRL_WB_BLUE] = {
- .base = { .ctrl = { .id = V4L2_CID_BLUE_BALANCE,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "White Balance Blue",
- .minimum = 0,
- .maximum = 255,
- .step = 1,
- .default_value = 127,
- .flags = 0 },
- .query_fn = r5u870_query_ctrl,
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_WB_BLUE_EX,
- .val_offset = offsetof(struct r5u870_ctx, vh_wb_blue),
- .auto_offset = offsetof(struct r5u870_ctx, vh_auto_wb)
- },
- [R5U870_WDM_CTRL_WB_AUTO] = {
- .base = { .ctrl = { .id = V4L2_CID_AUTO_WHITE_BALANCE,
- .type = V4L2_CTRL_TYPE_BOOLEAN,
- .name = "Auto White Balance",
- .minimum = 0,
- .maximum = 1,
- .step = 1,
- .default_value = 1,
- .flags = V4L2_CTRL_FLAG_UPDATE },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_WB_AUTO_EX,
- .is_auto = 1,
- .val_offset = offsetof(struct r5u870_ctx, vh_auto_wb)
- },
- [R5U870_WDM_CTRL_AUTO_EXPOSURE] = {
- .base = { .ctrl = { .id = V4L2_CID_R5U870_AUTOEXPOSURE,
- .type = V4L2_CTRL_TYPE_BOOLEAN,
- .name = "Auto Exposure Control",
- .minimum = 0,
- .maximum = 1,
- .step = 1,
- .default_value = 1,
- .flags = 0 },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_AEC_EX,
- .is_auto = 1,
- .val_offset = offsetof(struct r5u870_ctx, vh_aec)
- },
- [R5U870_WDM_CTRL_EXPOSURE] = {
- .base = { .ctrl = { .id = V4L2_CID_EXPOSURE,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "Exposure",
- .minimum = 0,
- .maximum = 511,
- .step = 1,
- .default_value = 255,
- .flags = 0 },
- .query_fn = r5u870_query_ctrl,
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_EXPOSURE_EX,
- .val_offset = offsetof(struct r5u870_ctx, vh_exposure),
- .auto_offset = offsetof(struct r5u870_ctx, vh_aec)
- },
- [R5U870_WDM_CTRL_AUTO_GAIN] = {
- .base = { .ctrl = { .id = V4L2_CID_AUTOGAIN,
- .type = V4L2_CTRL_TYPE_BOOLEAN,
- .name = "Auto Gain Control",
- .minimum = 0,
- .maximum = 1,
- .step = 1,
- .default_value = 1,
- .flags = 0 },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_AGC_EX,
- .is_auto = 1,
- .val_offset = offsetof(struct r5u870_ctx, vh_agc)
- },
- [R5U870_WDM_CTRL_GAIN] = {
- .base = { .ctrl = { .id = V4L2_CID_GAIN,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "Gain",
- .minimum = 0,
- .maximum = 127,
- .step = 1,
- .default_value = 63,
- .flags = 0 },
- .query_fn = r5u870_query_ctrl,
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_GAIN_EX,
- .val_offset = offsetof(struct r5u870_ctx, vh_gain),
- .auto_offset = offsetof(struct r5u870_ctx, vh_aec)
- },
- [R5U870_WDM_CTRL_POWERLINE] = {
- .base = { .ctrl = { .id = V4L2_CID_R5U870_POWERLINE,
- .type = V4L2_CTRL_TYPE_MENU,
- .name = "Power Line Frequency",
- .minimum = 0,
- .maximum = 2,
- .step = 1,
- .default_value = 0,
- .flags = 0 },
- .menu_names = r5u870_powerline_names,
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_POWERLINE_EX,
- .val_offset = offsetof(struct r5u870_ctx, vh_powerline)
- },
- [R5U870_WDM_CTRL_VFLIP] = {
- .base = { .ctrl = { .id = V4L2_CID_VFLIP,
- .type = V4L2_CTRL_TYPE_BOOLEAN,
- .name = "V-Flip",
- .minimum = 0,
- .maximum = 1,
- .step = 1,
- .default_value = 0,
- .flags = 0 },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_VFLIP_EX,
- .val_offset = offsetof(struct r5u870_ctx, vh_vflip)
- },
- [R5U870_WDM_CTRL_HFLIP] = {
- .base = { .ctrl = { .id = V4L2_CID_HFLIP,
- .type = V4L2_CTRL_TYPE_BOOLEAN,
- .name = "H-Flip",
- .minimum = 0,
- .maximum = 1,
- .step = 1,
- .default_value = 0,
- .flags = 0 },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_HFLIP_EX,
- .val_offset = offsetof(struct r5u870_ctx, vh_hflip)
- },
- [R5U870_WDM_CTRL_PRIVACY] = {
- .base = { .ctrl = { .id = V4L2_CID_R5U870_PRIVACY,
- .type = V4L2_CTRL_TYPE_BOOLEAN,
- .name = "Privacy",
- .minimum = 0,
- .maximum = 1,
- .step = 1,
- .default_value = 0,
- .flags = 0 },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_PRIVACY_EX,
- .val_offset = offsetof(struct r5u870_ctx, vh_privacy)
- },
- [R5U870_WDM_CTRL_NIGHTMODE] = {
- .base = { .ctrl = { .id = V4L2_CID_R5U870_NIGHT_MODE,
- .type = V4L2_CTRL_TYPE_BOOLEAN,
- .name = "Night Mode",
- .minimum = 0,
- .maximum = 1,
- .step = 1,
- .default_value = 0,
- .flags = 0 },
- .get_fn = r5u870_get_ctrl,
- .set_fn = r5u870_set_ctrl_wdm },
- .reg = R5U870_REG_NIGHT_MODE_EX,
- .val_offset = offsetof(struct r5u870_ctx, vh_nightmode)
- },
-};
-
-static int r5u870_wdm_add_ctrls(struct r5u870_ctx *vhp, const int *ctrlarray)
-{
- struct r5u870_ctrl *ncp;
- int curctrl, i, nctrls;
-
- /* Count the number of controls */
- for (nctrls = 0;
- ctrlarray[nctrls] != R5U870_WDM_CTRL_LAST;
- nctrls++);
-
- if (!nctrls)
- return 0;
-
- /* Allocate a new control array */
- ncp = (struct r5u870_ctrl *) kmalloc((vhp->vh_nctrls + nctrls) *
- sizeof(*ncp),
- GFP_KERNEL);
- if (!ncp)
- return -ENOMEM;
-
- if (vhp->vh_nctrls)
- memcpy(ncp, vhp->vh_ctrls, vhp->vh_nctrls * sizeof(*ncp));
- memset(&ncp[vhp->vh_nctrls], 0, nctrls * sizeof(*ncp));
-
- curctrl = vhp->vh_nctrls;
- for (i = 0; i < nctrls; i++, curctrl++)
- ncp[curctrl] = r5u870_wdm_ctrls[ctrlarray[i]];
-
- if (vhp->vh_nctrls && vhp->vh_dyn_ctrls)
- kfree(vhp->vh_ctrls);
- vhp->vh_ctrls = ncp;
- vhp->vh_nctrls = curctrl;
- vhp->vh_dyn_ctrls = 1;
-
- r5u_dbg(vhp, R5U_CTRL, "Added %d WDM controls", nctrls);
-
- return 0;
-}
-
-
-
-/*
- * UVC related code follows
- */
-
-enum {
- UVC_SC_VIDEOCONTROL = 1,
- UVC_SC_VIDEOSTREAMING = 1,
-
- UVC_VC_HEADER = 1,
- UVC_VC_INPUT_TERMINAL = 2,
- UVC_VC_OUTPUT_TERMINAL = 3,
- UVC_VC_SELECTOR_UNIT = 4,
- UVC_VC_PROCESSING_UNIT = 5,
- UVC_VC_EXTENSION_UNIT = 6,
-
- UVC_VC_REQUEST_ERROR_CODE_CONTROL = 0x02,
-
- UVC_VS_INPUT_HEADER = 0x01,
- UVC_VS_FORMAT_UNCOMPRESSED = 0x04,
- UVC_VS_FRAME_UNCOMPRESSED = 0x05,
-
- UVC_SET_CUR = 0x01,
- UVC_GET_CUR = 0x81,
- UVC_GET_MIN = 0x82,
- UVC_GET_MAX = 0x83,
- UVC_GET_RES = 0x84,
- UVC_GET_LEN = 0x85,
- UVC_GET_INFO = 0x86,
- UVC_GET_DEF = 0x87,
-
- UVC_PU_BACKLIGHT_COMPENSATION_CONTROL = 0x01,
- UVC_PU_BRIGHTNESS_CONTROL = 0x02,
- UVC_PU_CONTRAST_CONTROL = 0x03,
- UVC_PU_POWER_LINE_FREQUENCY_CONTROL = 0x05,
- UVC_PU_HUE_CONTROL = 0x06,
- UVC_PU_SATURATION_CONTROL = 0x07,
- UVC_PU_SHARPNESS_CONTROL = 0x08,
- UVC_PU_GAMMA_CONTROL = 0x09,
-
- UVC_VS_PROBE_CONTROL = 0x01,
- UVC_VS_COMMIT_CONTROL = 0x02,
-
-};
-
-static int r5u870_uvc_req(struct r5u870_ctx *vhp, int cmd,
- int valhi, int vallow, int idxhi, int idxlow,
- u8 *buf, int len)
-{
- int out, res, stres;
- int tries = 5;
- u8 stbuf[1];
-
- out = (cmd == UVC_SET_CUR) ? 1 : 0;
-
-retry:
- res = usb_control_msg(r5u870_dev(vhp),
- out
- ? usb_sndctrlpipe(r5u870_dev(vhp), 0)
- : usb_rcvctrlpipe(r5u870_dev(vhp), 0),
- cmd,
- (out ? USB_DIR_OUT : USB_DIR_IN) |
- USB_TYPE_CLASS | USB_RECIP_INTERFACE,
- ((valhi & 0xff) << 8) | (vallow & 0xff),
- ((idxhi & 0xff) << 8) | (idxlow & 0xff),
- buf, len,
- vhp->vh_timeout);
-
- if (res != -EPIPE)
- goto complete;
-
- stres = usb_control_msg(r5u870_dev(vhp),
- usb_rcvctrlpipe(r5u870_dev(vhp), 0),
- UVC_GET_CUR,
- USB_DIR_IN | USB_TYPE_CLASS |
- USB_RECIP_INTERFACE,
- UVC_VC_REQUEST_ERROR_CODE_CONTROL << 8,
- vhp->vh_ctrl_ifnum,
- stbuf, sizeof(stbuf),
- vhp->vh_timeout);
-
- if (((stres == -EPIPE) && --tries) ||
- ((stres == 1) && (stbuf[0] == 1) && --tries)) {
- msleep(5);
- goto retry;
- }
-
- if (stres != 1) {
- r5u_err(vhp, "uvc_req: status req failed: %d", stres);
- goto complete;
-
- } else {
- r5u_err(vhp, "uvc_req: status %d", stbuf[0]);
- }
-
-complete:
- if (res < 0) {
- r5u_err(vhp, "uvc_req %02x/%02x%02x/%02x%02x failed: %d",
- cmd, valhi, vallow, idxhi, idxlow, res);
- }
-
- return res;
-}
-
-static int r5u870_set_fmt_uvc(struct r5u870_ctx *vhp,
- const struct r5u870_pix_fmt *fmtp,
- const struct r5u870_resolution *resp)
-{
- unsigned char buf[26];
- int res;
-
- memset(buf, 0, sizeof(buf));
-
- buf[2] = fmtp->rp_formatidx;
- buf[3] = resp->rw_frameidx;
- *(__le32 *) &buf[4] = cpu_to_le32(resp->rw_interval);
-
- r5u_err(vhp, "set_format: fmtidx:%d frameidx:%d %dx%d ival:%d",
- fmtp->rp_formatidx, resp->rw_frameidx,
- resp->rw_width, resp->rw_height, resp->rw_interval);
-
- res = r5u870_uvc_req(vhp, UVC_SET_CUR, UVC_VS_PROBE_CONTROL, 0,
- 0, vhp->vh_iso_ifnum, buf, sizeof(buf));
- if (res != sizeof(buf)) {
- r5u_err(vhp, "%s: probe_control set_cur: short write %d",
- __FUNCTION__, res);
-
- return -EIO;
- }
-
- res = r5u870_uvc_req(vhp, UVC_GET_CUR, UVC_VS_PROBE_CONTROL, 0,
- 0, vhp->vh_iso_ifnum, buf, sizeof(buf));
- if (res != sizeof(buf)) {
- r5u_err(vhp, "%s: probe_control get_cur: short read %d",
- __FUNCTION__, res);
- return -EIO;
- }
-
- if (buf[2] != fmtp->rp_formatidx) {
- r5u_err(vhp, "%s: probe_control get_cur: got fmt %d",
- __FUNCTION__, buf[2]);
- return -EIO;
- }
-
- if (buf[3] != resp->rw_frameidx) {
- r5u_err(vhp, "%s: probe_control get_cur: got frame idx %d",
- __FUNCTION__, buf[3]);
- return -EIO;
- }
-
- res = r5u870_uvc_req(vhp, UVC_SET_CUR, UVC_VS_COMMIT_CONTROL, 0,
- 0, vhp->vh_iso_ifnum, buf, sizeof(buf));
- if (res != sizeof(buf)) {
- r5u_err(vhp, "%s: commit_control set_cur: short write %d",
- __FUNCTION__, res);
- return -EIO;
- }
-
- vhp->vh_iso_minpacket = le32_to_cpup((__le32 *) &buf[22]);
-
- return 0;
-}
-
-static int r5u870_cap_stop_uvc(struct r5u870_ctx *vhp)
-{
- /* UVC capture is controlled by changing the altsetting */
- return 0;
-}
-
-static int r5u870_decide_pkt_uvc(struct r5u870_ctx *vhp, int pktstatus,
- int pktlen, const u8 *pktdata, int *start)
-{
- if (!pktlen) {
- vhp->vh_emptypkts++;
- if (vhp->vh_emptypkts >= R5U870_EMPTYPKT_GIVE_UP) {
- r5u_err(vhp, "capture abort: too many empty pkts");
- return -EIO;
- }
- if (vhp->vh_frame_accum < 0)
- return -EPIPE;
- return -EAGAIN;
- }
-
- vhp->vh_emptypkts = 0;
- if (vhp->vh_frame_accum < 0) {
- if (pktdata[1] & 2)
- vhp->vh_frame_accum = 0;
- return -EPIPE;
- }
-
- if ((pktdata[0] < 2) || (pktdata[0] > pktlen)) {
- r5u_err(vhp, "capture abort: hdrlen=%d pktlen=%d",
- pktdata[0], pktlen);
- return -EIO;
- }
- vhp->vh_frame_accum += pktlen - pktdata[0];
- if (vhp->vh_frame_accum > vhp->vh_parent->ud_format.sizeimage) {
- r5u_err(vhp, "frame abort: accum=%d", vhp->vh_frame_accum);
- vhp->vh_frame_accum = -1;
- return -EPIPE;
- }
-
- *start = pktdata[0];
- if (pktdata[1] & 2) {
- if (vhp->vh_frame_accum <
- vhp->vh_parent->ud_format.sizeimage) {
- r5u_err(vhp, "warning: short frame (exp:%d got:%d)",
- vhp->vh_parent->ud_format.sizeimage,
- vhp->vh_frame_accum);
- }
- vhp->vh_frame_accum = 0;
- return 0;
- }
- return -EAGAIN;
-}
-
-
-/*
- * Known video format GUIDs and V4L pixel format translations
- *
- * Only uncompressed formats are supported, as Ricoh webcams are too
- * minimal to do anything else.
- */
-
-static const struct r5u870_uvc_fmtinfo {
- const char *fi_name;
- int fi_v4l_id;
- u8 fi_guid[16];
-
-} r5u870_uvc_fmts[] = {
- { .fi_name = "YUY2 4:2:2",
- .fi_v4l_id = V4L2_PIX_FMT_YUYV,
- .fi_guid = { 0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00,
- 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } },
- { }
-};
-
-static int r5u870_uvc_add_resolution(struct r5u870_ctx *vhp,
- struct r5u870_pix_fmt *fmtp,
- int width, int height, int reqbw,
- int frameidx, int interval)
-{
- int i;
- struct r5u870_resolution *resp;
-
- if (!width || !height) {
- r5u_dbg(vhp, R5U_INIT, "invalid frame descriptor %d at %dx%d",
- frameidx, width, height);
- return -EINVAL;
- }
-
- resp = NULL;
- for (i = 0; i < fmtp->rp_restbl_alloc; i++) {
- if (!fmtp->rp_restbl[i].rw_width) {
- resp = (struct r5u870_resolution *)
- &fmtp->rp_restbl[i];
- break;
- }
- }
-
- if (!resp) {
- r5u_dbg(vhp, R5U_INIT,
- "no space for frame descriptor %d at %dx%d",
- frameidx, width, height);
- return 0;
- }
-
- resp->rw_width = width;
- resp->rw_height = height;
- resp->rw_frameidx = frameidx;
- resp->rw_reqbw = reqbw;
- resp->rw_interval = interval;
-
- r5u_dbg(vhp, R5U_INIT, "Found resolution %d: %dx%d ival %d (%d B/s)",
- frameidx, width, height, interval, reqbw);
-
- return 0;
-}
-
-static int r5u870_uvc_add_fmt(struct r5u870_ctx *vhp, const u8 *guid,
- int fmtidx, int nresolutions,
- struct r5u870_pix_fmt **new_fmt)
-{
- const struct r5u870_uvc_fmtinfo *fip, *fmtarray = r5u870_uvc_fmts;
- struct r5u870_pix_fmt *nfp, *fmtp;
- int i;
-
- fip = NULL;
- for (i = 0; fmtarray[i].fi_name != NULL; i++) {
- if (!memcmp(fmtarray[i].fi_guid, guid, 16)) {
- fip = &fmtarray[i];
- break;
- }
- }
-
- if (fip == NULL) {
- r5u_dbg(vhp, R5U_INIT, "unknown format "
- "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
- "%02x%02x%02x%02x%02x%02x",
- guid[0], guid[1], guid[2], guid[3], guid[4], guid[5],
- guid[6], guid[7], guid[8], guid[9], guid[10],
- guid[11], guid[12], guid[13], guid[14], guid[15]);
- return -ENOENT;
- }
-
- nfp = (struct r5u870_pix_fmt *)
- kmalloc((1 + vhp->vh_npixfmts) * sizeof(*nfp), GFP_KERNEL);
- if (!nfp)
- return -ENOMEM;
- if (vhp->vh_npixfmts)
- memcpy(nfp, vhp->vh_pixfmts, vhp->vh_npixfmts * sizeof(*nfp));
-
- fmtp = &nfp[vhp->vh_npixfmts];
- memset(fmtp, 0, sizeof(*fmtp));
- strlcpy(fmtp->base.description,
- fip->fi_name,
- sizeof(fmtp->base.description));
- fmtp->base.pixelformat = fip->fi_v4l_id;
- fmtp->rp_formatidx = fmtidx;
- fmtp->rp_restbl = (struct r5u870_resolution *)
- kmalloc((1 + nresolutions) * sizeof(*fmtp->rp_restbl),
- GFP_KERNEL);
- if (!fmtp->rp_restbl) {
- kfree(nfp);
- return -ENOMEM;
- }
-
- memset((char *)fmtp->rp_restbl, 0,
- (1 + nresolutions) * sizeof(*fmtp->rp_restbl));
- fmtp->rp_restbl_alloc = nresolutions;
-
- if (vhp->vh_npixfmts && vhp->vh_dyn_pixfmts)
- kfree(vhp->vh_pixfmts);
- vhp->vh_pixfmts = nfp;
- vhp->vh_npixfmts++;
-
- if (new_fmt)
- (*new_fmt) = fmtp;
-
- r5u_dbg(vhp, R5U_INIT, "Found format %d: %c%c%c%c (%d frames)",
- fmtidx,
- fmtp->base.pixelformat & 0xff,
- (fmtp->base.pixelformat >> 8) & 0xff,
- (fmtp->base.pixelformat >> 16) & 0xff,
- (fmtp->base.pixelformat >> 24) & 0xff,
- nresolutions);
-
- return 0;
-}
-
-static int r5u870_uvc_parse_vs(struct r5u870_ctx *vhp, int ifnum)
-{
- struct usb_interface *intf;
- struct usb_host_interface *aintf;
- struct r5u870_pix_fmt *curfmt = NULL;
- u8 *desc;
- int dlen, rlen;
- int wid, hgt, reqbw, ival;
- int res;
-
- intf = usb_ifnum_to_if(r5u870_dev(vhp), ifnum);
- if (!intf)
- return -EINVAL;
-
- aintf = intf->cur_altsetting;
-
- for (desc = aintf->extra, rlen = aintf->extralen;
- rlen > 2;
- rlen -= desc[0], desc += desc[0]) {
-
- dlen = desc[0];
- if (dlen < 2)
- return -EINVAL;
- if (desc[1] != USB_DT_CS_INTERFACE)
- continue;
- if (dlen < 3)
- return -EINVAL;
-
- switch (desc[2]) {
- case UVC_VS_INPUT_HEADER:
- if (dlen < 7) {
- r5u_err(vhp, "VS_INPUT_HEADER too short "
- "at %d bytes", dlen);
- return -EINVAL;
- }
- vhp->vh_iso_ep = desc[6];
- break;
-
- case UVC_VS_FORMAT_UNCOMPRESSED:
- if (dlen < 21) {
- r5u_err(vhp, "VS_FORMAT_UNCOMP too short "
- "at %d bytes", dlen);
- break;
- }
- res = r5u870_uvc_add_fmt(vhp, &desc[5], desc[3],
- desc[4], &curfmt);
- if (res) {
- if (res != -ENOENT)
- return res;
- curfmt = NULL;
- }
- break;
-
- case UVC_VS_FRAME_UNCOMPRESSED:
- if (dlen < 26) {
- r5u_err(vhp, "VS_FRAME_UNCOMP too short "
- "at %d bytes", dlen);
- break;
- }
- if (!curfmt) {
- r5u_dbg(vhp, R5U_INIT, "VS_FRAME_UNCOMP "
- "not following VS_FORMAT_UNCOMP");
- break;
- }
-
- wid = desc[5] | (desc[6] << 8);
- hgt = desc[7] | (desc[8] << 8);
- reqbw = desc[13] | (desc[14] << 8) |
- (desc[15] << 16) | (desc[16] << 24);
- reqbw = (reqbw + 7) / 8;
- ival = le32_to_cpup((__le32 *) &desc[21]);
-
- res = r5u870_uvc_add_resolution(vhp, curfmt, wid, hgt,
- reqbw, desc[3], ival);
- if (res)
- return res;
- break;
- }
- }
-
- return 0;
-}
-
-
-/*
- * Known UVC controls for processing units
- *
- * Not all types of UVC controls are supported. We stick to simple
- * controls with one or two byte values, and don't do anything very
- * complicated.
- *
- * We don't support camera unit controls, or multiple processing units
- * with instances of the same control. We also don't check for
- * redefined control IDs, and let usbcam do this for us.
- */
-
-struct r5u870_uvc_ctrlinfo {
- const char *ci_name;
- int ci_v4l_id;
- int ci_v4l_type;
- int ci_v4l_flags;
- int ci_reg;
- int ci_size;
- int ci_bm_index;
- int ci_min, ci_max, ci_def;
- u8 ci_min_force, ci_max_force, ci_def_force;
- const char **ci_menu_names;
- int ci_val_offset;
-};
-
-static struct r5u870_uvc_ctrlinfo r5u870_uvc_proc_ctrls[] = {
- { .ci_name = "Brightness",
- .ci_v4l_id = V4L2_CID_BRIGHTNESS,
- .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
- .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
- .ci_reg = UVC_PU_BRIGHTNESS_CONTROL,
- .ci_size = 2,
- .ci_bm_index = 0,
- .ci_val_offset = offsetof(struct r5u870_ctx, vh_brightness) },
- { .ci_name = "Contrast",
- .ci_v4l_id = V4L2_CID_CONTRAST,
- .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
- .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
- .ci_reg = UVC_PU_CONTRAST_CONTROL,
- .ci_size = 2,
- .ci_bm_index = 1,
- .ci_val_offset = offsetof(struct r5u870_ctx, vh_contrast) },
- { .ci_name = "Hue",
- .ci_v4l_id = V4L2_CID_HUE,
- .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
- .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
- .ci_reg = UVC_PU_HUE_CONTROL,
- .ci_size = 2,
- .ci_min = -180, .ci_min_force = 1,
- .ci_max = 180, .ci_max_force = 1,
- .ci_def = 0, .ci_def_force = 1,
- .ci_bm_index = 2,
- .ci_val_offset = offsetof(struct r5u870_ctx, vh_hue) },
- { .ci_name = "Saturation",
- .ci_v4l_id = V4L2_CID_SATURATION,
- .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
- .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
- .ci_reg = UVC_PU_SATURATION_CONTROL,
- .ci_size = 2,
- .ci_bm_index = 3,
- .ci_val_offset = offsetof(struct r5u870_ctx, vh_saturation) },
- { .ci_name = "Sharpness",
- .ci_v4l_id = V4L2_CID_R5U870_SHARPNESS,
- .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
- .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
- .ci_reg = UVC_PU_SHARPNESS_CONTROL,
- .ci_size = 2,
- .ci_bm_index = 4,
- .ci_val_offset = offsetof(struct r5u870_ctx, vh_sharpness) },
- { .ci_name = "Gamma",
- .ci_v4l_id = V4L2_CID_GAMMA,
- .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
- .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
- .ci_reg = UVC_PU_GAMMA_CONTROL,
- .ci_size = 2,
- .ci_bm_index = 5,
- .ci_val_offset = offsetof(struct r5u870_ctx, vh_gamma) },
- { .ci_name = "Backlight Compensation",
- .ci_v4l_id = V4L2_CID_R5U870_BACKLIGHT,
- .ci_v4l_type = V4L2_CTRL_TYPE_INTEGER,
- .ci_v4l_flags = V4L2_CTRL_FLAG_SLIDER,
- .ci_reg = UVC_PU_BACKLIGHT_COMPENSATION_CONTROL,
- .ci_size = 2,
- .ci_bm_index = 8,
- .ci_val_offset = offsetof(struct r5u870_ctx, vh_backlight_comp) },
- { .ci_name = "Power Line Frequency",
- .ci_v4l_id = V4L2_CID_R5U870_POWERLINE,
- .ci_v4l_type = V4L2_CTRL_TYPE_MENU,
- .ci_reg = UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
- .ci_size = 1,
- .ci_bm_index = 10,
- .ci_min = 0, .ci_min_force = 1,
- .ci_max = 2, .ci_max_force = 1,
- .ci_menu_names = r5u870_powerline_names,
- .ci_val_offset = offsetof(struct r5u870_ctx, vh_powerline) },
- { }
-};
-
-static int r5u870_uvc_ctrl_req(struct r5u870_ctx *vhp,
- const struct r5u870_ctrl *ctrlp,
- int req, int *value)
-{
- u8 buf[4];
- int size, i, val, res;
-
- size = ctrlp->size;
- if (req == UVC_GET_INFO)
- size = 1;
-
- if (size > sizeof(buf)) {
- r5u_err(vhp, "Control ID %d is too large, %d bytes",
- ctrlp->reg, size);
- return -EINVAL;
- }
-
- memset(buf, 0, sizeof(buf));
-
- if (req != UVC_SET_CUR) {
- res = r5u870_uvc_req(vhp, req, ctrlp->reg, 0,
- ctrlp->unit, vhp->vh_ctrl_ifnum,
- buf, size);
- if (res < 0)
- return res;
- if (res != size) {
- r5u_err(vhp, "short read for UVC control %d",
- ctrlp->reg);
- return -EIO;
- }
-
- val = 0;
- switch (size) {
- case 1:
- val = buf[0];
- break;
- case 2:
- val = le16_to_cpu(*(__le16 *) buf);
- break;
- case 4:
- val = le32_to_cpu(*(__le32 *) buf);
- break;
- default:
- return -EINVAL;
- }
-
- *value = val;
- return 0;
- }
-
- val = *value;
- for (i = 0; i < size; i++, val >>= 8)
- buf[i] = val & 0xff;
-
- res = r5u870_uvc_req(vhp, UVC_SET_CUR, ctrlp->reg, 0,
- ctrlp->unit, vhp->vh_ctrl_ifnum,
- buf, size);
- if (res < 0)
- return res;
- if (res != size) {
- r5u_err(vhp, "short write for UVC control %d",
- ctrlp->reg);
- return -EIO;
- }
-
- return 0;
-}
-
-static int r5u870_set_ctrl_uvc(struct usbcam_dev *udp,
- const struct usbcam_ctrl *basep,
- const struct v4l2_control *c)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- struct r5u870_ctrl *ctlp = container_of(basep, struct r5u870_ctrl,
- base);
- int val;
- int res = 0;
-
- if (!(ctlp->info & 2)) {
- r5u_dbg(vhp, R5U_CTRL, "control %s <= %d [set not supported]",
- ctlp->base.ctrl.name, c->value);
- return -EINVAL;
- }
-
- r5u_dbg(vhp, R5U_CTRL, "control %s/uvc %02x <= %d",
- ctlp->base.ctrl.name, ctlp->reg, c->value);
-
- val = c->value;
- res = r5u870_uvc_ctrl_req(vhp, ctlp, UVC_SET_CUR, &val);
- if (res)
- return res;
-
- *(int *) (((char *) vhp) + ctlp->val_offset) = c->value;
-
- return res;
-}
-
-/*
- * Initialize a r5u870_ctrl structure based on a UVC descriptor array,
- * and a bit within a supported control bitmap.
- */
-static int r5u870_uvc_init_ctrl(struct r5u870_ctx *vhp,
- struct r5u870_ctrl *ctrlp,
- int unit,
- const struct r5u870_uvc_ctrlinfo *ci_array,
- int bit_offset)
-{
- const struct r5u870_uvc_ctrlinfo *cip = NULL;
- int i, res, val;
-
- for (i = 0; ci_array[i].ci_name != NULL; i++) {
- if (ci_array[i].ci_bm_index == bit_offset) {
- cip = &ci_array[i];
- break;
- }
- }
-
- if (!cip)
- return -ENOENT;
- if (!ctrlp)
- return 0;
-
- ctrlp->base.ctrl.id = cip->ci_v4l_id;
- ctrlp->base.ctrl.type = cip->ci_v4l_type;
- strlcpy(ctrlp->base.ctrl.name,
- cip->ci_name,
- sizeof(ctrlp->base.ctrl.name));
- ctrlp->base.ctrl.flags = cip->ci_v4l_flags;
- ctrlp->base.ctrl.step = 1;
- ctrlp->base.menu_names = cip->ci_menu_names;
- ctrlp->base.get_fn = r5u870_get_ctrl;
- ctrlp->base.set_fn = r5u870_set_ctrl_uvc;
- ctrlp->base.query_fn = r5u870_query_ctrl;
- ctrlp->reg = cip->ci_reg;
- ctrlp->unit = unit;
- ctrlp->size = cip->ci_size;
- ctrlp->is_auto = 0;
- ctrlp->val_offset = cip->ci_val_offset;
- ctrlp->auto_offset = 0;
-
- res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_INFO, &val);
- if (res)
- return res;
- ctrlp->info = val;
-
- if (cip->ci_min_force) {
- ctrlp->base.ctrl.minimum = cip->ci_min;
- } else {
- res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_MIN, &val);
- if (res < 0)
- return res;
- ctrlp->base.ctrl.minimum = val;
- }
- if (cip->ci_max_force) {
- ctrlp->base.ctrl.maximum = cip->ci_max;
- } else {
- res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_MAX, &val);
- if (res < 0)
- return res;
- ctrlp->base.ctrl.maximum = val;
- }
-
- if (cip->ci_def_force) {
- ctrlp->base.ctrl.default_value = cip->ci_def;
- } else {
- res = r5u870_uvc_ctrl_req(vhp, ctrlp, UVC_GET_DEF, &val);
- if (res)
- return res;
- ctrlp->base.ctrl.default_value = val;
- }
-
- r5u_dbg(vhp, R5U_INIT, "Found UVC control %s [%d,%d] def:%d info:%02x",
- ctrlp->base.ctrl.name, ctrlp->base.ctrl.minimum,
- ctrlp->base.ctrl.maximum, ctrlp->base.ctrl.default_value,
- ctrlp->info);
- return 0;
-}
-
-static int r5u870_uvc_add_ctrls(struct r5u870_ctx *vhp, int unit,
- const struct r5u870_uvc_ctrlinfo *ci_array,
- const u8 *bits, int nbits)
-{
- struct r5u870_ctrl *ncp;
- int nctrls = 0;
- int i, res, curctrl;
-
- /* Count the known, usable controls on this descriptor unit */
- for (i = 0; i < nbits; i++) {
- if (!test_bit(i, (const unsigned long *) bits))
- continue;
- res = r5u870_uvc_init_ctrl(vhp, NULL, unit, ci_array, i);
- if (res == -ENOENT) {
- r5u_dbg(vhp, R5U_INIT,
- "unit %d ctrl %d not recognized", unit, i);
- }
- else if (res)
- return res;
-
- else
- nctrls++;
- }
-
- if (!nctrls)
- return 0;
-
- /* Allocate a new control array */
- ncp = (struct r5u870_ctrl *) kmalloc((vhp->vh_nctrls + nctrls) *
- sizeof(*ncp),
- GFP_KERNEL);
- if (!ncp)
- return -ENOMEM;
-
- if (vhp->vh_nctrls)
- memcpy(ncp, vhp->vh_ctrls, vhp->vh_nctrls * sizeof(*ncp));
- memset(&ncp[vhp->vh_nctrls], 0, nctrls * sizeof(*ncp));
-
- /* Fill out the new control entries */
- curctrl = vhp->vh_nctrls;
- for (i = 0; i < nbits; i++) {
- if (!test_bit(i, (const unsigned long *) bits))
- continue;
-
- res = r5u870_uvc_init_ctrl(vhp, &ncp[curctrl],
- unit, ci_array, i);
- if (!res) {
- BUG_ON(curctrl > (vhp->vh_nctrls + nctrls));
- curctrl++;
- }
- else if (res != -ENOENT) {
- kfree(ncp);
- return res;
- }
- }
-
- BUG_ON(curctrl != (vhp->vh_nctrls + nctrls));
-
- if (vhp->vh_nctrls && vhp->vh_dyn_ctrls)
- kfree(vhp->vh_ctrls);
- vhp->vh_ctrls = ncp;
- vhp->vh_nctrls = curctrl;
- vhp->vh_dyn_ctrls = 1;
- return 0;
-}
-
-static int r5u870_uvc_parse_vc(struct r5u870_ctx *vhp)
-{
- struct usb_host_interface *aintf =
- vhp->vh_parent->ud_intf->cur_altsetting;
- u8 *desc;
- int dlen, rlen, count;
- int i, res;
-
- vhp->vh_ctrl_ifnum = aintf->desc.bInterfaceNumber;
-
- for (desc = aintf->extra, rlen = aintf->extralen;
- rlen > 2;
- rlen -= desc[0], desc += desc[0]) {
-
- dlen = desc[0];
- if (dlen < 2)
- return -EINVAL;
- if (desc[1] != USB_DT_CS_INTERFACE)
- continue;
- if (dlen < 3)
- return -EINVAL;
-
- switch (desc[2]) {
- case UVC_VC_HEADER:
- count = (dlen < 12) ? 0 : desc[11];
- if (dlen < (12 + count)) {
- r5u_err(vhp, "VC_HEADER too short at %d bytes",
- dlen);
- return -EINVAL;
- }
-
- for (i = 0; i < count; i++) {
- res = usbcam_claim_interface(vhp->vh_parent,
- desc[12 + i]);
- if (res)
- r5u_err(vhp, "interface %d already "
- "claimed", desc[12 + i]);
- vhp->vh_iso_ifnum = desc[12 + i];
-
- res = r5u870_uvc_parse_vs(vhp, desc[12 + i]);
- if (res)
- return res;
- }
- break;
-
- case UVC_VC_PROCESSING_UNIT:
- count = (dlen < 8) ? 0 : desc[7];
- if (dlen < (8 + count)) {
- r5u_err(vhp, "VC_PROCESSING_UNIT too short "
- "at %d bytes", dlen);
- return -EINVAL;
- }
-
- res = r5u870_uvc_add_ctrls(vhp, desc[3],
- r5u870_uvc_proc_ctrls,
- &desc[8], desc[7] * 8);
- if (res)
- return res;
- break;
- }
- }
-
- return 0;
-}
-
-
-static void r5u870_do_stop(struct r5u870_ctx *vhp)
-{
- if (vhp->vh_parent->ud_capturing) {
- vhp->vh_parent->ud_capturing = 0;
- vhp->vh_ctrl_reg_enable = 0;
-
- usbcam_isostream_stop(&vhp->vh_iso);
- usbcam_curframe_abortall(vhp->vh_parent);
-
- if (!vhp->vh_parent->ud_disconnected) {
- usb_set_interface(r5u870_dev(vhp),
- vhp->vh_iso_ifnum,
- 0);
- vhp->vh_cap_stop(vhp);
- }
-
- usbcam_isostream_cleanup(&vhp->vh_iso);
- }
-
- if (vhp->vh_test_info)
- complete(&vhp->vh_test_info->rt_completion);
-}
-
-/*
- * As long as we are requested to capture, we keep the iso stream running.
- * If we are explicitly requested to stop, we halt.
- * If we run out of frame buffers to capture into, we halt.
- */
-static void r5u870_iso_packet_done(struct usbcam_dev *udp,
- struct usbcam_isostream *isop,
- void *pktdata, int pktlen, int pktstatus)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- struct usbcam_curframe cf;
- int res, start = 0;
-
- res = vhp->vh_decide_pkt(vhp, pktstatus, pktlen,
- (u8 *) pktdata, &start);
- if (vhp->vh_test_info) {
- if (res == -EIO)
- r5u870_do_stop(vhp);
- else if (pktlen && (res != -EPIPE)) {
- vhp->vh_test_info->rt_status = 0;
- r5u870_do_stop(vhp);
- }
- return;
- }
-
- switch (res) {
- case -EPIPE:
- if (vhp->vh_framebuf_offset) {
- vhp->vh_framebuf_offset = 0;
- usbcam_curframe_complete(udp, 1);
- }
- break;
-
- case 0:
- case -EAGAIN:
- if (pktlen) {
- if (usbcam_curframe_get(udp, &cf)) {
- /*
- * We have data, but there is no frame buffer
- * queued to accept it, so we stop.
- */
- r5u870_do_stop(vhp);
- break;
- }
-
- BUG_ON(pktlen - start + vhp->vh_framebuf_offset >
- cf.uc_size);
-
- /*
- * This is our one and only memcpy.
- * It's kind of hard to get around doing this, as
- * we cannot predict into which isochronous
- * packets the camera will choose to return data.
- */
- memcpy(cf.uc_base + vhp->vh_framebuf_offset,
- pktdata + start,
- pktlen - start);
- vhp->vh_framebuf_offset += (pktlen - start);
- }
-
- if (!res) {
- vhp->vh_framebuf_offset = 0;
- usbcam_curframe_complete(udp, 0);
- }
- break;
-
- case -EIO:
- r5u870_do_stop(vhp);
- break;
-
- default:
- BUG();
- }
-}
-
-static void r5u870_iso_submit_error(struct usbcam_dev *udp,
- struct usbcam_isostream *isop, int status)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- r5u_dbg(vhp, R5U_FRAME, "iso submit error: %d", status);
- r5u870_do_stop(vhp);
-}
-
-static struct usbcam_isostream_ops r5u870_iso_data_ops = {
- .packet_done = r5u870_iso_packet_done,
- .submit_error = r5u870_iso_submit_error,
-};
-
-
-static void r5u870_usbcam_release(struct usbcam_dev *udp)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- int i;
-
- for (i = 0; i < vhp->vh_npixfmts; i++) {
- if (vhp->vh_pixfmts[i].rp_restbl_alloc)
- kfree(vhp->vh_pixfmts[i].rp_restbl);
- }
-
- if (vhp->vh_dyn_pixfmts)
- kfree(vhp->vh_pixfmts);
- vhp->vh_dyn_pixfmts = 0;
- vhp->vh_pixfmts = NULL;
- vhp->vh_npixfmts = 0;
-
- if (vhp->vh_dyn_ctrls)
- kfree(vhp->vh_ctrls);
- vhp->vh_ctrls = NULL;
- vhp->vh_nctrls = 0;
- vhp->vh_dyn_ctrls = 0;
-}
-
-
-static const struct r5u870_model *r5u870_find_model(int driver_info);
-static int r5u870_usbcam_init(struct usbcam_dev *udp,
- const struct usb_device_id *devid)
-{
- struct r5u870_ctx *vhp;
- int model_info;
- int res;
-
- model_info = devid->driver_info;
-
- /* Initialize the private structure, don't use r5u_dbg() before this */
- vhp = udp_r5u870(udp);
- memset(vhp, 0, sizeof(*vhp));
- vhp->vh_parent = udp;
- vhp->vh_timeout = 1000;
-
- vhp->vh_ctrl_ifnum = -1;
- vhp->vh_iso_ifnum = -1;
- vhp->vh_iso_minpacket = -1;
-
- vhp->vh_model = r5u870_find_model(model_info);
-
- if (!vhp->vh_model) {
- r5u_err(vhp, "no suitable model descriptor for %04x:%04x",
- le16_to_cpu(r5u870_dev(vhp)->descriptor.idVendor),
- le16_to_cpu(r5u870_dev(vhp)->descriptor.idProduct));
- return -ENODEV;
- }
-
- vhp->vh_pixfmts = vhp->vh_model->rm_pixfmts;
- vhp->vh_npixfmts = vhp->vh_model->rm_npixfmts;
-
- if (vhp->vh_model->rm_uvc) {
- vhp->vh_set_fmt = r5u870_set_fmt_uvc;
- vhp->vh_cap_stop = r5u870_cap_stop_uvc;
- vhp->vh_decide_pkt = r5u870_decide_pkt_uvc;
- } else {
- vhp->vh_set_fmt = r5u870_set_fmt_wdm;
- vhp->vh_cap_stop = r5u870_cap_stop_wdm;
- vhp->vh_decide_pkt = r5u870_decide_pkt_wdm;
- }
-
- r5u_info(vhp, "Detected %s", vhp->vh_model->rm_name);
- snprintf(vhp->vh_parent->ud_vdev.name,
- sizeof(vhp->vh_parent->ud_vdev.name),
- "%s #%d",
- vhp->vh_model->rm_name, vhp->vh_parent->ud_minidrv_id + 1);
-
- res = r5u870_dev_init(vhp);
- if (res < 0) {
- r5u_err(vhp, "initialization failed: %d", res);
- goto out_failed;
- }
-
- if (vhp->vh_model->rm_uvc) {
- /*
- * This appears to be a UVC VideoControl interface.
- * Claim all of the associated VideoStreaming interfaces
- * and configure the required endpoints and unit IDs
- */
- res = r5u870_uvc_parse_vc(vhp);
- if (res < 0) {
- r5u_err(vhp, "UVC setup failed: %d", res);
- goto out_failed;
- }
-
- } else {
- /* We are looking at a proprietary Ricoh interface */
- vhp->vh_iso_ifnum =
- udp->ud_intf->altsetting->desc.bInterfaceNumber;
- vhp->vh_iso_ep = 6;
- }
-
- if (vhp->vh_model->rm_wdm_ctrlids) {
- res = r5u870_wdm_add_ctrls(vhp, vhp->vh_model->rm_wdm_ctrlids);
- if (res < 0) {
- r5u_err(vhp, "Vendor control setup failed: %d", res);
- goto out_failed;
- }
- }
-
- /* Configure the usbcam pixel format and control arrays */
- if (!vhp->vh_npixfmts) {
- r5u_err(vhp, "No pixel formats detected");
- res = -ENODEV;
- goto out_failed;
- }
-
- udp->ud_fmt_array = &vhp->vh_pixfmts[0].base;
- udp->ud_fmt_array_elem_size = sizeof(vhp->vh_pixfmts[0]);
- udp->ud_fmt_array_len = vhp->vh_npixfmts;
-
- if (vhp->vh_nctrls) {
- udp->ud_ctrl_array = &vhp->vh_ctrls[0].base;
- udp->ud_ctrl_array_len = vhp->vh_nctrls;
- udp->ud_ctrl_array_elem_size = sizeof(*vhp->vh_ctrls);
- }
-
- /* Set the default format */
- vhp->vh_fmt = &vhp->vh_pixfmts[0];
- vhp->vh_res = &vhp->vh_fmt->rp_restbl[0];
- udp->ud_format.width = vhp->vh_res->rw_width;
- udp->ud_format.height = vhp->vh_res->rw_height;
- udp->ud_format.pixelformat = vhp->vh_fmt->base.pixelformat;
- udp->ud_format.field = V4L2_FIELD_INTERLACED;
- udp->ud_format.bytesperline = udp->ud_format.width * 2;
- udp->ud_format.sizeimage = (udp->ud_format.width *
- udp->ud_format.height * 2);
- udp->ud_format.colorspace = V4L2_COLORSPACE_SMPTE170M;
-
- /* Configure default values for all controls */
- res = r5u870_set_controls(vhp, 1);
- if (res < 0) {
- r5u_err(vhp, "set defaults failed: %d", res);
- goto out_failed;
- }
-
- return 0;
-
-out_failed:
- r5u870_usbcam_release(udp);
- return res;
-}
-
-static void r5u870_usbcam_disconnect(struct usbcam_dev *udp)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- r5u870_do_stop(vhp);
-}
-
-
-/*
- * The power management stuff doesn't quite work yet, so we don't
- * yet set the supports_autosuspend field in the ops structure.
- */
-
-static int r5u870_usbcam_suspend(struct usbcam_dev *udp, pm_message_t msg)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- r5u870_do_stop(vhp);
- return 0;
-}
-
-static int r5u870_usbcam_resume(struct usbcam_dev *udp)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- int res;
-
- res = r5u870_dev_init(vhp);
- if (res < 0) {
- r5u_err(vhp, "dev reinitialization failed: %d", res);
- return res;
- }
-
- vhp->vh_configured = 0;
-
- return 0;
-}
-
-static int r5u870_try_format(struct usbcam_dev *udp, struct v4l2_pix_format *f,
- const struct r5u870_pix_fmt **fmt_out,
- const struct r5u870_resolution **res_out)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- const struct r5u870_pix_fmt *fmt;
- const struct r5u870_resolution *res, *restbl;
- int i;
-
- fmt = NULL;
- for (i = 0; i < vhp->vh_npixfmts; i++) {
- if (vhp->vh_pixfmts[i].base.pixelformat == f->pixelformat) {
- fmt = &vhp->vh_pixfmts[i];
- break;
- }
- }
- if (fmt == NULL)
- return -EINVAL;
-
- restbl = fmt->rp_restbl;
- if (!restbl || !restbl[0].rw_width) {
- r5u_err(vhp, "invalid resolution table");
- return -EINVAL;
- }
-
- /* Find the most acceptable resolution */
- res = NULL;
- for (i = 0; restbl[i].rw_width > 0; i++) {
- if (!res) {
- res = &restbl[i];
- }
- else if (res->rw_width > f->width) {
- if (restbl[i].rw_width < res->rw_width)
- res = &restbl[i];
- }
- else if (res->rw_height > f->height) {
- if ((restbl[i].rw_width <= f->width) &&
- (restbl[i].rw_height < res->rw_height))
- res = &restbl[i];
- }
- else if ((restbl[i].rw_width <= f->width) &&
- (restbl[i].rw_height <= f->height) &&
- ((restbl[i].rw_width > res->rw_width) ||
- ((restbl[i].rw_width == res->rw_width) &&
- (restbl[i].rw_height > res->rw_height)))) {
- res = &restbl[i];
- }
- }
-
- if ((f->width > 1) && (f->height > 1)) {
- r5u_dbg(vhp, R5U_INIT, "pix_fmt width: %d height: %d", f->width, f->height);
- r5u_dbg(vhp, R5U_INIT, "res width: %d height %d", res->rw_width, res->rw_height);
-
- if (((res->rw_width > f->width) || (res->rw_height > f->height))) {
- r5u_dbg(vhp, R5U_INIT, "Bad size request. Returning -EINVAL.");
- return -EINVAL;
- }
- }
-
- memset(f, 0, sizeof(*f));
- f->width = res->rw_width;
- f->height = res->rw_height;
- f->pixelformat = fmt->base.pixelformat;
- f->bytesperline = f->width * 2;
- f->sizeimage = f->width * f->height * 2;
- f->field = V4L2_FIELD_INTERLACED;
- f->colorspace = V4L2_COLORSPACE_SMPTE170M;
-
- r5u_dbg(vhp, R5U_INIT, "Settled on pixel format: %d (%s)", fmt->base.pixelformat, fmt->base.description);
-
- if (fmt_out)
- (*fmt_out) = fmt;
- if (res_out)
- (*res_out) = res;
-
- return 0;
-}
-
-static int r5u870_usbcam_try_format(struct usbcam_dev *udp,
- struct v4l2_pix_format *f)
-{
- return r5u870_try_format(udp, f, NULL, NULL);
-}
-
-static int r5u870_usbcam_set_format(struct usbcam_dev *udp,
- struct v4l2_pix_format *f)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- const struct r5u870_pix_fmt *fmt_out;
- const struct r5u870_resolution *res_out;
- int res;
-
- res = r5u870_try_format(udp, f, &fmt_out, &res_out);
- if (res)
- return res;
-
- if ((udp->ud_format.width != f->width) ||
- (udp->ud_format.height != f->height) ||
- (udp->ud_format.pixelformat != f->pixelformat) ||
- (udp->ud_format.sizeimage != f->sizeimage))
- vhp->vh_configured = 0;
-
- udp->ud_format = *f;
- vhp->vh_fmt = fmt_out;
- vhp->vh_res = res_out;
- return 0;
-}
-
-static void r5u870_usbcam_cap_stop(struct usbcam_dev *udp)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- r5u870_do_stop(vhp);
-}
-
-static int r5u870_config_iso_ep(struct r5u870_ctx *vhp)
-{
- int res;
-
- if (!vhp->vh_configured) {
- vhp->vh_ctrl_sync = 0;
-
- res = usbcam_choose_altsetting(vhp->vh_parent,
- vhp->vh_iso_ifnum,
- usb_rcvisocpipe(r5u870_dev(vhp),
- vhp->vh_iso_ep),
- vhp->vh_res->rw_reqbw,
- vhp->vh_iso_minpacket, -1,
- &vhp->vh_act_altsetting);
- if (res) {
- r5u_err(vhp, "need %d B/s, no altsetting provides",
- vhp->vh_res->rw_reqbw);
- return res;
- }
-
- r5u_dbg(vhp, R5U_FRAME, "using altsetting %d",
- vhp->vh_act_altsetting);
- }
-
- res = usb_set_interface(r5u870_dev(vhp), vhp->vh_iso_ifnum,
- vhp->vh_act_altsetting);
- if (res) {
- r5u_err(vhp, "could not set altsetting: %d", res);
- return res;
- }
-
- return 0;
-}
-
-static int r5u870_usbcam_cap_start(struct usbcam_dev *udp)
-{
- struct r5u870_ctx *vhp = udp_r5u870(udp);
- int res;
-
- if (vhp->vh_parent->ud_capturing)
- return 0;
-
- if (!vhp->vh_model->rm_uvc) {
- res = r5u870_config_iso_ep(vhp);
- if (res)
- return res;
- }
-
- vhp->vh_ctrl_reg_enable = 1;
-
- if (!vhp->vh_configured) {
- r5u_dbg(vhp, R5U_MDINTF, "setting initial control values");
- res = r5u870_set_controls(vhp, 0);
- if (res)
- goto out_set_idle;
-
- res = vhp->vh_set_fmt(vhp, vhp->vh_fmt, vhp->vh_res);
- if (res) {
- r5u_err(vhp, "could not configure capture: %d", res);
- goto out_set_idle;
- }
-
- if (vhp->vh_model->rm_no_ctrl_reload)
- vhp->vh_ctrl_sync = 1;
- }
-
- if (vhp->vh_model->rm_uvc) {
- res = r5u870_config_iso_ep(vhp);
- if (res)
- goto out_set_idle;
- }
-
- vhp->vh_configured = 1;
-
- res = usbcam_isostream_init(&vhp->vh_iso, vhp->vh_parent,
- vhp->vh_iso_ep,
- &r5u870_iso_data_ops,
- 0, 0, 1, 0);
- if (res < 0) {
- r5u_err(vhp, "isostream init failed: %d", res);
- goto out_set_idle;
- }
-
- udp->ud_capturing = 1;
-
- if (!vhp->vh_model->rm_uvc) {
- res = r5u870_set_cap_state_wdm(vhp, 1);
- if (res)
- goto out_cleanup_isostream;
- }
-
- if (!vhp->vh_ctrl_sync) {
- r5u_dbg(vhp, R5U_MDINTF, "reloading control values");
-
- /* Reload the control values after changing res/format */
- res = r5u870_set_controls(vhp, 0);
- if (res) {
- r5u_err(vhp, "could not load control values: %d", res);
- goto out_stop_capture;
- }
-
- vhp->vh_ctrl_sync = 1;
- }
-
- if (res)
- goto out_failed;
-
- r5u_dbg(vhp, R5U_MDINTF, "starting capture");
-
- vhp->vh_firstframe = 0;
- vhp->vh_frame_accum = -1;
- vhp->vh_framebuf_offset = 0;
- vhp->vh_emptypkts = R5U870_EMPTYPKT_FRAME_DELIM - 1;
-
- res = usbcam_isostream_start(&vhp->vh_iso);
- if (res)
- goto out_stop_capture;
-
- return 0;
-
-out_stop_capture:
- (void) vhp->vh_cap_stop(vhp);
-out_cleanup_isostream:
- usbcam_isostream_cleanup(&vhp->vh_iso);
-out_set_idle:
- (void) usb_set_interface(r5u870_dev(vhp), vhp->vh_iso_ifnum, 0);
-out_failed:
- vhp->vh_ctrl_reg_enable = 0;
- vhp->vh_parent->ud_capturing = 0;
- return res;
-}
-
-static struct usbcam_dev_ops r5u870_usbcam_dev_ops = {
- .init = r5u870_usbcam_init,
- .disconnect = r5u870_usbcam_disconnect,
- .release = r5u870_usbcam_release,
- .suspend = r5u870_usbcam_suspend,
- .resume = r5u870_usbcam_resume,
- .try_format = r5u870_usbcam_try_format,
- .set_format = r5u870_usbcam_set_format,
- .cap_start = r5u870_usbcam_cap_start,
- .cap_stop = r5u870_usbcam_cap_stop,
-
- /* .supports_autosuspend = 1, */
-};
-
-
-/*
- * Per-device hard coded vendor control lists follow
- */
-
-static const int r5u870_1830_ctrls[] = {
- R5U870_WDM_CTRL_BRIGHTNESS,
- R5U870_WDM_CTRL_CONTRAST,
- R5U870_WDM_CTRL_SATURATION,
- R5U870_WDM_CTRL_SHARPNESS,
- R5U870_WDM_CTRL_HUE,
- R5U870_WDM_CTRL_GAMMA,
- R5U870_WDM_CTRL_BACKLIGHT_COMP_500,
- R5U870_WDM_CTRL_WB_RED,
- R5U870_WDM_CTRL_WB_GREEN,
- R5U870_WDM_CTRL_WB_BLUE,
- R5U870_WDM_CTRL_WB_AUTO,
- R5U870_WDM_CTRL_GAIN,
- R5U870_WDM_CTRL_POWERLINE,
- R5U870_WDM_CTRL_VFLIP,
- R5U870_WDM_CTRL_HFLIP,
- R5U870_WDM_CTRL_PRIVACY,
- R5U870_WDM_CTRL_NIGHTMODE,
- R5U870_WDM_CTRL_LAST,
-};
-static const int r5u870_1832_ctrls[] = {
- R5U870_WDM_CTRL_BRIGHTNESS,
- R5U870_WDM_CTRL_CONTRAST,
- R5U870_WDM_CTRL_HUE,
- R5U870_WDM_CTRL_SATURATION,
- R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1,
- R5U870_WDM_CTRL_POWERLINE,
- R5U870_WDM_CTRL_VFLIP,
- R5U870_WDM_CTRL_HFLIP,
- R5U870_WDM_CTRL_PRIVACY,
- R5U870_WDM_CTRL_NIGHTMODE,
- R5U870_WDM_CTRL_LAST,
-};
-static const int r5u870_1833_ctrls[] = {
- R5U870_WDM_CTRL_BRIGHTNESS,
- R5U870_WDM_CTRL_CONTRAST,
- R5U870_WDM_CTRL_HUE,
- R5U870_WDM_CTRL_SATURATION,
- R5U870_WDM_CTRL_SHARPNESS,
- R5U870_WDM_CTRL_GAMMA,
- R5U870_WDM_CTRL_BACKLIGHT_COMP_500_DEF1,
- R5U870_WDM_CTRL_WB_RED,
- R5U870_WDM_CTRL_WB_GREEN,
- R5U870_WDM_CTRL_WB_BLUE,
- R5U870_WDM_CTRL_WB_AUTO,
- R5U870_WDM_CTRL_POWERLINE,
- R5U870_WDM_CTRL_VFLIP,
- R5U870_WDM_CTRL_HFLIP,
- R5U870_WDM_CTRL_PRIVACY,
- R5U870_WDM_CTRL_NIGHTMODE,
- R5U870_WDM_CTRL_LAST,
-};
-static const int r5u870_1834_ctrls[] = {
- R5U870_WDM_CTRL_BRIGHTNESS,
- R5U870_WDM_CTRL_CONTRAST,
- R5U870_WDM_CTRL_HUE,
- R5U870_WDM_CTRL_SATURATION,
- R5U870_WDM_CTRL_SHARPNESS,
- R5U870_WDM_CTRL_GAMMA,
- R5U870_WDM_CTRL_BACKLIGHT_COMP_X1834,
- R5U870_WDM_CTRL_WB_AUTO,
- R5U870_WDM_CTRL_WB_RED,
- R5U870_WDM_CTRL_WB_BLUE,
- R5U870_WDM_CTRL_AUTO_EXPOSURE,
- R5U870_WDM_CTRL_EXPOSURE,
- R5U870_WDM_CTRL_AUTO_GAIN,
- R5U870_WDM_CTRL_GAIN,
- R5U870_WDM_CTRL_POWERLINE,
- R5U870_WDM_CTRL_VFLIP,
- R5U870_WDM_CTRL_HFLIP,
- R5U870_WDM_CTRL_PRIVACY,
- R5U870_WDM_CTRL_NIGHTMODE,
- R5U870_WDM_CTRL_LAST,
-};
-static const int r5u870_1870_ctrls[] = {
- R5U870_WDM_CTRL_BRIGHTNESS,
- R5U870_WDM_CTRL_CONTRAST,
- R5U870_WDM_CTRL_HUE,
- R5U870_WDM_CTRL_SATURATION,
- R5U870_WDM_CTRL_SHARPNESS,
- R5U870_WDM_CTRL_GAMMA,
- R5U870_WDM_CTRL_WB_AUTO,
- R5U870_WDM_CTRL_WB_RED,
- R5U870_WDM_CTRL_WB_BLUE,
- R5U870_WDM_CTRL_AUTO_EXPOSURE,
- R5U870_WDM_CTRL_EXPOSURE,
- R5U870_WDM_CTRL_AUTO_GAIN,
- R5U870_WDM_CTRL_GAIN,
- R5U870_WDM_CTRL_POWERLINE,
- R5U870_WDM_CTRL_VFLIP,
- R5U870_WDM_CTRL_HFLIP,
- R5U870_WDM_CTRL_PRIVACY,
- R5U870_WDM_CTRL_NIGHTMODE,
- R5U870_WDM_CTRL_LAST,
-};
-
-/*
- * Even the UVC models do not express all of their controls in the UVC
- * descriptor tables, and get sets of hard-coded vendor controls
- */
-static const int r5u870_1835_ctrls[] = {
- R5U870_WDM_CTRL_WB_RED,
- R5U870_WDM_CTRL_WB_GREEN,
- R5U870_WDM_CTRL_WB_BLUE,
- R5U870_WDM_CTRL_WB_AUTO,
- R5U870_WDM_CTRL_VFLIP,
- R5U870_WDM_CTRL_HFLIP,
- R5U870_WDM_CTRL_PRIVACY,
- R5U870_WDM_CTRL_LAST,
-};
-static const int r5u870_1810_1836_ctrls[] = {
- R5U870_WDM_CTRL_WB_AUTO,
- R5U870_WDM_CTRL_WB_RED,
- R5U870_WDM_CTRL_WB_BLUE,
- R5U870_WDM_CTRL_AUTO_EXPOSURE,
- R5U870_WDM_CTRL_EXPOSURE,
- R5U870_WDM_CTRL_AUTO_GAIN,
- R5U870_WDM_CTRL_GAIN,
- R5U870_WDM_CTRL_VFLIP,
- R5U870_WDM_CTRL_HFLIP,
- R5U870_WDM_CTRL_PRIVACY,
- R5U870_WDM_CTRL_NIGHTMODE,
- R5U870_WDM_CTRL_LAST,
-};
-static const int r5u870_1810_183a_ctrls[] = {
- /* these are the ones not automatically discovered in uvc controls */
- R5U870_WDM_CTRL_WB_RED,
- R5U870_WDM_CTRL_WB_GREEN,
- R5U870_WDM_CTRL_WB_BLUE,
- R5U870_WDM_CTRL_WB_AUTO,
- R5U870_WDM_CTRL_VFLIP,
- R5U870_WDM_CTRL_HFLIP,
- R5U870_WDM_CTRL_PRIVACY,
- R5U870_WDM_CTRL_NIGHTMODE,
- R5U870_WDM_CTRL_LAST,
-};
-
-
-/*
- * Standard resolution table for non-UVC cameras,
- * as UVC camera report back as to what resolutions
- * they support.
- * The Sony VGP-VCC2 Windows driver supports:
- * 160x120, 176x144, 320x240, 352x288, 640x480
- * The HP driver also supports 1280x1024
- */
-static const struct r5u870_resolution r5u870_vga_wdm_res[] = {
- { 160, 120, 1152000 },
- { 176, 144, 1520640 },
- { 320, 240, 4608000 },
- { 352, 288, 6082560 },
- { 640, 480, 18432000 },
- { }
-};
-static const struct r5u870_resolution r5u870_sxga_wdm_res[] = {
- { 160, 120, 1152000 },
- { 176, 144, 1520640 },
- { 320, 240, 4608000 },
- { 352, 288, 6082560 },
- { 640, 480, 18432000 },
- { 1280, 1024, 19660800 },
- { }
-};
-static struct r5u870_pix_fmt r5u870_vga_wdm_pixfmts[] = {
- { .base = { .description = "YUY2 4:2:2",
- .pixelformat = V4L2_PIX_FMT_YUYV,
- .flags = 0 },
- .rp_formatidx = 0,
- .rp_restbl = r5u870_vga_wdm_res },
-
- { .base = { .description = "UYVY 4:2:2",
- .pixelformat = V4L2_PIX_FMT_UYVY,
- .flags = 0 },
- .rp_formatidx = 1,
- .rp_restbl = r5u870_vga_wdm_res },
-};
-static struct r5u870_pix_fmt r5u870_sxga_wdm_pixfmts[] = {
- { .base = { .description = "YUY2 4:2:2",
- .pixelformat = V4L2_PIX_FMT_YUYV,
- .flags = 0 },
- .rp_formatidx = 0,
- .rp_restbl = r5u870_sxga_wdm_res },
-
- { .base = { .description = "UYVY 4:2:2",
- .pixelformat = V4L2_PIX_FMT_UYVY,
- .flags = 0 },
- .rp_formatidx = 1,
- .rp_restbl = r5u870_sxga_wdm_res },
-};
-
-enum {
- R5U870_DI_INVALID,
- R5U870_DI_VGP_VCC2_SZ,
- R5U870_DI_VGP_VCC3,
- R5U870_DI_VGP_VCC2_AR1,
- R5U870_DI_VGP_VCC2_AR2,
- R5U870_DI_VGP_VCC5,
- R5U870_DI_VGP_VCC4,
- R5U870_DI_VGP_VCC7,
- R5U870_DI_HP_WEBCAM1K,
- R5U870_DI_HP_PAVWC_WDM,
- R5U870_DI_HP_PAVWC_UVC,
- R5U870_DI_GENERIC_UVC,
-};
-
-static const struct r5u870_model r5u870_models[] = {
- [R5U870_DI_VGP_VCC2_SZ] = {
- .rm_name = "Sony VGP-VCC2 (VAIO SZ)",
- .rm_ucode_file = "r5u870_1830.fw",
- .rm_ucode_version = 0x0100,
- .rm_wdm_ctrlids = r5u870_1830_ctrls,
- .rm_pixfmts = r5u870_vga_wdm_pixfmts,
- .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts),
- },
- [R5U870_DI_VGP_VCC3] = {
- .rm_name = "Sony VGP-VCC3",
- .rm_ucode_file = "r5u870_1832.fw",
- .rm_ucode_version = 0x0100,
- .rm_wdm_ctrlids = r5u870_1832_ctrls,
- .rm_pixfmts = r5u870_vga_wdm_pixfmts,
- .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts),
- .rm_no_ctrl_reload = 1,
- },
- [R5U870_DI_VGP_VCC2_AR1] = {
- .rm_name = "Sony VGP-VCC2 (VAIO AR1)",
- .rm_ucode_file = "r5u870_1833.fw",
- .rm_ucode_version = 0x0100,
- .rm_wdm_ctrlids = r5u870_1833_ctrls,
- .rm_pixfmts = r5u870_vga_wdm_pixfmts,
- .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts),
- },
- [R5U870_DI_VGP_VCC2_AR2] = {
- .rm_name = "Sony VGP-VCC2 (VAIO AR)",
- .rm_ucode_file = "r5u870_1834.fw",
- .rm_ucode_version = 0x0111,
- .rm_wdm_ctrlids = r5u870_1834_ctrls,
- .rm_pixfmts = r5u870_vga_wdm_pixfmts,
- .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts),
- },
- [R5U870_DI_VGP_VCC5] = {
- .rm_name = "Sony VGP-VCC5",
- .rm_ucode_file = "r5u870_1835.fw",
- .rm_ucode_version = 0x0107,
- .rm_wdm_ctrlids = r5u870_1835_ctrls,
- .rm_uvc = 1,
- },
- [R5U870_DI_VGP_VCC4] = {
- .rm_name = "Sony VGP-VCC4",
- .rm_ucode_file = "r5u870_1836.fw",
- .rm_ucode_version = 0x0115,
- .rm_wdm_ctrlids = r5u870_1810_1836_ctrls,
- .rm_uvc = 1,
- },
- [R5U870_DI_VGP_VCC7] = {
- .rm_name = "Sony VGP-VCC7 (VAIO SZ)",
- .rm_ucode_file = "r5u870_183a.fw",
- .rm_ucode_version = 0x0111,
- .rm_wdm_ctrlids = r5u870_1810_183a_ctrls,
- .rm_uvc = 1,
- },
- [R5U870_DI_HP_WEBCAM1K] = {
- .rm_name = "HP Webcam 1000",
- .rm_ucode_file = "r5u870_1870_1.fw",
- .rm_ucode_version = 0x0100,
- .rm_wdm_ctrlids = r5u870_1870_ctrls,
- .rm_pixfmts = r5u870_vga_wdm_pixfmts,
- .rm_npixfmts = ARRAY_SIZE(r5u870_vga_wdm_pixfmts),
- },
- [R5U870_DI_HP_PAVWC_WDM] = {
- .rm_name = "HP Pavilion Webcam",
- .rm_ucode_file = "r5u870_1870.fw",
- .rm_ucode_version = 0x0112,
- .rm_wdm_ctrlids = r5u870_1870_ctrls,
- .rm_pixfmts = r5u870_sxga_wdm_pixfmts,
- .rm_npixfmts = ARRAY_SIZE(r5u870_sxga_wdm_pixfmts),
- },
- [R5U870_DI_HP_PAVWC_UVC] = {
- .rm_name = "HP Pavilion Webcam",
- .rm_ucode_file = "r5u870_1810.fw",
- .rm_ucode_version = 0x0115,
- .rm_wdm_ctrlids = r5u870_1810_1836_ctrls,
- .rm_uvc = 1,
- },
- [R5U870_DI_GENERIC_UVC] = {
- .rm_name = "Generic UVC Webcam",
- .rm_uvc = 1,
- },
-};
-
-/*
- * Some idiot at HP decided to use 05ca:1870 for two distinct devices.
- * The Pavilion dv1xxx machines all seem to have the less common of the
- * two. There is no known, working method to distinguish the devices
- * using USB commands only. We resort to reading the model number out
- * of DMI.
- */
-static int dv1000 = 2;
-module_param(dv1000, int, S_IRUGO|S_IWUSR);
-MODULE_PARM_DESC(dv1000, "HP dv1000 detect mode (0=no,1=yes,2=DMI)");
-
-static int r5u870_check_hp_dv1000(void)
-{
- const char *prod_name;
- if (!dv1000)
- return 0;
- if (dv1000 == 1)
- return 1;
- prod_name = dmi_get_system_info(DMI_PRODUCT_NAME);
- if (!prod_name)
- printk(KERN_INFO "r5u870: No DMI model found\n");
- else {
- printk(KERN_INFO "r5u870: Found DMI model: \"%s\"\n",
- prod_name);
- if (!strncmp(prod_name, "HP Pavilion dv1000", 18) &&
- !isdigit(prod_name[18]))
- return 1;
- }
- return 0;
-}
-
-static const struct r5u870_model *r5u870_find_model(int driver_info)
-{
- if (driver_info == R5U870_DI_HP_PAVWC_WDM) {
- if (r5u870_check_hp_dv1000())
- driver_info = R5U870_DI_HP_WEBCAM1K;
- }
- if ((driver_info <= R5U870_DI_INVALID) ||
- (driver_info >= ARRAY_SIZE(r5u870_models)))
- return NULL;
- if (!r5u870_models[driver_info].rm_name)
- return NULL;
- return &r5u870_models[driver_info];
-}
-
-
-#define R5U870_DEVICE_UVC(VID, PID, DINFO) \
- .match_flags = USB_DEVICE_ID_MATCH_DEVICE \
- | USB_DEVICE_ID_MATCH_INT_INFO, \
- .idVendor = (VID), \
- .idProduct = (PID), \
- .bInterfaceClass = USB_CLASS_VIDEO, \
- .bInterfaceSubClass = 1, \
- .bInterfaceProtocol = 0, \
- .driver_info = (DINFO)
-
-static const struct usb_device_id id_table[] = {
- { USB_DEVICE(0x05CA, 0x1830), .driver_info = R5U870_DI_VGP_VCC2_SZ },
- { USB_DEVICE(0x05CA, 0x1832), .driver_info = R5U870_DI_VGP_VCC3 },
- { USB_DEVICE(0x05CA, 0x1833), .driver_info = R5U870_DI_VGP_VCC2_AR1 },
- { USB_DEVICE(0x05CA, 0x1834), .driver_info = R5U870_DI_VGP_VCC2_AR2 },
- { USB_DEVICE(0x05CA, 0x1870), .driver_info = R5U870_DI_HP_PAVWC_WDM },
-
- { R5U870_DEVICE_UVC(0x05CA, 0x1810, R5U870_DI_HP_PAVWC_UVC) },
- { R5U870_DEVICE_UVC(0x05CA, 0x1835, R5U870_DI_VGP_VCC5) },
- { R5U870_DEVICE_UVC(0x05CA, 0x1836, R5U870_DI_VGP_VCC4) },
- { R5U870_DEVICE_UVC(0x05CA, 0x183a, R5U870_DI_VGP_VCC7) },
- { },
-};
-
-
-DEFINE_USBCAM_MINIDRV_MODULE(R5U870_VERSION, R5U870_VERSION_EXTRA,
- &r5u870_usbcam_dev_ops,
- sizeof(struct r5u870_ctx),
- id_table)
-
-MODULE_DEVICE_TABLE(usb, id_table);
-MODULE_DESCRIPTION("Driver for Ricoh R5U870-based Webcams");
-MODULE_AUTHOR("Sam Revitch <samr7@cs.washington.edu>");
-MODULE_LICENSE("GPL");
diff --git a/usbcam/Makefile b/usbcam/Makefile
@@ -0,0 +1,6 @@
+usbcam-objs := usbcam_dev.o \
+ usbcam_fops.o \
+ usbcam_buf.o \
+ usbcam_util.o
+
+obj-m += usbcam.o
diff --git a/usbcam/usbcam.c b/usbcam/usbcam.c
@@ -0,0 +1,3477 @@
+/*
+ * USBCAM abstraction library for USB webcam drivers
+ * Version 0.10.2
+ *
+ * Copyright (C) 2007 Sam Revitch <samr7@cs.washington.edu>
+ * Copyright (c) 2008 Alexander Hixon <hixon.alexander@mediati.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * TODO LIST:
+ * - Add debug tracing to more ioctl paths
+ * - Provide a cleaner mechanism for alerting minidrvers of URB
+ * underflows in the isostream component.
+ */
+
+#define CONFIG_USBCAM_DEBUG
+
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/smp_lock.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/videodev.h>
+
+#include "usbcam.h"
+
+/* If building integrated with a minidriver, don't export symbols */
+#undef EXPORT_SYMBOL
+#define EXPORT_SYMBOL(X)
+
+#define assert usbcam_assert
+
+#define usbcam_drvname(MDP) ((MDP)->um_owner->name)
+
+#if defined(CONFIG_USBCAM_DEBUG)
+#define usbcam_dbgm(MD, SUBSYS, FMT, ARG...) do { \
+ if ((MD)->um_debug && \
+ *(MD)->um_debug & (1UL << USBCAM_DBG_ ## SUBSYS)) \
+ printk(KERN_INFO "%s: " FMT "\n", \
+ (MD)->um_modname, ## ARG); \
+} while (0)
+#else
+#define usbcam_dbgm(MD, SUBSYS, FMT, ARG...)
+#endif /* defined(CONFIG_USBCAM_DEBUG) */
+
+#define usbcam_minidrv_op_present(UDP, CB) \
+ ((UDP)->ud_minidrv->um_ops->CB ? 1 : 0)
+#define usbcam_minidrv_op(UDP, CB, ARGS...) \
+ ((UDP)->ud_minidrv->um_ops->CB((UDP), ## ARGS))
+
+
+/*
+ * Private data structure definitions
+ */
+
+/*
+ * This structure represents a registered minidriver
+ */
+struct usbcam_minidrv {
+ struct kref um_kref;
+ struct module *um_owner;
+ const char *um_modname;
+ int *um_debug;
+ int um_version;
+ int um_dev_count;
+ struct list_head um_dev_list;
+ int um_dev_privsize;
+ struct usb_driver um_usbdrv;
+ struct mutex um_lock;
+ const struct usbcam_dev_ops *um_ops;
+ struct video_device um_videodev_template;
+ struct file_operations um_v4l_fops;
+ const int *um_video_nr_array;
+ int um_video_nr_array_len;
+};
+
+/*
+ * The frame structure is generally managed by the video-buf module
+ * and represents some chunk of memory that the video4linux client
+ * requested as a frame buffer. It might be vmalloc()'d, or it might
+ * be mapped from a user address space. In either case, usbcam
+ * guarantees a contiguous kernel mapping accessible to the minidriver.
+ *
+ * The primary reason to use video-buf in usbcam is for its
+ * implementation of buffer mapping methods and "zero-copy" kernel-user
+ * data movement. The V4L2 API is quite rich, and it's much easier to
+ * use video-buf than to create a private full-featured implementation,
+ * and much more desirable to use video-buf than to limp along with a
+ * substandard implementation. The video-buf module isn't specifically
+ * used for DMA functionality, as most USB devices, with the possible
+ * exception of those employing bulk transfers, are unsuitable for
+ * direct frame buffer DMA.
+ *
+ * Minidrivers can access details of the current frame using
+ * usbcam_curframe_get(), and can signal completion of the current
+ * frame with usbcam_curframe_complete(). It is up to the minidriver
+ * to fill in the frame buffer.
+ */
+struct usbcam_frame {
+ struct videobuf_buffer vbb;
+ struct list_head cap_links;
+ void *vmap_base;
+ void *vmap_sof;
+};
+
+/*
+ * This structure represents an open file handle and the frame
+ * buffers associated with that client
+ */
+struct usbcam_fh {
+ struct usbcam_dev *uf_dev;
+ int uf_flags;
+ struct videobuf_queue uf_vbq;
+};
+
+#define USBCAM_FH_USE_FIXED_FB 0x00000001
+
+
+/*
+ * APPBUG: Some applications expect VIDIOCGMBUF to provide a buffer
+ * large enough to accommodate whatever image format they choose in the
+ * future. We enable fixed size buffer mode from VIDIOCGMBUF, and
+ * disable it from VIDIOC_REQBUFS.
+ */
+static int fixed_fbsize = 1024 * 1024;
+module_param(fixed_fbsize, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(fixed_fbsize, "Size in bytes of fixed-length framebuffers");
+
+
+/*
+ * Frame capture handling helpers follow
+ */
+
+static inline struct usbcam_frame *
+usbcam_capture_curframe(struct usbcam_dev *udp)
+{
+ return list_empty(&udp->ud_frame_cap_queue)
+ ? NULL
+ : list_entry(udp->ud_frame_cap_queue.next,
+ struct usbcam_frame, cap_links);
+}
+
+static void usbcam_capture_abortall(struct usbcam_dev *udp)
+{
+ struct usbcam_frame *framep;
+
+ /* Abort all frames on the capture queue */
+ while (1) {
+ framep = usbcam_capture_curframe(udp);
+ if (!framep)
+ break;
+ usbcam_dbg(udp, VIDEOBUF, "completing frame %d STATE_ERROR",
+ framep->vbb.i);
+ list_del_init(&framep->cap_links);
+ framep->vbb.state = STATE_ERROR;
+ wake_up_all(&framep->vbb.done);
+ }
+}
+
+static inline void usbcam_capture_complete_frame(struct usbcam_dev *udp,
+ struct usbcam_frame *framep,
+ int is_error)
+{
+ usbcam_chklock(udp);
+ usbcam_dbg(udp, VIDEOBUF, "completing frame %d/%p %s", framep->vbb.i,
+ framep, is_error ? "STATE_ERROR" : "STATE_DONE");
+ list_del_init(&framep->cap_links);
+ framep->vbb.state = is_error ? STATE_ERROR : STATE_DONE;
+ wake_up_all(&framep->vbb.done);
+}
+
+static int usbcam_capture_start(struct usbcam_dev *udp)
+{
+ int res;
+
+ if (udp->ud_capturing) {
+ usbcam_warn(udp, "%s: already capturing", __FUNCTION__);
+ return 0;
+ }
+
+ if (list_empty(&udp->ud_frame_cap_queue)) {
+ usbcam_warn(udp, "%s: no frames queued to capture",
+ __FUNCTION__);
+ return -ENOENT;
+ }
+
+ if (udp->ud_disconnected) {
+ /*
+ * We can't let any frames through if the device has
+ * been disconnected
+ */
+ usbcam_capture_abortall(udp);
+ return -ENODEV;
+ }
+
+ usbcam_dbg(udp, CAPTURE, "invoking minidriver cap_start");
+
+ res = usbcam_minidrv_op(udp, cap_start);
+ if (res) {
+ usbcam_dbg(udp, CAPTURE,
+ "%s: could not start capture for %s: %d",
+ __FUNCTION__, usbcam_drvname(udp->ud_minidrv), res);
+
+ if (udp->ud_capturing) {
+ usbcam_warn(udp,
+ "%s: minidriver left ud_capturing set\n",
+ __FUNCTION__);
+ }
+
+ usbcam_capture_abortall(udp);
+ return res;
+ }
+
+ if (!udp->ud_capturing && usbcam_capture_curframe(udp)) {
+ usbcam_warn(udp, "%s: minidriver failed to set ud_capturing!",
+ __FUNCTION__);
+ } else {
+ usbcam_dbg(udp, CAPTURE, "minidriver capture started");
+ }
+
+ return 0;
+}
+
+static void usbcam_capture_stop(struct usbcam_dev *udp)
+{
+ if (udp->ud_capturing) {
+ usbcam_dbg(udp, CAPTURE, "invoking minidriver cap_stop");
+ usbcam_minidrv_op(udp, cap_stop);
+
+ if (udp->ud_capturing) {
+ usbcam_warn(udp, "%s: minidriver failed to clear "
+ "ud_capturing!", __FUNCTION__);
+ } else {
+ usbcam_dbg(udp, CAPTURE, "minidriver capture stopped");
+ }
+ }
+}
+
+static void usbcam_capture_stop_nondestructive(struct usbcam_dev *udp)
+{
+ /*
+ * Only stop capturing if no frames are queued.
+ *
+ * We allow and encourage the minidriver to continue
+ * capturing in the last requested format, and have it
+ * stop autonomously when it receives its first data
+ * for the next frame but finds no frame available.
+ * This expedites the process for situations such as
+ * S_FMT which cannot tolerate capture being in progress.
+ */
+ if (udp->ud_capturing && list_empty(&udp->ud_frame_cap_queue))
+ usbcam_capture_stop(udp);
+}
+
+static inline struct videobuf_dmabuf* usbframe_get_dmabuf(struct videobuf_buffer *buf)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+ return buf->dma;
+#else
+ return videobuf_to_dma(buf);
+#endif
+}
+
+
+/*
+ * External APIs for minidriver access to the frame queue
+ */
+
+int usbcam_curframe_get(struct usbcam_dev *udp, struct usbcam_curframe *cf)
+{
+ struct usbcam_frame *framep = usbcam_capture_curframe(udp);
+ struct videobuf_dmabuf *dma;
+
+ usbcam_chklock(udp);
+
+ if (!framep || !&framep->vbb)
+ return -ENOENT;
+
+ dma = usbframe_get_dmabuf(&framep->vbb);
+
+ cf->uc_base = (u8 *) (framep->vmap_sof
+ ? framep->vmap_sof
+ : dma->vmalloc);
+ cf->uc_size = framep->vbb.size;
+ cf->uc_field = framep->vbb.field;
+ memset(&cf->uc_timestamp, 0, sizeof(cf->uc_timestamp));
+
+ return 0;
+}
+EXPORT_SYMBOL(usbcam_curframe_get);
+
+void usbcam_curframe_complete_detail(struct usbcam_dev *udp, int is_error,
+ struct usbcam_curframe *cf)
+{
+ struct usbcam_frame *framep;
+
+ usbcam_chklock(udp);
+
+ framep = usbcam_capture_curframe(udp);
+ if (!framep) {
+ usbcam_warn(udp, "%s: no current frame!", __FUNCTION__);
+ return;
+ }
+
+ if (framep->vbb.state != STATE_ACTIVE) {
+ usbcam_err(udp, "%s: current frame is in unexpected state %d",
+ __FUNCTION__, framep->vbb.state);
+ }
+
+ if (cf && !is_error) {
+ framep->vbb.size = cf->uc_size;
+ framep->vbb.field = cf->uc_field;
+ framep->vbb.ts = cf->uc_timestamp;
+
+ if (framep->vbb.bsize < cf->uc_size) {
+ usbcam_warn(udp, "%s: minidriver supplied "
+ "excessive size %zu",
+ __FUNCTION__, cf->uc_size);
+ framep->vbb.size = framep->vbb.bsize;
+ }
+ }
+
+ else if (is_error && usbcam_curframe_testpattern(udp)) {
+ /*
+ * No test pattern available.
+ * Punt: just memset the frame buffer to zero.
+ */
+ const char tp[1] = { 0 };
+ usbcam_curframe_fill(udp, 0, tp, 1, udp->ud_format.sizeimage);
+ }
+
+ usbcam_capture_complete_frame(udp, framep, 0);
+}
+EXPORT_SYMBOL(usbcam_curframe_complete_detail);
+
+void usbcam_curframe_abortall(struct usbcam_dev *udp)
+{
+ usbcam_dbg(udp, CAPTURE, "minidriver aborting all frames");
+
+ usbcam_chklock(udp);
+
+ if (udp->ud_capturing) {
+ usbcam_warn(udp, "%s: minidriver left ud_capturing set",
+ __FUNCTION__);
+ }
+ usbcam_capture_abortall(udp);
+}
+EXPORT_SYMBOL(usbcam_curframe_abortall);
+
+
+/*
+ * Test pattern code
+ */
+
+void usbcam_curframe_fill(struct usbcam_dev *udp, size_t offset,
+ const void *pattern, int patlen, int nrecs)
+{
+ struct usbcam_curframe cf;
+ size_t end;
+
+ usbcam_chklock(udp);
+
+ if (usbcam_curframe_get(udp, &cf)) {
+ usbcam_warn(udp, "%s: no current frame", __FUNCTION__);
+ return;
+ }
+
+ end = offset + (patlen * nrecs);
+ if (end > cf.uc_size) {
+ usbcam_warn(udp, "%s(offs=%zu patlen=%d nrecs=%d) would "
+ "write past end of %zu byte framebuffer",
+ __FUNCTION__,
+ offset, patlen, nrecs, cf.uc_size);
+ if (offset > cf.uc_size)
+ nrecs = 0;
+ else
+ nrecs = (cf.uc_size - offset) / patlen;
+ }
+ else if (end > udp->ud_format.sizeimage)
+ usbcam_warn(udp, "%s(offs=%zu patlen=%d nrecs=%d) writing "
+ "beyond %u-byte sizeimage", __FUNCTION__,
+ offset, patlen, nrecs, udp->ud_format.sizeimage);
+
+ if (!nrecs)
+ return;
+
+ if (patlen == 1) {
+ memset(cf.uc_base + offset, *(char *)pattern, nrecs);
+ return;
+ }
+
+ while (nrecs--) {
+ memcpy(cf.uc_base + offset, pattern, patlen);
+ offset += patlen;
+ }
+}
+EXPORT_SYMBOL(usbcam_curframe_fill);
+
+void usbcam_curframe_fill_lines(struct usbcam_dev *udp,
+ const char *pat, int patlen,
+ int pixperpat)
+{
+ int line, nrecperline, stride;
+ size_t offset = 0;
+
+ nrecperline = (udp->ud_format.width + pixperpat - 1) / pixperpat;
+ stride = udp->ud_format.bytesperline
+ ? udp->ud_format.bytesperline
+ : (nrecperline * patlen);
+
+ if ((patlen * nrecperline) == stride) {
+ usbcam_curframe_fill(udp, 0, pat, patlen,
+ nrecperline * udp->ud_format.height);
+ return;
+ }
+
+ for (line = 0; line < udp->ud_format.height; line++) {
+ usbcam_curframe_fill(udp, offset, pat, patlen, nrecperline);
+ offset += stride;
+ }
+}
+
+void usbcam_curframe_fill_interleaved(struct usbcam_dev *udp,
+ const char *pat_even,
+ const char *pat_odd,
+ int patlen, int pixperpat)
+{
+ int line, nrecperline, stride;
+ size_t offset = 0;
+
+ nrecperline = (udp->ud_format.width + pixperpat - 1) / pixperpat;
+ stride = udp->ud_format.bytesperline
+ ? udp->ud_format.bytesperline
+ : (nrecperline * patlen);
+
+ for (line = 0; line < udp->ud_format.height; line++) {
+ usbcam_curframe_fill(udp, offset,
+ (line & 1) ? pat_odd : pat_even,
+ patlen, nrecperline);
+ offset += stride;
+ }
+}
+
+void usbcam_curframe_fill_planar(struct usbcam_dev *udp,
+ const char *pat0, int pat0len, int pixperpat0,
+ const char *pat1, int pat1len, int pixperpat1,
+ const char *pat2, int pat2len, int pixperpat2)
+{
+ int nrecperline;
+ size_t offset = 0;
+
+ if (pat0 && pat0len) {
+ nrecperline = ((udp->ud_format.width + pixperpat0 - 1) /
+ pixperpat0);
+ usbcam_curframe_fill(udp, offset, pat0, pat0len,
+ nrecperline * udp->ud_format.height);
+ offset += (nrecperline * udp->ud_format.height * pat0len);
+ }
+ if (pat1 && pat1len) {
+ nrecperline = ((udp->ud_format.width + pixperpat1 - 1) /
+ pixperpat1);
+ usbcam_curframe_fill(udp, offset, pat1, pat1len,
+ nrecperline * udp->ud_format.height);
+ offset += (nrecperline * udp->ud_format.height * pat1len);
+ }
+ if (pat2 && pat2len) {
+ nrecperline = ((udp->ud_format.width + pixperpat2 - 1) /
+ pixperpat2);
+ usbcam_curframe_fill(udp, offset, pat2, pat2len,
+ nrecperline * udp->ud_format.height);
+ offset += (nrecperline * udp->ud_format.height * pat2len);
+ }
+}
+
+/*
+ * The goal is to be able to come up with a solid blue image in all
+ * basic uncompressed formats. No JPEG or compressed formats, at least
+ * not yet.
+ */
+int usbcam_curframe_testpattern(struct usbcam_dev *udp)
+{
+ usbcam_chklock(udp);
+
+#define DO_FILL(PIXPERPAT, TP...) { \
+ const char tp[] = {TP}; \
+ usbcam_curframe_fill_lines(udp, tp, sizeof(tp), PIXPERPAT); \
+}
+ switch (udp->ud_format.pixelformat) {
+ case V4L2_PIX_FMT_RGB332:
+ DO_FILL(1, 0x03)
+ return 0;
+ case V4L2_PIX_FMT_RGB555:
+ case V4L2_PIX_FMT_RGB565:
+ DO_FILL(1, 0x1f, 0x00)
+ return 0;
+ case V4L2_PIX_FMT_RGB555X:
+ case V4L2_PIX_FMT_RGB565X:
+ DO_FILL(1, 0x00, 0x1f)
+ return 0;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
+ case V4L2_PIX_FMT_RGB444:
+ DO_FILL(1, 0x00, 0x0f)
+ return 0;
+#endif
+ case V4L2_PIX_FMT_BGR24:
+ DO_FILL(1, 0xff, 0x00, 0x00);
+ return 0;
+ case V4L2_PIX_FMT_RGB24:
+ DO_FILL(1, 0x00, 0x00, 0xff);
+ return 0;
+ case V4L2_PIX_FMT_BGR32:
+ DO_FILL(1, 0xff, 0x00, 0x00, 0x00);
+ return 0;
+ case V4L2_PIX_FMT_RGB32:
+ DO_FILL(1, 0x00, 0x00, 0xff, 0x00);
+ return 0;
+ case V4L2_PIX_FMT_GREY:
+ DO_FILL(1, 0x1f);
+ return 0;
+ case V4L2_PIX_FMT_YUYV:
+ DO_FILL(2, 0x29, 0xf0, 0x29, 0x6e);
+ return 0;
+ case V4L2_PIX_FMT_UYVY:
+ DO_FILL(2, 0xf0, 0x29, 0x6e, 0x29);
+ return 0;
+ case V4L2_PIX_FMT_YYUV:
+ DO_FILL(2, 0x29, 0x29, 0xf0, 0x6e);
+ return 0;
+ case V4L2_PIX_FMT_Y41P:
+ DO_FILL(8,
+ 0xf0, 0x29, 0x6e, 0x29, 0xf0, 0x29, 0x6e, 0x29,
+ 0x29, 0x29, 0x29, 0x29);
+ return 0;
+#undef DO_FILL
+
+ case V4L2_PIX_FMT_SBGGR8: {
+ const char tp0[] = { 0xff, 0x00 }, tp1[] = { 0x00, 0x00 };
+ usbcam_curframe_fill_interleaved(udp, tp0, tp1, 2, 2);
+ return 0;
+ }
+ case V4L2_PIX_FMT_YVU410: {
+ const char tp0[] = {0x29}, tp1[] = {0x6e}, tp2[] = {0xf0};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 16,
+ tp2, sizeof(tp2), 16);
+ return 0;
+ }
+ case V4L2_PIX_FMT_YUV410: {
+ const char tp0[] = {0x29}, tp1[] = {0xf0}, tp2[] = {0x6e};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 16,
+ tp2, sizeof(tp2), 16);
+ return 0;
+ }
+ case V4L2_PIX_FMT_YVU420: {
+ const char tp0[] = {0x29}, tp1[] = {0x6e}, tp2[] = {0xf0};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 4,
+ tp2, sizeof(tp2), 4);
+ return 0;
+ }
+ case V4L2_PIX_FMT_YUV411P:
+ case V4L2_PIX_FMT_YUV420: {
+ const char tp0[] = {0x29}, tp1[] = {0xf0}, tp2[] = {0x6e};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 4,
+ tp2, sizeof(tp2), 4);
+ return 0;
+ }
+ case V4L2_PIX_FMT_YUV422P: {
+ const char tp0[] = {0x29}, tp1[] = {0xf0}, tp2[] = {0x6e};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 2,
+ tp2, sizeof(tp2), 2);
+ return 0;
+ }
+ case V4L2_PIX_FMT_NV12: {
+ const char tp0[] = {0x29}, tp1[] = {0xf0, 0x6e};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 4,
+ NULL, 0, 0);
+ return 0;
+ }
+ case V4L2_PIX_FMT_NV21: {
+ const char tp0[] = {0x29}, tp1[] = {0x6e, 0xf0};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 4,
+ NULL, 0, 0);
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL(usbcam_curframe_testpattern);
+
+
+/*
+ * video-buf interfaces for managing frame buffers
+ */
+
+static int usbcam_videobuf_setup(struct videobuf_queue *vq,
+ unsigned int *count, unsigned int *size)
+{
+ struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, uf_vbq);
+ struct usbcam_dev *udp = ufp->uf_dev;
+
+ usbcam_lock(udp);
+
+ /* APPBUG: possibly request larger buffers than necessary */
+ if ((ufp->uf_flags & USBCAM_FH_USE_FIXED_FB) &&
+ (fixed_fbsize > udp->ud_format.sizeimage))
+ *size = fixed_fbsize;
+ else
+ *size = udp->ud_format.sizeimage;
+
+ if (!*count)
+ *count = 2;
+
+ usbcam_dbg(udp, VIDEOBUF, "videobuf setup: size=%u count=%u",
+ *size, *count);
+
+ usbcam_unlock(udp);
+ return 0;
+}
+
+static void usbcam_videobuf_free(struct videobuf_queue *vq,
+ struct usbcam_frame *framep)
+{
+ struct videobuf_dmabuf *dma = usbframe_get_dmabuf(&framep->vbb);
+
+ videobuf_waiton(&framep->vbb, 0, 0);
+ videobuf_dma_unmap(vq, dma);
+ videobuf_dma_free(dma);
+ if (framep->vbb.state != STATE_NEEDS_INIT) {
+ if (framep->vmap_base) {
+ vunmap(framep->vmap_base);
+ framep->vmap_base = NULL;
+ framep->vmap_sof = NULL;
+ }
+ assert(list_empty(&framep->cap_links));
+ framep->vbb.state = STATE_NEEDS_INIT;
+ }
+}
+
+static int usbcam_videobuf_prepare(struct videobuf_queue *vq,
+ struct videobuf_buffer *vb,
+ enum v4l2_field field)
+{
+ struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, uf_vbq);
+ struct usbcam_dev *udp = ufp->uf_dev;
+ struct usbcam_frame *framep =
+ container_of(vb, struct usbcam_frame, vbb);
+ struct videobuf_dmabuf *dma = usbframe_get_dmabuf(&framep->vbb);
+ int res;
+
+
+ framep->vbb.size = udp->ud_format.sizeimage;
+ if (framep->vbb.baddr && (framep->vbb.bsize < framep->vbb.size)) {
+ usbcam_warn(udp, "process %s requested capture of a frame "
+ "larger than its", current->comm);
+ usbcam_warn(udp, "allocated frame buffer, fix it!");
+ return -EINVAL;
+ }
+
+ if (framep->vbb.state == STATE_NEEDS_INIT) {
+ /*
+ * This is the place where we initialize the rest of
+ * the usbcam_frame structure.
+ */
+ INIT_LIST_HEAD(&framep->cap_links);
+ framep->vmap_base = NULL;
+ framep->vmap_sof = NULL;
+
+ usbcam_dbg(udp, VIDEOBUF,
+ "preparing frame %d/%p", framep->vbb.i, framep);
+
+ /* We also lock down the memory that was allocated for it */
+ res = videobuf_iolock(vq, &framep->vbb, NULL);
+ if (res)
+ goto fail;
+
+ /* If there's no kernel mapping, we must create one */
+ if (!dma->vmalloc) {
+ framep->vmap_base = vmap(dma->pages,
+ dma->nr_pages,
+ VM_MAP,
+ PAGE_KERNEL);
+ if (!framep->vmap_base) {
+ res = -ENOMEM;
+ goto fail;
+ }
+
+ framep->vmap_sof =
+ ((char *)framep->vmap_base) +
+ dma->offset;
+ }
+ }
+
+ framep->vbb.field = field;
+ framep->vbb.state = STATE_PREPARED;
+ return 0;
+
+fail:
+ usbcam_warn(udp, "videobuf_prepare failed.");
+ usbcam_videobuf_free(vq, framep);
+ return res;
+}
+
+static void usbcam_videobuf_queue(struct videobuf_queue *vq,
+ struct videobuf_buffer *vb)
+{
+ struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, uf_vbq);
+ struct usbcam_dev *udp = ufp->uf_dev;
+ struct usbcam_frame *framep =
+ container_of(vb, struct usbcam_frame, vbb);
+ int was_empty = 0;
+
+ assert(framep->vbb.state != STATE_NEEDS_INIT);
+
+ usbcam_lock(udp);
+
+ if (list_empty(&udp->ud_frame_cap_queue))
+ was_empty = 1;
+
+ usbcam_dbg(udp, VIDEOBUF, "queueing frame %d/%p",
+ framep->vbb.i, framep);
+
+ /*
+ * We always set buffers to STATE_ACTIVE to prevent them from
+ * being manipulated / dequeued by the videobuf code.
+ */
+ list_add_tail(&framep->cap_links, &udp->ud_frame_cap_queue);
+ framep->vbb.state = STATE_ACTIVE;
+
+ if (was_empty && !udp->ud_capturing) {
+ (void) usbcam_capture_start(udp);
+ }
+
+ usbcam_unlock(udp);
+}
+
+static void usbcam_videobuf_release(struct videobuf_queue *vq,
+ struct videobuf_buffer *vb)
+{
+ struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, uf_vbq);
+ struct usbcam_dev *udp = ufp->uf_dev;
+ struct usbcam_frame *framep =
+ container_of(vb, struct usbcam_frame, vbb);
+ int stopped_capture = 0;
+
+ usbcam_lock(udp);
+
+ if ((framep->vbb.state != STATE_NEEDS_INIT) &&
+ !list_empty(&framep->cap_links)) {
+
+ usbcam_dbg(udp, VIDEOBUF,
+ "aborting frame %d/%p", framep->vbb.i, framep);
+
+ /*
+ * An active frame is being shot down here, most
+ * likely by videobuf_queue_cancel.
+ */
+ assert(framep->vbb.state == STATE_ACTIVE);
+
+ if (udp->ud_capturing &&
+ !list_empty(&udp->ud_frame_cap_queue) &&
+ (framep == usbcam_capture_curframe(udp))) {
+ /*
+ * The current frame has been user-aborted.
+ * We will stop capturing. The minidriver may complete
+ * it with an error, or may leave it alone, in which
+ * case we will complete it with an error.
+ */
+ usbcam_dbg(udp, VIDEOBUF,
+ "current frame aborted, stopping capture");
+
+ usbcam_capture_stop(udp);
+ stopped_capture = 1;
+ }
+
+ if (!list_empty(&framep->cap_links))
+ usbcam_capture_complete_frame(udp, framep, 1);
+
+ /*
+ * Ideally, if we stopped capturing, and there are frames
+ * still in the queue, we would restart.
+ *
+ * In reality, we only take this code path if all frames
+ * from the owning file handle are aborted, and restarting
+ * would pointlessly slow down this process.
+ */
+ }
+
+ usbcam_unlock(udp);
+ usbcam_videobuf_free(vq, framep);
+}
+
+static struct videobuf_queue_ops usbcam_videobuf_qops = {
+ .buf_setup = usbcam_videobuf_setup,
+ .buf_prepare = usbcam_videobuf_prepare,
+ .buf_queue = usbcam_videobuf_queue,
+ .buf_release = usbcam_videobuf_release,
+};
+
+
+/*
+ * Reference Counting Notes
+ *
+ * Each usbcam_minidrv gets:
+ * - One reference for being in the registered state
+ * - One reference for each outstanding usbcam_dev
+ *
+ * Each usbcam_dev gets:
+ * - One reference for having its V4L minor registered and not released
+ * - One reference for having its underlying USB device not disconnected
+ * - One reference for each open file handle
+ */
+
+static void usbcam_minidrv_release(struct kref *kref)
+{
+ struct usbcam_minidrv *minidrv =
+ container_of(kref, struct usbcam_minidrv, um_kref);
+
+ assert(!minidrv->um_dev_count);
+ usbcam_dbgm(minidrv, DEV_STATE, "%s: destroying minidrvier",
+ __FUNCTION__);
+ kfree(minidrv);
+}
+
+/*
+ */
+static void usbcam_work_stop(struct usbcam_dev *udp);
+static inline void usbcam_work_maybe_stop(struct usbcam_dev *udp)
+{
+ if (!udp->ud_work_refs)
+ usbcam_work_stop(udp);
+}
+
+static void usbcam_dev_free(struct kref *kref)
+{
+ struct usbcam_dev *udp =
+ container_of(kref, struct usbcam_dev, ud_kref);
+
+ if (udp->ud_work_refs) {
+ usbcam_lock(udp);
+ usbcam_warn(udp, "%s: work queue has %d leaked refs",
+ __FUNCTION__, udp->ud_work_refs);
+ while (udp->ud_work_refs)
+ usbcam_work_unref(udp);
+ usbcam_unlock(udp);
+ }
+
+ usbcam_work_maybe_stop(udp);
+ assert(!udp->ud_work_thread);
+
+ usb_put_intf(udp->ud_intf);
+ udp->ud_intf = NULL;
+
+ usb_put_dev(udp->ud_dev);
+ udp->ud_dev = NULL;
+
+ mutex_lock(&udp->ud_minidrv->um_lock);
+
+ assert(!list_empty(&udp->ud_drv_links));
+ assert(udp->ud_minidrv->um_dev_count > 0);
+
+ list_del_init(&udp->ud_drv_links);
+ udp->ud_minidrv->um_dev_count--;
+
+ mutex_unlock(&udp->ud_minidrv->um_lock);
+
+ kref_put(&udp->ud_minidrv->um_kref, usbcam_minidrv_release);
+ kfree(udp);
+}
+
+static void usbcam_dev_release(struct kref *kref)
+{
+ struct usbcam_dev *udp =
+ container_of(kref, struct usbcam_dev, ud_kref);
+
+ usbcam_dbg(udp, DEV_STATE, "%s: destroying device", __FUNCTION__);
+
+ if (usbcam_minidrv_op_present(udp, release)) {
+ usbcam_lock(udp);
+ usbcam_minidrv_op(udp, release);
+ usbcam_unlock(udp);
+ }
+ usbcam_dev_free(kref);
+}
+
+void usbcam_put(struct usbcam_dev *udp)
+{
+ kref_put(&udp->ud_kref, usbcam_dev_release);
+}
+EXPORT_SYMBOL(usbcam_put);
+
+
+/*
+ * Work item crap
+ */
+
+enum {
+ USBCAM_WORKSTATE_DEAD,
+ USBCAM_WORKSTATE_IDLE,
+ USBCAM_WORKSTATE_MUTEX_WAIT,
+ USBCAM_WORKSTATE_RUNNING,
+};
+
+DECLARE_WAIT_QUEUE_HEAD(usbcam_work_idle_wait);
+
+static int usbcam_work_thread(void *arg)
+{
+ struct usbcam_dev *udp = (struct usbcam_dev *) arg;
+ struct usbcam_workitem *wip;
+ sigset_t wakesigs;
+ unsigned long flags;
+ usbcam_workfunc_t fn;
+ int res;
+
+ current->flags |= PF_NOFREEZE;
+ set_user_nice(current, -5);
+
+ sigemptyset(&wakesigs);
+ sigaddset(&wakesigs, SIGUSR1);
+
+ while (1) {
+ /* Wait for something to appear on the work queue */
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ udp->ud_work_lockwait = 0;
+ if (list_empty(&udp->ud_work_queue)) {
+ if (kthread_should_stop()) {
+ spin_unlock_irqrestore(&udp->ud_work_lock,
+ flags);
+ break;
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ wake_up_all(&usbcam_work_idle_wait);
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+ schedule();
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ }
+ udp->ud_work_lockwait = 1;
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+
+ /* Enable the mutex wait cancelation signal */
+ sigprocmask(SIG_UNBLOCK, &wakesigs, NULL);
+
+ /* Re-check the queue, wait if it's still nonempty */
+ res = -EINTR;
+ if (!list_empty(&udp->ud_work_queue)) {
+ res = mutex_lock_interruptible(&udp->ud_lock);
+ }
+
+ /* Disable the mutex wait cancelation signal */
+ sigprocmask(SIG_BLOCK, &wakesigs, NULL);
+ flush_signals(current);
+
+ if (res)
+ continue;
+
+ wip = NULL;
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ udp->ud_work_lockwait = 0;
+ if (!list_empty(&udp->ud_work_queue)) {
+ wip = container_of(udp->ud_work_queue.next,
+ struct usbcam_workitem,
+ uw_links);
+ list_del_init(&wip->uw_links);
+ }
+
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+
+ if (wip) {
+ fn = wip->uw_func;
+ fn(wip);
+ }
+
+ usbcam_unlock(udp);
+ }
+
+ return 0;
+}
+
+void usbcam_work_init(struct usbcam_workitem *wip, usbcam_workfunc_t func)
+{
+ INIT_LIST_HEAD(&wip->uw_links);
+ wip->uw_dev = NULL;
+ wip->uw_func = func;
+}
+EXPORT_SYMBOL(usbcam_work_init);
+
+int usbcam_work_queue(struct usbcam_dev *udp, struct usbcam_workitem *wip)
+{
+ unsigned long flags;
+ int res;
+
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ if (!list_empty(&wip->uw_links)) {
+ res = -EALREADY;
+ assert(wip->uw_dev == udp);
+ } else if (udp->ud_work_refs) {
+ res = 0;
+ wip->uw_dev = udp;
+ list_add_tail(&wip->uw_links, &udp->ud_work_queue);
+ if (udp->ud_work_queue.next == &wip->uw_links)
+ wake_up_process(udp->ud_work_thread);
+ } else {
+ res = -EBUSY;
+ }
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+
+ return res;
+}
+EXPORT_SYMBOL(usbcam_work_queue);
+
+int usbcam_work_cancel(struct usbcam_dev *udp, struct usbcam_workitem *wip)
+{
+ unsigned long flags;
+ int res, wakeit = 0;
+
+ usbcam_chklock(udp);
+
+ res = -ENOENT;
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ if (!list_empty(&wip->uw_links)) {
+ res = 0;
+ assert(wip->uw_dev == udp);
+ if ((udp->ud_work_queue.next == &wip->uw_links) &&
+ udp->ud_work_lockwait)
+ wakeit = 1;
+ list_del_init(&wip->uw_links);
+ if (wakeit)
+ force_sig(SIGUSR1, udp->ud_work_thread);
+ }
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+
+ return res;
+}
+EXPORT_SYMBOL(usbcam_work_cancel);
+
+int usbcam_work_ref(struct usbcam_dev *udp)
+{
+ struct task_struct *kt_new;
+ unsigned long flags;
+
+ usbcam_chklock(udp);
+
+ /*
+ * We adjust this value under the spinlock to synchronize with
+ * usbcam_work_queue().
+ */
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ udp->ud_work_refs++;
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+
+ if (!udp->ud_work_thread) {
+ kt_new = kthread_create(usbcam_work_thread, udp,
+ udp->ud_dev_name);
+ if (!kt_new) {
+ usbcam_err(udp, "%s: could not create worker thread",
+ __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ udp->ud_work_thread = kt_new;
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(usbcam_work_ref);
+
+void usbcam_work_unref(struct usbcam_dev *udp)
+{
+ unsigned long flags;
+
+ usbcam_chklock(udp);
+
+ if (!udp->ud_work_refs) {
+ usbcam_warn(udp, "%s: work queue has zero refs", __FUNCTION__);
+ return;
+ }
+
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ udp->ud_work_refs--;
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+}
+EXPORT_SYMBOL(usbcam_work_unref);
+
+void usbcam_work_runqueue(struct usbcam_dev *udp)
+{
+ struct usbcam_workitem *wip;
+ unsigned long flags;
+ usbcam_workfunc_t fn;
+
+ usbcam_chklock(udp);
+
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ while (!list_empty(&udp->ud_work_queue)) {
+ wip = container_of(udp->ud_work_queue.next,
+ struct usbcam_workitem,
+ uw_links);
+ list_del_init(&wip->uw_links);
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+
+ fn = wip->uw_func;
+ fn(wip);
+
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ }
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+}
+EXPORT_SYMBOL(usbcam_work_runqueue);
+
+
+static void usbcam_work_stop(struct usbcam_dev *udp)
+{
+ struct task_struct *kt_stop = NULL;
+ unsigned long flags;
+
+ usbcam_lock(udp);
+
+ if (!udp->ud_work_refs) {
+ /* Prevent further tasks from being queued */
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ kt_stop = udp->ud_work_thread;
+ udp->ud_work_thread = NULL;
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+ }
+
+ usbcam_unlock(udp);
+
+ if (kt_stop) {
+ /*
+ * Wait for the queue to empty out, then stop the
+ * thread. It might be easier to just call
+ * usbcam_work_flush() and execute the remaining
+ * tasks synchronously in the current thread.
+ */
+ wait_event(usbcam_work_idle_wait,
+ list_empty(&udp->ud_work_queue));
+ kthread_stop(kt_stop);
+ }
+}
+
+
+
+/*
+ * V4L file_operations callout implementations
+ */
+
+static int usbcam_v4l_open(struct inode *inode, struct file *filp)
+{
+ struct usbcam_dev *udp;
+ struct usbcam_fh *ufp;
+ int autopm_ref = 0;
+ int work_ref = 0;
+ int res = 0;
+
+ /* The usbcam_dev is referenced by the videodev at this point */
+ udp = container_of(video_devdata(filp), struct usbcam_dev, ud_vdev);
+
+ ufp = (struct usbcam_fh *) kzalloc(sizeof(*ufp), GFP_KERNEL);
+ if (!ufp)
+ return -ENOMEM;
+
+ ufp->uf_dev = udp;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+ videobuf_queue_init(&ufp->uf_vbq,
+ &usbcam_videobuf_qops,
+ NULL,
+ NULL,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ V4L2_FIELD_INTERLACED,
+ sizeof(struct usbcam_frame), ufp);
+#else
+ videobuf_queue_pci_init(&ufp->uf_vbq,
+ &usbcam_videobuf_qops,
+ NULL,
+ NULL,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ V4L2_FIELD_INTERLACED,
+ sizeof(struct usbcam_frame), ufp);
+#endif
+
+ mutex_lock(&udp->ud_open_lock);
+
+ if (!udp->ud_user_refs) {
+ res = usb_autopm_get_interface(udp->ud_intf);
+ if (res)
+ goto bail_nolock;
+ autopm_ref = 1;
+ }
+
+ usbcam_lock(udp);
+
+ if (udp->ud_disconnected) {
+ usbcam_warn(udp, "udp is disconnected; bailing");
+ res = -ENODEV;
+ goto bail;
+ }
+
+ if (!udp->ud_user_refs) {
+ if (!udp->ud_minidrv->um_ops->no_workref_on_open) {
+ res = usbcam_work_ref(udp);
+ if (res)
+ goto bail;
+ work_ref = 1;
+ }
+
+ if (usbcam_minidrv_op_present(udp, open)) {
+ res = usbcam_minidrv_op(udp, open);
+ if (res) {
+ /* Can release/reacquire device lock */
+ if (work_ref)
+ usbcam_work_unref(udp);
+ assert(!udp->ud_user_refs);
+ goto bail;
+ }
+ }
+
+ /* Transfer the autopm reference */
+ assert(autopm_ref);
+ autopm_ref = 0;
+ }
+
+ udp->ud_user_refs++;
+ filp->private_data = ufp;
+ usbcam_get(udp);
+
+bail:
+ usbcam_unlock(udp);
+bail_nolock:
+ mutex_unlock(&udp->ud_open_lock);
+ if (res)
+ kfree(ufp);
+ if (autopm_ref)
+ usb_autopm_put_interface(udp->ud_intf);
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+static int usbcam_v4l_release(struct inode *inode, struct file *filp)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data;
+ struct usbcam_dev *udp = ufp->uf_dev;
+ int autopm_ref = 0;
+
+ videobuf_mmap_free(&ufp->uf_vbq);
+
+ mutex_lock(&udp->ud_open_lock);
+ usbcam_lock(udp);
+
+ assert(udp->ud_user_refs);
+ if (udp->ud_excl_owner == filp)
+ udp->ud_excl_owner = NULL;
+ kfree(ufp);
+ filp->private_data = NULL;
+
+ if (!--udp->ud_user_refs) {
+ usbcam_capture_stop(udp);
+
+ if (usbcam_minidrv_op_present(udp, close))
+ usbcam_minidrv_op(udp, close);
+ if (!udp->ud_minidrv->um_ops->no_workref_on_open)
+ usbcam_work_unref(udp);
+ autopm_ref = 1;
+ }
+
+ usbcam_unlock(udp);
+ mutex_unlock(&udp->ud_open_lock);
+ if (autopm_ref)
+ usb_autopm_put_interface(udp->ud_intf);
+ usbcam_work_maybe_stop(udp);
+ usbcam_put(udp);
+ return 0;
+}
+
+static ssize_t usbcam_v4l_read(struct file *filp, char __user *data,
+ size_t count, loff_t *ppos)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data;
+ int res;
+
+ res = videobuf_read_one(&ufp->uf_vbq, data, count, ppos,
+ (filp->f_flags & O_NONBLOCK) ? 1 : 0);
+ usbcam_work_maybe_stop(ufp->uf_dev);
+ return res;
+}
+
+static unsigned int usbcam_v4l_poll(struct file *filp,
+ struct poll_table_struct *wait)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data;
+
+ return videobuf_poll_stream(filp, &ufp->uf_vbq, wait);
+}
+
+static int usbcam_v4l_mmap(struct file *filp, struct vm_area_struct * vma)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data;
+
+ return videobuf_mmap_mapper(&ufp->uf_vbq, vma);
+}
+
+static void usbcam_dbg_v4l2_buffer_res(struct usbcam_dev *udp, int res,
+ void *arg, const char *prefix)
+{
+ struct v4l2_buffer *b __attribute__((unused)) =
+ (struct v4l2_buffer *) arg;
+
+ if (res) {
+ usbcam_dbg(udp, IOCTL_BUF, "%s res:%d", prefix, res);
+ return;
+ }
+
+ usbcam_dbg(udp, IOCTL_BUF, "%s out: index=%d type=%d bytesused=%d "
+ "flags=0x%x field=%d memory=%d m=0x%lx length=%d",
+ prefix, b->index, b->type, b->bytesused,
+ b->flags, b->field, b->memory, b->m.userptr, b->length);
+}
+
+static void usbcam_dbg_v4l2_pix_format(struct usbcam_dev *udp,
+ struct v4l2_pix_format *f,
+ const char *prefix)
+{
+ __u32 pixfmt = f->pixelformat;
+ if (!pixfmt)
+ pixfmt = 0x3f3f3f3f;
+ usbcam_dbg(udp, IOCTL_FMT, "%s wid=%d hgt=%d fmt=%.4s field=%d "
+ "bpl=%d size=%d cs=%d", prefix,
+ f->width, f->height, (char *) &pixfmt, f->field,
+ f->bytesperline, f->sizeimage, f->colorspace);
+}
+
+static void usbcam_dbg_v4l2_pix_format_res(struct usbcam_dev *udp, int res,
+ struct v4l2_pix_format *f,
+ const char *prefix)
+{
+ if (res) {
+ usbcam_dbg(udp, IOCTL_FMT, "%s %d", prefix, res);
+ return;
+ }
+ usbcam_dbg_v4l2_pix_format(udp, f, prefix);
+}
+
+static int usbcam_v4l_int_ioctl(struct inode *inodep, struct file *filp,
+ unsigned int cmd, void *arg)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data;
+ struct usbcam_dev *udp = ufp->uf_dev;
+ int droplock = 0, res;
+
+#ifdef CONFIG_VIDEO_V4L1_COMPAT
+ /* V4L1 API -- Handle this before v4l_compat_translate_ioctl(). */
+ if (cmd == VIDIOCGMBUF) {
+ struct v4l2_requestbuffers req;
+ struct video_mbuf *p = (struct video_mbuf *) arg;
+ unsigned int i;
+
+ /*
+ * APPBUG: motion keeps the first mmap, yet requests
+ * larger capture sizes.
+ */
+ usbcam_lock(udp);
+ ufp->uf_flags |= USBCAM_FH_USE_FIXED_FB;
+ usbcam_unlock(udp);
+
+ req.type = ufp->uf_vbq.type;
+ req.count = 2;
+ req.memory = V4L2_MEMORY_MMAP;
+ res = videobuf_reqbufs(&ufp->uf_vbq, &req);
+ if (res == -EBUSY)
+ {
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOCGMBUF reqbufs failed: device was busy"
+ " - closing and trying again.");
+
+ res = videobuf_streamoff(&ufp->uf_vbq);
+ if (res < 0)
+ {
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOCGMBUF reqbufs failed:"
+ "couldn't free previous buffer.");
+ return -EBUSY;
+ }
+ else
+ {
+ // we freed previous reqbuf OK.
+ usbcam_lock(udp);
+ ufp->uf_flags |= USBCAM_FH_USE_FIXED_FB;
+ usbcam_unlock(udp);
+
+ req.type = ufp->uf_vbq.type;
+ req.count = 2;
+ req.memory = V4L2_MEMORY_MMAP;
+ res = videobuf_reqbufs(&ufp->uf_vbq, &req);
+ }
+ }
+ else if (res < 0) {
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOCGMBUF reqbufs failed: %d", res);
+ return res;
+ }
+
+ p->frames = req.count;
+ p->size = 0;
+ for (i = 0; i < p->frames; i++) {
+ p->offsets[i] = ufp->uf_vbq.bufs[i]->boff;
+ p->size += ufp->uf_vbq.bufs[i]->bsize;
+ }
+
+ usbcam_dbg(udp, IOCTL_BUF, "VIDIOCGMBUF frames=%d size=%d",
+ p->frames, p->size);
+ return 0;
+ }
+ else if (cmd == VIDIOCGCAP) {
+ struct video_capability *cap = (struct video_capability *) arg;
+
+ usbcam_lock(udp);
+
+ strlcpy(cap->name, udp->ud_vdev.name, sizeof(cap->name));
+ cap->type = VID_TYPE_CAPTURE;
+ cap->audios = 0;
+ cap->channels = 1; /* only one input source, the camera */
+
+ cap->maxwidth = udp->ud_format.width;
+ cap->maxheight = udp->ud_format.height;
+
+ /*
+ * We lie, here. These values normally return 640x480, which is
+ * actually the maximum, not the minimum. Minimum is usually
+ * 160x120. It's sort of useful to lie since lots of software
+ * just stick with the minimum - we want higher res for the
+ * user where possible.
+ */
+
+ cap->minwidth = udp->ud_format.width;
+ cap->minheight = udp->ud_format.height;
+
+ usbcam_unlock(udp);
+ return 0;
+ }
+ else if (cmd == VIDIOCGFBUF) {
+ struct video_buffer *buf = (struct video_buffer *) arg;
+
+ usbcam_lock(udp);
+ buf->base = NULL; /* no physical frame buffer access */
+ buf->height = udp->ud_format.height;
+ buf->width = udp->ud_format.width;
+
+ // graciously stolen from drivers/media/video/v4l1-compat.c
+ // and modified slightly.
+ switch (udp->ud_format.pixelformat) {
+ case V4L2_PIX_FMT_RGB332:
+ buf->depth = 8;
+ break;
+ case V4L2_PIX_FMT_RGB555:
+ buf->depth = 15;
+ break;
+ case V4L2_PIX_FMT_RGB565:
+ buf->depth = 16;
+ break;
+ case V4L2_PIX_FMT_BGR24:
+ buf->depth = 24;
+ break;
+ case V4L2_PIX_FMT_BGR32:
+ buf->depth = 32;
+ break;
+ default:
+ buf->depth = 0;
+ }
+
+ if (udp->ud_format.bytesperline) {
+ buf->bytesperline = udp->ud_format.bytesperline;
+
+ /* typically comes out at 16 bit depth as non-rgb */
+ if (!buf->depth && buf->width)
+ buf->depth = ((udp->ud_format.bytesperline<<3)
+ + (buf->width-1) )
+ /buf->width;
+ } else {
+ buf->bytesperline =
+ (buf->width * buf->depth + 7) & 7;
+ buf->bytesperline >>= 3;
+ }
+
+ usbcam_unlock(udp);
+ return 0;
+ }
+#endif
+ /* If the command is any other V4L1 API, pass it to the translator */
+ if (_IOC_TYPE(cmd) == 'v')
+ {
+ return v4l_compat_translate_ioctl(inodep, filp, cmd, arg,
+ usbcam_v4l_int_ioctl);
+ }
+
+ /*
+ * Pass 1: filter out ioctls that we don't want to give
+ * the minidriver a chance to handle.
+ * These tend to be related to frame management, which is
+ * none of the minidriver's business, and we do not allow the
+ * minidriver to intercept.
+ */
+
+ switch (cmd) {
+ case VIDIOC_REQBUFS: {
+ struct v4l2_requestbuffers *r =
+ (struct v4l2_requestbuffers *) arg;
+ /* APPBUG: disable USE_FIXED_FB if we enter this path */
+ usbcam_lock(udp);
+ ufp->uf_flags &= ~(USBCAM_FH_USE_FIXED_FB);
+ usbcam_unlock(udp);
+
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOC_REQBUFS count=%d type=%d memory=%d",
+ r->count, r->type, r->memory);
+ res = videobuf_reqbufs(&ufp->uf_vbq, r);
+ usbcam_dbg(udp, IOCTL_BUF, "REQBUFS result=%d", res);
+ usbcam_work_maybe_stop(udp);
+ return res;
+ }
+ case VIDIOC_QUERYBUF: {
+ struct v4l2_buffer *b = (struct v4l2_buffer *) arg;
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOC_QUERYBUF in: index=%d type=%d",
+ b->index, b->type);
+ res = videobuf_querybuf(&ufp->uf_vbq, b);
+ usbcam_dbg_v4l2_buffer_res(udp, res, b, "VIDIOC_QUERYBUF");
+ usbcam_work_maybe_stop(udp);
+ return res;
+ }
+ case VIDIOC_QBUF: {
+ struct v4l2_buffer *b = (struct v4l2_buffer *) arg;
+ /*
+ * APPBUG: ptlib / Ekiga has an issue with zeroing the
+ * flags field before calling QBUF.
+ *
+ * Minidriver support for fast input switching is
+ * unavailable for the time being.
+ */
+ b->flags = 0;
+
+ usbcam_dbg(udp, IOCTL_BUF, "VIDIOC_QBUF in: index=%d type=%d",
+ b->index, b->type);
+ res = videobuf_qbuf(&ufp->uf_vbq, b);
+ usbcam_dbg_v4l2_buffer_res(udp, res, b, "VIDIOC_QBUF");
+ usbcam_work_maybe_stop(udp);
+ return res;
+ }
+ case VIDIOC_DQBUF: {
+ struct v4l2_buffer *b = (struct v4l2_buffer *) arg;
+ res = videobuf_dqbuf(&ufp->uf_vbq, b,
+ (filp->f_flags & O_NONBLOCK) ? 1 : 0);
+ usbcam_dbg_v4l2_buffer_res(udp, res, b, "VIDIOC_DQBUF");
+ usbcam_work_maybe_stop(udp);
+ return res;
+ }
+ case VIDIOC_STREAMON: {
+ enum v4l2_buf_type f = *(int *) arg;
+
+ if (f != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOC_STREAMON: invalid buf type %d", f);
+ return -EINVAL;
+ }
+ if (!udp->ud_excl_owner) {
+ usbcam_lock(udp);
+ if (!udp->ud_excl_owner)
+ udp->ud_excl_owner = filp;
+ usbcam_unlock(udp);
+ }
+ if (udp->ud_excl_owner != filp) {
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOC_STREAMON: not exclusive owner");
+ return -EBUSY;
+ }
+ res = videobuf_streamon(&ufp->uf_vbq);
+ usbcam_dbg(udp, IOCTL_BUF, "VIDIOC_STREAMON: res:%d", res);
+ usbcam_work_maybe_stop(udp);
+ return res;
+ }
+ case VIDIOC_STREAMOFF: {
+ enum v4l2_buf_type f = *(int *) arg;
+
+ if (f != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOC_STREAMOFF: invalid buf type %d", f);
+ return -EINVAL;
+ }
+ res = videobuf_streamoff(&ufp->uf_vbq);
+ usbcam_dbg(udp, IOCTL_BUF, "VIDIOC_STREAMOFF: res:%d", res);
+ usbcam_work_maybe_stop(udp);
+ return res;
+ }
+ }
+
+ /* Pass the ioctl to the minidriver, see if it takes responsibility */
+
+ if (usbcam_minidrv_op_present(udp, ioctl)) {
+ if (!udp->ud_minidrv->um_ops->unlocked_ioctl) {
+ usbcam_lock(udp);
+ droplock = 1;
+ }
+ res = usbcam_minidrv_op(udp, ioctl, cmd, arg);
+ if (droplock)
+ usbcam_unlock(udp);
+
+ usbcam_work_maybe_stop(udp);
+
+ if (res != -ENOIOCTLCMD)
+ return res;
+ }
+
+ /*
+ * Pass 2: If the minidriver doesn't handle the ioctl, do something
+ * default. The minidriver can override all of this stuff by
+ * intercepting.
+ */
+
+ switch (cmd) {
+
+ /* DEFAULT CAPABILITIES / DEVICE NAME / DRIVER NAME / BUS INFO */
+ case VIDIOC_QUERYCAP: {
+ struct v4l2_capability *cap = (struct v4l2_capability *) arg;
+
+ usbcam_lock(udp);
+ strlcpy(cap->driver,
+ usbcam_drvname(udp->ud_minidrv),
+ sizeof(cap->driver));
+ strlcpy(cap->card, udp->ud_vdev.name, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info),
+ "usb:%s", udp->ud_dev->dev.bus_id);
+ cap->version = udp->ud_minidrv->um_version;
+ cap->capabilities = (V4L2_CAP_VIDEO_CAPTURE |
+ V4L2_CAP_READWRITE |
+ V4L2_CAP_STREAMING);
+ usbcam_unlock(udp);
+ return 0;
+ }
+
+ /* DEFAULT FORMAT HANDLING - USE MINDIRIVER CALLOUTS */
+ case VIDIOC_ENUM_FMT: {
+ struct v4l2_fmtdesc *f = (struct v4l2_fmtdesc *) arg;
+ struct usbcam_pix_fmt *pf;
+
+ usbcam_lock(udp);
+
+ res = -EINVAL;
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ goto enum_fmt_done;
+ if (f->index >= udp->ud_fmt_array_len)
+ goto enum_fmt_done;
+
+ res = 0;
+ pf = (struct usbcam_pix_fmt *)
+ &(((u8 *) udp->ud_fmt_array)
+ [f->index * udp->ud_fmt_array_elem_size]);
+ f->flags = pf->flags;
+ f->pixelformat = pf->pixelformat;
+ strlcpy(f->description,
+ pf->description,
+ sizeof(f->description));
+
+ enum_fmt_done:
+ usbcam_unlock(udp);
+ return res;
+ }
+
+ case VIDIOC_G_FMT: {
+ struct v4l2_format *f = (struct v4l2_format *) arg;
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_G_FMT: invalid buf type %d"
+ "(wasn't of type VIDEO_CAPTURE: %d)",
+ f->type, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+ return -EINVAL;
+ }
+
+ usbcam_lock(udp);
+ f->fmt.pix = udp->ud_format;
+ usbcam_unlock(udp);
+ usbcam_dbg_v4l2_pix_format(udp, &f->fmt.pix,
+ "VIDIOC_G_FMT: res:");
+ return 0;
+ }
+
+ case VIDIOC_S_FMT: {
+ struct v4l2_format *f = (struct v4l2_format *) arg;
+
+ usbcam_lock(udp);
+
+ usbcam_dbg_v4l2_pix_format(udp, &f->fmt.pix,
+ "VIDIOC_S_FMT: param:");
+
+ if (udp->ud_disconnected) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_S_FMT: device disconnected");
+ res = -EIO;
+ goto s_fmt_done;
+ }
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_S_FMT: invalid buf type %d",
+ f->type);
+ res = -EINVAL;
+ goto s_fmt_done;
+ }
+ if (!udp->ud_excl_owner)
+ udp->ud_excl_owner = filp;
+ if (!memcmp(&f->fmt.pix, &udp->ud_format,
+ sizeof(f->fmt.pix))) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_S_FMT: nothing to do");
+ res = 0;
+ goto s_fmt_done;
+ }
+ if (!usbcam_minidrv_op_present(udp, set_format)) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_S_FMT: no minidriver op");
+ res = -EINVAL;
+ goto s_fmt_done;
+ }
+ if (udp->ud_excl_owner != filp) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_S_FMT: not exclusive owner");
+ res = -EBUSY;
+ goto s_fmt_done;
+ }
+ usbcam_capture_stop_nondestructive(udp);
+ if (udp->ud_capturing) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_S_FMT: capture in progress");
+ res = -EBUSY;
+ goto s_fmt_done;
+ }
+ res = usbcam_minidrv_op(udp, set_format, &f->fmt.pix);
+ usbcam_dbg_v4l2_pix_format_res(udp, res, &f->fmt.pix,
+ "VIDIOC_S_FMT: res:");
+
+ s_fmt_done:
+ usbcam_unlock(udp);
+ return res;
+ }
+
+ case VIDIOC_TRY_FMT: {
+ struct v4l2_format *f = (struct v4l2_format *) arg;
+
+ usbcam_lock(udp);
+
+ usbcam_dbg_v4l2_pix_format(udp, &f->fmt.pix,
+ "VIDIOC_TRY_FMT: param:");
+
+ if (udp->ud_disconnected) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_TRY_FMT: device disconnected");
+ res = -EIO;
+ goto s_fmt_done;
+ }
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_TRY_FMT: invalid buf type %d",
+ f->type);
+ res = -EINVAL;
+ goto try_fmt_done;
+ }
+ if (!usbcam_minidrv_op_present(udp, try_format)) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_TRY_FMT: no minidriver op");
+ res = -EINVAL;
+ goto try_fmt_done;
+ }
+
+ res = usbcam_minidrv_op(udp, try_format, &f->fmt.pix);
+ usbcam_dbg_v4l2_pix_format_res(udp, res, &f->fmt.pix,
+ "VIDIOC_TRY_FMT: res:");
+
+ try_fmt_done:
+ usbcam_unlock(udp);
+ usbcam_work_maybe_stop(udp);
+ return res;
+ }
+
+ /* DEFAULT CONTROL HANDLING - USE MINIDRIVER ARRAY / CALLOUTS */
+ case VIDIOC_QUERYCTRL: {
+ struct v4l2_queryctrl *a = (struct v4l2_queryctrl *) arg;
+ const struct usbcam_ctrl *ctrlp, *resp;
+ unsigned int targ, highest_id = 0;
+ int i, res;
+
+ usbcam_lock(udp);
+ droplock = 1;
+ targ = a->id;
+
+ resp = NULL;
+
+ if (targ & V4L2_CTRL_FLAG_NEXT_CTRL) {
+ /*
+ * Find the control with the least ID greater than or
+ * equal to a->id
+ */
+ targ &= ~V4L2_CTRL_FLAG_NEXT_CTRL;
+ for (i = 0, ctrlp = udp->ud_ctrl_array;
+ i < udp->ud_ctrl_array_len;
+ i++, ctrlp = (struct usbcam_ctrl *)
+ (((u8 *) ctrlp) +
+ udp->ud_ctrl_array_elem_size)) {
+ if (ctrlp->ctrl.id <= targ) {
+ if (!resp ||
+ (ctrlp->ctrl.id < resp->ctrl.id))
+ resp = ctrlp;
+ }
+ }
+
+ } else {
+ /* Find an exact match */
+ for (i = 0, ctrlp = udp->ud_ctrl_array;
+ i < udp->ud_ctrl_array_len;
+ i++, ctrlp = (struct usbcam_ctrl *)
+ (((u8 *) ctrlp) +
+ udp->ud_ctrl_array_elem_size)) {
+ if (ctrlp->ctrl.id == targ) {
+ resp = ctrlp;
+ break;
+ }
+ else if ((ctrlp->ctrl.id >=
+ V4L2_CID_PRIVATE_BASE) &&
+ (ctrlp->ctrl.id <
+ (V4L2_CID_PRIVATE_BASE + 1024)) &&
+ (ctrlp->ctrl.id > highest_id)) {
+ highest_id = ctrlp->ctrl.id;
+ }
+ }
+ }
+
+ if (!resp && !highest_id)
+ res = -EINVAL;
+
+ else if (!resp) {
+ /*
+ * Deal with the private control enumeration
+ * nonsense that the CTRL_FLAG_NEXT_CTRL thing
+ * fixes.
+ */
+ memset(a, 0, sizeof(*a));
+ a->id = targ;
+ a->type = V4L2_CTRL_TYPE_INTEGER;
+ strlcpy(a->name, "Disabled", sizeof(a->name));
+ a->flags = V4L2_CTRL_FLAG_DISABLED;
+ res = 0;
+
+ } else {
+ *a = resp->ctrl;
+ res = 0;
+
+ /*
+ * If a query function was provided, call it to
+ * postprocess the response structure, e.g. to set
+ * flags.
+ */
+ if (resp->query_fn) {
+ if (udp->ud_minidrv->um_ops->unlocked_ctrl) {
+ usbcam_unlock(udp);
+ droplock = 0;
+ }
+ res = resp->query_fn(udp, resp, a);
+ }
+ }
+
+ if (droplock)
+ usbcam_unlock(udp);
+ usbcam_work_maybe_stop(udp);
+ return res;
+ }
+
+ case VIDIOC_G_CTRL: {
+ struct v4l2_control *a = (struct v4l2_control *) arg;
+ const struct usbcam_ctrl *ctrlp, *resp;
+ int i, res;
+
+ usbcam_lock(udp);
+ droplock = 1;
+
+ if (udp->ud_disconnected) {
+ usbcam_unlock(udp);
+ return -EIO;
+ }
+
+ resp = NULL;
+ for (i = 0, ctrlp = udp->ud_ctrl_array;
+ i < udp->ud_ctrl_array_len;
+ i++, ctrlp = (struct usbcam_ctrl *)
+ (((u8 *) ctrlp) +
+ udp->ud_ctrl_array_elem_size)) {
+ if (ctrlp->ctrl.id == a->id) {
+ resp = ctrlp;
+ break;
+ }
+ }
+
+ if (!resp || (resp->ctrl.type == V4L2_CTRL_TYPE_BUTTON))
+ res = -EINVAL;
+ else {
+ if (udp->ud_minidrv->um_ops->unlocked_ctrl) {
+ usbcam_unlock(udp);
+ droplock = 0;
+ }
+ res = resp->get_fn(udp, resp, a);
+ }
+
+ if (droplock)
+ usbcam_unlock(udp);
+ usbcam_work_maybe_stop(udp);
+ return res;
+ }
+
+ case VIDIOC_S_CTRL: {
+ struct v4l2_control *a = (struct v4l2_control *) arg;
+ const struct usbcam_ctrl *ctrlp, *resp;
+ int i, res;
+
+ usbcam_lock(udp);
+ droplock = 1;
+
+ if (udp->ud_disconnected) {
+ usbcam_unlock(udp);
+ return -EIO;
+ }
+
+ resp = NULL;
+ for (i = 0, ctrlp = udp->ud_ctrl_array;
+ i < udp->ud_ctrl_array_len;
+ i++, ctrlp = (struct usbcam_ctrl *)
+ (((u8 *) ctrlp) +
+ udp->ud_ctrl_array_elem_size)) {
+ if (ctrlp->ctrl.id == a->id) {
+ resp = ctrlp;
+ break;
+ }
+ }
+
+ if (!resp) {
+ res = -EINVAL;
+ } else if (!resp->set_fn) {
+ /* Read-only control */
+ res = -EBUSY;
+ } else if ((resp->ctrl.type != V4L2_CTRL_TYPE_BUTTON) &&
+ ((a->value < resp->ctrl.minimum) ||
+ (a->value > resp->ctrl.maximum))) {
+ res = -ERANGE;
+ } else {
+ if (udp->ud_minidrv->um_ops->unlocked_ctrl) {
+ usbcam_unlock(udp);
+ droplock = 0;
+ }
+ res = resp->set_fn(udp, resp, a);
+ }
+
+ if (droplock)
+ usbcam_unlock(udp);
+ usbcam_work_maybe_stop(udp);
+ return res;
+ }
+
+ case VIDIOC_QUERYMENU: {
+ struct v4l2_querymenu *a = (struct v4l2_querymenu *) arg;
+ const struct usbcam_ctrl *ctrlp, *resp;
+ int i, res;
+
+ usbcam_lock(udp);
+
+ resp = NULL;
+ for (i = 0, ctrlp = udp->ud_ctrl_array;
+ i < udp->ud_ctrl_array_len;
+ i++, ctrlp = (struct usbcam_ctrl *)
+ (((u8 *) ctrlp) +
+ udp->ud_ctrl_array_elem_size)) {
+ if (ctrlp->ctrl.id == a->id) {
+ resp = ctrlp;
+ break;
+ }
+ }
+
+ if (!resp ||
+ (resp->ctrl.type != V4L2_CTRL_TYPE_MENU) ||
+ (a->index > resp->ctrl.maximum)) {
+ res = -EINVAL;
+ goto querymenu_done;
+ }
+
+ strlcpy(a->name, resp->menu_names[a->index], sizeof(a->name));
+ res = 0;
+
+ querymenu_done:
+ usbcam_unlock(udp);
+ return res;
+ }
+
+ /* DEFAULT INPUT HANDLING -- There is one input called "Camera" */
+ case VIDIOC_ENUMINPUT: {
+ const struct v4l2_input dfl_input = {
+ .name = "Camera",
+ .type = V4L2_INPUT_TYPE_CAMERA,
+ };
+ struct v4l2_input *inp = (struct v4l2_input *) arg;
+
+ if (inp->index > 0)
+ return -EINVAL;
+ *inp = dfl_input;
+ return 0;
+ }
+
+ case VIDIOCGCHAN: {
+ const struct video_channel dfl_input = {
+ .name = "Camera",
+ .type = VIDEO_TYPE_CAMERA
+ };
+ struct video_channel *inp = (struct video_channel *) arg;
+
+ if (inp->channel > 0)
+ return -EINVAL;
+ *inp = dfl_input;
+ return 0;
+ }
+
+ case VIDIOC_G_INPUT: {
+ unsigned int *i = (unsigned int *) arg;
+ *i = 0;
+ return 0;
+ }
+
+ case VIDIOC_S_INPUT: {
+ unsigned int *i = (unsigned int *) arg;
+ if (*i != 0)
+ return -EINVAL;
+ return 0;
+ }
+
+ /* DEFAULT VIDEO STANDARD HANDLING */
+ case VIDIOC_ENUMSTD: {
+ struct v4l2_standard *s = (struct v4l2_standard *) arg;
+ if (s->index > 0)
+ return -EINVAL;
+ v4l2_video_std_construct(s, V4L2_STD_NTSC_M, "NTSC-M");
+ return 0;
+ }
+
+ case VIDIOC_G_STD: {
+ v4l2_std_id *std = (v4l2_std_id *) arg;
+ *std = V4L2_STD_NTSC_M;
+ return 0;
+ }
+
+ case VIDIOC_S_STD:
+ return 0;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+ /* Workaround for v4l1_compat bug in older kernels */
+ case VIDIOC_G_FBUF: {
+ struct v4l2_framebuffer *f = (struct v4l2_framebuffer *) arg;
+ memset(f, 0, sizeof(*f));
+ return -ENOIOCTLCMD;
+ }
+#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */
+ }
+
+ /* video_usercopy() will convert this to -EINVAL. */
+ return -ENOIOCTLCMD;
+}
+
+static int usbcam_v4l_ioctl(struct inode *inodep, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inodep, filp, cmd, arg, usbcam_v4l_int_ioctl);
+}
+
+
+/*
+ * The template file_operations structure
+ *
+ * Each usbcam_minidrv_t contains its own copy of this, which
+ * is associated with the video4linux device created for that
+ * minidriver.
+ *
+ * In general, copies will differ only in the .owner field, which
+ * will refer to the minidriver module, not usbcam.
+ */
+
+static struct file_operations usbcam_v4l_fops_template = {
+ .owner = THIS_MODULE,
+ .open = usbcam_v4l_open,
+ .release = usbcam_v4l_release,
+ .read = usbcam_v4l_read,
+ .poll = usbcam_v4l_poll,
+ .mmap = usbcam_v4l_mmap,
+ .ioctl = usbcam_v4l_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+
+/*
+ * V4L2 videodev callout implementations
+ */
+
+static void usbcam_videodev_release(struct video_device *vfd)
+{
+ struct usbcam_dev *udp = container_of(vfd, struct usbcam_dev, ud_vdev);
+
+ usbcam_warn(udp, "releasing videodev device.");
+
+ usbcam_lock(udp);
+
+ assert(!udp->ud_videodev_released);
+ udp->ud_videodev_released = 1;
+
+ usbcam_unlock(udp);
+ usbcam_put(udp);
+}
+
+static struct video_device usbcam_videodev_template = {
+ .name = "usbcam-unknown",
+ .type = VFL_TYPE_GRABBER,
+ .type2 = VID_TYPE_CAPTURE,
+ .minor = -1,
+ .release = usbcam_videodev_release,
+};
+
+
+/*
+ * USB subsystem operation implementations
+ */
+
+static int usbcam_check_ctrl_array(struct usbcam_dev *udp,
+ const struct usbcam_ctrl *ctrlp,
+ int elem_size, int count)
+{
+ const struct usbcam_ctrl *startp = ctrlp, *xctrlp;
+ int i, j;
+ int errors = 0;
+
+ for (i = 0; i < count;
+ i++, ctrlp = (struct usbcam_ctrl *)
+ (((u8 *) ctrlp) + elem_size)) {
+ /* Verify that the ID isn't already registered */
+ for (j = 0, xctrlp = startp;
+ j < i;
+ j++, xctrlp = (struct usbcam_ctrl *)
+ (((u8 *) xctrlp) + elem_size)) {
+ if (ctrlp->ctrl.id == xctrlp->ctrl.id) {
+ usbcam_warn(udp, "control %d is registered "
+ "more than once",
+ ctrlp->ctrl.id);
+ errors++;
+ break;
+ }
+ }
+
+ /* Check minimum, maximum, step, and default */
+ switch (ctrlp->ctrl.type) {
+ case V4L2_CTRL_TYPE_INTEGER:
+ if (ctrlp->ctrl.minimum > ctrlp->ctrl.maximum) {
+ usbcam_warn(udp, "control %d has "
+ "minimum > maximum",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+ break;
+
+ case V4L2_CTRL_TYPE_BOOLEAN:
+ if (ctrlp->ctrl.minimum) {
+ usbcam_warn(udp, "control %d is BOOLEAN "
+ "and has minimum != 0",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+ if (ctrlp->ctrl.maximum != 1) {
+ usbcam_warn(udp, "control %d is BOOLEAN "
+ "and has maximum != 1",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+ if (ctrlp->ctrl.step != 1) {
+ usbcam_warn(udp, "control %d is BOOLEAN "
+ "and has step != 1",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+ break;
+
+ case V4L2_CTRL_TYPE_MENU:
+ if (ctrlp->ctrl.minimum) {
+ usbcam_warn(udp, "control %d is MENU and has "
+ "minimum != 0",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+ if (!ctrlp->ctrl.maximum) {
+ usbcam_warn(udp, "control %d is MENU and has "
+ "maximum == 0",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+ if (ctrlp->ctrl.step != 1) {
+ usbcam_warn(udp, "control %d is MENU and has "
+ "step != 1",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+ if (!ctrlp->menu_names) {
+ usbcam_warn(udp, "control %d is MENU and has "
+ "NULL menu_names",
+ ctrlp->ctrl.id);
+ errors++;
+ break;
+ }
+ break;
+
+ case V4L2_CTRL_TYPE_BUTTON:
+ if (ctrlp->ctrl.minimum) {
+ usbcam_warn(udp, "control %d is BUTTON "
+ "and has minimum != 0",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+ if (ctrlp->ctrl.maximum) {
+ usbcam_warn(udp, "control %d is BUTTON "
+ "and has maximum != 0",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+ if (ctrlp->ctrl.step) {
+ usbcam_warn(udp, "control %d is BUTTON "
+ "and has step != 0",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+ break;
+
+ default:
+ usbcam_warn(udp, "control %d is of invalid type %d",
+ ctrlp->ctrl.id, ctrlp->ctrl.type);
+ errors++;
+ continue;
+ }
+
+ /* Check the range */
+ if ((ctrlp->ctrl.default_value < ctrlp->ctrl.minimum) ||
+ (ctrlp->ctrl.default_value > ctrlp->ctrl.maximum)) {
+ usbcam_warn(udp, "control %d default out of range",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+
+ /* Check the get_fn callout */
+ if (ctrlp->ctrl.type == V4L2_CTRL_TYPE_BUTTON) {
+ if (ctrlp->get_fn) {
+ usbcam_warn(udp, "control %d is BUTTON "
+ "and has a get_fn callout",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+ } else {
+ if (!ctrlp->get_fn) {
+ usbcam_warn(udp, "control %d has no "
+ "get_fn callout",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+ }
+
+ if ((ctrlp->ctrl.type == V4L2_CTRL_TYPE_BUTTON) &&
+ !ctrlp->set_fn) {
+ usbcam_warn(udp, "control %d has no set_fn callout",
+ ctrlp->ctrl.id);
+ errors++;
+ }
+ }
+
+ return errors;
+}
+
+struct usbcam_claimed_interface {
+ struct list_head uc_links;
+ struct usb_interface *uc_intf;
+};
+
+static int usbcam_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *devid)
+{
+ struct usb_driver *drvp;
+ struct usbcam_dev *udp = NULL, *udpx;
+ usbcam_minidrv_t *minidrv;
+ struct usb_device *dev;
+ struct list_head *listp;
+ struct usbcam_claimed_interface *cip;
+ int minidrv_init_failed = 0;
+ int res, i;
+
+ /* Locate the mini-driver */
+ dev = interface_to_usbdev(intf);
+ drvp = to_usb_driver(intf->dev.driver);
+ minidrv = container_of(drvp, usbcam_minidrv_t, um_usbdrv);
+
+ /* Allocate and initialize a device structure */
+ udp = (struct usbcam_dev *) kzalloc(sizeof(*udp) +
+ minidrv->um_dev_privsize,
+ GFP_KERNEL);
+ if (!udp)
+ return -ENOMEM;
+
+ mutex_init(&udp->ud_open_lock);
+ mutex_init(&udp->ud_lock);
+ spin_lock_init(&udp->ud_work_lock);
+ INIT_LIST_HEAD(&udp->ud_work_queue);
+ udp->ud_minidrv = minidrv;
+ udp->ud_dev = usb_get_dev(dev);
+ udp->ud_intf = usb_get_intf(intf);
+ udp->ud_debug = minidrv->um_debug;
+ INIT_LIST_HEAD(&udp->ud_drv_links);
+ kref_init(&udp->ud_kref);
+
+ INIT_LIST_HEAD(&udp->ud_interface_list);
+ INIT_LIST_HEAD(&udp->ud_frame_cap_queue);
+
+ if (minidrv->um_dev_privsize) {
+ udp->ud_minidrv_data = &udp[1];
+ }
+
+ /* Set up the video4linux structure */
+ udp->ud_vdev = minidrv->um_videodev_template;
+
+ /* Add the device to the minidriver's list of active devices */
+ usbcam_lock(udp);
+
+ mutex_lock(&minidrv->um_lock);
+
+ /* Inefficiently find an unused ID in the device list */
+ i = 0;
+ udpx = NULL;
+ list_for_each(listp, &minidrv->um_dev_list) {
+ udpx = list_entry(listp, struct usbcam_dev, ud_drv_links);
+ if (udpx->ud_minidrv_id < 0) {
+ udpx = NULL;
+ continue;
+ }
+ if (udpx->ud_minidrv_id != i)
+ break;
+ udpx = NULL;
+ i++;
+ }
+
+ udp->ud_minidrv_id = i;
+ if (udpx) {
+ list_add_tail(&udp->ud_drv_links, &udpx->ud_drv_links);
+ } else {
+ list_add_tail(&udp->ud_drv_links, &minidrv->um_dev_list);
+ }
+
+ minidrv->um_dev_count++;
+ kref_get(&minidrv->um_kref);
+
+ snprintf(udp->ud_dev_name, sizeof(udp->ud_dev_name),
+ "%s-%d", usbcam_drvname(udp->ud_minidrv),
+ udp->ud_minidrv_id);
+
+ snprintf(udp->ud_vdev.name, sizeof(udp->ud_vdev.name),
+ "%s USB Camera #%d",
+ usbcam_drvname(minidrv), udp->ud_minidrv_id + 1);
+
+
+ mutex_unlock(&minidrv->um_lock);
+
+ /* Invoke the minidriver initialization callout */
+ udp->ud_initializing = 1;
+ res = usbcam_minidrv_op(udp, init, devid);
+ udp->ud_initializing = 0;
+ if (res) {
+ usbcam_dbg(udp, DEV_STATE, "minidriver init failed: %d", res);
+ minidrv_init_failed = 1;
+ goto out_nodisconn;
+ }
+
+ /* Complain if the device isn't filled out correctly */
+ if (!udp->ud_format.width || !udp->ud_format.height) {
+ usbcam_warn(udp, "minidriver did not set default size");
+ res = -EINVAL;
+ goto out;
+ }
+ if (!udp->ud_format.pixelformat) {
+ usbcam_warn(udp, "minidriver did not set default pixelformat");
+ res = -EINVAL;
+ goto out;
+ }
+
+ /* Check the control array */
+ if (udp->ud_ctrl_array_len &&
+ usbcam_check_ctrl_array(udp,
+ udp->ud_ctrl_array,
+ udp->ud_ctrl_array_elem_size,
+ udp->ud_ctrl_array_len)) {
+ usbcam_warn(udp, "minidriver supplied ctrl_array with errors");
+ res = -EINVAL;
+ goto out;
+ }
+
+ usb_set_intfdata(intf, udp);
+ usbcam_unlock(udp);
+
+ /*
+ * Register the device with video4linux
+ *
+ * BUG: video_register_device() may or may not call back
+ * into usbcam_videodev_release(), depending on how it fails,
+ * and if it does call back, its callback may be latent.
+ *
+ * We will assume no callback on failure.
+ */
+
+ if (udp->ud_vdev.minor != -1) {
+ /* Minidriver has indicated its preference for a minor */
+ res = video_register_device(&udp->ud_vdev, VFL_TYPE_GRABBER,
+ -1);
+ if (!res)
+ goto video_registered;
+ }
+
+ for (i = 0; i < minidrv->um_video_nr_array_len; i++) {
+ res = video_register_device(&udp->ud_vdev, VFL_TYPE_GRABBER,
+ minidrv->um_video_nr_array[i]);
+ if (!res)
+ goto video_registered;
+ }
+
+ res = video_register_device(&udp->ud_vdev, VFL_TYPE_GRABBER, -1);
+ if (res) {
+ usbcam_err(udp, "%s: video_register_device failed",
+ __FUNCTION__);
+ usbcam_lock(udp);
+ assert(!udp->ud_videodev_released);
+ udp->ud_videodev_released = 1;
+ goto out;
+ }
+
+video_registered:
+ usbcam_get(udp);
+ /*
+ * There should now be at least two references on udp:
+ * One for the primary USB interface in the non-disconnected state
+ * One for the videodev stuff
+ * One for each additional claimed interface
+ */
+
+ usbcam_dbg(udp, DEV_STATE, "registered as video%d",
+ udp->ud_vdev.minor);
+
+ usbcam_work_maybe_stop(udp);
+ return 0;
+
+out:
+ assert(!udp->ud_disconnected);
+ udp->ud_disconnected = 1;
+ if (usbcam_minidrv_op_present(udp, disconnect))
+ usbcam_minidrv_op(udp, disconnect);
+
+out_nodisconn:
+ while (!list_empty(&udp->ud_interface_list)) {
+ cip = list_entry(udp->ud_interface_list.next,
+ struct usbcam_claimed_interface,
+ uc_links);
+ list_del_init(&cip->uc_links);
+ usb_set_intfdata(cip->uc_intf, NULL);
+ usb_driver_release_interface(&minidrv->um_usbdrv,
+ cip->uc_intf);
+ usb_put_intf(cip->uc_intf);
+ kfree(cip);
+ usbcam_put(udp);
+ }
+
+ usbcam_unlock(udp);
+
+ if (minidrv_init_failed)
+ kref_put(&udp->ud_kref, usbcam_dev_free);
+ else
+ usbcam_put(udp);
+ return res;
+}
+
+static void usbcam_usb_disconnect(struct usb_interface *intf)
+{
+ struct usbcam_dev *udp = (struct usbcam_dev *) usb_get_intfdata(intf);
+ struct usbcam_claimed_interface *iterp, *cip;
+ int put_intf = 0;
+ int put_udp = 0;
+
+ if (!udp)
+ return;
+
+ usbcam_lock(udp);
+ if (!udp->ud_disconnected) {
+ udp->ud_disconnected = 1;
+ usbcam_unlock(udp);
+
+ usbcam_dbg(udp, DEV_STATE, "disconnected");
+ video_unregister_device(&udp->ud_vdev);
+
+ usbcam_dbg(udp, DEV_STATE, "unregistered from video%d",
+ udp->ud_vdev.minor);
+
+ usbcam_lock(udp);
+ if (usbcam_minidrv_op_present(udp, disconnect))
+ usbcam_minidrv_op(udp, disconnect);
+
+ usbcam_capture_stop(udp);
+ }
+
+ if (intf == udp->ud_intf) {
+ assert(!udp->ud_disconnected_primary);
+ udp->ud_disconnected_primary = 1;
+ put_udp = 1;
+
+ } else {
+ cip = NULL;
+ list_for_each_entry(iterp, &udp->ud_interface_list, uc_links) {
+ if (iterp->uc_intf == intf) {
+ cip = iterp;
+ break;
+ }
+ }
+
+ if (cip) {
+ list_del_init(&cip->uc_links);
+ kfree(cip);
+ put_intf = 1;
+ put_udp = 1;
+ } else {
+ usbcam_err(udp, "interface %p is not claimed", intf);
+ }
+ }
+
+ usb_set_intfdata(intf, NULL);
+ usbcam_unlock(udp);
+
+ usbcam_work_maybe_stop(udp);
+
+ if (put_intf)
+ usb_put_intf(intf);
+ if (put_udp)
+ usbcam_put(udp);
+}
+
+#if defined(CONFIG_PM)
+static int usbcam_usb_suspend(struct usb_interface *intf, pm_message_t msg)
+{
+ struct usbcam_dev *udp = (struct usbcam_dev *) usb_get_intfdata(intf);
+ int relock = 0, res = 0;
+ if (!udp) {
+ printk(KERN_WARNING "%s: no associated device\n",
+ __FUNCTION__);
+ return 0;
+ }
+
+ usbcam_lock(udp);
+ if ((intf != udp->ud_intf) || udp->ud_suspended) {
+ /* Do nothing */
+ } else if (usbcam_minidrv_op_present(udp, suspend)) {
+ usbcam_dbg(udp, DEV_STATE, "invoking minidriver suspend");
+ udp->ud_suspended = 1;
+ if (udp->ud_minidrv->um_ops->unlocked_pm) {
+ usbcam_unlock(udp);
+ relock = 1;
+ }
+
+ res = usbcam_minidrv_op(udp, suspend, msg);
+
+ if (relock)
+ usbcam_lock(udp);
+ if (res)
+ udp->ud_suspended = 0;
+ } else {
+ usbcam_dbg(udp, DEV_STATE, "no minidriver suspend method");
+ udp->ud_suspended = 1;
+ }
+
+ usbcam_unlock(udp);
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+static int usbcam_usb_resume(struct usb_interface *intf)
+{
+ struct usbcam_dev *udp = (struct usbcam_dev *) usb_get_intfdata(intf);
+ int relock = 0, res = 0;
+ if (!udp) {
+ printk(KERN_WARNING "%s: no associated device\n",
+ __FUNCTION__);
+ return 0;
+ }
+
+ usbcam_lock(udp);
+ if ((intf != udp->ud_intf) || !udp->ud_suspended) {
+ /* Nothing to do! */
+ } else if (usbcam_minidrv_op_present(udp, resume)) {
+ usbcam_dbg(udp, DEV_STATE, "invoking minidriver resume");
+ if (udp->ud_minidrv->um_ops->unlocked_pm) {
+ usbcam_unlock(udp);
+ relock = 1;
+ }
+ res = usbcam_minidrv_op(udp, resume);
+ if (relock)
+ usbcam_lock(udp);
+ } else
+ usbcam_dbg(udp, DEV_STATE, "no minidriver resume method");
+
+ if (!res)
+ udp->ud_suspended = 0;
+
+ usbcam_unlock(udp);
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+#endif /* defined(CONFIG_PM) */
+
+
+static const struct usb_driver usbcam_usb_driver_template = {
+ .name = "usbcam minidriver",
+ .probe = usbcam_usb_probe,
+ .disconnect = usbcam_usb_disconnect,
+#if defined(CONFIG_PM)
+ .suspend = usbcam_usb_suspend,
+ .resume = usbcam_usb_resume,
+#endif
+};
+
+
+/*
+ * Minidriver registration/unregistration
+ */
+
+int usbcam_register_mod(usbcam_minidrv_t **driverpp,
+ int minidrv_version, const char *minidrv_verx,
+ const struct usbcam_dev_ops *ops,
+ const int dev_priv_size,
+ const struct usb_device_id *id_table,
+ const int *video_nrs, int video_nrs_len,
+ int *debug, struct module *md, const char *modname)
+{
+ usbcam_minidrv_t *minidrv;
+ int res;
+
+ printk(KERN_INFO "usbcam: registering driver %s %d.%d.%d%s\n",
+ modname,
+ (minidrv_version >> 16) & 0xff,
+ (minidrv_version >> 8) & 0xff,
+ minidrv_version & 0xff,
+ minidrv_verx ? minidrv_verx : "");
+
+ minidrv = (usbcam_minidrv_t *) kzalloc(sizeof(*minidrv), GFP_KERNEL);
+ if (!minidrv) {
+ err("%s: Failed to allocate usbcam_minidrv_t", __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ kref_init(&minidrv->um_kref);
+ minidrv->um_owner = md;
+ minidrv->um_modname = modname;
+ minidrv->um_version = minidrv_version;
+ minidrv->um_debug = debug;
+ minidrv->um_dev_privsize = dev_priv_size;
+ INIT_LIST_HEAD(&minidrv->um_dev_list);
+ mutex_init(&minidrv->um_lock);
+ minidrv->um_video_nr_array = video_nrs;
+ minidrv->um_video_nr_array_len = video_nrs_len;
+
+ minidrv->um_ops = ops;
+
+ minidrv->um_usbdrv = usbcam_usb_driver_template;
+ minidrv->um_usbdrv.name = usbcam_drvname(minidrv);
+ minidrv->um_usbdrv.id_table = id_table;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
+ minidrv->um_usbdrv.supports_autosuspend =
+ minidrv->um_ops->supports_autosuspend;
+#endif
+
+ /*
+ * We have a separate fops per minidriver structure so that
+ * module reference counting works without egregious hacks.
+ */
+ minidrv->um_v4l_fops = usbcam_v4l_fops_template;
+ minidrv->um_v4l_fops.owner = minidrv->um_owner;
+
+ minidrv->um_videodev_template = usbcam_videodev_template;
+ minidrv->um_videodev_template.fops = &minidrv->um_v4l_fops;
+
+ *driverpp = minidrv;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)
+ res = usb_register_driver(&minidrv->um_usbdrv, minidrv->um_owner,
+ minidrv->um_modname);
+#else
+ res = usb_register_driver(&minidrv->um_usbdrv, minidrv->um_owner);
+#endif
+ if (res) {
+ kref_put(&minidrv->um_kref, usbcam_minidrv_release);
+ *driverpp = NULL;
+ }
+
+ usbcam_dbgm(minidrv, DEV_STATE, "registered minidriver");
+
+ return res;
+}
+EXPORT_SYMBOL(usbcam_register_mod);
+
+void usbcam_unregister(usbcam_minidrv_t *minidrv)
+{
+ usbcam_dbgm(minidrv, DEV_STATE, "unregistering minidriver");
+
+ usb_deregister(&minidrv->um_usbdrv);
+
+ if (minidrv->um_dev_count) {
+ err("%s: %d \"%s\" devices remain",
+ __FUNCTION__, minidrv->um_dev_count,
+ usbcam_drvname(minidrv));
+ }
+
+ kref_put(&minidrv->um_kref, usbcam_minidrv_release);
+}
+EXPORT_SYMBOL(usbcam_unregister);
+
+
+int usbcam_claim_interface(struct usbcam_dev *udp, int ifnum)
+{
+ struct usb_interface *intf;
+ struct usbcam_claimed_interface *cip;
+ int res;
+
+ usbcam_chklock(udp);
+
+ if (!udp->ud_initializing) {
+ usbcam_warn(udp, "%s may only be called from minidriver init",
+ __FUNCTION__);
+ return -EINVAL;
+ }
+
+ intf = usb_ifnum_to_if(udp->ud_dev, ifnum);
+ if (!intf) {
+ usbcam_warn(udp, "%s: interface %d does not exist",
+ __FUNCTION__, ifnum);
+ return -ENODEV;
+ }
+
+ res = usb_driver_claim_interface(&udp->ud_minidrv->um_usbdrv,
+ intf, NULL);
+
+ if (!res) {
+ cip = kmalloc(sizeof(*cip), GFP_KERNEL);
+ if (!cip) {
+ usb_driver_release_interface(&udp->ud_minidrv->
+ um_usbdrv, intf);
+ return -ENOMEM;
+ }
+
+ INIT_LIST_HEAD(&cip->uc_links);
+ cip->uc_intf = usb_get_intf(intf);
+ usb_set_intfdata(intf, udp);
+ usbcam_get(udp);
+ list_add_tail(&cip->uc_links, &udp->ud_interface_list);
+ }
+
+ return res;
+}
+EXPORT_SYMBOL(usbcam_claim_interface);
+
+
+/*
+ * Traverse the alternate setting list and find one that provides
+ * the least bandwidth that satisfies the minimum requirement.
+ */
+int usbcam_choose_altsetting(struct usbcam_dev *udp, int ifnum,
+ int pipe, int bytes_per_sec_min,
+ int pkt_min, int pkt_max,
+ int *altsetting_nr)
+{
+ struct usb_interface *intf;
+ const struct usb_host_interface *aintf;
+ const struct usb_endpoint_descriptor *epd = NULL;
+ int i, j;
+
+ int wmp, bw;
+ int best_alt = -1, best_alt_bw = 0;
+
+ usbcam_chklock(udp);
+
+ if (udp->ud_disconnected) {
+ usbcam_warn(udp, "%s: device is disconnected", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ if (ifnum < 0)
+ ifnum = udp->ud_intf->cur_altsetting->desc.bInterfaceNumber;
+
+ intf = usb_ifnum_to_if(udp->ud_dev, ifnum);
+ if (!intf) {
+ usbcam_warn(udp, "%s: interface %d does not exist",
+ __FUNCTION__, ifnum);
+ return -ENODEV;
+ }
+
+ if ((bytes_per_sec_min >= 0) &&
+ !usb_pipeisoc(pipe) && !usb_pipeint(pipe)) {
+ usbcam_warn(udp, "%s: minidriver specified bytes_per_sec_min "
+ "on non-iso non-int pipe", __FUNCTION__);
+ }
+
+ for (i = 0; i < intf->num_altsetting; i++) {
+
+ aintf = &intf->altsetting[i];
+ for (j = 0; j < aintf->desc.bNumEndpoints; j++) {
+ epd = &aintf->endpoint[j].desc;
+ if ((epd->bEndpointAddress &
+ USB_ENDPOINT_NUMBER_MASK) ==
+ usb_pipeendpoint(pipe))
+ break;
+ }
+
+ if (j == aintf->desc.bNumEndpoints) {
+ /* No endpoint 6 in this descriptor, huh?? */
+ usbcam_dbg(udp, ALTSETTING,
+ "altsetting %d has no EP%d",
+ i, usb_pipeendpoint(pipe));
+ continue;
+ }
+
+ if (((usb_pipetype(pipe) == PIPE_ISOCHRONOUS) &&
+ !usb_endpoint_xfer_isoc(epd)) ||
+ ((usb_pipetype(pipe) == PIPE_INTERRUPT) &&
+ !usb_endpoint_xfer_int(epd)) ||
+ (usb_pipein(pipe) && !usb_endpoint_dir_in(epd)) ||
+ (!usb_pipein(pipe) && usb_endpoint_dir_in(epd))) {
+ /* Something is horribly wrong */
+ usbcam_dbg(udp, ALTSETTING,
+ "altsetting %d has unexpected EP%d",
+ i, usb_pipeendpoint(pipe));
+ continue;
+ }
+
+ bw = 0;
+ wmp = le16_to_cpu(epd->wMaxPacketSize);
+
+ /* Bandwidth only applies to iso & int pipes */
+ if (usb_pipeisoc(pipe) || usb_pipeint(pipe)) {
+ if (udp->ud_dev->speed == USB_SPEED_HIGH) {
+ /* 8 uframes per regular frame */
+ bw = 8000;
+
+ /* high bandwidth endpoint? */
+ wmp = ((wmp & 0x7ff) *
+ (((wmp >> 11) & 0x3) + 1));
+ } else {
+ bw = 1000;
+ wmp &= 0x7ff;
+ }
+
+ bw *= wmp;
+
+ /* Divide by interval / frame skippage */
+ bw = bw / (1 << (epd->bInterval - 1));
+
+ usbcam_dbg(udp, ALTSETTING,
+ "altsetting %d provides %d B/s bandwidth",
+ i, bw);
+
+ /* Check the bandwidth */
+ if (bw < bytes_per_sec_min)
+ continue;
+
+ } else
+ wmp &= 0x7ff;
+
+ /* Check the packet size */
+ if (((pkt_min >= 0) && (wmp < pkt_min)) ||
+ ((pkt_max >= 0) && (wmp > pkt_max)))
+ continue;
+
+ if ((best_alt < 0) || (bw < best_alt_bw)) {
+ best_alt = i;
+ best_alt_bw = bw;
+ }
+ }
+
+ if (best_alt == -1)
+ return -ENODEV;
+
+ *altsetting_nr = best_alt;
+ return 0;
+}
+EXPORT_SYMBOL(usbcam_choose_altsetting);
+
+
+/*
+ * DMA buffer helper routines
+ */
+static int usbcam_urb_allocbuf(struct usbcam_dev *udp, struct urb *urbp,
+ size_t nbytes)
+{
+ urbp->transfer_buffer = usb_buffer_alloc(udp->ud_dev,
+ nbytes,
+ GFP_KERNEL,
+ &urbp->transfer_dma);
+ if (!urbp->transfer_buffer)
+ return -ENOMEM;
+
+ urbp->dev = udp->ud_dev;
+ urbp->transfer_buffer_length = nbytes;
+ urbp->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ return 0;
+}
+
+static inline void usbcam_urb_freebuf(struct urb *urbp)
+{
+ usb_buffer_free(urbp->dev,
+ urbp->transfer_buffer_length,
+ urbp->transfer_buffer,
+ urbp->transfer_dma);
+}
+
+
+/*
+ * Isochronous stream helper functions
+ * This depends on the other usbcam stuff, but they don't depend on it,
+ * and it should be considered an extension sub-library.
+ */
+
+/* Default isostream parameters */
+#define USBCAM_DFL_ISO_URBS 8
+#define USBCAM_DFL_ISO_URB_PKTS 32
+
+/*
+ * This structure represents one Isoc request - URB and buffer
+ */
+struct usbcam_isobuf {
+ struct list_head ib_links;
+ struct usbcam_isostream *ib_isostream;
+ struct urb *ib_urb;
+ struct task_struct **ib_worker;
+ struct usbcam_workitem ib_workitem;
+};
+
+/* isop->iso_lock must be held on entry */
+static void usbcam_isostream_resubmit(struct usbcam_isostream *isop,
+ struct usbcam_isobuf *ibp)
+{
+ int res;
+
+ list_del(&ibp->ib_links);
+ list_add(&ibp->ib_links, &isop->iso_active_list);
+
+ res = usb_submit_urb(ibp->ib_urb, GFP_ATOMIC);
+ if (res == -EL2NSYNC)
+ res = usb_submit_urb(ibp->ib_urb, GFP_ATOMIC);
+ if (res) {
+ usbcam_dbg(isop->iso_dev, ISOSTREAM,
+ "iso resubmit %p failed: %d", ibp, res);
+ isop->iso_resubmit_err = res;
+
+ list_del(&ibp->ib_links);
+ list_add(&ibp->ib_links, &isop->iso_unused_list);
+
+ (void) usbcam_work_queue(isop->iso_dev,
+ &isop->iso_error_workitem);
+ }
+}
+
+static void usbcam_isostream_urb_process(struct usbcam_workitem *work)
+{
+ struct usbcam_isobuf *ibp = container_of(work, struct usbcam_isobuf,
+ ib_workitem);
+ struct usbcam_isostream *isop = ibp->ib_isostream;
+ struct task_struct *me = current;
+ long flags;
+ int i;
+
+ usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso processing %p", ibp);
+
+ assert(!ibp->ib_worker);
+ ibp->ib_worker = &me;
+
+ for (i = 0; i < ibp->ib_urb->number_of_packets; i++) {
+ char *buf = (((char *) ibp->ib_urb->transfer_buffer) +
+ ibp->ib_urb->iso_frame_desc[i].offset);
+ int len = ibp->ib_urb->iso_frame_desc[i].actual_length;
+ int status = ibp->ib_urb->iso_frame_desc[i].status;
+
+ ibp->ib_urb->iso_frame_desc[i].actual_length = 0;
+ ibp->ib_urb->iso_frame_desc[i].status = 0;
+
+ isop->iso_ops->packet_done(isop->iso_dev,
+ isop, buf, len, status);
+ if (!me)
+ return;
+ }
+
+ assert(ibp->ib_worker == &me);
+ ibp->ib_worker = NULL;
+
+ spin_lock_irqsave(&isop->iso_lock, flags);
+
+ if (isop->iso_streaming && (isop->iso_resubmit_err == -ENOSPC)) {
+ /* Try to limp along with iso underflows */
+
+ usbcam_isostream_resubmit(isop, ibp);
+
+ if ((isop->iso_active_list.next !=
+ isop->iso_active_list.prev) &&
+ (isop->iso_resubmit_err == -ENOSPC))
+ isop->iso_resubmit_err = 0;
+
+ } else {
+ list_del(&ibp->ib_links);
+ list_add(&ibp->ib_links, &isop->iso_unused_list);
+ }
+
+ spin_unlock_irqrestore(&isop->iso_lock, flags);
+}
+
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
+static void usbcam_isostream_urb_complete(struct urb *urb)
+#else
+static void usbcam_isostream_urb_complete(struct urb *urb,
+ struct pt_regs *unused)
+#endif
+{
+ struct usbcam_isobuf *ibp = (struct usbcam_isobuf *) urb->context;
+ struct usbcam_isostream *isop = ibp->ib_isostream;
+ long flags;
+ int res;
+
+ usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso urb complete: %p", ibp);
+
+ spin_lock_irqsave(&isop->iso_lock, flags);
+
+ if (list_empty(&ibp->ib_links)) {
+ /* We are being singled out for cancelation, do nothing */
+ usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso canceled, ignoring");
+ goto done;
+ }
+
+ else if (!isop->iso_streaming) {
+ /* Quietly and politely move this URB to the unused list */
+ list_del(&ibp->ib_links);
+ list_add(&ibp->ib_links, &isop->iso_unused_list);
+ usbcam_dbg(isop->iso_dev, ISOSTREAM,
+ "not resubmitting, streaming off");
+ goto done;
+ }
+
+ /* Move to the done queue, submit a new URB, wake */
+ list_del(&ibp->ib_links);
+ list_add_tail(&ibp->ib_links, &isop->iso_complete_list);
+
+ res = usbcam_work_queue(isop->iso_dev, &ibp->ib_workitem);
+ if (res) {
+ assert(res == -EBUSY);
+ list_del(&ibp->ib_links);
+ list_add_tail(&ibp->ib_links, &isop->iso_unused_list);
+ goto done;
+ }
+
+ if (isop->iso_resubmit_err &&
+ (isop->iso_resubmit_err != -ENOSPC)) {
+ /* Nothing to do here... */
+ usbcam_dbg(isop->iso_dev, ISOSTREAM,
+ "not resubmitting, pending error");
+ goto done;
+ }
+
+ if (list_empty(&isop->iso_unused_list)) {
+ isop->iso_resubmit_err = -ENOSPC;
+ usbcam_dbg(isop->iso_dev, ISOSTREAM,
+ "not resubmitting, no URBs available");
+ goto done;
+ }
+
+ ibp = list_entry(isop->iso_unused_list.next,
+ struct usbcam_isobuf, ib_links);
+
+ usbcam_isostream_resubmit(isop, ibp);
+
+done:
+ spin_unlock_irqrestore(&isop->iso_lock, flags);
+}
+
+static void usbcam_isostream_freebuf(struct usbcam_isobuf *ibp)
+{
+ assert(list_empty(&ibp->ib_links));
+
+ usbcam_urb_freebuf(ibp->ib_urb);
+ usb_free_urb(ibp->ib_urb);
+ kfree(ibp);
+}
+
+static struct usbcam_isobuf *
+usbcam_isostream_allocbuf(struct usbcam_isostream *isop)
+{
+ struct usbcam_isobuf *ibp;
+ size_t nbytes;
+ int i;
+
+ ibp = kzalloc(sizeof(*ibp), GFP_KERNEL);
+ if (!ibp)
+ return NULL;
+
+ INIT_LIST_HEAD(&ibp->ib_links);
+ usbcam_work_init(&ibp->ib_workitem, usbcam_isostream_urb_process);
+ ibp->ib_isostream = isop;
+
+ ibp->ib_urb = usb_alloc_urb(isop->iso_packet_count, GFP_KERNEL);
+ if (!ibp->ib_urb) {
+ kfree(ibp);
+ return NULL;
+ }
+
+ nbytes = (isop->iso_packet_len * isop->iso_packet_count);
+
+ if (usbcam_urb_allocbuf(isop->iso_dev, ibp->ib_urb, nbytes)) {
+ usb_free_urb(ibp->ib_urb);
+ kfree(ibp);
+ return NULL;
+ }
+
+ ibp->ib_urb->context = ibp;
+ ibp->ib_urb->number_of_packets = isop->iso_packet_count;
+
+
+ for (i = 0; i < isop->iso_packet_count; i++) {
+ ibp->ib_urb->iso_frame_desc[i].offset =
+ (i * isop->iso_packet_len);
+ ibp->ib_urb->iso_frame_desc[i].length = isop->iso_packet_len;
+ ibp->ib_urb->iso_frame_desc[i].actual_length = 0;
+ ibp->ib_urb->iso_frame_desc[i].status = 0;
+ }
+
+ return ibp;
+}
+
+static void usbcam_isostream_freebufs(struct list_head *head)
+{
+ struct usbcam_isobuf *ibp;
+ while (!list_empty(head)) {
+ ibp = list_entry(head->next, struct usbcam_isobuf, ib_links);
+ list_del_init(&ibp->ib_links);
+ usbcam_isostream_freebuf(ibp);
+ }
+}
+
+static int usbcam_isostream_allocbufs(struct usbcam_isostream *isop,
+ struct list_head *head, int count)
+{
+ struct usbcam_isobuf *ibp;
+ struct list_head new_bufs;
+
+ INIT_LIST_HEAD(&new_bufs);
+
+ while (count--) {
+ ibp = usbcam_isostream_allocbuf(isop);
+ if (!ibp) {
+ usbcam_isostream_freebufs(&new_bufs);
+ return -ENOMEM;
+ }
+ list_add_tail(&ibp->ib_links, &new_bufs);
+ }
+
+ list_splice(&new_bufs, head);
+ return 0;
+}
+
+static void usbcam_isostream_error(struct usbcam_workitem *work)
+{
+ struct usbcam_isostream *isop = container_of(work,
+ struct usbcam_isostream,
+ iso_error_workitem);
+ unsigned long flags;
+ int sts;
+
+ spin_lock_irqsave(&isop->iso_lock, flags);
+ sts = isop->iso_resubmit_err;
+ isop->iso_resubmit_err = 0;
+ spin_unlock_irqrestore(&isop->iso_lock, flags);
+
+ if (sts && isop->iso_ops && isop->iso_ops->submit_error)
+ isop->iso_ops->submit_error(isop->iso_dev, isop, sts);
+}
+
+int usbcam_isostream_init(struct usbcam_isostream *isop,
+ struct usbcam_dev *udp,
+ int ep, struct usbcam_isostream_ops *ops,
+ int pktcount, int nurbs, int interval, int pktlen)
+{
+ int res;
+
+ if (!interval) {
+ /* FIXME: find the appropriate interval for the endpoint */
+ return -EINVAL;
+ }
+
+ if (!pktlen) {
+ /* Choose a packet length based on the current altsetting */
+ pktlen = usb_maxpacket(udp->ud_dev,
+ usb_rcvisocpipe(udp->ud_dev, ep), 0);
+ if (!pktlen) {
+ usbcam_dbg(udp, ISOSTREAM,
+ "current altsetting for ep%d has "
+ "maxpacket=0", ep);
+ return -EINVAL;
+ }
+ if (udp->ud_dev->speed == USB_SPEED_HIGH)
+ pktlen = (pktlen & 0x7ff) *
+ (((pktlen >> 11) & 0x3) + 1);
+ else
+ pktlen &= 0x7ff;
+
+ usbcam_dbg(udp, ISOSTREAM, "isostream using pktlen %d",
+ pktlen);
+ }
+
+ if (!pktcount)
+ pktcount = USBCAM_DFL_ISO_URB_PKTS;
+ if (!nurbs)
+ nurbs = USBCAM_DFL_ISO_URBS;
+ if (nurbs < 2) {
+ usbcam_warn(udp, "%s: at least two iso URBs are required",
+ __FUNCTION__);
+ nurbs = 2;
+ }
+
+ memset(isop, 0, sizeof(*isop));
+ isop->iso_dev = udp;
+ isop->iso_endpoint = ep;
+ isop->iso_packet_len = pktlen;
+ isop->iso_packet_count = pktcount;
+ isop->iso_urb_interval = interval;
+ isop->iso_ops = ops;
+ spin_lock_init(&isop->iso_lock);
+ INIT_LIST_HEAD(&isop->iso_unused_list);
+ INIT_LIST_HEAD(&isop->iso_active_list);
+ INIT_LIST_HEAD(&isop->iso_complete_list);
+ usbcam_work_init(&isop->iso_error_workitem, usbcam_isostream_error);
+
+ res = usbcam_isostream_allocbufs(isop, &isop->iso_unused_list, nurbs);
+ return res;
+}
+EXPORT_SYMBOL(usbcam_isostream_init);
+
+void usbcam_isostream_stop(struct usbcam_isostream *isop)
+{
+ long flags;
+ struct usbcam_isobuf *ibp, *prev;
+ int res;
+
+ usbcam_chklock(isop->iso_dev);
+
+ usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso stream stopping");
+ spin_lock_irqsave(&isop->iso_lock, flags);
+
+ if (isop->iso_streaming) {
+ isop->iso_streaming = 0;
+
+ /* Cancel all in-flight requests */
+ while (!list_empty(&isop->iso_active_list)) {
+ ibp = list_entry(isop->iso_active_list.prev,
+ struct usbcam_isobuf, ib_links);
+ list_del_init(&ibp->ib_links);
+ spin_unlock_irqrestore(&isop->iso_lock, flags);
+ usb_kill_urb(ibp->ib_urb);
+ spin_lock_irqsave(&isop->iso_lock, flags);
+ list_add(&ibp->ib_links, &isop->iso_unused_list);
+ }
+
+ /* Cancel all done queue work items */
+ list_for_each_entry_safe(ibp, prev,
+ &isop->iso_complete_list,
+ ib_links) {
+
+ res = usbcam_work_cancel(isop->iso_dev,
+ &ibp->ib_workitem);
+
+ if (!ibp->ib_worker)
+ assert(!res);
+ else {
+ assert(res);
+ assert(*ibp->ib_worker == current);
+ *ibp->ib_worker = NULL;
+ ibp->ib_worker = NULL;
+ }
+
+ list_del(&ibp->ib_links);
+ list_add_tail(&ibp->ib_links, &isop->iso_unused_list);
+ }
+
+ /* Cancel the error work item */
+ (void) usbcam_work_cancel(isop->iso_dev,
+ &isop->iso_error_workitem);
+ }
+
+ /* Clear the resubmission error code */
+ isop->iso_resubmit_err = 0;
+
+ spin_unlock_irqrestore(&isop->iso_lock, flags);
+ usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso stream stopped");
+}
+EXPORT_SYMBOL(usbcam_isostream_stop);
+
+int usbcam_isostream_start(struct usbcam_isostream *isop)
+{
+ long flags;
+ struct usbcam_dev *udp;
+ struct usbcam_isobuf *ibp;
+ int submitted = 0, res;
+
+ udp = isop->iso_dev;
+
+ usbcam_chklock(udp);
+
+ spin_lock_irqsave(&isop->iso_lock, flags);
+ if (isop->iso_streaming) {
+ spin_unlock_irqrestore(&isop->iso_lock, flags);
+ usbcam_warn(udp, "%s: already streaming", __FUNCTION__);
+ return -EEXIST;
+ }
+
+ if (list_empty(&isop->iso_unused_list) ||
+ (isop->iso_unused_list.next == isop->iso_unused_list.prev)) {
+ spin_unlock_irqrestore(&isop->iso_lock, flags);
+ usbcam_warn(udp, "%s: not enough unused iso URBs",
+ __FUNCTION__);
+ return -ENOENT;
+ }
+
+ usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso stream starting");
+ isop->iso_streaming = 1;
+
+ /*
+ * Fill out generic details of each URB.
+ * The interval and endpoints can be changed after init, but
+ * the packet size and URB count cannot.
+ */
+ list_for_each_entry(ibp, &isop->iso_unused_list, ib_links) {
+ ibp->ib_urb->pipe = usb_rcvisocpipe(ibp->ib_urb->dev,
+ isop->iso_endpoint);
+ ibp->ib_urb->interval = isop->iso_urb_interval;
+ ibp->ib_urb->transfer_flags |= URB_ISO_ASAP;
+ ibp->ib_urb->complete = usbcam_isostream_urb_complete;
+ ibp->ib_urb->start_frame = 0;
+ }
+
+ /*
+ * Submit two (2) URBs.
+ */
+ while (1) {
+ assert(!list_empty(&isop->iso_unused_list));
+ ibp = list_entry(isop->iso_unused_list.next,
+ struct usbcam_isobuf, ib_links);
+
+ list_del(&ibp->ib_links);
+ list_add_tail(&ibp->ib_links, &isop->iso_active_list);
+
+ spin_unlock_irqrestore(&isop->iso_lock, flags);
+
+ usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso submit %p", ibp);
+ res = usb_submit_urb(ibp->ib_urb, GFP_KERNEL);
+ if (res == -EL2NSYNC)
+ res = usb_submit_urb(ibp->ib_urb, GFP_KERNEL);
+
+ if (res) {
+ usbcam_dbg(isop->iso_dev, ISOSTREAM,
+ "%s: ISO URB submit failed: %d",
+ __FUNCTION__, res);
+ usbcam_isostream_stop(isop);
+ return res;
+ }
+
+ if (++submitted == 2)
+ break;
+
+ spin_lock_irqsave(&isop->iso_lock, flags);
+ }
+
+ usbcam_dbg(isop->iso_dev, ISOSTREAM, "iso stream started");
+ return 0;
+
+}
+EXPORT_SYMBOL(usbcam_isostream_start);
+
+void usbcam_isostream_cleanup(struct usbcam_isostream *isop)
+{
+ usbcam_isostream_stop(isop);
+ usbcam_isostream_freebufs(&isop->iso_unused_list);
+ assert(list_empty(&isop->iso_active_list));
+ assert(list_empty(&isop->iso_complete_list));
+}
+EXPORT_SYMBOL(usbcam_isostream_cleanup);
+
+
+#ifdef usbcam_hexdump
+#undef usbcam_hexdump
+#endif
+#define dumpable_char(X) (((X) >= ' ') && ((X) <= '~'))
+void usbcam_hexdump(struct usbcam_dev *udp, const u8 *buf, size_t len)
+{
+ const int bpl = 16;
+ const int cend_max = ((bpl * 4) + 1);
+ static const char n2x[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+ char x, outbuf[cend_max + 1];
+ unsigned int cstart, cend, xpos, cpos, offset;
+
+ offset = 0;
+ cstart = (3 * bpl) + 1;
+ cend = cstart + bpl;
+ outbuf[cend] = '\0';
+ goto beginning;
+
+ while (len) {
+ x = *buf++;
+ outbuf[xpos++] = n2x[(x >> 4) & 0xf];
+ outbuf[xpos++] = n2x[x & 0xf];
+ outbuf[cpos+cstart] = dumpable_char(x) ? x : '.';
+ cpos++;
+ xpos++;
+ len--;
+
+ if (!len || (cpos == bpl)) {
+ printk(KERN_DEBUG "%s: %08x %s\n",
+ udp->ud_dev_name, offset, outbuf);
+ offset += bpl;
+
+ beginning:
+ memset(outbuf, ' ', cend);
+ cpos = 0;
+ xpos = 0;
+ }
+ }
+}
+EXPORT_SYMBOL(usbcam_hexdump);
+
+
+MODULE_DESCRIPTION("Abstraction Library for USB Webcam Drivers");
+MODULE_AUTHOR("Sam Revitch <samr7@cs.washington.edu>, "
+ "Alexander Hixon <hixon.alexander@mediati.org>");
+MODULE_LICENSE("GPL");
diff --git a/usbcam/usbcam.h b/usbcam/usbcam.h
@@ -0,0 +1,757 @@
+/*
+ * USBCAM abstraction library for USB webcam drivers
+ * Version 0.1.1
+ *
+ * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This library is intended to ease the process of creating drivers
+ * for simpler USB webcam devices. It handles most V4L interactions, and
+ * all USB driver entry points. It provides a minidriver callback API
+ * for handling most common tasks.
+ */
+
+#ifndef __USBCAM_H__
+#define __USBCAM_H__
+
+#ifdef __KERNEL__
+
+#define CONFIG_USB_USBCAM_DEBUG
+
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/videodev.h>
+#include <media/v4l2-common.h>
+
+/* The actual per-minidriver structure is opaque */
+typedef struct usbcam_minidrv usbcam_minidrv_t;
+struct usbcam_dev;
+struct usbcam_curframe;
+
+
+/*
+ * Log file and debug infrastructure
+ */
+#define usbcam_info(UDP, FMT, ARG...) do { \
+ printk(KERN_INFO "%s: " FMT "\n", \
+ (UDP)->ud_dev_name, ## ARG); \
+} while (0)
+#define usbcam_warn(UDP, FMT, ARG...) do { \
+ printk(KERN_WARNING "%s: " FMT "\n", \
+ (UDP)->ud_dev_name, ## ARG); \
+} while (0)
+#define usbcam_err(UDP, FMT, ARG...) do { \
+ printk(KERN_ERR "%s: " FMT "\n", \
+ (UDP)->ud_dev_name, ## ARG); \
+} while (0)
+
+
+#if defined(CONFIG_USB_USBCAM_DEBUG)
+#define usbcam_dbg(UDP, SUBSYS, FMT, ARG...) do { \
+ if ((UDP)->ud_debug && \
+ (*(UDP)->ud_debug & (1UL << USBCAM_DBG_ ## SUBSYS))) \
+ printk(KERN_DEBUG "%s: " FMT "\n", \
+ (UDP)->ud_dev_name, ## ARG); \
+} while (0)
+#define usbcam_assert(expr) \
+do { \
+ if (!(expr)) { \
+ printk(KERN_ERR "%s:%d: assertion \"" # expr "\" " \
+ "failed", __FILE__, __LINE__); \
+ dump_stack(); \
+ } \
+} while (0)
+extern void usbcam_hexdump(struct usbcam_dev *udp, const u8 *buf, size_t len);
+#else
+#define usbcam_dbg(UDP, SUBSYS, FMT, ARG...)
+#define usbcam_assert(expr)
+#define usbcam_hexdump(udp, buf, len)
+#endif
+
+/*
+ * Debug subsystem bit values
+ *
+ * The usbcam_dev.ud_debug debug subsystem mask is a pointer to a
+ * per-minidriver integer, which is usually a module parameter and can
+ * be manipulated via sysfs.
+ */
+enum {
+ USBCAM_DBG_VIDEOBUF = 0,
+ USBCAM_DBG_CAPTURE,
+ USBCAM_DBG_IOCTL_BUF,
+ USBCAM_DBG_IOCTL_FMT,
+ USBCAM_DBG_IOCTL_MISC,
+ USBCAM_DBG_DEV_STATE,
+ USBCAM_DBG_ALTSETTING,
+ USBCAM_DBG_URBSTREAM,
+
+ /* First bit available to minidrivers */
+ USBCAM_DBGBIT_MD_START = 8,
+};
+
+
+
+/*
+ * The usbcam_pix_fmt structure is used to describe a pixel format natively
+ * supported by the driver to V4L clients. A minidriver may set the
+ * ud_fmt_array member of usbcam_dev to point to an array of these
+ * structures, and the array will be used to service the ENUMFMT ioctl,
+ * as long as the minidriver does not intercept that ioctl.
+ */
+struct usbcam_pix_fmt {
+ char description[32];
+ unsigned int pixelformat;
+ unsigned int flags;
+};
+
+
+/*
+ * usbcam_dev: The per-device structure representing:
+ * (1) A USB device/interface
+ * (2) A registered V4L device
+ *
+ * Commented fields are of interest to minidrivers.
+ * Uncommented fields should be considered opaque.
+ */
+
+struct usbcam_dev {
+ /*
+ * ud_vdev is the video4linux device structure.
+ * The minidriver may be interested in a few fields:
+ * ud_vdev.name: The device name displayed by applications
+ */
+ struct video_device ud_vdev;
+
+ /*
+ * ud_dev, ud_intf: The associated USB device/primary interface
+ * These members are read-only to all except when set during
+ * usbcam_dev structure initialization.
+ */
+ struct usb_device *ud_dev;
+ struct usb_interface *ud_intf;
+
+ usbcam_minidrv_t *ud_minidrv;
+
+ /*
+ * ud_minidrv_data: Minidriver private data
+ * During structure initialization, if a minidriver structure
+ * size was specified to usbcam_register(), that many bytes of
+ * memory are allocated, the allocation is assigned here, and
+ * the original allocation is automatically freed with the
+ * usbcam_dev structure. Otherwise this member is initialized
+ * to NULL.
+ * The minidriver may set whatever policies it wants with how
+ * this field is managed.
+ */
+ void *ud_minidrv_data;
+
+ struct list_head ud_drv_links;
+
+ /*
+ * ud_minidrv_id: The device's unique number among all devices
+ * belonging to the minidriver. Set prior to the minidriver's
+ * init handler being called. Read-only at all other times.
+ */
+ int ud_minidrv_id;
+
+ /*
+ * ud_lock: the mutex protecting most of this structure and all
+ * minidriver entry points.
+ */
+ struct mutex ud_lock;
+
+ /*
+ * ud_format: Currently configured size/format
+ * Protected by ud_lock
+ * Modified only by minidriver
+ * Examined by ioctl handler and framebuffer allocator
+ */
+ struct v4l2_pix_format ud_format;
+
+ /*
+ * ud_fmt_array, ud_fmt_array_elem_size, ud_fmt_array_len:
+ * Supported pixel formats for enumeration
+ * Protected by ud_lock
+ * Modified only by minidriver, usually set by init callout
+ * Examined by default ioctl handler for ENUMFMT
+ */
+ const struct usbcam_pix_fmt *ud_fmt_array;
+ int ud_fmt_array_elem_size;
+ int ud_fmt_array_len;
+
+ struct list_head ud_ctrl_list;
+
+ /*
+ * ud_capturing: Minidriver capture-in-progress flag
+ * Protected by ud_lock
+ * Modified only by minidriver, e.g. cap_start, cap_stop
+ * Examined by framebuffer interface and S_FMT ioctl handler
+ */
+ unsigned int ud_capturing : 1,
+
+ /*
+ * ud_disconnected: Set if the underlying USB device has been
+ * disconnected, just prior to invoking the minidriver disconnect
+ * callout, if one is provided. This field should be considered
+ * read-only to minidrivers.
+ */
+ ud_disconnected : 1,
+
+ ud_initializing : 1,
+ ud_suspended : 1,
+ ud_disconnected_primary : 1,
+ ud_videodev_released : 1;
+
+ struct kref ud_kref;
+ struct list_head ud_interface_list;
+
+ struct list_head ud_frame_cap_queue;
+
+ int *ud_debug;
+
+ int ud_work_refs;
+ spinlock_t ud_work_lock;
+ int ud_work_lockwait;
+ struct task_struct *ud_work_thread;
+ struct list_head ud_work_queue;
+
+ struct mutex ud_open_lock;
+ int ud_user_refs;
+ unsigned int ud_autopm_ref : 1;
+ struct file *ud_excl_owner;
+
+ /*
+ * ud_dev_name: Name of device as used for worker thread names and
+ * debug messages. The minidriver may set this field in its init
+ * callout, but should consider it read-only after that point.
+ */
+ char ud_dev_name[32];
+};
+
+
+/*
+ * Per-device reference counting helpers
+ *
+ * When the last reference is released, the minidriver's release
+ * callout will be invoked.
+ *
+ * usbcam_get() may be called from any context.
+ * usbcam_put() can sleep, and may not be called while holding the device
+ * lock (see below).
+ */
+#define usbcam_get(UDP) kref_get(&(UDP)->ud_kref)
+extern void usbcam_put(struct usbcam_dev *udp);
+
+
+/*
+ * Per-Device locking helpers
+ *
+ * The minidriver callouts, which are described below in usbcam_dev_ops
+ * and usbcam_urbstream_ops, are all invoked with the device lock held.
+ * Also, all usbcam work queue callouts are invoked with the device lock
+ * held.
+ *
+ * Minidrivers that must be entered through paths other than those
+ * described above may need to examine or update data structures
+ * protected by the device lock, and may do so using the lock
+ * acquire/release macros. For example, minidrivers that use
+ * procfs/sysfs file operations, or completion callouts unsuitable for
+ * the usbcam work queue will need this.
+ *
+ * Minidrivers may release and reacquire the lock inside of certain
+ * usbcam minidriver callouts (see
+ */
+#define usbcam_lock(UDP) mutex_lock(&(UDP)->ud_lock)
+#define usbcam_unlock(UDP) mutex_unlock(&(UDP)->ud_lock)
+#define usbcam_chklock(UDP) usbcam_assert(mutex_is_locked(&(UDP)->ud_lock))
+
+
+/*
+ * MINIDRIVER CALLOUTS
+ *
+ * Callouts invoked at various stages of the lifetime of a usbcam_dev
+ * device.
+ *
+ * REQUIRED: init, cap_start, cap_stop.
+ *
+ * All other callouts are optional.
+ *
+ * By default, all callouts are invoked with the device lock held.
+ *
+ * The device lock is not intended to be held for long periods of time.
+ * Some operations may run for a long time, and may need to sleep
+ * waiting for an operation to complete on the device. If these
+ * operations need to be able to run at the same time as capture, the
+ * minidriver must ensure that it does not hold the device lock while
+ * waiting for such long operations to complete.
+ *
+ * To support operating without the device lock, there are flags
+ * in the ops structure to selectively disable this behavior for the
+ * ioctl callout, control callouts, and power management callouts.
+ * The minidriver will be responsible for acquiring and releasing the
+ * device lock, and re-verifying the device state upon reacquisition.
+ *
+ * Certain callouts are explicitly restricted from releasing and
+ * reacquiring the device lock. These include:
+ *
+ * disconnect, open, close, try_format, set_format, cap_start, cap_stop,
+ * testpattern
+ *
+ */
+struct usbcam_dev_ops {
+ int unlocked_ioctl : 1,
+ unlocked_ctrl : 1,
+ unlocked_pm : 1,
+ supports_autosuspend : 1,
+ no_workref_on_open : 1;
+
+ /* init: invoked when a matching USB device is discovered */
+ int (*init)(struct usbcam_dev *udp, const struct usb_device_id *);
+
+ /* suspend/resume: invoked from power management paths */
+ int (*suspend)(struct usbcam_dev *udp, pm_message_t message);
+ int (*resume)(struct usbcam_dev *udp);
+
+ /* disconnect: invoked when a device has been disconnected */
+ void (*disconnect)(struct usbcam_dev *udp);
+
+ /* release: invoked when a usbcam_dev is about to be freed */
+ void (*release)(struct usbcam_dev *udp);
+
+ /* open: invoked on first user open of the device */
+ int (*open)(struct usbcam_dev *udp);
+
+ /* close: invoked on last user close of the device */
+ void (*close)(struct usbcam_dev *udp);
+
+ /* try_format: invoked to negotiate capture format */
+ int (*try_format)(struct usbcam_dev *udp,
+ struct v4l2_pix_format *fmt_in_out);
+
+ /* set_format: invoked when the capture format is being set */
+ int (*set_format)(struct usbcam_dev *udp,
+ struct v4l2_pix_format *fmt_in_out);
+
+ /* ioctl: ioctl call interception for most commands */
+ int (*ioctl)(struct usbcam_dev *udp, int cmd, void *arg);
+
+ /* cap_start: invoked when capture should be initiated */
+ int (*cap_start)(struct usbcam_dev *udp);
+
+ /* cap_stop: invoked when capture should be halted */
+ void (*cap_stop)(struct usbcam_dev *udp);
+};
+
+
+/*
+ * Minidrivers may register themselves using usbcam_register_mod(),
+ * although the recommended method is to use the usbcam_register()
+ * macro instead.
+ *
+ * The driver should keep a usbcam_minidrv structure pointer as a
+ * handle to the usbcam registration, as it will need it later when it
+ * needs to unregister itself. usbcam_register_mod() accepts a pointer
+ * to this structure, and fills it in on success.
+ *
+ * The minidriver must report a primary version number in KERNEL_VERSION
+ * format, which is used by the default VIDIOC_QUERYCAP ioctl handler.
+ * The minidriver may optionally report an extra version string.
+ *
+ * usbcam_register_mod() will cause usbcam to register a new USB driver
+ * with the given device ID table, so that it may be called when
+ * matching devices are discovered.
+ *
+ * When a matching device is detected, usbcam will:
+ * -> Allocate a usbcam_dev structure, including a minidriver-specific
+ * portion of a given size, attached to ud_minidrv_data
+ * -> Invoke the ->init() callout for the minidriver
+ * -> If successful, register a new video4linux device and
+ * start accepting V4L API requests on it
+ *
+ * usbcam_register() depends on variables defined by the
+ * DEFINE_USBCAM_MODPARAMS macro to function correctly. This defines
+ * a set of static variables and marks them as module parameters. These
+ * include:
+ * -> video_nr: Favored video4linux minor numbers
+ * -> debug: A pointer to the debug level variable
+ */
+
+extern int usbcam_register_mod(usbcam_minidrv_t **driverpp,
+ int mdrv_version, const char *mdrv_verx,
+ const struct usbcam_dev_ops *ops,
+ const int dev_priv_size,
+ const struct usb_device_id *id_table,
+ const int *video_nrs, int video_nrs_len,
+ int *debug, struct module *md,
+ const char *modname);
+
+/*
+ * usbcam_unregister() will remove a minidriver registration with the
+ * USB subsystem, and will prepare the minidriver structure to be
+ * freed. It is safe to call this API in paths other than minidriver
+ * module unload, as long as the minidriver is registered.
+ */
+extern void usbcam_unregister(usbcam_minidrv_t *driverp);
+
+#define DEFINE_USBCAM_MODPARAMS_BASE \
+ static int video_nr_len = 0; \
+ static int video_nr[8]; \
+ module_param_array(video_nr, int, &video_nr_len, S_IRUGO|S_IWUSR); \
+ MODULE_PARM_DESC(video_nr, "=n[,n...] Force /dev/videoX IDs");
+
+#if defined(CONFIG_USB_USBCAM_DEBUG)
+#ifndef USBCAM_DEBUG_DEFAULT
+#define USBCAM_DEBUG_DEFAULT 0x0020
+#endif
+#define DEFINE_USBCAM_MODPARAMS \
+ DEFINE_USBCAM_MODPARAMS_BASE \
+ static int debug = USBCAM_DEBUG_DEFAULT; \
+ module_param(debug, int, S_IRUGO|S_IWUSR); \
+ MODULE_PARM_DESC(debug, "Enable debug trace messages");
+#define usbcam_register(DRVPP, MDV, VERX, CB, STRUCTSIZE, IDTBL) \
+ usbcam_register_mod((DRVPP), (MDV), (VERX), (CB), (STRUCTSIZE), \
+ (IDTBL), video_nr, video_nr_len, &debug, \
+ THIS_MODULE, KBUILD_MODNAME)
+#else
+#define DEFINE_USBCAM_MODPARAMS \
+ DEFINE_USBCAM_MODPARAMS_BASE
+#define usbcam_register(DRVPP, MDV, VERX, CB, STRUCTSIZE, IDTBL) \
+ usbcam_register_mod((DRVPP), (MDV), (VERX), (CB), (STRUCTSIZE), \
+ (IDTBL), video_nr, video_nr_len, NULL, \
+ THIS_MODULE, KBUILD_MODNAME)
+#endif
+
+
+/*
+ * For minidrivers that do not need to do anything other than register
+ * and unregister themselves as usbcam minidrivers when their modules
+ * are loaded and unloaded, the DEFINE_USBCAM_MINIDRV_MODULE macro
+ * is offered.
+ */
+#define DEFINE_USBCAM_MINIDRV_MODULE(VER, VERX, OPS, STRUCTSIZE, IDTBL) \
+ DEFINE_USBCAM_MODPARAMS \
+ static usbcam_minidrv_t *usbcam_minidrv_driver; \
+ static int __init usbcam_minidrv_init(void) \
+ { \
+ return usbcam_register(&usbcam_minidrv_driver, \
+ (VER), (VERX), (OPS), \
+ (STRUCTSIZE), (IDTBL)); \
+ } \
+ static void __exit usbcam_minidrv_exit(void) \
+ { \
+ usbcam_unregister(usbcam_minidrv_driver); \
+ } \
+ module_init(usbcam_minidrv_init); \
+ module_exit(usbcam_minidrv_exit);
+
+
+/*
+ * Function for claiming additional interfaces
+ *
+ * This may only be called from the minidriver init callout
+ *
+ * For devices with multiple interfaces that pertain to a single driver,
+ * a separate usbcam_dev would ordinarily be created for each interface,
+ * and the init callout would be invoked. With this function,
+ * additional pertinent interfaces can be bound to the base usbcam_dev.
+ *
+ * Additional claimed interfaces will be automatically released at
+ * disconnect time, or in the event of a failure from the init callout.
+ *
+ * If the subject interface is already claimed by another USB driver,
+ * this function will fail with -EBUSY.
+ */
+extern int usbcam_claim_interface(struct usbcam_dev *udp, int ifnum);
+
+
+/*
+ * Alternate setting choosing helper function
+ *
+ * This function assists in choosing an alternate setting based on
+ * overall bandwidth and packet size requirements for a given pipe.
+ */
+extern int usbcam_choose_altsetting(struct usbcam_dev *udp, int ifnum,
+ int pipe, int bytes_per_sec_min,
+ int pkt_min, int pkt_max,
+ int *altsetting_nr);
+
+
+/*
+ * CONTROL MANAGEMENT
+ *
+ * The control structure is interpreted by the usbcam ioctl handler to
+ * answer QUERYCTRL/G_CTRL/S_CTRL/QUERYMENU requests. A minidriver may
+ * define template controls, and add instances of them per device using
+ * usbcam_ctrl_add(). Template controls may include additional fields,
+ * as long as struct usbcam_ctrl is the first member.
+ *
+ * This mode of handling control-related ioctls is only used if the
+ * minidriver does not intercept and handle the appropriate ioctl
+ * commands in its ioctl callout.
+ */
+struct usbcam_ctrl {
+ struct list_head uc_links;
+ struct v4l2_queryctrl uc_v4l;
+
+ const char **uc_menu_names;
+
+ /* Retrieves details about the control dynamically, may be NULL */
+ int (*query_fn)(struct usbcam_dev *, const struct usbcam_ctrl *,
+ struct v4l2_queryctrl *);
+
+ /* Retrieves the current value of the control */
+ int (*get_fn)(struct usbcam_dev *, const struct usbcam_ctrl *,
+ struct v4l2_ext_control *);
+
+ /* If set_fn is not set, the control is considered read-only. */
+ int (*set_fn)(struct usbcam_dev *, const struct usbcam_ctrl *,
+ const struct v4l2_ext_control *);
+
+ /* Called when a control is being freed, not normally needed */
+ void (*release_fn)(struct usbcam_dev *, struct usbcam_ctrl *);
+};
+
+extern struct usbcam_ctrl *usbcam_ctrl_find(struct usbcam_dev *udp, u32 ctlid);
+
+/* Recommended API: Add a control based on a template */
+extern struct usbcam_ctrl *usbcam_ctrl_add_tmpl(struct usbcam_dev *udp,
+ const struct usbcam_ctrl *tplp,
+ size_t real_size);
+
+/* Less recommended APIs: allocate, configure, try to add, free on failure */
+extern struct usbcam_ctrl *usbcam_ctrl_alloc(size_t real_size);
+#define usbcam_ctrl_free(CTRLP) kfree(CTRLP)
+extern int usbcam_ctrl_add(struct usbcam_dev *udp,
+ struct usbcam_ctrl *ctrlp);
+
+
+/*
+ * CURRENT FRAME BUFFER ACCESS
+ *
+ * Minidrivers need to get the address to which the current frame
+ * buffer is mapped, and the allocated size of the frame buffer in
+ * order to do their job. The below functions facilitate that.
+ *
+ * usbcam_curframe_complete() will cause the current frame buffer to
+ * be returned to the client application with a successful status.
+ * The next queued frame buffer will become the current frame buffer.
+ *
+ * usbcam_curframe_abortall() will cause all pending frame buffers
+ * to be aborted and returned to the client application with -EIO.
+ */
+struct usbcam_curframe {
+ u8 *uf_base;
+ size_t uf_size;
+ enum v4l2_field uf_field;
+ struct timeval uf_timestamp;
+};
+
+extern int usbcam_curframe_get(struct usbcam_dev *udp,
+ struct usbcam_curframe *cf);
+extern void usbcam_curframe_complete_detail(struct usbcam_dev *udp,
+ struct usbcam_curframe *cf);
+#define usbcam_curframe_complete(UDP) \
+ usbcam_curframe_complete_detail(UDP, NULL)
+
+extern void usbcam_curframe_abortall(struct usbcam_dev *udp);
+
+/*
+ * FRAME FILLERS AND TEST PATTERNS
+ *
+ * usbcam_curframe_fill() is a glorified memset: it fills the current
+ * frame buffer, starting at offs, with nrecs instances of the repeating
+ * byte sequence pattern/patlen.
+ *
+ * It does range checking and will issue printk warnings if the frame
+ * buffer or the ud_format.sizeimage is overshot.
+ */
+extern void usbcam_curframe_fill(struct usbcam_dev *udp, size_t offset,
+ const void *pattern, int patlen, int nrecs);
+
+/*
+ * usbcam_curframe_testpattern() attempts to fill the current frame
+ * buffer with a blue test pattern. It paves over any partial frame
+ * data. If you wish to display part of an incomplete or interrupted
+ * frame, don't use this function. It is used by default to fill in
+ * frames that are completed with is_error = 1.
+ *
+ * This function understands many formats, but there will always be
+ * exceptions. To keep the testpattern mechanism consistent in this
+ * situation, this function will always defer to the 'testpattern'
+ * function in the minidriver's usbcam_dev_ops, if one is given,
+ * before attempting to fill the frame itself.
+ *
+ * This function is aware of bytesperline and padded line formats.
+ */
+extern int usbcam_curframe_testpattern(struct usbcam_dev *udp);
+
+
+/*
+ * WORK ITEMS AND THE PER-DEVICE WORKER THREAD
+ *
+ * So long as a usbcam video4linux device is open, there is a worker
+ * thread available to execute deferred minidriver tasks. The worker
+ * thread becomes available prior to the open callout issued to the
+ * minidriver, and the last close of the device will block after the
+ * minidriver close callout finishes, waiting for the work queue to
+ * drain.
+ *
+ * A work item may be queued from any context, including interrupt
+ * contexts and URB completion callouts.
+ *
+ * Work item callouts are invoked in the context of the worker thread
+ * with the device mutex held. When a work item is queued, when the
+ * worker thread is next idle, it will wait for the device mutex, and
+ * will start the work item once it acquires the device mutex. Until
+ * that point, the work item may be canceled and dequeued using the
+ * usbcam_work_cancel() API, which may only be called while holding
+ * the device mutex.
+ *
+ * It was decided not to use the core kernel work queue
+ * implementation for the usbcam work queue because:
+ * 1. Work item cancelation is seen as a useful feature
+ * 2. Multithreading is not seen as a useful feature
+ */
+
+struct usbcam_workitem;
+typedef void (*usbcam_workfunc_t)(struct usbcam_workitem *work);
+
+struct usbcam_workitem {
+ struct list_head uw_links;
+ struct usbcam_dev *uw_dev;
+ usbcam_workfunc_t uw_func;
+};
+
+extern void usbcam_work_init(struct usbcam_dev *udp,
+ struct usbcam_workitem *wip,
+ usbcam_workfunc_t func);
+extern int usbcam_work_queue(struct usbcam_workitem *wip);
+extern int usbcam_work_cancel(struct usbcam_workitem *wip);
+
+struct usbcam_delayedwork {
+ struct usbcam_workitem dw_work;
+ struct timer_list dw_timer;
+};
+
+extern void usbcam_delayedwork_init(struct usbcam_dev *udp,
+ struct usbcam_delayedwork *dwp,
+ usbcam_workfunc_t func);
+extern void usbcam_delayedwork_queue(struct usbcam_delayedwork *dwp,
+ unsigned int timeout_ms);
+extern int usbcam_delayedwork_cancel(struct usbcam_delayedwork *dwp);
+
+
+/*
+ * usbcam_work_ref() and usbcam_work_unref() are for managing the start
+ * and stop of the worker thread.
+ */
+extern int usbcam_work_ref(struct usbcam_dev *udp);
+extern void usbcam_work_unref(struct usbcam_dev *udp);
+
+/*
+ * usbcam_work_runqueue() will run work queue items in the current context
+ * until the work queue becomes empty.
+ */
+extern void usbcam_work_runqueue(struct usbcam_dev *udp);
+
+
+/*
+ * Streaming URB helper structure
+ *
+ * Minidrivers create any number of these, assign appropriate endpoints,
+ * and invoke start. Completed packets are automatically processed in the
+ * worker thread.
+ *
+ * This infrastructure may only be used when a device is opened, as the
+ * worker thread is shut down at other times.
+ */
+
+extern struct urb *usbcam_urb_alloc(struct usbcam_dev *udp, int pipe,
+ int pktsize, int npkts, int alloc_buf);
+extern void usbcam_urb_free(struct urb *urbp, int free_buf);
+
+
+struct usbcam_urbstream;
+
+struct usbcam_urbstream_ops {
+ void (*packet_done)(struct usbcam_dev *, struct usbcam_urbstream *,
+ const u8 *pktdata, int pktlen, int pktstatus);
+ void (*submit_error)(struct usbcam_dev *, struct usbcam_urbstream *,
+ int status);
+};
+
+struct usbcam_urbstream {
+ struct usbcam_dev *us_dev;
+ char us_endpoint;
+ char us_streaming;
+ char us_active_goal;
+ char us_active_count;
+ const struct usbcam_urbstream_ops *us_ops;
+ spinlock_t us_lock;
+ int us_timeout_ticks;
+ int us_resubmit_err;
+ struct list_head us_unused_list;
+ struct list_head us_active_list;
+ struct list_head us_complete_list;
+ struct completion us_active_empty;
+ struct usbcam_workitem us_error_workitem;
+};
+
+extern void usbcam_urbstream_init(struct usbcam_urbstream *usp,
+ struct usbcam_dev *udp, int ep);
+
+extern int usbcam_urbstream_start(struct usbcam_urbstream *);
+extern void usbcam_urbstream_stop(struct usbcam_urbstream *, int wait);
+extern void usbcam_urbstream_cleanup(struct usbcam_urbstream *usp);
+extern int usbcam_urbstream_submit_one(struct usbcam_urbstream *usp);
+
+extern int usbcam_urbstream_config_iso(struct usbcam_urbstream *usp,
+ const struct usbcam_urbstream_ops *ops,
+ int pktcount, int nreqs, int interval,
+ int pktlen);
+
+extern int usbcam_urbstream_config_bulk(struct usbcam_urbstream *usp,
+ const struct usbcam_urbstream_ops *ops,
+ int nreqs, int reqlen, int maxpkt,
+ int timeout_ms);
+
+
+/*
+ * Backward compatibility crap for slightly older 2.6 series kernels
+ */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
+#if !defined(V4L2_CTRL_FLAG_NEXT_CTRL)
+#define V4L2_CTRL_FLAG_NEXT_CTRL 0
+#endif
+#if !defined(V4L2_CTRL_FLAG_SLIDER)
+#define V4L2_CTRL_FLAG_SLIDER 0
+#endif
+#if !defined(V4L2_CTRL_FLAG_INACTIVE)
+#define V4L2_CTRL_FLAG_INACTIVE 0
+#endif
+#if !defined(V4L2_CTRL_FLAG_UPDATE)
+#define V4L2_CTRL_FLAG_UPDATE 0
+#endif
+#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) */
+
+#endif /* __KERNEL__ */
+
+#endif /* __USBCAM_H__ */
diff --git a/usbcam/usbcam_buf.c b/usbcam/usbcam_buf.c
@@ -0,0 +1,685 @@
+/*
+ * USBCAM abstraction library for USB webcam drivers
+ *
+ * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "usbcam_priv.h"
+
+/*
+ * This file contains videobuf frame buffer interface routines, and
+ * current frame access functions.
+ */
+
+/*
+ * APPBUG: Some applications expect VIDIOCGMBUF to provide a buffer
+ * large enough to accommodate whatever image format they choose in the
+ * future. We enable fixed size buffer mode from VIDIOCGMBUF, and
+ * disable it from VIDIOC_REQBUFS.
+ */
+static int fixed_fbsize = 1024 * 1024;
+module_param(fixed_fbsize, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(fixed_fbsize, "Size in bytes of fixed-length framebuffers");
+
+
+/*
+ * Frame capture handling helpers follow
+ */
+
+static inline struct usbcam_frame *
+usbcam_capture_curframe(struct usbcam_dev *udp)
+{
+ return list_empty(&udp->ud_frame_cap_queue)
+ ? NULL
+ : list_entry(udp->ud_frame_cap_queue.next,
+ struct usbcam_frame, cap_links);
+}
+
+static void usbcam_capture_abortall(struct usbcam_dev *udp)
+{
+ struct usbcam_frame *framep;
+
+ /* Abort all frames on the capture queue */
+ while (1) {
+ framep = usbcam_capture_curframe(udp);
+ if (!framep)
+ break;
+ usbcam_dbg(udp, VIDEOBUF, "completing frame %d STATE_ERROR",
+ framep->vbb.i);
+ list_del_init(&framep->cap_links);
+ framep->vbb.state = STATE_ERROR;
+ wake_up_all(&framep->vbb.done);
+ }
+}
+
+static inline void usbcam_capture_complete_frame(struct usbcam_dev *udp,
+ struct usbcam_frame *framep,
+ int is_error)
+{
+ usbcam_chklock(udp);
+ usbcam_dbg(udp, VIDEOBUF, "completing frame %d/%p %s", framep->vbb.i,
+ framep, is_error ? "STATE_ERROR" : "STATE_DONE");
+ list_del_init(&framep->cap_links);
+ framep->vbb.state = is_error ? STATE_ERROR : STATE_DONE;
+ wake_up_all(&framep->vbb.done);
+}
+
+static int usbcam_capture_start(struct usbcam_dev *udp)
+{
+ int res;
+
+ if (udp->ud_capturing) {
+ usbcam_warn(udp, "%s: already capturing", __FUNCTION__);
+ return 0;
+ }
+
+ if (list_empty(&udp->ud_frame_cap_queue)) {
+ usbcam_warn(udp, "%s: no frames queued to capture",
+ __FUNCTION__);
+ return -ENOENT;
+ }
+
+ if (udp->ud_disconnected) {
+ /*
+ * We can't let any frames through if the device has
+ * been disconnected
+ */
+ usbcam_capture_abortall(udp);
+ return -ENODEV;
+ }
+
+ usbcam_dbg(udp, CAPTURE, "invoking minidriver cap_start");
+
+ res = usbcam_minidrv_op(udp, cap_start);
+ if (res) {
+ usbcam_dbg(udp, CAPTURE,
+ "%s: could not start capture for %s: %d",
+ __FUNCTION__, usbcam_drvname(udp->ud_minidrv), res);
+
+ if (udp->ud_capturing) {
+ usbcam_warn(udp,
+ "%s: minidriver left ud_capturing set\n",
+ __FUNCTION__);
+ }
+
+ usbcam_capture_abortall(udp);
+ return res;
+ }
+
+ if (!udp->ud_capturing && usbcam_capture_curframe(udp)) {
+ usbcam_warn(udp, "%s: minidriver failed to set ud_capturing!",
+ __FUNCTION__);
+ } else {
+ usbcam_dbg(udp, CAPTURE, "minidriver capture started");
+ }
+
+ return 0;
+}
+
+void usbcam_capture_stop(struct usbcam_dev *udp)
+{
+ if (udp->ud_capturing) {
+ usbcam_dbg(udp, CAPTURE, "invoking minidriver cap_stop");
+ usbcam_minidrv_op(udp, cap_stop);
+
+ if (udp->ud_capturing) {
+ usbcam_warn(udp, "%s: minidriver failed to clear "
+ "ud_capturing!", __FUNCTION__);
+ } else {
+ usbcam_dbg(udp, CAPTURE, "minidriver capture stopped");
+ }
+ }
+}
+
+static inline struct videobuf_dmabuf* usbframe_get_dmabuf(struct videobuf_buffer *buf)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+ return buf->dma;
+#else
+ return videobuf_to_dma(buf);
+#endif
+}
+
+/*
+ * External APIs for minidriver access to the frame queue
+ */
+
+int usbcam_curframe_get(struct usbcam_dev *udp, struct usbcam_curframe *cf)
+{
+ struct usbcam_frame *framep = usbcam_capture_curframe(udp);
+ struct videobuf_dmabuf *dma;
+
+ usbcam_chklock(udp);
+
+ if (!framep)
+ return -ENOENT;
+
+ dma = usbframe_get_dmabuf(&framep->vbb);
+
+ cf->uf_base = (u8 *) (framep->vmap_sof
+ ? framep->vmap_sof
+ : dma->vmalloc);
+ cf->uf_size = framep->vbb.size;
+ cf->uf_field = framep->vbb.field;
+ memset(&cf->uf_timestamp, 0, sizeof(cf->uf_timestamp));
+
+ return 0;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_curframe_get);
+
+void usbcam_curframe_complete_detail(struct usbcam_dev *udp,
+ struct usbcam_curframe *cf)
+{
+ struct usbcam_frame *framep;
+
+ usbcam_chklock(udp);
+
+ framep = usbcam_capture_curframe(udp);
+ if (!framep) {
+ usbcam_warn(udp, "%s: no current frame!", __FUNCTION__);
+ return;
+ }
+
+ if (framep->vbb.state != STATE_ACTIVE) {
+ usbcam_err(udp, "%s: current frame is in unexpected state %d",
+ __FUNCTION__, framep->vbb.state);
+ }
+
+ if (cf) {
+ framep->vbb.size = cf->uf_size;
+ framep->vbb.field = cf->uf_field;
+ framep->vbb.ts = cf->uf_timestamp;
+
+ if (framep->vbb.bsize < cf->uf_size) {
+ usbcam_warn(udp, "%s: minidriver supplied "
+ "excessive size %zu",
+ __FUNCTION__, cf->uf_size);
+ framep->vbb.size = framep->vbb.bsize;
+ }
+ }
+
+ usbcam_capture_complete_frame(udp, framep, 0);
+}
+USBCAM_EXPORT_SYMBOL(usbcam_curframe_complete_detail);
+
+void usbcam_curframe_abortall(struct usbcam_dev *udp)
+{
+ usbcam_dbg(udp, CAPTURE, "minidriver aborting all frames");
+
+ usbcam_chklock(udp);
+
+ if (udp->ud_capturing) {
+ usbcam_warn(udp, "%s: minidriver left ud_capturing set",
+ __FUNCTION__);
+ }
+ usbcam_capture_abortall(udp);
+}
+USBCAM_EXPORT_SYMBOL(usbcam_curframe_abortall);
+
+
+/*
+ * Test pattern code
+ */
+
+void usbcam_curframe_fill(struct usbcam_dev *udp, size_t offset,
+ const void *pattern, int patlen, int nrecs)
+{
+ struct usbcam_curframe cf;
+ size_t end;
+
+ usbcam_chklock(udp);
+
+ if (usbcam_curframe_get(udp, &cf)) {
+ usbcam_warn(udp, "%s: no current frame", __FUNCTION__);
+ return;
+ }
+
+ end = offset + (patlen * nrecs);
+ if (end > cf.uf_size) {
+ usbcam_warn(udp, "%s(offs=%zu patlen=%d nrecs=%d) would "
+ "write past end of %zu byte framebuffer",
+ __FUNCTION__,
+ offset, patlen, nrecs, cf.uf_size);
+ if (offset > cf.uf_size)
+ nrecs = 0;
+ else
+ nrecs = (cf.uf_size - offset) / patlen;
+ }
+ else if (end > udp->ud_format.sizeimage)
+ usbcam_warn(udp, "%s(offs=%zu patlen=%d nrecs=%d) writing "
+ "beyond %u-byte sizeimage", __FUNCTION__,
+ offset, patlen, nrecs, udp->ud_format.sizeimage);
+
+ if (!nrecs)
+ return;
+
+ if (patlen == 1) {
+ memset(cf.uf_base + offset, *(char *)pattern, nrecs);
+ return;
+ }
+
+ while (nrecs--) {
+ memcpy(cf.uf_base + offset, pattern, patlen);
+ offset += patlen;
+ }
+}
+USBCAM_EXPORT_SYMBOL(usbcam_curframe_fill);
+
+void usbcam_curframe_fill_lines(struct usbcam_dev *udp,
+ const char *pat, int patlen,
+ int pixperpat)
+{
+ int line, nrecperline, stride;
+ size_t offset = 0;
+
+ nrecperline = (udp->ud_format.width + pixperpat - 1) / pixperpat;
+ stride = udp->ud_format.bytesperline
+ ? udp->ud_format.bytesperline
+ : (nrecperline * patlen);
+
+ if ((patlen * nrecperline) == stride) {
+ usbcam_curframe_fill(udp, 0, pat, patlen,
+ nrecperline * udp->ud_format.height);
+ return;
+ }
+
+ for (line = 0; line < udp->ud_format.height; line++) {
+ usbcam_curframe_fill(udp, offset, pat, patlen, nrecperline);
+ offset += stride;
+ }
+}
+
+void usbcam_curframe_fill_interleaved(struct usbcam_dev *udp,
+ const char *pat_even,
+ const char *pat_odd,
+ int patlen, int pixperpat)
+{
+ int line, nrecperline, stride;
+ size_t offset = 0;
+
+ nrecperline = (udp->ud_format.width + pixperpat - 1) / pixperpat;
+ stride = udp->ud_format.bytesperline
+ ? udp->ud_format.bytesperline
+ : (nrecperline * patlen);
+
+ for (line = 0; line < udp->ud_format.height; line++) {
+ usbcam_curframe_fill(udp, offset,
+ (line & 1) ? pat_odd : pat_even,
+ patlen, nrecperline);
+ offset += stride;
+ }
+}
+
+void usbcam_curframe_fill_planar(struct usbcam_dev *udp,
+ const char *pat0, int pat0len, int pixperpat0,
+ const char *pat1, int pat1len, int pixperpat1,
+ const char *pat2, int pat2len, int pixperpat2)
+{
+ int nrecperline;
+ size_t offset = 0;
+
+ if (pat0 && pat0len) {
+ nrecperline = ((udp->ud_format.width + pixperpat0 - 1) /
+ pixperpat0);
+ usbcam_curframe_fill(udp, offset, pat0, pat0len,
+ nrecperline * udp->ud_format.height);
+ offset += (nrecperline * udp->ud_format.height * pat0len);
+ }
+ if (pat1 && pat1len) {
+ nrecperline = ((udp->ud_format.width + pixperpat1 - 1) /
+ pixperpat1);
+ usbcam_curframe_fill(udp, offset, pat1, pat1len,
+ nrecperline * udp->ud_format.height);
+ offset += (nrecperline * udp->ud_format.height * pat1len);
+ }
+ if (pat2 && pat2len) {
+ nrecperline = ((udp->ud_format.width + pixperpat2 - 1) /
+ pixperpat2);
+ usbcam_curframe_fill(udp, offset, pat2, pat2len,
+ nrecperline * udp->ud_format.height);
+ offset += (nrecperline * udp->ud_format.height * pat2len);
+ }
+}
+
+/*
+ * The goal is to be able to come up with a solid blue image in all
+ * basic uncompressed formats. No JPEG or compressed formats, at least
+ * not yet.
+ */
+int usbcam_curframe_testpattern(struct usbcam_dev *udp)
+{
+ usbcam_chklock(udp);
+
+#define DO_FILL(PIXPERPAT, TP...) { \
+ const char tp[] = {TP}; \
+ usbcam_curframe_fill_lines(udp, tp, sizeof(tp), PIXPERPAT); \
+}
+ switch (udp->ud_format.pixelformat) {
+ case V4L2_PIX_FMT_RGB332:
+ DO_FILL(1, 0x03)
+ return 0;
+ case V4L2_PIX_FMT_RGB555:
+ case V4L2_PIX_FMT_RGB565:
+ DO_FILL(1, 0x1f, 0x00)
+ return 0;
+ case V4L2_PIX_FMT_RGB555X:
+ case V4L2_PIX_FMT_RGB565X:
+ DO_FILL(1, 0x00, 0x1f)
+ return 0;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
+ case V4L2_PIX_FMT_RGB444:
+ DO_FILL(1, 0x00, 0x0f)
+ return 0;
+#endif
+ case V4L2_PIX_FMT_BGR24:
+ DO_FILL(1, 0xff, 0x00, 0x00);
+ return 0;
+ case V4L2_PIX_FMT_RGB24:
+ DO_FILL(1, 0x00, 0x00, 0xff);
+ return 0;
+ case V4L2_PIX_FMT_BGR32:
+ DO_FILL(1, 0xff, 0x00, 0x00, 0x00);
+ return 0;
+ case V4L2_PIX_FMT_RGB32:
+ DO_FILL(1, 0x00, 0x00, 0xff, 0x00);
+ return 0;
+ case V4L2_PIX_FMT_GREY:
+ DO_FILL(1, 0x1f);
+ return 0;
+ case V4L2_PIX_FMT_YUYV:
+ DO_FILL(2, 0x29, 0xf0, 0x29, 0x6e);
+ return 0;
+ case V4L2_PIX_FMT_UYVY:
+ DO_FILL(2, 0xf0, 0x29, 0x6e, 0x29);
+ return 0;
+ case V4L2_PIX_FMT_YYUV:
+ DO_FILL(2, 0x29, 0x29, 0xf0, 0x6e);
+ return 0;
+ case V4L2_PIX_FMT_Y41P:
+ DO_FILL(8,
+ 0xf0, 0x29, 0x6e, 0x29, 0xf0, 0x29, 0x6e, 0x29,
+ 0x29, 0x29, 0x29, 0x29);
+ return 0;
+#undef DO_FILL
+
+ case V4L2_PIX_FMT_SBGGR8: {
+ const char tp0[] = { 0xff, 0x00 }, tp1[] = { 0x00, 0x00 };
+ usbcam_curframe_fill_interleaved(udp, tp0, tp1, 2, 2);
+ return 0;
+ }
+ case V4L2_PIX_FMT_YVU410: {
+ const char tp0[] = {0x29}, tp1[] = {0x6e}, tp2[] = {0xf0};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 16,
+ tp2, sizeof(tp2), 16);
+ return 0;
+ }
+ case V4L2_PIX_FMT_YUV410: {
+ const char tp0[] = {0x29}, tp1[] = {0xf0}, tp2[] = {0x6e};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 16,
+ tp2, sizeof(tp2), 16);
+ return 0;
+ }
+ case V4L2_PIX_FMT_YVU420: {
+ const char tp0[] = {0x29}, tp1[] = {0x6e}, tp2[] = {0xf0};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 4,
+ tp2, sizeof(tp2), 4);
+ return 0;
+ }
+ case V4L2_PIX_FMT_YUV411P:
+ case V4L2_PIX_FMT_YUV420: {
+ const char tp0[] = {0x29}, tp1[] = {0xf0}, tp2[] = {0x6e};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 4,
+ tp2, sizeof(tp2), 4);
+ return 0;
+ }
+ case V4L2_PIX_FMT_YUV422P: {
+ const char tp0[] = {0x29}, tp1[] = {0xf0}, tp2[] = {0x6e};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 2,
+ tp2, sizeof(tp2), 2);
+ return 0;
+ }
+ case V4L2_PIX_FMT_NV12: {
+ const char tp0[] = {0x29}, tp1[] = {0xf0, 0x6e};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 4,
+ NULL, 0, 0);
+ return 0;
+ }
+ case V4L2_PIX_FMT_NV21: {
+ const char tp0[] = {0x29}, tp1[] = {0x6e, 0xf0};
+ usbcam_curframe_fill_planar(udp, tp0, sizeof(tp0), 1,
+ tp1, sizeof(tp1), 4,
+ NULL, 0, 0);
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_curframe_testpattern);
+
+
+/*
+ * video-buf interfaces for managing frame buffers
+ * The device mutex is not held on entry to _any_ of these functions.
+ */
+
+static int usbcam_videobuf_setup(struct videobuf_queue *vq,
+ unsigned int *count, unsigned int *size)
+{
+ struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, ufh_vbq);
+ struct usbcam_dev *udp = ufp->ufh_dev;
+
+ usbcam_lock(udp);
+
+ /* APPBUG: possibly request larger buffers than necessary */
+ if ((ufp->ufh_flags & USBCAM_FH_USE_FIXED_FB) &&
+ (fixed_fbsize > udp->ud_format.sizeimage))
+ *size = fixed_fbsize;
+ else
+ *size = udp->ud_format.sizeimage;
+
+ if (!*count)
+ *count = 2;
+
+ usbcam_dbg(udp, VIDEOBUF, "videobuf setup: size=%u count=%u",
+ *size, *count);
+
+ usbcam_unlock(udp);
+ return 0;
+}
+
+static void usbcam_videobuf_free(struct videobuf_queue *vq,
+ struct usbcam_frame *framep)
+{
+ struct videobuf_dmabuf *dma = usbframe_get_dmabuf(&framep->vbb);
+
+ videobuf_waiton(&framep->vbb, 0, 0);
+ videobuf_dma_unmap(vq, dma);
+ videobuf_dma_free(dma);
+ if (framep->vbb.state != STATE_NEEDS_INIT) {
+ if (framep->vmap_base) {
+ vunmap(framep->vmap_base);
+ framep->vmap_base = NULL;
+ framep->vmap_sof = NULL;
+ }
+ assert(list_empty(&framep->cap_links));
+ framep->vbb.state = STATE_NEEDS_INIT;
+ }
+}
+
+static int usbcam_videobuf_prepare(struct videobuf_queue *vq,
+ struct videobuf_buffer *vb,
+ enum v4l2_field field)
+{
+ struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, ufh_vbq);
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ struct usbcam_frame *framep =
+ container_of(vb, struct usbcam_frame, vbb);
+ struct videobuf_dmabuf *dma = usbframe_get_dmabuf(&framep->vbb);
+ int res;
+
+ framep->vbb.size = udp->ud_format.sizeimage;
+ if (framep->vbb.baddr && (framep->vbb.bsize < framep->vbb.size)) {
+ usbcam_warn(udp, "process %s requested capture of a frame "
+ "larger than its", current->comm);
+ usbcam_warn(udp, "allocated frame buffer, fix it!");
+ return -EINVAL;
+ }
+
+ if (framep->vbb.state == STATE_NEEDS_INIT) {
+ /*
+ * This is the place where we initialize the rest of
+ * the usbcam_frame structure.
+ */
+ INIT_LIST_HEAD(&framep->cap_links);
+ framep->vmap_base = NULL;
+ framep->vmap_sof = NULL;
+
+ usbcam_dbg(udp, VIDEOBUF,
+ "preparing frame %d/%p", framep->vbb.i, framep);
+
+ /* We also lock down the memory that was allocated for it */
+ res = videobuf_iolock(vq, &framep->vbb, NULL);
+ if (res)
+ goto fail;
+
+ /* If there's no kernel mapping, we must create one */
+ if (!dma->vmalloc) {
+ framep->vmap_base = vmap(dma->pages,
+ dma->nr_pages,
+ VM_MAP,
+ PAGE_KERNEL);
+ if (!framep->vmap_base) {
+ res = -ENOMEM;
+ goto fail;
+ }
+
+ framep->vmap_sof =
+ ((char *)framep->vmap_base) +
+ dma->offset;
+ }
+ }
+
+ framep->vbb.field = field;
+ framep->vbb.state = STATE_PREPARED;
+ return 0;
+
+fail:
+ usbcam_videobuf_free(vq, framep);
+ return res;
+}
+
+static void usbcam_videobuf_queue(struct videobuf_queue *vq,
+ struct videobuf_buffer *vb)
+{
+ struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, ufh_vbq);
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ struct usbcam_frame *framep =
+ container_of(vb, struct usbcam_frame, vbb);
+ int was_empty = 0;
+
+ assert(framep->vbb.state != STATE_NEEDS_INIT);
+
+ usbcam_lock(udp);
+
+ if (list_empty(&udp->ud_frame_cap_queue))
+ was_empty = 1;
+
+ usbcam_dbg(udp, VIDEOBUF, "queueing frame %d/%p",
+ framep->vbb.i, framep);
+
+ /*
+ * We always set buffers to STATE_ACTIVE to prevent them from
+ * being manipulated / dequeued by the videobuf code.
+ */
+ list_add_tail(&framep->cap_links, &udp->ud_frame_cap_queue);
+ framep->vbb.state = STATE_ACTIVE;
+
+ if (was_empty && !udp->ud_capturing)
+ (void) usbcam_capture_start(udp);
+
+ usbcam_unlock(udp);
+}
+
+static void usbcam_videobuf_release(struct videobuf_queue *vq,
+ struct videobuf_buffer *vb)
+{
+ struct usbcam_fh *ufp = container_of(vq, struct usbcam_fh, ufh_vbq);
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ struct usbcam_frame *framep =
+ container_of(vb, struct usbcam_frame, vbb);
+ int stopped_capture = 0;
+
+ usbcam_lock(udp);
+
+ if ((framep->vbb.state != STATE_NEEDS_INIT) &&
+ !list_empty(&framep->cap_links)) {
+
+ usbcam_dbg(udp, VIDEOBUF,
+ "aborting frame %d/%p", framep->vbb.i, framep);
+
+ /*
+ * An active frame is being shot down here, most
+ * likely by videobuf_queue_cancel.
+ */
+ assert(framep->vbb.state == STATE_ACTIVE);
+
+ if (udp->ud_capturing &&
+ !list_empty(&udp->ud_frame_cap_queue) &&
+ (framep == usbcam_capture_curframe(udp))) {
+ /*
+ * The current frame has been user-aborted.
+ * We will stop capturing. The minidriver may complete
+ * it with an error, or may leave it alone, in which
+ * case we will complete it with an error.
+ */
+ usbcam_dbg(udp, VIDEOBUF,
+ "current frame aborted, stopping capture");
+
+ usbcam_capture_stop(udp);
+ stopped_capture = 1;
+ }
+
+ if (!list_empty(&framep->cap_links))
+ usbcam_capture_complete_frame(udp, framep, 1);
+
+ /*
+ * Ideally, if we stopped capturing, and there are frames
+ * still in the queue, we would restart.
+ *
+ * In reality, we only take this code path if all frames
+ * from the owning file handle are aborted, and restarting
+ * would pointlessly slow down this process.
+ */
+ }
+
+ usbcam_unlock(udp);
+ usbcam_videobuf_free(vq, framep);
+}
+
+struct videobuf_queue_ops usbcam_videobuf_qops = {
+ .buf_setup = usbcam_videobuf_setup,
+ .buf_prepare = usbcam_videobuf_prepare,
+ .buf_queue = usbcam_videobuf_queue,
+ .buf_release = usbcam_videobuf_release,
+};
diff --git a/usbcam/usbcam_dev.c b/usbcam/usbcam_dev.c
@@ -0,0 +1,1109 @@
+/*
+ * USBCAM abstraction library for USB webcam drivers
+ *
+ * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "usbcam_priv.h"
+
+/*
+ * This file contains:
+ * - Minidriver registration / deregistration handlers.
+ * - Device and minidriver reference counting functions
+ * - USB subsystem callouts
+ */
+
+/*
+ * Reference Counting Notes
+ *
+ * Each usbcam_minidrv gets:
+ * - One reference for being in the registered state
+ * - One reference for each outstanding usbcam_dev
+ *
+ * Each usbcam_dev gets:
+ * - One reference for having its V4L minor registered and not released
+ * - One reference for having its underlying USB device not disconnected
+ * - One reference for each open file handle
+ */
+
+static void usbcam_minidrv_release(struct kref *kref)
+{
+ struct usbcam_minidrv *minidrv =
+ container_of(kref, struct usbcam_minidrv, um_kref);
+
+ assert(!minidrv->um_dev_count);
+ usbcam_dbgm(minidrv, DEV_STATE, "%s: destroying minidrvier",
+ __FUNCTION__);
+ kfree(minidrv);
+}
+
+
+static void usbcam_dev_free(struct kref *kref)
+{
+ struct usbcam_dev *udp =
+ container_of(kref, struct usbcam_dev, ud_kref);
+
+ if (udp->ud_work_refs) {
+ usbcam_lock(udp);
+ usbcam_warn(udp, "%s: work queue has %d leaked refs",
+ __FUNCTION__, udp->ud_work_refs);
+ while (udp->ud_work_refs)
+ usbcam_work_unref(udp);
+ usbcam_unlock(udp);
+ }
+
+ usbcam_work_maybe_stop(udp);
+ assert(!udp->ud_work_thread);
+
+ usb_put_intf(udp->ud_intf);
+ udp->ud_intf = NULL;
+
+ usb_put_dev(udp->ud_dev);
+ udp->ud_dev = NULL;
+
+ mutex_lock(&udp->ud_minidrv->um_lock);
+
+ assert(!list_empty(&udp->ud_drv_links));
+ assert(udp->ud_minidrv->um_dev_count > 0);
+
+ list_del_init(&udp->ud_drv_links);
+ udp->ud_minidrv->um_dev_count--;
+
+ mutex_unlock(&udp->ud_minidrv->um_lock);
+
+ kref_put(&udp->ud_minidrv->um_kref, usbcam_minidrv_release);
+ kfree(udp);
+}
+
+static void usbcam_dev_release(struct kref *kref)
+{
+ struct usbcam_dev *udp =
+ container_of(kref, struct usbcam_dev, ud_kref);
+
+ usbcam_dbg(udp, DEV_STATE, "%s: destroying device", __FUNCTION__);
+
+ if (usbcam_minidrv_op_present(udp, release)) {
+ usbcam_lock(udp);
+ usbcam_ctrl_releaseall(udp);
+ usbcam_minidrv_op(udp, release);
+ usbcam_unlock(udp);
+ }
+ usbcam_dev_free(kref);
+}
+
+void usbcam_put(struct usbcam_dev *udp)
+{
+ kref_put(&udp->ud_kref, usbcam_dev_release);
+}
+USBCAM_EXPORT_SYMBOL(usbcam_put);
+
+/*
+ * V4L2 videodev callout implementations
+ */
+
+static void usbcam_videodev_release(struct video_device *vfd)
+{
+ struct usbcam_dev *udp = container_of(vfd, struct usbcam_dev, ud_vdev);
+
+ usbcam_lock(udp);
+
+ assert(!udp->ud_videodev_released);
+ udp->ud_videodev_released = 1;
+
+ usbcam_unlock(udp);
+ usbcam_put(udp);
+}
+
+
+/*
+ * USB subsystem operation implementations
+ */
+
+struct usbcam_claimed_interface {
+ struct list_head ui_links;
+ struct usb_interface *ui_intf;
+};
+
+static int usbcam_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *devid)
+{
+ struct usb_driver *drvp;
+ struct usbcam_dev *udp = NULL, *udpx;
+ usbcam_minidrv_t *minidrv;
+ struct usb_device *dev;
+ struct list_head *listp;
+ struct usbcam_claimed_interface *cip;
+ int minidrv_init_failed = 0;
+ int res, i;
+
+ /* Locate the mini-driver */
+ dev = interface_to_usbdev(intf);
+ drvp = to_usb_driver(intf->dev.driver);
+ minidrv = container_of(drvp, usbcam_minidrv_t, um_usbdrv);
+
+ /* Allocate and initialize a device structure */
+ udp = (struct usbcam_dev *) kzalloc(sizeof(*udp) +
+ minidrv->um_dev_privsize,
+ GFP_KERNEL);
+ if (!udp)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&udp->ud_ctrl_list);
+ mutex_init(&udp->ud_open_lock);
+ mutex_init(&udp->ud_lock);
+ spin_lock_init(&udp->ud_work_lock);
+ INIT_LIST_HEAD(&udp->ud_work_queue);
+ udp->ud_minidrv = minidrv;
+ udp->ud_dev = usb_get_dev(dev);
+ udp->ud_intf = usb_get_intf(intf);
+ udp->ud_debug = minidrv->um_debug;
+ INIT_LIST_HEAD(&udp->ud_drv_links);
+ kref_init(&udp->ud_kref);
+
+ INIT_LIST_HEAD(&udp->ud_interface_list);
+ INIT_LIST_HEAD(&udp->ud_frame_cap_queue);
+
+ if (minidrv->um_dev_privsize)
+ udp->ud_minidrv_data = &udp[1];
+
+ /* Set up the video4linux structure */
+ udp->ud_vdev = minidrv->um_videodev_template;
+ udp->ud_vdev.release = usbcam_videodev_release;
+
+ /* Add the device to the minidriver's list of active devices */
+ usbcam_lock(udp);
+
+ mutex_lock(&minidrv->um_lock);
+
+ /* Inefficiently find an unused ID in the device list */
+ i = 0;
+ udpx = NULL;
+ list_for_each(listp, &minidrv->um_dev_list) {
+ udpx = list_entry(listp, struct usbcam_dev, ud_drv_links);
+ if (udpx->ud_minidrv_id < 0) {
+ udpx = NULL;
+ continue;
+ }
+ if (udpx->ud_minidrv_id != i)
+ break;
+ udpx = NULL;
+ i++;
+ }
+
+ udp->ud_minidrv_id = i;
+ if (udpx) {
+ list_add_tail(&udp->ud_drv_links, &udpx->ud_drv_links);
+ } else {
+ list_add_tail(&udp->ud_drv_links, &minidrv->um_dev_list);
+ }
+
+ minidrv->um_dev_count++;
+ kref_get(&minidrv->um_kref);
+
+ snprintf(udp->ud_dev_name, sizeof(udp->ud_dev_name),
+ "%s-%d", usbcam_drvname(udp->ud_minidrv),
+ udp->ud_minidrv_id);
+
+ snprintf(udp->ud_vdev.name, sizeof(udp->ud_vdev.name),
+ "%s USB Camera #%d",
+ usbcam_drvname(minidrv), udp->ud_minidrv_id + 1);
+
+
+ mutex_unlock(&minidrv->um_lock);
+
+ /* Invoke the minidriver initialization callout */
+ udp->ud_initializing = 1;
+ res = usbcam_minidrv_op(udp, init, devid);
+ udp->ud_initializing = 0;
+ if (res) {
+ usbcam_dbg(udp, DEV_STATE, "minidriver init failed: %d", res);
+ minidrv_init_failed = 1;
+ goto out_nodisconn;
+ }
+
+ /* Complain if the device isn't filled out correctly */
+ if (!udp->ud_format.width || !udp->ud_format.height) {
+ usbcam_warn(udp, "minidriver did not set default size");
+ res = -EINVAL;
+ goto out;
+ }
+ if (!udp->ud_format.pixelformat) {
+ usbcam_warn(udp, "minidriver did not set default pixelformat");
+ res = -EINVAL;
+ goto out;
+ }
+
+ usb_set_intfdata(intf, udp);
+ usbcam_unlock(udp);
+
+ /*
+ * Register the device with video4linux
+ *
+ * BUG: video_register_device() may or may not call back
+ * into usbcam_videodev_release(), depending on how it fails,
+ * and if it does call back, its callback may be latent.
+ *
+ * We will assume no callback on failure.
+ */
+
+ if (udp->ud_vdev.minor != -1) {
+ /* Minidriver has indicated its preference for a minor */
+ res = video_register_device(&udp->ud_vdev, VFL_TYPE_GRABBER,
+ -1);
+ if (!res)
+ goto video_registered;
+ }
+
+ for (i = 0; i < minidrv->um_video_nr_array_len; i++) {
+ res = video_register_device(&udp->ud_vdev, VFL_TYPE_GRABBER,
+ minidrv->um_video_nr_array[i]);
+ if (!res)
+ goto video_registered;
+ }
+
+ res = video_register_device(&udp->ud_vdev, VFL_TYPE_GRABBER, -1);
+ if (res) {
+ usbcam_err(udp, "%s: video_register_device failed",
+ __FUNCTION__);
+ usbcam_lock(udp);
+ assert(!udp->ud_videodev_released);
+ udp->ud_videodev_released = 1;
+ goto out;
+ }
+
+video_registered:
+ usbcam_get(udp);
+ /*
+ * There should now be at least two references on udp:
+ * One for the primary USB interface in the non-disconnected state
+ * One for the videodev stuff
+ * One for each additional claimed interface
+ */
+
+ usbcam_info(udp, "registered as video%d", udp->ud_vdev.minor);
+
+ usbcam_work_maybe_stop(udp);
+ return 0;
+
+out:
+ assert(!udp->ud_disconnected);
+ udp->ud_disconnected = 1;
+ if (usbcam_minidrv_op_present(udp, disconnect))
+ usbcam_minidrv_op(udp, disconnect);
+
+out_nodisconn:
+ while (!list_empty(&udp->ud_interface_list)) {
+ cip = list_entry(udp->ud_interface_list.next,
+ struct usbcam_claimed_interface,
+ ui_links);
+ list_del_init(&cip->ui_links);
+ usb_set_intfdata(cip->ui_intf, NULL);
+ usb_driver_release_interface(&minidrv->um_usbdrv,
+ cip->ui_intf);
+ usb_put_intf(cip->ui_intf);
+ kfree(cip);
+ usbcam_put(udp);
+ }
+
+ usbcam_unlock(udp);
+
+ if (minidrv_init_failed)
+ kref_put(&udp->ud_kref, usbcam_dev_free);
+ else
+ usbcam_put(udp);
+ return res;
+}
+
+static void usbcam_usb_disconnect(struct usb_interface *intf)
+{
+ struct usbcam_dev *udp = (struct usbcam_dev *) usb_get_intfdata(intf);
+ struct usbcam_claimed_interface *iterp, *cip;
+ int put_intf = 0;
+ int put_udp = 0;
+
+ if (!udp)
+ return;
+
+ usbcam_lock(udp);
+ if (!udp->ud_disconnected) {
+ udp->ud_disconnected = 1;
+ usbcam_unlock(udp);
+
+ usbcam_dbg(udp, DEV_STATE, "disconnected");
+ video_unregister_device(&udp->ud_vdev);
+
+ usbcam_dbg(udp, DEV_STATE, "unregistered from video%d",
+ udp->ud_vdev.minor);
+
+ usbcam_lock(udp);
+ if (usbcam_minidrv_op_present(udp, disconnect))
+ usbcam_minidrv_op(udp, disconnect);
+
+ usbcam_capture_stop(udp);
+ }
+
+ if (intf == udp->ud_intf) {
+ assert(!udp->ud_disconnected_primary);
+ udp->ud_disconnected_primary = 1;
+ put_udp = 1;
+
+ } else {
+ cip = NULL;
+ list_for_each_entry(iterp, &udp->ud_interface_list, ui_links) {
+ if (iterp->ui_intf == intf) {
+ cip = iterp;
+ break;
+ }
+ }
+
+ if (cip) {
+ list_del_init(&cip->ui_links);
+ kfree(cip);
+ put_intf = 1;
+ put_udp = 1;
+ } else {
+ usbcam_err(udp, "interface %p is not claimed", intf);
+ }
+ }
+
+ usb_set_intfdata(intf, NULL);
+ usbcam_unlock(udp);
+
+ usbcam_work_maybe_stop(udp);
+
+ if (put_intf)
+ usb_put_intf(intf);
+ if (put_udp)
+ usbcam_put(udp);
+}
+
+#if defined(CONFIG_PM)
+static int usbcam_usb_suspend(struct usb_interface *intf, pm_message_t msg)
+{
+ struct usbcam_dev *udp = (struct usbcam_dev *) usb_get_intfdata(intf);
+ int relock = 0, res = 0;
+ if (!udp) {
+ printk(KERN_WARNING "%s: no associated device\n",
+ __FUNCTION__);
+ return 0;
+ }
+
+ usbcam_lock(udp);
+ if ((intf != udp->ud_intf) || udp->ud_suspended) {
+ /* Do nothing */
+ } else if (usbcam_minidrv_op_present(udp, suspend)) {
+ usbcam_dbg(udp, DEV_STATE, "invoking minidriver suspend");
+ udp->ud_suspended = 1;
+ if (udp->ud_minidrv->um_ops->unlocked_pm) {
+ usbcam_unlock(udp);
+ relock = 1;
+ }
+
+ res = usbcam_minidrv_op(udp, suspend, msg);
+
+ if (relock)
+ usbcam_lock(udp);
+ if (res)
+ udp->ud_suspended = 0;
+ } else {
+ usbcam_dbg(udp, DEV_STATE, "no minidriver suspend method");
+ udp->ud_suspended = 1;
+ }
+
+ usbcam_unlock(udp);
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+static int usbcam_usb_resume(struct usb_interface *intf)
+{
+ struct usbcam_dev *udp = (struct usbcam_dev *) usb_get_intfdata(intf);
+ int relock = 0, res = 0;
+ if (!udp) {
+ printk(KERN_WARNING "%s: no associated device\n",
+ __FUNCTION__);
+ return 0;
+ }
+
+ usbcam_lock(udp);
+ if ((intf != udp->ud_intf) || !udp->ud_suspended) {
+ /* Nothing to do! */
+ } else if (usbcam_minidrv_op_present(udp, resume)) {
+ usbcam_dbg(udp, DEV_STATE, "invoking minidriver resume");
+ if (udp->ud_minidrv->um_ops->unlocked_pm) {
+ usbcam_unlock(udp);
+ relock = 1;
+ }
+ res = usbcam_minidrv_op(udp, resume);
+ if (relock)
+ usbcam_lock(udp);
+ } else
+ usbcam_dbg(udp, DEV_STATE, "no minidriver resume method");
+
+ if (!res)
+ udp->ud_suspended = 0;
+
+ usbcam_unlock(udp);
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+#endif /* defined(CONFIG_PM) */
+
+
+static const struct usb_driver usbcam_usb_driver_template = {
+ .name = "usbcam minidriver",
+ .probe = usbcam_usb_probe,
+ .disconnect = usbcam_usb_disconnect,
+#if defined(CONFIG_PM)
+ .suspend = usbcam_usb_suspend,
+ .resume = usbcam_usb_resume,
+#endif
+};
+
+
+/*
+ * Minidriver registration/unregistration
+ */
+
+int usbcam_register_mod(usbcam_minidrv_t **driverpp,
+ int minidrv_version, const char *minidrv_verx,
+ const struct usbcam_dev_ops *ops,
+ const int dev_priv_size,
+ const struct usb_device_id *id_table,
+ const int *video_nrs, int video_nrs_len,
+ int *debug, struct module *md, const char *modname)
+{
+ usbcam_minidrv_t *minidrv;
+ int res;
+
+ printk(KERN_INFO "usbcam: registering driver %s %d.%d.%d%s\n",
+ modname,
+ (minidrv_version >> 16) & 0xff,
+ (minidrv_version >> 8) & 0xff,
+ minidrv_version & 0xff,
+ minidrv_verx ? minidrv_verx : "");
+
+ minidrv = (usbcam_minidrv_t *) kzalloc(sizeof(*minidrv), GFP_KERNEL);
+ if (!minidrv) {
+ err("%s: Failed to allocate usbcam_minidrv_t", __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ kref_init(&minidrv->um_kref);
+ minidrv->um_owner = md;
+ minidrv->um_modname = modname;
+ minidrv->um_version = minidrv_version;
+ minidrv->um_debug = debug;
+ minidrv->um_dev_privsize = dev_priv_size;
+ INIT_LIST_HEAD(&minidrv->um_dev_list);
+ mutex_init(&minidrv->um_lock);
+ minidrv->um_video_nr_array = video_nrs;
+ minidrv->um_video_nr_array_len = video_nrs_len;
+
+ minidrv->um_ops = ops;
+
+ minidrv->um_usbdrv = usbcam_usb_driver_template;
+ minidrv->um_usbdrv.name = usbcam_drvname(minidrv);
+ minidrv->um_usbdrv.id_table = id_table;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
+ minidrv->um_usbdrv.supports_autosuspend =
+ minidrv->um_ops->supports_autosuspend;
+#endif
+
+ /*
+ * We have a separate fops per minidriver structure so that
+ * module reference counting works without egregious hacks.
+ */
+ minidrv->um_v4l_fops = usbcam_v4l_fops_template;
+ minidrv->um_v4l_fops.owner = minidrv->um_owner;
+
+ minidrv->um_videodev_template = usbcam_videodev_template;
+ minidrv->um_videodev_template.fops = &minidrv->um_v4l_fops;
+
+ *driverpp = minidrv;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)
+ res = usb_register_driver(&minidrv->um_usbdrv, minidrv->um_owner,
+ minidrv->um_modname);
+#else
+ res = usb_register_driver(&minidrv->um_usbdrv, minidrv->um_owner);
+#endif
+ if (res) {
+ kref_put(&minidrv->um_kref, usbcam_minidrv_release);
+ *driverpp = NULL;
+ }
+
+ usbcam_dbgm(minidrv, DEV_STATE, "registered minidriver");
+
+ return res;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_register_mod);
+
+void usbcam_unregister(usbcam_minidrv_t *minidrv)
+{
+ usbcam_dbgm(minidrv, DEV_STATE, "unregistering minidriver");
+
+ usb_deregister(&minidrv->um_usbdrv);
+
+ if (minidrv->um_dev_count) {
+ /*
+ * This can happen if minidrivers unregister prior to
+ * module_exit(), but is usually bad.
+ */
+ err("%s: %d \"%s\" devices remain",
+ __FUNCTION__, minidrv->um_dev_count,
+ usbcam_drvname(minidrv));
+ }
+
+ kref_put(&minidrv->um_kref, usbcam_minidrv_release);
+}
+USBCAM_EXPORT_SYMBOL(usbcam_unregister);
+
+
+int usbcam_claim_interface(struct usbcam_dev *udp, int ifnum)
+{
+ struct usb_interface *intf;
+ struct usbcam_claimed_interface *cip;
+ int res;
+
+ usbcam_chklock(udp);
+
+ if (!udp->ud_initializing) {
+ usbcam_warn(udp, "%s may only be called from minidriver init",
+ __FUNCTION__);
+ return -EINVAL;
+ }
+
+ intf = usb_ifnum_to_if(udp->ud_dev, ifnum);
+ if (!intf) {
+ usbcam_warn(udp, "%s: interface %d does not exist",
+ __FUNCTION__, ifnum);
+ return -ENODEV;
+ }
+
+ res = usb_driver_claim_interface(&udp->ud_minidrv->um_usbdrv,
+ intf, NULL);
+
+ if (!res) {
+ cip = kmalloc(sizeof(*cip), GFP_KERNEL);
+ if (!cip) {
+ usb_driver_release_interface(&udp->ud_minidrv->
+ um_usbdrv, intf);
+ return -ENOMEM;
+ }
+
+ INIT_LIST_HEAD(&cip->ui_links);
+ cip->ui_intf = usb_get_intf(intf);
+ usb_set_intfdata(intf, udp);
+ usbcam_get(udp);
+ list_add_tail(&cip->ui_links, &udp->ud_interface_list);
+ }
+
+ return res;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_claim_interface);
+
+
+/*
+ * Work queue implementation
+ */
+
+static DECLARE_WAIT_QUEUE_HEAD(usbcam_work_idle_wait);
+
+static int usbcam_work_thread(void *arg)
+{
+ struct usbcam_dev *udp = (struct usbcam_dev *) arg;
+ struct usbcam_workitem *wip;
+ sigset_t wakesigs;
+ unsigned long flags;
+ usbcam_workfunc_t fn;
+ int res;
+
+ current->flags |= PF_NOFREEZE;
+ set_user_nice(current, -5);
+
+ sigemptyset(&wakesigs);
+ sigaddset(&wakesigs, SIGUSR1);
+
+ while (1) {
+ /* Wait for something to appear on the work queue */
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ udp->ud_work_lockwait = 0;
+ if (list_empty(&udp->ud_work_queue)) {
+ if (kthread_should_stop()) {
+ spin_unlock_irqrestore(&udp->ud_work_lock,
+ flags);
+ break;
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ wake_up_all(&usbcam_work_idle_wait);
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+ schedule();
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ }
+ udp->ud_work_lockwait = 1;
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+
+ /* Enable the mutex wait cancelation signal */
+ sigprocmask(SIG_UNBLOCK, &wakesigs, NULL);
+
+ /* Re-check the queue, wait if it's still nonempty */
+ res = -EINTR;
+ if (!list_empty(&udp->ud_work_queue))
+ res = mutex_lock_interruptible(&udp->ud_lock);
+
+ /* Disable the mutex wait cancelation signal */
+ sigprocmask(SIG_BLOCK, &wakesigs, NULL);
+ flush_signals(current);
+
+ if (res)
+ continue;
+
+ wip = NULL;
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ udp->ud_work_lockwait = 0;
+ if (!list_empty(&udp->ud_work_queue)) {
+ wip = container_of(udp->ud_work_queue.next,
+ struct usbcam_workitem,
+ uw_links);
+ list_del_init(&wip->uw_links);
+ }
+
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+
+ if (wip) {
+ fn = wip->uw_func;
+ fn(wip);
+ }
+
+ usbcam_unlock(udp);
+ }
+
+ return 0;
+}
+
+void usbcam_work_init(struct usbcam_dev *udp, struct usbcam_workitem *wip,
+ usbcam_workfunc_t func)
+{
+ INIT_LIST_HEAD(&wip->uw_links);
+ wip->uw_dev = udp;
+ wip->uw_func = func;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_work_init);
+
+int usbcam_work_queue(struct usbcam_workitem *wip)
+{
+ struct usbcam_dev *udp = wip->uw_dev;
+ unsigned long flags;
+ int res;
+
+ assert(udp != NULL);
+
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ if (!list_empty(&wip->uw_links)) {
+ res = -EALREADY;
+ assert(wip->uw_dev == udp);
+ } else if (udp->ud_work_refs) {
+ res = 0;
+ wip->uw_dev = udp;
+ list_add_tail(&wip->uw_links, &udp->ud_work_queue);
+ if (udp->ud_work_queue.next == &wip->uw_links)
+ wake_up_process(udp->ud_work_thread);
+ } else {
+ res = -EBUSY;
+ }
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+
+ return res;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_work_queue);
+
+int usbcam_work_cancel(struct usbcam_workitem *wip)
+{
+ struct usbcam_dev *udp = wip->uw_dev;
+ unsigned long flags;
+ int res, wakeit = 0;
+
+ assert(udp != NULL);
+ usbcam_chklock(udp);
+
+ res = -ENOENT;
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ if (!list_empty(&wip->uw_links)) {
+ res = 0;
+ assert(wip->uw_dev == udp);
+ if ((udp->ud_work_queue.next == &wip->uw_links) &&
+ udp->ud_work_lockwait)
+ wakeit = 1;
+ list_del_init(&wip->uw_links);
+ if (wakeit)
+ force_sig(SIGUSR1, udp->ud_work_thread);
+ }
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+
+ return res;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_work_cancel);
+
+int usbcam_work_ref(struct usbcam_dev *udp)
+{
+ struct task_struct *kt_new;
+ unsigned long flags;
+
+ usbcam_chklock(udp);
+
+ /*
+ * We adjust this value under the spinlock to synchronize with
+ * usbcam_work_queue().
+ */
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ udp->ud_work_refs++;
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+
+ if (!udp->ud_work_thread) {
+ kt_new = kthread_create(usbcam_work_thread, udp,
+ udp->ud_dev_name);
+ if (!kt_new) {
+ usbcam_err(udp, "%s: could not create worker thread",
+ __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ udp->ud_work_thread = kt_new;
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+ }
+
+ return 0;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_work_ref);
+
+void usbcam_work_unref(struct usbcam_dev *udp)
+{
+ unsigned long flags;
+
+ usbcam_chklock(udp);
+
+ if (!udp->ud_work_refs) {
+ usbcam_warn(udp, "%s: work queue has zero refs", __FUNCTION__);
+ return;
+ }
+
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ udp->ud_work_refs--;
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+}
+USBCAM_EXPORT_SYMBOL(usbcam_work_unref);
+
+void usbcam_work_runqueue(struct usbcam_dev *udp)
+{
+ struct usbcam_workitem *wip;
+ unsigned long flags;
+ usbcam_workfunc_t fn;
+
+ usbcam_chklock(udp);
+
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ while (!list_empty(&udp->ud_work_queue)) {
+ wip = container_of(udp->ud_work_queue.next,
+ struct usbcam_workitem,
+ uw_links);
+ list_del_init(&wip->uw_links);
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+
+ fn = wip->uw_func;
+ fn(wip);
+
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ }
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+}
+USBCAM_EXPORT_SYMBOL(usbcam_work_runqueue);
+
+
+void usbcam_work_stop(struct usbcam_dev *udp)
+{
+ struct task_struct *kt_stop = NULL;
+ unsigned long flags;
+
+ usbcam_lock(udp);
+
+ if (!udp->ud_work_refs) {
+ /* Prevent further tasks from being queued */
+ spin_lock_irqsave(&udp->ud_work_lock, flags);
+ kt_stop = udp->ud_work_thread;
+ udp->ud_work_thread = NULL;
+ spin_unlock_irqrestore(&udp->ud_work_lock, flags);
+ }
+
+ usbcam_unlock(udp);
+
+ if (kt_stop) {
+ /*
+ * Wait for the queue to empty out, then stop the
+ * thread. It might be easier to just call
+ * usbcam_work_flush() and execute the remaining
+ * tasks synchronously in the current thread.
+ */
+ wait_event(usbcam_work_idle_wait,
+ list_empty(&udp->ud_work_queue));
+ kthread_stop(kt_stop);
+ }
+}
+
+
+static void usbcam_delayedwork_timeout(unsigned long data)
+{
+ struct usbcam_delayedwork *dwp = (struct usbcam_delayedwork *) data;
+ int res;
+ res = usbcam_work_queue(&dwp->dw_work);
+ if (res)
+ usbcam_warn(dwp->dw_work.uw_dev,
+ "delayed work item submit failed: %d", res);
+}
+
+void usbcam_delayedwork_init(struct usbcam_dev *udp,
+ struct usbcam_delayedwork *dwp,
+ usbcam_workfunc_t func)
+{
+ usbcam_work_init(udp, &dwp->dw_work, func);
+ setup_timer(&dwp->dw_timer,
+ usbcam_delayedwork_timeout,
+ (unsigned long) dwp);
+}
+USBCAM_EXPORT_SYMBOL(usbcam_delayedwork_init);
+
+void usbcam_delayedwork_queue(struct usbcam_delayedwork *dwp,
+ unsigned int timeout_ms)
+{
+ dwp->dw_timer.expires = jiffies + ((timeout_ms * HZ) / 1000);
+ add_timer(&dwp->dw_timer);
+}
+USBCAM_EXPORT_SYMBOL(usbcam_delayedwork_queue);
+
+int usbcam_delayedwork_cancel(struct usbcam_delayedwork *dwp)
+{
+ if (timer_pending(&dwp->dw_timer) && del_timer_sync(&dwp->dw_timer))
+ return 0;
+ return usbcam_work_cancel(&dwp->dw_work);
+}
+USBCAM_EXPORT_SYMBOL(usbcam_delayedwork_cancel);
+
+
+/*
+ * Control related stuff
+ */
+
+struct usbcam_ctrl *usbcam_ctrl_find(struct usbcam_dev *udp, u32 ctlid)
+{
+ struct usbcam_ctrl *ctrlp;
+
+ usbcam_chklock(udp);
+ list_for_each_entry(ctrlp, &udp->ud_ctrl_list, uc_links) {
+ if (ctrlp->uc_v4l.id == ctlid)
+ return ctrlp;
+ }
+ return NULL;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_ctrl_find);
+
+int usbcam_ctrl_add(struct usbcam_dev *udp, struct usbcam_ctrl *ctrlp)
+{
+ int errors = 0;
+ struct usbcam_ctrl *xctrlp;
+
+ usbcam_chklock(udp);
+
+ /* Verify that the ID isn't already registered */
+ xctrlp = usbcam_ctrl_find(udp, ctrlp->uc_v4l.id);
+ if (xctrlp) {
+ usbcam_warn(udp, "control \"%s\" id=%d already defined",
+ ctrlp->uc_v4l.name, ctrlp->uc_v4l.id);
+ errors++;
+ }
+
+ /* Check minimum, maximum, step, and default */
+ switch (ctrlp->uc_v4l.type) {
+ case V4L2_CTRL_TYPE_INTEGER:
+ if (ctrlp->uc_v4l.minimum > ctrlp->uc_v4l.maximum) {
+ usbcam_warn(udp, "control \"%s\" has "
+ "minimum > maximum",
+ ctrlp->uc_v4l.name);
+ errors++;
+ }
+ break;
+
+ case V4L2_CTRL_TYPE_BOOLEAN:
+ break;
+
+ case V4L2_CTRL_TYPE_MENU:
+ if (ctrlp->uc_v4l.minimum) {
+ usbcam_warn(udp, "control \"%s\" is MENU and has "
+ "minimum != 0",
+ ctrlp->uc_v4l.name);
+ errors++;
+ }
+ if (!ctrlp->uc_v4l.maximum) {
+ usbcam_warn(udp, "control \"%s\" is MENU and has "
+ "maximum == 0",
+ ctrlp->uc_v4l.name);
+ errors++;
+ }
+ if (!ctrlp->uc_menu_names) {
+ usbcam_warn(udp, "control \"%s\" is MENU and has "
+ "NULL menu_names",
+ ctrlp->uc_v4l.name);
+ errors++;
+ break;
+ }
+ break;
+
+ case V4L2_CTRL_TYPE_BUTTON:
+ if (ctrlp->uc_v4l.minimum) {
+ usbcam_warn(udp, "control \"%s\" is BUTTON "
+ "and has minimum != 0",
+ ctrlp->uc_v4l.name);
+ errors++;
+ }
+ if (ctrlp->uc_v4l.maximum) {
+ usbcam_warn(udp, "control \"%s\" is BUTTON "
+ "and has maximum != 0",
+ ctrlp->uc_v4l.name);
+ errors++;
+ }
+ if (ctrlp->uc_v4l.step) {
+ usbcam_warn(udp, "control \"%s\" is BUTTON "
+ "and has step != 0",
+ ctrlp->uc_v4l.name);
+ errors++;
+ }
+ break;
+
+ default:
+ usbcam_warn(udp, "control \"%s\" is of "
+ "invalid type %d",
+ ctrlp->uc_v4l.name,
+ ctrlp->uc_v4l.type);
+ errors++;
+ }
+
+ /* Check the range */
+ if (ctrlp->uc_v4l.type == V4L2_CTRL_TYPE_BOOLEAN) {
+ if ((ctrlp->uc_v4l.default_value != 0) &&
+ (ctrlp->uc_v4l.default_value != 1)) {
+ usbcam_warn(udp, "control \"%s\" is BOOLEAN "
+ "and default value is %d",
+ ctrlp->uc_v4l.name,
+ ctrlp->uc_v4l.default_value);
+ errors++;
+ }
+ }
+
+ else if ((ctrlp->uc_v4l.default_value <
+ ctrlp->uc_v4l.minimum) ||
+ (ctrlp->uc_v4l.default_value >
+ ctrlp->uc_v4l.maximum)) {
+ usbcam_warn(udp, "control \"%s\" default out of range",
+ ctrlp->uc_v4l.name);
+ errors++;
+ }
+
+ /* Check the get_fn callout */
+ if (ctrlp->uc_v4l.type == V4L2_CTRL_TYPE_BUTTON) {
+ if (ctrlp->get_fn) {
+ usbcam_warn(udp, "control \"%s\" is BUTTON "
+ "and has a get_fn callout",
+ ctrlp->uc_v4l.name);
+ errors++;
+ }
+ if (!ctrlp->set_fn) {
+ usbcam_warn(udp, "control \"%s\" is BUTTON "
+ "and has no set_fn callout",
+ ctrlp->uc_v4l.name);
+ errors++;
+ }
+ }
+
+ if (errors)
+ return -EINVAL;
+
+ list_add_tail(&ctrlp->uc_links, &udp->ud_ctrl_list);
+ return 0;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_ctrl_add);
+
+struct usbcam_ctrl *usbcam_ctrl_alloc(size_t real_size)
+{
+ struct usbcam_ctrl *ctrlp;
+
+ if (!real_size)
+ real_size = sizeof(*ctrlp);
+ if (real_size < sizeof(*ctrlp)) {
+ printk(KERN_WARNING "Minidriver set control size %zd, "
+ "must be at least %zd\n", real_size, sizeof(*ctrlp));
+ return NULL;
+ }
+
+ ctrlp = kzalloc(real_size, GFP_KERNEL);
+ if (!ctrlp)
+ return NULL;
+
+ INIT_LIST_HEAD(&ctrlp->uc_links);
+ return ctrlp;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_ctrl_alloc);
+
+struct usbcam_ctrl *usbcam_ctrl_add_tmpl(struct usbcam_dev *udp,
+ const struct usbcam_ctrl *tmplp,
+ size_t real_size)
+{
+ struct usbcam_ctrl *ctrlp;
+
+ ctrlp = usbcam_ctrl_alloc(real_size);
+ if (!ctrlp)
+ return NULL;
+
+ memcpy(ctrlp, tmplp, real_size);
+ INIT_LIST_HEAD(&ctrlp->uc_links);
+
+ if (usbcam_ctrl_add(udp, ctrlp)) {
+ usbcam_ctrl_free(ctrlp);
+ return NULL;
+ }
+
+ return ctrlp;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_ctrl_add_tmpl);
+
+void usbcam_ctrl_releaseall(struct usbcam_dev *udp)
+{
+ struct usbcam_ctrl *ctrlp;
+ while (!list_empty(&udp->ud_ctrl_list)) {
+ ctrlp = list_entry(udp->ud_ctrl_list.next,
+ struct usbcam_ctrl,
+ uc_links);
+ list_del_init(&ctrlp->uc_links);
+ if (ctrlp->release_fn)
+ ctrlp->release_fn(udp, ctrlp);
+ kfree(ctrlp);
+ }
+}
+
+
+MODULE_DESCRIPTION("Abstraction Library for USB Webcam Drivers");
+MODULE_AUTHOR("Sam Revitch <samr7 cs washington edu>");
+MODULE_LICENSE("GPL");
diff --git a/usbcam/usbcam_fops.c b/usbcam/usbcam_fops.c
@@ -0,0 +1,852 @@
+/*
+ * USBCAM abstraction library for USB webcam drivers
+ * Version 0.1.1
+ *
+ * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "usbcam_priv.h"
+
+/*
+ * This file contains the file_operations implementation
+ *
+ * TODO LIST:
+ * - Add debug tracing to more ioctl paths
+ */
+
+/*
+ * V4L file_operations callout implementations
+ */
+
+static int usbcam_v4l_open(struct inode *inode, struct file *filp)
+{
+ struct usbcam_dev *udp;
+ struct usbcam_fh *ufp;
+ int autopm_ref = 0;
+ int work_ref = 0;
+ int res = 0;
+
+ /* The usbcam_dev is referenced by the videodev at this point */
+ udp = container_of(video_devdata(filp), struct usbcam_dev, ud_vdev);
+
+ ufp = (struct usbcam_fh *) kzalloc(sizeof(*ufp), GFP_KERNEL);
+ if (!ufp)
+ return -ENOMEM;
+
+ ufp->ufh_dev = udp;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+ videobuf_queue_init(&ufp->ufh_vbq,
+ &usbcam_videobuf_qops,
+ NULL,
+ NULL,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ V4L2_FIELD_INTERLACED,
+ sizeof(struct usbcam_frame), ufp);
+#else
+ videobuf_queue_pci_init(&ufp->ufh_vbq,
+ &usbcam_videobuf_qops,
+ NULL,
+ NULL,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ V4L2_FIELD_INTERLACED,
+ sizeof(struct usbcam_frame), ufp);
+#endif
+
+ mutex_lock(&udp->ud_open_lock);
+
+ if (!udp->ud_user_refs) {
+ res = usb_autopm_get_interface(udp->ud_intf);
+ if (res)
+ goto bail_nolock;
+ autopm_ref = 1;
+ }
+
+ usbcam_lock(udp);
+
+ if (udp->ud_disconnected) {
+ res = -ENODEV;
+ goto bail;
+ }
+
+ if (!udp->ud_user_refs) {
+ if (!udp->ud_minidrv->um_ops->no_workref_on_open) {
+ res = usbcam_work_ref(udp);
+ if (res)
+ goto bail;
+ work_ref = 1;
+ }
+
+ if (usbcam_minidrv_op_present(udp, open)) {
+ res = usbcam_minidrv_op(udp, open);
+ if (res) {
+ if (work_ref)
+ usbcam_work_unref(udp);
+ assert(!udp->ud_user_refs);
+ goto bail;
+ }
+ }
+
+ /* Transfer the autopm reference */
+ assert(autopm_ref);
+ autopm_ref = 0;
+ }
+
+ udp->ud_user_refs++;
+ filp->private_data = ufp;
+ usbcam_get(udp);
+
+bail:
+ usbcam_unlock(udp);
+bail_nolock:
+ mutex_unlock(&udp->ud_open_lock);
+ if (res)
+ kfree(ufp);
+ if (autopm_ref)
+ usb_autopm_put_interface(udp->ud_intf);
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+static int usbcam_v4l_release(struct inode *inode, struct file *filp)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ int autopm_ref = 0;
+
+ videobuf_mmap_free(&ufp->ufh_vbq);
+
+ mutex_lock(&udp->ud_open_lock);
+ usbcam_lock(udp);
+
+ assert(udp->ud_user_refs);
+ if (udp->ud_excl_owner == filp)
+ udp->ud_excl_owner = NULL;
+ kfree(ufp);
+ filp->private_data = NULL;
+
+ if (!--udp->ud_user_refs) {
+ usbcam_capture_stop(udp);
+
+ if (usbcam_minidrv_op_present(udp, close))
+ usbcam_minidrv_op(udp, close);
+ if (!udp->ud_minidrv->um_ops->no_workref_on_open)
+ usbcam_work_unref(udp);
+ autopm_ref = 1;
+ }
+
+ usbcam_unlock(udp);
+ mutex_unlock(&udp->ud_open_lock);
+ if (autopm_ref)
+ usb_autopm_put_interface(udp->ud_intf);
+ usbcam_work_maybe_stop(udp);
+ usbcam_put(udp);
+ return 0;
+}
+
+static ssize_t usbcam_v4l_read(struct file *filp, char __user *data,
+ size_t count, loff_t *ppos)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data;
+ ssize_t res;
+
+ res = videobuf_read_one(&ufp->ufh_vbq, data, count, ppos,
+ (filp->f_flags & O_NONBLOCK) ? 1 : 0);
+ usbcam_work_maybe_stop(ufp->ufh_dev);
+ return res;
+}
+
+static unsigned int usbcam_v4l_poll(struct file *filp,
+ struct poll_table_struct *wait)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data;
+
+ return videobuf_poll_stream(filp, &ufp->ufh_vbq, wait);
+}
+
+static int usbcam_v4l_mmap(struct file *filp, struct vm_area_struct * vma)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) filp->private_data;
+
+ return videobuf_mmap_mapper(&ufp->ufh_vbq, vma);
+}
+
+/*
+ * The template file_operations structure
+ *
+ * Each usbcam_minidrv_t contains its own copy of this, which
+ * is associated with the video4linux device created for that
+ * minidriver.
+ *
+ * In general, copies will differ only in the .owner field, which
+ * will refer to the minidriver module, not usbcam.
+ */
+
+struct file_operations usbcam_v4l_fops_template = {
+ .owner = THIS_MODULE,
+ .open = usbcam_v4l_open,
+ .release = usbcam_v4l_release,
+ .read = usbcam_v4l_read,
+ .poll = usbcam_v4l_poll,
+ .mmap = usbcam_v4l_mmap,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+
+static void usbcam_dbg_v4l2_buffer_res(struct usbcam_dev *udp, int res,
+ void *arg, const char *prefix)
+{
+ struct v4l2_buffer *b __attribute__((unused)) =
+ (struct v4l2_buffer *) arg;
+
+ if (res) {
+ usbcam_dbg(udp, IOCTL_BUF, "%s res:%d", prefix, res);
+ return;
+ }
+
+ usbcam_dbg(udp, IOCTL_BUF, "%s out: index=%d type=%d bytesused=%d "
+ "flags=0x%x field=%d memory=%d m=0x%lx length=%d",
+ prefix, b->index, b->type, b->bytesused,
+ b->flags, b->field, b->memory, b->m.userptr, b->length);
+}
+
+static void usbcam_dbg_v4l2_pix_format(struct usbcam_dev *udp,
+ struct v4l2_pix_format *f,
+ const char *prefix)
+{
+ __u32 pixfmt = f->pixelformat;
+ if (!pixfmt)
+ pixfmt = 0x3f3f3f3f;
+ usbcam_dbg(udp, IOCTL_FMT, "%s wid=%d hgt=%d fmt=%.4s field=%d "
+ "bpl=%d size=%d cs=%d", prefix,
+ f->width, f->height, (char *) &pixfmt, f->field,
+ f->bytesperline, f->sizeimage, f->colorspace);
+}
+
+static void usbcam_dbg_v4l2_pix_format_res(struct usbcam_dev *udp, int res,
+ struct v4l2_pix_format *f,
+ const char *prefix)
+{
+ if (res) {
+ usbcam_dbg(udp, IOCTL_FMT, "%s %d", prefix, res);
+ return;
+ }
+ usbcam_dbg_v4l2_pix_format(udp, f, prefix);
+}
+
+static int usbcam_v4l_vidiocgmbuf(struct file *filp, void *fh,
+ struct video_mbuf *p)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ struct v4l2_requestbuffers req;
+ unsigned int i;
+ int res;
+
+ /*
+ * APPBUG: motion keeps the first mmap, yet requests
+ * larger capture sizes.
+ */
+ usbcam_lock(udp);
+ ufp->ufh_flags |= USBCAM_FH_USE_FIXED_FB;
+ usbcam_unlock(udp);
+
+ req.type = ufp->ufh_vbq.type;
+ req.count = 2;
+ req.memory = V4L2_MEMORY_MMAP;
+ res = videobuf_reqbufs(&ufp->ufh_vbq, &req);
+ if (res) {
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOCGMBUF reqbufs failed: %d", res);
+ return res;
+ }
+
+ p->frames = req.count;
+ p->size = 0;
+ for (i = 0; i < p->frames; i++) {
+ p->offsets[i] = ufp->ufh_vbq.bufs[i]->boff;
+ p->size += ufp->ufh_vbq.bufs[i]->bsize;
+ }
+
+ usbcam_dbg(udp, IOCTL_BUF, "VIDIOCGMBUF frames=%d size=%d",
+ p->frames, p->size);
+ return 0;
+}
+
+static int usbcam_v4l_vidioc_reqbufs(struct file *filp, void *fh,
+ struct v4l2_requestbuffers *r)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ int res;
+
+ /* APPBUG: disable USE_FIXED_FB if we enter this path */
+ usbcam_lock(udp);
+ ufp->ufh_flags &= ~(USBCAM_FH_USE_FIXED_FB);
+ usbcam_unlock(udp);
+
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOC_REQBUFS count=%d type=%d memory=%d",
+ r->count, r->type, r->memory);
+ res = videobuf_reqbufs(&ufp->ufh_vbq, r);
+ usbcam_dbg(udp, IOCTL_BUF, "REQBUFS result=%d", res);
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+static int usbcam_v4l_vidioc_querybuf(struct file *filp, void *fh,
+ struct v4l2_buffer *b)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ int res;
+
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOC_QUERYBUF in: index=%d type=%d",
+ b->index, b->type);
+ res = videobuf_querybuf(&ufp->ufh_vbq, b);
+ usbcam_dbg_v4l2_buffer_res(udp, res, b, "VIDIOC_QUERYBUF");
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+static int usbcam_v4l_vidioc_qbuf(struct file *filp, void *fh,
+ struct v4l2_buffer *b)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ int res;
+
+ /*
+ * APPBUG: ptlib / Ekiga has an issue with zeroing the
+ * flags field before calling QBUF.
+ *
+ * Minidriver support for fast input switching is
+ * unavailable for the time being.
+ */
+ b->flags = 0;
+
+ usbcam_dbg(udp, IOCTL_BUF, "VIDIOC_QBUF in: index=%d type=%d",
+ b->index, b->type);
+ res = videobuf_qbuf(&ufp->ufh_vbq, b);
+ usbcam_dbg_v4l2_buffer_res(udp, res, b, "VIDIOC_QBUF");
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+static int usbcam_v4l_vidioc_dqbuf(struct file *filp, void *fh,
+ struct v4l2_buffer *b)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ int res;
+
+ res = videobuf_dqbuf(&ufp->ufh_vbq, b,
+ (filp->f_flags & O_NONBLOCK) ? 1 : 0);
+ usbcam_dbg_v4l2_buffer_res(udp, res, b, "VIDIOC_DQBUF");
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+static int usbcam_v4l_vidioc_streamon(struct file *filp, void *fh,
+ enum v4l2_buf_type f)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ int res;
+
+ if (f != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOC_STREAMON: invalid buf type %d", f);
+ return -EINVAL;
+ }
+ if (!udp->ud_excl_owner) {
+ usbcam_lock(udp);
+ if (!udp->ud_excl_owner)
+ udp->ud_excl_owner = filp;
+ usbcam_unlock(udp);
+ }
+ if (udp->ud_excl_owner != filp) {
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOC_STREAMON: not exclusive owner");
+ return -EBUSY;
+ }
+ res = videobuf_streamon(&ufp->ufh_vbq);
+ usbcam_dbg(udp, IOCTL_BUF, "VIDIOC_STREAMON: res:%d", res);
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+static int usbcam_v4l_vidioc_streamoff(struct file *filp, void *fh,
+ enum v4l2_buf_type f)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ int res;
+
+ if (f != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ usbcam_dbg(udp, IOCTL_BUF,
+ "VIDIOC_STREAMOFF: invalid buf type %d", f);
+ return -EINVAL;
+ }
+ res = videobuf_streamoff(&ufp->ufh_vbq);
+ usbcam_dbg(udp, IOCTL_BUF, "VIDIOC_STREAMOFF: res:%d", res);
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+
+/* DEFAULT CAPABILITIES / DEVICE NAME / DRIVER NAME / BUS INFO */
+static int usbcam_v4l_vidioc_querycap(struct file *filp, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+
+ usbcam_lock(udp);
+ strlcpy(cap->driver,
+ usbcam_drvname(udp->ud_minidrv),
+ sizeof(cap->driver));
+ strlcpy(cap->card, udp->ud_vdev.name, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info),
+ "usb:%s", udp->ud_dev->dev.bus_id);
+ cap->version = udp->ud_minidrv->um_version;
+ cap->capabilities = (V4L2_CAP_VIDEO_CAPTURE |
+ V4L2_CAP_READWRITE |
+ V4L2_CAP_STREAMING);
+ usbcam_unlock(udp);
+ return 0;
+}
+
+/* DEFAULT FORMAT HANDLING - USE MINDIRIVER CALLOUTS */
+static int usbcam_v4l_vidioc_enum_fmt_cap(struct file *filp, void *fh,
+ struct v4l2_fmtdesc *f)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ struct usbcam_pix_fmt *pf;
+ int res;
+
+ usbcam_lock(udp);
+
+ res = -EINVAL;
+ if (f->index >= udp->ud_fmt_array_len)
+ goto enum_fmt_done;
+
+ res = 0;
+ pf = (struct usbcam_pix_fmt *)
+ &(((u8 *) udp->ud_fmt_array)
+ [f->index * udp->ud_fmt_array_elem_size]);
+ f->flags = pf->flags;
+ f->pixelformat = pf->pixelformat;
+ strlcpy(f->description,
+ pf->description,
+ sizeof(f->description));
+
+enum_fmt_done:
+ usbcam_unlock(udp);
+ return res;
+}
+
+static int usbcam_v4l_vidioc_g_fmt_cap(struct file *filp, void *fh,
+ struct v4l2_format *f)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+
+ usbcam_lock(udp);
+ f->fmt.pix = udp->ud_format;
+ usbcam_unlock(udp);
+ usbcam_dbg_v4l2_pix_format(udp, &f->fmt.pix, "VIDIOC_G_FMT: res:");
+ return 0;
+}
+
+static int usbcam_v4l_vidioc_s_fmt_cap(struct file *filp, void *fh,
+ struct v4l2_format *f)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ int res;
+
+ usbcam_lock(udp);
+
+ usbcam_dbg_v4l2_pix_format(udp, &f->fmt.pix,
+ "VIDIOC_S_FMT: param:");
+
+ if (udp->ud_disconnected) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_S_FMT: device disconnected");
+ res = -EIO;
+ goto s_fmt_done;
+ }
+ if (!udp->ud_excl_owner)
+ udp->ud_excl_owner = filp;
+ if (!memcmp(&f->fmt.pix, &udp->ud_format, sizeof(f->fmt.pix))) {
+ usbcam_dbg(udp, IOCTL_FMT, "VIDIOC_S_FMT: nothing to do");
+ res = 0;
+ goto s_fmt_done;
+ }
+ if (!usbcam_minidrv_op_present(udp, set_format)) {
+ usbcam_dbg(udp, IOCTL_FMT, "VIDIOC_S_FMT: no minidriver op");
+ res = -EINVAL;
+ goto s_fmt_done;
+ }
+ if (udp->ud_excl_owner != filp) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_S_FMT: not exclusive owner");
+ res = -EBUSY;
+ goto s_fmt_done;
+ }
+ usbcam_capture_stop_nondestructive(udp);
+ if (udp->ud_capturing) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_S_FMT: capture in progress");
+ res = -EBUSY;
+ goto s_fmt_done;
+ }
+ res = usbcam_minidrv_op(udp, set_format, &f->fmt.pix);
+ usbcam_dbg_v4l2_pix_format_res(udp, res, &f->fmt.pix,
+ "VIDIOC_S_FMT: res:");
+
+s_fmt_done:
+ usbcam_unlock(udp);
+ return res;
+}
+
+static int usbcam_v4l_vidioc_try_fmt_cap(struct file *filp, void *fh,
+ struct v4l2_format *f)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ int res;
+
+ usbcam_lock(udp);
+
+ usbcam_dbg_v4l2_pix_format(udp, &f->fmt.pix,
+ "VIDIOC_TRY_FMT: param:");
+
+ if (udp->ud_disconnected) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_TRY_FMT: device disconnected");
+ res = -EIO;
+ goto try_fmt_done;
+ }
+ if (!usbcam_minidrv_op_present(udp, try_format)) {
+ usbcam_dbg(udp, IOCTL_FMT,
+ "VIDIOC_TRY_FMT: no minidriver op");
+ res = -EINVAL;
+ goto try_fmt_done;
+ }
+
+ res = usbcam_minidrv_op(udp, try_format, &f->fmt.pix);
+ usbcam_dbg_v4l2_pix_format_res(udp, res, &f->fmt.pix,
+ "VIDIOC_TRY_FMT: res:");
+
+try_fmt_done:
+ usbcam_unlock(udp);
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+/* DEFAULT CONTROL HANDLING - USE MINIDRIVER ARRAY / CALLOUTS */
+static int usbcam_v4l_vidioc_queryctrl(struct file *filp, void *fh,
+ struct v4l2_queryctrl *a)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ const struct usbcam_ctrl *ctrlp, *resp;
+ int droplock;
+ u32 targ;
+ int higher_ids = 0, res = -EINVAL;
+
+ usbcam_lock(udp);
+ droplock = 1;
+ targ = a->id;
+
+ resp = NULL;
+
+ if (targ & V4L2_CTRL_FLAG_NEXT_CTRL) {
+ /*
+ * Find the control with the least ID greater than or
+ * equal to a->id
+ */
+ targ &= ~V4L2_CTRL_FLAG_NEXT_CTRL;
+ list_for_each_entry(ctrlp, &udp->ud_ctrl_list, uc_links) {
+ if (ctrlp->uc_v4l.id <= targ) {
+ if (!resp || (ctrlp->uc_v4l.id <
+ resp->uc_v4l.id))
+ resp = ctrlp;
+ }
+ }
+
+ } else {
+ /* Find an exact match */
+ list_for_each_entry(ctrlp, &udp->ud_ctrl_list, uc_links) {
+ if (ctrlp->uc_v4l.id == targ) {
+ resp = ctrlp;
+ break;
+ }
+ else if ((ctrlp->uc_v4l.id >=
+ V4L2_CID_PRIVATE_BASE) &&
+ (ctrlp->uc_v4l.id <
+ (V4L2_CID_PRIVATE_BASE + 1024)) &&
+ (targ >= V4L2_CID_PRIVATE_BASE) &&
+ (targ < ctrlp->uc_v4l.id)) {
+ higher_ids = 1;
+ }
+ }
+
+ if (!resp && higher_ids) {
+ /*
+ * Deal with the private control enumeration
+ * nonsense that the CTRL_FLAG_NEXT_CTRL thing
+ * fixes.
+ */
+ memset(a, 0, sizeof(*a));
+ a->id = targ;
+ a->type = V4L2_CTRL_TYPE_INTEGER;
+ strlcpy(a->name, "Disabled", sizeof(a->name));
+ a->flags = V4L2_CTRL_FLAG_DISABLED;
+ res = 0;
+ }
+ }
+
+ if (resp) {
+ *a = resp->uc_v4l;
+ res = 0;
+
+ /* Fill in required values for certain types */
+ switch (a->type) {
+ case V4L2_CTRL_TYPE_BOOLEAN:
+ a->minimum = 0;
+ a->maximum = 1;
+ a->step = 1;
+ break;
+ case V4L2_CTRL_TYPE_MENU:
+ a->step = 1;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * If a query function was provided, call it to
+ * postprocess the response structure, e.g. to set
+ * flags.
+ */
+ if (resp->query_fn) {
+ if (udp->ud_minidrv->um_ops->unlocked_ctrl) {
+ usbcam_unlock(udp);
+ droplock = 0;
+ }
+ res = resp->query_fn(udp, resp, a);
+ }
+ }
+
+ if (droplock)
+ usbcam_unlock(udp);
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+static int usbcam_v4l_vidioc_g_ctrl(struct file *filp, void *fh,
+ struct v4l2_control *a)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ const struct usbcam_ctrl *resp;
+ struct v4l2_ext_control ec;
+ int droplock;
+ int res;
+
+ usbcam_lock(udp);
+ droplock = 1;
+
+ if (udp->ud_disconnected) {
+ usbcam_unlock(udp);
+ return -EIO;
+ }
+
+ resp = usbcam_ctrl_find(udp, a->id);
+ if (!resp ||
+ (resp->uc_v4l.type == V4L2_CTRL_TYPE_BUTTON) ||
+ !resp->get_fn)
+ res = -EINVAL;
+ else {
+ if (udp->ud_minidrv->um_ops->unlocked_ctrl) {
+ usbcam_unlock(udp);
+ droplock = 0;
+ }
+ memset(&ec, 0, sizeof(ec));
+ ec.id = a->id;
+ ec.value = a->value;
+ res = resp->get_fn(udp, resp, &ec);
+ memset(a, 0, sizeof(*a));
+ a->id = ec.id;
+ a->value = ec.value;
+ }
+
+ if (droplock)
+ usbcam_unlock(udp);
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+static int usbcam_v4l_vidioc_s_ctrl(struct file *filp, void *fh,
+ struct v4l2_control *a)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ const struct usbcam_ctrl *resp;
+ struct v4l2_ext_control ec;
+ int droplock;
+ int res;
+
+ usbcam_lock(udp);
+ droplock = 1;
+
+ if (udp->ud_disconnected) {
+ usbcam_unlock(udp);
+ return -EIO;
+ }
+
+ resp = usbcam_ctrl_find(udp, a->id);
+ if (!resp) {
+ res = -EINVAL;
+ } else if (!resp->set_fn) {
+ /* Read-only control */
+ res = -EBUSY;
+ } else if ((resp->uc_v4l.type != V4L2_CTRL_TYPE_BUTTON) &&
+ ((a->value < resp->uc_v4l.minimum) ||
+ (a->value > resp->uc_v4l.maximum))) {
+ res = -ERANGE;
+ } else {
+ if (udp->ud_minidrv->um_ops->unlocked_ctrl) {
+ usbcam_unlock(udp);
+ droplock = 0;
+ }
+ memset(&ec, 0, sizeof(ec));
+ ec.id = a->id;
+ ec.value = a->value;
+ res = resp->set_fn(udp, resp, &ec);
+ memset(a, 0, sizeof(*a));
+ a->id = ec.id;
+ a->value = ec.value;
+ }
+
+ if (droplock)
+ usbcam_unlock(udp);
+ usbcam_work_maybe_stop(udp);
+ return res;
+}
+
+static int usbcam_v4l_vidioc_querymenu(struct file *filp, void *fh,
+ struct v4l2_querymenu *a)
+{
+ struct usbcam_fh *ufp = (struct usbcam_fh *) fh;
+ struct usbcam_dev *udp = ufp->ufh_dev;
+ const struct usbcam_ctrl *resp;
+ int res;
+
+ usbcam_lock(udp);
+
+ resp = usbcam_ctrl_find(udp, a->id);
+
+ if (!resp ||
+ (resp->uc_v4l.type != V4L2_CTRL_TYPE_MENU) ||
+ (a->index > resp->uc_v4l.maximum)) {
+ res = -EINVAL;
+ goto querymenu_done;
+ }
+
+ strlcpy(a->name,
+ resp->uc_menu_names[a->index],
+ sizeof(a->name));
+ res = 0;
+
+querymenu_done:
+ usbcam_unlock(udp);
+ return res;
+}
+
+/* DEFAULT INPUT HANDLING -- There is one input called "Camera" */
+static int usbcam_v4l_vidioc_enum_input(struct file *filp, void *fh,
+ struct v4l2_input *inp)
+{
+ const struct v4l2_input dfl_input = {
+ .name = "Camera",
+ .type = V4L2_INPUT_TYPE_CAMERA,
+ };
+
+ if (inp->index > 0)
+ return -EINVAL;
+ *inp = dfl_input;
+ return 0;
+}
+
+static int usbcam_v4l_vidioc_g_input(struct file *filp, void *fh,
+ unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int usbcam_v4l_vidioc_s_input(struct file *filp, void *fh,
+ unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+/*
+ * The template video_device structure
+ *
+ * Each usbcam_dev contains its own copy of this. The minidriver is
+ * free to install its own handlers for each interface, although it
+ * should take care not to screw up the frame buffer handling.
+ */
+
+struct video_device usbcam_videodev_template = {
+ .name = "usbcam-unknown",
+ .type = VFL_TYPE_GRABBER,
+ .type2 = VID_TYPE_CAPTURE,
+ .minor = -1,
+
+ .vidioc_querycap = usbcam_v4l_vidioc_querycap,
+ .vidioc_enum_fmt_cap = usbcam_v4l_vidioc_enum_fmt_cap,
+ .vidioc_g_fmt_cap = usbcam_v4l_vidioc_g_fmt_cap,
+ .vidioc_s_fmt_cap = usbcam_v4l_vidioc_s_fmt_cap,
+ .vidioc_try_fmt_cap = usbcam_v4l_vidioc_try_fmt_cap,
+ .vidioc_reqbufs = usbcam_v4l_vidioc_reqbufs,
+ .vidioc_querybuf = usbcam_v4l_vidioc_querybuf,
+ .vidioc_qbuf = usbcam_v4l_vidioc_qbuf,
+ .vidioc_dqbuf = usbcam_v4l_vidioc_dqbuf,
+ .vidiocgmbuf = usbcam_v4l_vidiocgmbuf,
+ .vidioc_enum_input = usbcam_v4l_vidioc_enum_input,
+ .vidioc_streamon = usbcam_v4l_vidioc_streamon,
+ .vidioc_streamoff = usbcam_v4l_vidioc_streamoff,
+ .vidioc_g_input = usbcam_v4l_vidioc_g_input,
+ .vidioc_s_input = usbcam_v4l_vidioc_s_input,
+ .vidioc_queryctrl = usbcam_v4l_vidioc_queryctrl,
+ .vidioc_g_ctrl = usbcam_v4l_vidioc_g_ctrl,
+ .vidioc_s_ctrl = usbcam_v4l_vidioc_s_ctrl,
+ .vidioc_querymenu = usbcam_v4l_vidioc_querymenu,
+};
diff --git a/usbcam/usbcam_priv.h b/usbcam/usbcam_priv.h
@@ -0,0 +1,202 @@
+/*
+ * USBCAM abstraction library for USB webcam drivers
+ *
+ * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This header file is private and internal to usbcam.
+ * DO NOT INCLUDE THIS HEADER FILE IN MINIDRIVERS. USE usbcam.h INSTEAD.
+ */
+
+#if !defined(__USBCAM_PRIV_H__)
+#define __USBCAM_PRIV_H__
+
+#define CONFIG_USB_USBCAM_DEBUG
+
+#include "usbcam.h"
+
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/smp_lock.h>
+#include <linux/vmalloc.h>
+#include <linux/mm.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/videodev.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+#include <media/video-buf.h>
+#else
+#include <media/videobuf-dma-sg.h>
+#endif
+
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
+#define usb_endpoint_xfer_bulk(EPD) \
+ (((EPD)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == \
+ USB_ENDPOINT_XFER_BULK)
+#define usb_endpoint_xfer_int(EPD) \
+ (((EPD)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == \
+ USB_ENDPOINT_XFER_INT)
+#define usb_endpoint_xfer_isoc(EPD) \
+ (((EPD)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == \
+ USB_ENDPOINT_XFER_ISOC)
+#define usb_endpoint_dir_in(EPD) \
+ (((EPD)->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN)
+#define usb_autopm_get_interface(X) 0
+#define usb_autopm_put_interface(X)
+#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) */
+
+
+/*
+ * When creating a minidriver that is not part of the core kernel, it
+ * may be desired to use a specific version of usbcam. In this case,
+ * usbcam's symbols should be available only to the minidriver with
+ * which it is linked, and its symbols should not be exported.
+ */
+#define USBCAM_EXPORT_SYMBOL(X) EXPORT_SYMBOL(X)
+
+#define assert usbcam_assert
+
+#define usbcam_drvname(MDP) ((MDP)->um_owner->name)
+
+#if defined(CONFIG_USB_USBCAM_DEBUG)
+#define usbcam_dbgm(MD, SUBSYS, FMT, ARG...) do { \
+ if ((MD)->um_debug && \
+ *(MD)->um_debug & (1UL << USBCAM_DBG_ ## SUBSYS)) \
+ printk(KERN_DEBUG "%s: " FMT "\n", \
+ (MD)->um_modname, ## ARG); \
+} while (0)
+#else
+#define usbcam_dbgm(MD, SUBSYS, FMT, ARG...)
+#endif /* defined(CONFIG_USB_USBCAM_DEBUG) */
+
+#define usbcam_minidrv_op_present(UDP, CB) \
+ ((UDP)->ud_minidrv->um_ops->CB ? 1 : 0)
+#define usbcam_minidrv_op(UDP, CB, ARGS...) \
+ ((UDP)->ud_minidrv->um_ops->CB((UDP), ## ARGS))
+
+
+/*
+ * Private data structure definitions
+ */
+
+/*
+ * This structure represents a registered minidriver
+ */
+struct usbcam_minidrv {
+ struct kref um_kref;
+ struct module *um_owner;
+ const char *um_modname;
+ int *um_debug;
+ int um_version;
+ int um_dev_count;
+ struct list_head um_dev_list;
+ int um_dev_privsize;
+ struct usb_driver um_usbdrv;
+ struct mutex um_lock;
+ const struct usbcam_dev_ops *um_ops;
+ struct video_device um_videodev_template;
+ struct file_operations um_v4l_fops;
+ const int *um_video_nr_array;
+ int um_video_nr_array_len;
+};
+
+/*
+ * The frame structure is generally managed by the video-buf module
+ * and represents some chunk of memory that the video4linux client
+ * requested as a frame buffer. It might be vmalloc()'d, or it might
+ * be mapped from a user address space. In either case, usbcam
+ * guarantees a contiguous kernel mapping accessible to the minidriver.
+ *
+ * The primary reason to use video-buf in usbcam is for its
+ * implementation of buffer mapping methods and "zero-copy" kernel-user
+ * data movement. The V4L2 API is quite rich, and it's much easier to
+ * use video-buf than to create a private full-featured implementation,
+ * and much more desirable to use video-buf than to limp along with a
+ * substandard implementation. The video-buf module isn't specifically
+ * used for DMA functionality, as most USB devices, with the possible
+ * exception of those employing bulk transfers, are unsuitable for
+ * direct frame buffer DMA.
+ *
+ * Minidrivers can access details of the current frame using
+ * usbcam_curframe_get(), and can signal completion of the current
+ * frame with usbcam_curframe_complete(). It is up to the minidriver
+ * to fill in the frame buffer.
+ */
+struct usbcam_frame {
+ struct videobuf_buffer vbb;
+ struct list_head cap_links;
+ void *vmap_base;
+ void *vmap_sof;
+};
+
+/*
+ * This structure represents an open file handle and the frame
+ * buffers associated with that client
+ */
+struct usbcam_fh {
+ struct usbcam_dev *ufh_dev;
+ int ufh_flags;
+ struct videobuf_queue ufh_vbq;
+};
+
+#define USBCAM_FH_USE_FIXED_FB 0x00000001
+
+extern void usbcam_work_stop(struct usbcam_dev *udp);
+extern void usbcam_capture_stop(struct usbcam_dev *udp);
+extern void usbcam_ctrl_releaseall(struct usbcam_dev *udp);
+
+/*
+ * The below maybe_stop function allows usbcam to ensure that any kernel
+ * threads it creates are completely stopped and not executing any
+ * usbcam code at module unload time.
+ *
+ * This is ugly but it remains unclear how better to do it.
+ */
+static inline void usbcam_work_maybe_stop(struct usbcam_dev *udp)
+{
+ if (!udp->ud_work_refs)
+ usbcam_work_stop(udp);
+}
+
+/*
+ * Only stop capturing if no frames are queued.
+ *
+ * We allow and encourage the minidriver to continue capturing in the
+ * last requested format, and have it stop autonomously when it receives
+ * its first data for the next frame but finds no frame available. This
+ * expedites the process for situations such as S_FMT which cannot
+ * tolerate capture being in progress.
+ */
+static inline void usbcam_capture_stop_nondestructive(struct usbcam_dev *udp)
+{
+ if (udp->ud_capturing && list_empty(&udp->ud_frame_cap_queue))
+ usbcam_capture_stop(udp);
+}
+
+extern struct videobuf_queue_ops usbcam_videobuf_qops;
+extern struct file_operations usbcam_v4l_fops_template;
+extern struct video_device usbcam_videodev_template;
+
+#endif /* !defined(__USBCAM_PRIV_H__) */
diff --git a/usbcam/usbcam_skel.c b/usbcam/usbcam_skel.c
@@ -0,0 +1,119 @@
+/*
+ * USBCAM abstraction library for USB webcam drivers
+ *
+ * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu>
+ *
+ * This driver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This is a usbcam skeleton driver intended to be a starting point for
+ * minidriver development.
+ */
+
+#define USBCAM_DEBUG_DEFAULT 0xffff
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include "usbcam.h"
+
+
+#define USBCAM_DBG_SKEL_INIT (USBCAM_DBGBIT_MD_START + 0)
+#define USBCAM_DBG_SKEL_FRAME (USBCAM_DBGBIT_MD_START + 1)
+#define USBCAM_DBG_SKEL_MDINTF (USBCAM_DBGBIT_MD_START + 2)
+#define USBCAM_DBG_SKEL_CTRL (USBCAM_DBGBIT_MD_START + 3)
+
+
+#define skel_info(RV, FMT...) usbcam_info((RV)->cs_parent, FMT)
+#define skel_err(RV, FMT...) usbcam_err((RV)->cs_parent, FMT)
+#define skel_dbg(RV, SUBSYS, FMT...) usbcam_dbg((RV)->cs_parent, SUBSYS, FMT)
+
+#define SKEL_VERSION KERNEL_VERSION(1,0,0)
+#define SKEL_VERSION_EXTRA ""
+
+
+/*
+ * struct skel_dev is the per-device private structure for this
+ * minidriver. We store all the details of the current state of the
+ * camera in here, other than those explicitly shared with usbcam.
+ */
+struct skel_dev {
+ struct usbcam_dev *cs_parent;
+};
+
+#define udp_skel(UDP) ((struct skel_dev *)((UDP)->ud_minidrv_data))
+
+static int skel_usbcam_init(struct usbcam_dev *udp,
+ const struct usb_device_id *devid)
+{
+ struct skel_dev *cdp = udp_skel(udp);
+ cdp->cs_parent = udp;
+
+ /* Probe/initialize the device */
+
+ return 0;
+}
+
+static void skel_usbcam_disconnect(struct usbcam_dev *udp)
+{
+}
+
+static int skel_usbcam_try_format(struct usbcam_dev *udp,
+ struct v4l2_pix_format *f)
+{
+ return -EINVAL;
+}
+
+static int skel_usbcam_set_format(struct usbcam_dev *udp,
+ struct v4l2_pix_format *f)
+{
+ return -EINVAL;
+}
+
+static int skel_usbcam_cap_start(struct usbcam_dev *udp)
+{
+ return -EINVAL;
+}
+
+static void skel_usbcam_cap_stop(struct usbcam_dev *udp)
+{
+}
+
+
+static struct usbcam_dev_ops skel_usbcam_dev_ops = {
+ .init = skel_usbcam_init,
+ .disconnect = skel_usbcam_disconnect,
+ .try_format = skel_usbcam_try_format,
+ .set_format = skel_usbcam_set_format,
+ .cap_start = skel_usbcam_cap_start,
+ .cap_stop = skel_usbcam_cap_stop,
+};
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(0xfff0, 0xfff0), .driver_info = 0 },
+ { }
+};
+
+DEFINE_USBCAM_MINIDRV_MODULE(SKEL_VERSION,
+ SKEL_VERSION_EXTRA,
+ &skel_usbcam_dev_ops,
+ sizeof(struct skel_dev),
+ id_table)
+
+MODULE_DESCRIPTION("Driver for Skeleton Webcam");
+MODULE_AUTHOR("You <you yourdomain com>");
+MODULE_LICENSE("GPL");
diff --git a/usbcam/usbcam_util.c b/usbcam/usbcam_util.c
@@ -0,0 +1,1008 @@
+/*
+ * USBCAM abstraction library for USB webcam drivers
+ *
+ * Copyright (C) 2007 Sam Revitch <samr7 cs washington edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "usbcam_priv.h"
+
+/*
+ * This file contains utility routines, including:
+ * - usbcam_choose_altsetting() for selecting int/isoc altsettings
+ * - The usbcam_urbstream accessory module
+ * - usbcam_hexdump() buffer dumping utility function
+ *
+ * TODO:
+ * - Find some way to alert minidrivers of URB shortages
+ */
+
+/*
+ * Traverse the alternate setting list and find one that provides
+ * the least bandwidth that satisfies the minimum requirement.
+ */
+int usbcam_choose_altsetting(struct usbcam_dev *udp, int ifnum,
+ int pipe, int bytes_per_sec_min,
+ int pkt_min, int pkt_max,
+ int *altsetting_nr)
+{
+ struct usb_interface *intf;
+ const struct usb_host_interface *aintf;
+ const struct usb_endpoint_descriptor *epd = NULL;
+ int i, j;
+
+ int wmp, bw;
+ int best_alt = -1, best_alt_bw = 0;
+
+ usbcam_chklock(udp);
+
+ if (udp->ud_disconnected) {
+ usbcam_warn(udp, "%s: device is disconnected", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ if (ifnum < 0)
+ ifnum = udp->ud_intf->cur_altsetting->desc.bInterfaceNumber;
+
+ intf = usb_ifnum_to_if(udp->ud_dev, ifnum);
+ if (!intf) {
+ usbcam_warn(udp, "%s: interface %d does not exist",
+ __FUNCTION__, ifnum);
+ return -ENODEV;
+ }
+
+ if ((bytes_per_sec_min >= 0) &&
+ !usb_pipeisoc(pipe) && !usb_pipeint(pipe)) {
+ usbcam_warn(udp, "%s: minidriver specified bytes_per_sec_min "
+ "on non-iso non-int pipe", __FUNCTION__);
+ }
+
+ for (i = 0; i < intf->num_altsetting; i++) {
+
+ aintf = &intf->altsetting[i];
+ for (j = 0; j < aintf->desc.bNumEndpoints; j++) {
+ epd = &aintf->endpoint[j].desc;
+ if ((epd->bEndpointAddress &
+ USB_ENDPOINT_NUMBER_MASK) ==
+ usb_pipeendpoint(pipe))
+ break;
+ }
+
+ if (j == aintf->desc.bNumEndpoints) {
+ /* Desired endpoint not present in this descriptor */
+ usbcam_dbg(udp, ALTSETTING,
+ "altsetting %d has no EP%d",
+ i, usb_pipeendpoint(pipe));
+ continue;
+ }
+
+ if (((usb_pipetype(pipe) == PIPE_ISOCHRONOUS) &&
+ !usb_endpoint_xfer_isoc(epd)) ||
+ ((usb_pipetype(pipe) == PIPE_INTERRUPT) &&
+ !usb_endpoint_xfer_int(epd)) ||
+ (usb_pipein(pipe) && !usb_endpoint_dir_in(epd)) ||
+ (!usb_pipein(pipe) && usb_endpoint_dir_in(epd))) {
+ /* Something is horribly wrong */
+ usbcam_dbg(udp, ALTSETTING,
+ "altsetting %d has unexpected EP%d",
+ i, usb_pipeendpoint(pipe));
+ continue;
+ }
+
+ bw = 0;
+ wmp = le16_to_cpu(epd->wMaxPacketSize);
+
+ /* Bandwidth only applies to iso & int pipes */
+ if (usb_pipeisoc(pipe) || usb_pipeint(pipe)) {
+ if (udp->ud_dev->speed == USB_SPEED_HIGH) {
+ /* 8 uframes per regular frame */
+ bw = 8000;
+
+ /* high bandwidth endpoint? */
+ wmp = ((wmp & 0x7ff) *
+ (((wmp >> 11) & 0x3) + 1));
+ } else {
+ bw = 1000;
+ wmp &= 0x7ff;
+ }
+
+ bw *= wmp;
+
+ /* Divide by interval / frame skippage */
+ bw = bw / (1 << (epd->bInterval - 1));
+
+ usbcam_dbg(udp, ALTSETTING,
+ "altsetting %d provides %d B/s bandwidth",
+ i, bw);
+
+ /* Check the bandwidth */
+ if (bw < bytes_per_sec_min)
+ continue;
+
+ } else
+ wmp &= 0x7ff;
+
+ /* Check the packet size */
+ if (((pkt_min >= 0) && (wmp < pkt_min)) ||
+ ((pkt_max >= 0) && (wmp > pkt_max)))
+ continue;
+
+ if ((best_alt < 0) || (bw < best_alt_bw)) {
+ best_alt = i;
+ best_alt_bw = bw;
+ }
+ }
+
+ if (best_alt == -1)
+ return -ENODEV;
+
+ *altsetting_nr = best_alt;
+ return 0;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_choose_altsetting);
+
+
+/*
+ * DMA buffer helper routines
+ */
+static int usbcam_urb_allocbuf(struct urb *urbp, size_t nbytes)
+{
+ urbp->transfer_buffer = usb_buffer_alloc(urbp->dev,
+ nbytes,
+ GFP_KERNEL,
+ &urbp->transfer_dma);
+ if (!urbp->transfer_buffer)
+ return -ENOMEM;
+
+ urbp->transfer_buffer_length = nbytes;
+ urbp->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ return 0;
+}
+
+static inline void usbcam_urb_freebuf(struct urb *urbp)
+{
+ usb_buffer_free(urbp->dev,
+ urbp->transfer_buffer_length,
+ urbp->transfer_buffer,
+ urbp->transfer_dma);
+}
+
+struct urb *usbcam_urb_alloc(struct usbcam_dev *udp, int pipe,
+ int pktsize, int npkts, int alloc_buf)
+{
+ struct urb *urbp;
+ int nbytes, i;
+
+ if (npkts) {
+ if (!usb_pipeisoc(pipe)) {
+ usbcam_err(udp, "npkts=%d but !pipeisoc", npkts);
+ return NULL;
+ }
+ } else if (usb_pipeisoc(pipe)) {
+ usbcam_err(udp, "npkts=0 but pipeisoc");
+ return NULL;
+ }
+
+
+ urbp = usb_alloc_urb(npkts, GFP_KERNEL);
+ if (!urbp)
+ return NULL;
+
+ urbp->dev = udp->ud_dev;
+ urbp->pipe = pipe;
+ urbp->number_of_packets = npkts;
+ urbp->transfer_buffer = NULL;
+ urbp->transfer_buffer_length = 0;
+
+ if (alloc_buf) {
+ nbytes = pktsize;
+ if (npkts)
+ nbytes *= npkts;
+
+ if (usbcam_urb_allocbuf(urbp, nbytes)) {
+ usb_free_urb(urbp);
+ return NULL;
+ }
+ }
+
+ if (npkts)
+ urbp->transfer_flags |= URB_ISO_ASAP;
+
+ for (i = 0; i < npkts; i++) {
+ urbp->iso_frame_desc[i].offset = (i * pktsize);
+ urbp->iso_frame_desc[i].length = pktsize;
+ urbp->iso_frame_desc[i].actual_length = 0;
+ urbp->iso_frame_desc[i].status = 0;
+ }
+
+ return urbp;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_urb_alloc);
+
+void usbcam_urb_free(struct urb *urbp, int free_buf)
+{
+ if (free_buf)
+ usbcam_urb_freebuf(urbp);
+ usb_free_urb(urbp);
+}
+USBCAM_EXPORT_SYMBOL(usbcam_urb_free);
+
+
+/*
+ * Streaming request helper functions
+ * This depends on the other usbcam stuff, but they don't depend on it,
+ * and it should be considered an extension sub-library.
+ */
+
+/* Default parameters for config_iso and config_bulk */
+#define USBCAM_DFL_ISO_REQS 8
+#define USBCAM_DFL_ISO_URB_PKTS 32
+
+#define USBCAM_DFL_BULK_REQS 8
+
+
+/*
+ * This structure represents a set of USB requests - URBs and buffers
+ */
+struct usbcam_urbinfo {
+ struct list_head ib_links;
+ struct usbcam_urbstream *ib_urbstream;
+ struct task_struct **ib_worker;
+ struct usbcam_workitem ib_workitem;
+ struct timer_list ib_timeout;
+ unsigned short ib_nurbs;
+ unsigned short ib_cururb;
+ struct urb *ib_urbs[0];
+};
+
+/* usp->us_lock must be held on entry */
+static void usbcam_urbstream_resubmit(struct usbcam_urbstream *usp,
+ struct usbcam_urbinfo *ibp)
+{
+ int res;
+
+ if (!ibp->ib_cururb) {
+ list_del(&ibp->ib_links);
+ list_add(&ibp->ib_links, &usp->us_active_list);
+ usp->us_active_count++;
+ }
+
+ res = usb_submit_urb(ibp->ib_urbs[ibp->ib_cururb], GFP_ATOMIC);
+ if (res == -EL2NSYNC)
+ res = usb_submit_urb(ibp->ib_urbs[ibp->ib_cururb], GFP_ATOMIC);
+
+ if (res) {
+ usbcam_dbg(usp->us_dev, URBSTREAM,
+ "urbstream[%d] resubmit %p/%d failed: %d",
+ usp->us_endpoint, ibp, ibp->ib_cururb, res);
+ usp->us_resubmit_err = res;
+
+ ibp->ib_cururb = 0;
+ list_del(&ibp->ib_links);
+ list_add(&ibp->ib_links, &usp->us_unused_list);
+ if (!--usp->us_active_count)
+ complete(&usp->us_active_empty);
+
+ (void) usbcam_work_queue(&usp->us_error_workitem);
+ }
+
+ else if (usp->us_timeout_ticks) {
+ ibp->ib_timeout.expires = jiffies + usp->us_timeout_ticks;
+ add_timer(&ibp->ib_timeout);
+ }
+}
+
+/* usp->us_lock NOT held on entry */
+static int usbcam_urbstream_submit_unused(struct usbcam_urbstream *usp)
+{
+ struct usbcam_urbinfo *ibp = NULL;
+ long flags;
+ int res;
+
+ spin_lock_irqsave(&usp->us_lock, flags);
+
+ if (!list_empty(&usp->us_unused_list)) {
+ ibp = list_entry(usp->us_unused_list.next,
+ struct usbcam_urbinfo, ib_links);
+ list_del(&ibp->ib_links);
+ list_add_tail(&ibp->ib_links, &usp->us_active_list);
+ usp->us_active_count++;
+ ibp->ib_cururb = 0;
+ }
+
+ spin_unlock_irqrestore(&usp->us_lock, flags);
+
+ if (!ibp)
+ return -ENOENT;
+
+ usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] urb submit %p/%d",
+ usp->us_endpoint, ibp, ibp->ib_cururb);
+ res = usb_submit_urb(ibp->ib_urbs[ibp->ib_cururb], GFP_KERNEL);
+ if (res == -EL2NSYNC)
+ res = usb_submit_urb(ibp->ib_urbs[ibp->ib_cururb], GFP_KERNEL);
+
+ if (!res && usp->us_timeout_ticks) {
+ ibp->ib_timeout.expires = jiffies + usp->us_timeout_ticks;
+ add_timer(&ibp->ib_timeout);
+ }
+
+ if (res) {
+ usbcam_dbg(usp->us_dev, URBSTREAM, "%s: URB submit failed: %d",
+ __FUNCTION__, res);
+ spin_lock_irqsave(&usp->us_lock, flags);
+ list_del(&ibp->ib_links);
+ list_add_tail(&ibp->ib_links, &usp->us_unused_list);
+ assert(usp->us_active_count > 0);
+ usp->us_active_count--;
+ spin_unlock_irqrestore(&usp->us_lock, flags);
+ }
+
+ return res;
+}
+
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
+static void usbcam_urbstream_urb_complete(struct urb *urb)
+#else
+static void usbcam_urbstream_urb_complete(struct urb *urb,
+ struct pt_regs *unused)
+#endif
+{
+ struct usbcam_urbinfo *ibp = (struct usbcam_urbinfo *) urb->context;
+ struct usbcam_urbstream *usp = ibp->ib_urbstream;
+ long flags;
+ int timer_fired = 0, res;
+
+ assert(ibp->ib_urbs[ibp->ib_cururb] == urb);
+
+ if (usp->us_timeout_ticks)
+ timer_fired = !del_timer_sync(&ibp->ib_timeout);
+
+ usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] urb complete: %p/%d",
+ usp->us_endpoint, ibp, ibp->ib_cururb);
+
+ spin_lock_irqsave(&usp->us_lock, flags);
+
+ if (list_empty(&ibp->ib_links)) {
+ /* We are being singled out for cancelation, do nothing */
+ usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] "
+ "request canceled, ignoring", usp->us_endpoint);
+ goto done;
+ }
+
+ ibp->ib_cururb++;
+ if (!urb->status) {
+ if (ibp->ib_cururb != ibp->ib_nurbs) {
+ usbcam_urbstream_resubmit(usp, ibp);
+ goto done;
+ }
+ }
+
+ /* Move to the done queue, submit a new URB, wake */
+ list_del(&ibp->ib_links);
+ list_add_tail(&ibp->ib_links, &usp->us_complete_list);
+ assert(usp->us_active_count > 0);
+ usp->us_active_count--;
+
+ if ((urb->status == -ECONNRESET) && timer_fired) {
+ usbcam_dbg(usp->us_dev, URBSTREAM,
+ "urbstream[%d] urb %p/%d timed out",
+ usp->us_endpoint, ibp, ibp->ib_cururb);
+ urb->status = -ETIMEDOUT;
+ }
+
+ res = usbcam_work_queue(&ibp->ib_workitem);
+ if (res) {
+ assert(res == -EBUSY);
+ list_del(&ibp->ib_links);
+ list_add_tail(&ibp->ib_links, &usp->us_unused_list);
+ goto done;
+ }
+
+ if (usp->us_resubmit_err) {
+ usbcam_dbg(usp->us_dev, URBSTREAM,
+ "urbstream[%d] not resubmitting, pending error",
+ usp->us_endpoint);
+ goto done;
+ }
+
+ /* Is the active count sufficient? */
+ if (usp->us_active_count >= usp->us_active_goal)
+ goto done;
+
+ /* Should we automatically submit an URB from the unused list? */
+ if (usp->us_streaming) {
+ if (list_empty(&usp->us_unused_list)) {
+ usbcam_dbg(usp->us_dev, URBSTREAM,
+ "urbstream[%d] not resubmitting, "
+ "URBs exhausted",
+ usp->us_endpoint);
+ goto done;
+ }
+
+ ibp = list_entry(usp->us_unused_list.next,
+ struct usbcam_urbinfo, ib_links);
+
+ ibp->ib_cururb = 0;
+ usbcam_urbstream_resubmit(usp, ibp);
+ }
+
+done:
+ if (!usp->us_active_count)
+ complete(&usp->us_active_empty);
+ spin_unlock_irqrestore(&usp->us_lock, flags);
+}
+
+static void usbcam_urbstream_urb_timeout(unsigned long data)
+{
+ struct usbcam_urbinfo *ibp = (struct usbcam_urbinfo *) data;
+ usb_unlink_urb(ibp->ib_urbs[ibp->ib_cururb]);
+}
+
+static void usbcam_urbstream_req_done(struct usbcam_urbinfo *ibp)
+{
+ struct usbcam_urbstream *usp;
+ long flags;
+
+ usp = ibp->ib_urbstream;
+ spin_lock_irqsave(&usp->us_lock, flags);
+
+ if (usp->us_streaming &&
+ (usp->us_active_count < usp->us_active_goal)) {
+ /* Try to limp along with underflows */
+ ibp->ib_cururb = 0;
+ usbcam_urbstream_resubmit(usp, ibp);
+
+ } else {
+ list_del(&ibp->ib_links);
+ list_add(&ibp->ib_links, &usp->us_unused_list);
+ }
+
+ spin_unlock_irqrestore(&usp->us_lock, flags);
+}
+
+static void usbcam_urbstream_freereq(struct usbcam_urbinfo *ibp)
+{
+ int i;
+ assert(list_empty(&ibp->ib_links));
+ for (i = 0; i < ibp->ib_nurbs; i++)
+ usbcam_urb_free(ibp->ib_urbs[i], 1);
+ kfree(ibp);
+}
+
+static struct usbcam_urbinfo *
+usbcam_urbstream_allocreq(struct usbcam_urbstream *usp, int pipe, int ival,
+ int pktlen, int npkts, int alloc)
+{
+ struct usbcam_urbinfo *ibp;
+ int sizeremain, nurbs, cpktlen;
+ int i;
+
+ usbcam_chklock(usp->us_dev);
+
+ sizeremain = 0;
+ nurbs = 1;
+ if (usb_pipebulk(pipe)) {
+ sizeremain = npkts;
+ npkts = 0;
+ nurbs = (sizeremain + pktlen - 1) / pktlen;
+ }
+
+ ibp = kzalloc(sizeof(*ibp) + (nurbs * sizeof(struct urb *)),
+ GFP_KERNEL);
+ if (!ibp)
+ return NULL;
+
+ INIT_LIST_HEAD(&ibp->ib_links);
+ ibp->ib_urbstream = usp;
+ ibp->ib_nurbs = nurbs;
+
+ for (i = 0; i < nurbs; i++) {
+ cpktlen = pktlen;
+ if (sizeremain && (cpktlen > sizeremain))
+ cpktlen = sizeremain;
+ ibp->ib_urbs[i] = usbcam_urb_alloc(usp->us_dev, pipe, cpktlen,
+ npkts, alloc);
+
+ if (!ibp->ib_urbs[i])
+ break;
+
+ ibp->ib_urbs[i]->complete = usbcam_urbstream_urb_complete;
+ ibp->ib_urbs[i]->interval = ival;
+ ibp->ib_urbs[i]->context = ibp;
+
+
+ if (sizeremain)
+ sizeremain -= cpktlen;
+ }
+
+ if (i < nurbs) {
+ while (i--)
+ usbcam_urb_free(ibp->ib_urbs[i], alloc);
+ kfree(ibp);
+ return NULL;
+ }
+
+ setup_timer(&ibp->ib_timeout,
+ usbcam_urbstream_urb_timeout,
+ (unsigned long) ibp);
+
+ return ibp;
+}
+
+static void usbcam_urbstream_freereqs(struct list_head *head)
+{
+ struct usbcam_urbinfo *ibp;
+ while (!list_empty(head)) {
+ ibp = list_entry(head->next, struct usbcam_urbinfo, ib_links);
+ list_del_init(&ibp->ib_links);
+ usbcam_urbstream_freereq(ibp);
+ }
+}
+
+static void usbcam_urbstream_error(struct usbcam_workitem *work)
+{
+ struct usbcam_urbstream *usp = container_of(work,
+ struct usbcam_urbstream,
+ us_error_workitem);
+ unsigned long flags;
+ int sts;
+
+ spin_lock_irqsave(&usp->us_lock, flags);
+ sts = usp->us_resubmit_err;
+ usp->us_resubmit_err = 0;
+ spin_unlock_irqrestore(&usp->us_lock, flags);
+
+ if (sts && usp->us_ops && usp->us_ops->submit_error)
+ usp->us_ops->submit_error(usp->us_dev, usp, sts);
+}
+
+void usbcam_urbstream_init(struct usbcam_urbstream *usp,
+ struct usbcam_dev *udp, int ep)
+{
+ memset(usp, 0, sizeof(*usp));
+ usp->us_dev = udp;
+ usp->us_endpoint = ep;
+ spin_lock_init(&usp->us_lock);
+ INIT_LIST_HEAD(&usp->us_unused_list);
+ INIT_LIST_HEAD(&usp->us_active_list);
+ INIT_LIST_HEAD(&usp->us_complete_list);
+ init_completion(&usp->us_active_empty);
+ usbcam_work_init(usp->us_dev,
+ &usp->us_error_workitem,
+ usbcam_urbstream_error);
+}
+USBCAM_EXPORT_SYMBOL(usbcam_urbstream_init);
+
+void usbcam_urbstream_stop(struct usbcam_urbstream *usp, int wait)
+{
+ long flags;
+ struct usbcam_urbinfo *ibp, *prev;
+ int res;
+
+ usbcam_chklock(usp->us_dev);
+
+ usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] stopping",
+ usp->us_endpoint);
+ spin_lock_irqsave(&usp->us_lock, flags);
+
+ if (usp->us_streaming)
+ usp->us_streaming = 0;
+
+ if (!wait) {
+ /*
+ * Cancel all in-flight requests without waiting
+ */
+ while (!list_empty(&usp->us_active_list)) {
+ ibp = list_entry(usp->us_active_list.prev,
+ struct usbcam_urbinfo, ib_links);
+ list_del_init(&ibp->ib_links);
+ assert(usp->us_active_count > 0);
+ usp->us_active_count--;
+ spin_unlock_irqrestore(&usp->us_lock, flags);
+ usb_kill_urb(ibp->ib_urbs[ibp->ib_cururb]);
+ spin_lock_irqsave(&usp->us_lock, flags);
+ list_add(&ibp->ib_links, &usp->us_unused_list);
+ }
+ } else {
+ /*
+ * Wait for all in-flight request groups to complete
+ * Do so while holding the device mutex, which is a bit ugly
+ */
+ while (!list_empty(&usp->us_active_list)) {
+ init_completion(&usp->us_active_empty);
+ spin_unlock_irqrestore(&usp->us_lock, flags);
+ wait_for_completion(&usp->us_active_empty);
+ spin_lock_irqsave(&usp->us_lock, flags);
+ }
+ }
+
+
+ /* Cancel all completed request groups with queued work items */
+ list_for_each_entry_safe(ibp, prev,
+ &usp->us_complete_list,
+ ib_links) {
+
+ res = usbcam_work_cancel(&ibp->ib_workitem);
+
+ if (!ibp->ib_worker)
+ assert(!res);
+ else {
+ assert(res);
+ assert(*ibp->ib_worker == current);
+ *ibp->ib_worker = NULL;
+ ibp->ib_worker = NULL;
+ }
+
+ list_del(&ibp->ib_links);
+ list_add_tail(&ibp->ib_links, &usp->us_unused_list);
+ }
+
+ /* Cancel the error work item */
+ (void) usbcam_work_cancel(&usp->us_error_workitem);
+
+ /* Clear the resubmission error code */
+ usp->us_resubmit_err = 0;
+
+ spin_unlock_irqrestore(&usp->us_lock, flags);
+ usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] stopped",
+ usp->us_endpoint);
+}
+USBCAM_EXPORT_SYMBOL(usbcam_urbstream_stop);
+
+int usbcam_urbstream_start(struct usbcam_urbstream *usp)
+{
+ struct usbcam_dev *udp;
+ long flags;
+ int submitted = 0, res;
+
+ udp = usp->us_dev;
+
+ usbcam_chklock(udp);
+
+ spin_lock_irqsave(&usp->us_lock, flags);
+ if (usp->us_streaming) {
+ spin_unlock_irqrestore(&usp->us_lock, flags);
+ usbcam_warn(udp, "%s: urbstream[%d] already streaming",
+ __FUNCTION__, usp->us_endpoint);
+ return -EEXIST;
+ }
+
+ if (list_empty(&usp->us_unused_list)) {
+ usbcam_warn(udp, "%s urbstream[%d] no unused URBs",
+ __FUNCTION__, usp->us_endpoint);
+ return -ENOENT;
+ }
+
+ usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] starting",
+ usp->us_endpoint);
+ usp->us_streaming = 1;
+
+ spin_unlock_irqrestore(&usp->us_lock, flags);
+
+ /* Submit initial URB(s) */
+ while (1) {
+ res = usbcam_urbstream_submit_unused(usp);
+ assert(res != -ENOENT);
+ if (res) {
+ usbcam_urbstream_stop(usp, 0);
+ return res;
+ }
+
+ if (++submitted == usp->us_active_goal)
+ break;
+ }
+
+ usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] started",
+ usp->us_endpoint);
+ return 0;
+
+}
+USBCAM_EXPORT_SYMBOL(usbcam_urbstream_start);
+
+int usbcam_urbstream_submit_one(struct usbcam_urbstream *usp)
+{
+ usbcam_chklock(usp->us_dev);
+ assert(!usp->us_streaming);
+ return usbcam_urbstream_submit_unused(usp);
+}
+USBCAM_EXPORT_SYMBOL(usbcam_urbstream_submit_one);
+
+void usbcam_urbstream_cleanup(struct usbcam_urbstream *usp)
+{
+ usbcam_urbstream_stop(usp, 0);
+ usbcam_urbstream_freereqs(&usp->us_unused_list);
+ assert(list_empty(&usp->us_active_list));
+ assert(list_empty(&usp->us_complete_list));
+}
+USBCAM_EXPORT_SYMBOL(usbcam_urbstream_cleanup);
+
+
+static void usbcam_urbstream_iso_process(struct usbcam_workitem *work)
+{
+ struct usbcam_urbinfo *ibp = container_of(work, struct usbcam_urbinfo,
+ ib_workitem);
+ struct usbcam_urbstream *usp = ibp->ib_urbstream;
+ struct task_struct *me = current;
+ struct urb *urbp;
+ int i;
+
+ usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] processing %p",
+ usp->us_endpoint, ibp);
+
+ assert(!ibp->ib_worker);
+ ibp->ib_worker = &me;
+
+ urbp = ibp->ib_urbs[0];
+ for (i = 0; i < urbp->number_of_packets; i++) {
+ char *buf = (((char *) urbp->transfer_buffer) +
+ urbp->iso_frame_desc[i].offset);
+ int len = urbp->iso_frame_desc[i].actual_length;
+ int status = urbp->iso_frame_desc[i].status;
+
+ urbp->iso_frame_desc[i].actual_length = 0;
+ urbp->iso_frame_desc[i].status = 0;
+
+ usp->us_ops->packet_done(usp->us_dev,
+ usp, buf, len, status);
+ if (!me)
+ return;
+ }
+
+ assert(ibp->ib_worker == &me);
+ ibp->ib_worker = NULL;
+
+ usbcam_urbstream_req_done(ibp);
+}
+
+static int usbcam_urbstream_allocreqs_isorcv(struct usbcam_urbstream *usp,
+ struct list_head *head, int count,
+ int ival, int pktcount,
+ int pktlen)
+{
+ struct usbcam_urbinfo *ibp;
+ struct list_head new_bufs;
+ int pipe;
+
+ pipe = usb_rcvisocpipe(usp->us_dev->ud_dev, usp->us_endpoint);
+
+ INIT_LIST_HEAD(&new_bufs);
+
+ while (count--) {
+ ibp = usbcam_urbstream_allocreq(usp, pipe, ival,
+ pktlen, pktcount, 1);
+ if (!ibp) {
+ usbcam_urbstream_freereqs(&new_bufs);
+ return -ENOMEM;
+ }
+
+ usbcam_work_init(usp->us_dev,
+ &ibp->ib_workitem,
+ usbcam_urbstream_iso_process);
+ list_add_tail(&ibp->ib_links, &new_bufs);
+ }
+
+ list_splice(&new_bufs, head);
+ return 0;
+}
+
+int usbcam_urbstream_config_iso(struct usbcam_urbstream *usp,
+ const struct usbcam_urbstream_ops *ops,
+ int pktcount, int nreqs, int interval,
+ int pktlen)
+{
+ int res;
+
+ usbcam_urbstream_cleanup(usp);
+
+ usp->us_active_goal = 2;
+ usp->us_ops = ops;
+
+ if (!interval) {
+ /* FIXME: find the appropriate interval for the endpoint */
+ return -EINVAL;
+ }
+
+ if (!pktlen) {
+ /* Choose a packet length based on the current altsetting */
+ pktlen = usb_maxpacket(usp->us_dev->ud_dev,
+ usb_rcvisocpipe(usp->us_dev->ud_dev,
+ usp->us_endpoint), 0);
+ if (!pktlen) {
+ usbcam_dbg(usp->us_dev, URBSTREAM,
+ "urbstream[%d]: current altsetting has "
+ "maxpacket=0", usp->us_endpoint);
+ return -EINVAL;
+ }
+ if (usp->us_dev->ud_dev->speed == USB_SPEED_HIGH)
+ pktlen = (pktlen & 0x7ff) *
+ (((pktlen >> 11) & 0x3) + 1);
+ else
+ pktlen &= 0x7ff;
+
+ usbcam_dbg(usp->us_dev, URBSTREAM,
+ "urbstream[%d] using pktlen %d",
+ usp->us_endpoint, pktlen);
+ }
+
+ if (!pktcount)
+ pktcount = USBCAM_DFL_ISO_URB_PKTS;
+ if (!nreqs)
+ nreqs = USBCAM_DFL_ISO_REQS;
+ if (nreqs < usp->us_active_goal) {
+ usbcam_warn(usp->us_dev, "%s urbstream[%d]: at least %d reqs "
+ "are required", __FUNCTION__, usp->us_endpoint,
+ usp->us_active_goal);
+ nreqs = usp->us_active_goal;
+ }
+
+ usp->us_timeout_ticks = 0;
+
+ res = usbcam_urbstream_allocreqs_isorcv(usp, &usp->us_unused_list,
+ nreqs, interval, pktcount,
+ pktlen);
+ return res;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_urbstream_config_iso);
+
+
+static void usbcam_urbstream_bulk_process(struct usbcam_workitem *work)
+{
+ struct usbcam_urbinfo *ibp = container_of(work, struct usbcam_urbinfo,
+ ib_workitem);
+ struct usbcam_urbstream *usp = ibp->ib_urbstream;
+ struct task_struct *me = current;
+ struct urb *urbp;
+ int len, status;
+ int i;
+
+ usbcam_dbg(usp->us_dev, URBSTREAM, "urbstream[%d] processing %p",
+ usp->us_endpoint, ibp);
+
+ assert(!ibp->ib_worker);
+ ibp->ib_worker = &me;
+
+ for (i = 0; i < ibp->ib_cururb; i++) {
+ urbp = ibp->ib_urbs[i];
+ len = urbp->actual_length;
+ status = urbp->status;
+
+ urbp->actual_length = 0;
+ urbp->status = 0;
+
+ usp->us_ops->packet_done(usp->us_dev, usp,
+ urbp->transfer_buffer, len, status);
+ if (!me)
+ return;
+ }
+
+ assert(ibp->ib_worker == &me);
+ ibp->ib_worker = NULL;
+
+ usbcam_urbstream_req_done(ibp);
+}
+
+static int usbcam_urbstream_allocreqs_bulkrcv(struct usbcam_urbstream *usp,
+ struct list_head *head,
+ int count, int reqlen,
+ int maxpkt)
+{
+ struct usbcam_urbinfo *ibp;
+ struct list_head new_bufs;
+ int pipe;
+
+ pipe = usb_rcvbulkpipe(usp->us_dev->ud_dev, usp->us_endpoint);
+
+ INIT_LIST_HEAD(&new_bufs);
+
+ while (count--) {
+ ibp = usbcam_urbstream_allocreq(usp, pipe, 0,
+ maxpkt, reqlen, 1);
+ if (!ibp) {
+ usbcam_urbstream_freereqs(&new_bufs);
+ return -ENOMEM;
+ }
+
+ usbcam_work_init(usp->us_dev,
+ &ibp->ib_workitem,
+ usbcam_urbstream_bulk_process);
+ list_add_tail(&ibp->ib_links, &new_bufs);
+ }
+
+ list_splice(&new_bufs, head);
+ return 0;
+}
+
+int usbcam_urbstream_config_bulk(struct usbcam_urbstream *usp,
+ const struct usbcam_urbstream_ops *ops,
+ int nreqs, int reqlen, int maxpkt,
+ int timeout_ms)
+{
+ int res;
+
+ usbcam_urbstream_cleanup(usp);
+
+ usp->us_active_goal = 1;
+ usp->us_ops = ops;
+
+ if (!maxpkt)
+ maxpkt = 64 * 1024;
+ if (reqlen < 0) {
+ usbcam_dbg(usp->us_dev, URBSTREAM,
+ "urbstream[%d]: packet length must be >=0",
+ usp->us_endpoint);
+ return -EINVAL;
+ }
+
+ if (!nreqs)
+ nreqs = USBCAM_DFL_BULK_REQS;
+ if (nreqs < usp->us_active_goal) {
+ usbcam_warn(usp->us_dev, "%s urbstream[%d]: at least %d URBs "
+ "are required", __FUNCTION__, usp->us_endpoint,
+ usp->us_active_goal);
+ nreqs = usp->us_active_goal;
+ }
+
+ usp->us_timeout_ticks = (timeout_ms * HZ) / 1000;
+
+ res = usbcam_urbstream_allocreqs_bulkrcv(usp, &usp->us_unused_list,
+ nreqs, reqlen, maxpkt);
+ return res;
+}
+USBCAM_EXPORT_SYMBOL(usbcam_urbstream_config_bulk);
+
+
+#ifdef usbcam_hexdump
+#undef usbcam_hexdump
+#endif
+#define dumpable_char(X) (((X) >= ' ') && ((X) <= '~'))
+void usbcam_hexdump(struct usbcam_dev *udp, const u8 *buf, size_t len)
+{
+ const int bpl = 16;
+ const int cend_max = ((bpl * 4) + 1);
+ static const char n2x[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+ char x, outbuf[cend_max + 1];
+ unsigned int cstart, cend, xpos, cpos, offset;
+
+ offset = 0;
+ cstart = (3 * bpl) + 1;
+ cend = cstart + bpl;
+ outbuf[cend] = '\0';
+ goto beginning;
+
+ while (len) {
+ x = *buf++;
+ outbuf[xpos++] = n2x[(x >> 4) & 0xf];
+ outbuf[xpos++] = n2x[x & 0xf];
+ outbuf[cpos+cstart] = dumpable_char(x) ? x : '.';
+ cpos++;
+ xpos++;
+ len--;
+
+ if (!len || (cpos == bpl)) {
+ printk(KERN_DEBUG "%s: %08x %s\n",
+ udp->ud_dev_name, offset, outbuf);
+ offset += bpl;
+
+ beginning:
+ memset(outbuf, ' ', cend);
+ cpos = 0;
+ xpos = 0;
+ }
+ }
+}
+USBCAM_EXPORT_SYMBOL(usbcam_hexdump);