ChangeSet 1.1148.6.7, 2003/10/08 12:04:27-07:00, luca@libero.it [PATCH] USB: add W996[87]CF driver. CREDITS | 9 Documentation/Configure.help | 26 Documentation/usb/w9968cf.txt | 486 ++++ MAINTAINERS | 7 drivers/usb/Config.in | 1 drivers/usb/Makefile | 1 drivers/usb/w9968cf.c | 3989 ++++++++++++++++++++++++++++++++++++++ drivers/usb/w9968cf.h | 321 +++ drivers/usb/w9968cf_decoder.h | 86 drivers/usb/w9968cf_externaldef.h | 95 include/linux/i2c-id.h | 1 include/linux/videodev.h | 1 12 files changed, 5022 insertions(+), 1 deletion(-) diff -Nru a/CREDITS b/CREDITS --- a/CREDITS Fri Oct 24 17:01:09 2003 +++ b/CREDITS Fri Oct 24 17:01:09 2003 @@ -2590,6 +2590,13 @@ S: Tucson, Arizona S: USA +N: Luca Risolia +E: luca_ing@libero.it +D: V4L driver for W996[87]CF JPEG USB Dual Mode Camera Chip +S: Via Libertà 41/a +S: Osio Sotto, 24046, Bergamo +S: Italy + N: William E. Roadcap E: roadcapw@cfw.com W: http://www.cfw.com/~roadcapw @@ -3440,4 +3447,4 @@ # alphabetically. Leonard used to be very proud of being the # last entry, and he'll get positively pissed if he can't even # be second-to-last. (and this file really _is_ supposed to be -# in alphabetic order) +# in alphabetic order) diff -Nru a/Documentation/Configure.help b/Documentation/Configure.help --- a/Documentation/Configure.help Fri Oct 24 17:01:09 2003 +++ b/Documentation/Configure.help Fri Oct 24 17:01:10 2003 @@ -15271,6 +15271,32 @@ The module will be called ov511.o. If you want to compile it as a module, say M here and read . +USB W996[87]CF Camera support +CONFIG_USB_W9968CF + Say Y here if you want support for cameras based on + Winbond W9967CF/W9968CF JPEG USB Dual Mode Camera Chips. + + This driver has an optional plugin, which is distributed as a + separate module only (released under GPL). It contains code that + allows you to use higher resolutions and framerates, and can't + be included into the official Linux kernel for performance + purposes. + At the moment the driver needs a third-part module for the CMOS + sensors, which is available on internet: it is recommended to read + for more informations and for + a list of supported cameras. + + This driver uses the Video For Linux and the I2C APIs. + You must say Y or M to both "Video For Linux" and + "I2C Support" to use this driver. + Information on this API and pointers to "v4l" programs may be found + on the WWW at . + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called w9968cf.o. If you want to compile it as a + module, say M here and read . + USB Communication Class Ethernet device support CONFIG_USB_CDCETHER This driver supports devices conforming to the Communication Device diff -Nru a/Documentation/usb/w9968cf.txt b/Documentation/usb/w9968cf.txt --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/Documentation/usb/w9968cf.txt Fri Oct 24 17:01:10 2003 @@ -0,0 +1,486 @@ + + W996[87]CF JPEG USB Dual Mode Camera Chip driver for Linux 2.4 + ============================================================== + + - Documentation - + + +Index +===== +1. Copyright +2. License +3. Overview +4. Supported devices +5. Kernel configuration and third-part module compilation +6. Module loading +7. Module paramaters +8. Credits + + +1. Copyright +============ +Copyright (C) 2002 2003 by Luca Risolia + + +2. License +========== +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +3. Overview +=========== +This driver supports the video streaming capabilities of the devices mounting +Winbond W9967CF and Winbond W9968CF JPEG USB Dual Mode Camera Chips, when they +are being commanded by USB. + +The driver relies on the Video4Linux, USB and I2C core modules of the Linux +kernel, version 2.4.19 or greater, and is not compatible in any way with +previous versions. It has been designed to run properly on SMP systems +as well. At the moment, an additional module, "ovcamchip", is mandatory; it +provides support for some OmniVision CMOS sensors connected to the W996[87]CF +chips. + +The driver is split into two modules: the basic one, "w9968cf", is needed for +the supported devices to work; the second one, "w9968cf-vpp", is an optional +module, which provides some useful video post-processing functions like video +decoding, up-scaling and colour conversions (these routines can't be included +into official kernels). Once the driver is installed, every time an application +tries to open a recognized device, "w9968cf" checks the presence of the +"w9968cf-vpp" module and loads it automatically by default. + +Up to 32 cameras can be handled at the same time. They can be connected and +disconnected from the host many times without turning off the computer, if +your system supports the hotplug facility. + +To change the default settings for each camera, many paramaters can be passed +through command line when the module is loaded into memory. + +It is recommended to install the latest and full featured version of the +W996[87]CF driver, which can be found at: +http://go.lamarinapunto.com/ + +The "ovcamchip" module is part of the OV511 driver, version 2.25, which can be +downloaded from internet: +http://alpha.dyndns.org/ov511/ +To know how to patch, compile and load it, read the "Kernel configuration" +paragraph. + + +4. Supported devices +==================== +At the moment, known W996[87]CF based devices are: +- Aroma Digi Pen ADG-5000 Refurbished +- AVerTV USB +- Creative Labs Video Blaster WebCam Go +- Creative Labs Video Blaster WebCam Go Plus +- Die Lebon LDC-D35A Digital Kamera +- Ezonics EZ-802 EZMega Cam +- OPCOM Digi Pen VGA Dual Mode Pen Camera + +If you know any other W996[87]CF based cameras, please contact me. + +The list above does NOT imply that all those devices work with this driver: up +until now only webcams that have a CMOS sensor supported by the "ovcamchip" +module work. +For a list of supported CMOS sensors, please visit the module author homepage: +http://alpha.dyndns.org/ov511/ + +Possible external microcontrollers of those webcams are not supported: this +means that still images can't be downloaded from the device memory. + +Furthermore, it's worth to note that I was only able to run tests on my +"Creative Labs Video Blaster WebCam Go". Donations of other models, for +additional testing and full support, would be much appreciated. + + +5. Kernel configuration and third-part module compilation +========================================================= +As noted above, kernel 2.4.19 is the minimum for this driver; for it to work +properly, the driver needs kernel support for Video4Linux, USB and I2C, and a +third-part module for the CMOS sensor. + +The following options of the kernel configuration file must be enabled and +corresponding modules must be compiled: + + # Multimedia devices + # + CONFIG_VIDEO_DEV=m + + # I2C support + # + CONFIG_I2C=m + +The I2C core module can be compiled statically in the kernel as well. + + # USB support + # + CONFIG_USB=m + +In addition, depending on the hardware being used, just one of the modules +below is necessary: + + # USB Host Controller Drivers + # + CONFIG_USB_EHCI_HCD=m + CONFIG_USB_UHCI=m + CONFIG_USB_UHCI_ALT=m + CONFIG_USB_OHCI=m + +Also, make sure "Enforce bandwidth allocation" is NOT enabled. + +The /proc filesystem can be optionally built into the kernel: + + # File systems + # + CONFIG_PROC_FS=y + + # Video For Linux + # + CONFIG_VIDEO_PROC_FS=y + + # USB Multimedia devices + # + CONFIG_USB_W9968CF=m + +The last module we need is "ovcamchip.o". To obtain it, you have to download +the OV511 driver, version 2.25 - don't use other versions - which is available +at http://alpha.dyndns.org/ov511/ . Then you have to download the latest +version of the full featured W996[87]CF driver, which contains a patch for the +"ovcamchip" module; it is available at http://go.lamarinapunto.com . +Once you have obtained the packages, decompress, patch and compile the +"ovcamchip" module. In other words: + + [user@localhost home]$ tar xvzf w9968cf-x.x.tar.gz + [user@localhost home]$ tar xvjf ov511-2.25.tar.bz2 + [user@localhost home]$ cd ov511-2.25 + [user@localhost ov511-2.25]$ patch -p1 < \ + /path/to/w9968cf-x.x/ov511-2.25.patch + [user@localhost ov511-2.25]$ make + +It's worth to note that the full featured version of the W996[87]CF driver +can also be installed overwriting the one in the kernel; in this case, read the +documentation included in the package. + +If everything went well, the W996[87]CF driver can be immediatly used (see next +paragraph). + + +6. Module loading +================= +To use the driver, it is necessary to load the "w9968cf" module into memory +after every other module required; they are named, in order: "videodev", +"usbcore", then "ehci-hcd", "usb-uhci", "uhci", "usb-ohci" (just one), and also +"i2c-core" and "ovcamchip". + +Loading can be done this way, from root: + + [root@localhost home]# modprobe i2c-core + [root@localhost ov511-x.xx]# insmod ./ovcamchip.o + [root@localhost home]# modprobe w9968cf + +At this point the devices should be recognized. There are two ways of verifying +that the loading process has gone well: the first is to analyze kernel +messages: + + [user@localhost home]$ dmesg + +A second way is to retrieve informations from the entries that have just been +created in the /proc/video/w9968cf/ directory; this feature works if and only +if the kernel has been built with the /proc filesystem support. +As an example, the following command will print the list of registered cameras: + + [user@localhost home]$ cat /proc/video/w9968cf/global + +There are a lot of parameters the module can use to change the default +settings for each device. To list every possible parameter with a brief +explanation about them and which syntax to use, it is recommended to run the +"modinfo" command: + + [root@locahost home]# modinfo w9968cf + + +7. Module paramaters +==================== + +Module paramaters are listed below: +------------------------------------------------------------------------------- +Name: vppmod_load +Type: int +Syntax: <0|1> +Description: Automatic 'w9968cf-vpp' module loading: 0 disabled, 1 enabled. + If enabled, every time an application attempts to open a + camera, 'insmod' searches for the video post-processing module + in the system and loads it automatically (if present). + The 'w9968cf-vpp' module adds extra image manipulation + capabilities to the 'w9968cf' module,like software up-scaling, + colour conversions and video decoding. +Default: 1 +------------------------------------------------------------------------------- +Name: simcams +Type: int +Syntax: +Description: Number of cameras allowed to stream simultaneously. + n may vary from 0 to 32. +Default: 32 +------------------------------------------------------------------------------- +Name: video_nr +Type: int array (min = 0, max = 32) +Syntax: <-1|n[,...]> +Description: Specify V4L minor mode number. + -1 = use next available + n = use minor number n + You can specify 32 cameras this way. + For example: + video_nr=-1,2,-1 would assign minor number 2 to the second + recognized camera and use auto for the first one and for every + other camera. +Default: -1 +------------------------------------------------------------------------------- +Name: packet_size +Type: int array (min = 0, max = 32) +Syntax: +Description: Specify the maximum data payload size in bytes for alternate + settings, for each device. n is scaled between 63 and 1023. +Default: 1023 +------------------------------------------------------------------------------- +Name: max_buffers +Type: int array (min = 0, max = 32) +Syntax: +Description: Only for advanced users. + Specify the maximum number of video frame buffers to allocate + for each device, from 2 to 32. +Default: 2 +------------------------------------------------------------------------------- +Name: double_buffer +Type: int array (min = 0, max = 32) +Syntax: <0|1[,...]> +Description: Hardware double buffering: 0 disabled, 1 enabled. + It should be enabled if you want smooth video output: if you + obtain out of sync. video, disable it at all, or try to + decrease the 'clockdiv' module paramater value. +Default: 1 for every device. +------------------------------------------------------------------------------- +Name: clamping +Type: int array (min = 0, max = 32) +Syntax: <0|1[,...]> +Description: Video data clamping: 0 disabled, 1 enabled. +Default: 0 for every device. +------------------------------------------------------------------------------- +Name: filter_type +Type: int array (min = 0, max = 32) +Syntax: <0|1|2[,...]> +Description: Video filter type. + 0 none, 1 (1-2-1) 3-tap filter, 2 (2-3-6-3-2) 5-tap filter. + The filter is used to reduce noise and aliasing artifacts + produced by the CCD or CMOS sensor. +Default: 0 for every device. +------------------------------------------------------------------------------- +Name: largeview +Type: int array (min = 0, max = 32) +Syntax: <0|1[,...]> +Description: Large view: 0 disabled, 1 enabled. +Default: 1 for every device. +------------------------------------------------------------------------------- +Name: upscaling +Type: int array (min = 0, max = 32) +Syntax: <0|1[,...]> +Description: Software scaling (for non-compressed video only): + 0 disabled, 1 enabled. + Disable it if you have a slow CPU or you don't have enough + memory. +Default: 0 for every device. +Note: If 'w9968cf-vpp' is not loaded, this paramater is set to 0. +------------------------------------------------------------------------------- +Name: decompression +Type: int array (min = 0, max = 32) +Syntax: <0|1|2[,...]> +Description: Software video decompression: + 0 = disables decompression + (doesn't allow formats needing decompression). + 1 = forces decompression + (allows formats needing decompression only). + 2 = allows any permitted formats. + Formats supporting (de)compressed video are YUV422P and + YUV420P/YUV420 in any resolutions where width and height are + multiples of 16. +Default: 2 for every device. +Note: If 'w9968cf-vpp' is not loaded, this paramater is set to, + forcing decompression is not allowed; in this case this + paramater is set to 2. +------------------------------------------------------------------------------- +Name: force_palette +Type: int array (min = 0, max = 32) +Syntax: <0|9|10|13|15|8|7|1|6|3|4|5[,...]> +Description: Force picture palette. + In order: + 0 = Off - allows any of the following formats: + 9 = UYVY 16 bpp - Original video, compression disabled + 10 = YUV420 12 bpp - Original video, compression enabled + 13 = YUV422P 16 bpp - Original video, compression enabled + 15 = YUV420P 12 bpp - Original video, compression enabled + 8 = YUVY 16 bpp - Software conversion from UYVY + 7 = YUV422 16 bpp - Software conversion from UYVY + 1 = GREY 8 bpp - Software conversion from UYVY + 6 = RGB555 16 bpp - Software conversion from UYVY + 3 = RGB565 16 bpp - Software conversion from UYVY + 4 = RGB24 24 bpp - Software conversion from UYVY + 5 = RGB32 32 bpp - Software conversion from UYVY + When not 0, this paramater will override 'decompression'. +Default: 0 for every device. Initial palette is 9 (UYVY). +Note: If 'w9968cf-vpp' is not loaded, this paramater is set to 9. +------------------------------------------------------------------------------- +Name: force_rgb +Type: int array (min = 0, max = 32) +Syntax: <0|1[,...]> +Description: Read RGB video data instead of BGR: + 1 = use RGB component ordering. + 0 = use BGR component ordering. + This parameter has effect when using RGBX palettes only. +Default: 0 for every device. +------------------------------------------------------------------------------- +Name: autobright +Type: long array (min = 0, max = 32) +Syntax: <0|1[,...]> +Description: CMOS sensor automatically changes brightness: + 0 = no, 1 = yes +Default: 0 for every device. +------------------------------------------------------------------------------- +Name: autoexp +Type: long array (min = 0, max = 32) +Syntax: <0|1[,...]> +Description: CMOS sensor automatically changes exposure: + 0 = no, 1 = yes +Default: 1 for every device. +------------------------------------------------------------------------------- +Name: lightfreq +Type: long array (min = 0, max = 32) +Syntax: <50|60[,...]> +Description: Light frequency in Hz: + 50 for European and Asian lighting, 60 for American lighting. +Default: 50 for every device. +------------------------------------------------------------------------------- +Name: bandingfilter +Type: long array (min = 0, max = 32) +Syntax: <0|1[,...]> +Description: Banding filter to reduce effects of fluorescent + lighting: + 0 disabled, 1 enabled. + This filter tries to reduce the pattern of horizontal + light/dark bands caused by some (usually fluorescent) lighting. +Default: 0 for every device. +------------------------------------------------------------------------------- +Name: clockdiv +Type: long array (min = 0, max = 32) +Syntax: <-1|n[,...]> +Description: Force pixel clock divisor to a specific value (for experts): + n may vary from 0 to 127. + -1 for automatic value. + See also the 'double_buffer' module paramater. +Default: -1 for every device. +------------------------------------------------------------------------------- +Name: backlight +Type: long array (min = 0, max = 32) +Syntax: <0|1[,...]> +Description: Objects are lit from behind: + 0 = no, 1 = yes +Default: 0 for every device. +------------------------------------------------------------------------------- +Name: mirror +Type: long array (min = 0, max = 32) +Syntax: <0|1[,...]> +Description: Reverse image horizontally: + 0 = no, 1 = yes +Default: 0 for every device. +------------------------------------------------------------------------------- +Name: sensor_mono +Type: long array (min = 0, max = 32) +Syntax: <0|1[,...]> +Description: The CMOS sensor is monochrome: + 0 = no, 1 = yes +Default: 0 for every device. +------------------------------------------------------------------------------- +Name: brightness +Type: long array (min = 0, max = 32) +Syntax: +Description: Set picture brightness (0-65535). + This parameter has no effect if 'autobright' is enabled. +Default: 31000 for every device. +------------------------------------------------------------------------------- +Name: hue +Type: long array (min = 0, max = 32) +Syntax: +Description: Set picture hue (0-65535). +Default: 32768 for every device. +------------------------------------------------------------------------------- +Name: colour +Type: long array (min = 0, max = 32) +Syntax: +Description: Set picture saturation (0-65535). +Default: 32768 for every device. +------------------------------------------------------------------------------- +Name: contrast +Type: long array (min = 0, max = 32) +Syntax: +Description: Set picture contrast (0-65535). +Default: 50000 for every device. +------------------------------------------------------------------------------- +Name: whiteness +Type: long array (min = 0, max = 32) +Syntax: +Description: Set picture whiteness (0-65535). +Default: 32768 for every device. +------------------------------------------------------------------------------- +Name: debug +Type: int +Syntax: +Description: Debugging information level, from 0 to 6: + 0 = none (be cautious) + 1 = critical errors + 2 = significant informations + 3 = configuration or general messages + 4 = warnings + 5 = called functions + 6 = function internals + Level 5 and 6 are useful for testing only, when just one + device is used. +Default: 2 +------------------------------------------------------------------------------- +Name: specific_debug +Type: int +Syntax: <0|1> +Description: Enable or disable specific debugging messages: + 0 = print messages concerning every level <= 'debug' level. + 1 = print messages concerning the level indicated by 'debug'. +Default: 0 +------------------------------------------------------------------------------- + + +8. Credits +========== +The development would not have proceed much further without having looked at +the source code of other drivers and without the help of several persons; in +particular: + +- the I2C interface to kernel and high-level CMOS sensor control routines have + been taken from the OV511 driver by Mark McClelland; + +- memory management code has been copied from the bttv driver by Ralph Metzler, + Marcus Metzler and Gerd Knorr; + +- the low-level I2C read function has been written by Frédéric Jouault, who + also gave me commented logs about sniffed USB traffic taken from another + driver for another system; + +- the low-level I2C fast write function has been written by Piotr Czerczak; diff -Nru a/MAINTAINERS b/MAINTAINERS --- a/MAINTAINERS Fri Oct 24 17:01:09 2003 +++ b/MAINTAINERS Fri Oct 24 17:01:09 2003 @@ -2068,6 +2068,13 @@ L: linux-usb-devel@lists.sourceforge.net S: Maintained +USB W9968CF DRIVER +P: Luca Risolia +M: luca_ing@libero.it +L: linux-usb-devel@lists.sourceforge.net +W: http://go.lamarinapunto.com +S: Maintained + VFAT FILESYSTEM: P: Gordon Chaffee M: chaffee@cs.berkeley.edu diff -Nru a/drivers/usb/Config.in b/drivers/usb/Config.in --- a/drivers/usb/Config.in Fri Oct 24 17:01:10 2003 +++ b/drivers/usb/Config.in Fri Oct 24 17:01:10 2003 @@ -78,6 +78,7 @@ dep_tristate ' USB Philips Cameras' CONFIG_USB_PWC $CONFIG_USB $CONFIG_VIDEO_DEV dep_tristate ' USB SE401 Camera support' CONFIG_USB_SE401 $CONFIG_USB $CONFIG_VIDEO_DEV dep_tristate ' USB STV680 (Pencam) Camera support' CONFIG_USB_STV680 $CONFIG_USB $CONFIG_VIDEO_DEV + dep_tristate ' USB W996[87]CF Camera support' CONFIG_USB_W9968CF $CONFIG_USB $CONFIG_VIDEO_DEV $CONFIG_I2C dep_tristate ' USB 3com HomeConnect (aka vicam) support (EXPERIMENTAL)' CONFIG_USB_VICAM $CONFIG_USB $CONFIG_VIDEO_DEV $CONFIG_EXPERIMENTAL dep_tristate ' D-Link USB FM radio support (EXPERIMENTAL)' CONFIG_USB_DSBR $CONFIG_USB $CONFIG_VIDEO_DEV $CONFIG_EXPERIMENTAL dep_tristate ' DABUSB driver' CONFIG_USB_DABUSB $CONFIG_USB diff -Nru a/drivers/usb/Makefile b/drivers/usb/Makefile --- a/drivers/usb/Makefile Fri Oct 24 17:01:10 2003 +++ b/drivers/usb/Makefile Fri Oct 24 17:01:10 2003 @@ -103,6 +103,7 @@ obj-$(CONFIG_USB_DABUSB) += dabusb.o obj-$(CONFIG_USB_VICAM) += vicam.o usbvideo.o obj-$(CONFIG_USB_OV511) += ov511.o +obj-$(CONFIG_USB_W9968CF) += w9968cf.o obj-$(CONFIG_USB_SE401) += se401.o obj-$(CONFIG_USB_STV680) += stv680.o obj-$(CONFIG_USB_PEGASUS) += pegasus.o diff -Nru a/drivers/usb/w9968cf.c b/drivers/usb/w9968cf.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/w9968cf.c Fri Oct 24 17:01:10 2003 @@ -0,0 +1,3989 @@ +/*************************************************************************** + * Video4Linux driver for W996[87]CF JPEG USB Dual Mode Camera Chip. * + * * + * Copyright (C) 2002 2003 by Luca Risolia * + * * + * - Memory management code from bttv driver by Ralph Metzler, * + * Marcus Metzler and Gerd Knorr. * + * - I2C interface to kernel, high-level CMOS sensor control routines and * + * some symbolic names from OV511 driver by Mark W. McClelland. * + * - Low-level I2C fast write function by Piotr Czerczak. * + * - Low-level I2C read function by Frédéric Jouault. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "w9968cf.h" +#include "w9968cf_decoder.h" + +EXPORT_NO_SYMBOLS; + + + +/**************************************************************************** + * Modules paramaters * + ****************************************************************************/ + +static u8 vppmod_load = W9968CF_VPPMOD_LOAD; +static u8 simcams = W9968CF_SIMCAMS; +static int video_nr[]={[0 ... W9968CF_MAX_DEVICES-1] = -1}; /* -1=first free */ +static u16 packet_size[] = {[0 ... W9968CF_MAX_DEVICES-1]=W9968CF_PACKET_SIZE}; +static u8 max_buffers[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_BUFFERS}; +static u8 double_buffer[] = {[0 ... W9968CF_MAX_DEVICES-1] = + W9968CF_DOUBLE_BUFFER}; +static u8 clamping[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_CLAMPING}; +static u8 filter_type[]= {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_FILTER_TYPE}; +static u8 largeview[]= {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_LARGEVIEW}; +static u8 decompression[] = {[0 ... W9968CF_MAX_DEVICES-1] = + W9968CF_DECOMPRESSION}; +static u8 upscaling[]= {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_UPSCALING}; +static u8 force_palette[] = {[0 ... W9968CF_MAX_DEVICES-1] = 0}; +static u8 force_rgb[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_FORCE_RGB}; +static u8 autobright[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_AUTOBRIGHT}; +static u8 autoexp[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_AUTOEXP}; +static u8 lightfreq[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_LIGHTFREQ}; +static u8 bandingfilter[] = {[0 ... W9968CF_MAX_DEVICES-1]= + W9968CF_BANDINGFILTER}; +static int clockdiv[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_CLOCKDIV}; +static u8 backlight[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_BACKLIGHT}; +static u8 mirror[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_MIRROR}; +static u8 sensor_mono[] = {[0 ... W9968CF_MAX_DEVICES-1]=W9968CF_SENSOR_MONO}; +static u16 brightness[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_BRIGHTNESS}; +static u16 hue[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_HUE}; +static u16 colour[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_COLOUR}; +static u16 contrast[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_CONTRAST}; +static u16 whiteness[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_WHITENESS}; +#ifdef W9968CF_DEBUG +static u8 debug = W9968CF_DEBUG_LEVEL; +static u8 specific_debug = W9968CF_SPECIFIC_DEBUG; +#endif + +MODULE_AUTHOR("Luca Risolia "); + +MODULE_DESCRIPTION("Video4Linux driver for " + "W996[87]CF JPEG USB Dual Mode Camera Chip"); + +MODULE_SUPPORTED_DEVICE("Video"); + +MODULE_LICENSE("GPL"); + +MODULE_PARM(vppmod_load, "i"); +MODULE_PARM(simcams, "i"); +MODULE_PARM(video_nr, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i"); +MODULE_PARM(packet_size, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i"); +MODULE_PARM(max_buffers, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i"); +MODULE_PARM(double_buffer, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i"); +MODULE_PARM(clamping, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i"); +MODULE_PARM(filter_type, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i"); +MODULE_PARM(largeview, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i"); +MODULE_PARM(decompression, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i"); +MODULE_PARM(upscaling, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i"); +MODULE_PARM(force_palette, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i"); +MODULE_PARM(force_rgb, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i"); +MODULE_PARM(autobright, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l"); +MODULE_PARM(autoexp, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l"); +MODULE_PARM(lightfreq, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l"); +MODULE_PARM(bandingfilter, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l"); +MODULE_PARM(clockdiv, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l"); +MODULE_PARM(backlight, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l"); +MODULE_PARM(mirror, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l"); +MODULE_PARM(sensor_mono, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l"); +MODULE_PARM(brightness, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l"); +MODULE_PARM(hue, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l"); +MODULE_PARM(colour, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l"); +MODULE_PARM(contrast, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l"); +MODULE_PARM(whiteness, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l"); +#ifdef W9968CF_DEBUG +MODULE_PARM(debug, "i"); +MODULE_PARM(specific_debug, "i"); +#endif + +MODULE_PARM_DESC(vppmod_load, + "\n<0|1> Automatic 'w9968cf-vpp' module loading." + "\n0 disable, 1 enable." + "\nIf enabled, every time an application attempts to open a" + "\ncamera, 'insmod' searches for the video post-processing" + "\nmodule in the system and loads it automatically (if" + "\npresent). The 'w9968cf-vpp' module adds extra image" + "\nmanipulation functions to the 'w9968cf' module, like" + "\nsoftware up-scaling,colour conversions and video decoding." + "\nDefault value is "__MODULE_STRING(W9968CF_VPPMOD_LOAD)"." + "\n"); +MODULE_PARM_DESC(simcams, + "\n Number of cameras allowed to stream simultaneously." + "\nn may vary from 0 to " + __MODULE_STRING(W9968CF_MAX_DEVICES)"." + "\nDefault value is "__MODULE_STRING(W9968CF_SIMCAMS)"." + "\n"); +MODULE_PARM_DESC(video_nr, + "\n<-1|n[,...]> Specify V4L minor mode number." + "\n -1 = use next available (default)" + "\n n = use minor number n (integer >= 0)" + "\nYou can specify " __MODULE_STRING(W9968CF_MAX_DEVICES) + " cameras this way." + "\nFor example:" + "\nvideo_nr=-1,2,-1 would assign minor number 2 to" + "\nthe second camera and use auto for the first" + "\none and for every other camera." + "\n"); +MODULE_PARM_DESC(packet_size, + "\n Specify the maximum data payload" + "\nsize in bytes for alternate settings, for each device." + "\nn is scaled between 63 and 1023 " + "(default is "__MODULE_STRING(W9968CF_PACKET_SIZE)")." + "\n"); +MODULE_PARM_DESC(max_buffers, + "\n Only for advanced users." + "\nSpecify the maximum number of video frame buffers" + "\nto allocate for each device, from 2 to " + __MODULE_STRING(W9968CF_MAX_BUFFERS) + ". (default is "__MODULE_STRING(W9968CF_BUFFERS)")." + "\n"); +MODULE_PARM_DESC(double_buffer, + "\n<0|1[,...]> " + "Hardware double buffering: 0 disabled, 1 enabled." + "\nIt should be enabled if you want smooth video output: if" + "\nyou obtain out of sync. video, disable it at all, or try" + "\nto decrease the 'clockdiv' module paramater value." + "\nDefault value is "__MODULE_STRING(W9968CF_DOUBLE_BUFFER) + " for every device." + "\n"); +MODULE_PARM_DESC(clamping, + "\n<0|1[,...]> Video data clamping: 0 disabled, 1 enabled." + "\nDefault value is "__MODULE_STRING(W9968CF_CLAMPING) + " for every device." + "\n"); +MODULE_PARM_DESC(filter_type, + "\n<0|1|2[,...]> Video filter type." + "\n0 none, 1 (1-2-1) 3-tap filter, " + "2 (2-3-6-3-2) 5-tap filter." + "\nDefault value is "__MODULE_STRING(W9968CF_FILTER_TYPE) + " for every device." + "\nThe filter is used to reduce noise and aliasing artifacts" + "\nproduced by the CCD or CMOS sensor, and the scaling" + " process." + "\n"); +MODULE_PARM_DESC(largeview, + "\n<0|1[,...]> Large view: 0 disabled, 1 enabled." + "\nDefault value is "__MODULE_STRING(W9968CF_LARGEVIEW) + " for every device." + "\n"); +MODULE_PARM_DESC(upscaling, + "\n<0|1[,...]> Software scaling (for non-compressed video):" + "\n0 disabled, 1 enabled." + "\nDisable it if you have a slow CPU or you don't have" + " enough memory." + "\nDefault value is "__MODULE_STRING(W9968CF_UPSCALING) + " for every device." + "\nIf 'w9968cf-vpp' is not loaded, this paramater is" + " set to 0."); +MODULE_PARM_DESC(decompression, + "\n<0|1|2[,...]> Software video decompression:" + "\n- 0 disables decompression (doesn't allow formats needing" + " decompression)" + "\n- 1 forces decompression (allows formats needing" + " decompression only);" + "\n- 2 allows any permitted formats." + "\nFormats supporting compressed video are YUV422P and" + " YUV420P/YUV420 " + "\nin any resolutions where both width and height are " + "a multiple of 16." + "\nDefault value is "__MODULE_STRING(W9968CF_DECOMPRESSION) + " for every device." + "\nIf 'w9968cf-vpp' is not loaded, forcing decompression is " + "\nnot allowed; in this case this paramater is set to 2."); +MODULE_PARM_DESC(force_palette, + "\n<0" + "|" __MODULE_STRING(VIDEO_PALETTE_UYVY) + "|" __MODULE_STRING(VIDEO_PALETTE_YUV420) + "|" __MODULE_STRING(VIDEO_PALETTE_YUV422P) + "|" __MODULE_STRING(VIDEO_PALETTE_YUV420P) + "|" __MODULE_STRING(VIDEO_PALETTE_YUYV) + "|" __MODULE_STRING(VIDEO_PALETTE_YUV422) + "|" __MODULE_STRING(VIDEO_PALETTE_GREY) + "|" __MODULE_STRING(VIDEO_PALETTE_RGB555) + "|" __MODULE_STRING(VIDEO_PALETTE_RGB565) + "|" __MODULE_STRING(VIDEO_PALETTE_RGB24) + "|" __MODULE_STRING(VIDEO_PALETTE_RGB32) + "[,...]>" + " Force picture palette." + "\nIn order:" + "\n- 0 allows any of the following formats:" + "\n- UYVY 16 bpp - Original video, compression disabled" + "\n- YUV420 12 bpp - Original video, compression enabled" + "\n- YUV422P 16 bpp - Original video, compression enabled" + "\n- YUV420P 12 bpp - Original video, compression enabled" + "\n- YUVY 16 bpp - Software conversion from UYVY" + "\n- YUV422 16 bpp - Software conversion from UYVY" + "\n- GREY 8 bpp - Software conversion from UYVY" + "\n- RGB555 16 bpp - Software conversion from UYVY" + "\n- RGB565 16 bpp - Software conversion from UYVY" + "\n- RGB24 24 bpp - Software conversion from UYVY" + "\n- RGB32 32 bpp - Software conversion from UYVY" + "\nWhen not 0, this paramater will override 'decompression'." + "\nDefault value is 0 for every device." + "\nInitial palette is " + __MODULE_STRING(W9968CF_PALETTE_DECOMP_ON)"." + "\nIf 'w9968cf-vpp' is not loaded, this paramater is" + " set to 9 (UYVY)."); +MODULE_PARM_DESC(force_rgb, + "\n<0|1[,...]> Read RGB video data instead of BGR:" + "\n 1 = use RGB component ordering." + "\n 0 = use BGR component ordering." + "\nThis parameter has effect when using RGBX palettes only." + "\nDefault value is "__MODULE_STRING(W9968CF_FORCE_RGB) + " for every device." + "\n"); +MODULE_PARM_DESC(autobright, + "\n<0|1[,...]> CMOS sensor automatically changes brightness:" + "\n 0 = no, 1 = yes" + "\nDefault value is "__MODULE_STRING(W9968CF_AUTOBRIGHT) + " for every device." + "\n"); +MODULE_PARM_DESC(autoexp, + "\n<0|1[,...]> CMOS sensor automatically changes exposure:" + "\n 0 = no, 1 = yes" + "\nDefault value is "__MODULE_STRING(W9968CF_AUTOEXP) + " for every device." + "\n"); +MODULE_PARM_DESC(lightfreq, + "\n<50|60[,...]> Light frequency in Hz:" + "\n 50 for European and Asian lighting," + " 60 for American lighting." + "\nDefault value is "__MODULE_STRING(W9968CF_LIGHTFREQ) + " for every device." + "\n"); +MODULE_PARM_DESC(bandingfilter, + "\n<0|1[,...]> Banding filter to reduce effects of" + " fluorescent lighting:" + "\n 0 disabled, 1 enabled." + "\nThis filter tries to reduce the pattern of horizontal" + "\nlight/dark bands caused by some (usually fluorescent)" + " lighting." + "\nDefault value is "__MODULE_STRING(W9968CF_BANDINGFILTER) + " for every device." + "\n"); +MODULE_PARM_DESC(clockdiv, + "\n<-1|n[,...]> " + "Force pixel clock divisor to a specific value (for experts):" + "\n n may vary from 0 to 127." + "\n -1 for automatic value." + "\nSee also the 'double_buffer' module paramater." + "\nDefault value is "__MODULE_STRING(W9968CF_CLOCKDIV) + " for every device." + "\n"); +MODULE_PARM_DESC(backlight, + "\n<0|1[,...]> Objects are lit from behind:" + "\n 0 = no, 1 = yes" + "\nDefault value is "__MODULE_STRING(W9968CF_BACKLIGHT) + " for every device." + "\n"); +MODULE_PARM_DESC(mirror, + "\n<0|1[,...]> Reverse image horizontally:" + "\n 0 = no, 1 = yes" + "\nDefault value is "__MODULE_STRING(W9968CF_MIRROR) + " for every device." + "\n"); +MODULE_PARM_DESC(sensor_mono, + "\n<0|1[,...]> The OV CMOS sensor is monochrome:" + "\n 0 = no, 1 = yes" + "\nDefault value is "__MODULE_STRING(W9968CF_SENSOR_MONO) + " for every device." + "\n"); +MODULE_PARM_DESC(brightness, + "\n Set picture brightness (0-65535)." + "\nDefault value is "__MODULE_STRING(W9968CF_BRIGHTNESS) + " for every device." + "\nThis parameter has no effect if 'autobright' is enabled." + "\n"); +MODULE_PARM_DESC(hue, + "\n Set picture hue (0-65535)." + "\nDefault value is "__MODULE_STRING(W9968CF_HUE) + " for every device." + "\n"); +MODULE_PARM_DESC(colour, + "\n Set picture saturation (0-65535)." + "\nDefault value is "__MODULE_STRING(W9968CF_COLOUR) + " for every device." + "\n"); +MODULE_PARM_DESC(contrast, + "\n Set picture contrast (0-65535)." + "\nDefault value is "__MODULE_STRING(W9968CF_CONTRAST) + " for every device." + "\n"); +MODULE_PARM_DESC(whiteness, + "\n Set picture whiteness (0-65535)." + "\nDefault value is "__MODULE_STRING(W9968CF_WHITENESS) + " for every device." + "\n"); +#ifdef W9968CF_DEBUG +MODULE_PARM_DESC(debug, + "\n Debugging information level, from 0 to 6:" + "\n0 = none (be cautious)" + "\n1 = critical errors" + "\n2 = significant informations" + "\n3 = configuration or general messages" + "\n4 = warnings" + "\n5 = called functions" + "\n6 = function internals" + "\nLevel 5 and 6 are useful for testing only, when just " + "one device is used." + "\nDefault value is "__MODULE_STRING(W9968CF_DEBUG_LEVEL)"." + "\n"); +MODULE_PARM_DESC(specific_debug, + "\n<0|1> Enable or disable specific debugging messages:" + "\n0 = print messages concerning every level" + " <= 'debug' level." + "\n1 = print messages concerning the level" + " indicated by 'debug'." + "\nDefault value is " + __MODULE_STRING(W9968CF_SPECIFIC_DEBUG)"." + "\n"); +#endif /* W9968CF_DEBUG */ + + + +/**************************************************************************** + * Some prototypes * + ****************************************************************************/ + +/* Video4linux interface */ +static struct file_operations w9968cf_fops; +static int w9968cf_open(struct inode*, struct file*); +static int w9968cf_release(struct inode*, struct file*); +static ssize_t w9968cf_read(struct file*, char*, size_t, loff_t*); +static int w9968cf_mmap(struct file*, struct vm_area_struct*); +static int w9968cf_ioctl(struct inode*, struct file*, + unsigned int, unsigned long); +static int w9968cf_do_ioctl(struct w9968cf_device*, unsigned int, void*); + +/* /proc interface */ +#if defined(CONFIG_VIDEO_PROC_FS) +static void w9968cf_proc_create(void); +static void w9968cf_proc_destroy(void); +static void w9968cf_proc_create_dev(struct w9968cf_device*); +static void w9968cf_proc_destroy_dev(struct w9968cf_device*); +static int w9968cf_proc_read_global(char*, char**, off_t, int, int*, void*); +static int w9968cf_proc_read_dev(char*, char**, off_t, int, int*, void*); +#else +static inline void w9968cf_proc_create(void) {} +static inline void w9968cf_proc_destroy(void) {} +static inline void w9968cf_proc_create_dev(struct w9968cf_device* cam) {} +static inline void w9968cf_proc_destroy_dev(struct w9968cf_device* cam) {} +#endif + +/* USB-specific */ +static void w9968cf_urb_complete(struct urb *urb); +static int w9968cf_start_transfer(struct w9968cf_device*); +static int w9968cf_stop_transfer(struct w9968cf_device*); +static int w9968cf_write_reg(struct w9968cf_device*, u16 value, u16 index); +static int w9968cf_read_reg(struct w9968cf_device*, u16 index); +static int w9968cf_write_fsb(struct w9968cf_device*, u16* data); +static int w9968cf_write_sb(struct w9968cf_device*, u16 value); +static int w9968cf_read_sb(struct w9968cf_device*); +static int w9968cf_upload_quantizationtables(struct w9968cf_device*); + + +/* Low-level I2C (SMBus) I/O */ +static int w9968cf_smbus_start(struct w9968cf_device*); +static int w9968cf_smbus_stop(struct w9968cf_device*); +static int w9968cf_smbus_write_byte(struct w9968cf_device*, u8 v); +static int w9968cf_smbus_read_byte(struct w9968cf_device*, u8* v); +static int w9968cf_smbus_write_ack(struct w9968cf_device*); +static int w9968cf_smbus_read_ack(struct w9968cf_device*); +static int w9968cf_i2c_adap_read_byte(struct w9968cf_device* cam, + u16 address, u8* value); +static int w9968cf_i2c_adap_read_byte_data(struct w9968cf_device*, u16 address, + u8 subaddress, u8* value); +static int w9968cf_i2c_adap_write_byte(struct w9968cf_device*, + u16 address, u8 subaddress); +static int w9968cf_i2c_adap_fastwrite_byte_data(struct w9968cf_device*, + u16 address, u8 subaddress, + u8 value); + +/* I2C interface to kernel */ +static int w9968cf_i2c_init(struct w9968cf_device*); +static int w9968cf_i2c_smbus_xfer(struct i2c_adapter*, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data*); +static u32 w9968cf_i2c_func(struct i2c_adapter*); +static int w9968cf_i2c_attach_inform(struct i2c_client*); +static int w9968cf_i2c_detach_inform(struct i2c_client*); +static int w9968cf_i2c_control(struct i2c_adapter*, unsigned int cmd, + unsigned long arg); +static void w9968cf_i2c_inc_use(struct i2c_adapter*); +static void w9968cf_i2c_dec_use(struct i2c_adapter*); + +/* Memory management */ +static inline unsigned long kvirt_to_pa(unsigned long adr); +static void* rvmalloc(unsigned long size); +static void rvfree(void *mem, unsigned long size); +static void w9968cf_deallocate_memory(struct w9968cf_device*); +static int w9968cf_allocate_memory(struct w9968cf_device*); +static inline unsigned long w9968cf_get_max_bufsize(struct w9968cf_device*); + +/* High-level CMOS sensor control functions */ +static int w9968cf_sensor_set_control(struct w9968cf_device*,int cid,int val); +static int w9968cf_sensor_get_control(struct w9968cf_device*,int cid,int *val); +static inline int w9968cf_sensor_cmd(struct w9968cf_device*, + unsigned int cmd, void *arg); +static void w9968cf_sensor_configure(struct w9968cf_device*); +static int w9968cf_sensor_change_settings(struct w9968cf_device*); +static int w9968cf_sensor_get_picture(struct w9968cf_device*, + struct video_picture*); +static int w9968cf_sensor_set_picture(struct w9968cf_device*, + struct video_picture pict); + +/* Other helper functions */ +static void w9968cf_configure_camera(struct w9968cf_device*,struct usb_device*, + enum w9968cf_model_id, + const unsigned short dev_nr); +static int w9968cf_turn_on_led(struct w9968cf_device*); +static int w9968cf_init_chip(struct w9968cf_device*); +static int w9968cf_set_picture(struct w9968cf_device*, struct video_picture); +static int w9968cf_set_window(struct w9968cf_device*, struct video_window); +static inline u16 w9968cf_valid_palette(u16 palette); +static inline u16 w9968cf_valid_depth(u16 palette); +static inline u8 w9968cf_need_decompression(u16 palette); +static int w9968cf_postprocess_frame(struct w9968cf_device*, + struct w9968cf_frame_t*); +static int w9968cf_adjust_window_size(struct w9968cf_device*, u16* w, u16* h); +static void w9968cf_init_framelist(struct w9968cf_device*); +static void w9968cf_push_frame(struct w9968cf_device*, u8 f_num); +static void w9968cf_pop_frame(struct w9968cf_device*,struct w9968cf_frame_t**); +static void w9968cf_release_resources(struct w9968cf_device*); + +/* Intermodule communication */ +static int w9968cf_vppmod_detect(void); +static void w9968cf_vppmod_release(void); + +/* Pointers to registered video post-processing functions */ +static void (*w9968cf_vpp_init_decoder)(void); +static int (*w9968cf_vpp_check_headers)(const unsigned char*, + const unsigned long); +static int (*w9968cf_vpp_decode)(const char*, const unsigned, + const unsigned, const unsigned, char*); +static void (*w9968cf_vpp_swap_yuvbytes)(void*, unsigned long); +static void (*w9968cf_vpp_uyvy_to_rgbx)(u8*, unsigned long, u8*, u16, u8); +static void (*w9968cf_vpp_scale_up)(u8*, u8*, u16, u16, u16, u16, u16); + + + +/**************************************************************************** + * Symbolic names * + ****************************************************************************/ + +/* Used to represent a list of values and their respective symbolic names */ +struct w9968cf_symbolic_list { + const int num; + const char *name; +}; + +/*-------------------------------------------------------------------------- + Returns the name of the matching element in the symbolic_list array. The + end of the list must be marked with an element that has a NULL name. + --------------------------------------------------------------------------*/ +static inline const char * +symbolic(struct w9968cf_symbolic_list list[], const int num) +{ + int i; + + for (i = 0; list[i].name != NULL; i++) + if (list[i].num == num) + return (list[i].name); + + return "Unknown"; +} + +static struct w9968cf_symbolic_list camlist[] = { + { W9968CF_MOD_GENERIC, "W996[87]CF JPEG USB Dual Mode Camera" }, + { W9968CF_MOD_CLVBWGP, "Creative Labs Video Blaster WebCam Go Plus" }, + + /* Other cameras (having the same descriptors as Generic W996[87]CF) */ + { W9968CF_MOD_ADPA5R, "Aroma Digi Pen ADG-5000 Refurbished" }, + { W9986CF_MOD_AU, "AVerTV USB" }, + { W9968CF_MOD_CLVBWG, "Creative Labs Video Blaster WebCam Go" }, + { W9968CF_MOD_DLLDK, "Die Lebon LDC-D35A Digital Kamera" }, + { W9968CF_MOD_EEEMC, "Ezonics EZ-802 EZMega Cam" }, + { W9968CF_MOD_ODPVDMPC, "OPCOM Digi Pen VGA Dual Mode Pen Camera" }, + + { -1, NULL } +}; + +static struct w9968cf_symbolic_list senlist[] = { + { CC_OV76BE, "OV76BE" }, + { CC_OV7610, "OV7610" }, + { CC_OV7620, "OV7620" }, + { CC_OV7620AE, "OV7620AE" }, + { CC_OV6620, "OV6620" }, + { CC_OV6630, "OV6630" }, + { CC_OV6630AE, "OV6630AE" }, + { CC_OV6630AF, "OV6630AF" }, + { -1, NULL } +}; + +/* Video4Linux1 palettes */ +static struct w9968cf_symbolic_list v4l1_plist[] = { + { VIDEO_PALETTE_GREY, "GREY" }, + { VIDEO_PALETTE_HI240, "HI240" }, + { VIDEO_PALETTE_RGB565, "RGB565" }, + { VIDEO_PALETTE_RGB24, "RGB24" }, + { VIDEO_PALETTE_RGB32, "RGB32" }, + { VIDEO_PALETTE_RGB555, "RGB555" }, + { VIDEO_PALETTE_YUV422, "YUV422" }, + { VIDEO_PALETTE_YUYV, "YUYV" }, + { VIDEO_PALETTE_UYVY, "UYVY" }, + { VIDEO_PALETTE_YUV420, "YUV420" }, + { VIDEO_PALETTE_YUV411, "YUV411" }, + { VIDEO_PALETTE_RAW, "RAW" }, + { VIDEO_PALETTE_YUV422P, "YUV422P" }, + { VIDEO_PALETTE_YUV411P, "YUV411P" }, + { VIDEO_PALETTE_YUV420P, "YUV420P" }, + { VIDEO_PALETTE_YUV410P, "YUV410P" }, + { -1, NULL } +}; + +/* Decoder error codes: */ +static struct w9968cf_symbolic_list decoder_errlist[] = { + { W9968CF_DEC_ERR_CORRUPTED_DATA, "Corrupted data" }, + { W9968CF_DEC_ERR_BUF_OVERFLOW, "Buffer overflow" }, + { W9968CF_DEC_ERR_NO_SOI, "SOI marker not found" }, + { W9968CF_DEC_ERR_NO_SOF0, "SOF0 marker not found" }, + { W9968CF_DEC_ERR_NO_SOS, "SOS marker not found" }, + { W9968CF_DEC_ERR_NO_EOI, "EOI marker not found" }, + { -1, NULL } +}; + +/* URB error codes: */ +static struct w9968cf_symbolic_list urb_errlist[] = { + { -ENOMEM, "No memory for allocation of internal structures" }, + { -ENOSPC, "The host controller's bandwidth is already consumed" }, + { -ENOENT, "URB was canceled by unlink_urb" }, + { -EXDEV, "ISO transfer only partially completed" }, + { -EAGAIN, "Too match scheduled for the future" }, + { -ENXIO, "URB already queued" }, + { -EFBIG, "Too much ISO frames requested" }, + { -ENOSR, "Buffer error (overrun)" }, + { -EPIPE, "Specified endpoint is stalled (device not responding)"}, + { -EOVERFLOW, "Babble (bad cable?)" }, + { -EPROTO, "Bit-stuff error (bad cable?)" }, + { -EILSEQ, "CRC/Timeout" }, + { -ETIMEDOUT, "NAK (device does not respond)" }, + { -1, NULL } +}; + + + +/**************************************************************************** + * Memory management functions * + ****************************************************************************/ + +/* Shameless copy from bttv-driver.c */ + +/* Here we want the physical address of the memory. + This is used when initializing the contents of the area. */ +static inline unsigned long kvirt_to_pa(unsigned long adr) +{ + unsigned long kva, ret; + + kva = (unsigned long) page_address(vmalloc_to_page((void *)adr)); + kva |= adr & (PAGE_SIZE-1); /* restore the offset */ + ret = __pa(kva); + return ret; +} + + +static void* rvmalloc(unsigned long size) +{ + void* mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + mem = vmalloc_32(size); + if (!mem) + return NULL; + + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr = (unsigned long) mem; + while (size > 0) { + mem_map_reserve(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return mem; +} + + +static void rvfree(void* mem, unsigned long size) +{ + unsigned long adr; + + if (!mem) + return; + + adr = (unsigned long) mem; + while ((long) size > 0) { + mem_map_unreserve(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + vfree(mem); +} +/* End of shameless copy */ + + +/*-------------------------------------------------------------------------- + Return the maximum size (in bytes) of a frame buffer. + --------------------------------------------------------------------------*/ +static inline unsigned long w9968cf_get_max_bufsize(struct w9968cf_device* cam) +{ + u8 bpp = (w9968cf_vppmod_present) ? 4 : 2; + return (cam->upscaling) ? W9968CF_MAX_WIDTH*W9968CF_MAX_HEIGHT*bpp : + cam->maxwidth*cam->maxheight*bpp; +} + + +/*-------------------------------------------------------------------------- + Deallocate previously allocated memory. + --------------------------------------------------------------------------*/ +static void w9968cf_deallocate_memory(struct w9968cf_device* cam) +{ + u8 i; + + /* Free the isochronous transfer buffers */ + for (i = 0; i < W9968CF_URBS; i++) { + kfree(cam->transfer_buffer[i]); + cam->transfer_buffer[i] = NULL; + } + + /* Free temporary frame buffer */ + if (cam->frame_tmp.buffer) { + rvfree(cam->frame_tmp.buffer, W9968CF_HW_BUF_SIZE); + cam->frame_tmp.buffer = NULL; + } + + /* Free helper buffer */ + if (cam->vpp_buffer) { + rvfree(cam->vpp_buffer, w9968cf_get_max_bufsize(cam)); + cam->vpp_buffer = NULL; + } + + /* Free video frame buffers */ + if (cam->frame[0].buffer) { + rvfree(cam->frame[0].buffer, + cam->nbuffers * w9968cf_get_max_bufsize(cam)); + cam->frame[0].buffer = NULL; + } + + cam->nbuffers = 0; + + DBG(5, "Memory successfully deallocated.") +} + + +/*-------------------------------------------------------------------------- + Allocate memory buffers for USB transfers and video frames. + This function is called by open() only. + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_allocate_memory(struct w9968cf_device* cam) +{ + const unsigned long bufsize = w9968cf_get_max_bufsize(cam); + const u16 p_size = wMaxPacketSize[cam->altsetting-1]; + void* buff = NULL; + u8 i; + + /* NOTE: Deallocation is done elsewhere in case of error */ + + /* Allocate memory for the isochronous transfer buffers */ + for (i = 0; i < W9968CF_URBS; i++) { + if (!(cam->transfer_buffer[i] = + kmalloc(W9968CF_ISO_PACKETS*p_size, GFP_KERNEL))) { + DBG(1, "Couldn't allocate memory for the isochronous " + "transfer buffers (%d bytes).", + p_size * W9968CF_ISO_PACKETS) + return -ENOMEM; + } + memset(cam->transfer_buffer[i], 0, W9968CF_ISO_PACKETS*p_size); + } + + /* Allocate memory for the temporary frame buffer */ + if (!(cam->frame_tmp.buffer = rvmalloc(W9968CF_HW_BUF_SIZE))) { + DBG(1, "Couldn't allocate memory for the temporary " + "video frame buffer (%i bytes).", W9968CF_HW_BUF_SIZE) + return -ENOMEM; + } + + /* Allocate memory for the helper buffer */ + if (w9968cf_vppmod_present) { + if (!(cam->vpp_buffer = rvmalloc(bufsize))) { + DBG(1, "Couldn't allocate memory for the helper buffer" + " (%li bytes).", bufsize) + return -ENOMEM; + } + } else + cam->vpp_buffer = NULL; + + /* Allocate memory for video frame buffers */ + cam->nbuffers = cam->max_buffers; + while (cam->nbuffers >= 2) { + if ((buff = rvmalloc(cam->nbuffers * bufsize))) + break; + else + cam->nbuffers--; + } + + if (!buff) { + DBG(1, "Couldn't allocate memory for the video frame buffers.") + cam->nbuffers = 0; + return -ENOMEM; + } + + if (cam->nbuffers != cam->max_buffers) + DBG(2, "Couldn't allocate memory for %d video frame buffers. " + "Only memory for %d buffers has been allocated.", + cam->max_buffers, cam->nbuffers) + + for (i = 0; i < cam->nbuffers; i++) { + cam->frame[i].buffer = buff + i*bufsize; + /* Circular list */ + if (i != cam->nbuffers-1) + cam->frame[i].next = &cam->frame[i+1]; + else + cam->frame[i].next = &cam->frame[0]; + cam->frame[i].status = F_UNUSED; + } + + DBG(5, "Memory successfully allocated.") + return 0; +} + + + +/**************************************************************************** + * /proc interface * + ****************************************************************************/ + +#if defined(CONFIG_VIDEO_PROC_FS) + +static struct proc_dir_entry* w9968cf_proc_entry, * w9968cf_proc_entry_global; +extern struct proc_dir_entry* video_proc_entry; + +#define YES_NO(x) ((x) ? "yes" : "no") + +/*-------------------------------------------------------------------------- + Read /proc/video/w9968cf/global + --------------------------------------------------------------------------*/ +static int +w9968cf_proc_read_global(char* page, char** start, off_t offset, + int count, int* eof, void* data) +{ + struct list_head *list = (struct list_head*)data, *ptr; + struct w9968cf_device* cam; + char* out = page; + int len; + u8 ncams = 0; + + if (down_interruptible(&w9968cf_devlist_sem)) + return -ERESTARTSYS; + + /* How many connected cameras ? */ + list_for_each(ptr, list) + ncams++; + + out += sprintf(out,"driver_name : %s\n", W9968CF_MODULE_NAME); + out += sprintf(out,"driver_version : %s\n", W9968CF_MODULE_VERSION); + out += sprintf(out,"driver_author : %s\n", W9968CF_MODULE_AUTHOR); + out += sprintf(out,"author_email : %s\n", W9968CF_AUTHOR_EMAIL); + out += sprintf(out,"vpp_w9968cf_module: %s\n", + w9968cf_vppmod_present ? "detected" : "not detected"); + out += sprintf(out,"max_allowed_cams : %d\n", simcams); + out += sprintf(out,"registered_cameras: %d\n", ncams); + + list_for_each(ptr, list) { + cam = list_entry(ptr, struct w9968cf_device, v4llist); + out += sprintf(out,"/dev/video%d : %s\n", + cam->v4ldev.minor, symbolic(camlist, cam->id)); + } + + up(&w9968cf_devlist_sem); + + len = out - page; + len -= offset; + if (len < count) { + *eof = 1; + if (len <= 0) + return 0; + } else + len = count; + + *start = page + offset; + + return len; +} + + +/*-------------------------------------------------------------------------- + Read /proc/video/w9968cf/dev + --------------------------------------------------------------------------*/ +static int +w9968cf_proc_read_dev(char* page, char** start, off_t offset, + int count, int* eof, void* data) +{ + struct w9968cf_device* cam = (struct w9968cf_device* )data; + int len; + char* out = page; + + static struct video_picture pict; /* it has to be static */ + static int rc = 0; + + if (down_interruptible(&cam->procfs_sem)) + return -ERESTARTSYS; + + if (offset == 0) + rc = w9968cf_sensor_get_picture(cam, &pict); + + out += sprintf(out,"camera_model : %s\n", + symbolic(camlist, cam->id)); + out += sprintf(out,"sensor_model : %s\n", + symbolic(senlist, cam->sensor)); + out += sprintf(out,"sensor_monochrome : %s\n", + YES_NO(cam->sensor_mono)); + if (cam->users) + out += sprintf(out,"user_program : %s\n",cam->command); + out += sprintf(out,"packet_size_bytes : %d\n", + wMaxPacketSize[cam->altsetting-1]); + out += sprintf(out,"allocated_buffers : %d\n",cam->nbuffers); + out += sprintf(out,"streaming : %s\n",YES_NO(cam->streaming)); + out += sprintf(out,"compression : %s\n", + YES_NO(cam->vpp_flag & VPP_DECOMPRESSION)); + out += sprintf(out,"uyvy_to_rgbx : %s\n", + YES_NO(cam->vpp_flag & VPP_UYVY_TO_RGBX)); + out += sprintf(out,"uv-y_reordering : %s\n", + YES_NO(cam->vpp_flag & VPP_SWAP_YUV_BYTES)); + out += sprintf(out,"software_upscaling: %s\n", + YES_NO(cam->vpp_flag & VPP_UPSCALE)); + out += sprintf(out,"clock_divisor : %d\n",cam->clockdiv); + out += sprintf(out,"palette : %s\n", + symbolic(v4l1_plist, cam->picture.palette)); + out += sprintf(out,"depth_bpp : %d\n",cam->picture.depth); + out += sprintf(out,"x_window_offset : %d\n",cam->window.x); + out += sprintf(out,"y_window_offset : %d\n",cam->window.y); + out += sprintf(out,"width : %d\n",cam->window.width); + out += sprintf(out,"height : %d\n",cam->window.height); + out += sprintf(out,"max_buffers : %d\n",cam->max_buffers); + out += sprintf(out,"decompression : %s\n", + (!cam->decompression)? "off" : + ((cam->decompression == 1) ? "force" : "on")); + out += sprintf(out,"force_rgb : %s\n",YES_NO(cam->force_rgb)); + out += sprintf(out,"video_clamping : %s\n",YES_NO(cam->clamping)); + out += sprintf(out,"hw_double_buffer : %s\n", + YES_NO(cam->double_buffer)); + out += sprintf(out,"filter_type : %s\n", + (!cam->filter_type) ? "none" : + ((cam->filter_type == 1) ? "3-tap" : "5-tap")); + out += sprintf(out,"large_view : %s\n",YES_NO(cam->largeview)); + out += sprintf(out,"auto_brightness : %s\n",YES_NO(cam->auto_brt)); + out += sprintf(out,"auto_exposure : %s\n",YES_NO(cam->auto_exp)); + out += sprintf(out,"light_frequency : %d\n",cam->lightfreq); + out += sprintf(out,"banding_filter : %s\n",YES_NO(cam->bandfilt)); + out += sprintf(out,"back_light : %s\n",YES_NO(cam->backlight)); + out += sprintf(out,"mirror : %s\n",YES_NO(cam->mirror)); + if (!rc) { + out += sprintf(out,"brightness : %d\n",pict.brightness); + out += sprintf(out,"colour : %d\n",pict.colour); + out += sprintf(out,"contrast : %d\n",pict.contrast); + out += sprintf(out,"hue : %d\n",pict.hue); + out += sprintf(out,"whiteness : %d\n",pict.whiteness); + } + out += sprintf(out,"hs_polarity : %d\n",cam->hs_polarity); + out += sprintf(out,"vs_polarity : %d\n",cam->vs_polarity); +#ifdef W9968CF_DEBUG + out += sprintf(out,"debug_level : %d\n",debug); + out += sprintf(out,"specific_debug : %s\n",YES_NO(specific_debug)); +#endif + + len = out - page; + len -= offset; + if (len < count) { + *eof = 1; + if (len <= 0) { + up(&cam->procfs_sem); + return 0; + } + } else + len = count; + + *start = page + offset; + + up(&cam->procfs_sem); + return len; +} + + +static void w9968cf_proc_create_dev(struct w9968cf_device* cam) +{ + char name[5]; + + if (!w9968cf_proc_entry) + return; + + /* Create per-device readable entry */ + sprintf(name, "dev%d", cam->v4ldev.minor); + cam->proc_dev = create_proc_read_entry(name, S_IFREG|S_IRUGO|S_IWUSR, + w9968cf_proc_entry, + w9968cf_proc_read_dev, + (void*)cam); + if (!cam->proc_dev) + return; + cam->proc_dev->owner = THIS_MODULE; +} + + +static void w9968cf_proc_destroy_dev(struct w9968cf_device* cam) +{ + char name[5]; + + if (!cam->proc_dev) + return; + + sprintf(name, "dev%d", cam->v4ldev.minor); + + /* Destroy per-device entry */ + remove_proc_entry(name, w9968cf_proc_entry); +} + + +static void w9968cf_proc_create(void) +{ + if (!video_proc_entry) { + DBG(2, "Error: /proc/video/ does not exist.") + return; + } + + w9968cf_proc_entry = create_proc_entry("w9968cf", S_IFDIR, + video_proc_entry); + if (!w9968cf_proc_entry) { + DBG(2, "Unable to create /proc/video/w9968cf/") + return; + } + w9968cf_proc_entry->owner = THIS_MODULE; + + w9968cf_proc_entry_global = create_proc_read_entry("global", + S_IFREG|S_IRUGO|S_IWUSR, + w9968cf_proc_entry, + w9968cf_proc_read_global, + (void*)&w9968cf_dev_list); + if (w9968cf_proc_entry_global) + w9968cf_proc_entry_global->owner = THIS_MODULE; + else + DBG(2, "Unable to create /proc/video/w9968cf/global") + + DBG(5, "/proc entries successfully created.") +} + + +static void w9968cf_proc_destroy(void) +{ + if (!w9968cf_proc_entry) + return; + + if (w9968cf_proc_entry_global) + remove_proc_entry("global", w9968cf_proc_entry); + + remove_proc_entry("w9968cf", video_proc_entry); + + DBG(5, "/proc entries removed.") +} + +#endif /* CONFIG_VIDEO_PROC_FS */ + + + +/**************************************************************************** + * USB-specific functions * + ****************************************************************************/ + +/*-------------------------------------------------------------------------- + This is an handler function which is called after the URBs are completed. + It collects multiple data packets coming from the camera by putting them + into frame buffers: one or more zero data length data packets are used to + mark the end of a video frame; the first non-zero data packet is the start + of the next video frame; if an error is encountered in a packet, the entire + video frame is discarded and grabbed again. + If there are no requested frames in the FIFO list, packets are collected into + a temporary buffer. + --------------------------------------------------------------------------*/ +static void w9968cf_urb_complete(struct urb *urb) +{ + struct w9968cf_device* cam = (struct w9968cf_device*)urb->context; + struct w9968cf_frame_t** f; + unsigned long maxbufsize; + unsigned int len, status; + void* pos; + u8 i; + int err = 0; + + if ((!cam->streaming) || cam->disconnected) { + DBG(4, "Got interrupt, but not streaming.") + return; + } + + maxbufsize = min( (unsigned long)W9968CF_HW_BUF_SIZE, + w9968cf_get_max_bufsize(cam) ); + + /* "(*f)" will be used instead of "cam->frame_current" */ + f = &cam->frame_current; + + /* If a frame has been requested and we are grabbing into + the temporary frame, we'll switch to that requested frame */ + if ((*f) == &cam->frame_tmp && *cam->requested_frame) { + if (cam->frame_tmp.status == F_GRABBING) { + w9968cf_pop_frame(cam, &cam->frame_current); + (*f)->status = F_GRABBING; + (*f)->length = cam->frame_tmp.length; + memcpy((*f)->buffer, cam->frame_tmp.buffer, + (*f)->length); + DBG(6, "Switched from temp. frame to frame #%d", + (*f) - &cam->frame[0]) + } + } + + for (i = 0; i < urb->number_of_packets; i++) { + len = urb->iso_frame_desc[i].actual_length; + status = urb->iso_frame_desc[i].status; + pos = urb->iso_frame_desc[i].offset + urb->transfer_buffer; + + if (status && len != 0) { + DBG(4, "URB failed, error in data packet " + "(error #%d, %s).", + status, symbolic(urb_errlist, status)) + (*f)->status = F_ERROR; + continue; + } + + if (len) { /* start of frame */ + + if ((*f)->status == F_UNUSED) { + (*f)->status = F_GRABBING; + (*f)->length = 0; + } + + /* Buffer overflows shouldn't happen, however...*/ + if ((*f)->length + len > maxbufsize) { + DBG(4, "Buffer overflow: bad data packets.") + (*f)->status = F_ERROR; + } + + if ((*f)->status == F_GRABBING) { + memcpy((*f)->buffer + (*f)->length, pos, len); + (*f)->length += len; + } + + } else if ((*f)->status == F_GRABBING) { /* end of frame */ + + DBG(6, "Frame #%d successfully grabbed.", + ((*f)==&cam->frame_tmp ? -1 : (*f)-&cam->frame[0])) + + if (cam->vpp_flag & VPP_DECOMPRESSION) { + err=(*w9968cf_vpp_check_headers)((*f)->buffer, + (*f)->length); + if (err) { + DBG(4, "Skip corrupted frame: %s", + symbolic(decoder_errlist, err)) + (*f)->status = F_UNUSED; + continue; /* grab this frame again */ + } + } + + (*f)->status = F_READY; + (*f)->queued = 0; + + /* Take a pointer to the new frame from the FIFO list. + If the list is empty,we'll use the temporary frame*/ + if (*cam->requested_frame) + w9968cf_pop_frame(cam, &cam->frame_current); + else { + cam->frame_current = &cam->frame_tmp; + (*f)->status = F_UNUSED; + } + + } else if ((*f)->status == F_ERROR) + (*f)->status = F_UNUSED; /* grab it again */ + + PDBGG("Frame length %li | pack.#%d | pack.len. %d | state %d", + (unsigned long)(*f)->length, i, len, (*f)->status) + + } /* end for */ + + /* Resubmit this URB */ + urb->dev = cam->usbdev; + urb->status = 0; + spin_lock(&cam->urb_lock); + if (cam->streaming) + if ((err = usb_submit_urb(urb))) { + cam->misconfigured = 1; + DBG(1, "Couldn't resubmit the URB: error %d, %s", + err, symbolic(urb_errlist, err)); + } + spin_unlock(&cam->urb_lock); + + /* Wake up the user process */ + if (waitqueue_active(&cam->wait_queue)) + wake_up_interruptible(&cam->wait_queue); +} + + +/*--------------------------------------------------------------------------- + Setup the URB structures for the isochronous transfer. + Submit the URBs so that the data transfer begins. + Return 0 on success, a negative number otherwise. + ---------------------------------------------------------------------------*/ +static int w9968cf_start_transfer(struct w9968cf_device* cam) +{ + struct usb_device *udev = cam->usbdev; + struct urb* urb; + const u16 p_size = wMaxPacketSize[cam->altsetting-1]; + u16 w, h, d; + int vidcapt; + u32 t_size; + int err = 0; + s8 i, j; + + for (i = 0; i < W9968CF_URBS; i++) { + urb = usb_alloc_urb(W9968CF_ISO_PACKETS); + cam->urb[i] = urb; + + if (!urb) { + for (j = 0; j < i; j++) + usb_free_urb(cam->urb[j]); + DBG(1, "Couldn't allocate the URB structures.") + return -ENOMEM; + } + + urb->dev = udev; + urb->context = (void*)cam; + urb->pipe = usb_rcvisocpipe(udev, 1); + urb->transfer_flags = USB_ISO_ASAP; + urb->number_of_packets = W9968CF_ISO_PACKETS; + urb->complete = w9968cf_urb_complete; + urb->transfer_buffer = cam->transfer_buffer[i]; + urb->transfer_buffer_length = p_size*W9968CF_ISO_PACKETS; + urb->interval = 1; + for (j = 0; j < W9968CF_ISO_PACKETS; j++) { + urb->iso_frame_desc[j].offset = p_size*j; + urb->iso_frame_desc[j].length = p_size; + } + } + + /* Transfer size per frame, in WORD ! */ + d = cam->hw_depth; + w = cam->hw_width; + h = cam->hw_height; + + t_size = (w*h*d)/16; + + err = w9968cf_write_reg(cam, 0xbf17, 0x00); /* reset everything */ + err += w9968cf_write_reg(cam, 0xbf10, 0x00); /* normal operation */ + + /* Transfer size */ + err += w9968cf_write_reg(cam, t_size & 0xffff, 0x3d); /* low bits */ + err += w9968cf_write_reg(cam, t_size >> 16, 0x3e); /* high bits */ + + if (cam->vpp_flag & VPP_DECOMPRESSION) + err += w9968cf_upload_quantizationtables(cam); + + vidcapt = w9968cf_read_reg(cam, 0x16); /* read picture settings */ + err += w9968cf_write_reg(cam, vidcapt|0x8000, 0x16); /* capt. enable */ + + err += usb_set_interface(udev, 0, cam->altsetting); + err += w9968cf_write_reg(cam, 0x8a05, 0x3c); /* USB FIFO enable */ + + if (err || (vidcapt < 0)) { + for (i = 0; i < W9968CF_URBS; i++) + usb_free_urb(cam->urb[i]); + DBG(1, "Couldn't tell the camera to start the data transfer.") + return err; + } + + w9968cf_init_framelist(cam); + + /* Begin to grab into the temporary buffer */ + cam->frame_tmp.status = F_UNUSED; + cam->frame_tmp.queued = 0; + cam->frame_current = &cam->frame_tmp; + + if (!(cam->vpp_flag & VPP_DECOMPRESSION)) + DBG(5, "Isochronous transfer size: %li bytes/frame.", + (unsigned long)t_size*2) + + DBG(5, "Starting the isochronous transfer...") + + /* Submit the URBs */ + for (i = 0; i < W9968CF_URBS; i++) { + err = usb_submit_urb(cam->urb[i]); + if (err) { + for (j = i-1; j >= 0; j--) + if (!usb_unlink_urb(cam->urb[j])) + usb_free_urb(cam->urb[j]); + DBG(1, "Couldn't send a transfer request to the " + "USB core (error #%d, %s).", err, + symbolic(urb_errlist, err)) + } + } + + cam->streaming = 1; + + return 0; +} + + +/*-------------------------------------------------------------------------- + Stop the isochronous transfer and set alternate setting to 0 (0Mb/s). + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_stop_transfer(struct w9968cf_device* cam) +{ + struct usb_device *udev = cam->usbdev; + unsigned long lock_flags; + int err = 0; + s8 i; + + /* This avoids race conditions with usb_submit_urb() + in the URB completition handler */ + spin_lock_irqsave(&cam->urb_lock, lock_flags); + cam->streaming = 0; + spin_unlock_irqrestore(&cam->urb_lock, lock_flags); + + for (i = W9968CF_URBS-1; i >= 0; i--) + if (cam->urb[i]) + if (!usb_unlink_urb(cam->urb[i])) { + usb_free_urb(cam->urb[i]); + cam->urb[i] = NULL; + } + + if (cam->disconnected) + goto exit; + + err = w9968cf_write_reg(cam, 0x0a05, 0x3c); /* stop USB transfer */ + err += usb_set_interface(udev, 0, 0); /* 0 Mb/s */ + err += w9968cf_write_reg(cam, 0x0000, 0x39); /* disable JPEG encoder */ + err += w9968cf_write_reg(cam, 0x0000, 0x16); /* stop video capture */ + + if (err) { + DBG(2, "Failed to tell the camera to stop the isochronous " + "transfer. However this is not a critical error.") + return -EIO; + } + +exit: + DBG(5, "Isochronous transfer stopped.") + return 0; +} + + +/*-------------------------------------------------------------------------- + Write a W9968CF register. + Return 0 on success, -1 otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_write_reg(struct w9968cf_device* cam, u16 value, u16 index) +{ + struct usb_device* udev = cam->usbdev; + int res; + + res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0, + USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE, + value, index, NULL, 0, W9968CF_USB_CTRL_TIMEOUT); + + if (res < 0) + DBG(4, "Failed to write a register " + "(value 0x%04X, index 0x%02X, error #%d, %s).", + value, index, res, symbolic(urb_errlist, res)) + + return (res >= 0) ? 0 : -1; +} + + +/*-------------------------------------------------------------------------- + Read a W9968CF register. + Return the register value on success, -1 otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_read_reg(struct w9968cf_device* cam, u16 index) +{ + struct usb_device* udev = cam->usbdev; + u16* buff = cam->control_buffer; + int res; + + res = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 1, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, index, (void*)buff, + 2, W9968CF_USB_CTRL_TIMEOUT); + + if (res < 0) + DBG(4, "Failed to read a register " + "(index 0x%02X, error #%d, %s).", + index, res, symbolic(urb_errlist, res)) + + return (res >= 0) ? (int)(*buff) : -1; +} + + +/*-------------------------------------------------------------------------- + Write data to the fast serial bus registers. + Return 0 on success, -1 otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_write_fsb(struct w9968cf_device* cam, u16* data) +{ + struct usb_device* udev = cam->usbdev; + u16 value; + int res; + + value = *data++; + + res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0, + USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE, + value, 0x06, (void*)data, 6, + W9968CF_USB_CTRL_TIMEOUT); + + if (res < 0) + DBG(4, "Failed to write the FSB registers " + "(error #%d, %s).", res, symbolic(urb_errlist, res)) + + return (res >= 0) ? 0 : -1; +} + + +/*-------------------------------------------------------------------------- + Write data to the serial bus control register. + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_write_sb(struct w9968cf_device* cam, u16 value) +{ + int err = 0; + + err = w9968cf_write_reg(cam, value, 0x01); + udelay(W9968CF_I2C_BUS_DELAY); + + return err; +} + + +/*-------------------------------------------------------------------------- + Read data from the serial bus control register. + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_read_sb(struct w9968cf_device* cam) +{ + int v = 0; + + v = w9968cf_read_reg(cam, 0x01); + udelay(W9968CF_I2C_BUS_DELAY); + + return v; +} + + +/*-------------------------------------------------------------------------- + Upload quantization tables for the JPEG compression. + This function is called by w9968cf_start_transfer(). + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_upload_quantizationtables(struct w9968cf_device* cam) +{ + u16 a, b; + int err = 0, i, j; + + err += w9968cf_write_reg(cam, 0x0010, 0x39); /* JPEG clock enable */ + + for (i = 0, j = 0; i < 32; i++, j += 2) { + a = Y_QUANTABLE[j] | ((unsigned)(Y_QUANTABLE[j+1]) << 8); + b = UV_QUANTABLE[j] | ((unsigned)(UV_QUANTABLE[j+1]) << 8); + err += w9968cf_write_reg(cam, a, 0x40+i); + err += w9968cf_write_reg(cam, b, 0x60+i); + } + err += w9968cf_write_reg(cam, 0x0012, 0x39); /* JPEG encoder enable */ + + return err; +} + + + +/**************************************************************************** + * Low-level I2C I/O functions. * + * The adapter supports the following I2C transfer functions: * + * i2c_adap_fastwrite_byte_data() (at 400 kHz bit frequency only) * + * i2c_adap_read_byte_data() * + * i2c_adap_read_byte() * + ****************************************************************************/ + +static int w9968cf_smbus_start(struct w9968cf_device* cam) +{ + int err = 0; + + err += w9968cf_write_sb(cam, 0x0011); /* SDE=1, SDA=0, SCL=1 */ + err += w9968cf_write_sb(cam, 0x0010); /* SDE=1, SDA=0, SCL=0 */ + + return err; +} + + +static int w9968cf_smbus_stop(struct w9968cf_device* cam) +{ + int err = 0; + + err += w9968cf_write_sb(cam, 0x0011); /* SDE=1, SDA=0, SCL=1 */ + err += w9968cf_write_sb(cam, 0x0013); /* SDE=1, SDA=1, SCL=1 */ + + return err; +} + + +static int w9968cf_smbus_write_byte(struct w9968cf_device* cam, u8 v) +{ + u8 bit; + int err = 0, sda; + + for (bit = 0 ; bit < 8 ; bit++) { + sda = (v & 0x80) ? 2 : 0; + v <<= 1; + /* SDE=1, SDA=sda, SCL=0 */ + err += w9968cf_write_sb(cam, 0x10 | sda); + /* SDE=1, SDA=sda, SCL=1 */ + err += w9968cf_write_sb(cam, 0x11 | sda); + /* SDE=1, SDA=sda, SCL=0 */ + err += w9968cf_write_sb(cam, 0x10 | sda); + } + + return err; +} + + +static int w9968cf_smbus_read_byte(struct w9968cf_device* cam, u8* v) +{ + u8 bit; + int err = 0; + + *v = 0; + for (bit = 0 ; bit < 8 ; bit++) { + *v <<= 1; + err += w9968cf_write_sb(cam, 0x0013); + *v |= (w9968cf_read_sb(cam) & 0x0008) ? 1 : 0; + err += w9968cf_write_sb(cam, 0x0012); + } + + return err; +} + + +static int w9968cf_smbus_write_ack(struct w9968cf_device* cam) +{ + int err = 0; + + err += w9968cf_write_sb(cam, 0x0010); /* SDE=1, SDA=0, SCL=0 */ + err += w9968cf_write_sb(cam, 0x0011); /* SDE=1, SDA=0, SCL=1 */ + err += w9968cf_write_sb(cam, 0x0010); /* SDE=1, SDA=0, SCL=0 */ + + return err; +} + + +static int w9968cf_smbus_read_ack(struct w9968cf_device* cam) +{ + int err = 0, sda; + + err += w9968cf_write_sb(cam, 0x0013); /* SDE=1, SDA=1, SCL=1 */ + sda = (w9968cf_read_sb(cam) & 0x08) ? 1 : 0; /* sda = SDA */ + err += w9968cf_write_sb(cam, 0x0012); /* SDE=1, SDA=1, SCL=0 */ + if (sda < 0) + err += sda; + if (sda == 1) { + DBG(6, "Couldn't receive the ACK.") + err += -1; + } + + return err; +} + + +/* SMBus protocol: S Addr Wr [A] Subaddr [A] Value [A] P */ +static int +w9968cf_i2c_adap_fastwrite_byte_data(struct w9968cf_device* cam, + u16 address, u8 subaddress,u8 value) +{ + u16* data = cam->data_buffer; + int err = 0; + + /* Enable SBUS outputs */ + err += w9968cf_write_reg(cam, 0x0020, 0x01); + + data[0] = 0x082f | ((address & 0x80) ? 0x1500 : 0x0); + data[0] |= (address & 0x40) ? 0x4000 : 0x0; + data[1] = 0x2082 | ((address & 0x40) ? 0x0005 : 0x0); + data[1] |= (address & 0x20) ? 0x0150 : 0x0; + data[1] |= (address & 0x10) ? 0x5400 : 0x0; + data[2] = 0x8208 | ((address & 0x08) ? 0x0015 : 0x0); + data[2] |= (address & 0x04) ? 0x0540 : 0x0; + data[2] |= (address & 0x02) ? 0x5000 : 0x0; + data[3] = 0x1d20 | ((address & 0x02) ? 0x0001 : 0x0); + data[3] |= (address & 0x01) ? 0x0054 : 0x0; + + err += w9968cf_write_fsb(cam, data); + + data[0] = 0x8208 | ((subaddress & 0x80) ? 0x0015 : 0x0); + data[0] |= (subaddress & 0x40) ? 0x0540 : 0x0; + data[0] |= (subaddress & 0x20) ? 0x5000 : 0x0; + data[1] = 0x0820 | ((subaddress & 0x20) ? 0x0001 : 0x0); + data[1] |= (subaddress & 0x10) ? 0x0054 : 0x0; + data[1] |= (subaddress & 0x08) ? 0x1500 : 0x0; + data[1] |= (subaddress & 0x04) ? 0x4000 : 0x0; + data[2] = 0x2082 | ((subaddress & 0x04) ? 0x0005 : 0x0); + data[2] |= (subaddress & 0x02) ? 0x0150 : 0x0; + data[2] |= (subaddress & 0x01) ? 0x5400 : 0x0; + data[3] = 0x001d; + + err += w9968cf_write_fsb(cam, data); + + data[0] = 0x8208 | ((value & 0x80) ? 0x0015 : 0x0); + data[0] |= (value & 0x40) ? 0x0540 : 0x0; + data[0] |= (value & 0x20) ? 0x5000 : 0x0; + data[1] = 0x0820 | ((value & 0x20) ? 0x0001 : 0x0); + data[1] |= (value & 0x10) ? 0x0054 : 0x0; + data[1] |= (value & 0x08) ? 0x1500 : 0x0; + data[1] |= (value & 0x04) ? 0x4000 : 0x0; + data[2] = 0x2082 | ((value & 0x04) ? 0x0005 : 0x0); + data[2] |= (value & 0x02) ? 0x0150 : 0x0; + data[2] |= (value & 0x01) ? 0x5400 : 0x0; + data[3] = 0xfe1d; + + err += w9968cf_write_fsb(cam, data); + + /* Disable SBUS outputs */ + err += w9968cf_write_reg(cam, 0x0000, 0x01); + + if (!err) + DBG(5, "I2C write byte data done, addr.0x%04X, subaddr.0x%02X " + "value 0x%02X.", address, subaddress, value) + else + DBG(5, "I2C write byte data failed, addr.0x%04X, " + "subaddr.0x%02X, value 0x%02X.", + address, subaddress, value) + + return err; +} + + +/* SMBus protocol: S Addr Wr [A] Subaddr [A] P S Addr+1 Rd [A] [Value] NA P */ +static int +w9968cf_i2c_adap_read_byte_data(struct w9968cf_device* cam, + u16 address, u8 subaddress, + u8* value) +{ + int err = 0; + + /* Serial data enable */ + err += w9968cf_write_sb(cam, 0x0013); /* don't change ! */ + + err += w9968cf_smbus_start(cam); + err += w9968cf_smbus_write_byte(cam, address); + err += w9968cf_smbus_read_ack(cam); + err += w9968cf_smbus_write_byte(cam, subaddress); + err += w9968cf_smbus_read_ack(cam); + err += w9968cf_smbus_stop(cam); + err += w9968cf_smbus_start(cam); + err += w9968cf_smbus_write_byte(cam, address + 1); + err += w9968cf_smbus_read_ack(cam); + err += w9968cf_smbus_read_byte(cam, value); + err += w9968cf_smbus_write_ack(cam); + err += w9968cf_smbus_stop(cam); + + /* Serial data disable */ + err += w9968cf_write_sb(cam, 0x0000); + + if (!err) + DBG(5, "I2C read byte data done, addr.0x%04X, " + "subaddr.0x%02X, value 0x%02X.", + address, subaddress, *value) + else + DBG(5, "I2C read byte data failed, addr.0x%04X, " + "subaddr.0x%02X, wrong value 0x%02X.", + address, subaddress, *value) + + return err; +} + + +/* SMBus protocol: S Addr+1 Rd [A] [Value] NA P */ +static int +w9968cf_i2c_adap_read_byte(struct w9968cf_device* cam, + u16 address, u8* value) +{ + int err = 0; + + /* Serial data enable */ + err += w9968cf_write_sb(cam, 0x0013); + + err += w9968cf_smbus_start(cam); + err += w9968cf_smbus_write_byte(cam, address + 1); + err += w9968cf_smbus_read_ack(cam); + err += w9968cf_smbus_read_byte(cam, value); + err += w9968cf_smbus_write_ack(cam); + err += w9968cf_smbus_stop(cam); + + /* Serial data disable */ + err += w9968cf_write_sb(cam, 0x0000); + + if (!err) + DBG(5, "I2C read byte done, addr.0x%04X." + "value 0x%02X.", address, *value) + else + DBG(5, "I2C read byte failed, addr.0x%04X." + "wrong value 0x%02X.", address, *value) + + return err; +} + + +/* SMBus protocol: S Addr Wr [A] Value [A] P */ +static int +w9968cf_i2c_adap_write_byte(struct w9968cf_device* cam, + u16 address, u8 value) +{ + DBG(4, "i2c_write_byte() is an unsupported transfer mode.") + return -EINVAL; +} + + + +/**************************************************************************** + * I2C interface to kernel * + ****************************************************************************/ + +static int +w9968cf_i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, + unsigned short flags, char read_write, u8 command, + int size, union i2c_smbus_data *data) +{ + struct w9968cf_device* cam = adapter->data; + u8 i, j; + int rc = 0, err = 0; + + switch (addr) { + case OV6xx0_SID: + case OV7xx0_SID: + break; + default: + DBG(4, "Rejected slave ID 0x%04X", addr) + return -EINVAL; + } + + if (size == I2C_SMBUS_BYTE) { + /* Why addr <<= 1? See OVXXX0_SID defines in ovcamchip.h */ + addr <<= 1; + + if (read_write == I2C_SMBUS_WRITE) + rc = w9968cf_i2c_adap_write_byte(cam, addr, command); + else if (read_write == I2C_SMBUS_READ) + rc = w9968cf_i2c_adap_read_byte(cam,addr, &data->byte); + + } else if (size == I2C_SMBUS_BYTE_DATA) { + addr <<= 1; + + if (read_write == I2C_SMBUS_WRITE) + rc = w9968cf_i2c_adap_fastwrite_byte_data(cam, addr, + command, data->byte); + else if (read_write == I2C_SMBUS_READ) { + for (i = 1; i <= W9968CF_I2C_RW_RETRIES; i++) { + rc = w9968cf_i2c_adap_read_byte_data(cam, addr, + command, &data->byte); + if (rc < 0) { + /* Work around: this seems to wake up + the EEPROM from the stall state */ + for (j = 0; j <= 10; j++) { + err += w9968cf_write_sb(cam,0x0020); + err += w9968cf_write_sb(cam,0x0000); + if (err) + break; + } + } + else + break; + } + + } else + return -EINVAL; + + } else { + DBG(4, "Unsupported I2C transfer mode (%d)", size) + return -EINVAL; + } + + /* This works around a bug in the I2C core */ + if (rc > 0) + rc = 0; + + return rc; +} + + +static u32 w9968cf_i2c_func(struct i2c_adapter* adap) +{ + return I2C_FUNC_SMBUS_READ_BYTE | + I2C_FUNC_SMBUS_READ_BYTE_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE_DATA; +} + + +static int w9968cf_i2c_attach_inform(struct i2c_client* client) +{ + struct w9968cf_device* cam = client->adapter->data; + const char* clientname = client->name; + int id = client->driver->id; + + if (id == I2C_DRIVERID_OVCAMCHIP) { + int rc = 0; + + cam->sensor_client = client; + + rc = w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_INITIALIZE, + &cam->sensor_mono); + if (rc < 0) { + DBG(1, "CMOS sensor initialization failed (rc=%d)",rc); + cam->sensor_client = NULL; + return rc; + } + + if (w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_Q_SUBTYPE, + &cam->sensor) < 0) + rc = -EIO; + else if (client->addr==OV7xx0_SID || client->addr==OV6xx0_SID) + w9968cf_sensor_configure(cam); + else + rc = -EINVAL; + + if (rc < 0) { + cam->sensor_client = NULL; + cam->sensor = CC_UNKNOWN; + return rc; + } + } else { + DBG(4, "Rejected client [%s] with [%s]", + clientname, client->driver->name) + return -1; + } + + DBG(2, "I2C attach client [%s] with [%s]", + clientname, client->driver->name) + + return 0; +} + + +static int w9968cf_i2c_detach_inform(struct i2c_client* client) +{ + struct w9968cf_device* cam = client->adapter->data; + const char* clientname = client->name; + + if (cam->sensor_client == client) { + cam->sensor_client = NULL; + } + + DBG(2, "I2C detach [%s]", clientname) + + return 0; +} + + +static int +w9968cf_i2c_control(struct i2c_adapter* adapter, unsigned int cmd, + unsigned long arg) +{ + return 0; +} + + +static void w9968cf_i2c_inc_use(struct i2c_adapter* adap) +{ + MOD_INC_USE_COUNT; +} + + +static void w9968cf_i2c_dec_use(struct i2c_adapter* adap) +{ + MOD_DEC_USE_COUNT; +} + + +static int w9968cf_i2c_init(struct w9968cf_device* cam) +{ + int rc = 0; + + static struct i2c_algorithm algo = { + .name = "W996[87]CF algorithm", + .id = I2C_ALGO_SMBUS, + .smbus_xfer = w9968cf_i2c_smbus_xfer, + .algo_control = w9968cf_i2c_control, + .functionality = w9968cf_i2c_func, + }; + + static struct i2c_adapter adap = { + .id = I2C_ALGO_SMBUS | I2C_HW_SMBUS_W9968CF, + .inc_use = w9968cf_i2c_inc_use, + .dec_use = w9968cf_i2c_dec_use, + .client_register = w9968cf_i2c_attach_inform, + .client_unregister = w9968cf_i2c_detach_inform, + .algo = &algo, + }; + + memcpy(&cam->i2c_adapter, &adap, sizeof(struct i2c_adapter)); + strcpy(cam->i2c_adapter.name, "w9968cf"); + cam->i2c_adapter.data = cam; + + DBG(6, "Registering I2C bus with kernel...") + + rc = i2c_add_adapter(&cam->i2c_adapter); + if (rc) + DBG(5, "Failed to register the I2C bus.") + else + DBG(5, "I2C bus registered.") + + return rc; +} + + + +/**************************************************************************** + * Helper functions * + ****************************************************************************/ + +/*-------------------------------------------------------------------------- + Turn on the LED on some webcams. A beep should be heard too. + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_turn_on_led(struct w9968cf_device* cam) +{ + int err = 0; + + err += w9968cf_write_reg(cam, 0xff00, 0x00); /* power-down */ + err += w9968cf_write_reg(cam, 0xbf17, 0x00); /* reset everything */ + err += w9968cf_write_reg(cam, 0xbf10, 0x00); /* normal operation */ + err += w9968cf_write_reg(cam, 0x0010, 0x01); /* serial bus, SDS high */ + err += w9968cf_write_reg(cam, 0x0000, 0x01); /* serial bus, SDS low */ + err += w9968cf_write_reg(cam, 0x0010, 0x01); /* ..high 'beep-beep' */ + + if (err) + DBG(2, "Couldn't turn on the LED.") + + DBG(5, "LED turned on.") + + return err; +} + + +/*-------------------------------------------------------------------------- + Write some registers for the device initialization. + This function is called once on open(). + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_init_chip(struct w9968cf_device* cam) +{ + int err = 0, rc = 0; + + err += w9968cf_write_reg(cam, 0xff00, 0x00); /* power off */ + err += w9968cf_write_reg(cam, 0xbf10, 0x00); /* power on */ + + err += w9968cf_write_reg(cam, 0x405d, 0x03); /* DRAM timings */ + err += w9968cf_write_reg(cam, 0x0030, 0x04); /* SDRAM timings */ + + err += w9968cf_write_reg(cam, 0x0000, 0x20); /* Y frame buf.0, low */ + err += w9968cf_write_reg(cam, 0x0000, 0x21); /* Y frame buf.0, high */ + err += w9968cf_write_reg(cam, 0xb000, 0x22); /* Y frame buf.1, low */ + err += w9968cf_write_reg(cam, 0x0004, 0x23); /* Y frame buf.1, high */ + err += w9968cf_write_reg(cam, 0x5800, 0x24); /* U frame buf.0, low */ + err += w9968cf_write_reg(cam, 0x0002, 0x25); /* U frame buf.0, high */ + err += w9968cf_write_reg(cam, 0x0800, 0x26); /* U frame buf.1, low */ + err += w9968cf_write_reg(cam, 0x0007, 0x27); /* U frame buf.1, high */ + err += w9968cf_write_reg(cam, 0x8400, 0x28); /* V frame buf.0, low */ + err += w9968cf_write_reg(cam, 0x0003, 0x29); /* V frame buf.0, high */ + err += w9968cf_write_reg(cam, 0x3400, 0x2a); /* V frame buf.1, low */ + err += w9968cf_write_reg(cam, 0x0008, 0x2b); /* V frame buf.1, high */ + + err += w9968cf_write_reg(cam, 0x6000, 0x32); /* JPEG bitstream buf 0 */ + err += w9968cf_write_reg(cam, 0x0009, 0x33); /* JPEG bitstream buf 0 */ + err += w9968cf_write_reg(cam, 0x2000, 0x34); /* JPEG bitstream buf 1 */ + err += w9968cf_write_reg(cam, 0x000d, 0x35); /* JPEG bitstream buf 1 */ + + err += w9968cf_write_reg(cam, 0x0000, 0x36);/* JPEG restart interval */ + err += w9968cf_write_reg(cam, 0x0804, 0x37);/*JPEG VLE FIFO threshold*/ + err += w9968cf_write_reg(cam, 0x0000, 0x38);/* disable hw up-scaling */ + err += w9968cf_write_reg(cam, 0x0000, 0x3f); /* JPEG/MCTL test data */ + + err += w9968cf_set_picture(cam, cam->picture); /* this before */ + err += w9968cf_set_window(cam, cam->window); + + if (err) + goto error; + + rc = w9968cf_sensor_change_settings(cam); + if (rc) + goto error; + + DBG(5, "Chip successfully initialized."); + + return 0; + +error: + DBG(1, "Chip initialization failed.") + if (err) + return err; + else + return rc; +} + + +/*-------------------------------------------------------------------------- + Change the picture settings of the camera. + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int +w9968cf_set_picture(struct w9968cf_device* cam, struct video_picture pict) +{ + u16 fmt, hw_depth, hw_palette, reg_v = 0x0000; + int err = 0, rc = 0; + + /* Make sure we are using a valid depth */ + pict.depth = w9968cf_valid_depth(pict.palette); + + fmt = pict.palette; + + hw_depth = pict.depth; /* depth used by the winbond chip */ + hw_palette = pict.palette; /* palette used by the winbond chip */ + + /* VS & HS polarities */ + reg_v = (cam->vs_polarity << 12) | (cam->hs_polarity << 11); + + switch (fmt) + { + case VIDEO_PALETTE_UYVY: + reg_v |= 0x0000; + cam->vpp_flag = VPP_NONE; + break; + case VIDEO_PALETTE_YUV422P: + reg_v |= 0x0002; + cam->vpp_flag = VPP_DECOMPRESSION; + break; + case VIDEO_PALETTE_YUV420: + case VIDEO_PALETTE_YUV420P: + reg_v |= 0x0003; + cam->vpp_flag = VPP_DECOMPRESSION; + break; + case VIDEO_PALETTE_YUYV: + case VIDEO_PALETTE_YUV422: + reg_v |= 0x0000; + cam->vpp_flag = VPP_SWAP_YUV_BYTES; + hw_palette = VIDEO_PALETTE_UYVY; + break; + /* Original video is used instead of RGBX palettes. + Software conversion later. */ + case VIDEO_PALETTE_GREY: + case VIDEO_PALETTE_RGB555: + case VIDEO_PALETTE_RGB565: + case VIDEO_PALETTE_RGB24: + case VIDEO_PALETTE_RGB32: + reg_v |= 0x0000; /* UYVY 16 bit is used */ + hw_depth = 16; + hw_palette = VIDEO_PALETTE_UYVY; + cam->vpp_flag = VPP_UYVY_TO_RGBX; + break; + } + + /* FIXME: 'hardware double buffer' doesn't work when compressed video + is enabled (corrupted frames). */ + if (cam->double_buffer && !(cam->vpp_flag & VPP_DECOMPRESSION)) + reg_v |= 0x0080; + + if (cam->clamping) + reg_v |= 0x0020; + + if (cam->filter_type == 1) + reg_v |= 0x0008; + else if (cam->filter_type == 2) + reg_v |= 0x000c; + + err = w9968cf_write_reg(cam, reg_v, 0x16); + if (err) + goto error; + + rc = w9968cf_sensor_set_picture(cam, pict); + if (rc) + goto error; + + /* If all went well, update the device data structure */ + memcpy(&cam->picture, &pict, sizeof(pict)); + cam->hw_depth = hw_depth; + cam->hw_palette = hw_palette; + + /* Settings changed, so we clear the frame buffers */ + memset(cam->frame[0].buffer, 0, + cam->nbuffers*w9968cf_get_max_bufsize(cam)); + + DBG(4, "Palette is %s, depth is %d bpp.", + symbolic(v4l1_plist, pict.palette), pict.depth) + + return 0; + +error: + DBG(1, "Failed to change picture settings.") + if (err) + return err; + else + return rc; +} + + +/*-------------------------------------------------------------------------- + Change the capture area size of the camera. + This function _must_ be called _after_ w9968cf_set_picture(). + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int +w9968cf_set_window(struct w9968cf_device* cam, struct video_window win) +{ + u16 x, y, w, h, scx, scy, cw, ch, ax, ay; + unsigned long fw, fh; + struct ovcamchip_window s_win; + int err=0, rc=0; + + /* Work around to avoid FP arithmetics */ + #define __SC(x) ((x) << 10) + #define __UNSC(x) ((x) >> 10) + + /* Make sure we are using a supported resolution */ + if ((err = w9968cf_adjust_window_size(cam, (u16*)&win.width, + (u16*)&win.height))) + goto error; + + /* Scaling factors */ + fw = __SC(win.width) / cam->maxwidth; + fh = __SC(win.height) / cam->maxheight; + + /* Set up the width and height values used by the chip */ + if ((win.width > cam->maxwidth) || (win.height > cam->maxheight)) { + cam->vpp_flag |= VPP_UPSCALE; + /* Calculate largest w,h mantaining the same w/h ratio */ + w = (fw >= fh) ? cam->maxwidth : __SC(win.width)/fh; + h = (fw >= fh) ? __SC(win.height)/fw : cam->maxheight; + if (w < cam->minwidth) /* just in case */ + w = cam->minwidth; + if (h < cam->minheight) /* just in case */ + h = cam->minheight; + } else { + cam->vpp_flag &= ~VPP_UPSCALE; + w = win.width; + h = win.height; + } + + /* x,y offsets of the cropped area */ + scx = cam->start_cropx; + scy = cam->start_cropy; + + /* Calculate cropped area manteining the right w/h ratio */ + if (cam->largeview && !(cam->vpp_flag & VPP_UPSCALE)) { + cw = (fw >= fh) ? cam->maxwidth : __SC(win.width)/fh; + ch = (fw >= fh) ? __SC(win.height)/fw : cam->maxheight; + } else { + cw = w; + ch = h; + } + + /* Setup the sensor window */ + s_win.format = SENSOR_FORMAT; + s_win.width = cam->maxwidth; + s_win.height = cam->maxheight; + s_win.quarter = 0; /* full progressive video */ + + /* Center it */ + s_win.x = (s_win.width - cw) / 2; + s_win.y = (s_win.height - ch) / 2; + + /* Clock divisor */ + if (cam->clockdiv >= 0) + s_win.clockdiv = cam->clockdiv; /* manual override */ + else + switch (cam->sensor) { + case CC_OV6620: + s_win.clockdiv = 0; + break; + case CC_OV6630: + s_win.clockdiv = 0; + break; + case CC_OV76BE: + case CC_OV7610: + case CC_OV7620: + s_win.clockdiv = 0; + break; + default: + s_win.clockdiv = W9968CF_DEF_CLOCKDIVISOR; + } + + /* We have to scale win.x and win.y offsets */ + if ( (cam->largeview && !(cam->vpp_flag & VPP_UPSCALE)) + || (cam->vpp_flag & VPP_UPSCALE) ) { + ax = __SC(win.x)/fw; + ay = __SC(win.y)/fh; + } else { + ax = win.x; + ay = win.y; + } + + if ((ax + cw) > cam->maxwidth) + ax = cam->maxwidth - cw; + + if ((ay + ch) > cam->maxheight) + ay = cam->maxheight - ch; + + /* Adjust win.x, win.y */ + if ( (cam->largeview && !(cam->vpp_flag & VPP_UPSCALE)) + || (cam->vpp_flag & VPP_UPSCALE) ) { + win.x = __UNSC(ax*fw); + win.y = __UNSC(ay*fh); + } else { + win.x = ax; + win.y = ay; + } + + /* Offsets used by the chip */ + x = ax + s_win.x; + y = ay + s_win.y; + + /* Go ! */ + if ((rc = w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_S_MODE, &s_win))) + goto error; + + err += w9968cf_write_reg(cam, scx + x, 0x10); + err += w9968cf_write_reg(cam, scy + y, 0x11); + err += w9968cf_write_reg(cam, scx + x + cw, 0x12); + err += w9968cf_write_reg(cam, scy + y + ch, 0x13); + err += w9968cf_write_reg(cam, w, 0x14); + err += w9968cf_write_reg(cam, h, 0x15); + + /* JPEG width & height */ + err += w9968cf_write_reg(cam, w, 0x30); + err += w9968cf_write_reg(cam, h, 0x31); + + /* Y & UV frame buffer strides (in WORD) */ + if (cam->vpp_flag & VPP_DECOMPRESSION) { + err += w9968cf_write_reg(cam, w/2, 0x2c); + err += w9968cf_write_reg(cam, w/4, 0x2d); + } else + err += w9968cf_write_reg(cam, w, 0x2c); + + if (err) + goto error; + + /* If all went well, update the device data structure */ + memcpy(&cam->window, &win, sizeof(win)); + cam->hw_width = w; + cam->hw_height = h; + + /* Settings changed, so we clear the frame buffers */ + memset(cam->frame[0].buffer, 0, + cam->nbuffers*w9968cf_get_max_bufsize(cam)); + + DBG(4, "The capture area is %dx%d, Offset (x,y)=(%d,%d).", + win.width, win.height, win.x, win.y) + + PDBGG("x=%d ,y=%d, w=%d, h=%d, ax=%d, ay=%d, s_win.x=%d, s_win.y=%d, " + "cw=%d, ch=%d, win.x=%d ,win.y=%d, win.width=%d, win.height=%d", + x, y, w, h, ax, ay, s_win.x, s_win.y, cw, ch, win.x, win.y, + win.width, win.height) + + return 0; + +error: + DBG(1, "Failed to change the capture area size.") + if (err) + return err; + else + return rc; +} + + +/*-------------------------------------------------------------------------- + Return non-zero if the palette is supported, 0 otherwise. + --------------------------------------------------------------------------*/ +static inline u16 w9968cf_valid_palette(u16 palette) +{ + u8 i = 0; + while (w9968cf_formatlist[i].palette != 0) { + if (palette == w9968cf_formatlist[i].palette) + return palette; + i++; + } + return 0; +} + + +/*-------------------------------------------------------------------------- + Return the depth corresponding to the given palette. + Palette _must_ be supported ! + --------------------------------------------------------------------------*/ +static inline u16 w9968cf_valid_depth(u16 palette) +{ + u8 i=0; + while (w9968cf_formatlist[i].palette != palette) + i++; + + return w9968cf_formatlist[i].depth; +} + + +/*-------------------------------------------------------------------------- + Return non-zero if the format requires decompression, 0 otherwise. + --------------------------------------------------------------------------*/ +static inline u8 w9968cf_need_decompression(u16 palette) +{ + u8 i = 0; + while (w9968cf_formatlist[i].palette != 0) { + if (palette == w9968cf_formatlist[i].palette) + return w9968cf_formatlist[i].compression; + i++; + } + return 0; +} + + +/*-------------------------------------------------------------------------- + Adjust the asked values for window width and height. + Return 0 on success, -1 otherwise. + --------------------------------------------------------------------------*/ +static int +w9968cf_adjust_window_size(struct w9968cf_device* cam, u16* width, u16* height) +{ + u16 maxw, maxh; + + if ((*width < cam->minwidth) || (*height < cam->minheight)) + return -ERANGE; + + maxw = cam->upscaling && !(cam->vpp_flag & VPP_DECOMPRESSION) + && w9968cf_vppmod_present ? W9968CF_MAX_WIDTH : cam->maxwidth; + maxh = cam->upscaling && !(cam->vpp_flag & VPP_DECOMPRESSION) + && w9968cf_vppmod_present ? W9968CF_MAX_HEIGHT : cam->maxheight; + + if (*width > maxw) + *width = maxw; + if (*height > maxh) + *height = maxh; + + if (cam->vpp_flag & VPP_DECOMPRESSION) { + *width &= ~15L; /* multiple of 16 */ + *height &= ~15L; + } + + PDBGG("Window size adjusted w=%d, h=%d ", *width, *height) + + return 0; +} + + +/*-------------------------------------------------------------------------- + Initialize the FIFO list of requested frames. + --------------------------------------------------------------------------*/ +static void w9968cf_init_framelist(struct w9968cf_device* cam) +{ + u8 i; + + for (i = 0; i < cam->nbuffers; i++) { + cam->requested_frame[i] = NULL; + cam->frame[i].queued = 0; + cam->frame[i].status = F_UNUSED; + } +} + + +/*-------------------------------------------------------------------------- + Add a frame in the FIFO list of requested frames. + This function is called in process context. + --------------------------------------------------------------------------*/ +static void w9968cf_push_frame(struct w9968cf_device* cam, u8 f_num) +{ + u8 f; + unsigned long lock_flags; + + spin_lock_irqsave(&cam->flist_lock, lock_flags); + + for (f=0; cam->requested_frame[f] != NULL; f++); + cam->requested_frame[f] = &cam->frame[f_num]; + cam->frame[f_num].queued = 1; + cam->frame[f_num].status = F_UNUSED; /* clear the status */ + + spin_unlock_irqrestore(&cam->flist_lock, lock_flags); + + DBG(6, "Frame #%d pushed into the FIFO list. Position %d.", f_num, f) +} + + +/*-------------------------------------------------------------------------- + Read, store and remove the first pointer in the FIFO list of requested + frames. This function is called in interrupt context. + --------------------------------------------------------------------------*/ +static void +w9968cf_pop_frame(struct w9968cf_device* cam, struct w9968cf_frame_t** framep) +{ + u8 i; + + spin_lock(&cam->flist_lock); + + *framep = cam->requested_frame[0]; + + /* Shift the list of pointers */ + for (i = 0; i < cam->nbuffers-1; i++) + cam->requested_frame[i] = cam->requested_frame[i+1]; + cam->requested_frame[i] = NULL; + + spin_unlock(&cam->flist_lock); + + DBG(6,"Popped frame #%d from the list.",*framep-&cam->frame[0]) +} + + +/*-------------------------------------------------------------------------- + High-level video post-processing routine on grabbed frames. + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int +w9968cf_postprocess_frame(struct w9968cf_device* cam, + struct w9968cf_frame_t* fr) +{ + void *pIn = fr->buffer, *pOut = cam->vpp_buffer, *tmp; + u16 w = cam->window.width, + h = cam->window.height, + d = cam->picture.depth, + fmt = cam->picture.palette, + rgb = cam->force_rgb, + hw_w = cam->hw_width, + hw_h = cam->hw_height, + hw_d = cam->hw_depth; + int err = 0; + + #define _PSWAP(pIn, pOut) {tmp = (pIn); (pIn) = (pOut); (pOut) = tmp;} + + if (cam->vpp_flag & VPP_DECOMPRESSION) { + memcpy(pOut, pIn, fr->length); + _PSWAP(pIn, pOut) + err = (*w9968cf_vpp_decode)(pIn, fr->length, hw_w, hw_h, pOut); + PDBGG("Compressed frame length: %li",(unsigned long)fr->length) + fr->length = (hw_w*hw_h*hw_d)/8; + _PSWAP(pIn, pOut) + if (err) { + DBG(4, "An error occurred while decoding the frame: " + "%s.", symbolic(decoder_errlist, err)) + return err; + } else + DBG(6, "Frame decoded") + } + + if (cam->vpp_flag & VPP_SWAP_YUV_BYTES) { + (*w9968cf_vpp_swap_yuvbytes)(pIn, fr->length); + DBG(6, "Original UYVY component ordering changed.") + } + + if (cam->vpp_flag & VPP_UPSCALE) { + (*w9968cf_vpp_scale_up)(pIn, pOut, hw_w, hw_h, hw_d, w, h); + fr->length = (w*h*hw_d)/8; + _PSWAP(pIn, pOut) + DBG(6, "Vertical up-scaling done: %d,%d,%dbpp->%d,%d", + hw_w, hw_h, hw_d, w, h) + } + + if (cam->vpp_flag & VPP_UYVY_TO_RGBX) { + (*w9968cf_vpp_uyvy_to_rgbx)(pIn, fr->length, pOut, fmt, rgb); + fr->length = (w*h*d)/8; + _PSWAP(pIn, pOut) + DBG(6, "UYVY-16bit to %s conversion done.", + symbolic(v4l1_plist, fmt)) + } + + if (pOut == fr->buffer) + memcpy(fr->buffer, cam->vpp_buffer, fr->length); + + return 0; +} + + + +/**************************************************************************** + * CMOS sensor control routines * + ****************************************************************************/ + +static int +w9968cf_sensor_set_control(struct w9968cf_device* cam, int cid, int val) +{ + struct ovcamchip_control ctl; + int rc; + + ctl.id = cid; + ctl.value = val; + + rc = w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_S_CTRL, &ctl); + + return rc; +} + +static int +w9968cf_sensor_get_control(struct w9968cf_device* cam, int cid, int *val) +{ + struct ovcamchip_control ctl; + int rc; + + ctl.id = cid; + + rc = w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_G_CTRL, &ctl); + if (rc >= 0) + *val = ctl.value; + + return rc; +} + + +static inline int +w9968cf_sensor_cmd(struct w9968cf_device* cam, unsigned int cmd, void *arg) +{ + struct i2c_client* c = cam->sensor_client; + + DBG(6, "Executing CMOS sensor command...") + + if (c && c->driver->command) + return c->driver->command(cam->sensor_client, cmd, arg); + else + return -ENODEV; +} + + +/*-------------------------------------------------------------------------- + Change some settings of the CMOS sensor. + Returns: 0 for success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_sensor_change_settings(struct w9968cf_device* cam) +{ + int rc; + + /* Auto brightness */ + rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_AUTOBRIGHT, + cam->auto_brt); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + + /* Auto exposure */ + rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_AUTOEXP, + cam->auto_exp); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + + /* Banding filter */ + rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_BANDFILT, + cam->bandfilt); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + + /* Light frequency */ + rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_FREQ, + cam->lightfreq); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + + /* Back light */ + rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_BACKLIGHT, + cam->backlight); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + + /* Mirror */ + rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_MIRROR, + cam->mirror); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + + return 0; +} + + +/*-------------------------------------------------------------------------- + Get some current picture settings from the CMOS sensor. + Returns: 0 for success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int +w9968cf_sensor_get_picture(struct w9968cf_device* cam, + struct video_picture* pict) +{ + int rc, v; + + /* Don't return error if a setting is unsupported, or rest of settings + will not be performed */ + + rc = w9968cf_sensor_get_control(cam, OVCAMCHIP_CID_CONT, &v); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + pict->contrast = v; + + rc = w9968cf_sensor_get_control(cam, OVCAMCHIP_CID_BRIGHT, &v); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + pict->brightness = v; + + rc = w9968cf_sensor_get_control(cam, OVCAMCHIP_CID_SAT, &v); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + pict->colour = v; + + rc = w9968cf_sensor_get_control(cam, OVCAMCHIP_CID_HUE, &v); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + pict->hue = v; + + pict->whiteness = W9968CF_WHITENESS; /* to do! */ + + DBG(5, "Got picture settings from the CMOS sensor.") + + PDBGG("Brightness, contrast, hue, colour, whiteness are " + "%d,%d,%d,%d,%d.", pict->brightness, pict->contrast, + pict->hue, pict->colour, pict->whiteness) + + return 0; +} + + +/*-------------------------------------------------------------------------- + Change picture settings of the CMOS sensor. + Returns: 0 for success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int +w9968cf_sensor_set_picture(struct w9968cf_device* cam, + struct video_picture pict) +{ + int rc; + + rc = w9968cf_sensor_set_control(cam,OVCAMCHIP_CID_CONT, pict.contrast); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + + if (!cam->auto_brt) { + rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_BRIGHT, + pict.brightness); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + } + + rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_SAT, pict.colour); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + + rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_HUE, pict.hue); + if (SENSOR_FATAL_ERROR(rc)) + return rc; + + PDBGG("Brightness, contrast, hue, colour, whiteness are " + "%d,%d,%d,%d,%d.", pict.brightness, pict.contrast, + pict.hue, pict.colour, pict.whiteness) + + return 0; +} + + + +/**************************************************************************** + * Camera configuration * + ****************************************************************************/ + +/*-------------------------------------------------------------------------- + This function is called when the CMOS sensor is detected. + --------------------------------------------------------------------------*/ +static void w9968cf_sensor_configure(struct w9968cf_device* cam) +{ + /* NOTE: Make sure width and height are a multiple of 16 */ + + switch (cam->sensor_client->addr) { + case OV6xx0_SID: + cam->maxwidth = 352; + cam->maxheight = 288; + cam->minwidth = 64; + cam->minheight = 48; + break; + case OV7xx0_SID: + cam->maxwidth = 640; + cam->maxheight = 480; + cam->minwidth = 64; + cam->minheight = 48; + break; + } + + /* These values depend on the ones in the ovxxx0.c sources */ + switch (cam->sensor) { + case CC_OV7620: + cam->start_cropx = 287; + cam->start_cropy = 35; + /* Seems to work around a bug in the CMOS sensor */ + cam->vs_polarity = 1; + cam->hs_polarity = 1; + break; + default: + cam->start_cropx = 320; + cam->start_cropy = 35; + cam->vs_polarity = 1; + cam->hs_polarity = 0; + } + + DBG(5, "CMOS sensor %s configured.", symbolic(senlist, cam->sensor)) +} + + +/*-------------------------------------------------------------------------- + Fill some basic fields in the main device data structure. + This function is called once on w9968cf_usb_probe() for each recognized + camera. + --------------------------------------------------------------------------*/ +static void +w9968cf_configure_camera(struct w9968cf_device* cam, + struct usb_device* udev, + enum w9968cf_model_id mod_id, + const unsigned short dev_nr) +{ +#if defined(CONFIG_VIDEO_PROC_FS) + init_MUTEX(&cam->procfs_sem); +#endif + init_MUTEX(&cam->fileop_sem); + init_waitqueue_head(&cam->open); + spin_lock_init(&cam->urb_lock); + spin_lock_init(&cam->flist_lock); + + cam->users = 0; + cam->disconnected = 0; + cam->usbdev = udev; + cam->id = mod_id; + cam->sensor = CC_UNKNOWN; + + strcpy(cam->v4ldev.name, symbolic(camlist, mod_id)); + cam->v4ldev.type = VID_TYPE_CAPTURE | VID_TYPE_SCALES; + cam->v4ldev.hardware = VID_HARDWARE_W9968CF; + cam->v4ldev.fops = &w9968cf_fops; + cam->v4ldev.priv = (void*)cam; + cam->v4ldev.minor = video_nr[dev_nr]; + + /* Calculate the alternate setting number (from 1 to 16) + according to the 'packet_size' module parameter */ + if (packet_size[dev_nr] < W9968CF_MIN_PACKET_SIZE) + packet_size[dev_nr] = W9968CF_MIN_PACKET_SIZE; + for (cam->altsetting = 1; + packet_size[dev_nr] < wMaxPacketSize[cam->altsetting-1]; + cam->altsetting++); + + cam->max_buffers = (max_buffers[dev_nr] < 2 || + max_buffers[dev_nr] > W9968CF_MAX_BUFFERS) + ? W9968CF_BUFFERS : max_buffers[dev_nr]; + + cam->double_buffer = (double_buffer[dev_nr] == 0 || + double_buffer[dev_nr] == 1) + ? double_buffer[dev_nr] : W9968CF_DOUBLE_BUFFER; + + cam->clamping = (clamping[dev_nr] == 0 || clamping[dev_nr] == 1) + ? clamping[dev_nr] : W9968CF_CLAMPING; + + cam->filter_type = (filter_type[dev_nr] == 0 || + filter_type[dev_nr] == 1 || + filter_type[dev_nr] == 2) + ? filter_type[dev_nr] : W9968CF_FILTER_TYPE; + + cam->capture = 1; + + cam->largeview = (largeview[dev_nr] == 0 || largeview[dev_nr] == 1) + ? largeview[dev_nr] : W9968CF_LARGEVIEW; + + cam->decompression = (decompression[dev_nr] == 0 || + decompression[dev_nr] == 1 || + decompression[dev_nr] == 2) + ? decompression[dev_nr] : W9968CF_DECOMPRESSION; + + cam->upscaling = (upscaling[dev_nr] == 0 || + upscaling[dev_nr] == 1) + ? upscaling[dev_nr] : W9968CF_UPSCALING; + + cam->auto_brt = (autobright[dev_nr] == 0 || autobright[dev_nr] == 1) + ? autobright[dev_nr] : W9968CF_AUTOBRIGHT; + + cam->auto_exp = (autoexp[dev_nr] == 0 || autoexp[dev_nr] == 1) + ? autoexp[dev_nr] : W9968CF_AUTOEXP; + + cam->lightfreq = (lightfreq[dev_nr] == 50 || lightfreq[dev_nr] == 60) + ? lightfreq[dev_nr] : W9968CF_LIGHTFREQ; + + cam->bandfilt = (bandingfilter[dev_nr] == 0 || + bandingfilter[dev_nr] == 1) + ? bandingfilter[dev_nr] : W9968CF_BANDINGFILTER; + + cam->backlight = (backlight[dev_nr] == 0 || backlight[dev_nr] == 1) + ? backlight[dev_nr] : W9968CF_BACKLIGHT; + + cam->clockdiv = (clockdiv[dev_nr] == -1 || clockdiv[dev_nr] >= 0) + ? clockdiv[dev_nr] : W9968CF_CLOCKDIV; + + cam->mirror = (mirror[dev_nr] == 0 || mirror[dev_nr] == 1) + ? mirror[dev_nr] : W9968CF_MIRROR; + + cam->sensor_mono = (sensor_mono[dev_nr]==0 || sensor_mono[dev_nr]==1) + ? sensor_mono[dev_nr] : W9968CF_SENSOR_MONO; + + cam->picture.brightness = brightness[dev_nr]; + cam->picture.hue = hue[dev_nr]; + cam->picture.colour = colour[dev_nr]; + cam->picture.contrast = contrast[dev_nr]; + cam->picture.whiteness = whiteness[dev_nr]; + if (w9968cf_valid_palette(force_palette[dev_nr])) { + cam->picture.palette = force_palette[dev_nr]; + cam->force_palette = 1; + } else { + cam->force_palette = 0; + if (cam->decompression == 0) + cam->picture.palette = W9968CF_PALETTE_DECOMP_OFF; + else if (cam->decompression == 1) + cam->picture.palette = W9968CF_PALETTE_DECOMP_FORCE; + else + cam->picture.palette = W9968CF_PALETTE_DECOMP_ON; + } + + cam->force_rgb = (force_rgb[dev_nr] == 0 || force_rgb[dev_nr] == 1) + ? force_rgb[dev_nr] : W9968CF_FORCE_RGB; + + cam->window.x = 0; + cam->window.y = 0; + cam->window.width = W9968CF_WIDTH; + cam->window.height = W9968CF_HEIGHT; + cam->window.chromakey = 0; + cam->window.clipcount = 0; + cam->window.flags = 0; + + /* If the video post-processing module is not present, some paramaters + must be overridden: */ + if (!w9968cf_vppmod_present) { + if (cam->decompression == 1) + cam->decompression = 2; + cam->upscaling = 0; + if (cam->picture.palette != VIDEO_PALETTE_UYVY) + cam->force_palette = 0; + cam->picture.palette = VIDEO_PALETTE_UYVY; + } + + cam->picture.depth = w9968cf_valid_depth(cam->picture.palette); + + DBG(3, "%s configured with settings #%d:", + symbolic(camlist, cam->id), dev_nr) + + DBG(3, "- Data packet size for USB isochrnous transfer: %d bytes.", + wMaxPacketSize[cam->altsetting-1]) + + DBG(3, "- Number of requested video frame buffers: %d", + cam->max_buffers) + + if (cam->double_buffer) + DBG(3, "- Hardware double buffering enabled.") + else + DBG(3, "- Hardware double buffering disabled.") + + if (cam->filter_type == 0) + DBG(3, "- Video filtering disabled.") + else if (cam->filter_type == 1) + DBG(3, "- Video filtering enabled: type 1-2-1.") + else if (cam->filter_type == 2) + DBG(3, "- Video filtering enabled: type 2-3-6-3-2.") + + if (cam->clamping) + DBG(3, "- Video data clamping (CCIR-601 format) enabled.") + else + DBG(3, "- Video data clamping (CCIR-601 format) disabled.") + + if (cam->largeview) + DBG(3, "- Large view enabled.") + else + DBG(3, "- Large view disabled.") + + if ((cam->decompression) == 0 && (!cam->force_palette)) + DBG(3, "- Decompression disabled.") + else if ((cam->decompression) == 1 && (!cam->force_palette)) + DBG(3, "- Decompression forced.") + else if ((cam->decompression) == 2 && (!cam->force_palette)) + DBG(3, "- Decompression allowed.") + + if (cam->upscaling) + DBG(3, "- Software image scaling enabled.") + else + DBG(3, "- Software image scaling disabled.") + + if (cam->force_palette) + DBG(3, "- Image palette forced to %s.", + symbolic(v4l1_plist, cam->picture.palette)) + + if (cam->force_rgb) + DBG(3, "- RGB component ordering will be used instead of BGR.") + + if (cam->auto_brt) + DBG(3, "- Auto brightness enabled.") + else + DBG(3, "- Auto brightness disabled.") + + if (cam->auto_exp) + DBG(3, "- Auto exposure enabled.") + else + DBG(3, "- Auto exposure disabled.") + + if (cam->backlight) + DBG(3, "- Backlight exposure algorithm enabled.") + else + DBG(3, "- Backlight exposure algorithm disabled.") + + if (cam->mirror) + DBG(3, "- Mirror enabled.") + else + DBG(3, "- Mirror disabled.") + + if (cam->bandfilt) + DBG(3, "- Banding filter enabled.") + else + DBG(3, "- Banding filter disabled.") + + DBG(3, "- Power lighting frequency: %d", cam->lightfreq) + + if (cam->clockdiv == -1) + DBG(3, "- Automatic clock divisor enabled.") + else + DBG(3, "- Clock divisor: %d", cam->clockdiv) + + if (cam->sensor_mono) + DBG(3, "- CMOS sensor used as monochrome.") + else + DBG(3, "- CMOS sensor not used as monochrome.") +} + + +/*-------------------------------------------------------------------------- + Release the resources used by the driver. + This function is called on disconnect + (or on close if deallocation has been deferred) + --------------------------------------------------------------------------*/ +static void w9968cf_release_resources(struct w9968cf_device* cam) +{ + down(&w9968cf_devlist_sem); + + DBG(2, "V4L device deregistered: /dev/video%d", cam->v4ldev.minor) + + w9968cf_proc_destroy_dev(cam); + video_unregister_device(&cam->v4ldev); + list_del(&cam->v4llist); + i2c_del_adapter(&cam->i2c_adapter); + w9968cf_deallocate_memory(cam); + kfree(cam->control_buffer); + kfree(cam->data_buffer); + + up(&w9968cf_devlist_sem); + + DBG(5, "Resources released.") +} + + + +/**************************************************************************** + * Video4Linux interface * + ****************************************************************************/ + +static int w9968cf_open(struct inode* inode, struct file* filp) +{ + struct w9968cf_device* cam = + (struct w9968cf_device*)video_devdata(filp)->priv; + int err; + + down(&cam->dev_sem); + + if (cam->sensor == CC_UNKNOWN) { + DBG(2, "No supported CMOS sensor has been detected by the " + "'ovcamchip' module for the %s (/dev/video%d). Make " + "sure it is loaded *before* the 'w9968cf' module.", + symbolic(camlist, cam->id),cam->v4ldev.minor) + up(&cam->dev_sem); + return -ENODEV; + } + + if (cam->users) { + DBG(2, "%s (/dev/video%d) has been already occupied by '%s'.", + symbolic(camlist, cam->id),cam->v4ldev.minor, cam->command) + if ((filp->f_flags & O_NONBLOCK)||(filp->f_flags & O_NDELAY)) { + up(&cam->dev_sem); + return -EWOULDBLOCK; + } + up(&cam->dev_sem); + err = wait_event_interruptible(cam->open, cam->disconnected || + (cam->users == 0)); + if (err) + return err; + if (cam->disconnected) + return -ENODEV; + down(&cam->dev_sem); + } + + DBG(5, "Opening the %s, /dev/video%d ...", + symbolic(camlist, cam->id), cam->v4ldev.minor) + + cam->streaming = 0; + cam->misconfigured = 0; + + if (!w9968cf_vppmod_present) + w9968cf_vppmod_detect(); + + if ((err = w9968cf_allocate_memory(cam))) + goto deallocate_memory; + + if ((err = w9968cf_init_chip(cam))) + goto deallocate_memory; + + if ((err = w9968cf_start_transfer(cam))) + goto deallocate_memory; + + filp->private_data = (void*)cam; + + cam->users++; + strcpy(cam->command, current->comm); + + init_waitqueue_head(&cam->wait_queue); + + up(&cam->dev_sem); + + DBG(5, "Video device is open.") + return 0; + +deallocate_memory: + w9968cf_deallocate_memory(cam); + DBG(2, "Failed to open the video device.") + up(&cam->dev_sem); + return err; +} + + +static int w9968cf_release(struct inode* inode, struct file* filp) +{ + struct w9968cf_device* cam = + (struct w9968cf_device*)video_devdata(filp)->priv; + + down(&cam->dev_sem); /* prevent disconnect() to be called */ + + w9968cf_stop_transfer(cam); + + if (cam->disconnected) { + w9968cf_release_resources(cam); + up(&cam->dev_sem); + kfree(cam); + return 0; + } + + cam->users--; + w9968cf_deallocate_memory(cam); + + if (waitqueue_active(&cam->open)) + wake_up_interruptible(&cam->open); + + DBG(5, "Video device closed.") + up(&cam->dev_sem); + return 0; +} + + +static ssize_t +w9968cf_read(struct file* filp, char* buf, size_t count, loff_t* f_pos) +{ + struct w9968cf_device* cam = + (struct w9968cf_device*)video_devdata(filp)->priv; + struct w9968cf_frame_t* fr; + int err = 0; + + if (filp->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + + if (down_interruptible(&cam->fileop_sem)) + return -ERESTARTSYS; + + if (cam->disconnected) { + DBG(2, "Device not present.") + up(&cam->fileop_sem); + return -ENODEV; + } + + if (cam->misconfigured) { + DBG(2, "The camera is misconfigured. Close and open it again.") + up(&cam->fileop_sem); + return -EIO; + } + + if (!cam->frame[0].queued) + w9968cf_push_frame(cam, 0); + + if (!cam->frame[1].queued) + w9968cf_push_frame(cam, 1); + + err = wait_event_interruptible(cam->wait_queue, + cam->frame[0].status == F_READY || + cam->frame[1].status == F_READY || + cam->disconnected); + if (err) { + up(&cam->fileop_sem); + return err; + } + if (cam->disconnected) { + up(&cam->fileop_sem); + return -ENODEV; + } + + fr = (cam->frame[0].status == F_READY) ? &cam->frame[0]:&cam->frame[1]; + + if (w9968cf_vppmod_present) + w9968cf_postprocess_frame(cam, fr); + + if (count > fr->length) + count = fr->length; + + if (copy_to_user(buf, fr->buffer, count)) { + fr->status = F_UNUSED; + up(&cam->fileop_sem); + return -EFAULT; + } + *f_pos += count; + + fr->status = F_UNUSED; + + DBG(5, "%d bytes read.", count) + + up(&cam->fileop_sem); + return count; +} + + +static int w9968cf_mmap(struct file* filp, struct vm_area_struct *vma) +{ + struct w9968cf_device* cam = + (struct w9968cf_device*)video_devdata(filp)->priv; + + unsigned long vsize = vma->vm_end - vma->vm_start, + psize = cam->nbuffers * w9968cf_get_max_bufsize(cam), + start = vma->vm_start, + pos = (unsigned long)cam->frame[0].buffer, + page; + + if (cam->disconnected) { + DBG(2, "Device not present.") + return -ENODEV; + } + + if (cam->misconfigured) { + DBG(2, "The camera is misconfigured. Close and open it again.") + return -EIO; + } + + PDBGG("mmapping %li bytes...", vsize) + + if (vsize > psize - (vma->vm_pgoff << PAGE_SHIFT)) + return -EAGAIN; + + while (vsize > 0) { + page = kvirt_to_pa(pos) + vma->vm_pgoff; + if(remap_page_range(start, page, PAGE_SIZE, vma->vm_page_prot)) + return -EAGAIN; + start += PAGE_SIZE; + pos += PAGE_SIZE; + vsize = (vsize > PAGE_SIZE) ? vsize-PAGE_SIZE : 0; + } + + DBG(5, "mmap method successfully called.") + return 0; +} + + +static int +w9968cf_ioctl(struct inode* inode, struct file* filp, + unsigned int cmd, unsigned long arg) +{ + struct w9968cf_device* cam = + (struct w9968cf_device*)video_devdata(filp)->priv; + int err; + + if (down_interruptible(&cam->fileop_sem)) + return -ERESTARTSYS; + + if (cam->disconnected) { + DBG(2, "Device not present.") + up(&cam->fileop_sem); + return -ENODEV; + } + + if (cam->misconfigured) { + DBG(2, "The camera is misconfigured. Close and open it again.") + up(&cam->fileop_sem); + return -EIO; + } + + err = w9968cf_do_ioctl(cam, cmd, (void*)arg); + + up(&cam->fileop_sem); + return err; +} + + +static int +w9968cf_do_ioctl(struct w9968cf_device* cam, unsigned cmd, void* arg) +{ + const char* v4l1_ioctls[] = { + "?", "CGAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", + "GPICT", "SPICT", "CCAPTURE", "GWIN", "SWIN", "GFBUF", + "SFBUF", "KEY", "GFREQ", "SFREQ", "GAUDIO", "SAUDIO", + "SYNC", "MCAPTURE", "GMBUF", "GUNIT", "GCAPTURE", "SCAPTURE", + "SPLAYMODE", "SWRITEMODE", "GPLAYINFO", "SMICROCODE", + "GVBIFMT", "SVBIFMT" + }; + + #define V4L1_IOCTL(cmd) \ + ((_IOC_NR((cmd)) < sizeof(v4l1_ioctls)/sizeof(char*)) ? \ + v4l1_ioctls[_IOC_NR((cmd))] : "???") + + switch (cmd) { + + case VIDIOCGCAP: /* get video capability */ + { + struct video_capability cap = { + .type = VID_TYPE_CAPTURE | VID_TYPE_SCALES, + .channels = 1, + .audios = 0, + .minwidth = cam->minwidth, + .minheight = cam->minheight, + }; + sprintf(cap.name, "W996[87]CF USB Camera #%d", + cam->v4ldev.minor); + cap.maxwidth = (cam->upscaling && w9968cf_vppmod_present) + ? W9968CF_MAX_WIDTH : cam->maxwidth; + cap.maxheight = (cam->upscaling && w9968cf_vppmod_present) + ? W9968CF_MAX_HEIGHT : cam->maxheight; + + if (copy_to_user(arg, &cap, sizeof(cap))) + return -EFAULT; + + DBG(5, "VIDIOCGCAP successfully called.") + return 0; + } + + case VIDIOCGCHAN: /* get video channel informations */ + { + struct video_channel chan; + if (copy_from_user(&chan, arg, sizeof(chan))) + return -EFAULT; + + if (chan.channel != 0) + return -EINVAL; + + strcpy(chan.name, "Camera"); + chan.tuners = 0; + chan.flags = 0; + chan.type = VIDEO_TYPE_CAMERA; + chan.norm = VIDEO_MODE_AUTO; + + if (copy_to_user(arg, &chan, sizeof(chan))) + return -EFAULT; + + DBG(5, "VIDIOCGCHAN successfully called.") + return 0; + } + + case VIDIOCSCHAN: /* set active channel */ + { + struct video_channel chan; + + if (copy_from_user(&chan, arg, sizeof(chan))) + return -EFAULT; + + if (chan.channel != 0) + return -EINVAL; + + DBG(5, "VIDIOCSCHAN successfully called.") + return 0; + } + + case VIDIOCGPICT: /* get image properties of the picture */ + { + struct video_picture pict; + + if (w9968cf_sensor_get_picture(cam, &pict)) + return -EIO; + + pict.depth = cam->picture.depth; + pict.palette = cam->picture.palette; + + if (copy_to_user(arg, &pict, sizeof(pict))) + return -EFAULT; + + DBG(5, "VIDIOCGPICT successfully called.") + return 0; + } + + case VIDIOCSPICT: /* change picture settings */ + { + struct video_picture pict; + int err = 0; + + if (copy_from_user(&pict, arg, sizeof(pict))) + return -EFAULT; + + if ( (cam->force_palette || !w9968cf_vppmod_present) + && pict.palette != cam->picture.palette ) { + DBG(4, "Palette %s rejected. Only %s is allowed.", + symbolic(v4l1_plist, pict.palette), + symbolic(v4l1_plist, cam->picture.palette)) + return -EINVAL; + } + + if (!w9968cf_valid_palette(pict.palette)) { + DBG(4, "Palette %s not supported. VIDIOCSPICT failed.", + symbolic(v4l1_plist, pict.palette)) + return -EINVAL; + } + + if (pict.depth != w9968cf_valid_depth(pict.palette)) { + DBG(4, "Depth %d bpp is not supported for %s palette. " + "VIDIOCSPICT failed.", + pict.depth, symbolic(v4l1_plist, pict.palette)) + return -EINVAL; + } + + if (!cam->force_palette) { + if (cam->decompression == 0) { + if (w9968cf_need_decompression(pict.palette)) { + DBG(4, "Decompression disabled: palette %s is not " + "allowed. VIDIOCSPICT failed.", + symbolic(v4l1_plist, pict.palette)) + return -EINVAL; + } + } else if (cam->decompression == 1) { + if (!w9968cf_need_decompression(pict.palette)) { + DBG(4, "Decompression forced: palette %s is not " + "allowed. VIDIOCSPICT failed.", + symbolic(v4l1_plist, pict.palette)) + return -EINVAL; + } + } + } + + if (pict.palette != cam->picture.palette || + pict.depth != cam->picture.depth) + { + if(*cam->requested_frame + || cam->frame_current->queued) { + err = wait_event_interruptible + ( cam->wait_queue, + cam->disconnected || + (!*cam->requested_frame && + !cam->frame_current->queued) ); + if (err) + return err; + if (cam->disconnected) + return -ENODEV; + } + + if (w9968cf_stop_transfer(cam)) + goto ioctl_fail; + + if (w9968cf_set_picture(cam, pict)) + goto ioctl_fail; + + if (w9968cf_start_transfer(cam)) + goto ioctl_fail; + + } else if ( ((pict.brightness != cam->picture.brightness) && + (!cam->auto_brt)) || + pict.hue != cam->picture.hue || + pict.colour != cam->picture.colour || + pict.contrast != cam->picture.contrast || + pict.whiteness != cam->picture.whiteness ) { + if (w9968cf_sensor_set_picture(cam, pict)) + return -EIO; + } + + DBG(5, "VIDIOCSPICT successfully called.") + return 0; + } + + case VIDIOCSWIN: /* set capture area */ + { + struct video_window win; + int err = 0; + + if (copy_from_user(&win, arg, sizeof(win))) + return -EFAULT; + + DBG(6, "VIDIOCSWIN called: clipcount=%d, flags=%d, " + "x=%d, y=%d, %dx%d", win.clipcount, win.flags, + win.x, win.y, win.width, win.height) + + if (win.clipcount != 0 || win.flags != 0) + return -EINVAL; + + if ((err = w9968cf_adjust_window_size(cam, (u16*)&win.width, + (u16*)&win.height))) { + DBG(4, "Resolution not supported (%dx%d)." + "VIDIOCSWIN failed.", win.width, win.height) + return err; + } + + if (win.x != cam->window.x || + win.y != cam->window.y || + win.width != cam->window.width || + win.height != cam->window.height) { + + if(*cam->requested_frame + || cam->frame_current->queued) { + err = wait_event_interruptible + ( cam->wait_queue, + cam->disconnected || + (!*cam->requested_frame && + !cam->frame_current->queued) ); + if (err) + return err; + if (cam->disconnected) + return -ENODEV; + } + + if (w9968cf_stop_transfer(cam)) + goto ioctl_fail; + + /* This _must_ be called before set_window() */ + if (w9968cf_set_picture(cam, cam->picture)) + goto ioctl_fail; + + if (w9968cf_set_window(cam, win)) + goto ioctl_fail; + + if (w9968cf_start_transfer(cam)) + goto ioctl_fail; + } + + DBG(5, "VIDIOCSWIN successfully called. ") + return 0; + } + + case VIDIOCGWIN: /* get current window properties */ + { + if (copy_to_user(arg,&cam->window,sizeof(struct video_window))) + return -EFAULT; + + DBG(5, "VIDIOCGWIN successfully called.") + return 0; + } + + case VIDIOCGMBUF: /* request for memory (mapped) buffer */ + { + struct video_mbuf mbuf; + u8 i; + + mbuf.size = cam->nbuffers * w9968cf_get_max_bufsize(cam); + mbuf.frames = cam->nbuffers; + for (i = 0; i < cam->nbuffers; i++) + mbuf.offsets[i] = (unsigned long)cam->frame[i].buffer - + (unsigned long)cam->frame[0].buffer; + + if (copy_to_user(arg, &mbuf, sizeof(mbuf))) + return -EFAULT; + + DBG(5, "VIDIOCGMBUF successfully called.") + return 0; + } + + case VIDIOCMCAPTURE: /* start the capture to a frame */ + { + struct video_mmap mmap; + struct w9968cf_frame_t* fr; + int err = 0; + + if (copy_from_user(&mmap, arg, sizeof(mmap))) + return -EFAULT; + + DBG(6, "VIDIOCMCAPTURE called: frame #%d, format=%s, %dx%d", + mmap.frame, symbolic(v4l1_plist, mmap.format), + mmap.width, mmap.height) + + if (mmap.frame >= cam->nbuffers) { + DBG(4, "Invalid frame number (%d). " + "VIDIOCMCAPTURE failed.", mmap.frame) + return -EINVAL; + } + + if (mmap.format!=cam->picture.palette && + (cam->force_palette || !w9968cf_vppmod_present)) { + DBG(4, "Palette %s rejected. Only %s is allowed.", + symbolic(v4l1_plist, mmap.format), + symbolic(v4l1_plist, cam->picture.palette)) + return -EINVAL; + } + + if (!w9968cf_valid_palette(mmap.format)) { + DBG(4, "Palette %s not supported. " + "VIDIOCMCAPTURE failed.", + symbolic(v4l1_plist, mmap.format)) + return -EINVAL; + } + + if (!cam->force_palette) { + if (cam->decompression == 0) { + if (w9968cf_need_decompression(mmap.format)) { + DBG(4, "Decompression disabled: palette %s is not " + "allowed. VIDIOCSPICT failed.", + symbolic(v4l1_plist, mmap.format)) + return -EINVAL; + } + } else if (cam->decompression == 1) { + if (!w9968cf_need_decompression(mmap.format)) { + DBG(4, "Decompression forced: palette %s is not " + "allowed. VIDIOCSPICT failed.", + symbolic(v4l1_plist, mmap.format)) + return -EINVAL; + } + } + } + + if (w9968cf_adjust_window_size(cam, (u16*)&mmap.width, + (u16*)&mmap.height)) { + DBG(4, "Resolution not supported (%dx%d). " + "VIDIOCMCAPTURE failed.", + mmap.width, mmap.height) + return -EINVAL; + } + + fr = &cam->frame[mmap.frame]; + + if (mmap.width != cam->window.width || + mmap.height != cam->window.height || + mmap.format != cam->picture.palette) { + + struct video_window win; + struct video_picture pict; + + if(*cam->requested_frame + || cam->frame_current->queued) { + DBG(6, "VIDIOCMCAPTURE. Change settings for " + "frame #%d: %dx%d, format %s. Wait...", + mmap.frame, mmap.width, mmap.height, + symbolic(v4l1_plist, mmap.format)) + err = wait_event_interruptible + ( cam->wait_queue, + cam->disconnected || + (!*cam->requested_frame && + !cam->frame_current->queued) ); + if (err) + return err; + if (cam->disconnected) + return -ENODEV; + } + + memcpy(&win, &cam->window, sizeof(win)); + memcpy(&pict, &cam->picture, sizeof(pict)); + win.width = mmap.width; + win.height = mmap.height; + pict.palette = mmap.format; + + if (w9968cf_stop_transfer(cam)) + goto ioctl_fail; + + /* This before set_window */ + if (w9968cf_set_picture(cam, pict)) + goto ioctl_fail; + + if (w9968cf_set_window(cam, win)) + goto ioctl_fail; + + if (w9968cf_start_transfer(cam)) + goto ioctl_fail; + + } else if (fr->queued) { + + DBG(6, "Wait until frame #%d is free.", mmap.frame) + + err = wait_event_interruptible(cam->wait_queue, + cam->disconnected || + (!fr->queued)); + if (err) + return err; + if (cam->disconnected) + return -ENODEV; + } + + w9968cf_push_frame(cam, mmap.frame); + DBG(5, "VIDIOCMCAPTURE(%d): successfully called.", mmap.frame) + return 0; + } + + case VIDIOCSYNC: /* wait until the capture of a frame is finished */ + { + unsigned int f_num = *((unsigned int *) arg); + struct w9968cf_frame_t* fr; + int err = 0; + + if (f_num >= cam->nbuffers) { + DBG(4, "Invalid frame number (%d). " + "VIDIOCMCAPTURE failed.", f_num) + return -EINVAL; + } + + DBG(6, "VIDIOCSYNC called for frame #%d", f_num) + + fr = &cam->frame[f_num]; + + switch (fr->status) { + case F_UNUSED: + if (!fr->queued) { + DBG(4, "VIDIOSYNC: Frame #%d not requested!", + f_num) + return -EFAULT; + } + case F_ERROR: + case F_GRABBING: + err = wait_event_interruptible(cam->wait_queue, + (fr->status == F_READY) + || cam->disconnected); + if (err) + return err; + if (cam->disconnected) + return -ENODEV; + break; + case F_READY: + break; + } + + if (w9968cf_vppmod_present) + w9968cf_postprocess_frame(cam, fr); + + fr->status = F_UNUSED; + + DBG(5, "VIDIOCSYNC(%d) successfully called.", f_num) + return 0; + } + + case VIDIOCGUNIT:/* report the unit numbers of the associated devices*/ + { + struct video_unit unit = { + .video = cam->v4ldev.minor, + .vbi = VIDEO_NO_UNIT, + .radio = VIDEO_NO_UNIT, + .audio = VIDEO_NO_UNIT, + .teletext = VIDEO_NO_UNIT, + }; + + if (copy_to_user(arg, &unit, sizeof(unit))) + return -EFAULT; + + DBG(5, "VIDIOCGUNIT successfully called.") + return 0; + } + + case VIDIOCKEY: + return 0; + + case VIDIOCGFBUF: + { + struct video_buffer* buffer = (struct video_buffer*)arg; + + memset(buffer, 0, sizeof(struct video_buffer)); + + DBG(5, "VIDIOCGFBUF successfully called.") + return 0; + } + + case VIDIOCGTUNER: + { + struct video_tuner tuner; + if (copy_from_user(&tuner, arg, sizeof(tuner))) + return -EFAULT; + + if (tuner.tuner != 0); + return -EINVAL; + + strcpy(tuner.name, "no_tuner"); + tuner.rangelow = 0; + tuner.rangehigh = 0; + tuner.flags = VIDEO_TUNER_NORM; + tuner.mode = VIDEO_MODE_AUTO; + tuner.signal = 0xffff; + + if (copy_to_user(arg, &tuner, sizeof(tuner))) + return -EFAULT; + + DBG(5, "VIDIOCGTUNER successfully called.") + return 0; + } + + case VIDIOCSTUNER: + { + struct video_tuner tuner; + if (copy_from_user(&tuner, arg, sizeof(tuner))) + return -EFAULT; + + if (tuner.tuner != 0) + return -EINVAL; + + if (tuner.mode != VIDEO_MODE_AUTO) + return -EINVAL; + + DBG(5, "VIDIOCSTUNER successfully called.") + return 0; + } + + case VIDIOCSFBUF: + case VIDIOCCAPTURE: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + case VIDIOCSPLAYMODE: + case VIDIOCSWRITEMODE: + case VIDIOCGPLAYINFO: + case VIDIOCSMICROCODE: + case VIDIOCGVBIFMT: + case VIDIOCSVBIFMT: + DBG(4, "Unsupported V4L1 IOCtl: VIDIOC%s " + "(type 0x%01X, " + "n. 0x%01X, " + "dir. 0x%01X, " + "size 0x%02X).", + V4L1_IOCTL(cmd), + _IOC_TYPE(cmd),_IOC_NR(cmd),_IOC_DIR(cmd),_IOC_SIZE(cmd)) + + return -EINVAL; + + default: + DBG(4, "Invalid V4L1 IOCtl: VIDIOC%s " + "type 0x%01X, " + "n. 0x%01X, " + "dir. 0x%01X, " + "size 0x%02X.", + V4L1_IOCTL(cmd), + _IOC_TYPE(cmd),_IOC_NR(cmd),_IOC_DIR(cmd),_IOC_SIZE(cmd)) + + return -ENOIOCTLCMD; + + } /* end of switch */ + +ioctl_fail: + cam->misconfigured = 1; + DBG(1, "VIDIOC%s failed because of hardware problems. " + "To use the camera, close and open it again.", V4L1_IOCTL(cmd)) + return -EFAULT; +} + + +static struct file_operations w9968cf_fops = { + .owner = THIS_MODULE, + .open = w9968cf_open, + .release = w9968cf_release, + .read = w9968cf_read, + .ioctl = w9968cf_ioctl, + .mmap = w9968cf_mmap, + .llseek = no_llseek, +}; + + + +/**************************************************************************** + * USB probe and V4L registration, disconnect and id_table[] definition * + ****************************************************************************/ + +static void* +w9968cf_usb_probe(struct usb_device* udev, + unsigned int ifnum, const struct usb_device_id* id) +{ + struct w9968cf_device* cam; + int err = 0; + enum w9968cf_model_id mod_id; + struct list_head* ptr; + u8 sc = 0; /* number of simultaneous cameras */ + static unsigned short dev_nr = 0; /* we are handling device number n */ + + if (udev->descriptor.idVendor == winbond_id_table[0].idVendor && + udev->descriptor.idProduct == winbond_id_table[0].idProduct) + mod_id = W9968CF_MOD_CLVBWGP; /* see camlist[] table */ + + else if (udev->descriptor.idVendor == winbond_id_table[1].idVendor && + udev->descriptor.idProduct == winbond_id_table[1].idProduct) + mod_id = W9968CF_MOD_GENERIC; /* see camlist[] table */ + + else + return NULL; + + /* We don't handle multi-config cameras */ + if (udev->descriptor.bNumConfigurations != 1) + return NULL; + + DBG(2, "%s detected.", symbolic(camlist, mod_id)) + + if (simcams > W9968CF_MAX_DEVICES) + simcams = W9968CF_SIMCAMS; + + /* How many cameras are connected ? */ + down(&w9968cf_devlist_sem); + list_for_each(ptr, &w9968cf_dev_list) + sc++; + up(&w9968cf_devlist_sem); + + if (sc >= simcams) { + DBG(2, "Device rejected: too many connected cameras " + "(max. %d)", simcams) + return NULL; + } + + err = usb_set_configuration(udev, 1); + err += usb_set_interface(udev, 0, 0); + + if (err) { + DBG(1, "Device configuration failed.") + return NULL; + } + + cam = (struct w9968cf_device*) + kmalloc(sizeof(struct w9968cf_device), GFP_KERNEL); + + if (!cam) { + DBG(1, "Couldn't allocate %d bytes of kernel memory.", + sizeof(struct w9968cf_device)) + err = -ENOMEM; + goto fail; + } + memset(cam, 0, sizeof(*cam)); + + init_MUTEX(&cam->dev_sem); + down(&cam->dev_sem); + + /* Allocate 2 bytes of memory for camera control USB transfers */ + if (!(cam->control_buffer = (u16*)kmalloc(2, GFP_KERNEL))) { + DBG(1,"Couldn't allocate memory for camera control transfers.") + err = -ENOMEM; + goto fail; + } + memset(cam->control_buffer, 0, 2); + + /* Allocate 8 bytes of memory for USB data transfers to the FSB */ + if (!(cam->data_buffer = (u16*)kmalloc(8, GFP_KERNEL))) { + DBG(1, "Couldn't allocate memory for data " + "transfers to the FSB.") + err = -ENOMEM; + goto fail; + } + memset(cam->data_buffer, 0, 8); + + /* Set some basic constants */ + w9968cf_configure_camera(cam, udev, mod_id, dev_nr); + + err = video_register_device(&cam->v4ldev, VFL_TYPE_GRABBER, + video_nr[dev_nr]); + if (err) { + DBG(1, "V4L device registration failed.") + if (err == -ENFILE && video_nr[dev_nr] == -1) + DBG(2, "Couldn't find a free /dev/videoX node.") + video_nr[dev_nr] = -1; + dev_nr = (dev_nr < W9968CF_MAX_DEVICES-1) ? dev_nr+1 : 0; + goto fail; + } + + DBG(2, "V4L device registered as /dev/video%d", cam->v4ldev.minor) + + /* Ok, add a new entry into the list of V4L registered devices */ + down(&w9968cf_devlist_sem); + list_add(&cam->v4llist, &w9968cf_dev_list); + up(&w9968cf_devlist_sem); + + dev_nr = (dev_nr < W9968CF_MAX_DEVICES-1) ? dev_nr+1 : 0; + + w9968cf_turn_on_led(cam); + + w9968cf_i2c_init(cam); + + w9968cf_proc_create_dev(cam); + + up(&cam->dev_sem); + return (void*)cam; + +fail: /* Free unused memory */ + if (cam) { + if (cam->control_buffer) + kfree(cam->control_buffer); + if (cam->data_buffer) + kfree(cam->data_buffer); + up(&cam->dev_sem); + kfree(cam); + } + return NULL; +} + + +static void w9968cf_usb_disconnect(struct usb_device* udev, void* drv_context) +{ + struct w9968cf_device* cam = (struct w9968cf_device*)drv_context; + + if (cam) { + /* Prevent concurrent accesses to data */ + down(&cam->dev_sem); + + cam->streaming = 0; + cam->disconnected = 1; + + DBG(2, "Disconnecting %s...", symbolic(camlist, cam->id)) + + if (waitqueue_active(&cam->open)) + wake_up_interruptible(&cam->open); + + if (cam->users) { + DBG(2, "The device is open (/dev/video%d)! " + "Process name: %s. Deregistration and memory " + "deallocation are deferred on close.", + cam->v4ldev.minor, cam->command) + + cam->misconfigured = 1; + + if (waitqueue_active(&cam->wait_queue)) + wake_up_interruptible(&cam->wait_queue); + } else + w9968cf_release_resources(cam); + + up(&cam->dev_sem); + + if (!cam->users) + kfree(cam); + } +} + + +static struct usb_driver w9968cf_usb_driver = { + .owner = THIS_MODULE, + .name = "w9968cf", + .id_table = winbond_id_table, + .probe = w9968cf_usb_probe, + .disconnect = w9968cf_usb_disconnect, +}; + + + +/**************************************************************************** + * Module init, exit and intermodule communication * + ****************************************************************************/ + +static int w9968cf_vppmod_detect(void) +{ + w9968cf_vpp_init_decoder = inter_module_get("w9968cf_init_decoder"); + + if (!w9968cf_vpp_init_decoder) { + if (vppmod_load) + w9968cf_vpp_init_decoder = inter_module_get_request + ( "w9968cf_init_decoder", + "w9968cf-vpp" ); + if (!w9968cf_vpp_init_decoder) { + w9968cf_vppmod_present = 0; + DBG(4, "Video post-processing module not detected.") + return -ENODEV; + } + } + + w9968cf_vpp_check_headers = inter_module_get("w9968cf_check_headers"); + w9968cf_vpp_decode = inter_module_get("w9968cf_decode"); + w9968cf_vpp_swap_yuvbytes = inter_module_get("w9968cf_swap_yuvbytes"); + w9968cf_vpp_uyvy_to_rgbx = inter_module_get("w9968cf_uyvy_to_rgbx"); + w9968cf_vpp_scale_up = inter_module_get("w9968cf_scale_up"); + + w9968cf_vppmod_present = 1; + + /* Initialization */ + (*w9968cf_vpp_init_decoder)(); + + DBG(2, "Video post-processing module detected.") + return 0; +} + + +static void w9968cf_vppmod_release(void) +{ + inter_module_put("w9968cf_init_decoder"); + inter_module_put("w9968cf_check_headers"); + inter_module_put("w9968cf_decode"); + inter_module_put("w9968cf_swap_yuvbytes"); + inter_module_put("w9968cf_uyvy_to_rgbx"); + inter_module_put("w9968cf_scale_up"); + + DBG(2, "Video post-processing module released.") +} + + +static int __init w9968cf_module_init(void) +{ + int err; + + DBG(2, W9968CF_MODULE_NAME" "W9968CF_MODULE_VERSION) + DBG(3, W9968CF_MODULE_AUTHOR) + + init_MUTEX(&w9968cf_devlist_sem); + + w9968cf_proc_create(); + + w9968cf_vppmod_detect(); + + if ((err = usb_register(&w9968cf_usb_driver))) { + if (w9968cf_vppmod_present) + w9968cf_vppmod_release(); + w9968cf_proc_destroy(); + return err; + } + + return 0; +} + + +static void __exit w9968cf_module_exit(void) +{ + /* w9968cf_usb_disconnect() will be called */ + usb_deregister(&w9968cf_usb_driver); + + w9968cf_proc_destroy(); + + if (w9968cf_vppmod_present) + w9968cf_vppmod_release(); + + DBG(2, W9968CF_MODULE_NAME" deregistered.") +} + + +module_init(w9968cf_module_init); +module_exit(w9968cf_module_exit); diff -Nru a/drivers/usb/w9968cf.h b/drivers/usb/w9968cf.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/w9968cf.h Fri Oct 24 17:01:10 2003 @@ -0,0 +1,321 @@ +/*************************************************************************** + * Video4Linux driver for W996[87]CF JPEG USB Dual Mode Camera Chip. * + * * + * Copyright (C) 2002 2003 by Luca Risolia * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#ifndef _W9968CF_H_ +#define _W9968CF_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "w9968cf_externaldef.h" + + +/**************************************************************************** + * Default values * + ****************************************************************************/ + +#define W9968CF_VPPMOD_LOAD 1 /* automatic 'w9968cf-vpp' module loading */ + +/* Comment/uncomment the following line to enable/disable debugging messages */ +#define W9968CF_DEBUG + +/* These have effect only if W9968CF_DEBUG is defined */ +#define W9968CF_DEBUG_LEVEL 2 /* from 0 to 6. 0 for no debug informations */ +#define W9968CF_SPECIFIC_DEBUG 0 /* 0 or 1 */ + +#define W9968CF_MAX_DEVICES 32 +#define W9968CF_SIMCAMS W9968CF_MAX_DEVICES /* simultaneous cameras */ + +#define W9968CF_MAX_BUFFERS 32 +#define W9968CF_BUFFERS 2 /* n. of frame buffers from 2 to MAX_BUFFERS */ + +/* Maximum data payload sizes in bytes for alternate settings */ +static const u16 wMaxPacketSize[] = {1023, 959, 895, 831, 767, 703, 639, 575, + 511, 447, 383, 319, 255, 191, 127, 63}; +#define W9968CF_PACKET_SIZE 1023 /* according to wMaxPacketSizes[] */ +#define W9968CF_MIN_PACKET_SIZE 63 /* minimum value */ +#define W9968CF_ISO_PACKETS 5 /* n.of packets for isochronous transfers */ +#define W9968CF_USB_CTRL_TIMEOUT HZ /* timeout for usb control commands */ +#define W9968CF_URBS 2 /* n. of scheduled URBs for ISO transfer */ + +#define W9968CF_I2C_BUS_DELAY 4 /* delay in us for I2C bit r/w operations */ +#define W9968CF_I2C_RW_RETRIES 15 /* number of max I2C r/w retries */ + +/* Available video formats */ +struct w9968cf_format { + const u16 palette; + const u16 depth; + const u8 compression; +}; + +static const struct w9968cf_format w9968cf_formatlist[] = { + { VIDEO_PALETTE_UYVY, 16, 0 }, /* original video */ + { VIDEO_PALETTE_YUV422P, 16, 1 }, /* with JPEG compression */ + { VIDEO_PALETTE_YUV420P, 12, 1 }, /* with JPEG compression */ + { VIDEO_PALETTE_YUV420, 12, 1 }, /* same as YUV420P */ + { VIDEO_PALETTE_YUYV, 16, 0 }, /* software conversion */ + { VIDEO_PALETTE_YUV422, 16, 0 }, /* software conversion */ + { VIDEO_PALETTE_GREY, 8, 0 }, /* software conversion */ + { VIDEO_PALETTE_RGB555, 16, 0 }, /* software conversion */ + { VIDEO_PALETTE_RGB565, 16, 0 }, /* software conversion */ + { VIDEO_PALETTE_RGB24, 24, 0 }, /* software conversion */ + { VIDEO_PALETTE_RGB32, 32, 0 }, /* software conversion */ + { 0, 0, 0 } /* 0 is a terminating entry */ +}; + +#define W9968CF_DECOMPRESSION 2 /* decomp:0=disable,1=force,2=any formats */ +#define W9968CF_PALETTE_DECOMP_OFF VIDEO_PALETTE_UYVY /* when decomp=0 */ +#define W9968CF_PALETTE_DECOMP_FORCE VIDEO_PALETTE_YUV420P /* when decomp=1 */ +#define W9968CF_PALETTE_DECOMP_ON VIDEO_PALETTE_UYVY /* when decomp=2 */ + +#define W9968CF_FORCE_RGB 0 /* read RGB instead of BGR, yes=1/no=0 */ + +#define W9968CF_MAX_WIDTH 800 /* should be >= 640 */ +#define W9968CF_MAX_HEIGHT 600 /* should be >= 480 */ +#define W9968CF_WIDTH 320 /* from 128 to 352, multiple of 16 */ +#define W9968CF_HEIGHT 240 /* from 96 to 288, multiple of 16 */ + +#define W9968CF_CLAMPING 0 /* 0 disable, 1 enable video data clamping */ +#define W9968CF_FILTER_TYPE 0 /* 0 disable 1 (1-2-1), 2 (2-3-6-3-2) */ +#define W9968CF_DOUBLE_BUFFER 1 /* 0 disable, 1 enable double buffer */ +#define W9968CF_LARGEVIEW 1 /* 0 disable, 1 enable */ +#define W9968CF_UPSCALING 0 /* 0 disable, 1 enable */ + +#define W9968CF_SENSOR_MONO 0 /* 0 not monochrome, 1 monochrome sensor */ +#define W9968CF_BRIGHTNESS 31000 /* from 0 to 65535 */ +#define W9968CF_HUE 32768 /* from 0 to 65535 */ +#define W9968CF_COLOUR 32768 /* from 0 to 65535 */ +#define W9968CF_CONTRAST 50000 /* from 0 to 65535 */ +#define W9968CF_WHITENESS 32768 /* from 0 to 65535 */ + +#define W9968CF_AUTOBRIGHT 0 /* 0 disable, 1 enable automatic brightness */ +#define W9968CF_AUTOEXP 1 /* 0 disable, 1 enable automatic exposure */ +#define W9968CF_LIGHTFREQ 50 /* light frequency. 50Hz (Europe) or 60Hz */ +#define W9968CF_BANDINGFILTER 0 /* 0 disable, 1 enable banding filter */ +#define W9968CF_BACKLIGHT 0 /* 0 or 1, 1=object is lit from behind */ +#define W9968CF_MIRROR 0 /* 0 or 1 [don't] reverse image horizontally*/ + +#define W9968CF_CLOCKDIV -1 /* -1 = automatic clock divisor */ +#define W9968CF_DEF_CLOCKDIVISOR 0 /* default sensor clock divisor value */ + + +/**************************************************************************** + * Globals * + ****************************************************************************/ + +#define W9968CF_MODULE_NAME "V4L driver for W996[87]CF JPEG USB " \ + "Dual Mode Camera Chip" +#define W9968CF_MODULE_VERSION "v1.22" +#define W9968CF_MODULE_AUTHOR "(C) 2002 2003 Luca Risolia" +#define W9968CF_AUTHOR_EMAIL "" + +static u8 w9968cf_vppmod_present; /* status flag: yes=1, no=0 */ + +static const struct usb_device_id winbond_id_table[] = { + { + /* Creative Labs Video Blaster WebCam Go Plus */ + USB_DEVICE(0x041e, 0x4003), + .driver_info = (unsigned long)"w9968cf", + }, + { + /* Generic W996[87]CF JPEG USB Dual Mode Camera */ + USB_DEVICE(0x1046, 0x9967), + .driver_info = (unsigned long)"w9968cf", + }, + { } /* terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, winbond_id_table); + +/* W996[87]CF camera models, internal ids: */ +enum w9968cf_model_id { + W9968CF_MOD_GENERIC = 1, /* Generic W996[87]CF based device */ + W9968CF_MOD_CLVBWGP = 11,/*Creative Labs Video Blaster WebCam Go Plus*/ + W9968CF_MOD_ADPA5R = 21, /* Aroma Digi Pen ADG-5000 Refurbished */ + W9986CF_MOD_AU = 31, /* AVerTV USB */ + W9968CF_MOD_CLVBWG = 34, /* Creative Labs Video Blaster WebCam Go */ + W9968CF_MOD_DLLDK = 37, /* Die Lebon LDC-D35A Digital Kamera */ + W9968CF_MOD_EEEMC = 40, /* Ezonics EZ-802 EZMega Cam */ + W9968CF_MOD_ODPVDMPC = 43,/* OPCOM Digi Pen VGA Dual Mode Pen Camera */ +}; + +enum w9968cf_frame_status { + F_READY, /* finished grabbing & ready to be read/synced */ + F_GRABBING, /* in the process of being grabbed into */ + F_ERROR, /* something bad happened while processing */ + F_UNUSED /* unused (no VIDIOCMCAPTURE) */ +}; + +struct w9968cf_frame_t { + void* buffer; + u32 length; + enum w9968cf_frame_status status; + struct w9968cf_frame_t* next; + u8 queued; +}; + +enum w9968cf_vpp_flag { + VPP_NONE = 0x00, + VPP_UPSCALE = 0x01, + VPP_SWAP_YUV_BYTES = 0x02, + VPP_DECOMPRESSION = 0x04, + VPP_UYVY_TO_RGBX = 0x08, +}; + +struct list_head w9968cf_dev_list; /* head of V4L registered cameras list */ +LIST_HEAD(w9968cf_dev_list); +struct semaphore w9968cf_devlist_sem; /* semaphore for list traversal */ + +/* Main device driver structure */ +struct w9968cf_device { + enum w9968cf_model_id id; /* private device identifier */ + + struct video_device v4ldev; /* V4L structure */ + struct list_head v4llist; /* entry of the list of V4L cameras */ + + struct usb_device* usbdev; /* -> main USB structure */ + struct urb* urb[W9968CF_URBS]; /* -> USB request block structs */ + void* transfer_buffer[W9968CF_URBS]; /* -> ISO transfer buffers */ + u16* control_buffer; /* -> buffer for control req.*/ + u16* data_buffer; /* -> data to send to the FSB */ + + struct w9968cf_frame_t frame[W9968CF_MAX_BUFFERS]; + struct w9968cf_frame_t frame_tmp; /* temporary frame */ + struct w9968cf_frame_t* frame_current; /* -> frame being grabbed */ + struct w9968cf_frame_t* requested_frame[W9968CF_MAX_BUFFERS]; + void* vpp_buffer; /* -> helper buffer for post-processing routines */ + + u8 max_buffers, /* number of requested buffers */ + force_palette, /* yes=1/no=0 */ + force_rgb, /* read RGB instead of BGR, yes=1, no=0 */ + double_buffer, /* hardware double buffering yes=1/no=0 */ + clamping, /* video data clamping yes=1/no=0 */ + filter_type, /* 0=disabled, 1=3 tap, 2=5 tap filter */ + capture, /* 0=disabled, 1=enabled */ + largeview, /* 0=disabled, 1=enabled */ + decompression, /* 0=disabled, 1=forced, 2=allowed */ + upscaling; /* software image scaling, 0=enabled, 1=disabled */ + + struct video_picture picture; /* current window settings */ + struct video_window window; /* current picture settings */ + + u16 hw_depth, /* depth (used by the chip) */ + hw_palette, /* palette (used by the chip) */ + hw_width, /* width (used by the chip) */ + hw_height, /* height (used by the chip) */ + hs_polarity, /* 0=negative sync pulse, 1=positive sync pulse */ + vs_polarity; /* 0=negative sync pulse, 1=positive sync pulse */ + + enum w9968cf_vpp_flag vpp_flag; /* post-processing routines in use */ + + u8 nbuffers, /* number of allocated frame buffers */ + altsetting, /* camera alternate setting */ + disconnected, /* flag: yes=1, no=0 */ + misconfigured, /* flag: yes=1, no=0 */ + users, /* flag: number of users holding the device */ + streaming; /* flag: yes=1, no=0 */ + + int sensor; /* type of image CMOS sensor chip (CC_*) */ + + /* Determined by CMOS sensor type */ + u16 maxwidth, + maxheight, + minwidth, + minheight, + start_cropx, + start_cropy; + + u8 auto_brt, /* auto brightness enabled flag */ + auto_exp, /* auto exposure enabled flag */ + backlight, /* backlight exposure algorithm flag */ + mirror, /* image is reversed horizontally */ + lightfreq, /* power (lighting) frequency */ + bandfilt; /* banding filter enabled flag */ + s8 clockdiv; /* clock divisor */ + int sensor_mono; /* CMOS sensor is (probably) monochrome */ + + /* I2C interface to kernel */ + struct i2c_adapter i2c_adapter; + struct i2c_client* sensor_client; + +#if defined(CONFIG_VIDEO_PROC_FS) + /* /proc entries, relative to /proc/video/w9968cf/ */ + struct proc_dir_entry *proc_dev; /* readable per-device entry */ +#endif + + /* Locks */ + struct semaphore dev_sem, /* for probe, disconnect,open and close */ + fileop_sem; /* for read and ioctl */ +#if defined(CONFIG_VIDEO_PROC_FS) + struct semaphore procfs_sem; /* for /proc read/write calls */ +#endif + spinlock_t urb_lock, /* for submit_urb() and unlink_urb() */ + flist_lock; /* for requested frame list accesses */ + char command[16]; /* name of the program holding the device */ + wait_queue_head_t open, wait_queue; +}; + +#define W9968CF_HW_BUF_SIZE 640*480*2 /* buf. size for original video frames */ + +#define SENSOR_FORMAT VIDEO_PALETTE_UYVY +#define SENSOR_FATAL_ERROR(rc) ((rc) < 0 && (rc) != -EPERM) + + +/**************************************************************************** + * Macros and other constants * + ****************************************************************************/ + +#undef DBG +#ifdef W9968CF_DEBUG +# define DBG(level, fmt, args...) \ +{ \ +if ( ((specific_debug) && (debug == (level))) || \ + ((!specific_debug) && (debug >= (level))) ) { \ + if ((level) == 1) \ + err(fmt, ## args); \ + else if ((level) == 2 || (level) == 3) \ + info(fmt, ## args); \ + else if ((level) == 4) \ + warn(fmt, ## args); \ + else if ((level) >= 5) \ + info("[%s,%d] " fmt, \ + __PRETTY_FUNCTION__, __LINE__ , ## args); \ +} \ +} +#else + /* Not debugging: nothing */ +# define DBG(level, fmt, args...) do {;} while(0); +#endif + +#undef PDBG +#undef PDBGG +#define PDBG(fmt, args...) info("[%s, %d] "fmt, \ + __PRETTY_FUNCTION__, __LINE__ , ## args); +#define PDBGG(fmt, args...) do {;} while(0); /* nothing: it's a placeholder */ + +#endif /* _W9968CF_H_ */ diff -Nru a/drivers/usb/w9968cf_decoder.h b/drivers/usb/w9968cf_decoder.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/w9968cf_decoder.h Fri Oct 24 17:01:10 2003 @@ -0,0 +1,86 @@ +/*************************************************************************** + * Video decoder for the W996[87]CF driver for Linux. * + * * + * Copyright (C) 2003 by Luca Risolia * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#ifndef _W9968CF_DECODER_H_ +#define _W9968CF_DECODER_H_ + +/* Comment/uncomment this for high/low quality of compressed video */ +#define W9968CF_DEC_FAST_LOWQUALITY_VIDEO + +#ifdef W9968CF_DEC_FAST_LOWQUALITY_VIDEO +static const unsigned char Y_QUANTABLE[64] = { + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, + 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, + 72, 92, 95, 98, 112, 100, 103, 99 +}; + +static const unsigned char UV_QUANTABLE[64] = { + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 +}; +#else +static const unsigned char Y_QUANTABLE[64] = { + 8, 5, 5, 8, 12, 20, 25, 30, + 6, 6, 7, 9, 13, 29, 30, 27, + 7, 6, 8, 12, 20, 28, 34, 28, + 7, 8, 11, 14, 25, 43, 40, 31, + 9, 11, 18, 28, 34, 54, 51, 38, + 12, 17, 27, 32, 40, 52, 56, 46, + 24, 32, 39, 43, 51, 60, 60, 50, + 36, 46, 47, 49, 56, 50, 51, 49 +}; + +static const unsigned char UV_QUANTABLE[64] = { + 8, 9, 12, 23, 49, 49, 49, 49, + 9, 10, 13, 33, 49, 49, 49, 49, + 12, 13, 28, 49, 49, 49, 49, 49, + 23, 33, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49 +}; +#endif + +#define W9968CF_DEC_ERR_CORRUPTED_DATA -1 +#define W9968CF_DEC_ERR_BUF_OVERFLOW -2 +#define W9968CF_DEC_ERR_NO_SOI -3 +#define W9968CF_DEC_ERR_NO_SOF0 -4 +#define W9968CF_DEC_ERR_NO_SOS -5 +#define W9968CF_DEC_ERR_NO_EOI -6 + +extern void w9968cf_init_decoder(void); +extern int w9968cf_check_headers(const unsigned char* Pin, + const unsigned long BUF_SIZE); +extern int w9968cf_decode(const char* Pin, const unsigned long BUF_SIZE, + const unsigned W, const unsigned H, char* Pout); + +#endif /* _W9968CF_DECODER_H_ */ diff -Nru a/drivers/usb/w9968cf_externaldef.h b/drivers/usb/w9968cf_externaldef.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/w9968cf_externaldef.h Fri Oct 24 17:01:10 2003 @@ -0,0 +1,95 @@ +/*************************************************************************** + * Various definitions for compatibility with external modules. * + * This file is part of the W996[87]CF driver for Linux. * + * * + * Copyright (C) 2002 2003 by Luca Risolia * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#ifndef _W9968CF_EXTERNALDEF_H_ +#define _W9968CF_EXTERNALDEF_H_ + +#include +#include +#include +#include + +/* The following values have been copied from the "ovcamchip" module. */ + +#ifndef I2C_DRIVERID_OVCAMCHIP +# define I2C_DRIVERID_OVCAMCHIP 0xf00f +#endif + +/* Controls */ +enum { + OVCAMCHIP_CID_CONT, /* Contrast */ + OVCAMCHIP_CID_BRIGHT, /* Brightness */ + OVCAMCHIP_CID_SAT, /* Saturation */ + OVCAMCHIP_CID_HUE, /* Hue */ + OVCAMCHIP_CID_EXP, /* Exposure */ + OVCAMCHIP_CID_FREQ, /* Light frequency */ + OVCAMCHIP_CID_BANDFILT, /* Banding filter */ + OVCAMCHIP_CID_AUTOBRIGHT, /* Auto brightness */ + OVCAMCHIP_CID_AUTOEXP, /* Auto exposure */ + OVCAMCHIP_CID_BACKLIGHT, /* Back light compensation */ + OVCAMCHIP_CID_MIRROR, /* Mirror horizontally */ +}; + +/* I2C addresses */ +#define OV7xx0_SID (0x42 >> 1) +#define OV6xx0_SID (0xC0 >> 1) + +/* Sensor types */ +enum { + CC_UNKNOWN, + CC_OV76BE, + CC_OV7610, + CC_OV7620, + CC_OV7620AE, + CC_OV6620, + CC_OV6630, + CC_OV6630AE, + CC_OV6630AF, +}; + +/* API */ +struct ovcamchip_control { + __u32 id; + __s32 value; +}; + +struct ovcamchip_window { + int x; + int y; + int width; + int height; + int format; + int quarter; /* Scale width and height down 2x */ + + /* This stuff will be removed eventually */ + int clockdiv; /* Clock divisor setting */ +}; + +/* Commands. + You must call OVCAMCHIP_CMD_INITIALIZE before any of other commands */ +#define OVCAMCHIP_CMD_Q_SUBTYPE _IOR (0x88, 0x00, int) +#define OVCAMCHIP_CMD_INITIALIZE _IOW (0x88, 0x01, int) +#define OVCAMCHIP_CMD_S_CTRL _IOW (0x88, 0x02, struct ovcamchip_control) +#define OVCAMCHIP_CMD_G_CTRL _IOWR (0x88, 0x03, struct ovcamchip_control) +#define OVCAMCHIP_CMD_S_MODE _IOW (0x88, 0x04, struct ovcamchip_window) +#define OVCAMCHIP_MAX_CMD _IO (0x88, 0x3f) + +#endif /* _W9968CF_EXTERNALDEF_H_ */ diff -Nru a/include/linux/i2c-id.h b/include/linux/i2c-id.h --- a/include/linux/i2c-id.h Fri Oct 24 17:01:09 2003 +++ b/include/linux/i2c-id.h Fri Oct 24 17:01:09 2003 @@ -199,6 +199,7 @@ #define I2C_HW_SMBUS_AMD756 0x05 #define I2C_HW_SMBUS_SIS5595 0x06 #define I2C_HW_SMBUS_ALI1535 0x07 +#define I2C_HW_SMBUS_W9968CF 0x08 /* --- ISA pseudo-adapter */ #define I2C_HW_ISA 0x00 diff -Nru a/include/linux/videodev.h b/include/linux/videodev.h --- a/include/linux/videodev.h Fri Oct 24 17:01:10 2003 +++ b/include/linux/videodev.h Fri Oct 24 17:01:10 2003 @@ -405,6 +405,7 @@ #define VID_HARDWARE_CPIA2 33 #define VID_HARDWARE_VICAM 34 /* ViCam, 3Com Homeconnect */ #define VID_HARDWARE_SF16FMR2 35 +#define VID_HARDWARE_W9968CF 36 #endif /* __LINUX_VIDEODEV_H */