diff -ur linux-2.4.9-dist/drivers/usb/pwc-ctrl.c linux-2.4.9/drivers/usb/pwc-ctrl.c
--- linux-2.4.9-dist/drivers/usb/pwc-ctrl.c	Wed Sep  5 02:36:37 2001
+++ linux-2.4.9/drivers/usb/pwc-ctrl.c	Mon Aug 20 00:20:33 2001
@@ -18,6 +18,12 @@
    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__
@@ -603,12 +609,12 @@
 	char buf;
 	int ret;
 
-	if (pdev->type < 730)
+	if (pdev->type < 675)
 		return -1;
 	ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0),
 		GET_CHROM_CTL,
 		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
-		SATURATION_MODE_FORMATTER1,
+		pdev->type < 730 ? SATURATION_MODE_FORMATTER2 : SATURATION_MODE_FORMATTER1,
 		pdev->vcinterface,
 		&buf, 1, HZ / 2);
 	if (ret < 0)
@@ -620,7 +626,7 @@
 {
 	char buf;
 
-	if (pdev->type < 730)
+	if (pdev->type < 675)
 		return -EINVAL;
 	if (value < 0)
 		value = 0;
@@ -631,7 +637,7 @@
 	return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0),
 		SET_CHROM_CTL,
 		USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
-		SATURATION_MODE_FORMATTER1,
+		pdev->type < 730 ? SATURATION_MODE_FORMATTER2 : SATURATION_MODE_FORMATTER1,
 		pdev->vcinterface,
 		&buf, 1, HZ / 2);
 }
@@ -825,6 +831,218 @@
 		NULL, 0, HZ / 2);
 }
 
