patch-2.4.5 linux/drivers/usb/pwc-if.c

Next file: linux/drivers/usb/pwc-ioctl.h
Previous file: linux/drivers/usb/pwc-ctrl.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.4/linux/drivers/usb/pwc-if.c linux/drivers/usb/pwc-if.c
@@ -0,0 +1,1841 @@
+/* Linux driver for Philips webcam 
+   USB and Video4Linux interface part.
+   (C) 1999-2001 Nemosoft Unv.
+
+   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 *
+*/   
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/wrapper.h>
+#include <asm/io.h>
+
+#include "pwc.h"
+#include "pwc-ioctl.h"
+#include "pwc-uncompress.h"
+
+#if !defined(MAP_NR)
+#define MAP_NR(a) virt_to_page(a)
+#endif
+
+/* Function prototypes and driver templates */
+
+/* hotplug device table support */
+static __devinitdata struct usb_device_id pwc_device_table [] = {
+	{ USB_DEVICE(0x0471, 0x0302) },
+	{ 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(0x069A, 0x0001) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, pwc_device_table);
+
+static void *usb_pwc_probe(struct usb_device *udev, unsigned int ifnum, const struct usb_device_id *id);
+static void usb_pwc_disconnect(struct usb_device *udev, void *ptr);
+
+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() */
+};
+
+static int default_size = PSZ_QCIF;
+static int default_fps = 10;
+static int default_palette = VIDEO_PALETTE_RGB24; /* This is normal for webcams */
+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;
+static int power_save = 0;
+int pwc_preferred_compression = 2; /* 0..3 = uncompressed..high */
+
+static struct semaphore mem_lock;
+static void *mem_leak = NULL; /* For delayed kfree()s. See below */
+
+static int video_nr = -1;
+
+/***/
+
+static int  pwc_video_open(struct video_device *vdev, int mode);
+static void pwc_video_close(struct video_device *vdev);
+static long pwc_video_read(struct video_device *vdev, char *buf, unsigned long count, int noblock);
+static long pwc_video_write(struct video_device *vdev, const char *buf, unsigned long count, int noblock);
+static unsigned int pwc_video_poll(struct video_device *vdev, struct file *file, poll_table *wait);
+static int  pwc_video_ioctl(struct video_device *vdev, unsigned int cmd, void *arg);
+static int  pwc_video_mmap(struct video_device *dev, const char *adr, unsigned long size);
+
+static struct video_device pwc_template = {
+	owner:		THIS_MODULE,
+	name:		"Philips Webcam",	/* Filled in later */
+	type:		VID_TYPE_CAPTURE,
+	hardware:	VID_HARDWARE_PWC,	/* Let's pretend for now */
+	open:		pwc_video_open,
+	close:		pwc_video_close,
+	read:		pwc_video_read,
+	write:		pwc_video_write,
+	poll:		pwc_video_poll,
+	ioctl:		pwc_video_ioctl,
+	mmap:		pwc_video_mmap,
+	initialize:	NULL,			/* initialize */
+	minor:		0			/* minor */
+};
+
+/***************************************************************************/
+
+/* 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 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 */
+
+/* Memory management functions, nicked from cpia.c, which nicked them from
+   bttv.c. So far, I've counted duplication of this code 6 times 
+   (bttv, cpia, ibmcam, ov511, pwc, ieee1394).
+ */
+
+/* Given PGD from the address space's page table, return the kernel
+ * virtual mapping of the physical memory mapped at ADR.
+ */
+static inline unsigned long uvirt_to_kva(pgd_t *pgd, unsigned long adr)
+{
+	unsigned long ret = 0UL;
+	pmd_t *pmd;
+	pte_t *ptep, pte;
+
+	if (!pgd_none(*pgd)) {
+		pmd = pmd_offset(pgd, adr);
+		if (!pmd_none(*pmd)) {
+			ptep = pte_offset(pmd, adr);
+			pte = *ptep;
+			if (pte_present(pte)) {
+				ret = (unsigned long) page_address(pte_page(pte));
+				ret |= (adr & (PAGE_SIZE-1));
+			}
+		}
+	}
+	return ret;
+}
+
+static inline unsigned long uvirt_to_bus(unsigned long adr)
+{
+	unsigned long kva, ret;
+
+	kva = uvirt_to_kva(pgd_offset(current->mm, adr), adr);
+	ret = virt_to_bus((void *)kva);
+	return ret;
+}
+
+static inline unsigned long kvirt_to_bus(unsigned long adr)
+{
+	unsigned long va, kva, ret;
+
+	va = VMALLOC_VMADDR(adr);
+	kva = uvirt_to_kva(pgd_offset_k(va), va);
+	ret = virt_to_bus((void *)kva);
+	return ret;
+}
+
+/* Here we want the physical address of the memory.
+ * This is used when initializing the contents of the
+ * area and marking the pages as reserved.
+ */
+static inline unsigned long kvirt_to_pa(unsigned long adr)
+{
+	unsigned long va, kva, ret;
+
+	va = VMALLOC_VMADDR(adr);
+	kva = uvirt_to_kva(pgd_offset_k(va), va);
+	ret = __pa(kva);
+	return ret;
+}
+
+static void *rvmalloc(unsigned long size)
+{
+	void *mem;
+	unsigned long adr, page;
+
+	/* Round it off to PAGE_SIZE */
+	size += (PAGE_SIZE - 1);
+	size &= ~(PAGE_SIZE - 1);
+
+	mem = vmalloc(size);
+	if (!mem)
+		return NULL;
+
+	memset(mem, 0, size); /* Clear the ram out, no junk to the user */
+	adr = (unsigned long) mem;
+	while (size > 0) {
+		page = kvirt_to_pa(adr);
+		mem_map_reserve(MAP_NR(__va(page)));
+		adr += PAGE_SIZE;
+		if (size > PAGE_SIZE)
+			size -= PAGE_SIZE;
+		else
+			size = 0;
+	}
+	return mem;
+}
+
+static void rvfree(void *mem, unsigned long size)
+{
+	unsigned long adr, page;
+
+	if (!mem)
+		return;
+
+	size += (PAGE_SIZE - 1);
+	size &= ~(PAGE_SIZE - 1);
+
+	adr=(unsigned long) mem;
+	while (size > 0) {
+		page = kvirt_to_pa(adr);
+		mem_map_unreserve(MAP_NR(__va(page)));
+		adr += PAGE_SIZE;
+		if (size > PAGE_SIZE)
+			size -= PAGE_SIZE;
+		else
+			size = 0;
+	}
+	vfree(mem);
+}
+
+
+
+static int pwc_allocate_buffers(struct pwc_device *pdev)
+{
+	int i;
+	void *kbuf;
+
+	Trace(TRACE_MEMORY, "Entering allocate_buffers(%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 Isochronuous 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;
+			}
+			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;
+		}
+		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(FRAME_SIZE); /* need vmalloc since frame buffer > 128K */
+			if (kbuf == NULL) {
+				Err("Failed to allocate frame buffer %d.\n", i);
+				return -ENOMEM;
+			}
+			pdev->fbuf[i].data = kbuf;
+			memset(kbuf, 0, FRAME_SIZE);
+		}
+	}
+	
+	/* Allocate decompressor buffer space */
+	kbuf = vmalloc(FRAME_SIZE);
+	if (kbuf == NULL) {
+		Err("Failed to allocate compressed image buffer.\n");
+		return -ENOMEM;
+	}
+	memset(kbuf, 0, FRAME_SIZE);
+	pdev->decompress_buffer = kbuf;
+	
+	/* Allocate image buffer; double buffer for mmap() */
+	kbuf = rvmalloc(default_mbufs * pdev->view_max.size * 4);
+	if (kbuf == NULL) {
+		Err("Failed to allocate image buffer(s).\n");
+		return -ENOMEM;
+	}
+	pdev->image_data = kbuf;
+	for (i = 0; i < default_mbufs; i++)
+		pdev->image_ptr[i] = kbuf + (i * pdev->view_max.size * 4);
+	for (; i < MAX_IMAGES; i++)
+		pdev->image_ptr[i] = NULL;
+
+	Trace(TRACE_MEMORY, "Leaving 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 */
+	Trace(TRACE_MEMORY, "Freeing ISO buffers.\n");
+	for (i = 0; i < MAX_ISO_BUFS; i++)
+		if (pdev->sbuf[i].data != NULL) {
+			kfree(pdev->sbuf[i].data);
+			pdev->sbuf[i].data = NULL;
+		}
+	/* The same for frame buffers */
+	Trace(TRACE_MEMORY, "Freeing frame buffers.\n");
+	if (pdev->fbuf != NULL) {
+		for (i = 0; i < default_fbufs; i++) {
+			if (pdev->fbuf[i].data != NULL) {
+				vfree(pdev->fbuf[i].data);
+				pdev->fbuf[i].data = NULL;
+			}
+		}
+		kfree(pdev->fbuf);
+		pdev->fbuf = NULL;
+	}
+	/* Intermediate decompression buffer */
+	Trace(TRACE_MEMORY, "Freeing decompression buffer\n");
+	if (pdev->decompress_buffer != NULL)
+		vfree(pdev->decompress_buffer);
+	pdev->decompress_buffer = NULL;
+
+	/* Release image buffers */
+	Trace(TRACE_MEMORY, "Freeing image buffers\n");
+	if (pdev->image_data != NULL)
+		rvfree(pdev->image_data, default_mbufs * pdev->view_max.size * 4);
+	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_hanlder) 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 any of the desired image formats (rgb, bgr, yuv, etc).
+   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 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, 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, 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, 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;
+}
+
+static int pwc_set_palette(struct pwc_device *pdev, int pal)
+{
+	if (
+            pal == VIDEO_PALETTE_YUV420
+#if PWC_DEBUG
+            || pal == VIDEO_PALETTE_RAW
+#endif
+	) {
+		pdev->vpalette = pal;
+		pwc_set_image_buffer_size(pdev);
+		return 0;
+	}
+	return -1;
+}
+
+
+
+/* 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(purb_t urb)
+{
+	struct pwc_device *pdev;
+	int i, fst, flen;
+	int awake;
+	struct pwc_frame_buf *fbuf;
+	unsigned char *fillptr, *iso_buf;
+
+	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 unlinked.\n");
+		return;
+	}
+	if (urb->status != -EINPROGRESS && urb->status != 0) {
+		Trace(TRACE_FLOW, "pwc_isoc_handler() called with status %d.\n", urb->status);
+		return;
+	}
+
+	fbuf = pdev->fill_frame;
+	if (fbuf == NULL) {
+		Err("pwc_isoc_handler without valid fill frame.\n");
+		if (waitqueue_active(&pdev->frameq))
+			wake_up_interruptible(&pdev->frameq);
+		if (waitqueue_active(&pdev->pollq))
+			wake_up_interruptible(&pdev->pollq);
+		return;
+	}
+	fillptr = fbuf->data + fbuf->filled;
+	awake = 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_size) {
+						Trace(TRACE_FLOW, "Frame buffer overflow (flen = %d, frame_size = %d).\n", flen, pdev->frame_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++;
+						}
+						/* 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)
+						pdev->drop_frames--;
+					else {
+						/* Check for underflow first */
+						if (fbuf->filled < pdev->frame_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)
+										Info("Dumping frame %d.\n", pdev->vframe_count);
+									if (pdev->vframes_dumped == 20)
+										Info("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 (awake) {
+		if (waitqueue_active(&pdev->frameq))
+			wake_up_interruptible(&pdev->frameq);
+		if (waitqueue_active(&pdev->pollq))
+			wake_up_interruptible(&pdev->pollq);
+	}
+}
+
+
+static int pwc_isoc_init(struct pwc_device *pdev)
+{
+	struct usb_device *udev;
+	purb_t urb;
+	int i, j, ret;
+
+	struct usb_interface_descriptor *idesc;
+	int cur_alt;
+
+	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;
+	cur_alt = udev->actconfig->interface[0].act_altsetting;
+	idesc = &udev->actconfig->interface[0].altsetting[cur_alt];
+	if (!idesc)
+		return -EFAULT;
+
+	/* Search video endpoint */
+	pdev->vmax_packet_size = -1;
+	for (i = 0; i < idesc->bNumEndpoints; i++)
+		if ((idesc->endpoint[i].bEndpointAddress & 0xF) == pdev->vendpoint) {
+			pdev->vmax_packet_size = idesc->endpoint[i].wMaxPacketSize;
+			break;
+		}
+	
+	if (pdev->vmax_packet_size < 0) {
+		Err("Failed to find packet size for video endpoint in current alternate setting.\n");
+		return -ENFILE; /* Odd error, that should be noticable */
+	}
+
+	ret = 0;
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		urb = usb_alloc_urb(ISO_FRAMES_PER_DESC);
+		if (urb == NULL) {
+			Err("Failed to allocate urb %d\n", i);
+			ret = -ENOMEM;
+			break;
+		}
+		pdev->sbuf[i].urb = urb;
+	}
+	if (ret) {
+		/* De-allocate in reverse order */
+		while (i >= 0) {
+			if (pdev->sbuf[i].urb != NULL)
+				usb_free_urb(pdev->sbuf[i].urb);
+			i--;
+		}
+		return ret;
+	}
+	
+	
+	/* init URB structure */	
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		urb = pdev->sbuf[i].urb;
+
+		urb->next = pdev->sbuf[(i + 1) % MAX_ISO_BUFS].urb;
+		urb->dev = udev;
+	        urb->pipe = usb_rcvisocpipe(udev, pdev->vendpoint);
+		urb->transfer_flags = USB_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 = ISO_MAX_FRAME_SIZE;
+		}
+	}
+
+	/* link */
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		ret = usb_submit_urb(pdev->sbuf[i].urb);
+		if (ret)
+			Err("isoc_init() submit_urb %d failed with error %d\n", i, ret);
+	}
+
+	/* data should stream in now */
+	pdev->iso_init = 1;
+	return 0;
+}
+
+static void pwc_isoc_cleanup(struct pwc_device *pdev)
+{
+	int i;
+	
+	if (pdev == NULL)
+		return;
+	if (!pdev->iso_init)
+		return;
+	/* Stop camera, but only if we are sure the camera is still there */
+	if (!pdev->unplugged)
+		usb_set_interface(pdev->udev, 0, 0);
+	for (i = MAX_ISO_BUFS - 1; i >= 0; i--) {
+		pdev->sbuf[i].urb->next = NULL;
+		usb_unlink_urb(pdev->sbuf[i].urb);
+		usb_free_urb(pdev->sbuf[i].urb);
+		pdev->sbuf[i].urb = NULL;
+	}
+	pdev->iso_init = 0;
+}
+
+int pwc_try_video_mode(struct pwc_device *pdev, int width, int height, int new_fps, int new_compression, int new_snapshot)
+{
+	int ret;
+
+	/* Stop isoc stuff */
+	pwc_isoc_cleanup(pdev);
+	/* Reset parameters */
+	pwc_reset_buffers(pdev);
+	/* Try to set video mode... */
+	ret = pwc_set_video_mode(pdev, width, height, new_fps, new_compression, new_snapshot);
+	if (ret) /* That failed... restore old mode (we know that worked) */
+		ret = pwc_set_video_mode(pdev, pdev->view.x, pdev->view.y, pdev->vframes, pdev->vcompression, pdev->vsnapshot);
+	else /* Set (new) alternate interface */
+		ret = usb_set_interface(pdev->udev, 0, pdev->valternate);
+	if (!ret)
+		ret = pwc_isoc_init(pdev);
+	pdev->drop_frames = 1; /* try to avoid garbage during switch */
+	return ret;
+}
+
+
+static inline void set_mem_leak(void *ptr)
+{
+	down(&mem_lock);
+	if (mem_leak != NULL)
+		Err("Memleak: overwriting mem_leak pointer!\n");
+	Trace(TRACE_MEMORY, "Setting mem_leak to 0x%p.\n", ptr);
+	mem_leak = ptr;
+	up(&mem_lock);
+}
+
+static inline void free_mem_leak(void)
+{
+	down(&mem_lock);
+	if (mem_leak != NULL) {
+		Trace(TRACE_MEMORY, "Freeing mem_leak ptr 0x%p.\n", mem_leak);
+		kfree(mem_leak);
+		mem_leak = NULL;
+	}
+	up(&mem_lock);
+}
+
+
+/***************************************************************************/
+/* Video4Linux functions */
+
+static int pwc_video_open(struct video_device *vdev, int mode)
+{
+	int i;
+	struct pwc_device *pdev;
+
+	Trace(TRACE_OPEN, "video_open called(0x%p, 0%o).\n", vdev, mode);
+	
+	if (vdev == NULL)
+		BUG();
+	pdev = (struct pwc_device *)vdev->priv;
+	if (pdev == NULL)
+		BUG();
+	
+	down(&pdev->modlock);
+	if (!pdev->usb_init) {
+		Trace(TRACE_OPEN, "Doing first time initialization.\n");
+		/* Reset camera */
+		if (usb_set_interface(pdev->udev, 0, 0))
+			Info("Failed to set alternate interface to 0.\n");
+		pdev->usb_init = 1;
+	}
+	else {
+		/* 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);
+		}
+	}
+
+	/* So far, so good. Allocate memory. */
+	i = pwc_allocate_buffers(pdev);
+	if (i < 0) {
+		Trace(TRACE_OPEN, "Failed to allocate 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->vpalette = default_palette;
+#if PWC_DEBUG	
+	pdev->sequence = 0;
+#endif
+
+	/* Find our decompressor, if any */
+	pdev->decompressor = pwc_find_decompressor(pdev->type);
+#if PWC_DEBUG	
+	Debug("Found decompressor for %d at 0x%p\n", pdev->type, pdev->decompressor);
+#endif
+
+	/* Set some defaults */
+	pdev->vsnapshot = 0;
+	if (pdev->type == 730 || pdev->type == 740)
+		pdev->vsize = PSZ_QSIF;
+	else
+		pdev->vsize = PSZ_QCIF;
+	pdev->vframes = 10;
+
+	/* Start iso pipe for video; first try user-supplied size/fps, if
+	   that fails try QCIF/10 or QSIF/10 (a reasonable default), 
+	   then give up 
+	 */
+	i = pwc_set_video_mode(pdev, pwc_image_sizes[default_size].x, pwc_image_sizes[default_size].y, default_fps, pdev->vcompression, 0);
+	if (i)	{
+		Trace(TRACE_OPEN, "First attempt at set_video_mode failed.\n");
+		if (pdev->type == 730 || pdev->type == 740) 
+			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 = usb_set_interface(pdev->udev, 0, pdev->valternate);
+	if (i) {
+		Trace(TRACE_OPEN, "Failed to set alternate interface = %d.\n", i);
+		up(&pdev->modlock);
+		return -EINVAL;
+	}
+	i = pwc_isoc_init(pdev);
+	if (i) {
+		Trace(TRACE_OPEN, "Failed to init ISOC stuff = %d.\n", i);
+		MOD_DEC_USE_COUNT;
+		up(&pdev->modlock);
+		return i;
+	}
+
+	pdev->vopen++;
+	/* lock decompressor; this has a small race condition, since we 
+	   could in theory unload pwcx.o between pwc_find_decompressor()
+	   above and this call. I doubt it's ever going to be a problem.
+	 */
+	if (pdev->decompressor != NULL)
+		pdev->decompressor->lock();
+	up(&pdev->modlock);
+	Trace(TRACE_OPEN, "video_open() returning 0.\n");
+	return 0;
+}
+
+/* Note that all cleanup is done in the reverse order as in _open */
+static void pwc_video_close(struct video_device *vdev)
+{
+	struct pwc_device *pdev;
+	int i;
+
+	Trace(TRACE_OPEN, "video_close called(0x%p).\n", vdev);
+
+	pdev = (struct pwc_device *)vdev->priv;
+	if (pdev->vopen == 0)
+		Info("video_close() called on closed device?\n");
+
+	/* Free isoc URBs */
+	pwc_isoc_cleanup(pdev);
+
+	/* 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);
+
+	if (pdev->unplugged) {
+		/* The device was unplugged or some other error occured */
+		/* We unregister the video_device */
+		Trace(TRACE_OPEN, "Delayed video device unregistered.\n");
+		video_unregister_device(pdev->vdev);
+	}
+	else {
+		/* Normal close: stop isochronuous and interrupt endpoint */
+		Trace(TRACE_OPEN, "Normal close(): setting interface to 0.\n");
+		usb_set_interface(pdev->udev, 0, 0);
+
+		/* Turn off LED by powering down camera */
+		if (power_save) {
+			i = pwc_camera_power(pdev, 0);
+			if (i < 0) 
+				Err("Failed to power down camera (%d)\n", i);
+		}
+	}
+
+	pdev->vopen = 0;
+	pwc_free_buffers(pdev);
+	if (pdev->decompressor != NULL) {
+		pdev->decompressor->exit(&pdev->decompress_data);
+		pdev->decompressor->unlock();
+	}
+	pdev->decompressor = NULL;
+
+	/* wake up _disconnect() routine */
+	if (pdev->unplugged)
+		wake_up(&pdev->remove_ok);
+}
+
+/*
+ *	FIXME: what about two parallel reads ????
+ */
+ 
+static long pwc_video_read(struct video_device *vdev, char *buf, unsigned long count, int noblock)
+{
+	struct pwc_device *pdev;
+
+	Trace(TRACE_READ, "video_read(0x%p, %p, %ld, %d) called.\n", vdev, buf, count, noblock);
+	if (vdev == NULL)
+		return -EFAULT;
+	pdev = vdev->priv;
+	if (pdev == NULL)
+		return -EFAULT;
+	if (pdev->unplugged) {
+		Debug("pwc_video_read: Device got unplugged (1).\n");
+		return -EPIPE; /* unplugged device! */
+	}
+
+	/* In case we're doing partial reads, we don't have to wait for a frame */
+	if (pdev->image_read_pos == 0) {
+		/* See if a frame is completed, process that */
+		if (noblock && pdev->full_frames == NULL)
+			return -EAGAIN;
+		while (pdev->full_frames == NULL) {
+			interruptible_sleep_on(&pdev->frameq);
+			if (pdev->unplugged) {
+				Debug("pwc_video_read: Device got unplugged (2).\n");
+				return -EPIPE;
+			}
+			if (signal_pending(current))
+				return -EINTR;
+		}
+ 
+		/* Decompress & convert now */
+		if (pwc_handle_frame(pdev))
+			return -EFAULT;
+	}
+
+	Trace(TRACE_READ, "Copying data to user space.\n");
+	/* copy bytes to user space; we allow for partial reads */
+	if (count + pdev->image_read_pos > pdev->view.size)
+		count = pdev->view.size - 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 >= pdev->view.size) { /* All data has been read */
+		pdev->image_read_pos = 0;
+		pwc_next_image(pdev);
+	}
+	return count;
+}
+
+
+static long pwc_video_write(struct video_device *vdev, const char *buf, unsigned long count, int noblock)
+{
+	return -EINVAL;   
+}
+
+static unsigned int pwc_video_poll(struct video_device *vdev, struct file *file, poll_table *wait)
+{
+	struct pwc_device *pdev;
+	
+	if (vdev == NULL)
+		return -EFAULT;
+	pdev = vdev->priv;
+	if (pdev == NULL)
+		return -EFAULT;
+	
+	poll_wait(file, &pdev->pollq, wait);
+	if (pdev->unplugged) {
+		Debug("pwc_video_poll: Device got unplugged.\n");
+		return POLLERR;
+	}		
+	if (pdev->full_frames != NULL) /* we have frames waiting */
+		return (POLLIN | POLLRDNORM);
+
+	return 0;
+}
+        
+static int pwc_video_ioctl(struct video_device *vdev, unsigned int cmd, void *arg)
+{
+	struct pwc_device *pdev;
+	
+	if (vdev == NULL)
+		return -EFAULT;
+	pdev = vdev->priv;
+	if (pdev == NULL)
+		return -EFAULT;
+
+	switch (cmd) {
+		/* Query cabapilities */
+		case VIDIOCGCAP: 
+		{
+			struct video_capability caps;
+
+			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;
+			if (copy_to_user(arg, &caps, sizeof(caps)))
+				return -EFAULT;
+			break;
+		}
+
+		/* Channel functions (simulate 1 channel) */
+		case VIDIOCGCHAN:
+		{
+			struct video_channel v;
+
+			if (copy_from_user(&v, arg, sizeof(v)))
+				return -EFAULT;
+			if (v.channel != 0)
+				return -EINVAL;
+
+			v.flags = 0;
+			v.tuners = 0;
+			v.type = VIDEO_TYPE_CAMERA;
+			strcpy(v.name, "Webcam");
+
+			if (copy_to_user(arg, &v, sizeof(v)))
+				return -EFAULT;
+
+			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;
+
+			if (copy_from_user(&v, arg, sizeof(v)))
+				return -EFAULT;
+
+			if (v.channel != 0)
+				return -EINVAL;
+
+			return 0;
+		}
+
+
+		/* Picture functions; contrast etc. */
+		case VIDIOCGPICT:
+		{
+			struct video_picture p;
+			int val;
+
+			p.colour = 0x8000;
+			p.hue = 0x8000;
+			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 */
+
+			if (copy_to_user(arg, &p, sizeof(p)))
+				return -EFAULT;
+			break;
+		}
+		
+		case VIDIOCSPICT:
+		{
+			struct video_picture p;
+
+			if (copy_from_user(&p, arg, sizeof(p)))
+				return -EFAULT;
+
+			/*
+			 *	FIXME:	Suppose we are mid read
+			 */
+			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) {
+				if (pwc_set_palette(pdev, p.palette) < 0)
+					return -EINVAL;
+			}
+			break;
+		}
+
+		/* Window/size parameters */		
+		case VIDIOCGWIN:
+		{
+			struct video_window vw;
+			
+			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);
+			
+			if (copy_to_user(arg, &vw, sizeof(vw)))
+				return -EFAULT;			
+			break;
+		}
+		
+		case VIDIOCSWIN:
+		{
+			struct video_window vw;
+			int fps, snapshot, ret;
+		
+			if (copy_from_user(&vw, arg, sizeof(vw)))
+				return -EFAULT;
+
+/*
+			size = pwc_decode_size(pdev, vw.width, vw.height);
+			if (size < 0)
+				return -EINVAL;
+*/				
+			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;
+			
+			vb.base = NULL;
+			vb.height = 0;
+			vb.width = 0;
+			vb.depth = 0;
+			vb.bytesperline = 0;
+			
+			if (copy_to_user((void *)arg, (void *)&vb, sizeof(vb)))
+				return -EFAULT;
+			break;
+		}
+
+		/* mmap() functions */
+		case VIDIOCGMBUF:
+		{
+			/* Tell the user program how much memory is needed for a mmap() */
+			struct video_mbuf vm;
+			int i;
+
+			memset(&vm, 0, sizeof(vm));
+			vm.size = default_mbufs * pdev->view_max.size * 4;
+			vm.frames = default_mbufs; /* double buffering should be enough */
+			for (i = 0; i < default_mbufs; i++)
+				vm.offsets[i] = i * pdev->view_max.size * 4;
+
+			if (copy_to_user((void *)arg, (void *)&vm, sizeof(vm)))
+				return -EFAULT;
+			break;
+		}
+
+		case VIDIOCMCAPTURE:
+		{
+			/* Start capture into a given image buffer (called 'frame' in video_mmap structure) */
+			struct video_mmap vm;
+
+			if (copy_from_user((void *)&vm, (void *)arg, sizeof(vm)))
+				return -EFAULT;
+			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 && vm.format != pdev->vpalette)
+				if (pwc_set_palette(pdev, vm.format) < 0)
+					return -EINVAL;
+			 
+			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");
+/*
+				size = pwc_decode_size(pdev, vm.width, vm.height);
+				if (size < 0)
+					return -EINVAL;
+*/					
+				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, ret;
+
+			if (copy_from_user((void *)&mbuf, arg, sizeof(int)))
+				return -EFAULT;
+
+			Trace(TRACE_READ, "VIDIOCSYNC called (%d).\n", mbuf);
+
+			if (mbuf < 0 || mbuf >= default_mbufs)
+				return -EINVAL;
+			/* check if this buffer was requested anyway */
+			if (pdev->image_used[mbuf] == 0)
+				return -EINVAL;
+
+			/* We (re)use the frame-waitqueue here. That may
+			   conflict with read(), but any programmer that uses
+			   read() and mmap() simultaneously should be given 
+			   a job at Micro$oft. As janitor.
+			   
+			   FIXME: needs auditing for safety.
+			 */
+			while (pdev->full_frames == NULL) {
+				interruptible_sleep_on(&pdev->frameq);
+				if (pdev->unplugged) {
+					Debug("VIDIOCSYNC: Device got unplugged.\n");
+					return -EPIPE;
+				}
+				if (signal_pending(current))
+					return -EINTR;
+			}
+			/* 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;
+		}
+
+		default:
+			return pwc_ioctl(pdev, cmd, arg);
+	} /* ..switch */
+	return 0;
+}	
+
+static int pwc_video_mmap(struct video_device *vdev, const char *adr, unsigned long size)
+{
+	struct pwc_device *pdev;
+	unsigned long start = (unsigned long)adr;
+	unsigned long page, pos;
+	
+	Trace(TRACE_READ, "mmap(0x%p, 0x%p, %lu) called.\n", vdev, adr, size);
+	pdev = vdev->priv;
+
+	/* FIXME - audit mmap during a read */		
+
+	pos = (unsigned long)pdev->image_data;
+	while (size > 0) {
+		page = kvirt_to_pa(pos);
+		if (remap_page_range(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 void *usb_pwc_probe(struct usb_device *udev, unsigned int ifnum, const struct usb_device_id *id)
+{
+	struct pwc_device *pdev = NULL;
+	struct video_device *vdev;
+	struct usb_config_descriptor *config;
+	struct usb_interface *iface;
+	int vendor_id, product_id, type_id;
+	int i;
+
+	free_mem_leak();
+	
+	/* Check if we can handle this device */
+	Trace(TRACE_PROBE, "probe() called [%04X %04X], if %d\n", udev->descriptor.idVendor, udev->descriptor.idProduct, ifnum);
+
+	/* 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 (ifnum > 0) 
+		return NULL;
+
+	vendor_id = udev->descriptor.idVendor;
+	product_id = udev->descriptor.idProduct;
+	
+
+	if (vendor_id == 0x0471) {
+		switch (product_id) {
+		case 0x0302:
+			Info("Philips PCA645VC USB webcam detected.\n");
+			type_id = 645;
+			break;
+		case 0x0303:
+			Info("Philips PCA646VC USB webcam detected.\n");
+			type_id = 646;
+			break;
+		case 0x0304:
+			Info("Askey VC010 type 2 USB webcam detected.\n");
+			type_id = 646;
+			break;
+		case 0x0307:
+			Info("Philips PCVC675K (Vesta) USB webcam detected.\n");
+			type_id = 675;
+			break;
+		case 0x0308:
+			Info("Philips PCVC680K (Vesta Pro) USB webcam detected.\n");
+			type_id = 680;
+			break;
+		case 0x030C:
+			Info("Philips PCVC690K (Vesta Scanner) USB webcam detected.\n");
+			type_id = 690;
+			break;
+		case 0x0310:
+			Info("Philips PCVC730K (ToUCam Fun) USB webcam detected.\n");
+			type_id = 730;
+			break;
+		case 0x0311:
+			Info("Philips PCVC740K (ToUCam Pro) USB webcam detected.\n");
+			type_id = 740;
+			break;
+		default:
+			return NULL;
+			break;
+		}
+	}
+	else if (vendor_id == 0x069A) {
+		switch(product_id) {
+		case 0x0001:
+			Info("Askey VC010 type 1 USB webcam detected.\n");
+			type_id = 645;
+			break;
+		default:
+			return NULL;
+			break;
+		}
+	}
+	else return NULL; /* Not Philips or Askey, for sure. */
+
+	if (udev->descriptor.bNumConfigurations > 1)
+		Info("Warning: more than 1 configuration available.\n");
+
+	config = udev->actconfig;
+	iface = &config->interface[0];
+
+	/* Allocate structure, initialize pointers, mutexes, etc. and link it to the usb_device */
+	pdev = kmalloc(sizeof(struct pwc_device), GFP_KERNEL);
+	if (pdev == NULL) {
+		Err("Oops, could not allocate memory for pwc_device.\n");
+		return NULL;
+	}
+	memset(pdev, 0, sizeof(struct pwc_device));
+	pdev->type = type_id;
+	pwc_construct(pdev);
+
+	init_MUTEX(&pdev->modlock);
+	pdev->ptrlock = SPIN_LOCK_UNLOCKED;
+
+	pdev->udev = udev;
+	init_waitqueue_head(&pdev->frameq);
+	init_waitqueue_head(&pdev->pollq);
+	init_waitqueue_head(&pdev->remove_ok);
+	pdev->vcompression = pwc_preferred_compression;
+
+	/* Now hook it up to the video subsystem */
+	vdev = kmalloc(sizeof(struct video_device), GFP_KERNEL);
+	if (vdev == NULL) {
+		Err("Oops, could not allocate memory for video_device.\n");
+		return NULL;
+	}
+	memcpy(vdev, &pwc_template, sizeof(pwc_template));
+	sprintf(vdev->name, "Philips %d webcam", pdev->type);
+	SET_MODULE_OWNER(vdev);
+	pdev->vdev = vdev;
+	vdev->priv = pdev;
+
+	pdev->release = udev->descriptor.bcdDevice;
+	Trace(TRACE_PROBE, "Release: %04x\n", pdev->release);
+
+	i = video_register_device(vdev, VFL_TYPE_GRABBER, video_nr);
+	if (i < 0) {
+		Err("Failed to register as video device (%d).\n", i);
+		return NULL;
+	}
+	else {
+		Trace(TRACE_PROBE, "Registered video struct at 0x%p.\n", vdev);
+		Info("Registered as /dev/video%d.\n", vdev->minor & 0x3F);
+	}
+
+#if 0
+	/* Shut down camera now (some people like the LED off) */
+	if (power_save) {
+		Trace(TRACE_PROBE, "Powering down camera");
+		i = pwc_camera_power(pdev, 0);
+		if (i < 0)
+			Info("Failed to power-down the camera (%d)\n", i);
+	}
+#endif
+
+	Trace(TRACE_PROBE, "probe() function returning struct at 0x%p.\n", pdev);
+	return pdev;
+}
+
+/* The user janked out the cable... */
+static void usb_pwc_disconnect(struct usb_device *udev, void *ptr)
+{
+	struct pwc_device *pdev;
+
+	free_mem_leak();
+
+	pdev = (struct pwc_device *)ptr;
+	if (pdev == NULL) {
+		Err("pwc_disconnect() Called without private pointer.\n");
+		return;
+	}
+	if (pdev->udev == NULL) {
+		Err("pwc_disconnect() already called for %p\n", pdev);
+		return;
+	}
+	if (pdev->udev != udev) {
+		Err("pwc_disconnect() Woops: pointer mismatch udev/pdev.\n");
+		return;
+	}
+#ifdef PWC_MAGIC	
+	if (pdev->magic != PWC_MAGIC) {
+		Err("pwc_disconnect() Magic number failed. Consult your scrolls and try again.\n");
+		return;
+	}
+#endif	
+	
+	pdev->unplugged = 1;
+	if (pdev->vdev != NULL) {
+		if (pdev->vopen) {
+			Info("Disconnected while device/video is open!\n");
+			
+			/* Wake up any processes that might be waiting for
+			   a frame, let them return an error condition
+			 */
+			wake_up(&pdev->frameq);
+			wake_up(&pdev->pollq);
+			
+			/* Wait until we get a 'go' from _close(). This
+			   had a gigantic race condition, since we kfree()
+			   stuff here, but we have to wait until close() 
+			   is finished. */
+			   
+			Trace(TRACE_PROBE, "Sleeping on remove_ok.\n");
+			sleep_on(&pdev->remove_ok);
+			Trace(TRACE_PROBE, "Done sleeping.\n");
+			set_mem_leak(pdev->vdev);
+			pdev->vdev = NULL;
+		}
+		else {
+			/* Normal disconnect; remove from available devices */
+			Trace(TRACE_PROBE, "Unregistering video device normally.\n");
+			video_unregister_device(pdev->vdev); 
+			kfree(pdev->vdev);
+			pdev->vdev = NULL;
+		}
+	}
+	pdev->udev = NULL;
+	kfree(pdev);
+}
+
+
+
+/* 
+ * Initialization code & module stuff 
+ */
+
+static char *size = NULL;
+static int fps = 0;
+static char *palette = NULL;
+static int fbufs = 0;
+static int mbufs = 0;
+static int trace = -1;
+static int compression = -1;
+
+MODULE_PARM(video_nr, "i");
+MODULE_PARM(size, "s");
+MODULE_PARM_DESC(size, "Initial image size. One of sqcif, qsif, qcif, sif, cif, vga");
+MODULE_PARM(fps, "i");
+MODULE_PARM_DESC(fps, "Initial frames per second. Varies with model, useful range 5-30");
+MODULE_PARM(palette, "s");
+MODULE_PARM_DESC(palette, "Initial colour format of images. One of rgb24, bgr24, rgb32, bgr32, yuyv, yuv420, yuv420p");
+MODULE_PARM(fbufs, "i");
+MODULE_PARM_DESC(fbufs, "Number of internal frame buffers to reserve");
+MODULE_PARM(mbufs, "i");
+MODULE_PARM_DESC(mbufs, "Number of external (mmap()ed) image buffers");
+MODULE_PARM(trace, "i");
+MODULE_PARM_DESC(trace, "For debugging purposes");
+MODULE_PARM(power_save, "i");
+MODULE_PARM_DESC(power_save, "Turn power save feature in camera on or off");
+MODULE_PARM(compression, "i");
+MODULE_PARM_DESC(compression, "Preferred compression quality. Range 0 (uncompressed) to 3 (high compression)");
+
+MODULE_DESCRIPTION("Philips USB webcam driver");
+MODULE_AUTHOR("Nemosoft Unv. <nemosoft@smcc.demon.nl>");
+
+static int __init usb_pwc_init(void)
+{
+	int s;
+	char *sizenames[PSZ_MAX] = { "sqcif", "qsif", "qcif", "sif", "cif", "vga" };
+
+	Info("Philips PCA645/646 + PCVC675/680/690 + PCVC730/740 webcam module version " PWC_VERSION " loaded.\n");
+	Info("Also supports Askey VC010 cam.\n");
+
+	if (fps) {
+		if (fps < 5 || fps > 30) {
+			Err("Framerate out of bounds (5-30).\n");
+			return -EINVAL;
+		}
+		default_fps = fps;
+		Info("Default framerate set to %d.\n", default_fps);
+	}
+	
+	if (size) {
+		/* string; try matching with array */
+		for (s = 0; s < PSZ_MAX; s++) {
+			if (!strcmp(sizenames[s], size)) { /* Found! */
+				default_size = s;
+				break;
+			}
+		}
+		if (s == 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 (palette) {
+		/* Determine default palette */
+		if (!strcmp(palette, "yuv420"))
+			default_palette = VIDEO_PALETTE_YUV420;
+		else {
+			Err("Palette not recognized: try palette=yuv420.\n");
+			return -EINVAL;
+		}
+		Info("Default palette set to %d.\n", default_palette);
+	}
+	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");
+
+	init_MUTEX(&mem_lock);
+ 	Trace(TRACE_PROBE, "Registering driver at address 0x%p.\n", &pwc_driver);
+	return usb_register(&pwc_driver);
+}
+
+static void __exit usb_pwc_exit(void)
+{
+	free_mem_leak();
+	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);
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)