diff options
Diffstat (limited to 'drivers/media/video/pwc')
-rw-r--r-- | drivers/media/video/pwc/Makefile | 20 | ||||
-rw-r--r-- | drivers/media/video/pwc/philips.txt | 236 | ||||
-rw-r--r-- | drivers/media/video/pwc/pwc-ctrl.c | 1541 | ||||
-rw-r--r-- | drivers/media/video/pwc/pwc-if.c | 2205 | ||||
-rw-r--r-- | drivers/media/video/pwc/pwc-ioctl.h | 292 | ||||
-rw-r--r-- | drivers/media/video/pwc/pwc-kiara.c | 318 | ||||
-rw-r--r-- | drivers/media/video/pwc/pwc-kiara.h | 45 | ||||
-rw-r--r-- | drivers/media/video/pwc/pwc-misc.c | 140 | ||||
-rw-r--r-- | drivers/media/video/pwc/pwc-nala.h | 66 | ||||
-rw-r--r-- | drivers/media/video/pwc/pwc-timon.c | 316 | ||||
-rw-r--r-- | drivers/media/video/pwc/pwc-timon.h | 61 | ||||
-rw-r--r-- | drivers/media/video/pwc/pwc-uncompress.c | 146 | ||||
-rw-r--r-- | drivers/media/video/pwc/pwc-uncompress.h | 41 | ||||
-rw-r--r-- | drivers/media/video/pwc/pwc.h | 272 |
14 files changed, 5699 insertions, 0 deletions
diff --git a/drivers/media/video/pwc/Makefile b/drivers/media/video/pwc/Makefile new file mode 100644 index 00000000000..8326684f49f --- /dev/null +++ b/drivers/media/video/pwc/Makefile @@ -0,0 +1,20 @@ +ifneq ($(KERNELRELEASE),) + +pwc-objs := pwc-if.o pwc-misc.o pwc-ctrl.o pwc-uncompress.o pwc-timon.o pwc-kiara.o + +obj-$(CONFIG_USB_PWC) += pwc.o + +else + +KDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +default: + $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules + +endif + +clean: + rm -f *.[oas] .*.flags *.ko .*.cmd .*.d .*.tmp *.mod.c + rm -rf .tmp_versions + diff --git a/drivers/media/video/pwc/philips.txt b/drivers/media/video/pwc/philips.txt new file mode 100644 index 00000000000..11f751a6bda --- /dev/null +++ b/drivers/media/video/pwc/philips.txt @@ -0,0 +1,236 @@ +This file contains some additional information for the Philips and OEM webcams. +E-mail: webcam@smcc.demon.nl Last updated: 2004-01-19 +Site: http://www.smcc.demon.nl/webcam/ + +As of this moment, the following cameras are supported: + * Philips PCA645 + * Philips PCA646 + * Philips PCVC675 + * Philips PCVC680 + * Philips PCVC690 + * Philips PCVC720/40 + * Philips PCVC730 + * Philips PCVC740 + * Philips PCVC750 + * Askey VC010 + * Creative Labs Webcam 5 + * Creative Labs Webcam Pro Ex + * Logitech QuickCam 3000 Pro + * Logitech QuickCam 4000 Pro + * Logitech QuickCam Notebook Pro + * Logitech QuickCam Zoom + * Logitech QuickCam Orbit + * Logitech QuickCam Sphere + * Samsung MPC-C10 + * Samsung MPC-C30 + * Sotec Afina Eye + * AME CU-001 + * Visionite VCS-UM100 + * Visionite VCS-UC300 + +The main webpage for the Philips driver is at the address above. It contains +a lot of extra information, a FAQ, and the binary plugin 'PWCX'. This plugin +contains decompression routines that allow you to use higher image sizes and +framerates; in addition the webcam uses less bandwidth on the USB bus (handy +if you want to run more than 1 camera simultaneously). These routines fall +under a NDA, and may therefor not be distributed as source; however, its use +is completely optional. + +You can build this code either into your kernel, or as a module. I recommend +the latter, since it makes troubleshooting a lot easier. The built-in +microphone is supported through the USB Audio class. + +When you load the module you can set some default settings for the +camera; some programs depend on a particular image-size or -format and +don't know how to set it properly in the driver. The options are: + +size + Can be one of 'sqcif', 'qsif', 'qcif', 'sif', 'cif' or + 'vga', for an image size of resp. 128x96, 160x120, 176x144, + 320x240, 352x288 and 640x480 (of course, only for those cameras that + support these resolutions). + +fps + Specifies the desired framerate. Is an integer in the range of 4-30. + +fbufs + This paramter specifies the number of internal buffers to use for storing + frames from the cam. This will help if the process that reads images from + the cam is a bit slow or momentarely busy. However, on slow machines it + only introduces lag, so choose carefully. The default is 3, which is + reasonable. You can set it between 2 and 5. + +mbufs + This is an integer between 1 and 10. It will tell the module the number of + buffers to reserve for mmap(), VIDIOCCGMBUF, VIDIOCMCAPTURE and friends. + The default is 2, which is adequate for most applications (double + buffering). + + Should you experience a lot of 'Dumping frame...' messages during + grabbing with a tool that uses mmap(), you might want to increase if. + However, it doesn't really buffer images, it just gives you a bit more + slack when your program is behind. But you need a multi-threaded or + forked program to really take advantage of these buffers. + + The absolute maximum is 10, but don't set it too high! Every buffer takes + up 460 KB of RAM, so unless you have a lot of memory setting this to + something more than 4 is an absolute waste. This memory is only + allocated during open(), so nothing is wasted when the camera is not in + use. + +power_save + When power_save is enabled (set to 1), the module will try to shut down + the cam on close() and re-activate on open(). This will save power and + turn off the LED. Not all cameras support this though (the 645 and 646 + don't have power saving at all), and some models don't work either (they + will shut down, but never wake up). Consider this experimental. By + default this option is disabled. + +compression (only useful with the plugin) + With this option you can control the compression factor that the camera + uses to squeeze the image through the USB bus. You can set the + parameter between 0 and 3: + 0 = prefer uncompressed images; if the requested mode is not available + in an uncompressed format, the driver will silently switch to low + compression. + 1 = low compression. + 2 = medium compression. + 3 = high compression. + + High compression takes less bandwidth of course, but it could also + introduce some unwanted artefacts. The default is 2, medium compression. + See the FAQ on the website for an overview of which modes require + compression. + + The compression parameter does not apply to the 645 and 646 cameras + and OEM models derived from those (only a few). Most cams honour this + parameter. + +leds + This settings takes 2 integers, that define the on/off time for the LED + (in milliseconds). One of the interesting things that you can do with + this is let the LED blink while the camera is in use. This: + + leds=500,500 + + will blink the LED once every second. But with: + + leds=0,0 + + the LED never goes on, making it suitable for silent surveillance. + + By default the camera's LED is on solid while in use, and turned off + when the camera is not used anymore. + + This parameter works only with the ToUCam range of cameras (720, 730, 740, + 750) and OEMs. For other cameras this command is silently ignored, and + the LED cannot be controlled. + + Finally: this parameters does not take effect UNTIL the first time you + open the camera device. Until then, the LED remains on. + +dev_hint + A long standing problem with USB devices is their dynamic nature: you + never know what device a camera gets assigned; it depends on module load + order, the hub configuration, the order in which devices are plugged in, + and the phase of the moon (i.e. it can be random). With this option you + can give the driver a hint as to what video device node (/dev/videoX) it + should use with a specific camera. This is also handy if you have two + cameras of the same model. + + A camera is specified by its type (the number from the camera model, + like PCA645, PCVC750VC, etc) and optionally the serial number (visible + in /proc/bus/usb/devices). A hint consists of a string with the following + format: + + [type[.serialnumber]:]node + + The square brackets mean that both the type and the serialnumber are + optional, but a serialnumber cannot be specified without a type (which + would be rather pointless). The serialnumber is separated from the type + by a '.'; the node number by a ':'. + + This somewhat cryptic syntax is best explained by a few examples: + + dev_hint=3,5 The first detected cam gets assigned + /dev/video3, the second /dev/video5. Any + other cameras will get the first free + available slot (see below). + + dev_hint=645:1,680:2 The PCA645 camera will get /dev/video1, + and a PCVC680 /dev/video2. + + dev_hint=645.0123:3,645.4567:0 The PCA645 camera with serialnumber + 0123 goes to /dev/video3, the same + camera model with the 4567 serial + gets /dev/video0. + + dev_hint=750:1,4,5,6 The PCVC750 camera will get /dev/video1, the + next 3 Philips cams will use /dev/video4 + through /dev/video6. + + Some points worth knowing: + - Serialnumbers are case sensitive and must be written full, including + leading zeroes (it's treated as a string). + - If a device node is already occupied, registration will fail and + the webcam is not available. + - You can have up to 64 video devices; be sure to make enough device + nodes in /dev if you want to spread the numbers (this does not apply + to devfs). After /dev/video9 comes /dev/video10 (not /dev/videoA). + - If a camera does not match any dev_hint, it will simply get assigned + the first available device node, just as it used to be. + +trace + In order to better detect problems, it is now possible to turn on a + 'trace' of some of the calls the module makes; it logs all items in your + kernel log at debug level. + + The trace variable is a bitmask; each bit represents a certain feature. + If you want to trace something, look up the bit value(s) in the table + below, add the values together and supply that to the trace variable. + + Value Value Description Default + (dec) (hex) + 1 0x1 Module initialization; this will log messages On + while loading and unloading the module + + 2 0x2 probe() and disconnect() traces On + + 4 0x4 Trace open() and close() calls Off + + 8 0x8 read(), mmap() and associated ioctl() calls Off + + 16 0x10 Memory allocation of buffers, etc. Off + + 32 0x20 Showing underflow, overflow and Dumping frame On + messages + + 64 0x40 Show viewport and image sizes Off + + 128 0x80 PWCX debugging Off + + For example, to trace the open() & read() fuctions, sum 8 + 4 = 12, + so you would supply trace=12 during insmod or modprobe. If + you want to turn the initialization and probing tracing off, set trace=0. + The default value for trace is 35 (0x23). + + + +Example: + + # modprobe pwc size=cif fps=15 power_save=1 + +The fbufs, mbufs and trace parameters are global and apply to all connected +cameras. Each camera has its own set of buffers. + +size and fps only specify defaults when you open() the device; this is to +accommodate some tools that don't set the size. You can change these +settings after open() with the Video4Linux ioctl() calls. The default of +defaults is QCIF size at 10 fps. + +The compression parameter is semiglobal; it sets the initial compression +preference for all camera's, but this parameter can be set per camera with +the VIDIOCPWCSCQUAL ioctl() call. + +All parameters are optional. + diff --git a/drivers/media/video/pwc/pwc-ctrl.c b/drivers/media/video/pwc/pwc-ctrl.c new file mode 100644 index 00000000000..4ba549bfa0e --- /dev/null +++ b/drivers/media/video/pwc/pwc-ctrl.c @@ -0,0 +1,1541 @@ +/* Driver for Philips webcam + Functions that send various control messages to the webcam, including + video modes. + (C) 1999-2003 Nemosoft Unv. + (C) 2004 Luc Saillard (luc@saillard.org) + + NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx + driver and thus may have bugs that are not present in the original version. + Please send bug reports and support requests to <luc@saillard.org>. + + NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx + driver and thus may have bugs that are not present in the original version. + Please send bug reports and support requests to <luc@saillard.org>. + The decompression routines have been implemented by reverse-engineering the + Nemosoft binary pwcx module. Caveat emptor. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* + Changes + 2001/08/03 Alvarado Added methods for changing white balance and + red/green gains + */ + +/* Control functions for the cam; brightness, contrast, video mode, etc. */ + +#ifdef __KERNEL__ +#include <asm/uaccess.h> +#endif +#include <asm/errno.h> + +#include "pwc.h" +#include "pwc-ioctl.h" +#include "pwc-uncompress.h" +#include "pwc-kiara.h" +#include "pwc-timon.h" + +/* Request types: video */ +#define SET_LUM_CTL 0x01 +#define GET_LUM_CTL 0x02 +#define SET_CHROM_CTL 0x03 +#define GET_CHROM_CTL 0x04 +#define SET_STATUS_CTL 0x05 +#define GET_STATUS_CTL 0x06 +#define SET_EP_STREAM_CTL 0x07 +#define GET_EP_STREAM_CTL 0x08 +#define SET_MPT_CTL 0x0D +#define GET_MPT_CTL 0x0E + +/* Selectors for the Luminance controls [GS]ET_LUM_CTL */ +#define AGC_MODE_FORMATTER 0x2000 +#define PRESET_AGC_FORMATTER 0x2100 +#define SHUTTER_MODE_FORMATTER 0x2200 +#define PRESET_SHUTTER_FORMATTER 0x2300 +#define PRESET_CONTOUR_FORMATTER 0x2400 +#define AUTO_CONTOUR_FORMATTER 0x2500 +#define BACK_LIGHT_COMPENSATION_FORMATTER 0x2600 +#define CONTRAST_FORMATTER 0x2700 +#define DYNAMIC_NOISE_CONTROL_FORMATTER 0x2800 +#define FLICKERLESS_MODE_FORMATTER 0x2900 +#define AE_CONTROL_SPEED 0x2A00 +#define BRIGHTNESS_FORMATTER 0x2B00 +#define GAMMA_FORMATTER 0x2C00 + +/* Selectors for the Chrominance controls [GS]ET_CHROM_CTL */ +#define WB_MODE_FORMATTER 0x1000 +#define AWB_CONTROL_SPEED_FORMATTER 0x1100 +#define AWB_CONTROL_DELAY_FORMATTER 0x1200 +#define PRESET_MANUAL_RED_GAIN_FORMATTER 0x1300 +#define PRESET_MANUAL_BLUE_GAIN_FORMATTER 0x1400 +#define COLOUR_MODE_FORMATTER 0x1500 +#define SATURATION_MODE_FORMATTER1 0x1600 +#define SATURATION_MODE_FORMATTER2 0x1700 + +/* Selectors for the Status controls [GS]ET_STATUS_CTL */ +#define SAVE_USER_DEFAULTS_FORMATTER 0x0200 +#define RESTORE_USER_DEFAULTS_FORMATTER 0x0300 +#define RESTORE_FACTORY_DEFAULTS_FORMATTER 0x0400 +#define READ_AGC_FORMATTER 0x0500 +#define READ_SHUTTER_FORMATTER 0x0600 +#define READ_RED_GAIN_FORMATTER 0x0700 +#define READ_BLUE_GAIN_FORMATTER 0x0800 +#define SENSOR_TYPE_FORMATTER1 0x0C00 +#define READ_RAW_Y_MEAN_FORMATTER 0x3100 +#define SET_POWER_SAVE_MODE_FORMATTER 0x3200 +#define MIRROR_IMAGE_FORMATTER 0x3300 +#define LED_FORMATTER 0x3400 +#define SENSOR_TYPE_FORMATTER2 0x3700 + +/* Formatters for the Video Endpoint controls [GS]ET_EP_STREAM_CTL */ +#define VIDEO_OUTPUT_CONTROL_FORMATTER 0x0100 + +/* Formatters for the motorized pan & tilt [GS]ET_MPT_CTL */ +#define PT_RELATIVE_CONTROL_FORMATTER 0x01 +#define PT_RESET_CONTROL_FORMATTER 0x02 +#define PT_STATUS_FORMATTER 0x03 + +static const char *size2name[PSZ_MAX] = +{ + "subQCIF", + "QSIF", + "QCIF", + "SIF", + "CIF", + "VGA", +}; + +/********/ + +/* Entries for the Nala (645/646) camera; the Nala doesn't have compression + preferences, so you either get compressed or non-compressed streams. + + An alternate value of 0 means this mode is not available at all. + */ + +struct Nala_table_entry { + char alternate; /* USB alternate setting */ + int compressed; /* Compressed yes/no */ + + unsigned char mode[3]; /* precomputed mode table */ +}; + +static struct Nala_table_entry Nala_table[PSZ_MAX][8] = +{ +#include "pwc-nala.h" +}; + + +/****************************************************************************/ + + +#define SendControlMsg(request, value, buflen) \ + usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0), \ + request, \ + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, \ + value, \ + pdev->vcinterface, \ + &buf, buflen, 500) + +#define RecvControlMsg(request, value, buflen) \ + usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0), \ + request, \ + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, \ + value, \ + pdev->vcinterface, \ + &buf, buflen, 500) + + +#if PWC_DEBUG +void pwc_hexdump(void *p, int len) +{ + int i; + unsigned char *s; + char buf[100], *d; + + s = (unsigned char *)p; + d = buf; + *d = '\0'; + Debug("Doing hexdump @ %p, %d bytes.\n", p, len); + for (i = 0; i < len; i++) { + d += sprintf(d, "%02X ", *s++); + if ((i & 0xF) == 0xF) { + Debug("%s\n", buf); + d = buf; + *d = '\0'; + } + } + if ((i & 0xF) != 0) + Debug("%s\n", buf); +} +#endif + +static inline int send_video_command(struct usb_device *udev, int index, void *buf, int buflen) +{ + return usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + SET_EP_STREAM_CTL, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + VIDEO_OUTPUT_CONTROL_FORMATTER, + index, + buf, buflen, 1000); +} + + + +static inline int set_video_mode_Nala(struct pwc_device *pdev, int size, int frames) +{ + unsigned char buf[3]; + int ret, fps; + struct Nala_table_entry *pEntry; + int frames2frames[31] = + { /* closest match of framerate */ + 0, 0, 0, 0, 4, /* 0-4 */ + 5, 5, 7, 7, 10, /* 5-9 */ + 10, 10, 12, 12, 15, /* 10-14 */ + 15, 15, 15, 20, 20, /* 15-19 */ + 20, 20, 20, 24, 24, /* 20-24 */ + 24, 24, 24, 24, 24, /* 25-29 */ + 24 /* 30 */ + }; + int frames2table[31] = + { 0, 0, 0, 0, 0, /* 0-4 */ + 1, 1, 1, 2, 2, /* 5-9 */ + 3, 3, 4, 4, 4, /* 10-14 */ + 5, 5, 5, 5, 5, /* 15-19 */ + 6, 6, 6, 6, 7, /* 20-24 */ + 7, 7, 7, 7, 7, /* 25-29 */ + 7 /* 30 */ + }; + + if (size < 0 || size > PSZ_CIF || frames < 4 || frames > 25) + return -EINVAL; + frames = frames2frames[frames]; + fps = frames2table[frames]; + pEntry = &Nala_table[size][fps]; + if (pEntry->alternate == 0) + return -EINVAL; + + if (pEntry->compressed) + return -ENOENT; /* Not supported. */ + + memcpy(buf, pEntry->mode, 3); + ret = send_video_command(pdev->udev, pdev->vendpoint, buf, 3); + if (ret < 0) { + Debug("Failed to send video command... %d\n", ret); + return ret; + } + if (pEntry->compressed && pdev->vpalette != VIDEO_PALETTE_RAW) + { + switch(pdev->type) { + case 645: + case 646: +/* pwc_dec1_init(pdev->type, pdev->release, buf, pdev->decompress_data); */ + break; + + case 675: + case 680: + case 690: + case 720: + case 730: + case 740: + case 750: +/* pwc_dec23_init(pdev->type, pdev->release, buf, pdev->decompress_data); */ + break; + } + } + + pdev->cmd_len = 3; + memcpy(pdev->cmd_buf, buf, 3); + + /* Set various parameters */ + pdev->vframes = frames; + pdev->vsize = size; + pdev->valternate = pEntry->alternate; + pdev->image = pwc_image_sizes[size]; + pdev->frame_size = (pdev->image.x * pdev->image.y * 3) / 2; + if (pEntry->compressed) { + if (pdev->release < 5) { /* 4 fold compression */ + pdev->vbandlength = 528; + pdev->frame_size /= 4; + } + else { + pdev->vbandlength = 704; + pdev->frame_size /= 3; + } + } + else + pdev->vbandlength = 0; + return 0; +} + + +static inline int set_video_mode_Timon(struct pwc_device *pdev, int size, int frames, int compression, int snapshot) +{ + unsigned char buf[13]; + const struct Timon_table_entry *pChoose; + int ret, fps; + + if (size >= PSZ_MAX || frames < 5 || frames > 30 || compression < 0 || compression > 3) + return -EINVAL; + if (size == PSZ_VGA && frames > 15) + return -EINVAL; + fps = (frames / 5) - 1; + + /* Find a supported framerate with progressively higher compression ratios + if the preferred ratio is not available. + */ + pChoose = NULL; + while (compression <= 3) { + pChoose = &Timon_table[size][fps][compression]; + if (pChoose->alternate != 0) + break; + compression++; + } + if (pChoose == NULL || pChoose->alternate == 0) + return -ENOENT; /* Not supported. */ + + memcpy(buf, pChoose->mode, 13); + if (snapshot) + buf[0] |= 0x80; + ret = send_video_command(pdev->udev, pdev->vendpoint, buf, 13); + if (ret < 0) + return ret; + +/* if (pChoose->bandlength > 0 && pdev->vpalette != VIDEO_PALETTE_RAW) + pwc_dec23_init(pdev->type, pdev->release, buf, pdev->decompress_data); */ + + pdev->cmd_len = 13; + memcpy(pdev->cmd_buf, buf, 13); + + /* Set various parameters */ + pdev->vframes = frames; + pdev->vsize = size; + pdev->vsnapshot = snapshot; + pdev->valternate = pChoose->alternate; + pdev->image = pwc_image_sizes[size]; + pdev->vbandlength = pChoose->bandlength; + if (pChoose->bandlength > 0) + pdev->frame_size = (pChoose->bandlength * pdev->image.y) / 4; + else + pdev->frame_size = (pdev->image.x * pdev->image.y * 12) / 8; + return 0; +} + + +static inline int set_video_mode_Kiara(struct pwc_device *pdev, int size, int frames, int compression, int snapshot) +{ + const struct Kiara_table_entry *pChoose = NULL; + int fps, ret; + unsigned char buf[12]; + struct Kiara_table_entry RawEntry = {6, 773, 1272, {0xAD, 0xF4, 0x10, 0x27, 0xB6, 0x24, 0x96, 0x02, 0x30, 0x05, 0x03, 0x80}}; + + if (size >= PSZ_MAX || frames < 5 || frames > 30 || compression < 0 || compression > 3) + return -EINVAL; + if (size == PSZ_VGA && frames > 15) + return -EINVAL; + fps = (frames / 5) - 1; + + /* special case: VGA @ 5 fps and snapshot is raw bayer mode */ + if (size == PSZ_VGA && frames == 5 && snapshot) + { + /* Only available in case the raw palette is selected or + we have the decompressor available. This mode is + only available in compressed form + */ + if (pdev->vpalette == VIDEO_PALETTE_RAW) + { + Info("Choosing VGA/5 BAYER mode (%d).\n", pdev->vpalette); + pChoose = &RawEntry; + } + else + { + Info("VGA/5 BAYER mode _must_ have a decompressor available, or use RAW palette.\n"); + } + } + else + { + /* Find a supported framerate with progressively higher compression ratios + if the preferred ratio is not available. + Skip this step when using RAW modes. + */ + while (compression <= 3) { + pChoose = &Kiara_table[size][fps][compression]; + if (pChoose->alternate != 0) + break; + compression++; + } + } + if (pChoose == NULL || pChoose->alternate == 0) + return -ENOENT; /* Not supported. */ + + Debug("Using alternate setting %d.\n", pChoose->alternate); + + /* usb_control_msg won't take staticly allocated arrays as argument?? */ + memcpy(buf, pChoose->mode, 12); + if (snapshot) + buf[0] |= 0x80; + + /* Firmware bug: video endpoint is 5, but commands are sent to endpoint 4 */ + ret = send_video_command(pdev->udev, 4 /* pdev->vendpoint */, buf, 12); + if (ret < 0) + return ret; + +/* if (pChoose->bandlength > 0 && pdev->vpalette != VIDEO_PALETTE_RAW) + pwc_dec23_init(pdev->type, pdev->release, buf, pdev->decompress_data); */ + + pdev->cmd_len = 12; + memcpy(pdev->cmd_buf, buf, 12); + /* All set and go */ + pdev->vframes = frames; + pdev->vsize = size; + pdev->vsnapshot = snapshot; + pdev->valternate = pChoose->alternate; + pdev->image = pwc_image_sizes[size]; + pdev->vbandlength = pChoose->bandlength; + if (pdev->vbandlength > 0) + pdev->frame_size = (pdev->vbandlength * pdev->image.y) / 4; + else + pdev->frame_size = (pdev->image.x * pdev->image.y * 12) / 8; + return 0; +} + + + +static void pwc_set_image_buffer_size(struct pwc_device *pdev) +{ + int i, factor = 0, filler = 0; + + /* for PALETTE_YUV420P */ + switch(pdev->vpalette) + { + case VIDEO_PALETTE_YUV420P: + factor = 6; + filler = 128; + break; + case VIDEO_PALETTE_RAW: + factor = 6; /* can be uncompressed YUV420P */ + filler = 0; + break; + } + + /* Set sizes in bytes */ + pdev->image.size = pdev->image.x * pdev->image.y * factor / 4; + pdev->view.size = pdev->view.x * pdev->view.y * factor / 4; + + /* Align offset, or you'll get some very weird results in + YUV420 mode... x must be multiple of 4 (to get the Y's in + place), and y even (or you'll mixup U & V). This is less of a + problem for YUV420P. + */ + pdev->offset.x = ((pdev->view.x - pdev->image.x) / 2) & 0xFFFC; + pdev->offset.y = ((pdev->view.y - pdev->image.y) / 2) & 0xFFFE; + + /* Fill buffers with gray or black */ + for (i = 0; i < MAX_IMAGES; i++) { + if (pdev->image_ptr[i] != NULL) + memset(pdev->image_ptr[i], filler, pdev->view.size); + } +} + + + +/** + @pdev: device structure + @width: viewport width + @height: viewport height + @frame: framerate, in fps + @compression: preferred compression ratio + @snapshot: snapshot mode or streaming + */ +int pwc_set_video_mode(struct pwc_device *pdev, int width, int height, int frames, int compression, int snapshot) +{ + int ret, size; + + Trace(TRACE_FLOW, "set_video_mode(%dx%d @ %d, palette %d).\n", width, height, frames, pdev->vpalette); + size = pwc_decode_size(pdev, width, height); + if (size < 0) { + Debug("Could not find suitable size.\n"); + return -ERANGE; + } + Debug("decode_size = %d.\n", size); + + ret = -EINVAL; + switch(pdev->type) { + case 645: + case 646: + ret = set_video_mode_Nala(pdev, size, frames); + break; + + case 675: + case 680: + case 690: + ret = set_video_mode_Timon(pdev, size, frames, compression, snapshot); + break; + + case 720: + case 730: + case 740: + case 750: + ret = set_video_mode_Kiara(pdev, size, frames, compression, snapshot); + break; + } + if (ret < 0) { + if (ret == -ENOENT) + Info("Video mode %s@%d fps is only supported with the decompressor module (pwcx).\n", size2name[size], frames); + else { + Err("Failed to set video mode %s@%d fps; return code = %d\n", size2name[size], frames, ret); + } + return ret; + } + pdev->view.x = width; + pdev->view.y = height; + pdev->frame_total_size = pdev->frame_size + pdev->frame_header_size + pdev->frame_trailer_size; + pwc_set_image_buffer_size(pdev); + Trace(TRACE_SIZE, "Set viewport to %dx%d, image size is %dx%d.\n", width, height, pwc_image_sizes[size].x, pwc_image_sizes[size].y); + return 0; +} + + +/* BRIGHTNESS */ + +int pwc_get_brightness(struct pwc_device *pdev) +{ + char buf; + int ret; + + ret = RecvControlMsg(GET_LUM_CTL, BRIGHTNESS_FORMATTER, 1); + if (ret < 0) + return ret; + return buf << 9; +} + +int pwc_set_brightness(struct pwc_device *pdev, int value) +{ + char buf; + + if (value < 0) + value = 0; + if (value > 0xffff) + value = 0xffff; + buf = (value >> 9) & 0x7f; + return SendControlMsg(SET_LUM_CTL, BRIGHTNESS_FORMATTER, 1); +} + +/* CONTRAST */ + +int pwc_get_contrast(struct pwc_device *pdev) +{ + char buf; + int ret; + + ret = RecvControlMsg(GET_LUM_CTL, CONTRAST_FORMATTER, 1); + if (ret < 0) + return ret; + return buf << 10; +} + +int pwc_set_contrast(struct pwc_device *pdev, int value) +{ + char buf; + + if (value < 0) + value = 0; + if (value > 0xffff) + value = 0xffff; + buf = (value >> 10) & 0x3f; + return SendControlMsg(SET_LUM_CTL, CONTRAST_FORMATTER, 1); +} + +/* GAMMA */ + +int pwc_get_gamma(struct pwc_device *pdev) +{ + char buf; + int ret; + + ret = RecvControlMsg(GET_LUM_CTL, GAMMA_FORMATTER, 1); + if (ret < 0) + return ret; + return buf << 11; +} + +int pwc_set_gamma(struct pwc_device *pdev, int value) +{ + char buf; + + if (value < 0) + value = 0; + if (value > 0xffff) + value = 0xffff; + buf = (value >> 11) & 0x1f; + return SendControlMsg(SET_LUM_CTL, GAMMA_FORMATTER, 1); +} + + +/* SATURATION */ + +int pwc_get_saturation(struct pwc_device *pdev) +{ + char buf; + int ret; + + if (pdev->type < 675) + return -1; + ret = RecvControlMsg(GET_CHROM_CTL, pdev->type < 730 ? SATURATION_MODE_FORMATTER2 : SATURATION_MODE_FORMATTER1, 1); + if (ret < 0) + return ret; + return 32768 + buf * 327; +} + +int pwc_set_saturation(struct pwc_device *pdev, int value) +{ + char buf; + + if (pdev->type < 675) + return -EINVAL; + if (value < 0) + value = 0; + if (value > 0xffff) + value = 0xffff; + /* saturation ranges from -100 to +100 */ + buf = (value - 32768) / 327; + return SendControlMsg(SET_CHROM_CTL, pdev->type < 730 ? SATURATION_MODE_FORMATTER2 : SATURATION_MODE_FORMATTER1, 1); +} + +/* AGC */ + +static inline int pwc_set_agc(struct pwc_device *pdev, int mode, int value) +{ + char buf; + int ret; + + if (mode) + buf = 0x0; /* auto */ + else + buf = 0xff; /* fixed */ + + ret = SendControlMsg(SET_LUM_CTL, AGC_MODE_FORMATTER, 1); + + if (!mode && ret >= 0) { + if (value < 0) + value = 0; + if (value > 0xffff) + value = 0xffff; + buf = (value >> 10) & 0x3F; + ret = SendControlMsg(SET_LUM_CTL, PRESET_AGC_FORMATTER, 1); + } + if (ret < 0) + return ret; + return 0; +} + +static inline int pwc_get_agc(struct pwc_device *pdev, int *value) +{ + unsigned char buf; + int ret; + + ret = RecvControlMsg(GET_LUM_CTL, AGC_MODE_FORMATTER, 1); + if (ret < 0) + return ret; + + if (buf != 0) { /* fixed */ + ret = RecvControlMsg(GET_LUM_CTL, PRESET_AGC_FORMATTER, 1); + if (ret < 0) + return ret; + if (buf > 0x3F) + buf = 0x3F; + *value = (buf << 10); + } + else { /* auto */ + ret = RecvControlMsg(GET_STATUS_CTL, READ_AGC_FORMATTER, 1); + if (ret < 0) + return ret; + /* Gah... this value ranges from 0x00 ... 0x9F */ + if (buf > 0x9F) + buf = 0x9F; + *value = -(48 + buf * 409); + } + + return 0; +} + +static inline int pwc_set_shutter_speed(struct pwc_device *pdev, int mode, int value) +{ + char buf[2]; + int speed, ret; + + + if (mode) + buf[0] = 0x0; /* auto */ + else + buf[0] = 0xff; /* fixed */ + + ret = SendControlMsg(SET_LUM_CTL, SHUTTER_MODE_FORMATTER, 1); + + if (!mode && ret >= 0) { + if (value < 0) + value = 0; + if (value > 0xffff) + value = 0xffff; + switch(pdev->type) { + case 675: + case 680: + case 690: + /* speed ranges from 0x0 to 0x290 (656) */ + speed = (value / 100); + buf[1] = speed >> 8; + buf[0] = speed & 0xff; + break; + case 720: + case 730: + case 740: + case 750: + /* speed seems to range from 0x0 to 0xff */ + buf[1] = 0; + buf[0] = value >> 8; + break; + } + + ret = SendControlMsg(SET_LUM_CTL, PRESET_SHUTTER_FORMATTER, 2); + } + return ret; +} + + +/* POWER */ + +int pwc_camera_power(struct pwc_device *pdev, int power) +{ + char buf; + + if (pdev->type < 675 || (pdev->type < 730 && pdev->release < 6)) + return 0; /* Not supported by Nala or Timon < release 6 */ + + if (power) + buf = 0x00; /* active */ + else + buf = 0xFF; /* power save */ + return SendControlMsg(SET_STATUS_CTL, SET_POWER_SAVE_MODE_FORMATTER, 1); +} + + + +/* private calls */ + +static inline int pwc_restore_user(struct pwc_device *pdev) +{ + char buf; /* dummy */ + return SendControlMsg(SET_STATUS_CTL, RESTORE_USER_DEFAULTS_FORMATTER, 0); +} + +static inline int pwc_save_user(struct pwc_device *pdev) +{ + char buf; /* dummy */ + return SendControlMsg(SET_STATUS_CTL, SAVE_USER_DEFAULTS_FORMATTER, 0); +} + +static inline int pwc_restore_factory(struct pwc_device *pdev) +{ + char buf; /* dummy */ + return SendControlMsg(SET_STATUS_CTL, RESTORE_FACTORY_DEFAULTS_FORMATTER, 0); +} + + /* ************************************************* */ + /* Patch by Alvarado: (not in the original version */ + + /* + * the camera recognizes modes from 0 to 4: + * + * 00: indoor (incandescant lighting) + * 01: outdoor (sunlight) + * 02: fluorescent lighting + * 03: manual + * 04: auto + */ +static inline int pwc_set_awb(struct pwc_device *pdev, int mode) +{ + char buf; + int ret; + + if (mode < 0) + mode = 0; + + if (mode > 4) + mode = 4; + + buf = mode & 0x07; /* just the lowest three bits */ + + ret = SendControlMsg(SET_CHROM_CTL, WB_MODE_FORMATTER, 1); + + if (ret < 0) + return ret; + return 0; +} + +static inline int pwc_get_awb(struct pwc_device *pdev) +{ + unsigned char buf; + int ret; + + ret = RecvControlMsg(GET_CHROM_CTL, WB_MODE_FORMATTER, 1); + + if (ret < 0) + return ret; + return buf; +} + +static inline int pwc_set_red_gain(struct pwc_device *pdev, int value) +{ + unsigned char buf; + + if (value < 0) + value = 0; + if (value > 0xffff) + value = 0xffff; + /* only the msb is considered */ + buf = value >> 8; + return SendControlMsg(SET_CHROM_CTL, PRESET_MANUAL_RED_GAIN_FORMATTER, 1); +} + +static inline int pwc_get_red_gain(struct pwc_device *pdev, int *value) +{ + unsigned char buf; + int ret; + + ret = RecvControlMsg(GET_CHROM_CTL, PRESET_MANUAL_RED_GAIN_FORMATTER, 1); + if (ret < 0) + return ret; + *value = buf << 8; + return 0; +} + + +static inline int pwc_set_blue_gain(struct pwc_device *pdev, int value) +{ + unsigned char buf; + + if (value < 0) + value = 0; + if (value > 0xffff) + value = 0xffff; + /* only the msb is considered */ + buf = value >> 8; + return SendControlMsg(SET_CHROM_CTL, PRESET_MANUAL_BLUE_GAIN_FORMATTER, 1); +} + +static inline int pwc_get_blue_gain(struct pwc_device *pdev, int *value) +{ + unsigned char buf; + int ret; + + ret = RecvControlMsg(GET_CHROM_CTL, PRESET_MANUAL_BLUE_GAIN_FORMATTER, 1); + if (ret < 0) + return ret; + *value = buf << 8; + return 0; +} + + +/* The following two functions are different, since they only read the + internal red/blue gains, which may be different from the manual + gains set or read above. + */ +static inline int pwc_read_red_gain(struct pwc_device *pdev, int *value) +{ + unsigned char buf; + int ret; + + ret = RecvControlMsg(GET_STATUS_CTL, READ_RED_GAIN_FORMATTER, 1); + if (ret < 0) + return ret; + *value = buf << 8; + return 0; +} + +static inline int pwc_read_blue_gain(struct pwc_device *pdev, int *value) +{ + unsigned char buf; + int ret; + + ret = RecvControlMsg(GET_STATUS_CTL, READ_BLUE_GAIN_FORMATTER, 1); + if (ret < 0) + return ret; + *value = buf << 8; + return 0; +} + + +static inline int pwc_set_wb_speed(struct pwc_device *pdev, int speed) +{ + unsigned char buf; + + /* useful range is 0x01..0x20 */ + buf = speed / 0x7f0; + return SendControlMsg(SET_CHROM_CTL, AWB_CONTROL_SPEED_FORMATTER, 1); +} + +static inline int pwc_get_wb_speed(struct pwc_device *pdev, int *value) +{ + unsigned char buf; + int ret; + + ret = RecvControlMsg(GET_CHROM_CTL, AWB_CONTROL_SPEED_FORMATTER, 1); + if (ret < 0) + return ret; + *value = buf * 0x7f0; + return 0; +} + + +static inline int pwc_set_wb_delay(struct pwc_device *pdev, int delay) +{ + unsigned char buf; + + /* useful range is 0x01..0x3F */ + buf = (delay >> 10); + return SendControlMsg(SET_CHROM_CTL, AWB_CONTROL_DELAY_FORMATTER, 1); +} + +static inline int pwc_get_wb_delay(struct pwc_device *pdev, int *value) +{ + unsigned char buf; + int ret; + + ret = RecvControlMsg(GET_CHROM_CTL, AWB_CONTROL_DELAY_FORMATTER, 1); + if (ret < 0) + return ret; + *value = buf << 10; + return 0; +} + + +int pwc_set_leds(struct pwc_device *pdev, int on_value, int off_value) +{ + unsigned char buf[2]; + + if (pdev->type < 730) + return 0; + on_value /= 100; + off_value /= 100; + if (on_value < 0) + on_value = 0; + if (on_value > 0xff) + on_value = 0xff; + if (off_value < 0) + off_value = 0; + if (off_value > 0xff) + off_value = 0xff; + + buf[0] = on_value; + buf[1] = off_value; + + return SendControlMsg(SET_STATUS_CTL, LED_FORMATTER, 2); +} + +static int pwc_get_leds(struct pwc_device *pdev, int *on_value, int *off_value) +{ + unsigned char buf[2]; + int ret; + + if (pdev->type < 730) { + *on_value = -1; + *off_value = -1; + return 0; + } + + ret = RecvControlMsg(GET_STATUS_CTL, LED_FORMATTER, 2); + if (ret < 0) + return ret; + *on_value = buf[0] * 100; + *off_value = buf[1] * 100; + return 0; +} + +static inline int pwc_set_contour(struct pwc_device *pdev, int contour) +{ + unsigned char buf; + int ret; + + if (contour < 0) + buf = 0xff; /* auto contour on */ + else + buf = 0x0; /* auto contour off */ + ret = SendControlMsg(SET_LUM_CTL, AUTO_CONTOUR_FORMATTER, 1); + if (ret < 0) + return ret; + + if (contour < 0) + return 0; + if (contour > 0xffff) + contour = 0xffff; + + buf = (contour >> 10); /* contour preset is [0..3f] */ + ret = SendControlMsg(SET_LUM_CTL, PRESET_CONTOUR_FORMATTER, 1); + if (ret < 0) + return ret; + return 0; +} + +static inline int pwc_get_contour(struct pwc_device *pdev, int *contour) +{ + unsigned char buf; + int ret; + + ret = RecvControlMsg(GET_LUM_CTL, AUTO_CONTOUR_FORMATTER, 1); + if (ret < 0) + return ret; + + if (buf == 0) { + /* auto mode off, query current preset value */ + ret = RecvControlMsg(GET_LUM_CTL, PRESET_CONTOUR_FORMATTER, 1); + if (ret < 0) + return ret; + *contour = buf << 10; + } + else + *contour = -1; + return 0; +} + + +static inline int pwc_set_backlight(struct pwc_device *pdev, int backlight) +{ + unsigned char buf; + + if (backlight) + buf = 0xff; + else + buf = 0x0; + return SendControlMsg(SET_LUM_CTL, BACK_LIGHT_COMPENSATION_FORMATTER, 1); +} + +static inline int pwc_get_backlight(struct pwc_device *pdev, int *backlight) +{ + int ret; + unsigned char buf; + + ret = RecvControlMsg(GET_LUM_CTL, BACK_LIGHT_COMPENSATION_FORMATTER, 1); + if (ret < 0) + return ret; + *backlight = buf; + return 0; +} + + +static inline int pwc_set_flicker(struct pwc_device *pdev, int flicker) +{ + unsigned char buf; + + if (flicker) + buf = 0xff; + else + buf = 0x0; + return SendControlMsg(SET_LUM_CTL, FLICKERLESS_MODE_FORMATTER, 1); +} + +static inline int pwc_get_flicker(struct pwc_device *pdev, int *flicker) +{ + int ret; + unsigned char buf; + + ret = RecvControlMsg(GET_LUM_CTL, FLICKERLESS_MODE_FORMATTER, 1); + if (ret < 0) + return ret; + *flicker = buf; + return 0; +} + + +static inline int pwc_set_dynamic_noise(struct pwc_device *pdev, int noise) +{ + unsigned char buf; + + if (noise < 0) + noise = 0; + if (noise > 3) + noise = 3; + buf = noise; + return SendControlMsg(SET_LUM_CTL, DYNAMIC_NOISE_CONTROL_FORMATTER, 1); +} + +static inline int pwc_get_dynamic_noise(struct pwc_device *pdev, int *noise) +{ + int ret; + unsigned char buf; + + ret = RecvControlMsg(GET_LUM_CTL, DYNAMIC_NOISE_CONTROL_FORMATTER, 1); + if (ret < 0) + return ret; + *noise = buf; + return 0; +} + +static int pwc_mpt_reset(struct pwc_device *pdev, int flags) +{ + unsigned char buf; + + buf = flags & 0x03; // only lower two bits are currently used + return SendControlMsg(SET_MPT_CTL, PT_RESET_CONTROL_FORMATTER, 1); +} + +static inline int pwc_mpt_set_angle(struct pwc_device *pdev, int pan, int tilt) +{ + unsigned char buf[4]; + + /* set new relative angle; angles are expressed in degrees * 100, + but cam as .5 degree resolution, hence divide by 200. Also + the angle must be multiplied by 64 before it's send to + the cam (??) + */ + pan = 64 * pan / 100; + tilt = -64 * tilt / 100; /* positive tilt is down, which is not what the user would expect */ + buf[0] = pan & 0xFF; + buf[1] = (pan >> 8) & 0xFF; + buf[2] = tilt & 0xFF; + buf[3] = (tilt >> 8) & 0xFF; + return SendControlMsg(SET_MPT_CTL, PT_RELATIVE_CONTROL_FORMATTER, 4); +} + +static inline int pwc_mpt_get_status(struct pwc_device *pdev, struct pwc_mpt_status *status) +{ + int ret; + unsigned char buf[5]; + + ret = RecvControlMsg(GET_MPT_CTL, PT_STATUS_FORMATTER, 5); + if (ret < 0) + return ret; + status->status = buf[0] & 0x7; // 3 bits are used for reporting + status->time_pan = (buf[1] << 8) + buf[2]; + status->time_tilt = (buf[3] << 8) + buf[4]; + return 0; +} + + +int pwc_get_cmos_sensor(struct pwc_device *pdev, int *sensor) +{ + unsigned char buf; + int ret = -1, request; + + if (pdev->type < 675) + request = SENSOR_TYPE_FORMATTER1; + else if (pdev->type < 730) + return -1; /* The Vesta series doesn't have this call */ + else + request = SENSOR_TYPE_FORMATTER2; + + ret = RecvControlMsg(GET_STATUS_CTL, request, 1); + if (ret < 0) + return ret; + if (pdev->type < 675) + *sensor = buf | 0x100; + else + *sensor = buf; + return 0; +} + + + /* End of Add-Ons */ + /* ************************************************* */ + + +int pwc_ioctl(struct pwc_device *pdev, unsigned int cmd, void *arg) +{ + int ret = 0; + + switch(cmd) { + case VIDIOCPWCRUSER: + { + if (pwc_restore_user(pdev)) + ret = -EINVAL; + break; + } + + case VIDIOCPWCSUSER: + { + if (pwc_save_user(pdev)) + ret = -EINVAL; + break; + } + + case VIDIOCPWCFACTORY: + { + if (pwc_restore_factory(pdev)) + ret = -EINVAL; + break; + } + + case VIDIOCPWCSCQUAL: + { + int *qual = arg; + + if (*qual < 0 || *qual > 3) + ret = -EINVAL; + else + ret = pwc_try_video_mode(pdev, pdev->view.x, pdev->view.y, pdev->vframes, *qual, pdev->vsnapshot); + if (ret >= 0) + pdev->vcompression = *qual; + break; + } + + case VIDIOCPWCGCQUAL: + { + int *qual = arg; + *qual = pdev->vcompression; + break; + } + + case VIDIOCPWCPROBE: + { + struct pwc_probe *probe = arg; + strcpy(probe->name, pdev->vdev->name); + probe->type = pdev->type; + break; + } + + case VIDIOCPWCGSERIAL: + { + struct pwc_serial *serial = arg; + strcpy(serial->serial, pdev->serial); + break; + } + + case VIDIOCPWCSAGC: + { + int *agc = arg; + if (pwc_set_agc(pdev, *agc < 0 ? 1 : 0, *agc)) + ret = -EINVAL; + break; + } + + case VIDIOCPWCGAGC: + { + int *agc = arg; + + if (pwc_get_agc(pdev, agc)) + ret = -EINVAL; + break; + } + + case VIDIOCPWCSSHUTTER: + { + int *shutter_speed = arg; + ret = pwc_set_shutter_speed(pdev, *shutter_speed < 0 ? 1 : 0, *shutter_speed); + break; + } + + case VIDIOCPWCSAWB: + { + struct pwc_whitebalance *wb = arg; + + ret = pwc_set_awb(pdev, wb->mode); + if (ret >= 0 && wb->mode == PWC_WB_MANUAL) { + pwc_set_red_gain(pdev, wb->manual_red); + pwc_set_blue_gain(pdev, wb->manual_blue); + } + break; + } + + case VIDIOCPWCGAWB: + { + struct pwc_whitebalance *wb = arg; + + memset(wb, 0, sizeof(struct pwc_whitebalance)); + wb->mode = pwc_get_awb(pdev); + if (wb->mode < 0) + ret = -EINVAL; + else { + if (wb->mode == PWC_WB_MANUAL) { + ret = pwc_get_red_gain(pdev, &wb->manual_red); + if (ret < 0) + break; + ret = pwc_get_blue_gain(pdev, &wb->manual_blue); + if (ret < 0) + break; + } + if (wb->mode == PWC_WB_AUTO) { + ret = pwc_read_red_gain(pdev, &wb->read_red); + if (ret < 0) + break; + ret = pwc_read_blue_gain(pdev, &wb->read_blue); + if (ret < 0) + break; + } + } + break; + } + + case VIDIOCPWCSAWBSPEED: + { + struct pwc_wb_speed *wbs = arg; + + if (wbs->control_speed > 0) { + ret = pwc_set_wb_speed(pdev, wbs->control_speed); + } + if (wbs->control_delay > 0) { + ret = pwc_set_wb_delay(pdev, wbs->control_delay); + } + break; + } + + case VIDIOCPWCGAWBSPEED: + { + struct pwc_wb_speed *wbs = arg; + + ret = pwc_get_wb_speed(pdev, &wbs->control_speed); + if (ret < 0) + break; + ret = pwc_get_wb_delay(pdev, &wbs->control_delay); + if (ret < 0) + break; + break; + } + + case VIDIOCPWCSLED: + { + struct pwc_leds *leds = arg; + ret = pwc_set_leds(pdev, leds->led_on, leds->led_off); + break; + } + + + case VIDIOCPWCGLED: + { + struct pwc_leds *leds = arg; + ret = pwc_get_leds(pdev, &leds->led_on, &leds->led_off); + break; + } + + case VIDIOCPWCSCONTOUR: + { + int *contour = arg; + ret = pwc_set_contour(pdev, *contour); + break; + } + + case VIDIOCPWCGCONTOUR: + { + int *contour = arg; + ret = pwc_get_contour(pdev, contour); + break; + } + + case VIDIOCPWCSBACKLIGHT: + { + int *backlight = arg; + ret = pwc_set_backlight(pdev, *backlight); + break; + } + + case VIDIOCPWCGBACKLIGHT: + { + int *backlight = arg; + ret = pwc_get_backlight(pdev, backlight); + break; + } + + case VIDIOCPWCSFLICKER: + { + int *flicker = arg; + ret = pwc_set_flicker(pdev, *flicker); + break; + } + + case VIDIOCPWCGFLICKER: + { + int *flicker = arg; + ret = pwc_get_flicker(pdev, flicker); + break; + } + + case VIDIOCPWCSDYNNOISE: + { + int *dynnoise = arg; + ret = pwc_set_dynamic_noise(pdev, *dynnoise); + break; + } + + case VIDIOCPWCGDYNNOISE: + { + int *dynnoise = arg; + ret = pwc_get_dynamic_noise(pdev, dynnoise); + break; + } + + case VIDIOCPWCGREALSIZE: + { + struct pwc_imagesize *size = arg; + size->width = pdev->image.x; + size->height = pdev->image.y; + break; + } + + case VIDIOCPWCMPTRESET: + { + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + int *flags = arg; + + ret = pwc_mpt_reset(pdev, *flags); + if (ret >= 0) + { + pdev->pan_angle = 0; + pdev->tilt_angle = 0; + } + } + else + { + ret = -ENXIO; + } + break; + } + + case VIDIOCPWCMPTGRANGE: + { + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + struct pwc_mpt_range *range = arg; + *range = pdev->angle_range; + } + else + { + ret = -ENXIO; + } + break; + } + + case VIDIOCPWCMPTSANGLE: + { + int new_pan, new_tilt; + + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + struct pwc_mpt_angles *angles = arg; + /* The camera can only set relative angles, so + do some calculations when getting an absolute angle . + */ + if (angles->absolute) + { + new_pan = angles->pan; + new_tilt = angles->tilt; + } + else + { + new_pan = pdev->pan_angle + angles->pan; + new_tilt = pdev->tilt_angle + angles->tilt; + } + /* check absolute ranges */ + if (new_pan < pdev->angle_range.pan_min || + new_pan > pdev->angle_range.pan_max || + new_tilt < pdev->angle_range.tilt_min || + new_tilt > pdev->angle_range.tilt_max) + { + ret = -ERANGE; + } + else + { + /* go to relative range, check again */ + new_pan -= pdev->pan_angle; + new_tilt -= pdev->tilt_angle; + /* angles are specified in degrees * 100, thus the limit = 36000 */ + if (new_pan < -36000 || new_pan > 36000 || new_tilt < -36000 || new_tilt > 36000) + ret = -ERANGE; + } + if (ret == 0) /* no errors so far */ + { + ret = pwc_mpt_set_angle(pdev, new_pan, new_tilt); + if (ret >= 0) + { + pdev->pan_angle += new_pan; + pdev->tilt_angle += new_tilt; + } + if (ret == -EPIPE) /* stall -> out of range */ + ret = -ERANGE; + } + } + else + { + ret = -ENXIO; + } + break; + } + + case VIDIOCPWCMPTGANGLE: + { + + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + struct pwc_mpt_angles *angles = arg; + + angles->absolute = 1; + angles->pan = pdev->pan_angle; + angles->tilt = pdev->tilt_angle; + } + else + { + ret = -ENXIO; + } + break; + } + + case VIDIOCPWCMPTSTATUS: + { + if (pdev->features & FEATURE_MOTOR_PANTILT) + { + struct pwc_mpt_status *status = arg; + ret = pwc_mpt_get_status(pdev, status); + } + else + { + ret = -ENXIO; + } + break; + } + + case VIDIOCPWCGVIDCMD: + { + struct pwc_video_command *cmd = arg; + + cmd->type = pdev->type; + cmd->release = pdev->release; + cmd->command_len = pdev->cmd_len; + memcpy(&cmd->command_buf, pdev->cmd_buf, pdev->cmd_len); + cmd->bandlength = pdev->vbandlength; + cmd->frame_size = pdev->frame_size; + break; + } + /* + case VIDIOCPWCGVIDTABLE: + { + struct pwc_table_init_buffer *table = arg; + table->len = pdev->cmd_len; + memcpy(&table->buffer, pdev->decompress_data, pdev->decompressor->table_size); + break; + } + */ + + default: + ret = -ENOIOCTLCMD; + break; + } + + if (ret > 0) + return 0; + return ret; +} + + + diff --git a/drivers/media/video/pwc/pwc-if.c b/drivers/media/video/pwc/pwc-if.c new file mode 100644 index 00000000000..41418294a32 --- /dev/null +++ b/drivers/media/video/pwc/pwc-if.c @@ -0,0 +1,2205 @@ +/* Linux driver for Philips webcam + USB and Video4Linux interface part. + (C) 1999-2004 Nemosoft Unv. + (C) 2004 Luc Saillard (luc@saillard.org) + + NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx + driver and thus may have bugs that are not present in the original version. + Please send bug reports and support requests to <luc@saillard.org>. + The decompression routines have been implemented by reverse-engineering the + Nemosoft binary pwcx module. Caveat emptor. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +/* + This code forms the interface between the USB layers and the Philips + specific stuff. Some adanved stuff of the driver falls under an + NDA, signed between me and Philips B.V., Eindhoven, the Netherlands, and + is thus not distributed in source form. The binary pwcx.o module + contains the code that falls under the NDA. + + In case you're wondering: 'pwc' stands for "Philips WebCam", but + I really didn't want to type 'philips_web_cam' every time (I'm lazy as + any Linux kernel hacker, but I don't like uncomprehensible abbreviations + without explanation). + + Oh yes, convention: to disctinguish between all the various pointers to + device-structures, I use these names for the pointer variables: + udev: struct usb_device * + vdev: struct video_device * + pdev: struct pwc_devive * +*/ + +/* Contributors: + - Alvarado: adding whitebalance code + - Alistar Moire: QuickCam 3000 Pro device/product ID + - Tony Hoyle: Creative Labs Webcam 5 device/product ID + - Mark Burazin: solving hang in VIDIOCSYNC when camera gets unplugged + - Jk Fang: Sotec Afina Eye ID + - Xavier Roche: QuickCam Pro 4000 ID + - Jens Knudsen: QuickCam Zoom ID + - J. Debert: QuickCam for Notebooks ID +*/ + +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <asm/io.h> + +#include "pwc.h" +#include "pwc-ioctl.h" +#include "pwc-kiara.h" +#include "pwc-timon.h" +#include "pwc-uncompress.h" + +/* Function prototypes and driver templates */ + +/* hotplug device table support */ +static struct usb_device_id pwc_device_table [] = { + { USB_DEVICE(0x0471, 0x0302) }, /* Philips models */ + { USB_DEVICE(0x0471, 0x0303) }, + { USB_DEVICE(0x0471, 0x0304) }, + { USB_DEVICE(0x0471, 0x0307) }, + { USB_DEVICE(0x0471, 0x0308) }, + { USB_DEVICE(0x0471, 0x030C) }, + { USB_DEVICE(0x0471, 0x0310) }, + { USB_DEVICE(0x0471, 0x0311) }, + { USB_DEVICE(0x0471, 0x0312) }, + { USB_DEVICE(0x0471, 0x0313) }, /* the 'new' 720K */ + { USB_DEVICE(0x069A, 0x0001) }, /* Askey */ + { USB_DEVICE(0x046D, 0x08B0) }, /* Logitech QuickCam Pro 3000 */ + { USB_DEVICE(0x046D, 0x08B1) }, /* Logitech QuickCam Notebook Pro */ + { USB_DEVICE(0x046D, 0x08B2) }, /* Logitech QuickCam Pro 4000 */ + { USB_DEVICE(0x046D, 0x08B3) }, /* Logitech QuickCam Zoom (old model) */ + { USB_DEVICE(0x046D, 0x08B4) }, /* Logitech QuickCam Zoom (new model) */ + { USB_DEVICE(0x046D, 0x08B5) }, /* Logitech QuickCam Orbit/Sphere */ + { USB_DEVICE(0x046D, 0x08B6) }, /* Logitech (reserved) */ + { USB_DEVICE(0x046D, 0x08B7) }, /* Logitech (reserved) */ + { USB_DEVICE(0x046D, 0x08B8) }, /* Logitech (reserved) */ + { USB_DEVICE(0x055D, 0x9000) }, /* Samsung */ + { USB_DEVICE(0x055D, 0x9001) }, + { USB_DEVICE(0x041E, 0x400C) }, /* Creative Webcam 5 */ + { USB_DEVICE(0x041E, 0x4011) }, /* Creative Webcam Pro Ex */ + { USB_DEVICE(0x04CC, 0x8116) }, /* Afina Eye */ + { USB_DEVICE(0x06BE, 0x8116) }, /* new Afina Eye */ + { USB_DEVICE(0x0d81, 0x1910) }, /* Visionite */ + { USB_DEVICE(0x0d81, 0x1900) }, + { } +}; +MODULE_DEVICE_TABLE(usb, pwc_device_table); + +static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id *id); +static void usb_pwc_disconnect(struct usb_interface *intf); + +static struct usb_driver pwc_driver = { + .name = "Philips webcam", /* name */ + .id_table = pwc_device_table, + .probe = usb_pwc_probe, /* probe() */ + .disconnect = usb_pwc_disconnect, /* disconnect() */ +}; + +#define MAX_DEV_HINTS 20 +#define MAX_ISOC_ERRORS 20 + +static int default_size = PSZ_QCIF; +static int default_fps = 10; +static int default_fbufs = 3; /* Default number of frame buffers */ +static int default_mbufs = 2; /* Default number of mmap() buffers */ + int pwc_trace = TRACE_MODULE | TRACE_FLOW | TRACE_PWCX; +static int power_save = 0; +static int led_on = 100, led_off = 0; /* defaults to LED that is on while in use */ +static int pwc_preferred_compression = 2; /* 0..3 = uncompressed..high */ +static struct { + int type; + char serial_number[30]; + int device_node; + struct pwc_device *pdev; +} device_hint[MAX_DEV_HINTS]; + +/***/ + +static int pwc_video_open(struct inode *inode, struct file *file); +static int pwc_video_close(struct inode *inode, struct file *file); +static ssize_t pwc_video_read(struct file *file, char __user * buf, + size_t count, loff_t *ppos); +static unsigned int pwc_video_poll(struct file *file, poll_table *wait); +static int pwc_video_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, unsigned long arg); +static int pwc_video_mmap(struct file *file, struct vm_area_struct *vma); + +static struct file_operations pwc_fops = { + .owner = THIS_MODULE, + .open = pwc_video_open, + .release = pwc_video_close, + .read = pwc_video_read, + .poll = pwc_video_poll, + .mmap = pwc_video_mmap, + .ioctl = pwc_video_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; +static struct video_device pwc_template = { + .owner = THIS_MODULE, + .name = "Philips Webcam", /* Filled in later */ + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_PWC, + .release = video_device_release, + .fops = &pwc_fops, + .minor = -1, +}; + +/***************************************************************************/ + +/* Okay, this is some magic that I worked out and the reasoning behind it... + + The biggest problem with any USB device is of course: "what to do + when the user unplugs the device while it is in use by an application?" + We have several options: + 1) Curse them with the 7 plagues when they do (requires divine intervention) + 2) Tell them not to (won't work: they'll do it anyway) + 3) Oops the kernel (this will have a negative effect on a user's uptime) + 4) Do something sensible. + + Of course, we go for option 4. + + It happens that this device will be linked to two times, once from + usb_device and once from the video_device in their respective 'private' + pointers. This is done when the device is probed() and all initialization + succeeded. The pwc_device struct links back to both structures. + + When a device is unplugged while in use it will be removed from the + list of known USB devices; I also de-register it as a V4L device, but + unfortunately I can't free the memory since the struct is still in use + by the file descriptor. This free-ing is then deferend until the first + opportunity. Crude, but it works. + + A small 'advantage' is that if a user unplugs the cam and plugs it back + in, it should get assigned the same video device minor, but unfortunately + it's non-trivial to re-link the cam back to the video device... (that + would surely be magic! :)) +*/ + +/***************************************************************************/ +/* Private functions */ + +/* 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) + { + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr=(unsigned long) mem; + while (size > 0) + { + SetPageReserved(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) + { + adr=(unsigned long) mem; + while ((long) size > 0) + { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr+=PAGE_SIZE; + size-=PAGE_SIZE; + } + vfree(mem); + } +} + + + + +static int pwc_allocate_buffers(struct pwc_device *pdev) +{ + int i; + void *kbuf; + + Trace(TRACE_MEMORY, ">> pwc_allocate_buffers(pdev = 0x%p)\n", pdev); + + if (pdev == NULL) + return -ENXIO; + +#ifdef PWC_MAGIC + if (pdev->magic != PWC_MAGIC) { + Err("allocate_buffers(): magic failed.\n"); + return -ENXIO; + } +#endif + /* Allocate Isochronous pipe buffers */ + for (i = 0; i < MAX_ISO_BUFS; i++) { + if (pdev->sbuf[i].data == NULL) { + kbuf = kmalloc(ISO_BUFFER_SIZE, GFP_KERNEL); + if (kbuf == NULL) { + Err("Failed to allocate iso buffer %d.\n", i); + return -ENOMEM; + } + Trace(TRACE_MEMORY, "Allocated iso buffer at %p.\n", kbuf); + pdev->sbuf[i].data = kbuf; + memset(kbuf, 0, ISO_BUFFER_SIZE); + } + } + + /* Allocate frame buffer structure */ + if (pdev->fbuf == NULL) { + kbuf = kmalloc(default_fbufs * sizeof(struct pwc_frame_buf), GFP_KERNEL); + if (kbuf == NULL) { + Err("Failed to allocate frame buffer structure.\n"); + return -ENOMEM; + } + Trace(TRACE_MEMORY, "Allocated frame buffer structure at %p.\n", kbuf); + pdev->fbuf = kbuf; + memset(kbuf, 0, default_fbufs * sizeof(struct pwc_frame_buf)); + } + /* create frame buffers, and make circular ring */ + for (i = 0; i < default_fbufs; i++) { + if (pdev->fbuf[i].data == NULL) { + kbuf = vmalloc(PWC_FRAME_SIZE); /* need vmalloc since frame buffer > 128K */ + if (kbuf == NULL) { + Err("Failed to allocate frame buffer %d.\n", i); + return -ENOMEM; + } + Trace(TRACE_MEMORY, "Allocated frame buffer %d at %p.\n", i, kbuf); + pdev->fbuf[i].data = kbuf; + memset(kbuf, 128, PWC_FRAME_SIZE); + } + } + + /* Allocate decompressor table space */ + kbuf = NULL; + switch (pdev->type) + { + case 675: + case 680: + case 690: + case 720: + case 730: + case 740: + case 750: +#if 0 + Trace(TRACE_MEMORY,"private_data(%zu)\n",sizeof(struct pwc_dec23_private)); + kbuf = kmalloc(sizeof(struct pwc_dec23_private), GFP_KERNEL); /* Timon & Kiara */ + break; + case 645: + case 646: + /* TODO & FIXME */ + kbuf = kmalloc(sizeof(struct pwc_dec23_private), GFP_KERNEL); + break; +#endif + ; + } + pdev->decompress_data = kbuf; + + /* Allocate image buffer; double buffer for mmap() */ + kbuf = rvmalloc(default_mbufs * pdev->len_per_image); + if (kbuf == NULL) { + Err("Failed to allocate image buffer(s). needed (%d)\n",default_mbufs * pdev->len_per_image); + return -ENOMEM; + } + Trace(TRACE_MEMORY, "Allocated image buffer at %p.\n", kbuf); + pdev->image_data = kbuf; + for (i = 0; i < default_mbufs; i++) + pdev->image_ptr[i] = kbuf + i * pdev->len_per_image; + for (; i < MAX_IMAGES; i++) + pdev->image_ptr[i] = NULL; + + kbuf = NULL; + + Trace(TRACE_MEMORY, "<< pwc_allocate_buffers()\n"); + return 0; +} + +static void pwc_free_buffers(struct pwc_device *pdev) +{ + int i; + + Trace(TRACE_MEMORY, "Entering free_buffers(%p).\n", pdev); + + if (pdev == NULL) + return; +#ifdef PWC_MAGIC + if (pdev->magic != PWC_MAGIC) { + Err("free_buffers(): magic failed.\n"); + return; + } +#endif + + /* Release Iso-pipe buffers */ + for (i = 0; i < MAX_ISO_BUFS; i++) + if (pdev->sbuf[i].data != NULL) { + Trace(TRACE_MEMORY, "Freeing ISO buffer at %p.\n", pdev->sbuf[i].data); + kfree(pdev->sbuf[i].data); + pdev->sbuf[i].data = NULL; + } + + /* The same for frame buffers */ + if (pdev->fbuf != NULL) { + for (i = 0; i < default_fbufs; i++) { + if (pdev->fbuf[i].data != NULL) { + Trace(TRACE_MEMORY, "Freeing frame buffer %d at %p.\n", i, pdev->fbuf[i].data); + vfree(pdev->fbuf[i].data); + pdev->fbuf[i].data = NULL; + } + } + kfree(pdev->fbuf); + pdev->fbuf = NULL; + } + + /* Intermediate decompression buffer & tables */ + if (pdev->decompress_data != NULL) { + Trace(TRACE_MEMORY, "Freeing decompression buffer at %p.\n", pdev->decompress_data); + kfree(pdev->decompress_data); + pdev->decompress_data = NULL; + } + pdev->decompressor = NULL; + + /* Release image buffers */ + if (pdev->image_data != NULL) { + Trace(TRACE_MEMORY, "Freeing image buffer at %p.\n", pdev->image_data); + rvfree(pdev->image_data, default_mbufs * pdev->len_per_image); + } + pdev->image_data = NULL; + + Trace(TRACE_MEMORY, "Leaving free_buffers().\n"); +} + +/* The frame & image buffer mess. + + Yes, this is a mess. Well, it used to be simple, but alas... In this + module, 3 buffers schemes are used to get the data from the USB bus to + the user program. The first scheme involves the ISO buffers (called thus + since they transport ISO data from the USB controller), and not really + interesting. Suffices to say the data from this buffer is quickly + gathered in an interrupt handler (pwc_isoc_handler) and placed into the + frame buffer. + + The frame buffer is the second scheme, and is the central element here. + It collects the data from a single frame from the camera (hence, the + name). Frames are delimited by the USB camera with a short USB packet, + so that's easy to detect. The frame buffers form a list that is filled + by the camera+USB controller and drained by the user process through + either read() or mmap(). + + The image buffer is the third scheme, in which frames are decompressed + and converted into planar format. For mmap() there is more than + one image buffer available. + + The frame buffers provide the image buffering. In case the user process + is a bit slow, this introduces lag and some undesired side-effects. + The problem arises when the frame buffer is full. I used to drop the last + frame, which makes the data in the queue stale very quickly. But dropping + the frame at the head of the queue proved to be a litte bit more difficult. + I tried a circular linked scheme, but this introduced more problems than + it solved. + + Because filling and draining are completely asynchronous processes, this + requires some fiddling with pointers and mutexes. + + Eventually, I came up with a system with 2 lists: an 'empty' frame list + and a 'full' frame list: + * Initially, all frame buffers but one are on the 'empty' list; the one + remaining buffer is our initial fill frame. + * If a frame is needed for filling, we try to take it from the 'empty' + list, unless that list is empty, in which case we take the buffer at + the head of the 'full' list. + * When our fill buffer has been filled, it is appended to the 'full' + list. + * If a frame is needed by read() or mmap(), it is taken from the head of + the 'full' list, handled, and then appended to the 'empty' list. If no + buffer is present on the 'full' list, we wait. + The advantage is that the buffer that is currently being decompressed/ + converted, is on neither list, and thus not in our way (any other scheme + I tried had the problem of old data lingering in the queue). + + Whatever strategy you choose, it always remains a tradeoff: with more + frame buffers the chances of a missed frame are reduced. On the other + hand, on slower machines it introduces lag because the queue will + always be full. + */ + +/** + \brief Find next frame buffer to fill. Take from empty or full list, whichever comes first. + */ +static inline int pwc_next_fill_frame(struct pwc_device *pdev) +{ + int ret; + unsigned long flags; + + ret = 0; + spin_lock_irqsave(&pdev->ptrlock, flags); + if (pdev->fill_frame != NULL) { + /* append to 'full' list */ + if (pdev->full_frames == NULL) { + pdev->full_frames = pdev->fill_frame; + pdev->full_frames_tail = pdev->full_frames; + } + else { + pdev->full_frames_tail->next = pdev->fill_frame; + pdev->full_frames_tail = pdev->fill_frame; + } + } + if (pdev->empty_frames != NULL) { + /* We have empty frames available. That's easy */ + pdev->fill_frame = pdev->empty_frames; + pdev->empty_frames = pdev->empty_frames->next; + } + else { + /* Hmm. Take it from the full list */ +#if PWC_DEBUG + /* sanity check */ + if (pdev->full_frames == NULL) { + Err("Neither empty or full frames available!\n"); + spin_unlock_irqrestore(&pdev->ptrlock, flags); + return -EINVAL; + } +#endif + pdev->fill_frame = pdev->full_frames; + pdev->full_frames = pdev->full_frames->next; + ret = 1; + } + pdev->fill_frame->next = NULL; +#if PWC_DEBUG + Trace(TRACE_SEQUENCE, "Assigning sequence number %d.\n", pdev->sequence); + pdev->fill_frame->sequence = pdev->sequence++; +#endif + spin_unlock_irqrestore(&pdev->ptrlock, flags); + return ret; +} + + +/** + \brief Reset all buffers, pointers and lists, except for the image_used[] buffer. + + If the image_used[] buffer is cleared too, mmap()/VIDIOCSYNC will run into trouble. + */ +static void pwc_reset_buffers(struct pwc_device *pdev) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&pdev->ptrlock, flags); + pdev->full_frames = NULL; + pdev->full_frames_tail = NULL; + for (i = 0; i < default_fbufs; i++) { + pdev->fbuf[i].filled = 0; + if (i > 0) + pdev->fbuf[i].next = &pdev->fbuf[i - 1]; + else + pdev->fbuf->next = NULL; + } + pdev->empty_frames = &pdev->fbuf[default_fbufs - 1]; + pdev->empty_frames_tail = pdev->fbuf; + pdev->read_frame = NULL; + pdev->fill_frame = pdev->empty_frames; + pdev->empty_frames = pdev->empty_frames->next; + + pdev->image_read_pos = 0; + pdev->fill_image = 0; + spin_unlock_irqrestore(&pdev->ptrlock, flags); +} + + +/** + \brief Do all the handling for getting one frame: get pointer, decompress, advance pointers. + */ +static int pwc_handle_frame(struct pwc_device *pdev) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&pdev->ptrlock, flags); + /* First grab our read_frame; this is removed from all lists, so + we can release the lock after this without problems */ + if (pdev->read_frame != NULL) { + /* This can't theoretically happen */ + Err("Huh? Read frame still in use?\n"); + } + else { + if (pdev->full_frames == NULL) { + Err("Woops. No frames ready.\n"); + } + else { + pdev->read_frame = pdev->full_frames; + pdev->full_frames = pdev->full_frames->next; + pdev->read_frame->next = NULL; + } + + if (pdev->read_frame != NULL) { +#if PWC_DEBUG + Trace(TRACE_SEQUENCE, "Decompressing frame %d\n", pdev->read_frame->sequence); +#endif + /* Decompression is a lenghty process, so it's outside of the lock. + This gives the isoc_handler the opportunity to fill more frames + in the mean time. + */ + spin_unlock_irqrestore(&pdev->ptrlock, flags); + ret = pwc_decompress(pdev); + spin_lock_irqsave(&pdev->ptrlock, flags); + + /* We're done with read_buffer, tack it to the end of the empty buffer list */ + if (pdev->empty_frames == NULL) { + pdev->empty_frames = pdev->read_frame; + pdev->empty_frames_tail = pdev->empty_frames; + } + else { + pdev->empty_frames_tail->next = pdev->read_frame; + pdev->empty_frames_tail = pdev->read_frame; + } + pdev->read_frame = NULL; + } + } + spin_unlock_irqrestore(&pdev->ptrlock, flags); + return ret; +} + +/** + \brief Advance pointers of image buffer (after each user request) +*/ +static inline void pwc_next_image(struct pwc_device *pdev) +{ + pdev->image_used[pdev->fill_image] = 0; + pdev->fill_image = (pdev->fill_image + 1) % default_mbufs; +} + + +/* This gets called for the Isochronous pipe (video). This is done in + * interrupt time, so it has to be fast, not crash, and not stall. Neat. + */ +static void pwc_isoc_handler(struct urb *urb, struct pt_regs *regs) +{ + struct pwc_device *pdev; + int i, fst, flen; + int awake; + struct pwc_frame_buf *fbuf; + unsigned char *fillptr = NULL, *iso_buf = NULL; + + awake = 0; + pdev = (struct pwc_device *)urb->context; + if (pdev == NULL) { + Err("isoc_handler() called with NULL device?!\n"); + return; + } +#ifdef PWC_MAGIC + if (pdev->magic != PWC_MAGIC) { + Err("isoc_handler() called with bad magic!\n"); + return; + } +#endif + if (urb->status == -ENOENT || urb->status == -ECONNRESET) { + Trace(TRACE_OPEN, "pwc_isoc_handler(): URB (%p) unlinked %ssynchronuously.\n", urb, urb->status == -ENOENT ? "" : "a"); + return; + } + if (urb->status != -EINPROGRESS && urb->status != 0) { + const char *errmsg; + + errmsg = "Unknown"; + switch(urb->status) { + case -ENOSR: errmsg = "Buffer error (overrun)"; break; + case -EPIPE: errmsg = "Stalled (device not responding)"; break; + case -EOVERFLOW: errmsg = "Babble (bad cable?)"; break; + case -EPROTO: errmsg = "Bit-stuff error (bad cable?)"; break; + case -EILSEQ: errmsg = "CRC/Timeout (could be anything)"; break; + case -ETIMEDOUT: errmsg = "NAK (device does not respond)"; break; + } + Trace(TRACE_FLOW, "pwc_isoc_handler() called with status %d [%s].\n", urb->status, errmsg); + /* Give up after a number of contiguous errors on the USB bus. + Appearantly something is wrong so we simulate an unplug event. + */ + if (++pdev->visoc_errors > MAX_ISOC_ERRORS) + { + Info("Too many ISOC errors, bailing out.\n"); + pdev->error_status = EIO; + awake = 1; + wake_up_interruptible(&pdev->frameq); + } + goto handler_end; // ugly, but practical + } + + fbuf = pdev->fill_frame; + if (fbuf == NULL) { + Err("pwc_isoc_handler without valid fill frame.\n"); + awake = 1; + goto handler_end; + } + else { + fillptr = fbuf->data + fbuf->filled; + } + + /* Reset ISOC error counter. We did get here, after all. */ + pdev->visoc_errors = 0; + + /* vsync: 0 = don't copy data + 1 = sync-hunt + 2 = synched + */ + /* Compact data */ + for (i = 0; i < urb->number_of_packets; i++) { + fst = urb->iso_frame_desc[i].status; + flen = urb->iso_frame_desc[i].actual_length; + iso_buf = urb->transfer_buffer + urb->iso_frame_desc[i].offset; + if (fst == 0) { + if (flen > 0) { /* if valid data... */ + if (pdev->vsync > 0) { /* ...and we are not sync-hunting... */ + pdev->vsync = 2; + + /* ...copy data to frame buffer, if possible */ + if (flen + fbuf->filled > pdev->frame_total_size) { + Trace(TRACE_FLOW, "Frame buffer overflow (flen = %d, frame_total_size = %d).\n", flen, pdev->frame_total_size); + pdev->vsync = 0; /* Hmm, let's wait for an EOF (end-of-frame) */ + pdev->vframes_error++; + } + else { + memmove(fillptr, iso_buf, flen); + fillptr += flen; + } + } + fbuf->filled += flen; + } /* ..flen > 0 */ + + if (flen < pdev->vlast_packet_size) { + /* Shorter packet... We probably have the end of an image-frame; + wake up read() process and let select()/poll() do something. + Decompression is done in user time over there. + */ + if (pdev->vsync == 2) { + /* The ToUCam Fun CMOS sensor causes the firmware to send 2 or 3 bogus + frames on the USB wire after an exposure change. This conditition is + however detected in the cam and a bit is set in the header. + */ + if (pdev->type == 730) { + unsigned char *ptr = (unsigned char *)fbuf->data; + + if (ptr[1] == 1 && ptr[0] & 0x10) { +#if PWC_DEBUG + Debug("Hyundai CMOS sensor bug. Dropping frame %d.\n", fbuf->sequence); +#endif + pdev->drop_frames += 2; + pdev->vframes_error++; + } + if ((ptr[0] ^ pdev->vmirror) & 0x01) { + if (ptr[0] & 0x01) + Info("Snapshot button pressed.\n"); + else + Info("Snapshot button released.\n"); + } + if ((ptr[0] ^ pdev->vmirror) & 0x02) { + if (ptr[0] & 0x02) + Info("Image is mirrored.\n"); + else + Info("Image is normal.\n"); + } + pdev->vmirror = ptr[0] & 0x03; + /* Sometimes the trailer of the 730 is still sent as a 4 byte packet + after a short frame; this condition is filtered out specifically. A 4 byte + frame doesn't make sense anyway. + So we get either this sequence: + drop_bit set -> 4 byte frame -> short frame -> good frame + Or this one: + drop_bit set -> short frame -> good frame + So we drop either 3 or 2 frames in all! + */ + if (fbuf->filled == 4) + pdev->drop_frames++; + } + + /* In case we were instructed to drop the frame, do so silently. + The buffer pointers are not updated either (but the counters are reset below). + */ + if (pdev->drop_frames > 0) + pdev->drop_frames--; + else { + /* Check for underflow first */ + if (fbuf->filled < pdev->frame_total_size) { + Trace(TRACE_FLOW, "Frame buffer underflow (%d bytes); discarded.\n", fbuf->filled); + pdev->vframes_error++; + } + else { + /* Send only once per EOF */ + awake = 1; /* delay wake_ups */ + + /* Find our next frame to fill. This will always succeed, since we + * nick a frame from either empty or full list, but if we had to + * take it from the full list, it means a frame got dropped. + */ + if (pwc_next_fill_frame(pdev)) { + pdev->vframes_dumped++; + if ((pdev->vframe_count > FRAME_LOWMARK) && (pwc_trace & TRACE_FLOW)) { + if (pdev->vframes_dumped < 20) + Trace(TRACE_FLOW, "Dumping frame %d.\n", pdev->vframe_count); + if (pdev->vframes_dumped == 20) + Trace(TRACE_FLOW, "Dumping frame %d (last message).\n", pdev->vframe_count); + } + } + fbuf = pdev->fill_frame; + } + } /* !drop_frames */ + pdev->vframe_count++; + } + fbuf->filled = 0; + fillptr = fbuf->data; + pdev->vsync = 1; + } /* .. flen < last_packet_size */ + pdev->vlast_packet_size = flen; + } /* ..status == 0 */ +#if PWC_DEBUG + /* This is normally not interesting to the user, unless you are really debugging something */ + else { + static int iso_error = 0; + iso_error++; + if (iso_error < 20) + Trace(TRACE_FLOW, "Iso frame %d of USB has error %d\n", i, fst); + } +#endif + } + +handler_end: + if (awake) + wake_up_interruptible(&pdev->frameq); + + urb->dev = pdev->udev; + i = usb_submit_urb(urb, GFP_ATOMIC); + if (i != 0) + Err("Error (%d) re-submitting urb in pwc_isoc_handler.\n", i); +} + + +static int pwc_isoc_init(struct pwc_device *pdev) +{ + struct usb_device *udev; + struct urb *urb; + int i, j, ret; + + struct usb_interface *intf; + struct usb_host_interface *idesc = NULL; + + if (pdev == NULL) + return -EFAULT; + if (pdev->iso_init) + return 0; + pdev->vsync = 0; + udev = pdev->udev; + + /* Get the current alternate interface, adjust packet size */ + if (!udev->actconfig) + return -EFAULT; + + intf = usb_ifnum_to_if(udev, 0); + if (intf) + idesc = usb_altnum_to_altsetting(intf, pdev->valternate); + + if (!idesc) + return -EFAULT; + + /* Search video endpoint */ + pdev->vmax_packet_size = -1; + for (i = 0; i < idesc->desc.bNumEndpoints; i++) + if ((idesc->endpoint[i].desc.bEndpointAddress & 0xF) == pdev->vendpoint) { + pdev->vmax_packet_size = le16_to_cpu(idesc->endpoint[i].desc.wMaxPacketSize); + break; + } + + if (pdev->vmax_packet_size < 0 || pdev->vmax_packet_size > ISO_MAX_FRAME_SIZE) { + Err("Failed to find packet size for video endpoint in current alternate setting.\n"); + return -ENFILE; /* Odd error, that should be noticeable */ + } + + /* Set alternate interface */ + ret = 0; + Trace(TRACE_OPEN, "Setting alternate interface %d\n", pdev->valternate); + ret = usb_set_interface(pdev->udev, 0, pdev->valternate); + if (ret < 0) + return ret; + + for (i = 0; i < MAX_ISO_BUFS; i++) { + urb = usb_alloc_urb(ISO_FRAMES_PER_DESC, GFP_KERNEL); + if (urb == NULL) { + Err("Failed to allocate urb %d\n", i); + ret = -ENOMEM; + break; + } + pdev->sbuf[i].urb = urb; + Trace(TRACE_MEMORY, "Allocated URB at 0x%p\n", urb); + } + if (ret) { + /* De-allocate in reverse order */ + while (i >= 0) { + if (pdev->sbuf[i].urb != NULL) + usb_free_urb(pdev->sbuf[i].urb); + pdev->sbuf[i].urb = NULL; + i--; + } + return ret; + } + + /* init URB structure */ + for (i = 0; i < MAX_ISO_BUFS; i++) { + urb = pdev->sbuf[i].urb; + + urb->interval = 1; // devik + urb->dev = udev; + urb->pipe = usb_rcvisocpipe(udev, pdev->vendpoint); + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = pdev->sbuf[i].data; + urb->transfer_buffer_length = ISO_BUFFER_SIZE; + urb->complete = pwc_isoc_handler; + urb->context = pdev; + urb->start_frame = 0; + urb->number_of_packets = ISO_FRAMES_PER_DESC; + for (j = 0; j < ISO_FRAMES_PER_DESC; j++) { + urb->iso_frame_desc[j].offset = j * ISO_MAX_FRAME_SIZE; + urb->iso_frame_desc[j].length = pdev->vmax_packet_size; + } + } + + /* link */ + for (i = 0; i < MAX_ISO_BUFS; i++) { + ret = usb_submit_urb(pdev->sbuf[i].urb, GFP_KERNEL); + if (ret) + Err("isoc_init() submit_urb %d failed with error %d\n", i, ret); + else + Trace(TRACE_MEMORY, "URB 0x%p submitted.\n", pdev->sbuf[i].urb); + } + + /* All is done... */ + pdev->iso_init = 1; + Trace(TRACE_OPEN, "<< pwc_isoc_init()\n"); + return 0; +} + +static void pwc_isoc_cleanup(struct pwc_device *pdev) +{ + int i; + + Trace(TRACE_OPEN, ">> pwc_isoc_cleanup()\n"); + if (pdev == NULL) + return; + + /* Unlinking ISOC buffers one by one */ + for (i = 0; i < MAX_ISO_BUFS; i++) { + struct urb *urb; + + urb = pdev->sbuf[i].urb; + if (urb != 0) { + if (pdev->iso_init) { + Trace(TRACE_MEMORY, "Unlinking URB %p\n", urb); + usb_kill_urb(urb); + } + Trace(TRACE_MEMORY, "Freeing URB\n"); + usb_free_urb(urb); + pdev->sbuf[i].urb = NULL; + } + } + + /* Stop camera, but only if we are sure the camera is still there (unplug + is signalled by EPIPE) + */ + if (pdev->error_status && pdev->error_status != EPIPE) { + Trace(TRACE_OPEN, "Setting alternate interface 0.\n"); + usb_set_interface(pdev->udev, 0, 0); + } + + pdev->iso_init = 0; + Trace(TRACE_OPEN, "<< pwc_isoc_cleanup()\n"); +} + +int pwc_try_video_mode(struct pwc_device *pdev, int width, int height, int new_fps, int new_compression, int new_snapshot) +{ + int ret, start; + + /* Stop isoc stuff */ + pwc_isoc_cleanup(pdev); + /* Reset parameters */ + pwc_reset_buffers(pdev); + /* Try to set video mode... */ + start = ret = pwc_set_video_mode(pdev, width, height, new_fps, new_compression, new_snapshot); + if (ret) { + Trace(TRACE_FLOW, "pwc_set_video_mode attempt 1 failed.\n"); + /* That failed... restore old mode (we know that worked) */ + start = pwc_set_video_mode(pdev, pdev->view.x, pdev->view.y, pdev->vframes, pdev->vcompression, pdev->vsnapshot); + if (start) { + Trace(TRACE_FLOW, "pwc_set_video_mode attempt 2 failed.\n"); + } + } + if (start == 0) + { + if (pwc_isoc_init(pdev) < 0) + { + Info("Failed to restart ISOC transfers in pwc_try_video_mode.\n"); + ret = -EAGAIN; /* let's try again, who knows if it works a second time */ + } + } + pdev->drop_frames++; /* try to avoid garbage during switch */ + return ret; /* Return original error code */ +} + + +/***************************************************************************/ +/* Video4Linux functions */ + +static int pwc_video_open(struct inode *inode, struct file *file) +{ + int i; + struct video_device *vdev = video_devdata(file); + struct pwc_device *pdev; + + Trace(TRACE_OPEN, ">> video_open called(vdev = 0x%p).\n", vdev); + + pdev = (struct pwc_device *)vdev->priv; + if (pdev == NULL) + BUG(); + if (pdev->vopen) + return -EBUSY; + + down(&pdev->modlock); + if (!pdev->usb_init) { + Trace(TRACE_OPEN, "Doing first time initialization.\n"); + pdev->usb_init = 1; + + if (pwc_trace & TRACE_OPEN) + { + /* Query sensor type */ + const char *sensor_type = NULL; + int ret; + + ret = pwc_get_cmos_sensor(pdev, &i); + if (ret >= 0) + { + switch(i) { + case 0x00: sensor_type = "Hyundai CMOS sensor"; break; + case 0x20: sensor_type = "Sony CCD sensor + TDA8787"; break; + case 0x2E: sensor_type = "Sony CCD sensor + Exas 98L59"; break; + case 0x2F: sensor_type = "Sony CCD sensor + ADI 9804"; break; + case 0x30: sensor_type = "Sharp CCD sensor + TDA8787"; break; + case 0x3E: sensor_type = "Sharp CCD sensor + Exas 98L59"; break; + case 0x3F: sensor_type = "Sharp CCD sensor + ADI 9804"; break; + case 0x40: sensor_type = "UPA 1021 sensor"; break; + case 0x100: sensor_type = "VGA sensor"; break; + case 0x101: sensor_type = "PAL MR sensor"; break; + default: sensor_type = "unknown type of sensor"; break; + } + } + if (sensor_type != NULL) + Info("This %s camera is equipped with a %s (%d).\n", pdev->vdev->name, sensor_type, i); + } + } + + /* Turn on camera */ + if (power_save) { + i = pwc_camera_power(pdev, 1); + if (i < 0) + Info("Failed to restore power to the camera! (%d)\n", i); + } + /* Set LED on/off time */ + if (pwc_set_leds(pdev, led_on, led_off) < 0) + Info("Failed to set LED on/off time.\n"); + + pwc_construct(pdev); /* set min/max sizes correct */ + + /* So far, so good. Allocate memory. */ + i = pwc_allocate_buffers(pdev); + if (i < 0) { + Trace(TRACE_OPEN, "Failed to allocate buffer memory.\n"); + up(&pdev->modlock); + return i; + } + + /* Reset buffers & parameters */ + pwc_reset_buffers(pdev); + for (i = 0; i < default_mbufs; i++) + pdev->image_used[i] = 0; + pdev->vframe_count = 0; + pdev->vframes_dumped = 0; + pdev->vframes_error = 0; + pdev->visoc_errors = 0; + pdev->error_status = 0; +#if PWC_DEBUG + pdev->sequence = 0; +#endif + pwc_construct(pdev); /* set min/max sizes correct */ + + /* Set some defaults */ + pdev->vsnapshot = 0; + + /* Start iso pipe for video; first try the last used video size + (or the default one); if that fails try QCIF/10 or QSIF/10; + it that fails too, give up. + */ + i = pwc_set_video_mode(pdev, pwc_image_sizes[pdev->vsize].x, pwc_image_sizes[pdev->vsize].y, pdev->vframes, pdev->vcompression, 0); + if (i) { + Trace(TRACE_OPEN, "First attempt at set_video_mode failed.\n"); + if (pdev->type == 730 || pdev->type == 740 || pdev->type == 750) + i = pwc_set_video_mode(pdev, pwc_image_sizes[PSZ_QSIF].x, pwc_image_sizes[PSZ_QSIF].y, 10, pdev->vcompression, 0); + else + i = pwc_set_video_mode(pdev, pwc_image_sizes[PSZ_QCIF].x, pwc_image_sizes[PSZ_QCIF].y, 10, pdev->vcompression, 0); + } + if (i) { + Trace(TRACE_OPEN, "Second attempt at set_video_mode failed.\n"); + up(&pdev->modlock); + return i; + } + + i = pwc_isoc_init(pdev); + if (i) { + Trace(TRACE_OPEN, "Failed to init ISOC stuff = %d.\n", i); + up(&pdev->modlock); + return i; + } + + pdev->vopen++; + file->private_data = vdev; + up(&pdev->modlock); + Trace(TRACE_OPEN, "<< video_open() returns 0.\n"); + return 0; +} + +/* Note that all cleanup is done in the reverse order as in _open */ +static int pwc_video_close(struct inode *inode, struct file *file) +{ + struct video_device *vdev = file->private_data; + struct pwc_device *pdev; + int i; + + Trace(TRACE_OPEN, ">> video_close called(vdev = 0x%p).\n", vdev); + + pdev = (struct pwc_device *)vdev->priv; + if (pdev->vopen == 0) + Info("video_close() called on closed device?\n"); + + /* Dump statistics, but only if a reasonable amount of frames were + processed (to prevent endless log-entries in case of snap-shot + programs) + */ + if (pdev->vframe_count > 20) + Info("Closing video device: %d frames received, dumped %d frames, %d frames with errors.\n", pdev->vframe_count, pdev->vframes_dumped, pdev->vframes_error); + + switch (pdev->type) + { + case 675: + case 680: + case 690: + case 720: + case 730: + case 740: + case 750: +/* pwc_dec23_exit(); *//* Timon & Kiara */ + break; + case 645: + case 646: +/* pwc_dec1_exit(); */ + break; + } + + pwc_isoc_cleanup(pdev); + pwc_free_buffers(pdev); + + /* Turn off LEDS and power down camera, but only when not unplugged */ + if (pdev->error_status != EPIPE) { + /* Turn LEDs off */ + if (pwc_set_leds(pdev, 0, 0) < 0) + Info("Failed to set LED on/off time.\n"); + if (power_save) { + i = pwc_camera_power(pdev, 0); + if (i < 0) + Err("Failed to power down camera (%d)\n", i); + } + } + pdev->vopen = 0; + Trace(TRACE_OPEN, "<< video_close()\n"); + return 0; +} + +/* + * FIXME: what about two parallel reads ???? + * ANSWER: Not supported. You can't open the device more than once, + despite what the V4L1 interface says. First, I don't see + the need, second there's no mechanism of alerting the + 2nd/3rd/... process of events like changing image size. + And I don't see the point of blocking that for the + 2nd/3rd/... process. + In multi-threaded environments reading parallel from any + device is tricky anyhow. + */ + +static ssize_t pwc_video_read(struct file *file, char __user * buf, + size_t count, loff_t *ppos) +{ + struct video_device *vdev = file->private_data; + struct pwc_device *pdev; + int noblock = file->f_flags & O_NONBLOCK; + DECLARE_WAITQUEUE(wait, current); + int bytes_to_read; + + Trace(TRACE_READ, "video_read(0x%p, %p, %zu) called.\n", vdev, buf, count); + if (vdev == NULL) + return -EFAULT; + pdev = vdev->priv; + if (pdev == NULL) + return -EFAULT; + if (pdev->error_status) + return -pdev->error_status; /* Something happened, report what. */ + + /* In case we're doing partial reads, we don't have to wait for a frame */ + if (pdev->image_read_pos == 0) { + /* Do wait queueing according to the (doc)book */ + add_wait_queue(&pdev->frameq, &wait); + while (pdev->full_frames == NULL) { + /* Check for unplugged/etc. here */ + if (pdev->error_status) { + remove_wait_queue(&pdev->frameq, &wait); + set_current_state(TASK_RUNNING); + return -pdev->error_status ; + } + if (noblock) { + remove_wait_queue(&pdev->frameq, &wait); + set_current_state(TASK_RUNNING); + return -EWOULDBLOCK; + } + if (signal_pending(current)) { + remove_wait_queue(&pdev->frameq, &wait); + set_current_state(TASK_RUNNING); + return -ERESTARTSYS; + } + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } + remove_wait_queue(&pdev->frameq, &wait); + set_current_state(TASK_RUNNING); + + /* Decompress and release frame */ + if (pwc_handle_frame(pdev)) + return -EFAULT; + } + + Trace(TRACE_READ, "Copying data to user space.\n"); + if (pdev->vpalette == VIDEO_PALETTE_RAW) + bytes_to_read = pdev->frame_size; + else + bytes_to_read = pdev->view.size; + + /* copy bytes to user space; we allow for partial reads */ + if (count + pdev->image_read_pos > bytes_to_read) + count = bytes_to_read - pdev->image_read_pos; + if (copy_to_user(buf, pdev->image_ptr[pdev->fill_image] + pdev->image_read_pos, count)) + return -EFAULT; + pdev->image_read_pos += count; + if (pdev->image_read_pos >= bytes_to_read) { /* All data has been read */ + pdev->image_read_pos = 0; + pwc_next_image(pdev); + } + return count; +} + +static unsigned int pwc_video_poll(struct file *file, poll_table *wait) +{ + struct video_device *vdev = file->private_data; + struct pwc_device *pdev; + + if (vdev == NULL) + return -EFAULT; + pdev = vdev->priv; + if (pdev == NULL) + return -EFAULT; + + poll_wait(file, &pdev->frameq, wait); + if (pdev->error_status) + return POLLERR; + if (pdev->full_frames != NULL) /* we have frames waiting */ + return (POLLIN | POLLRDNORM); + + return 0; +} + +static int pwc_video_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *vdev = file->private_data; + struct pwc_device *pdev; + DECLARE_WAITQUEUE(wait, current); + + if (vdev == NULL) + return -EFAULT; + pdev = vdev->priv; + if (pdev == NULL) + return -EFAULT; + + switch (cmd) { + /* Query cabapilities */ + case VIDIOCGCAP: + { + struct video_capability *caps = arg; + + strcpy(caps->name, vdev->name); + caps->type = VID_TYPE_CAPTURE; + caps->channels = 1; + caps->audios = 1; + caps->minwidth = pdev->view_min.x; + caps->minheight = pdev->view_min.y; + caps->maxwidth = pdev->view_max.x; + caps->maxheight = pdev->view_max.y; + break; + } + + /* Channel functions (simulate 1 channel) */ + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + + if (v->channel != 0) + return -EINVAL; + v->flags = 0; + v->tuners = 0; + v->type = VIDEO_TYPE_CAMERA; + strcpy(v->name, "Webcam"); + return 0; + } + + case VIDIOCSCHAN: + { + /* The spec says the argument is an integer, but + the bttv driver uses a video_channel arg, which + makes sense becasue it also has the norm flag. + */ + struct video_channel *v = arg; + if (v->channel != 0) + return -EINVAL; + return 0; + } + + + /* Picture functions; contrast etc. */ + case VIDIOCGPICT: + { + struct video_picture *p = arg; + int val; + + val = pwc_get_brightness(pdev); + if (val >= 0) + p->brightness = val; + else + p->brightness = 0xffff; + val = pwc_get_contrast(pdev); + if (val >= 0) + p->contrast = val; + else + p->contrast = 0xffff; + /* Gamma, Whiteness, what's the difference? :) */ + val = pwc_get_gamma(pdev); + if (val >= 0) + p->whiteness = val; + else + p->whiteness = 0xffff; + val = pwc_get_saturation(pdev); + if (val >= 0) + p->colour = val; + else + p->colour = 0xffff; + p->depth = 24; + p->palette = pdev->vpalette; + p->hue = 0xFFFF; /* N/A */ + break; + } + + case VIDIOCSPICT: + { + struct video_picture *p = arg; + /* + * FIXME: Suppose we are mid read + ANSWER: No problem: the firmware of the camera + can handle brightness/contrast/etc + changes at _any_ time, and the palette + is used exactly once in the uncompress + routine. + */ + pwc_set_brightness(pdev, p->brightness); + pwc_set_contrast(pdev, p->contrast); + pwc_set_gamma(pdev, p->whiteness); + pwc_set_saturation(pdev, p->colour); + if (p->palette && p->palette != pdev->vpalette) { + switch (p->palette) { + case VIDEO_PALETTE_YUV420P: + case VIDEO_PALETTE_RAW: + pdev->vpalette = p->palette; + return pwc_try_video_mode(pdev, pdev->image.x, pdev->image.y, pdev->vframes, pdev->vcompression, pdev->vsnapshot); + break; + default: + return -EINVAL; + break; + } + } + break; + } + + /* Window/size parameters */ + case VIDIOCGWIN: + { + struct video_window *vw = arg; + + vw->x = 0; + vw->y = 0; + vw->width = pdev->view.x; + vw->height = pdev->view.y; + vw->chromakey = 0; + vw->flags = (pdev->vframes << PWC_FPS_SHIFT) | + (pdev->vsnapshot ? PWC_FPS_SNAPSHOT : 0); + break; + } + + case VIDIOCSWIN: + { + struct video_window *vw = arg; + int fps, snapshot, ret; + + fps = (vw->flags & PWC_FPS_FRMASK) >> PWC_FPS_SHIFT; + snapshot = vw->flags & PWC_FPS_SNAPSHOT; + if (fps == 0) + fps = pdev->vframes; + if (pdev->view.x == vw->width && pdev->view.y && fps == pdev->vframes && snapshot == pdev->vsnapshot) + return 0; + ret = pwc_try_video_mode(pdev, vw->width, vw->height, fps, pdev->vcompression, snapshot); + if (ret) + return ret; + break; + } + + /* We don't have overlay support (yet) */ + case VIDIOCGFBUF: + { + struct video_buffer *vb = arg; + + memset(vb,0,sizeof(*vb)); + break; + } + + /* mmap() functions */ + case VIDIOCGMBUF: + { + /* Tell the user program how much memory is needed for a mmap() */ + struct video_mbuf *vm = arg; + int i; + + memset(vm, 0, sizeof(*vm)); + vm->size = default_mbufs * pdev->len_per_image; + vm->frames = default_mbufs; /* double buffering should be enough for most applications */ + for (i = 0; i < default_mbufs; i++) + vm->offsets[i] = i * pdev->len_per_image; + break; + } + + case VIDIOCMCAPTURE: + { + /* Start capture into a given image buffer (called 'frame' in video_mmap structure) */ + struct video_mmap *vm = arg; + + Trace(TRACE_READ, "VIDIOCMCAPTURE: %dx%d, frame %d, format %d\n", vm->width, vm->height, vm->frame, vm->format); + if (vm->frame < 0 || vm->frame >= default_mbufs) + return -EINVAL; + + /* xawtv is nasty. It probes the available palettes + by setting a very small image size and trying + various palettes... The driver doesn't support + such small images, so I'm working around it. + */ + if (vm->format) + { + switch (vm->format) + { + case VIDEO_PALETTE_YUV420P: + case VIDEO_PALETTE_RAW: + break; + default: + return -EINVAL; + break; + } + } + + if ((vm->width != pdev->view.x || vm->height != pdev->view.y) && + (vm->width >= pdev->view_min.x && vm->height >= pdev->view_min.y)) { + int ret; + + Trace(TRACE_OPEN, "VIDIOCMCAPTURE: changing size to please xawtv :-(.\n"); + ret = pwc_try_video_mode(pdev, vm->width, vm->height, pdev->vframes, pdev->vcompression, pdev->vsnapshot); + if (ret) + return ret; + } /* ... size mismatch */ + + /* FIXME: should we lock here? */ + if (pdev->image_used[vm->frame]) + return -EBUSY; /* buffer wasn't available. Bummer */ + pdev->image_used[vm->frame] = 1; + + /* Okay, we're done here. In the SYNC call we wait until a + frame comes available, then expand image into the given + buffer. + In contrast to the CPiA cam the Philips cams deliver a + constant stream, almost like a grabber card. Also, + we have separate buffers for the rawdata and the image, + meaning we can nearly always expand into the requested buffer. + */ + Trace(TRACE_READ, "VIDIOCMCAPTURE done.\n"); + break; + } + + case VIDIOCSYNC: + { + /* The doc says: "Whenever a buffer is used it should + call VIDIOCSYNC to free this frame up and continue." + + The only odd thing about this whole procedure is + that MCAPTURE flags the buffer as "in use", and + SYNC immediately unmarks it, while it isn't + after SYNC that you know that the buffer actually + got filled! So you better not start a CAPTURE in + the same frame immediately (use double buffering). + This is not a problem for this cam, since it has + extra intermediate buffers, but a hardware + grabber card will then overwrite the buffer + you're working on. + */ + int *mbuf = arg; + int ret; + + Trace(TRACE_READ, "VIDIOCSYNC called (%d).\n", *mbuf); + + /* bounds check */ + if (*mbuf < 0 || *mbuf >= default_mbufs) + return -EINVAL; + /* check if this buffer was requested anyway */ + if (pdev->image_used[*mbuf] == 0) + return -EINVAL; + + /* Add ourselves to the frame wait-queue. + + FIXME: needs auditing for safety. + QUESTION: In what respect? I think that using the + frameq is safe now. + */ + add_wait_queue(&pdev->frameq, &wait); + while (pdev->full_frames == NULL) { + if (pdev->error_status) { + remove_wait_queue(&pdev->frameq, &wait); + set_current_state(TASK_RUNNING); + return -pdev->error_status; + } + + if (signal_pending(current)) { + remove_wait_queue(&pdev->frameq, &wait); + set_current_state(TASK_RUNNING); + return -ERESTARTSYS; + } + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } + remove_wait_queue(&pdev->frameq, &wait); + set_current_state(TASK_RUNNING); + + /* The frame is ready. Expand in the image buffer + requested by the user. I don't care if you + mmap() 5 buffers and request data in this order: + buffer 4 2 3 0 1 2 3 0 4 3 1 . . . + Grabber hardware may not be so forgiving. + */ + Trace(TRACE_READ, "VIDIOCSYNC: frame ready.\n"); + pdev->fill_image = *mbuf; /* tell in which buffer we want the image to be expanded */ + /* Decompress, etc */ + ret = pwc_handle_frame(pdev); + pdev->image_used[*mbuf] = 0; + if (ret) + return -EFAULT; + break; + } + + case VIDIOCGAUDIO: + { + struct video_audio *v = arg; + + strcpy(v->name, "Microphone"); + v->audio = -1; /* unknown audio minor */ + v->flags = 0; + v->mode = VIDEO_SOUND_MONO; + v->volume = 0; + v->bass = 0; + v->treble = 0; + v->balance = 0x8000; + v->step = 1; + break; + } + + case VIDIOCSAUDIO: + { + /* Dummy: nothing can be set */ + break; + } + + case VIDIOCGUNIT: + { + struct video_unit *vu = arg; + + vu->video = pdev->vdev->minor & 0x3F; + vu->audio = -1; /* not known yet */ + vu->vbi = -1; + vu->radio = -1; + vu->teletext = -1; + break; + } + default: + return pwc_ioctl(pdev, cmd, arg); + } /* ..switch */ + return 0; +} + +static int pwc_video_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, pwc_video_do_ioctl); +} + + +static int pwc_video_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *vdev = file->private_data; + struct pwc_device *pdev; + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end-vma->vm_start; + unsigned long page, pos; + + Trace(TRACE_MEMORY, "mmap(0x%p, 0x%lx, %lu) called.\n", vdev, start, size); + pdev = vdev->priv; + + vma->vm_flags |= VM_IO; + + pos = (unsigned long)pdev->image_data; + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + return 0; +} + +/***************************************************************************/ +/* USB functions */ + +/* This function gets called when a new device is plugged in or the usb core + * is loaded. + */ + +static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct pwc_device *pdev = NULL; + int vendor_id, product_id, type_id; + int i, hint; + int features = 0; + int video_nr = -1; /* default: use next available device */ + char serial_number[30], *name; + + /* Check if we can handle this device */ + Trace(TRACE_PROBE, "probe() called [%04X %04X], if %d\n", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct), + intf->altsetting->desc.bInterfaceNumber); + + /* the interfaces are probed one by one. We are only interested in the + video interface (0) now. + Interface 1 is the Audio Control, and interface 2 Audio itself. + */ + if (intf->altsetting->desc.bInterfaceNumber > 0) + return -ENODEV; + + vendor_id = le16_to_cpu(udev->descriptor.idVendor); + product_id = le16_to_cpu(udev->descriptor.idProduct); + + if (vendor_id == 0x0471) { + switch (product_id) { + case 0x0302: + Info("Philips PCA645VC USB webcam detected.\n"); + name = "Philips 645 webcam"; + type_id = 645; + break; + case 0x0303: + Info("Philips PCA646VC USB webcam detected.\n"); + name = "Philips 646 webcam"; + type_id = 646; + break; + case 0x0304: + Info("Askey VC010 type 2 USB webcam detected.\n"); + name = "Askey VC010 webcam"; + type_id = 646; + break; + case 0x0307: + Info("Philips PCVC675K (Vesta) USB webcam detected.\n"); + name = "Philips 675 webcam"; + type_id = 675; + break; + case 0x0308: + Info("Philips PCVC680K (Vesta Pro) USB webcam detected.\n"); + name = "Philips 680 webcam"; + type_id = 680; + break; + case 0x030C: + Info("Philips PCVC690K (Vesta Pro Scan) USB webcam detected.\n"); + name = "Philips 690 webcam"; + type_id = 690; + break; + case 0x0310: + Info("Philips PCVC730K (ToUCam Fun)/PCVC830 (ToUCam II) USB webcam detected.\n"); + name = "Philips 730 webcam"; + type_id = 730; + break; + case 0x0311: + Info("Philips PCVC740K (ToUCam Pro)/PCVC840 (ToUCam II) USB webcam detected.\n"); + name = "Philips 740 webcam"; + type_id = 740; + break; + case 0x0312: + Info("Philips PCVC750K (ToUCam Pro Scan) USB webcam detected.\n"); + name = "Philips 750 webcam"; + type_id = 750; + break; + case 0x0313: + Info("Philips PCVC720K/40 (ToUCam XS) USB webcam detected.\n"); + name = "Philips 720K/40 webcam"; + type_id = 720; + break; + default: + return -ENODEV; + break; + } + } + else if (vendor_id == 0x069A) { + switch(product_id) { + case 0x0001: + Info("Askey VC010 type 1 USB webcam detected.\n"); + name = "Askey VC010 webcam"; + type_id = 645; + break; + default: + return -ENODEV; + break; + } + } + else if (vendor_id == 0x046d) { + switch(product_id) { + case 0x08b0: + Info("Logitech QuickCam Pro 3000 USB webcam detected.\n"); + name = "Logitech QuickCam Pro 3000"; + type_id = 740; /* CCD sensor */ + break; + case 0x08b1: + Info("Logitech QuickCam Notebook Pro USB webcam detected.\n"); + name = "Logitech QuickCam Notebook Pro"; + type_id = 740; /* CCD sensor */ + break; + case 0x08b2: + Info("Logitech QuickCam 4000 Pro USB webcam detected.\n"); + name = "Logitech QuickCam Pro 4000"; + type_id = 740; /* CCD sensor */ + break; + case 0x08b3: + Info("Logitech QuickCam Zoom USB webcam detected.\n"); + name = "Logitech QuickCam Zoom"; + type_id = 740; /* CCD sensor */ + break; + case 0x08B4: + Info("Logitech QuickCam Zoom (new model) USB webcam detected.\n"); + name = "Logitech QuickCam Zoom"; + type_id = 740; /* CCD sensor */ + break; + case 0x08b5: + Info("Logitech QuickCam Orbit/Sphere USB webcam detected.\n"); + name = "Logitech QuickCam Orbit"; + type_id = 740; /* CCD sensor */ + features |= FEATURE_MOTOR_PANTILT; + break; + case 0x08b6: + case 0x08b7: + case 0x08b8: + Info("Logitech QuickCam detected (reserved ID).\n"); + name = "Logitech QuickCam (res.)"; + type_id = 730; /* Assuming CMOS */ + break; + default: + return -ENODEV; + break; + } + } + else if (vendor_id == 0x055d) { + /* I don't know the difference between the C10 and the C30; + I suppose the difference is the sensor, but both cameras + work equally well with a type_id of 675 + */ + switch(product_id) { + case 0x9000: + Info("Samsung MPC-C10 USB webcam detected.\n"); + name = "Samsung MPC-C10"; + type_id = 675; + break; + case 0x9001: + Info("Samsung MPC-C30 USB webcam detected.\n"); + name = "Samsung MPC-C30"; + type_id = 675; + break; + default: + return -ENODEV; + break; + } + } + else if (vendor_id == 0x041e) { + switch(product_id) { + case 0x400c: + Info("Creative Labs Webcam 5 detected.\n"); + name = "Creative Labs Webcam 5"; + type_id = 730; + break; + case 0x4011: + Info("Creative Labs Webcam Pro Ex detected.\n"); + name = "Creative Labs Webcam Pro Ex"; + type_id = 740; + break; + default: + return -ENODEV; + break; + } + } + else if (vendor_id == 0x04cc) { + switch(product_id) { + case 0x8116: + Info("Sotec Afina Eye USB webcam detected.\n"); + name = "Sotec Afina Eye"; + type_id = 730; + break; + default: + return -ENODEV; + break; + } + } + else if (vendor_id == 0x06be) { + switch(product_id) { + case 0x8116: + /* This is essentially the same cam as the Sotec Afina Eye */ + Info("AME Co. Afina Eye USB webcam detected.\n"); + name = "AME Co. Afina Eye"; + type_id = 750; + break; + default: + return -ENODEV; + break; + } + + } + else if (vendor_id == 0x0d81) { + switch(product_id) { + case 0x1900: + Info("Visionite VCS-UC300 USB webcam detected.\n"); + name = "Visionite VCS-UC300"; + type_id = 740; /* CCD sensor */ + break; + case 0x1910: + Info("Visionite VCS-UM100 USB webcam detected.\n"); + name = "Visionite VCS-UM100"; + type_id = 730; /* CMOS sensor */ + break; + default: + return -ENODEV; + break; + } + } + else + return -ENODEV; /* Not any of the know types; but the list keeps growing. */ + + memset(serial_number, 0, 30); + usb_string(udev, udev->descriptor.iSerialNumber, serial_number, 29); + Trace(TRACE_PROBE, "Device serial number is %s\n", serial_number); + + if (udev->descriptor.bNumConfigurations > 1) + Info("Warning: more than 1 configuration available.\n"); + + /* Allocate structure, initialize pointers, mutexes, etc. and link it to the usb_device */ + pdev = kzalloc(sizeof(struct pwc_device), GFP_KERNEL); + if (pdev == NULL) { + Err("Oops, could not allocate memory for pwc_device.\n"); + return -ENOMEM; + } + pdev->type = type_id; + pdev->vsize = default_size; + pdev->vframes = default_fps; + strcpy(pdev->serial, serial_number); + pdev->features = features; + if (vendor_id == 0x046D && product_id == 0x08B5) + { + /* Logitech QuickCam Orbit + The ranges have been determined experimentally; they may differ from cam to cam. + Also, the exact ranges left-right and up-down are different for my cam + */ + pdev->angle_range.pan_min = -7000; + pdev->angle_range.pan_max = 7000; + pdev->angle_range.tilt_min = -3000; + pdev->angle_range.tilt_max = 2500; + } + + init_MUTEX(&pdev->modlock); + spin_lock_init(&pdev->ptrlock); + + pdev->udev = udev; + init_waitqueue_head(&pdev->frameq); + pdev->vcompression = pwc_preferred_compression; + + /* Allocate video_device structure */ + pdev->vdev = video_device_alloc(); + if (pdev->vdev == 0) + { + Err("Err, cannot allocate video_device struture. Failing probe."); + kfree(pdev); + return -ENOMEM; + } + memcpy(pdev->vdev, &pwc_template, sizeof(pwc_template)); + strcpy(pdev->vdev->name, name); + pdev->vdev->owner = THIS_MODULE; + video_set_drvdata(pdev->vdev, pdev); + + pdev->release = le16_to_cpu(udev->descriptor.bcdDevice); + Trace(TRACE_PROBE, "Release: %04x\n", pdev->release); + + /* Now search device_hint[] table for a match, so we can hint a node number. */ + for (hint = 0; hint < MAX_DEV_HINTS; hint++) { + if (((device_hint[hint].type == -1) || (device_hint[hint].type == pdev->type)) && + (device_hint[hint].pdev == NULL)) { + /* so far, so good... try serial number */ + if ((device_hint[hint].serial_number[0] == '*') || !strcmp(device_hint[hint].serial_number, serial_number)) { + /* match! */ + video_nr = device_hint[hint].device_node; + Trace(TRACE_PROBE, "Found hint, will try to register as /dev/video%d\n", video_nr); + break; + } + } + } + + pdev->vdev->release = video_device_release; + i = video_register_device(pdev->vdev, VFL_TYPE_GRABBER, video_nr); + if (i < 0) { + Err("Failed to register as video device (%d).\n", i); + video_device_release(pdev->vdev); /* Drip... drip... drip... */ + kfree(pdev); /* Oops, no memory leaks please */ + return -EIO; + } + else { + Info("Registered as /dev/video%d.\n", pdev->vdev->minor & 0x3F); + } + + /* occupy slot */ + if (hint < MAX_DEV_HINTS) + device_hint[hint].pdev = pdev; + + Trace(TRACE_PROBE, "probe() function returning struct at 0x%p.\n", pdev); + usb_set_intfdata (intf, pdev); + return 0; +} + +/* The user janked out the cable... */ +static void usb_pwc_disconnect(struct usb_interface *intf) +{ + struct pwc_device *pdev; + int hint; + + lock_kernel(); + pdev = usb_get_intfdata (intf); + usb_set_intfdata (intf, NULL); + if (pdev == NULL) { + Err("pwc_disconnect() Called without private pointer.\n"); + goto disconnect_out; + } + if (pdev->udev == NULL) { + Err("pwc_disconnect() already called for %p\n", pdev); + goto disconnect_out; + } + if (pdev->udev != interface_to_usbdev(intf)) { + Err("pwc_disconnect() Woops: pointer mismatch udev/pdev.\n"); + goto disconnect_out; + } +#ifdef PWC_MAGIC + if (pdev->magic != PWC_MAGIC) { + Err("pwc_disconnect() Magic number failed. Consult your scrolls and try again.\n"); + goto disconnect_out; + } +#endif + + /* We got unplugged; this is signalled by an EPIPE error code */ + if (pdev->vopen) { + Info("Disconnected while webcam is in use!\n"); + pdev->error_status = EPIPE; + } + + /* Alert waiting processes */ + wake_up_interruptible(&pdev->frameq); + /* Wait until device is closed */ + while (pdev->vopen) + schedule(); + /* Device is now closed, so we can safely unregister it */ + Trace(TRACE_PROBE, "Unregistering video device in disconnect().\n"); + video_unregister_device(pdev->vdev); + + /* Free memory (don't set pdev to 0 just yet) */ + kfree(pdev); + +disconnect_out: + /* search device_hint[] table if we occupy a slot, by any chance */ + for (hint = 0; hint < MAX_DEV_HINTS; hint++) + if (device_hint[hint].pdev == pdev) + device_hint[hint].pdev = NULL; + + unlock_kernel(); +} + + +/* *grunt* We have to do atoi ourselves :-( */ +static int pwc_atoi(const char *s) +{ + int k = 0; + + k = 0; + while (*s != '\0' && *s >= '0' && *s <= '9') { + k = 10 * k + (*s - '0'); + s++; + } + return k; +} + + +/* + * Initialization code & module stuff + */ + +static char size[10]; +static int fps = 0; +static int fbufs = 0; +static int mbufs = 0; +static int trace = -1; +static int compression = -1; +static int leds[2] = { -1, -1 }; +static char *dev_hint[MAX_DEV_HINTS] = { }; + +module_param_string(size, size, sizeof(size), 0); +MODULE_PARM_DESC(size, "Initial image size. One of sqcif, qsif, qcif, sif, cif, vga"); +module_param(fps, int, 0000); +MODULE_PARM_DESC(fps, "Initial frames per second. Varies with model, useful range 5-30"); +module_param(fbufs, int, 0000); +MODULE_PARM_DESC(fbufs, "Number of internal frame buffers to reserve"); +module_param(mbufs, int, 0000); +MODULE_PARM_DESC(mbufs, "Number of external (mmap()ed) image buffers"); +module_param(trace, int, 0000); +MODULE_PARM_DESC(trace, "For debugging purposes"); +module_param(power_save, bool, 0000); +MODULE_PARM_DESC(power_save, "Turn power save feature in camera on or off"); +module_param(compression, int, 0000); +MODULE_PARM_DESC(compression, "Preferred compression quality. Range 0 (uncompressed) to 3 (high compression)"); +module_param_array(leds, int, NULL, 0000); +MODULE_PARM_DESC(leds, "LED on,off time in milliseconds"); +module_param_array(dev_hint, charp, NULL, 0000); +MODULE_PARM_DESC(dev_hint, "Device node hints"); + +MODULE_DESCRIPTION("Philips & OEM USB webcam driver"); +MODULE_AUTHOR("Luc Saillard <luc@saillard.org>"); +MODULE_LICENSE("GPL"); + +static int __init usb_pwc_init(void) +{ + int i, sz; + char *sizenames[PSZ_MAX] = { "sqcif", "qsif", "qcif", "sif", "cif", "vga" }; + + Info("Philips webcam module version " PWC_VERSION " loaded.\n"); + Info("Supports Philips PCA645/646, PCVC675/680/690, PCVC720[40]/730/740/750 & PCVC830/840.\n"); + Info("Also supports the Askey VC010, various Logitech Quickcams, Samsung MPC-C10 and MPC-C30,\n"); + Info("the Creative WebCam 5 & Pro Ex, SOTEC Afina Eye and Visionite VCS-UC300 and VCS-UM100.\n"); + + if (fps) { + if (fps < 4 || fps > 30) { + Err("Framerate out of bounds (4-30).\n"); + return -EINVAL; + } + default_fps = fps; + Info("Default framerate set to %d.\n", default_fps); + } + + if (size[0]) { + /* string; try matching with array */ + for (sz = 0; sz < PSZ_MAX; sz++) { + if (!strcmp(sizenames[sz], size)) { /* Found! */ + default_size = sz; + break; + } + } + if (sz == PSZ_MAX) { + Err("Size not recognized; try size=[sqcif | qsif | qcif | sif | cif | vga].\n"); + return -EINVAL; + } + Info("Default image size set to %s [%dx%d].\n", sizenames[default_size], pwc_image_sizes[default_size].x, pwc_image_sizes[default_size].y); + } + if (mbufs) { + if (mbufs < 1 || mbufs > MAX_IMAGES) { + Err("Illegal number of mmap() buffers; use a number between 1 and %d.\n", MAX_IMAGES); + return -EINVAL; + } + default_mbufs = mbufs; + Info("Number of image buffers set to %d.\n", default_mbufs); + } + if (fbufs) { + if (fbufs < 2 || fbufs > MAX_FRAMES) { + Err("Illegal number of frame buffers; use a number between 2 and %d.\n", MAX_FRAMES); + return -EINVAL; + } + default_fbufs = fbufs; + Info("Number of frame buffers set to %d.\n", default_fbufs); + } + if (trace >= 0) { + Info("Trace options: 0x%04x\n", trace); + pwc_trace = trace; + } + if (compression >= 0) { + if (compression > 3) { + Err("Invalid compression setting; use a number between 0 (uncompressed) and 3 (high).\n"); + return -EINVAL; + } + pwc_preferred_compression = compression; + Info("Preferred compression set to %d.\n", pwc_preferred_compression); + } + if (power_save) + Info("Enabling power save on open/close.\n"); + if (leds[0] >= 0) + led_on = leds[0]; + if (leds[1] >= 0) + led_off = leds[1]; + + /* Big device node whoopla. Basically, it allows you to assign a + device node (/dev/videoX) to a camera, based on its type + & serial number. The format is [type[.serialnumber]:]node. + + Any camera that isn't matched by these rules gets the next + available free device node. + */ + for (i = 0; i < MAX_DEV_HINTS; i++) { + char *s, *colon, *dot; + + /* This loop also initializes the array */ + device_hint[i].pdev = NULL; + s = dev_hint[i]; + if (s != NULL && *s != '\0') { + device_hint[i].type = -1; /* wildcard */ + strcpy(device_hint[i].serial_number, "*"); + + /* parse string: chop at ':' & '/' */ + colon = dot = s; + while (*colon != '\0' && *colon != ':') + colon++; + while (*dot != '\0' && *dot != '.') + dot++; + /* Few sanity checks */ + if (*dot != '\0' && dot > colon) { + Err("Malformed camera hint: the colon must be after the dot.\n"); + return -EINVAL; + } + + if (*colon == '\0') { + /* No colon */ + if (*dot != '\0') { + Err("Malformed camera hint: no colon + device node given.\n"); + return -EINVAL; + } + else { + /* No type or serial number specified, just a number. */ + device_hint[i].device_node = pwc_atoi(s); + } + } + else { + /* There's a colon, so we have at least a type and a device node */ + device_hint[i].type = pwc_atoi(s); + device_hint[i].device_node = pwc_atoi(colon + 1); + if (*dot != '\0') { + /* There's a serial number as well */ + int k; + + dot++; + k = 0; + while (*dot != ':' && k < 29) { + device_hint[i].serial_number[k++] = *dot; + dot++; + } + device_hint[i].serial_number[k] = '\0'; + } + } +#if PWC_DEBUG + Debug("device_hint[%d]:\n", i); + Debug(" type : %d\n", device_hint[i].type); + Debug(" serial# : %s\n", device_hint[i].serial_number); + Debug(" node : %d\n", device_hint[i].device_node); +#endif + } + else + device_hint[i].type = 0; /* not filled */ + } /* ..for MAX_DEV_HINTS */ + + Trace(TRACE_PROBE, "Registering driver at address 0x%p.\n", &pwc_driver); + return usb_register(&pwc_driver); +} + +static void __exit usb_pwc_exit(void) +{ + Trace(TRACE_MODULE, "Deregistering driver.\n"); + usb_deregister(&pwc_driver); + Info("Philips webcam module removed.\n"); +} + +module_init(usb_pwc_init); +module_exit(usb_pwc_exit); + diff --git a/drivers/media/video/pwc/pwc-ioctl.h b/drivers/media/video/pwc/pwc-ioctl.h new file mode 100644 index 00000000000..784bc72521f --- /dev/null +++ b/drivers/media/video/pwc/pwc-ioctl.h @@ -0,0 +1,292 @@ +#ifndef PWC_IOCTL_H +#define PWC_IOCTL_H + +/* (C) 2001-2004 Nemosoft Unv. + (C) 2004 Luc Saillard (luc@saillard.org) + + NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx + driver and thus may have bugs that are not present in the original version. + Please send bug reports and support requests to <luc@saillard.org>. + The decompression routines have been implemented by reverse-engineering the + Nemosoft binary pwcx module. Caveat emptor. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* This is pwc-ioctl.h belonging to PWC 8.12.1 + It contains structures and defines to communicate from user space + directly to the driver. + */ + +/* + Changes + 2001/08/03 Alvarado Added ioctl constants to access methods for + changing white balance and red/blue gains + 2002/12/15 G. H. Fernandez-Toribio VIDIOCGREALSIZE + 2003/12/13 Nemosft Unv. Some modifications to make interfacing to + PWCX easier + */ + +/* These are private ioctl() commands, specific for the Philips webcams. + They contain functions not found in other webcams, and settings not + specified in the Video4Linux API. + + The #define names are built up like follows: + VIDIOC VIDeo IOCtl prefix + PWC Philps WebCam + G optional: Get + S optional: Set + ... the function + */ + + + /* Enumeration of image sizes */ +#define PSZ_SQCIF 0x00 +#define PSZ_QSIF 0x01 +#define PSZ_QCIF 0x02 +#define PSZ_SIF 0x03 +#define PSZ_CIF 0x04 +#define PSZ_VGA 0x05 +#define PSZ_MAX 6 + + +/* The frame rate is encoded in the video_window.flags parameter using + the upper 16 bits, since some flags are defined nowadays. The following + defines provide a mask and shift to filter out this value. + + In 'Snapshot' mode the camera freezes its automatic exposure and colour + balance controls. + */ +#define PWC_FPS_SHIFT 16 +#define PWC_FPS_MASK 0x00FF0000 +#define PWC_FPS_FRMASK 0x003F0000 +#define PWC_FPS_SNAPSHOT 0x00400000 + + +/* structure for transferring x & y coordinates */ +struct pwc_coord +{ + int x, y; /* guess what */ + int size; /* size, or offset */ +}; + + +/* Used with VIDIOCPWCPROBE */ +struct pwc_probe +{ + char name[32]; + int type; +}; + +struct pwc_serial +{ + char serial[30]; /* String with serial number. Contains terminating 0 */ +}; + +/* pwc_whitebalance.mode values */ +#define PWC_WB_INDOOR 0 +#define PWC_WB_OUTDOOR 1 +#define PWC_WB_FL 2 +#define PWC_WB_MANUAL 3 +#define PWC_WB_AUTO 4 + +/* Used with VIDIOCPWC[SG]AWB (Auto White Balance). + Set mode to one of the PWC_WB_* values above. + *red and *blue are the respective gains of these colour components inside + the camera; range 0..65535 + When 'mode' == PWC_WB_MANUAL, 'manual_red' and 'manual_blue' are set or read; + otherwise undefined. + 'read_red' and 'read_blue' are read-only. +*/ +struct pwc_whitebalance +{ + int mode; + int manual_red, manual_blue; /* R/W */ + int read_red, read_blue; /* R/O */ +}; + +/* + 'control_speed' and 'control_delay' are used in automatic whitebalance mode, + and tell the camera how fast it should react to changes in lighting, and + with how much delay. Valid values are 0..65535. +*/ +struct pwc_wb_speed +{ + int control_speed; + int control_delay; + +}; + +/* Used with VIDIOCPWC[SG]LED */ +struct pwc_leds +{ + int led_on; /* Led on-time; range = 0..25000 */ + int led_off; /* Led off-time; range = 0..25000 */ +}; + +/* Image size (used with GREALSIZE) */ +struct pwc_imagesize +{ + int width; + int height; +}; + +/* Defines and structures for Motorized Pan & Tilt */ +#define PWC_MPT_PAN 0x01 +#define PWC_MPT_TILT 0x02 +#define PWC_MPT_TIMEOUT 0x04 /* for status */ + +/* Set angles; when absolute != 0, the angle is absolute and the + driver calculates the relative offset for you. This can only + be used with VIDIOCPWCSANGLE; VIDIOCPWCGANGLE always returns + absolute angles. + */ +struct pwc_mpt_angles +{ + int absolute; /* write-only */ + int pan; /* degrees * 100 */ + int tilt; /* degress * 100 */ +}; + +/* Range of angles of the camera, both horizontally and vertically. + */ +struct pwc_mpt_range +{ + int pan_min, pan_max; /* degrees * 100 */ + int tilt_min, tilt_max; +}; + +struct pwc_mpt_status +{ + int status; + int time_pan; + int time_tilt; +}; + + +/* This is used for out-of-kernel decompression. With it, you can get + all the necessary information to initialize and use the decompressor + routines in standalone applications. + */ +struct pwc_video_command +{ + int type; /* camera type (645, 675, 730, etc.) */ + int release; /* release number */ + + int size; /* one of PSZ_* */ + int alternate; + int command_len; /* length of USB video command */ + unsigned char command_buf[13]; /* Actual USB video command */ + int bandlength; /* >0 = compressed */ + int frame_size; /* Size of one (un)compressed frame */ +}; + +/* Flags for PWCX subroutines. Not all modules honour all flags. */ +#define PWCX_FLAG_PLANAR 0x0001 +#define PWCX_FLAG_BAYER 0x0008 + + +/* IOCTL definitions */ + + /* Restore user settings */ +#define VIDIOCPWCRUSER _IO('v', 192) + /* Save user settings */ +#define VIDIOCPWCSUSER _IO('v', 193) + /* Restore factory settings */ +#define VIDIOCPWCFACTORY _IO('v', 194) + + /* You can manipulate the compression factor. A compression preference of 0 + means use uncompressed modes when available; 1 is low compression, 2 is + medium and 3 is high compression preferred. Of course, the higher the + compression, the lower the bandwidth used but more chance of artefacts + in the image. The driver automatically chooses a higher compression when + the preferred mode is not available. + */ + /* Set preferred compression quality (0 = uncompressed, 3 = highest compression) */ +#define VIDIOCPWCSCQUAL _IOW('v', 195, int) + /* Get preferred compression quality */ +#define VIDIOCPWCGCQUAL _IOR('v', 195, int) + + +/* Retrieve serial number of camera */ +#define VIDIOCPWCGSERIAL _IOR('v', 198, struct pwc_serial) + + /* This is a probe function; since so many devices are supported, it + becomes difficult to include all the names in programs that want to + check for the enhanced Philips stuff. So in stead, try this PROBE; + it returns a structure with the original name, and the corresponding + Philips type. + To use, fill the structure with zeroes, call PROBE and if that succeeds, + compare the name with that returned from VIDIOCGCAP; they should be the + same. If so, you can be assured it is a Philips (OEM) cam and the type + is valid. + */ +#define VIDIOCPWCPROBE _IOR('v', 199, struct pwc_probe) + + /* Set AGC (Automatic Gain Control); int < 0 = auto, 0..65535 = fixed */ +#define VIDIOCPWCSAGC _IOW('v', 200, int) + /* Get AGC; int < 0 = auto; >= 0 = fixed, range 0..65535 */ +#define VIDIOCPWCGAGC _IOR('v', 200, int) + /* Set shutter speed; int < 0 = auto; >= 0 = fixed, range 0..65535 */ +#define VIDIOCPWCSSHUTTER _IOW('v', 201, int) + + /* Color compensation (Auto White Balance) */ +#define VIDIOCPWCSAWB _IOW('v', 202, struct pwc_whitebalance) +#define VIDIOCPWCGAWB _IOR('v', 202, struct pwc_whitebalance) + + /* Auto WB speed */ +#define VIDIOCPWCSAWBSPEED _IOW('v', 203, struct pwc_wb_speed) +#define VIDIOCPWCGAWBSPEED _IOR('v', 203, struct pwc_wb_speed) + + /* LEDs on/off/blink; int range 0..65535 */ +#define VIDIOCPWCSLED _IOW('v', 205, struct pwc_leds) +#define VIDIOCPWCGLED _IOR('v', 205, struct pwc_leds) + + /* Contour (sharpness); int < 0 = auto, 0..65536 = fixed */ +#define VIDIOCPWCSCONTOUR _IOW('v', 206, int) +#define VIDIOCPWCGCONTOUR _IOR('v', 206, int) + + /* Backlight compensation; 0 = off, otherwise on */ +#define VIDIOCPWCSBACKLIGHT _IOW('v', 207, int) +#define VIDIOCPWCGBACKLIGHT _IOR('v', 207, int) + + /* Flickerless mode; = 0 off, otherwise on */ +#define VIDIOCPWCSFLICKER _IOW('v', 208, int) +#define VIDIOCPWCGFLICKER _IOR('v', 208, int) + + /* Dynamic noise reduction; 0 off, 3 = high noise reduction */ +#define VIDIOCPWCSDYNNOISE _IOW('v', 209, int) +#define VIDIOCPWCGDYNNOISE _IOR('v', 209, int) + + /* Real image size as used by the camera; tells you whether or not there's a gray border around the image */ +#define VIDIOCPWCGREALSIZE _IOR('v', 210, struct pwc_imagesize) + + /* Motorized pan & tilt functions */ +#define VIDIOCPWCMPTRESET _IOW('v', 211, int) +#define VIDIOCPWCMPTGRANGE _IOR('v', 211, struct pwc_mpt_range) +#define VIDIOCPWCMPTSANGLE _IOW('v', 212, struct pwc_mpt_angles) +#define VIDIOCPWCMPTGANGLE _IOR('v', 212, struct pwc_mpt_angles) +#define VIDIOCPWCMPTSTATUS _IOR('v', 213, struct pwc_mpt_status) + + /* Get the USB set-video command; needed for initializing libpwcx */ +#define VIDIOCPWCGVIDCMD _IOR('v', 215, struct pwc_video_command) +struct pwc_table_init_buffer { + int len; + char *buffer; + +}; +#define VIDIOCPWCGVIDTABLE _IOR('v', 216, struct pwc_table_init_buffer) + +#endif diff --git a/drivers/media/video/pwc/pwc-kiara.c b/drivers/media/video/pwc/pwc-kiara.c new file mode 100644 index 00000000000..4c96037f7be --- /dev/null +++ b/drivers/media/video/pwc/pwc-kiara.c @@ -0,0 +1,318 @@ +/* Linux driver for Philips webcam + (C) 2004 Luc Saillard (luc@saillard.org) + + NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx + driver and thus may have bugs that are not present in the original version. + Please send bug reports and support requests to <luc@saillard.org>. + The decompression routines have been implemented by reverse-engineering the + Nemosoft binary pwcx module. Caveat emptor. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +/* This tables contains entries for the 730/740/750 (Kiara) camera, with + 4 different qualities (no compression, low, medium, high). + It lists the bandwidth requirements for said mode by its alternate interface + number. An alternate of 0 means that the mode is unavailable. + + There are 6 * 4 * 4 entries: + 6 different resolutions subqcif, qsif, qcif, sif, cif, vga + 6 framerates: 5, 10, 15, 20, 25, 30 + 4 compression modi: none, low, medium, high + + When an uncompressed mode is not available, the next available compressed mode + will be chosen (unless the decompressor is absent). Sometimes there are only + 1 or 2 compressed modes available; in that case entries are duplicated. +*/ + + +#include "pwc-kiara.h" +#include "pwc-uncompress.h" + +const struct Kiara_table_entry Kiara_table[PSZ_MAX][6][4] = +{ + /* SQCIF */ + { + /* 5 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 10 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 15 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 20 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 25 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 30 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + }, + /* QSIF */ + { + /* 5 fps */ + { + {1, 146, 0, {0x1D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0x00, 0x80}}, + {1, 146, 0, {0x1D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0x00, 0x80}}, + {1, 146, 0, {0x1D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0x00, 0x80}}, + {1, 146, 0, {0x1D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0x00, 0x80}}, + }, + /* 10 fps */ + { + {2, 291, 0, {0x1C, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x23, 0x01, 0x80}}, + {1, 192, 630, {0x14, 0xF4, 0x30, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xC0, 0x00, 0x80}}, + {1, 192, 630, {0x14, 0xF4, 0x30, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xC0, 0x00, 0x80}}, + {1, 192, 630, {0x14, 0xF4, 0x30, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xC0, 0x00, 0x80}}, + }, + /* 15 fps */ + { + {3, 437, 0, {0x1B, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xB5, 0x01, 0x80}}, + {2, 292, 640, {0x13, 0xF4, 0x30, 0x13, 0xF7, 0x13, 0x2F, 0x13, 0x20, 0x24, 0x01, 0x80}}, + {2, 292, 640, {0x13, 0xF4, 0x30, 0x13, 0xF7, 0x13, 0x2F, 0x13, 0x20, 0x24, 0x01, 0x80}}, + {1, 192, 420, {0x13, 0xF4, 0x30, 0x0D, 0x1B, 0x0C, 0x53, 0x1E, 0x18, 0xC0, 0x00, 0x80}}, + }, + /* 20 fps */ + { + {4, 589, 0, {0x1A, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x4D, 0x02, 0x80}}, + {3, 448, 730, {0x12, 0xF4, 0x30, 0x16, 0xC9, 0x16, 0x01, 0x0E, 0x18, 0xC0, 0x01, 0x80}}, + {2, 292, 476, {0x12, 0xF4, 0x30, 0x0E, 0xD8, 0x0E, 0x10, 0x19, 0x18, 0x24, 0x01, 0x80}}, + {1, 192, 312, {0x12, 0xF4, 0x50, 0x09, 0xB3, 0x08, 0xEB, 0x1E, 0x18, 0xC0, 0x00, 0x80}}, + }, + /* 25 fps */ + { + {5, 703, 0, {0x19, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xBF, 0x02, 0x80}}, + {3, 447, 610, {0x11, 0xF4, 0x30, 0x13, 0x0B, 0x12, 0x43, 0x14, 0x28, 0xBF, 0x01, 0x80}}, + {2, 292, 398, {0x11, 0xF4, 0x50, 0x0C, 0x6C, 0x0B, 0xA4, 0x1E, 0x28, 0x24, 0x01, 0x80}}, + {1, 193, 262, {0x11, 0xF4, 0x50, 0x08, 0x23, 0x07, 0x5B, 0x1E, 0x28, 0xC1, 0x00, 0x80}}, + }, + /* 30 fps */ + { + {8, 874, 0, {0x18, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x6A, 0x03, 0x80}}, + {5, 704, 730, {0x10, 0xF4, 0x30, 0x16, 0xC9, 0x16, 0x01, 0x0E, 0x28, 0xC0, 0x02, 0x80}}, + {3, 448, 492, {0x10, 0xF4, 0x30, 0x0F, 0x5D, 0x0E, 0x95, 0x15, 0x28, 0xC0, 0x01, 0x80}}, + {2, 292, 320, {0x10, 0xF4, 0x50, 0x09, 0xFB, 0x09, 0x33, 0x1E, 0x28, 0x24, 0x01, 0x80}}, + }, + }, + /* QCIF */ + { + /* 5 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 10 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 15 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 20 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 25 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 30 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + }, + /* SIF */ + { + /* 5 fps */ + { + {4, 582, 0, {0x0D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x46, 0x02, 0x80}}, + {3, 387, 1276, {0x05, 0xF4, 0x30, 0x27, 0xD8, 0x26, 0x48, 0x03, 0x10, 0x83, 0x01, 0x80}}, + {2, 291, 960, {0x05, 0xF4, 0x30, 0x1D, 0xF2, 0x1C, 0x62, 0x04, 0x10, 0x23, 0x01, 0x80}}, + {1, 191, 630, {0x05, 0xF4, 0x50, 0x13, 0xA9, 0x12, 0x19, 0x05, 0x18, 0xBF, 0x00, 0x80}}, + }, + /* 10 fps */ + { + {0, }, + {6, 775, 1278, {0x04, 0xF4, 0x30, 0x27, 0xE8, 0x26, 0x58, 0x05, 0x30, 0x07, 0x03, 0x80}}, + {3, 447, 736, {0x04, 0xF4, 0x30, 0x16, 0xFB, 0x15, 0x6B, 0x05, 0x28, 0xBF, 0x01, 0x80}}, + {2, 292, 480, {0x04, 0xF4, 0x70, 0x0E, 0xF9, 0x0D, 0x69, 0x09, 0x28, 0x24, 0x01, 0x80}}, + }, + /* 15 fps */ + { + {0, }, + {9, 955, 1050, {0x03, 0xF4, 0x30, 0x20, 0xCF, 0x1F, 0x3F, 0x06, 0x48, 0xBB, 0x03, 0x80}}, + {4, 592, 650, {0x03, 0xF4, 0x30, 0x14, 0x44, 0x12, 0xB4, 0x08, 0x30, 0x50, 0x02, 0x80}}, + {3, 448, 492, {0x03, 0xF4, 0x50, 0x0F, 0x52, 0x0D, 0xC2, 0x09, 0x38, 0xC0, 0x01, 0x80}}, + }, + /* 20 fps */ + { + {0, }, + {9, 958, 782, {0x02, 0xF4, 0x30, 0x18, 0x6A, 0x16, 0xDA, 0x0B, 0x58, 0xBE, 0x03, 0x80}}, + {5, 703, 574, {0x02, 0xF4, 0x50, 0x11, 0xE7, 0x10, 0x57, 0x0B, 0x40, 0xBF, 0x02, 0x80}}, + {3, 446, 364, {0x02, 0xF4, 0x90, 0x0B, 0x5C, 0x09, 0xCC, 0x0E, 0x38, 0xBE, 0x01, 0x80}}, + }, + /* 25 fps */ + { + {0, }, + {9, 958, 654, {0x01, 0xF4, 0x30, 0x14, 0x66, 0x12, 0xD6, 0x0B, 0x50, 0xBE, 0x03, 0x80}}, + {6, 776, 530, {0x01, 0xF4, 0x50, 0x10, 0x8C, 0x0E, 0xFC, 0x0C, 0x48, 0x08, 0x03, 0x80}}, + {4, 592, 404, {0x01, 0xF4, 0x70, 0x0C, 0x96, 0x0B, 0x06, 0x0B, 0x48, 0x50, 0x02, 0x80}}, + }, + /* 30 fps */ + { + {0, }, + {9, 957, 526, {0x00, 0xF4, 0x50, 0x10, 0x68, 0x0E, 0xD8, 0x0D, 0x58, 0xBD, 0x03, 0x80}}, + {6, 775, 426, {0x00, 0xF4, 0x70, 0x0D, 0x48, 0x0B, 0xB8, 0x0F, 0x50, 0x07, 0x03, 0x80}}, + {4, 590, 324, {0x00, 0x7A, 0x88, 0x0A, 0x1C, 0x08, 0xB4, 0x0E, 0x50, 0x4E, 0x02, 0x80}}, + }, + }, + /* CIF */ + { + /* 5 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 10 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 15 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 20 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 25 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 30 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + }, + /* VGA */ + { + /* 5 fps */ + { + {0, }, + {6, 773, 1272, {0x25, 0xF4, 0x30, 0x27, 0xB6, 0x24, 0x96, 0x02, 0x30, 0x05, 0x03, 0x80}}, + {4, 592, 976, {0x25, 0xF4, 0x50, 0x1E, 0x78, 0x1B, 0x58, 0x03, 0x30, 0x50, 0x02, 0x80}}, + {3, 448, 738, {0x25, 0xF4, 0x90, 0x17, 0x0C, 0x13, 0xEC, 0x04, 0x30, 0xC0, 0x01, 0x80}}, + }, + /* 10 fps */ + { + {0, }, + {9, 956, 788, {0x24, 0xF4, 0x70, 0x18, 0x9C, 0x15, 0x7C, 0x03, 0x48, 0xBC, 0x03, 0x80}}, + {6, 776, 640, {0x24, 0xF4, 0xB0, 0x13, 0xFC, 0x11, 0x2C, 0x04, 0x48, 0x08, 0x03, 0x80}}, + {4, 592, 488, {0x24, 0x7A, 0xE8, 0x0F, 0x3C, 0x0C, 0x6C, 0x06, 0x48, 0x50, 0x02, 0x80}}, + }, + /* 15 fps */ + { + {0, }, + {9, 957, 526, {0x23, 0x7A, 0xE8, 0x10, 0x68, 0x0D, 0x98, 0x06, 0x58, 0xBD, 0x03, 0x80}}, + {9, 957, 526, {0x23, 0x7A, 0xE8, 0x10, 0x68, 0x0D, 0x98, 0x06, 0x58, 0xBD, 0x03, 0x80}}, + {8, 895, 492, {0x23, 0x7A, 0xE8, 0x0F, 0x5D, 0x0C, 0x8D, 0x06, 0x58, 0x7F, 0x03, 0x80}}, + }, + /* 20 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 25 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 30 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + }, +}; + diff --git a/drivers/media/video/pwc/pwc-kiara.h b/drivers/media/video/pwc/pwc-kiara.h new file mode 100644 index 00000000000..12929abbb1f --- /dev/null +++ b/drivers/media/video/pwc/pwc-kiara.h @@ -0,0 +1,45 @@ +/* Linux driver for Philips webcam + (C) 2004 Luc Saillard (luc@saillard.org) + + NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx + driver and thus may have bugs that are not present in the original version. + Please send bug reports and support requests to <luc@saillard.org>. + The decompression routines have been implemented by reverse-engineering the + Nemosoft binary pwcx module. Caveat emptor. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* Entries for the Kiara (730/740/750) camera */ + +#ifndef PWC_KIARA_H +#define PWC_KIARA_H + +#include "pwc-ioctl.h" + +struct Kiara_table_entry +{ + char alternate; /* USB alternate interface */ + unsigned short packetsize; /* Normal packet size */ + unsigned short bandlength; /* Bandlength when decompressing */ + unsigned char mode[12]; /* precomputed mode settings for cam */ +}; + +const extern struct Kiara_table_entry Kiara_table[PSZ_MAX][6][4]; +const extern unsigned int KiaraRomTable[8][2][16][8]; + +#endif + + diff --git a/drivers/media/video/pwc/pwc-misc.c b/drivers/media/video/pwc/pwc-misc.c new file mode 100644 index 00000000000..58fe7974799 --- /dev/null +++ b/drivers/media/video/pwc/pwc-misc.c @@ -0,0 +1,140 @@ +/* Linux driver for Philips webcam + Various miscellaneous functions and tables. + (C) 1999-2003 Nemosoft Unv. + (C) 2004 Luc Saillard (luc@saillard.org) + + NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx + driver and thus may have bugs that are not present in the original version. + Please send bug reports and support requests to <luc@saillard.org>. + The decompression routines have been implemented by reverse-engineering the + Nemosoft binary pwcx module. Caveat emptor. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <linux/slab.h> + +#include "pwc.h" + +struct pwc_coord pwc_image_sizes[PSZ_MAX] = +{ + { 128, 96, 0 }, + { 160, 120, 0 }, + { 176, 144, 0 }, + { 320, 240, 0 }, + { 352, 288, 0 }, + { 640, 480, 0 }, +}; + +/* x,y -> PSZ_ */ +int pwc_decode_size(struct pwc_device *pdev, int width, int height) +{ + int i, find; + + /* Make sure we don't go beyond our max size. + NB: we have different limits for RAW and normal modes. In case + you don't have the decompressor loaded or use RAW mode, + the maximum viewable size is smaller. + */ + if (pdev->vpalette == VIDEO_PALETTE_RAW) + { + if (width > pdev->abs_max.x || height > pdev->abs_max.y) + { + Debug("VIDEO_PALETTE_RAW: going beyond abs_max.\n"); + return -1; + } + } + else + { + if (width > pdev->view_max.x || height > pdev->view_max.y) + { + Debug("VIDEO_PALETTE_ not RAW: going beyond view_max.\n"); + return -1; + } + } + + /* Find the largest size supported by the camera that fits into the + requested size. + */ + find = -1; + for (i = 0; i < PSZ_MAX; i++) { + if (pdev->image_mask & (1 << i)) { + if (pwc_image_sizes[i].x <= width && pwc_image_sizes[i].y <= height) + find = i; + } + } + return find; +} + +/* initialize variables depending on type and decompressor*/ +void pwc_construct(struct pwc_device *pdev) +{ + switch(pdev->type) { + case 645: + case 646: + pdev->view_min.x = 128; + pdev->view_min.y = 96; + pdev->view_max.x = 352; + pdev->view_max.y = 288; + pdev->abs_max.x = 352; + pdev->abs_max.y = 288; + pdev->image_mask = 1 << PSZ_SQCIF | 1 << PSZ_QCIF | 1 << PSZ_CIF; + pdev->vcinterface = 2; + pdev->vendpoint = 4; + pdev->frame_header_size = 0; + pdev->frame_trailer_size = 0; + break; + case 675: + case 680: + case 690: + pdev->view_min.x = 128; + pdev->view_min.y = 96; + /* Anthill bug #38: PWC always reports max size, even without PWCX */ + pdev->view_max.x = 640; + pdev->view_max.y = 480; + pdev->image_mask = 1 << PSZ_SQCIF | 1 << PSZ_QSIF | 1 << PSZ_QCIF | 1 << PSZ_SIF | 1 << PSZ_CIF | 1 << PSZ_VGA; + pdev->abs_max.x = 640; + pdev->abs_max.y = 480; + pdev->vcinterface = 3; + pdev->vendpoint = 4; + pdev->frame_header_size = 0; + pdev->frame_trailer_size = 0; + break; + case 720: + case 730: + case 740: + case 750: + pdev->view_min.x = 160; + pdev->view_min.y = 120; + pdev->view_max.x = 640; + pdev->view_max.y = 480; + pdev->image_mask = 1 << PSZ_QSIF | 1 << PSZ_SIF | 1 << PSZ_VGA; + pdev->abs_max.x = 640; + pdev->abs_max.y = 480; + pdev->vcinterface = 3; + pdev->vendpoint = 5; + pdev->frame_header_size = TOUCAM_HEADER_SIZE; + pdev->frame_trailer_size = TOUCAM_TRAILER_SIZE; + break; + } + Debug("type = %d\n",pdev->type); + pdev->vpalette = VIDEO_PALETTE_YUV420P; /* default */ + pdev->view_min.size = pdev->view_min.x * pdev->view_min.y; + pdev->view_max.size = pdev->view_max.x * pdev->view_max.y; + /* length of image, in YUV format; always allocate enough memory. */ + pdev->len_per_image = (pdev->abs_max.x * pdev->abs_max.y * 3) / 2; +} + + diff --git a/drivers/media/video/pwc/pwc-nala.h b/drivers/media/video/pwc/pwc-nala.h new file mode 100644 index 00000000000..168c73ef75d --- /dev/null +++ b/drivers/media/video/pwc/pwc-nala.h @@ -0,0 +1,66 @@ + /* SQCIF */ + { + {0, 0, {0x04, 0x01, 0x03}}, + {8, 0, {0x05, 0x01, 0x03}}, + {7, 0, {0x08, 0x01, 0x03}}, + {7, 0, {0x0A, 0x01, 0x03}}, + {6, 0, {0x0C, 0x01, 0x03}}, + {5, 0, {0x0F, 0x01, 0x03}}, + {4, 0, {0x14, 0x01, 0x03}}, + {3, 0, {0x18, 0x01, 0x03}}, + }, + /* QSIF */ + { + {0}, + {0}, + {0}, + {0}, + {0}, + {0}, + {0}, + {0}, + }, + /* QCIF */ + { + {0, 0, {0x04, 0x01, 0x02}}, + {8, 0, {0x05, 0x01, 0x02}}, + {7, 0, {0x08, 0x01, 0x02}}, + {6, 0, {0x0A, 0x01, 0x02}}, + {5, 0, {0x0C, 0x01, 0x02}}, + {4, 0, {0x0F, 0x01, 0x02}}, + {1, 0, {0x14, 0x01, 0x02}}, + {1, 0, {0x18, 0x01, 0x02}}, + }, + /* SIF */ + { + {0}, + {0}, + {0}, + {0}, + {0}, + {0}, + {0}, + {0}, + }, + /* CIF */ + { + {4, 0, {0x04, 0x01, 0x01}}, + {7, 1, {0x05, 0x03, 0x01}}, + {6, 1, {0x08, 0x03, 0x01}}, + {4, 1, {0x0A, 0x03, 0x01}}, + {3, 1, {0x0C, 0x03, 0x01}}, + {2, 1, {0x0F, 0x03, 0x01}}, + {0}, + {0}, + }, + /* VGA */ + { + {0}, + {0}, + {0}, + {0}, + {0}, + {0}, + {0}, + {0}, + }, diff --git a/drivers/media/video/pwc/pwc-timon.c b/drivers/media/video/pwc/pwc-timon.c new file mode 100644 index 00000000000..175250d089c --- /dev/null +++ b/drivers/media/video/pwc/pwc-timon.c @@ -0,0 +1,316 @@ +/* Linux driver for Philips webcam + (C) 2004 Luc Saillard (luc@saillard.org) + + NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx + driver and thus may have bugs that are not present in the original version. + Please send bug reports and support requests to <luc@saillard.org>. + The decompression routines have been implemented by reverse-engineering the + Nemosoft binary pwcx module. Caveat emptor. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +/* This tables contains entries for the 675/680/690 (Timon) camera, with + 4 different qualities (no compression, low, medium, high). + It lists the bandwidth requirements for said mode by its alternate interface + number. An alternate of 0 means that the mode is unavailable. + + There are 6 * 4 * 4 entries: + 6 different resolutions subqcif, qsif, qcif, sif, cif, vga + 6 framerates: 5, 10, 15, 20, 25, 30 + 4 compression modi: none, low, medium, high + + When an uncompressed mode is not available, the next available compressed mode + will be chosen (unless the decompressor is absent). Sometimes there are only + 1 or 2 compressed modes available; in that case entries are duplicated. +*/ + +#include "pwc-timon.h" + +const struct Timon_table_entry Timon_table[PSZ_MAX][6][4] = +{ + /* SQCIF */ + { + /* 5 fps */ + { + {1, 140, 0, {0x05, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x8C, 0xFC, 0x80, 0x02}}, + {1, 140, 0, {0x05, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x8C, 0xFC, 0x80, 0x02}}, + {1, 140, 0, {0x05, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x8C, 0xFC, 0x80, 0x02}}, + {1, 140, 0, {0x05, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x8C, 0xFC, 0x80, 0x02}}, + }, + /* 10 fps */ + { + {2, 280, 0, {0x04, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x18, 0xA9, 0x80, 0x02}}, + {2, 280, 0, {0x04, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x18, 0xA9, 0x80, 0x02}}, + {2, 280, 0, {0x04, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x18, 0xA9, 0x80, 0x02}}, + {2, 280, 0, {0x04, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x18, 0xA9, 0x80, 0x02}}, + }, + /* 15 fps */ + { + {3, 410, 0, {0x03, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x9A, 0x71, 0x80, 0x02}}, + {3, 410, 0, {0x03, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x9A, 0x71, 0x80, 0x02}}, + {3, 410, 0, {0x03, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x9A, 0x71, 0x80, 0x02}}, + {3, 410, 0, {0x03, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x9A, 0x71, 0x80, 0x02}}, + }, + /* 20 fps */ + { + {4, 559, 0, {0x02, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x2F, 0x56, 0x80, 0x02}}, + {4, 559, 0, {0x02, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x2F, 0x56, 0x80, 0x02}}, + {4, 559, 0, {0x02, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x2F, 0x56, 0x80, 0x02}}, + {4, 559, 0, {0x02, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x2F, 0x56, 0x80, 0x02}}, + }, + /* 25 fps */ + { + {5, 659, 0, {0x01, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x93, 0x46, 0x80, 0x02}}, + {5, 659, 0, {0x01, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x93, 0x46, 0x80, 0x02}}, + {5, 659, 0, {0x01, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x93, 0x46, 0x80, 0x02}}, + {5, 659, 0, {0x01, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x93, 0x46, 0x80, 0x02}}, + }, + /* 30 fps */ + { + {7, 838, 0, {0x00, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x46, 0x3B, 0x80, 0x02}}, + {7, 838, 0, {0x00, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x46, 0x3B, 0x80, 0x02}}, + {7, 838, 0, {0x00, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x46, 0x3B, 0x80, 0x02}}, + {7, 838, 0, {0x00, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x46, 0x3B, 0x80, 0x02}}, + }, + }, + /* QSIF */ + { + /* 5 fps */ + { + {1, 146, 0, {0x2D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0xFC, 0xC0, 0x02}}, + {1, 146, 0, {0x2D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0xFC, 0xC0, 0x02}}, + {1, 146, 0, {0x2D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0xFC, 0xC0, 0x02}}, + {1, 146, 0, {0x2D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0xFC, 0xC0, 0x02}}, + }, + /* 10 fps */ + { + {2, 291, 0, {0x2C, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x23, 0xA1, 0xC0, 0x02}}, + {1, 191, 630, {0x2C, 0xF4, 0x05, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xBF, 0xF4, 0xC0, 0x02}}, + {1, 191, 630, {0x2C, 0xF4, 0x05, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xBF, 0xF4, 0xC0, 0x02}}, + {1, 191, 630, {0x2C, 0xF4, 0x05, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xBF, 0xF4, 0xC0, 0x02}}, + }, + /* 15 fps */ + { + {3, 437, 0, {0x2B, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xB5, 0x6D, 0xC0, 0x02}}, + {2, 291, 640, {0x2B, 0xF4, 0x05, 0x13, 0xF7, 0x13, 0x2F, 0x13, 0x08, 0x23, 0xA1, 0xC0, 0x02}}, + {2, 291, 640, {0x2B, 0xF4, 0x05, 0x13, 0xF7, 0x13, 0x2F, 0x13, 0x08, 0x23, 0xA1, 0xC0, 0x02}}, + {1, 191, 420, {0x2B, 0xF4, 0x0D, 0x0D, 0x1B, 0x0C, 0x53, 0x1E, 0x08, 0xBF, 0xF4, 0xC0, 0x02}}, + }, + /* 20 fps */ + { + {4, 588, 0, {0x2A, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x4C, 0x52, 0xC0, 0x02}}, + {3, 447, 730, {0x2A, 0xF4, 0x05, 0x16, 0xC9, 0x16, 0x01, 0x0E, 0x18, 0xBF, 0x69, 0xC0, 0x02}}, + {2, 292, 476, {0x2A, 0xF4, 0x0D, 0x0E, 0xD8, 0x0E, 0x10, 0x19, 0x18, 0x24, 0xA1, 0xC0, 0x02}}, + {1, 192, 312, {0x2A, 0xF4, 0x1D, 0x09, 0xB3, 0x08, 0xEB, 0x1E, 0x18, 0xC0, 0xF4, 0xC0, 0x02}}, + }, + /* 25 fps */ + { + {5, 703, 0, {0x29, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xBF, 0x42, 0xC0, 0x02}}, + {3, 447, 610, {0x29, 0xF4, 0x05, 0x13, 0x0B, 0x12, 0x43, 0x14, 0x18, 0xBF, 0x69, 0xC0, 0x02}}, + {2, 292, 398, {0x29, 0xF4, 0x0D, 0x0C, 0x6C, 0x0B, 0xA4, 0x1E, 0x18, 0x24, 0xA1, 0xC0, 0x02}}, + {1, 192, 262, {0x29, 0xF4, 0x25, 0x08, 0x23, 0x07, 0x5B, 0x1E, 0x18, 0xC0, 0xF4, 0xC0, 0x02}}, + }, + /* 30 fps */ + { + {8, 873, 0, {0x28, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x69, 0x37, 0xC0, 0x02}}, + {5, 704, 774, {0x28, 0xF4, 0x05, 0x18, 0x21, 0x17, 0x59, 0x0F, 0x18, 0xC0, 0x42, 0xC0, 0x02}}, + {3, 448, 492, {0x28, 0xF4, 0x05, 0x0F, 0x5D, 0x0E, 0x95, 0x15, 0x18, 0xC0, 0x69, 0xC0, 0x02}}, + {2, 291, 320, {0x28, 0xF4, 0x1D, 0x09, 0xFB, 0x09, 0x33, 0x1E, 0x18, 0x23, 0xA1, 0xC0, 0x02}}, + }, + }, + /* QCIF */ + { + /* 5 fps */ + { + {1, 193, 0, {0x0D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xC1, 0xF4, 0xC0, 0x02}}, + {1, 193, 0, {0x0D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xC1, 0xF4, 0xC0, 0x02}}, + {1, 193, 0, {0x0D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xC1, 0xF4, 0xC0, 0x02}}, + {1, 193, 0, {0x0D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xC1, 0xF4, 0xC0, 0x02}}, + }, + /* 10 fps */ + { + {3, 385, 0, {0x0C, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x81, 0x79, 0xC0, 0x02}}, + {2, 291, 800, {0x0C, 0xF4, 0x05, 0x18, 0xF4, 0x18, 0x18, 0x11, 0x08, 0x23, 0xA1, 0xC0, 0x02}}, + {2, 291, 800, {0x0C, 0xF4, 0x05, 0x18, 0xF4, 0x18, 0x18, 0x11, 0x08, 0x23, 0xA1, 0xC0, 0x02}}, + {1, 194, 532, {0x0C, 0xF4, 0x05, 0x10, 0x9A, 0x0F, 0xBE, 0x1B, 0x08, 0xC2, 0xF0, 0xC0, 0x02}}, + }, + /* 15 fps */ + { + {4, 577, 0, {0x0B, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x41, 0x52, 0xC0, 0x02}}, + {3, 447, 818, {0x0B, 0xF4, 0x05, 0x19, 0x89, 0x18, 0xAD, 0x0F, 0x10, 0xBF, 0x69, 0xC0, 0x02}}, + {2, 292, 534, {0x0B, 0xF4, 0x05, 0x10, 0xA3, 0x0F, 0xC7, 0x19, 0x10, 0x24, 0xA1, 0xC0, 0x02}}, + {1, 195, 356, {0x0B, 0xF4, 0x15, 0x0B, 0x11, 0x0A, 0x35, 0x1E, 0x10, 0xC3, 0xF0, 0xC0, 0x02}}, + }, + /* 20 fps */ + { + {6, 776, 0, {0x0A, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x08, 0x3F, 0xC0, 0x02}}, + {4, 591, 804, {0x0A, 0xF4, 0x05, 0x19, 0x1E, 0x18, 0x42, 0x0F, 0x18, 0x4F, 0x4E, 0xC0, 0x02}}, + {3, 447, 608, {0x0A, 0xF4, 0x05, 0x12, 0xFD, 0x12, 0x21, 0x15, 0x18, 0xBF, 0x69, 0xC0, 0x02}}, + {2, 291, 396, {0x0A, 0xF4, 0x15, 0x0C, 0x5E, 0x0B, 0x82, 0x1E, 0x18, 0x23, 0xA1, 0xC0, 0x02}}, + }, + /* 25 fps */ + { + {9, 928, 0, {0x09, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xA0, 0x33, 0xC0, 0x02}}, + {5, 703, 800, {0x09, 0xF4, 0x05, 0x18, 0xF4, 0x18, 0x18, 0x10, 0x18, 0xBF, 0x42, 0xC0, 0x02}}, + {3, 447, 508, {0x09, 0xF4, 0x0D, 0x0F, 0xD2, 0x0E, 0xF6, 0x1B, 0x18, 0xBF, 0x69, 0xC0, 0x02}}, + {2, 292, 332, {0x09, 0xF4, 0x1D, 0x0A, 0x5A, 0x09, 0x7E, 0x1E, 0x18, 0x24, 0xA1, 0xC0, 0x02}}, + }, + /* 30 fps */ + { + {0, }, + {9, 956, 876, {0x08, 0xF4, 0x05, 0x1B, 0x58, 0x1A, 0x7C, 0x0E, 0x20, 0xBC, 0x33, 0x10, 0x02}}, + {4, 592, 542, {0x08, 0xF4, 0x05, 0x10, 0xE4, 0x10, 0x08, 0x17, 0x20, 0x50, 0x4E, 0x10, 0x02}}, + {2, 291, 266, {0x08, 0xF4, 0x25, 0x08, 0x48, 0x07, 0x6C, 0x1E, 0x20, 0x23, 0xA1, 0x10, 0x02}}, + }, + }, + /* SIF */ + { + /* 5 fps */ + { + {4, 582, 0, {0x35, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x46, 0x52, 0x60, 0x02}}, + {3, 387, 1276, {0x35, 0xF4, 0x05, 0x27, 0xD8, 0x26, 0x48, 0x03, 0x10, 0x83, 0x79, 0x60, 0x02}}, + {2, 291, 960, {0x35, 0xF4, 0x0D, 0x1D, 0xF2, 0x1C, 0x62, 0x04, 0x10, 0x23, 0xA1, 0x60, 0x02}}, + {1, 191, 630, {0x35, 0xF4, 0x1D, 0x13, 0xA9, 0x12, 0x19, 0x05, 0x08, 0xBF, 0xF4, 0x60, 0x02}}, + }, + /* 10 fps */ + { + {0, }, + {6, 775, 1278, {0x34, 0xF4, 0x05, 0x27, 0xE8, 0x26, 0x58, 0x05, 0x30, 0x07, 0x3F, 0x10, 0x02}}, + {3, 447, 736, {0x34, 0xF4, 0x15, 0x16, 0xFB, 0x15, 0x6B, 0x05, 0x18, 0xBF, 0x69, 0x10, 0x02}}, + {2, 291, 480, {0x34, 0xF4, 0x2D, 0x0E, 0xF9, 0x0D, 0x69, 0x09, 0x18, 0x23, 0xA1, 0x10, 0x02}}, + }, + /* 15 fps */ + { + {0, }, + {9, 955, 1050, {0x33, 0xF4, 0x05, 0x20, 0xCF, 0x1F, 0x3F, 0x06, 0x48, 0xBB, 0x33, 0x10, 0x02}}, + {4, 591, 650, {0x33, 0xF4, 0x15, 0x14, 0x44, 0x12, 0xB4, 0x08, 0x30, 0x4F, 0x4E, 0x10, 0x02}}, + {3, 448, 492, {0x33, 0xF4, 0x25, 0x0F, 0x52, 0x0D, 0xC2, 0x09, 0x28, 0xC0, 0x69, 0x10, 0x02}}, + }, + /* 20 fps */ + { + {0, }, + {9, 958, 782, {0x32, 0xF4, 0x0D, 0x18, 0x6A, 0x16, 0xDA, 0x0B, 0x58, 0xBE, 0x33, 0xD0, 0x02}}, + {5, 703, 574, {0x32, 0xF4, 0x1D, 0x11, 0xE7, 0x10, 0x57, 0x0B, 0x40, 0xBF, 0x42, 0xD0, 0x02}}, + {3, 446, 364, {0x32, 0xF4, 0x3D, 0x0B, 0x5C, 0x09, 0xCC, 0x0E, 0x30, 0xBE, 0x69, 0xD0, 0x02}}, + }, + /* 25 fps */ + { + {0, }, + {9, 958, 654, {0x31, 0xF4, 0x15, 0x14, 0x66, 0x12, 0xD6, 0x0B, 0x50, 0xBE, 0x33, 0x90, 0x02}}, + {6, 776, 530, {0x31, 0xF4, 0x25, 0x10, 0x8C, 0x0E, 0xFC, 0x0C, 0x48, 0x08, 0x3F, 0x90, 0x02}}, + {4, 592, 404, {0x31, 0xF4, 0x35, 0x0C, 0x96, 0x0B, 0x06, 0x0B, 0x38, 0x50, 0x4E, 0x90, 0x02}}, + }, + /* 30 fps */ + { + {0, }, + {9, 957, 526, {0x30, 0xF4, 0x25, 0x10, 0x68, 0x0E, 0xD8, 0x0D, 0x58, 0xBD, 0x33, 0x60, 0x02}}, + {6, 775, 426, {0x30, 0xF4, 0x35, 0x0D, 0x48, 0x0B, 0xB8, 0x0F, 0x50, 0x07, 0x3F, 0x60, 0x02}}, + {4, 590, 324, {0x30, 0x7A, 0x4B, 0x0A, 0x1C, 0x08, 0xB4, 0x0E, 0x40, 0x4E, 0x52, 0x60, 0x02}}, + }, + }, + /* CIF */ + { + /* 5 fps */ + { + {6, 771, 0, {0x15, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x3F, 0x80, 0x02}}, + {4, 465, 1278, {0x15, 0xF4, 0x05, 0x27, 0xEE, 0x26, 0x36, 0x03, 0x18, 0xD1, 0x65, 0x80, 0x02}}, + {2, 291, 800, {0x15, 0xF4, 0x15, 0x18, 0xF4, 0x17, 0x3C, 0x05, 0x18, 0x23, 0xA1, 0x80, 0x02}}, + {1, 193, 528, {0x15, 0xF4, 0x2D, 0x10, 0x7E, 0x0E, 0xC6, 0x0A, 0x18, 0xC1, 0xF4, 0x80, 0x02}}, + }, + /* 10 fps */ + { + {0, }, + {9, 932, 1278, {0x14, 0xF4, 0x05, 0x27, 0xEE, 0x26, 0x36, 0x04, 0x30, 0xA4, 0x33, 0x10, 0x02}}, + {4, 591, 812, {0x14, 0xF4, 0x15, 0x19, 0x56, 0x17, 0x9E, 0x06, 0x28, 0x4F, 0x4E, 0x10, 0x02}}, + {2, 291, 400, {0x14, 0xF4, 0x3D, 0x0C, 0x7A, 0x0A, 0xC2, 0x0E, 0x28, 0x23, 0xA1, 0x10, 0x02}}, + }, + /* 15 fps */ + { + {0, }, + {9, 956, 876, {0x13, 0xF4, 0x0D, 0x1B, 0x58, 0x19, 0xA0, 0x05, 0x38, 0xBC, 0x33, 0x60, 0x02}}, + {5, 703, 644, {0x13, 0xF4, 0x1D, 0x14, 0x1C, 0x12, 0x64, 0x08, 0x38, 0xBF, 0x42, 0x60, 0x02}}, + {3, 448, 410, {0x13, 0xF4, 0x3D, 0x0C, 0xC4, 0x0B, 0x0C, 0x0E, 0x38, 0xC0, 0x69, 0x60, 0x02}}, + }, + /* 20 fps */ + { + {0, }, + {9, 956, 650, {0x12, 0xF4, 0x1D, 0x14, 0x4A, 0x12, 0x92, 0x09, 0x48, 0xBC, 0x33, 0x10, 0x03}}, + {6, 776, 528, {0x12, 0xF4, 0x2D, 0x10, 0x7E, 0x0E, 0xC6, 0x0A, 0x40, 0x08, 0x3F, 0x10, 0x03}}, + {4, 591, 402, {0x12, 0xF4, 0x3D, 0x0C, 0x8F, 0x0A, 0xD7, 0x0E, 0x40, 0x4F, 0x4E, 0x10, 0x03}}, + }, + /* 25 fps */ + { + {0, }, + {9, 956, 544, {0x11, 0xF4, 0x25, 0x10, 0xF4, 0x0F, 0x3C, 0x0A, 0x48, 0xBC, 0x33, 0xC0, 0x02}}, + {7, 840, 478, {0x11, 0xF4, 0x2D, 0x0E, 0xEB, 0x0D, 0x33, 0x0B, 0x48, 0x48, 0x3B, 0xC0, 0x02}}, + {5, 703, 400, {0x11, 0xF4, 0x3D, 0x0C, 0x7A, 0x0A, 0xC2, 0x0E, 0x48, 0xBF, 0x42, 0xC0, 0x02}}, + }, + /* 30 fps */ + { + {0, }, + {9, 956, 438, {0x10, 0xF4, 0x35, 0x0D, 0xAC, 0x0B, 0xF4, 0x0D, 0x50, 0xBC, 0x33, 0x10, 0x02}}, + {7, 838, 384, {0x10, 0xF4, 0x45, 0x0B, 0xFD, 0x0A, 0x45, 0x0F, 0x50, 0x46, 0x3B, 0x10, 0x02}}, + {6, 773, 354, {0x10, 0x7A, 0x4B, 0x0B, 0x0C, 0x09, 0x80, 0x10, 0x50, 0x05, 0x3F, 0x10, 0x02}}, + }, + }, + /* VGA */ + { + /* 5 fps */ + { + {0, }, + {6, 773, 1272, {0x1D, 0xF4, 0x15, 0x27, 0xB6, 0x24, 0x96, 0x02, 0x30, 0x05, 0x3F, 0x10, 0x02}}, + {4, 592, 976, {0x1D, 0xF4, 0x25, 0x1E, 0x78, 0x1B, 0x58, 0x03, 0x30, 0x50, 0x4E, 0x10, 0x02}}, + {3, 448, 738, {0x1D, 0xF4, 0x3D, 0x17, 0x0C, 0x13, 0xEC, 0x04, 0x30, 0xC0, 0x69, 0x10, 0x02}}, + }, + /* 10 fps */ + { + {0, }, + {9, 956, 788, {0x1C, 0xF4, 0x35, 0x18, 0x9C, 0x15, 0x7C, 0x03, 0x48, 0xBC, 0x33, 0x10, 0x02}}, + {6, 776, 640, {0x1C, 0x7A, 0x53, 0x13, 0xFC, 0x11, 0x2C, 0x04, 0x48, 0x08, 0x3F, 0x10, 0x02}}, + {4, 592, 488, {0x1C, 0x7A, 0x6B, 0x0F, 0x3C, 0x0C, 0x6C, 0x06, 0x48, 0x50, 0x4E, 0x10, 0x02}}, + }, + /* 15 fps */ + { + {0, }, + {9, 957, 526, {0x1B, 0x7A, 0x63, 0x10, 0x68, 0x0D, 0x98, 0x06, 0x58, 0xBD, 0x33, 0x80, 0x02}}, + {9, 957, 526, {0x1B, 0x7A, 0x63, 0x10, 0x68, 0x0D, 0x98, 0x06, 0x58, 0xBD, 0x33, 0x80, 0x02}}, + {8, 895, 492, {0x1B, 0x7A, 0x6B, 0x0F, 0x5D, 0x0C, 0x8D, 0x06, 0x58, 0x7F, 0x37, 0x80, 0x02}}, + }, + /* 20 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 25 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + /* 30 fps */ + { + {0, }, + {0, }, + {0, }, + {0, }, + }, + }, +}; + diff --git a/drivers/media/video/pwc/pwc-timon.h b/drivers/media/video/pwc/pwc-timon.h new file mode 100644 index 00000000000..a86b3782a08 --- /dev/null +++ b/drivers/media/video/pwc/pwc-timon.h @@ -0,0 +1,61 @@ +/* Linux driver for Philips webcam + (C) 2004 Luc Saillard (luc@saillard.org) + + NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx + driver and thus may have bugs that are not present in the original version. + Please send bug reports and support requests to <luc@saillard.org>. + The decompression routines have been implemented by reverse-engineering the + Nemosoft binary pwcx module. Caveat emptor. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + + +/* This tables contains entries for the 675/680/690 (Timon) camera, with + 4 different qualities (no compression, low, medium, high). + It lists the bandwidth requirements for said mode by its alternate interface + number. An alternate of 0 means that the mode is unavailable. + + There are 6 * 4 * 4 entries: + 6 different resolutions subqcif, qsif, qcif, sif, cif, vga + 6 framerates: 5, 10, 15, 20, 25, 30 + 4 compression modi: none, low, medium, high + + When an uncompressed mode is not available, the next available compressed mode + will be chosen (unless the decompressor is absent). Sometimes there are only + 1 or 2 compressed modes available; in that case entries are duplicated. +*/ + +#ifndef PWC_TIMON_H +#define PWC_TIMON_H + +#include "pwc-ioctl.h" + +struct Timon_table_entry +{ + char alternate; /* USB alternate interface */ + unsigned short packetsize; /* Normal packet size */ + unsigned short bandlength; /* Bandlength when decompressing */ + unsigned char mode[13]; /* precomputed mode settings for cam */ +}; + +const extern struct Timon_table_entry Timon_table[PSZ_MAX][6][4]; +const extern unsigned int TimonRomTable [16][2][16][8]; + + +#endif + + diff --git a/drivers/media/video/pwc/pwc-uncompress.c b/drivers/media/video/pwc/pwc-uncompress.c new file mode 100644 index 00000000000..b37a89a163f --- /dev/null +++ b/drivers/media/video/pwc/pwc-uncompress.c @@ -0,0 +1,146 @@ +/* Linux driver for Philips webcam + Decompression frontend. + (C) 1999-2003 Nemosoft Unv. + (C) 2004 Luc Saillard (luc@saillard.org) + + NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx + driver and thus may have bugs that are not present in the original version. + Please send bug reports and support requests to <luc@saillard.org>. + The decompression routines have been implemented by reverse-engineering the + Nemosoft binary pwcx module. Caveat emptor. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <asm/current.h> +#include <asm/types.h> + +#include "pwc.h" +#include "pwc-uncompress.h" + +int pwc_decompress(struct pwc_device *pdev) +{ + struct pwc_frame_buf *fbuf; + int n, line, col, stride; + void *yuv, *image; + u16 *src; + u16 *dsty, *dstu, *dstv; + + if (pdev == NULL) + return -EFAULT; +#if defined(__KERNEL__) && defined(PWC_MAGIC) + if (pdev->magic != PWC_MAGIC) { + Err("pwc_decompress(): magic failed.\n"); + return -EFAULT; + } +#endif + + fbuf = pdev->read_frame; + if (fbuf == NULL) + return -EFAULT; + image = pdev->image_ptr[pdev->fill_image]; + if (!image) + return -EFAULT; + + yuv = fbuf->data + pdev->frame_header_size; /* Skip header */ + + /* Raw format; that's easy... */ + if (pdev->vpalette == VIDEO_PALETTE_RAW) + { + memcpy(image, yuv, pdev->frame_size); + return 0; + } + + if (pdev->vbandlength == 0) { + /* Uncompressed mode. We copy the data into the output buffer, + using the viewport size (which may be larger than the image + size). Unfortunately we have to do a bit of byte stuffing + to get the desired output format/size. + */ + /* + * We do some byte shuffling here to go from the + * native format to YUV420P. + */ + src = (u16 *)yuv; + n = pdev->view.x * pdev->view.y; + + /* offset in Y plane */ + stride = pdev->view.x * pdev->offset.y + pdev->offset.x; + dsty = (u16 *)(image + stride); + + /* offsets in U/V planes */ + stride = pdev->view.x * pdev->offset.y / 4 + pdev->offset.x / 2; + dstu = (u16 *)(image + n + stride); + dstv = (u16 *)(image + n + n / 4 + stride); + + /* increment after each line */ + stride = (pdev->view.x - pdev->image.x) / 2; /* u16 is 2 bytes */ + + for (line = 0; line < pdev->image.y; line++) { + for (col = 0; col < pdev->image.x; col += 4) { + *dsty++ = *src++; + *dsty++ = *src++; + if (line & 1) + *dstv++ = *src++; + else + *dstu++ = *src++; + } + dsty += stride; + if (line & 1) + dstv += (stride >> 1); + else + dstu += (stride >> 1); + } + } + else { + /* Compressed; the decompressor routines will write the data + in planar format immediately. + */ + int flags; + + flags = PWCX_FLAG_PLANAR; + if (pdev->vsize == PSZ_VGA && pdev->vframes == 5 && pdev->vsnapshot) + { + printk(KERN_ERR "pwc: Mode Bayer is not supported for now\n"); + flags |= PWCX_FLAG_BAYER; + return -ENXIO; /* No such device or address: missing decompressor */ + } + +#if 0 + switch (pdev->type) + { + case 675: + case 680: + case 690: + case 720: + case 730: + case 740: + case 750: + pwc_dec23_decompress(&pdev->image, &pdev->view, + &pdev->offset, yuv, image, flags, + pdev->decompress_data, pdev->vbandlength); + break; + case 645: + case 646: + /* TODO & FIXME */ + return -ENXIO; /* Missing decompressor */ + break; + } +#endif + } + return 0; +} + + diff --git a/drivers/media/video/pwc/pwc-uncompress.h b/drivers/media/video/pwc/pwc-uncompress.h new file mode 100644 index 00000000000..f75e1b6cbe1 --- /dev/null +++ b/drivers/media/video/pwc/pwc-uncompress.h @@ -0,0 +1,41 @@ +/* (C) 1999-2003 Nemosoft Unv. + (C) 2004 Luc Saillard (luc@saillard.org) + + NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx + driver and thus may have bugs that are not present in the original version. + Please send bug reports and support requests to <luc@saillard.org>. + The decompression routines have been implemented by reverse-engineering the + Nemosoft binary pwcx module. Caveat emptor. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* This file is the bridge between the kernel module and the plugin; it + describes the structures and datatypes used in both modules. Any + significant change should be reflected by increasing the + pwc_decompressor_version major number. + */ +#ifndef PWC_UNCOMPRESS_H +#define PWC_UNCOMPRESS_H + +#include <linux/config.h> + +#include "pwc-ioctl.h" + +/* from pwc-dec.h */ +#define PWCX_FLAG_PLANAR 0x0001 +/* */ + +#endif diff --git a/drivers/media/video/pwc/pwc.h b/drivers/media/video/pwc/pwc.h new file mode 100644 index 00000000000..1b0ee0ced0e --- /dev/null +++ b/drivers/media/video/pwc/pwc.h @@ -0,0 +1,272 @@ +/* (C) 1999-2003 Nemosoft Unv. + (C) 2004 Luc Saillard (luc@saillard.org) + + NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx + driver and thus may have bugs that are not present in the original version. + Please send bug reports and support requests to <luc@saillard.org>. + The decompression routines have been implemented by reverse-engineering the + Nemosoft binary pwcx module. Caveat emptor. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef PWC_H +#define PWC_H + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/spinlock.h> +#include <linux/videodev.h> +#include <linux/wait.h> +#include <linux/smp_lock.h> +#include <asm/semaphore.h> +#include <asm/errno.h> + +#include "pwc-uncompress.h" +#include "pwc-ioctl.h" + +/* Defines and structures for the Philips webcam */ +/* Used for checking memory corruption/pointer validation */ +#define PWC_MAGIC 0x89DC10ABUL +#undef PWC_MAGIC + +/* Turn some debugging options on/off */ +#define PWC_DEBUG 0 + +/* Trace certain actions in the driver */ +#define TRACE_MODULE 0x0001 +#define TRACE_PROBE 0x0002 +#define TRACE_OPEN 0x0004 +#define TRACE_READ 0x0008 +#define TRACE_MEMORY 0x0010 +#define TRACE_FLOW 0x0020 +#define TRACE_SIZE 0x0040 +#define TRACE_PWCX 0x0080 +#define TRACE_SEQUENCE 0x1000 + +#define Trace(R, A...) if (pwc_trace & R) printk(KERN_DEBUG PWC_NAME " " A) +#define Debug(A...) printk(KERN_DEBUG PWC_NAME " " A) +#define Info(A...) printk(KERN_INFO PWC_NAME " " A) +#define Err(A...) printk(KERN_ERR PWC_NAME " " A) + + +/* Defines for ToUCam cameras */ +#define TOUCAM_HEADER_SIZE 8 +#define TOUCAM_TRAILER_SIZE 4 + +#define FEATURE_MOTOR_PANTILT 0x0001 + +/* Version block */ +#define PWC_MAJOR 9 +#define PWC_MINOR 0 +#define PWC_VERSION "9.0.2-unofficial" +#define PWC_NAME "pwc" + +/* Turn certain features on/off */ +#define PWC_INT_PIPE 0 + +/* Ignore errors in the first N frames, to allow for startup delays */ +#define FRAME_LOWMARK 5 + +/* Size and number of buffers for the ISO pipe. */ +#define MAX_ISO_BUFS 2 +#define ISO_FRAMES_PER_DESC 10 +#define ISO_MAX_FRAME_SIZE 960 +#define ISO_BUFFER_SIZE (ISO_FRAMES_PER_DESC * ISO_MAX_FRAME_SIZE) + +/* Frame buffers: contains compressed or uncompressed video data. */ +#define MAX_FRAMES 5 +/* Maximum size after decompression is 640x480 YUV data, 1.5 * 640 * 480 */ +#define PWC_FRAME_SIZE (460800 + TOUCAM_HEADER_SIZE + TOUCAM_TRAILER_SIZE) + +/* Absolute maximum number of buffers available for mmap() */ +#define MAX_IMAGES 10 + +/* The following structures were based on cpia.h. Why reinvent the wheel? :-) */ +struct pwc_iso_buf +{ + void *data; + int length; + int read; + struct urb *urb; +}; + +/* intermediate buffers with raw data from the USB cam */ +struct pwc_frame_buf +{ + void *data; + volatile int filled; /* number of bytes filled */ + struct pwc_frame_buf *next; /* list */ +#if PWC_DEBUG + int sequence; /* Sequence number */ +#endif +}; + +struct pwc_device +{ + struct video_device *vdev; +#ifdef PWC_MAGIC + int magic; +#endif + /* Pointer to our usb_device */ + struct usb_device *udev; + + int type; /* type of cam (645, 646, 675, 680, 690, 720, 730, 740, 750) */ + int release; /* release number */ + int features; /* feature bits */ + char serial[30]; /* serial number (string) */ + int error_status; /* set when something goes wrong with the cam (unplugged, USB errors) */ + int usb_init; /* set when the cam has been initialized over USB */ + + /*** Video data ***/ + int vopen; /* flag */ + int vendpoint; /* video isoc endpoint */ + int vcinterface; /* video control interface */ + int valternate; /* alternate interface needed */ + int vframes, vsize; /* frames-per-second & size (see PSZ_*) */ + int vpalette; /* palette: 420P, RAW or RGBBAYER */ + int vframe_count; /* received frames */ + int vframes_dumped; /* counter for dumped frames */ + int vframes_error; /* frames received in error */ + int vmax_packet_size; /* USB maxpacket size */ + int vlast_packet_size; /* for frame synchronisation */ + int visoc_errors; /* number of contiguous ISOC errors */ + int vcompression; /* desired compression factor */ + int vbandlength; /* compressed band length; 0 is uncompressed */ + char vsnapshot; /* snapshot mode */ + char vsync; /* used by isoc handler */ + char vmirror; /* for ToUCaM series */ + + int cmd_len; + unsigned char cmd_buf[13]; + + /* The image acquisition requires 3 to 4 steps: + 1. data is gathered in short packets from the USB controller + 2. data is synchronized and packed into a frame buffer + 3a. in case data is compressed, decompress it directly into image buffer + 3b. in case data is uncompressed, copy into image buffer with viewport + 4. data is transferred to the user process + + Note that MAX_ISO_BUFS != MAX_FRAMES != MAX_IMAGES.... + We have in effect a back-to-back-double-buffer system. + */ + /* 1: isoc */ + struct pwc_iso_buf sbuf[MAX_ISO_BUFS]; + char iso_init; + + /* 2: frame */ + struct pwc_frame_buf *fbuf; /* all frames */ + struct pwc_frame_buf *empty_frames, *empty_frames_tail; /* all empty frames */ + struct pwc_frame_buf *full_frames, *full_frames_tail; /* all filled frames */ + struct pwc_frame_buf *fill_frame; /* frame currently being filled */ + struct pwc_frame_buf *read_frame; /* frame currently read by user process */ + int frame_header_size, frame_trailer_size; + int frame_size; + int frame_total_size; /* including header & trailer */ + int drop_frames; +#if PWC_DEBUG + int sequence; /* Debugging aid */ +#endif + + /* 3: decompression */ + struct pwc_decompressor *decompressor; /* function block with decompression routines */ + void *decompress_data; /* private data for decompression engine */ + + /* 4: image */ + /* We have an 'image' and a 'view', where 'image' is the fixed-size image + as delivered by the camera, and 'view' is the size requested by the + program. The camera image is centered in this viewport, laced with + a gray or black border. view_min <= image <= view <= view_max; + */ + int image_mask; /* bitmask of supported sizes */ + struct pwc_coord view_min, view_max; /* minimum and maximum viewable sizes */ + struct pwc_coord abs_max; /* maximum supported size with compression */ + struct pwc_coord image, view; /* image and viewport size */ + struct pwc_coord offset; /* offset within the viewport */ + + void *image_data; /* total buffer, which is subdivided into ... */ + void *image_ptr[MAX_IMAGES]; /* ...several images... */ + int fill_image; /* ...which are rotated. */ + int len_per_image; /* length per image */ + int image_read_pos; /* In case we read data in pieces, keep track of were we are in the imagebuffer */ + int image_used[MAX_IMAGES]; /* For MCAPTURE and SYNC */ + + struct semaphore modlock; /* to prevent races in video_open(), etc */ + spinlock_t ptrlock; /* for manipulating the buffer pointers */ + + /*** motorized pan/tilt feature */ + struct pwc_mpt_range angle_range; + int pan_angle; /* in degrees * 100 */ + int tilt_angle; /* absolute angle; 0,0 is home position */ + + /*** Misc. data ***/ + wait_queue_head_t frameq; /* When waiting for a frame to finish... */ +#if PWC_INT_PIPE + void *usb_int_handler; /* for the interrupt endpoint */ +#endif +}; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Global variable */ +extern int pwc_trace; + +/** functions in pwc-if.c */ +int pwc_try_video_mode(struct pwc_device *pdev, int width, int height, int new_fps, int new_compression, int new_snapshot); + +/** Functions in pwc-misc.c */ +/* sizes in pixels */ +extern struct pwc_coord pwc_image_sizes[PSZ_MAX]; + +int pwc_decode_size(struct pwc_device *pdev, int width, int height); +void pwc_construct(struct pwc_device *pdev); + +/** Functions in pwc-ctrl.c */ +/* Request a certain video mode. Returns < 0 if not possible */ +extern int pwc_set_video_mode(struct pwc_device *pdev, int width, int height, int frames, int compression, int snapshot); + +/* Various controls; should be obvious. Value 0..65535, or < 0 on error */ +extern int pwc_get_brightness(struct pwc_device *pdev); +extern int pwc_set_brightness(struct pwc_device *pdev, int value); +extern int pwc_get_contrast(struct pwc_device *pdev); +extern int pwc_set_contrast(struct pwc_device *pdev, int value); +extern int pwc_get_gamma(struct pwc_device *pdev); +extern int pwc_set_gamma(struct pwc_device *pdev, int value); +extern int pwc_get_saturation(struct pwc_device *pdev); +extern int pwc_set_saturation(struct pwc_device *pdev, int value); +extern int pwc_set_leds(struct pwc_device *pdev, int on_value, int off_value); +extern int pwc_get_cmos_sensor(struct pwc_device *pdev, int *sensor); + +/* Power down or up the camera; not supported by all models */ +extern int pwc_camera_power(struct pwc_device *pdev, int power); + +/* Private ioctl()s; see pwc-ioctl.h */ +extern int pwc_ioctl(struct pwc_device *pdev, unsigned int cmd, void *arg); + + +/** pwc-uncompress.c */ +/* Expand frame to image, possibly including decompression. Uses read_frame and fill_image */ +extern int pwc_decompress(struct pwc_device *pdev); + +#ifdef __cplusplus +} +#endif + + +#endif |