+ /* ************************************************* */
+ /* 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 = usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0),
+		SET_CHROM_CTL,
+		USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		WB_MODE_FORMATTER,
+		pdev->vcinterface,
+		&buf, 1, HZ / 2);
+	
+	if (ret < 0)
+		return ret;
+	return 0;
+}
+
+static inline int pwc_get_awb(struct pwc_device *pdev)
+{
+	unsigned char buf;
+	int ret;
+	
+	ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0),
+		GET_CHROM_CTL,
+		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		WB_MODE_FORMATTER,
+		pdev->vcinterface,
+		&buf, 1, HZ / 2);
+
+	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 are considered */
+	buf = value >> 8;
+
+	return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0),
+		SET_CHROM_CTL,
+		USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		PRESET_MANUAL_RED_GAIN_FORMATTER,
+		pdev->vcinterface,
+		&buf, 1, HZ / 2);
+}
+
+static inline int pwc_get_red_gain(struct pwc_device *pdev)
+{
+	unsigned char buf;
+	int ret;
+	
+	ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0),
+ 	        GET_STATUS_CTL, 
+		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+	        PRESET_MANUAL_RED_GAIN_FORMATTER,
+		pdev->vcinterface,
+		&buf, 1, HZ / 2);
+
+	if (ret < 0)
+	    return ret;
+	
+	return (buf << 8);
+}
+
+
+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;
+
+	/* linear mapping of 0..0xffff to -0x80..0x7f */
+	buf = (value >> 8);
+
+	return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0),
+		SET_CHROM_CTL,
+		USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		PRESET_MANUAL_BLUE_GAIN_FORMATTER,
+		pdev->vcinterface,
+		&buf, 1, HZ / 2);
+}
+
+static inline int pwc_get_blue_gain(struct pwc_device *pdev)
+{
+	unsigned char buf;
+	int ret;
+	
+	ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0),
+   	        GET_STATUS_CTL,
+		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		PRESET_MANUAL_BLUE_GAIN_FORMATTER,
+		pdev->vcinterface,
+		&buf, 1, HZ / 2);
+
+	if (ret < 0)
+	    return ret;
+	
+	return (buf << 8);
+}
+
+/* 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)
+{
+	unsigned char buf;
+	int ret;
+	
+	ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0),
+ 	        GET_STATUS_CTL, 
+		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+	        READ_RED_GAIN_FORMATTER,
+		pdev->vcinterface,
+		&buf, 1, HZ / 2);
+
+	if (ret < 0)
+	    return ret;
+	
+	return (buf << 8);
+}
+static inline int pwc_read_blue_gain(struct pwc_device *pdev)
+{
+	unsigned char buf;
+	int ret;
+	
+	ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0),
+   	        GET_STATUS_CTL,
+		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		READ_BLUE_GAIN_FORMATTER,
+		pdev->vcinterface,
+		&buf, 1, HZ / 2);
+
+	if (ret < 0)
+	    return ret;
+	
+	return (buf << 8);
+}
+
+/* still unused (it doesn't work yet...) */
+static inline int pwc_set_led(struct pwc_device *pdev, int value)
+{
+	unsigned char buf;
+
+	if (value < 0)
+		value = 0;
+	if (value > 0xffff)
+		value = 0xffff;
+
+	buf = (value >> 8);
+
+	return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0),
+		SET_STATUS_CTL,
+		USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		LED_FORMATTER,
+		pdev->vcinterface,
+		&buf, 1, HZ / 2);
+}
+
+/* still unused (it doesn't work yet...) */
+static inline int pwc_get_led(struct pwc_device *pdev)
+{
+	unsigned char buf;
+	int ret;
+	
+	ret = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0),
+   	        GET_STATUS_CTL,
+		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		LED_FORMATTER,
+		pdev->vcinterface,
+		&buf, 1, HZ / 2);
+
+	if (ret < 0)
+	    return ret;
+	
+	return (buf << 8);
+}
+
+ /* End of Add-Ons                                    */
+ /* ************************************************* */
+
 int pwc_ioctl(struct pwc_device *pdev, unsigned int cmd, void *arg)
 {
 	switch(cmd) {
@@ -910,6 +1128,74 @@
 		break;
 	}
 	
+
+ /* ************************************************* */
+ /* Begin of Add-Ons for color compensation           */
+
+        case VIDIOCPWCSAWB:
+	{
+		struct pwc_whitebalance wb;
+		int ret;
+		
+		if (copy_from_user(&wb, arg, sizeof(wb)))
+			return -EFAULT;
+	
+		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;
+		
+		memset(&wb, 0, sizeof(wb));
+		wb.mode = pwc_get_awb(pdev);
+		if (wb.mode < 0)
+			return -EINVAL;
+		wb.manual_red = pwc_get_red_gain(pdev);
+		wb.manual_blue = pwc_get_blue_gain(pdev);
+		if (wb.mode == PWC_WB_AUTO) {
+			wb.read_red = pwc_read_red_gain(pdev);
+			wb.read_blue = pwc_read_blue_gain(pdev);
+		}
+		break;
+	}
+
+        case VIDIOCPWCSLED:
+	{
+	    int led, ret;
+	    if (copy_from_user(&led,arg,sizeof(led)))
+		return -EFAULT;
+	    else {
+		/* ret = pwc_set_led(pdev, led); */
+		ret = 0;
+		if (ret<0)
+		    return ret;
+	    }
+	    break;
+	}
+
+
+
+	case VIDIOCPWCGLED:
+	{
+		int led;
+		
+		led = pwc_get_led(pdev); 
+		if (led < 0)
+			return -EINVAL;
+		if (copy_to_user(arg, &led, sizeof(led)))
+			return -EFAULT;
+		break;
+	}
+
+ /* End of Add-Ons                                    */
+ /* ************************************************* */
+
 	default:
 		return -ENOIOCTLCMD;
 		break;
@@ -918,3 +1204,7 @@
 }
 
 #endif
+
+
+
+
diff -ur linux-2.4.9-dist/drivers/usb/pwc-if.c linux-2.4.9/drivers/usb/pwc-if.c
--- linux-2.4.9-dist/drivers/usb/pwc-if.c	Wed Sep  5 02:36:37 2001
+++ linux-2.4.9/drivers/usb/pwc-if.c	Fri Sep 14 01:43:45 2001
@@ -35,7 +35,12 @@
    udev: struct usb_device *
    vdev: struct video_device *
    pdev: struct pwc_devive *
-*/   
+*/
+
+/* Contributors:
+   - Alvarado: adding whitebalance code
+   - Alistar Moire: QuickCam 3000 Pro testing
+*/
 
 #include <linux/errno.h>
 #include <linux/init.h>
@@ -68,6 +73,7 @@
 	{ USB_DEVICE(0x0471, 0x0311) },
 	{ USB_DEVICE(0x0471, 0x0312) },
 	{ USB_DEVICE(0x069A, 0x0001) },
+	{ USB_DEVICE(0x046D, 0x0b80) },
 	{ }
 };
 MODULE_DEVICE_TABLE(usb, pwc_device_table);
@@ -85,10 +91,10 @@
 
 static int default_size = PSZ_QCIF;
 static int default_fps = 10;
-static int default_palette = VIDEO_PALETTE_YUV420P; /* This is normal for webcams */
+static int default_palette = VIDEO_PALETTE_YUV420P; /* This format is understood by most tools */
 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;
+       int pwc_trace = TRACE_MODULE | TRACE_FLOW | TRACE_PWCX;
 static int power_save = 0;
 int pwc_preferred_compression = 2; /* 0..3 = uncompressed..high */
 
@@ -167,109 +173,90 @@
  */
 static inline unsigned long uvirt_to_kva(pgd_t *pgd, unsigned long adr)
 {
-	unsigned long ret = 0UL;
+        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));
+                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)
+static inline unsigned long kvirt_to_pa(unsigned long adr) 
 {
-	unsigned long va, kva, ret;
+        unsigned long va, kva, ret;
 
-	va = VMALLOC_VMADDR(adr);
-	kva = uvirt_to_kva(pgd_offset_k(va), va);
+        va = VMALLOC_VMADDR(adr);
+        kva = uvirt_to_kva(pgd_offset_k(va), va);
 	ret = __pa(kva);
-	return ret;
+        return ret;
 }
 
-static void *rvmalloc(unsigned long size)
+static void * rvmalloc(signed long size)
 {
-	void *mem;
+	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;
+        /* Round it off to PAGE_SIZE */
+        size += (PAGE_SIZE - 1);
+        size &= ~(PAGE_SIZE - 1);	
+        
+        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) 
+                {
+	                page = kvirt_to_pa(adr);
+			mem_map_reserve(virt_to_page(__va(page)));
+			adr+=PAGE_SIZE;
+			size-=PAGE_SIZE;
+		}
 	}
 	return mem;
 }
 
-static void rvfree(void *mem, unsigned long size)
+static void rvfree(void * mem, signed 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;
+        unsigned long adr, page;
+        
+        /* Round it off to PAGE_SIZE */
+        size += (PAGE_SIZE - 1);
+        size &= ~(PAGE_SIZE - 1);	
+	if (mem) 
+	{
+	        adr=(unsigned long) mem;
+		while (size > 0) 
+                {
+	                page = kvirt_to_pa(adr);
+			mem_map_unreserve(virt_to_page(__va(page)));
+			adr+=PAGE_SIZE;
+			size-=PAGE_SIZE;
+		}
+		vfree(mem);
 	}
-	vfree(mem);
 }
 
 
 
+
 static int pwc_allocate_buffers(struct pwc_device *pdev)
 {
 	int i;
@@ -294,6 +281,7 @@
 				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);
 		}
@@ -306,6 +294,7 @@
 			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));
 	}
@@ -317,8 +306,9 @@
 				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, 0, FRAME_SIZE);
+			memset(kbuf, 128, FRAME_SIZE);
 		}
 	}
 	
@@ -330,23 +320,24 @@
 			Err("Failed to allocate decompress table.\n");
 			return -ENOMEM;
 		}
+		Trace(TRACE_MEMORY, "Allocated decompress table %p.\n", kbuf);
 	}
 	pdev->decompress_data = kbuf;
 	
 	/* Allocate image buffer; double buffer for mmap() */
-	kbuf = rvmalloc(default_mbufs * pdev->view_max.size * 4);
+	kbuf = rvmalloc(default_mbufs * pdev->len_per_image);
 	if (kbuf == NULL) {
 		Err("Failed to allocate image buffer(s).\n");
 		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->view_max.size * 4);
+		pdev->image_ptr[i] = kbuf + i * pdev->len_per_image;
 	for (; i < MAX_IMAGES; i++)
 		pdev->image_ptr[i] = NULL;
 
 	Trace(TRACE_MEMORY, "Leaving pwc_allocate_buffers().\n");
-
 	return 0;
 }
 
@@ -366,18 +357,18 @@
 #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) {
+			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 */
-	Trace(TRACE_MEMORY, "Freeing frame buffers.\n");
 	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;
 			}
@@ -387,17 +378,18 @@
 	}
 
 	/* Intermediate decompression buffer & tables */
-	Trace(TRACE_MEMORY, "Freeing decompression buffer\n");
 	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 */
-	Trace(TRACE_MEMORY, "Freeing image buffers\n");
-	if (pdev->image_data != NULL)
-		rvfree(pdev->image_data, default_mbufs * pdev->view_max.size * 4);
+	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");
 }
@@ -610,6 +602,7 @@
 		pwc_set_image_buffer_size(pdev);
 		return 0;
 	}
+	Trace(TRACE_READ, "Palette %d not supported.\n", pal);
 	return -1;
 }
 
@@ -642,7 +635,17 @@
 		return;
 	}
 	if (urb->status != -EINPROGRESS && urb->status != 0) {
-		Trace(TRACE_FLOW, "pwc_isoc_handler() called with status %d.\n", urb->status);
+		char *errmsg;
+		
+		errmsg = "Unknown";
+		switch(urb->status) {
+			case -ENOSR:		errmsg = "Buffer error (overrun)"; break;
+			case -EPIPE:		errmsg = "Babble/stalled (bad cable?)"; break;
+			case -EPROTO:		errmsg = "Bit-stuff error (bad cable?)"; break;
+			case -EILSEQ:		errmsg = "CRC/Timeout"; 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);
 		return;
 	}
 
@@ -739,9 +742,9 @@
 								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);
+										Trace(TRACE_FLOW, "Dumping frame %d.\n", pdev->vframe_count);
 									if (pdev->vframes_dumped == 20)
-										Info("Dumping frame %d (last message).\n", pdev->vframe_count);
+										Trace(TRACE_FLOW, "Dumping frame %d (last message).\n", pdev->vframe_count);
 								}
 							}
 							fbuf = pdev->fill_frame;
@@ -1104,7 +1107,7 @@
 	if (pdev == NULL)
 		return -EFAULT;
 	if (pdev->unplugged) {
-		Debug("pwc_video_read: Device got unplugged (1).\n");
+		Info("pwc_video_read: Device got unplugged (1).\n");
 		return -EPIPE; /* unplugged device! */
 	}
 
@@ -1166,7 +1169,7 @@
 	
 	poll_wait(file, &pdev->frameq, wait);
 	if (pdev->unplugged) {
-		Debug("pwc_video_poll: Device got unplugged.\n");
+		Info("pwc_video_poll: Device got unplugged.\n");
 		return POLLERR;
 	}		
 	if (pdev->full_frames != NULL) /* we have frames waiting */
@@ -1370,10 +1373,10 @@
 			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 */
+			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->view_max.size * 4;
+				vm.offsets[i] = i * pdev->len_per_image;
 
 			if (copy_to_user((void *)arg, (void *)&vm, sizeof(vm)))
 				return -EFAULT;
@@ -1546,11 +1549,10 @@
 	unsigned long start = (unsigned long)adr;
 	unsigned long page, pos;
 	
-	Trace(TRACE_READ, "mmap(0x%p, 0x%p, %lu) called.\n", vdev, adr, size);
+	Trace(TRACE_MEMORY, "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);
@@ -1651,7 +1653,18 @@
 			break;
 		}
 	}
-	else return NULL; /* Not Philips or Askey, for sure. */
+        else if (vendor_id == 0x046d) {
+        	switch(product_id) {
+        	case 0x08b0:
+        		Info("Logitech QuickCam 3000 Pro detected.\n");
+        		type_id = 730;
+        		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");
@@ -1718,6 +1731,7 @@
 {
 	struct pwc_device *pdev;
 
+	lock_kernel();
 	free_mem_leak();
 
 	pdev = (struct pwc_device *)ptr;
@@ -1770,6 +1784,7 @@
 		}
 	}
 	pdev->udev = NULL;
+	unlock_kernel();
 	kfree(pdev);
 }
 
@@ -1847,8 +1862,6 @@
 			default_palette = VIDEO_PALETTE_YUV420P;
 		else {
 			Err("Palette not recognized: try palette=yuv420 or yuv420p.\n");
-			Info("Download the driver from http://www.smcc.demon.nl/webcam/ for in kernel\n");
-			Info("format conversion support.\n");
 			return -EINVAL;
 		}
 		Info("Default palette set to %d.\n", default_palette);
diff -ur linux-2.4.9-dist/drivers/usb/pwc-ioctl.h linux-2.4.9/drivers/usb/pwc-ioctl.h
--- linux-2.4.9-dist/drivers/usb/pwc-ioctl.h	Wed Sep  5 02:23:19 2001
+++ linux-2.4.9/drivers/usb/pwc-ioctl.h	Mon Aug 13 23:32:09 2001
@@ -1,3 +1,6 @@
+#ifndef PWC_IOCTL_H
+#define PWC_IOCTL_H
+
 /* (C) 2001 Nemosoft Unv.    webcam@smcc.demon.nl
    
    This program is free software; you can redistribute it and/or modify
@@ -15,8 +18,11 @@
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
 
-#ifndef PWC_IOCTL_H
-#define PWC_IOCTL_H
+/*
+   Changes
+   2001/08/03  Alvarado   Added ioctl constants to access methods for 
+                          changing white balance and red/blue gains
+ */
 
 /* These are private ioctl() commands, specific for the Philips webcams.
    They contain functions not found in other webcams, and settings not
@@ -46,6 +52,30 @@
 #define PWC_FPS_SNAPSHOT	0x00400000
 
 
+/* 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 */
+};
+
+
  /* Restore user settings */
 #define VIDIOCPWCRUSER		_IO('v', 192)
  /* Save user settings */
@@ -71,5 +101,15 @@
 #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)
+
+ /* Turn LED on/off ; int range 0..65535 */
+#define VIDIOCPWCSLED           _IOW('v', 205, int)
+
+ /* Get state of LED; int range 0..65535 */
+#define VIDIOCPWCGLED           _IOR('v', 205, int)
 
 #endif
diff -ur linux-2.4.9-dist/drivers/usb/pwc-misc.c linux-2.4.9/drivers/usb/pwc-misc.c
--- linux-2.4.9-dist/drivers/usb/pwc-misc.c	Wed Sep  5 02:36:37 2001
+++ linux-2.4.9/drivers/usb/pwc-misc.c	Fri Sep 14 01:29:28 2001
@@ -97,6 +97,8 @@
 	}
 	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 */
+	pdev->len_per_image = (pdev->view_max.size * 3) / 2;
 }
 
 
diff -ur linux-2.4.9-dist/drivers/usb/pwc.h linux-2.4.9/drivers/usb/pwc.h
--- linux-2.4.9-dist/drivers/usb/pwc.h	Wed Sep  5 02:36:37 2001
+++ linux-2.4.9/drivers/usb/pwc.h	Fri Sep 14 01:30:32 2001
@@ -24,6 +24,7 @@
 #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>
@@ -33,9 +34,10 @@
 #define PWC_MAGIC 0x89DC10ABUL
 #undef PWC_MAGIC
 
-/* Debugging info on/off */
+/* 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
@@ -58,8 +60,8 @@
 
 /* Version block */
 #define PWC_MAJOR	8
-#define PWC_MINOR	1
-#define PWC_VERSION 	"8.1"
+#define PWC_MINOR	2
+#define PWC_VERSION 	"8.2"
 #define PWC_NAME 	"pwc"
 
 /* Turn certain features on/off */
@@ -184,6 +186,7 @@
    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 */
 
