diff -Nru a/CREDITS b/CREDITS
--- a/CREDITS	Fri Dec 12 15:06:58 2003
+++ b/CREDITS	Fri Dec 12 15:06:58 2003
@@ -2669,6 +2669,13 @@
 S: 70110 Kuopio
 S: Finland
 
+N: Luca Risolia
+E: luca_ing@libero.it
+D: V4L driver for W996[87]CF JPEG USB Dual Mode Camera Chip
+S: Via Libertà 41/a
+S: Osio Sotto, 24046, Bergamo
+S: Italy
+
 N: William E. Roadcap
 E: roadcapw@cfw.com
 W: http://www.cfw.com/~roadcapw
@@ -3569,4 +3576,4 @@
 # alphabetically. Leonard used to be very proud of being the 
 # last entry, and he'll get positively pissed if he can't even
 # be second-to-last.  (and this file really _is_ supposed to be
-# in alphabetic order) 
+# in alphabetic order)
diff -Nru a/Documentation/usb/w9968cf.txt b/Documentation/usb/w9968cf.txt
--- /dev/null	Wed Dec 31 16:00:00 1969
+++ b/Documentation/usb/w9968cf.txt	Fri Dec 12 15:06:58 2003
@@ -0,0 +1,463 @@
+
+         W996[87]CF JPEG USB Dual Mode Camera Chip driver for Linux 2.6
+         ==============================================================
+
+                               - Documentation -
+
+
+Index
+=====
+1. Copyright
+2. License
+3. Overview
+4. Supported devices
+5. Kernel configuration and third-part module compilation
+6. Module loading
+7. Module paramaters
+8. Credits
+
+
+1. Copyright
+============
+Copyright (C) 2002 2003 by Luca Risolia <luca_ing@libero.it>
+
+
+2. License
+==========
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+
+3. Overview
+===========
+This driver supports the video streaming capabilities of the devices mounting
+Winbond W9967CF and Winbond W9968CF JPEG USB Dual Mode Camera Chips, when they
+are being commanded by USB.
+
+The driver relies on the Video4Linux, USB and I2C core modules of the Linux
+kernel, version 2.6.0 or greater, and is not compatible in any way with
+previous versions. It has been designed to run properly on SMP systems
+as well. At the moment, an additional module, "ovcamchip", is mandatory; it
+provides support for some OmniVision CMOS sensors connected to the W996[87]CF
+chips.
+
+The driver is split into two modules: the basic one, "w9968cf", is needed for
+the supported devices to work; the second one, "w9968cf-vpp", is an optional
+module, which provides some useful video post-processing functions like video
+decoding, up-scaling and colour conversions. These routines can't be included
+into official kernels for performance purposes. Once the driver is installed,
+every time an application tries to open a recognized device, "w9968cf" checks
+the presence of the "w9968cf-vpp" module and loads it automatically by default.
+
+Up to 32 cameras can be handled at the same time. They can be connected and
+disconnected from the host many times without turning off the computer, if
+your system supports the hotplug facility.
+
+To change the default settings for each camera, many paramaters can be passed
+through command line when the module is loaded into memory.
+
+The latest and full featured version of the W996[87]CF driver can be found at:
+http://go.lamarinapunto.com/
+
+The "ovcamchip" module is part of the OV511 driver, version 2.25, which can be
+downloaded from internet:
+http://alpha.dyndns.org/ov511/
+To know how to patch, compile and load it, read the paragraphs below.
+
+
+4. Supported devices
+====================
+At the moment, known W996[87]CF based devices are:
+- Aroma Digi Pen ADG-5000 Refurbished
+- AVerTV USB
+- Creative Labs Video Blaster WebCam Go
+- Creative Labs Video Blaster WebCam Go Plus
+- Die Lebon LDC-D35A Digital Kamera
+- Ezonics EZ-802 EZMega Cam
+- OPCOM Digi Pen VGA Dual Mode Pen Camera
+
+If you know any other W996[87]CF based cameras, please contact me.
+
+The list above does NOT imply that all those devices work with this driver: up
+until now only webcams that have a CMOS sensor supported by the "ovcamchip"
+module work. 
+For a list of supported CMOS sensors, please visit the module author homepage:
+http://alpha.dyndns.org/ov511/
+
+Possible external microcontrollers of those webcams are not supported: this
+means that still images can't be downloaded from the device memory.
+
+Furthermore, it's worth to note that I was only able to run tests on my
+"Creative Labs Video Blaster WebCam Go". Donations of other models, for
+additional testing and full support, would be much appreciated.
+
+
+5. Kernel configuration and third-part module compilation
+=========================================================
+As noted above, kernel 2.6.0 is the minimum for this driver; for it to work
+properly, the driver needs kernel support for Video4Linux, USB and I2C, and a
+third-part module for the CMOS sensor.
+
+The following options of the kernel configuration file must be enabled and
+corresponding modules must be compiled:
+
+	# Multimedia devices
+	#
+	CONFIG_VIDEO_DEV=m
+
+	# I2C support
+	#
+	CONFIG_I2C=m
+
+The I2C core module can be compiled statically in the kernel as well.
+
+	# USB support
+	#
+	CONFIG_USB=m
+
+In addition, depending on the hardware being used, just one of the modules
+below is necessary:
+
+	# USB Host Controller Drivers
+	#
+	CONFIG_USB_EHCI_HCD=m
+	CONFIG_USB_UHCI_HCD=m
+	CONFIG_USB_OHCI_HCD=m
+
+Also, make sure "Enforce bandwidth allocation" is NOT enabled.
+
+	# USB Multimedia devices
+	#
+	CONFIG_USB_W9968CF=m
+
+The last module we need is "ovcamchip.o". To obtain it, you have to download
+the OV511 driver, version 2.25 - don't use other versions - which is available
+at http://alpha.dyndns.org/ov511/ . Then you have to download the latest 
+version of the full featured W996[87]CF driver, which contains a patch for the
+"ovcamchip" module; it is available at http://go.lamarinapunto.com .
+Once you have obtained the packages, decompress, patch and compile the 
+"ovcamchip" module. In other words:
+
+	[user@localhost home]$ tar xvzf w9968cf-x.x.tar.gz
+	[user@localhost home]$ tar xvjf ov511-2.25.tar.bz2
+	[user@localhost home]$ cd ov511-2.25
+	[user@localhost ov511-2.25]$ patch -p1 <                              \
+	                             /path/to/w9968cf-x.x/ov511-2.25.patch
+	[user@localhost ov511-2.25]$ make
+
+It's worth to note that the full featured version of the W996[87]CF driver
+can also be installed overwriting the one in the kernel; in this case, read the
+documentation included in the package.
+
+If everything went well, the W996[87]CF driver can be immediatly used (see next
+paragraph).
+
+
+6. Module loading
+=================
+To use the driver, it is necessary to load the "w9968cf" module into memory
+after every other module required.
+
+For example, loading can be done this way, as root:
+
+	[root@localhost home]# modprobe usbcore
+	[root@localhost home]# modprobe i2c-core
+	[root@localhost ov511-x.xx]# insmod ./ovcamchip.ko
+	[root@localhost home]# modprobe w9968cf
+
+At this point the devices should be recognized: "dmesg" can be used to analyze
+kernel messages:
+
+	[user@localhost home]$ dmesg
+
+There are a lot of parameters the module can use to change the default
+settings for each device. To list every possible parameter with a brief
+explanation about them and which syntax to use, it is recommended to run the
+"modinfo" command:
+
+	[root@locahost home]# modinfo w9968cf
+
+
+7. Module paramaters
+====================
+
+Module paramaters are listed below:
+-------------------------------------------------------------------------------
+Name:           vppmod_load
+Type:           int
+Syntax:         <0|1>
+Description:    Automatic 'w9968cf-vpp' module loading: 0 disabled, 1 enabled.
+                If enabled, every time an application attempts to open a
+                camera, 'insmod' searches for the video post-processing module
+                in the system and loads it automatically (if present).
+                The 'w9968cf-vpp' module adds extra image manipulation 
+                capabilities to the 'w9968cf' module,like software up-scaling,
+                colour conversions and video decoding.
+Default:        1
+-------------------------------------------------------------------------------
+Name:           simcams 
+Type:           int 
+Syntax:         <n> 
+Description:    Number of cameras allowed to stream simultaneously.
+                n may vary from 0 to 32.
+Default:        32
+-------------------------------------------------------------------------------
+Name:           video_nr
+Type:           int array (min = 0, max = 32)
+Syntax:         <-1|n[,...]> 
+Description:    Specify V4L minor mode number.
+                -1 = use next available
+                 n = use minor number n
+                You can specify 32 cameras this way.
+                For example:
+                video_nr=-1,2,-1 would assign minor number 2 to the second
+                recognized camera and use auto for the first one and for every
+                other camera.
+Default:        -1
+-------------------------------------------------------------------------------
+Name:           packet_size
+Type:           int array (min = 0, max = 32)
+Syntax:         <n[,...]> 
+Description:    Specify the maximum data payload size in bytes for alternate
+                settings, for each device. n is scaled between 63 and 1023.
+Default:        1023
+-------------------------------------------------------------------------------
+Name:           max_buffers
+Type:           int array (min = 0, max = 32)
+Syntax:         <n[,...]>
+Description:    Only for advanced users.
+                Specify the maximum number of video frame buffers to allocate
+                for each device, from 2 to 32.
+Default:        2
+-------------------------------------------------------------------------------
+Name:           double_buffer
+Type:           int array (min = 0, max = 32)
+Syntax:         <0|1[,...]> 
+Description:    Hardware double buffering: 0 disabled, 1 enabled.
+                It should be enabled if you want smooth video output: if you
+                obtain out of sync. video, disable it at all, or try to
+                decrease the 'clockdiv' module paramater value.
+Default:        1 for every device.
+-------------------------------------------------------------------------------
+Name:           clamping
+Type:           int array (min = 0, max = 32)
+Syntax:         <0|1[,...]> 
+Description:    Video data clamping: 0 disabled, 1 enabled.
+Default:        0 for every device.
+-------------------------------------------------------------------------------
+Name:           filter_type
+Type:           int array (min = 0, max = 32)
+Syntax:         <0|1|2[,...]> 
+Description:    Video filter type.
+                0 none, 1 (1-2-1) 3-tap filter, 2 (2-3-6-3-2) 5-tap filter.
+                The filter is used to reduce noise and aliasing artifacts
+                produced by the CCD or CMOS sensor.
+Default:        0 for every device.
+-------------------------------------------------------------------------------
+Name:           largeview
+Type:           int array (min = 0, max = 32)
+Syntax:         <0|1[,...]> 
+Description:    Large view: 0 disabled, 1 enabled.
+Default:        1 for every device.
+-------------------------------------------------------------------------------
+Name:           upscaling
+Type:           int array (min = 0, max = 32)
+Syntax:         <0|1[,...]> 
+Description:    Software scaling (for non-compressed video only):
+                0 disabled, 1 enabled.
+                Disable it if you have a slow CPU or you don't have enough
+                memory.
+Default:        0 for every device.
+Note:           If 'w9968cf-vpp' is not loaded, this paramater is set to 0.
+-------------------------------------------------------------------------------
+Name:           decompression
+Type:           int array (min = 0, max = 32)
+Syntax:         <0|1|2[,...]>
+Description:    Software video decompression:
+                0 = disables decompression
+                    (doesn't allow formats needing decompression).
+                1 = forces decompression
+                    (allows formats needing decompression only).
+                2 = allows any permitted formats.
+                Formats supporting (de)compressed video are YUV422P and
+                YUV420P/YUV420 in any resolutions where width and height are
+                multiples of 16.
+Default:        2 for every device.
+Note:           If 'w9968cf-vpp' is not loaded, forcing decompression is not
+                allowed; in this case this paramater is set to 2.
+-------------------------------------------------------------------------------
+Name:           force_palette
+Type:           int array (min = 0, max = 32)
+Syntax:         <0|9|10|13|15|8|7|1|6|3|4|5[,...]>
+Description:    Force picture palette.
+                In order:
+                 0 = Off - allows any of the following formats:
+                 9 = UYVY    16 bpp - Original video, compression disabled
+                10 = YUV420  12 bpp - Original video, compression enabled
+                13 = YUV422P 16 bpp - Original video, compression enabled
+                15 = YUV420P 12 bpp - Original video, compression enabled
+                 8 = YUVY    16 bpp - Software conversion from UYVY
+                 7 = YUV422  16 bpp - Software conversion from UYVY
+                 1 = GREY     8 bpp - Software conversion from UYVY
+                 6 = RGB555  16 bpp - Software conversion from UYVY
+                 3 = RGB565  16 bpp - Software conversion from UYVY
+                 4 = RGB24   24 bpp - Software conversion from UYVY
+                 5 = RGB32   32 bpp - Software conversion from UYVY
+                When not 0, this paramater will override 'decompression'.
+Default:        0 for every device. Initial palette is 9 (UYVY).
+Note:           If 'w9968cf-vpp' is not loaded, this paramater is set to 9.
+-------------------------------------------------------------------------------
+Name:           force_rgb
+Type:           int array (min = 0, max = 32)
+Syntax:         <0|1[,...]>
+Description:    Read RGB video data instead of BGR:
+                1 = use RGB component ordering.
+                0 = use BGR component ordering.
+                This parameter has effect when using RGBX palettes only.
+Default:        0 for every device.
+-------------------------------------------------------------------------------
+Name:           autobright
+Type:           long array (min = 0, max = 32)
+Syntax:         <0|1[,...]>
+Description:    CMOS sensor automatically changes brightness:
+                0 = no, 1 = yes
+Default:        0 for every device.
+-------------------------------------------------------------------------------
+Name:           autoexp
+Type:           long array (min = 0, max = 32)
+Syntax:         <0|1[,...]>
+Description:    CMOS sensor automatically changes exposure:
+                0 = no, 1 = yes
+Default:        1 for every device.
+-------------------------------------------------------------------------------
+Name:           lightfreq
+Type:           long array (min = 0, max = 32)
+Syntax:         <50|60[,...]>
+Description:    Light frequency in Hz:
+                50 for European and Asian lighting, 60 for American lighting.
+Default:        50 for every device.
+-------------------------------------------------------------------------------
+Name:           bandingfilter
+Type:           long array (min = 0, max = 32)
+Syntax:         <0|1[,...]> 
+Description:    Banding filter to reduce effects of fluorescent 
+                lighting:
+                0 disabled, 1 enabled.
+                This filter tries to reduce the pattern of horizontal
+                light/dark bands caused by some (usually fluorescent) lighting.
+Default:        0 for every device.
+-------------------------------------------------------------------------------
+Name:           clockdiv
+Type:           long array (min = 0, max = 32)
+Syntax:         <-1|n[,...]>
+Description:    Force pixel clock divisor to a specific value (for experts):
+                n may vary from 0 to 127.
+                -1 for automatic value.
+                See also the 'double_buffer' module paramater.
+Default:        -1 for every device.
+-------------------------------------------------------------------------------
+Name:           backlight
+Type:           long array (min = 0, max = 32)
+Syntax:         <0|1[,...]>
+Description:    Objects are lit from behind:
+                0 = no, 1 = yes
+Default:        0 for every device.
+-------------------------------------------------------------------------------
+Name:           mirror
+Type:           long array (min = 0, max = 32)
+Syntax:         <0|1[,...]>
+Description:    Reverse image horizontally:
+                0 = no, 1 = yes
+Default:        0 for every device.
+-------------------------------------------------------------------------------
+Name:           sensor_mono
+Type:           long array (min = 0, max = 32)
+Syntax:         <0|1[,...]> 
+Description:    The CMOS sensor is monochrome:
+                0 = no, 1 = yes
+Default:        0 for every device.
+-------------------------------------------------------------------------------
+Name:           brightness
+Type:           long array (min = 0, max = 32)
+Syntax:         <n[,...]>
+Description:    Set picture brightness (0-65535).
+                This parameter has no effect if 'autobright' is enabled.
+Default:        31000 for every device.
+-------------------------------------------------------------------------------
+Name:           hue
+Type:           long array (min = 0, max = 32)
+Syntax:         <n[,...]>
+Description:    Set picture hue (0-65535).
+Default:        32768 for every device.
+-------------------------------------------------------------------------------
+Name:           colour
+Type:           long array (min = 0, max = 32)
+Syntax:         <n[,...]>
+Description:    Set picture saturation (0-65535).
+Default:        32768 for every device.
+-------------------------------------------------------------------------------
+Name:           contrast
+Type:           long array (min = 0, max = 32)
+Syntax:         <n[,...]> 
+Description:    Set picture contrast (0-65535).
+Default:        50000 for every device.
+-------------------------------------------------------------------------------
+Name:           whiteness
+Type:           long array (min = 0, max = 32)
+Syntax:         <n[,...]> 
+Description:    Set picture whiteness (0-65535).
+Default:        32768 for every device.
+-------------------------------------------------------------------------------
+Name:           debug
+Type:           int
+Syntax:         <n> 
+Description:    Debugging information level, from 0 to 6:
+                0 = none (be cautious)
+                1 = critical errors
+                2 = significant informations
+                3 = configuration or general messages
+                4 = warnings
+                5 = called functions
+                6 = function internals
+                Level 5 and 6 are useful for testing only, when just one
+                device is used.
+Default:        2
+-------------------------------------------------------------------------------
+Name:           specific_debug
+Type:           int
+Syntax:         <0|1>
+Description:    Enable or disable specific debugging messages:
+                0 = print messages concerning every level <= 'debug' level.
+                1 = print messages concerning the level indicated by 'debug'.
+Default:        0
+-------------------------------------------------------------------------------
+
+
+8. Credits
+==========
+The development would not have proceed much further without having looked at
+the source code of other drivers and without the help of several persons; in
+particular:
+
+- the I2C interface to kernel and high-level CMOS sensor control routines have
+  been taken from the OV511 driver by Mark McClelland;
+
+- memory management code has been copied from the bttv driver by Ralph Metzler,
+  Marcus Metzler and Gerd Knorr;
+
+- the low-level I2C read function has been written by Frédéric Jouault, who
+  also gave me commented logs about sniffed USB traffic taken from another
+  driver for another system;
+
+- the low-level I2C fast write function has been written by Piotr Czerczak;
diff -Nru a/MAINTAINERS b/MAINTAINERS
--- a/MAINTAINERS	Fri Dec 12 15:06:58 2003
+++ b/MAINTAINERS	Fri Dec 12 15:06:58 2003
@@ -2218,6 +2218,13 @@
 L:	linux-usb-devel@lists.sourceforge.net
 S:	Maintained
 
+USB W996[87]CF DRIVER
+P:	Luca Risolia
+M:	luca_ing@libero.it
+L:	linux-usb-devel@lists.sourceforge.net
+W:	http://go.lamarinapunto.com
+S:	Maintained
+
 USER-MODE LINUX
 P:	Jeff Dike
 M:	jdike@karaya.com
diff -Nru a/drivers/usb/Makefile b/drivers/usb/Makefile
--- a/drivers/usb/Makefile	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/Makefile	Fri Dec 12 15:06:58 2003
@@ -34,6 +34,7 @@
 obj-$(CONFIG_USB_SE401)		+= media/
 obj-$(CONFIG_USB_STV680)	+= media/
 obj-$(CONFIG_USB_VICAM)		+= media/
+obj-$(CONFIG_USB_W9968CF)	+= media/
 
 obj-$(CONFIG_USB_CATC)		+= net/
 obj-$(CONFIG_USB_KAWETH)	+= net/
@@ -58,4 +59,3 @@
 obj-$(CONFIG_USB_TEST)		+= misc/
 obj-$(CONFIG_USB_TIGL)		+= misc/
 obj-$(CONFIG_USB_USS720)	+= misc/
-
diff -Nru a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c
--- a/drivers/usb/class/cdc-acm.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/class/cdc-acm.c	Fri Dec 12 15:06:58 2003
@@ -1,5 +1,5 @@
 /*
- * acm.c  Version 0.22
+ * cdc-acm.c
  *
  * Copyright (c) 1999 Armin Fuerst	<fuerst@in.tum.de>
  * Copyright (c) 1999 Pavel Machek	<pavel@suse.cz>
@@ -26,6 +26,7 @@
  *	v0.21 - revert to probing on device for devices with multiple configs
  *	v0.22 - probe only the control interface. if usbcore doesn't choose the
  *		config we want, sysadmin changes bConfigurationValue in sysfs.
+ *	v0.23 - use softirq for rx processing, as needed by tty layer
  */
 
 /*
@@ -44,6 +45,8 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  */
 
+#undef DEBUG
+
 #include <linux/kernel.h>
 #include <linux/errno.h>
 #include <linux/init.h>
@@ -54,14 +57,13 @@
 #include <linux/module.h>
 #include <linux/smp_lock.h>
 #include <asm/uaccess.h>
-#undef DEBUG
 #include <linux/usb.h>
 #include <asm/byteorder.h>
 
 /*
  * Version Information
  */
-#define DRIVER_VERSION "v0.21"
+#define DRIVER_VERSION "v0.23"
 #define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik"
 #define DRIVER_DESC "USB Abstract Control Model driver for USB modems and ISDN adapters"
 
@@ -146,7 +148,8 @@
 	struct tty_struct *tty;				/* the corresponding tty */
 	struct urb *ctrlurb, *readurb, *writeurb;	/* urbs */
 	struct acm_line line;				/* line coding (bits, stop, parity) */
-	struct work_struct work;					/* work queue entry for line discipline waking up */
+	struct work_struct work;			/* work queue entry for line discipline waking up */
+	struct tasklet_struct bh;			/* rx processing */
 	unsigned int ctrlin;				/* input control lines (DCD, DSR, RI, break, overruns) */
 	unsigned int ctrlout;				/* output control lines (DTR, RTS) */
 	unsigned int writesize;				/* max packet size for the output bulk endpoint */
@@ -184,9 +187,10 @@
 #define acm_send_break(acm, ms)		acm_ctrl_msg(acm, ACM_REQ_SEND_BREAK, ms, NULL, 0)
 
 /*
- * Interrupt handler for various ACM control events
+ * Interrupt handlers for various ACM device responses
  */
 
+/* control interface reports status changes with "interrupt" transfers */
 static void acm_ctrl_irq(struct urb *urb, struct pt_regs *regs)
 {
 	struct acm *acm = urb->context;
@@ -251,20 +255,30 @@
 		     __FUNCTION__, status);
 }
 
+/* data interface returns incoming bytes, or we got unthrottled */
 static void acm_read_bulk(struct urb *urb, struct pt_regs *regs)
 {
 	struct acm *acm = urb->context;
-	struct tty_struct *tty = acm->tty;
-	unsigned char *data = urb->transfer_buffer;
-	int i = 0;
 
 	if (!ACM_READY(acm))
 		return;
 
 	if (urb->status)
-		dbg("nonzero read bulk status received: %d", urb->status);
+		dev_dbg(&acm->data->dev, "bulk rx status %d\n", urb->status);
+
+	/* calling tty_flip_buffer_push() in_irq() isn't allowed */
+	tasklet_schedule(&acm->bh);
+}
+
+static void acm_rx_tasklet(unsigned long _acm)
+{
+	struct acm *acm = (void *)_acm;
+	struct urb *urb = acm->readurb;
+	struct tty_struct *tty = acm->tty;
+	unsigned char *data = urb->transfer_buffer;
+	int i = 0;
 
-	if (!urb->status && !acm->throttle)  {
+	if (urb->actual_length > 0 && !acm->throttle)  {
 		for (i = 0; i < urb->actual_length && !acm->throttle; i++) {
 			/* if we insert more than TTY_FLIPBUF_SIZE characters,
 			 * we drop them. */
@@ -285,10 +299,12 @@
 	urb->actual_length = 0;
 	urb->dev = acm->dev;
 
-	if (usb_submit_urb(urb, GFP_ATOMIC))
-		dbg("failed resubmitting read urb");
+	i = usb_submit_urb(urb, GFP_ATOMIC);
+	if (i)
+		dev_dbg(&acm->data->dev, "bulk rx resubmit %d\n", i);
 }
 
+/* data interface wrote those outgoing bytes */
 static void acm_write_bulk(struct urb *urb, struct pt_regs *regs)
 {
 	struct acm *acm = (struct acm *)urb->context;
@@ -621,6 +637,8 @@
 			acm->minor = minor;
 			acm->dev = dev;
 
+			acm->bh.func = acm_rx_tasklet;
+			acm->bh.data = (unsigned long) acm;
 			INIT_WORK(&acm->work, acm_softint, acm);
 
 			if (!(buf = kmalloc(ctrlsize + readsize + acm->writesize, GFP_KERNEL))) {
diff -Nru a/drivers/usb/class/usblp.c b/drivers/usb/class/usblp.c
--- a/drivers/usb/class/usblp.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/class/usblp.c	Fri Dec 12 15:06:57 2003
@@ -610,7 +610,7 @@
 		if (!usblp->wcomplete) {
 			barrier();
 			if (file->f_flags & O_NONBLOCK)
-				return -EAGAIN;
+				return writecount ? writecount : -EAGAIN;
 
 			timeout = USBLP_WRITE_TIMEOUT;
 			add_wait_queue(&usblp->wait, &wait);
@@ -673,8 +673,12 @@
 
 		usblp->writeurb->dev = usblp->dev;
 		usblp->wcomplete = 0;
-		if (usb_submit_urb(usblp->writeurb, GFP_KERNEL)) {
-			count = -EIO;
+		err = usb_submit_urb(usblp->writeurb, GFP_KERNEL);
+		if (err) {
+			if (err != -ENOMEM)
+				count = -EIO;
+			else
+				count = writecount ? writecount : -ENOMEM;
 			up (&usblp->sem);
 			break;
 		}
@@ -706,8 +710,6 @@
 			goto done;
 		}
 
-		// FIXME:  only use urb->status inside completion
-		// callbacks; this way is racey...
 		add_wait_queue(&usblp->wait, &wait);
 		while (1==1) {
 			if (signal_pending(current)) {
@@ -1093,7 +1095,7 @@
 	/* First two bytes are length in big-endian.
 	 * They count themselves, and we copy them into
 	 * the user's buffer. */
-	length = (usblp->device_id_string[0] << 8) + usblp->device_id_string[1];
+	length = be16_to_cpu(*((u16 *)usblp->device_id_string));
 	if (length < 2)
 		length = 2;
 	else if (length >= USBLP_DEVICE_ID_SIZE)
diff -Nru a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
--- a/drivers/usb/core/hcd.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/core/hcd.c	Fri Dec 12 15:06:58 2003
@@ -1165,6 +1165,7 @@
 	struct device			*sys = 0;
 	unsigned long			flags;
 	struct completion_splice	splice;
+	struct list_head		*tmp;
 	int				retval;
 
 	if (!urb)
@@ -1203,7 +1204,12 @@
 	 */
 	WARN_ON (!HCD_IS_RUNNING (hcd->state) && hcd->state != USB_STATE_HALT);
 
-	if (!urb->hcpriv) {
+	/* insist the urb is still queued */
+	list_for_each(tmp, &dev->urb_list) {
+		if (tmp == &urb->urb_list)
+			break;
+	}
+	if (tmp != &urb->urb_list) {
 		retval = -EINVAL;
 		goto done;
 	}
diff -Nru a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
--- a/drivers/usb/core/hub.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/core/hub.c	Fri Dec 12 15:06:57 2003
@@ -126,14 +126,20 @@
 static void hub_irq(struct urb *urb, struct pt_regs *regs)
 {
 	struct usb_hub *hub = (struct usb_hub *)urb->context;
-	unsigned long flags;
 	int status;
 
+	spin_lock(&hub_event_lock);
+	hub->urb_active = 0;
+	if (hub->urb_complete) {	/* disconnect or rmmod */
+		complete(hub->urb_complete);
+		goto done;
+	}
+
 	switch (urb->status) {
 	case -ENOENT:		/* synchronous unlink */
 	case -ECONNRESET:	/* async unlink */
 	case -ESHUTDOWN:	/* hardware going away */
-		return;
+		goto done;
 
 	default:		/* presumably an error */
 		/* Cause a hub reset after 10 consecutive errors */
@@ -151,18 +157,20 @@
 	hub->nerrors = 0;
 
 	/* Something happened, let khubd figure it out */
-	spin_lock_irqsave(&hub_event_lock, flags);
 	if (list_empty(&hub->event_list)) {
 		list_add(&hub->event_list, &hub_event_list);
 		wake_up(&khubd_wait);
 	}
-	spin_unlock_irqrestore(&hub_event_lock, flags);
 
 resubmit:
 	if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0
 			/* ENODEV means we raced disconnect() */
 			&& status != -ENODEV)
 		dev_err (&hub->intf->dev, "resubmit --> %d\n", urb->status);
+	if (status == 0)
+		hub->urb_active = 1;
+done:
+	spin_unlock(&hub_event_lock);
 }
 
 /* USB 2.0 spec Section 11.24.2.3 */
@@ -467,7 +475,8 @@
 		message = "couldn't submit status urb";
 		goto fail;
 	}
-		
+	hub->urb_active = 1;
+
 	/* Wake up khubd */
 	wake_up(&khubd_wait);
 
@@ -485,6 +494,7 @@
 static void hub_disconnect(struct usb_interface *intf)
 {
 	struct usb_hub *hub = usb_get_intfdata (intf);
+	DECLARE_COMPLETION(urb_complete);
 	unsigned long flags;
 
 	if (!hub)
@@ -492,12 +502,11 @@
 
 	usb_set_intfdata (intf, NULL);
 	spin_lock_irqsave(&hub_event_lock, flags);
+	hub->urb_complete = &urb_complete;
 
 	/* Delete it and then reset it */
-	list_del(&hub->event_list);
-	INIT_LIST_HEAD(&hub->event_list);
-	list_del(&hub->hub_list);
-	INIT_LIST_HEAD(&hub->hub_list);
+	list_del_init(&hub->event_list);
+	list_del_init(&hub->hub_list);
 
 	spin_unlock_irqrestore(&hub_event_lock, flags);
 
@@ -510,6 +519,8 @@
 
 	if (hub->urb) {
 		usb_unlink_urb(hub->urb);
+		if (hub->urb_active)
+			wait_for_completion(&urb_complete);
 		usb_free_urb(hub->urb);
 		hub->urb = NULL;
 	}
diff -Nru a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
--- a/drivers/usb/core/hub.h	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/core/hub.h	Fri Dec 12 15:06:57 2003
@@ -172,6 +172,8 @@
 struct usb_hub {
 	struct usb_interface	*intf;		/* the "real" device */
 	struct urb		*urb;		/* for interrupt polling pipe */
+	struct completion	*urb_complete;	/* wait for urb to end */
+	unsigned int		urb_active:1;
 
 	/* buffer for urb ... 1 bit each for hub and children, rounded up */
 	char			(*buffer)[(USB_MAXCHILDREN + 1 + 7) / 8];
diff -Nru a/drivers/usb/core/message.c b/drivers/usb/core/message.c
--- a/drivers/usb/core/message.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/core/message.c	Fri Dec 12 15:06:57 2003
@@ -1086,6 +1086,11 @@
 		ret = -EINVAL;
 		goto out;
 	}
+
+	/* The USB spec says configuration 0 means unconfigured.
+	 * But if a device includes a configuration numbered 0,
+	 * we will accept it as a correctly configured state.
+	 */
 	if (cp && configuration == 0)
 		dev_warn(&dev->dev, "config 0 descriptor??\n");
 
@@ -1101,7 +1106,7 @@
 		goto out;
 
 	dev->actconfig = cp;
-	if (!configuration)
+	if (!cp)
 		dev->state = USB_STATE_ADDRESS;
 	else {
 		dev->state = USB_STATE_CONFIGURED;
diff -Nru a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
--- a/drivers/usb/core/usb.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/core/usb.c	Fri Dec 12 15:06:58 2003
@@ -80,7 +80,7 @@
 
 static int usb_generic_driver_data;
 
-/* needs to be called with BKL held */
+/* called from driver core with usb_bus_type.subsys writelock */
 int usb_probe_interface(struct device *dev)
 {
 	struct usb_interface * intf = to_usb_interface(dev);
@@ -93,12 +93,14 @@
 	if (!driver->probe)
 		return error;
 
+	/* driver claim() doesn't yet affect dev->driver... */
+	if (intf->driver)
+		return error;
+
 	id = usb_match_id (intf, driver->id_table);
 	if (id) {
 		dev_dbg (dev, "%s - got id\n", __FUNCTION__);
-		down (&driver->serialize);
 		error = driver->probe (intf, id);
-		up (&driver->serialize);
 	}
 	if (!error)
 		intf->driver = driver;
@@ -106,23 +108,24 @@
 	return error;
 }
 
+/* called from driver core with usb_bus_type.subsys writelock */
 int usb_unbind_interface(struct device *dev)
 {
 	struct usb_interface *intf = to_usb_interface(dev);
-	struct usb_driver *driver = to_usb_driver(dev->driver);
-
-	down(&driver->serialize);
+	struct usb_driver *driver = intf->driver;
 
 	/* release all urbs for this interface */
 	usb_disable_interface(interface_to_usbdev(intf), intf);
 
-	if (intf->driver && intf->driver->disconnect)
-		intf->driver->disconnect(intf);
-
-	/* force a release and re-initialize the interface */
-	usb_driver_release_interface(driver, intf);
+	if (driver && driver->disconnect)
+		driver->disconnect(intf);
 
-	up(&driver->serialize);
+	/* reset other interface state */
+	usb_set_interface(interface_to_usbdev(intf),
+			intf->altsetting[0].desc.bInterfaceNumber,
+			0);
+	usb_set_intfdata(intf, NULL);
+	intf->driver = NULL;
 
 	return 0;
 }
@@ -152,8 +155,6 @@
 	new_driver->driver.probe = usb_probe_interface;
 	new_driver->driver.remove = usb_unbind_interface;
 
-	init_MUTEX(&new_driver->serialize);
-
 	retval = driver_register(&new_driver->driver);
 
 	if (!retval) {
@@ -170,7 +171,7 @@
 /**
  * usb_deregister - unregister a USB driver
  * @driver: USB operations of the driver to unregister
- * Context: !in_interrupt (), must be called with BKL held
+ * Context: must be able to sleep
  *
  * Unlinks the specified driver from the internal USB driver list.
  * 
@@ -264,26 +265,22 @@
  * Few drivers should need to use this routine, since the most natural
  * way to bind to an interface is to return the private data from
  * the driver's probe() method.
+ *
+ * Callers must own the driver model's usb bus writelock.  So driver
+ * probe() entries don't need extra locking, but other call contexts
+ * may need to explicitly claim that lock.
  */
 int usb_driver_claim_interface(struct usb_driver *driver, struct usb_interface *iface, void* priv)
 {
 	if (!iface || !driver)
 		return -EINVAL;
 
-	/* this is mainly to lock against usbfs */
-	lock_kernel();
-	if (iface->driver) {
-		unlock_kernel();
-		err ("%s driver booted %s off interface %p",
-			driver->name, iface->driver->name, iface);
+	if (iface->driver)
 		return -EBUSY;
-	} else {
-	    dbg("%s driver claimed interface %p", driver->name, iface);
-	}
 
+	/* FIXME should device_bind_driver() */
 	iface->driver = driver;
 	usb_set_intfdata(iface, priv);
-	unlock_kernel();
 	return 0;
 }
 
@@ -323,13 +320,22 @@
  * usually won't need to call this.
  *
  * This call is synchronous, and may not be used in an interrupt context.
+ * Callers must own the driver model's usb bus writelock.  So driver
+ * disconnect() entries don't need extra locking, but other call contexts
+ * may need to explicitly claim that lock.
  */
 void usb_driver_release_interface(struct usb_driver *driver, struct usb_interface *iface)
 {
 	/* this should never happen, don't release something that's not ours */
-	if (iface->driver && iface->driver != driver)
+	if (!iface || !iface->driver || iface->driver != driver)
 		return;
 
+	if (iface->dev.driver) {
+		/* FIXME should be the ONLY case here */
+		device_release_driver(&iface->dev);
+		return;
+	}
+
 	usb_set_interface(interface_to_usbdev(iface),
 			iface->altsetting[0].desc.bInterfaceNumber,
 			0);
@@ -991,6 +997,7 @@
 	int err = -EINVAL;
 	int i;
 	int j;
+	int config;
 
 	/*
 	 * Set the driver for the usb device to point to the "generic" driver.
@@ -1108,18 +1115,30 @@
 
 	/* choose and set the configuration. that registers the interfaces
 	 * with the driver core, and lets usb device drivers bind to them.
+	 * NOTE:  should interact with hub power budgeting.
 	 */
+	config = dev->config[0].desc.bConfigurationValue;
 	if (dev->descriptor.bNumConfigurations != 1) {
+		for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
+			/* heuristic:  Linux is more likely to have class
+			 * drivers, so avoid vendor-specific interfaces.
+			 */
+			if (dev->config[i].interface[0]->altsetting
+						->desc.bInterfaceClass
+					== USB_CLASS_VENDOR_SPEC)
+				continue;
+			config = dev->config[i].desc.bConfigurationValue;
+			break;
+		}
 		dev_info(&dev->dev,
 			"configuration #%d chosen from %d choices\n",
-			dev->config[0].desc.bConfigurationValue,
+			config,
 			dev->descriptor.bNumConfigurations);
 	}
-	err = usb_set_configuration(dev,
-			dev->config[0].desc.bConfigurationValue);
+	err = usb_set_configuration(dev, config);
 	if (err) {
 		dev_err(&dev->dev, "can't set config #%d, error %d\n",
-			dev->config[0].desc.bConfigurationValue, err);
+			config, err);
 		goto fail;
 	}
 
diff -Nru a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c
--- a/drivers/usb/host/ohci-dbg.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/host/ohci-dbg.c	Fri Dec 12 15:06:58 2003
@@ -269,18 +269,19 @@
 	ohci_dump_status (controller, NULL, 0);
 	if (controller->hcca)
 		ohci_dbg (controller,
-			"hcca frame #%04x\n", controller->hcca->frame_no);
+			"hcca frame #%04x\n", OHCI_FRAME_NO(controller->hcca));
 	ohci_dump_roothub (controller, 1, NULL, 0);
 }
 
 static const char data0 [] = "DATA0";
 static const char data1 [] = "DATA1";
 
-static void ohci_dump_td (struct ohci_hcd *ohci, char *label, struct td *td)
+static void ohci_dump_td (const struct ohci_hcd *ohci, const char *label,
+		const struct td *td)
 {
 	u32	tmp = le32_to_cpup (&td->hwINFO);
 
-	ohci_dbg (ohci, "%s td %p%s; urb %p index %d; hw next td %08x",
+	ohci_dbg (ohci, "%s td %p%s; urb %p index %d; hw next td %08x\n",
 		label, td,
 		(tmp & TD_DONE) ? " (DONE)" : "",
 		td->urb, td->index,
@@ -301,28 +302,28 @@
 		case TD_DP_OUT: pid = "OUT"; break;
 		default: pid = "(bad pid)"; break;
 		}
-		ohci_dbg (ohci, "     info %08x CC=%x %s DI=%d %s %s", tmp,
+		ohci_dbg (ohci, "     info %08x CC=%x %s DI=%d %s %s\n", tmp,
 			TD_CC_GET(tmp), /* EC, */ toggle,
 			(tmp & TD_DI) >> 21, pid,
 			(tmp & TD_R) ? "R" : "");
 		cbp = le32_to_cpup (&td->hwCBP);
 		be = le32_to_cpup (&td->hwBE);
-		ohci_dbg (ohci, "     cbp %08x be %08x (len %d)", cbp, be,
+		ohci_dbg (ohci, "     cbp %08x be %08x (len %d)\n", cbp, be,
 			cbp ? (be + 1 - cbp) : 0);
 	} else {
 		unsigned	i;
-		ohci_dbg (ohci, "     info %08x CC=%x FC=%d DI=%d SF=%04x", tmp,
+		ohci_dbg (ohci, "  info %08x CC=%x FC=%d DI=%d SF=%04x\n", tmp,
 			TD_CC_GET(tmp),
 			(tmp >> 24) & 0x07,
 			(tmp & TD_DI) >> 21,
 			tmp & 0x0000ffff);
-		ohci_dbg (ohci, "     bp0 %08x be %08x",
+		ohci_dbg (ohci, "  bp0 %08x be %08x\n",
 			le32_to_cpup (&td->hwCBP) & ~0x0fff,
 			le32_to_cpup (&td->hwBE));
 		for (i = 0; i < MAXPSW; i++) {
 			u16	psw = le16_to_cpup (&td->hwPSW [i]);
 			int	cc = (psw >> 12) & 0x0f;
-			ohci_dbg (ohci, "       psw [%d] = %2x, CC=%x %s=%d", i,
+			ohci_dbg (ohci, "    psw [%d] = %2x, CC=%x %s=%d\n", i,
 				psw, cc,
 				(cc >= 0x0e) ? "OFFSET" : "SIZE",
 				psw & 0x0fff);
@@ -332,12 +333,13 @@
 
 /* caller MUST own hcd spinlock if verbose is set! */
 static void __attribute__((unused))
-ohci_dump_ed (struct ohci_hcd *ohci, char *label, struct ed *ed, int verbose)
+ohci_dump_ed (const struct ohci_hcd *ohci, const char *label,
+		const struct ed *ed, int verbose)
 {
 	u32	tmp = ed->hwINFO;
 	char	*type = "";
 
-	ohci_dbg (ohci, "%s, ed %p state 0x%x type %s; next ed %08x",
+	ohci_dbg (ohci, "%s, ed %p state 0x%x type %s; next ed %08x\n",
 		label,
 		ed, ed->state, edstring (ed->type),
 		le32_to_cpup (&ed->hwNextED));
@@ -347,7 +349,7 @@
 	/* else from TDs ... control */
 	}
 	ohci_dbg (ohci,
-		"  info %08x MAX=%d%s%s%s%s EP=%d%s DEV=%d", le32_to_cpu (tmp),
+		"  info %08x MAX=%d%s%s%s%s EP=%d%s DEV=%d\n", le32_to_cpu (tmp),
 		0x03ff & (le32_to_cpu (tmp) >> 16),
 		(tmp & ED_DEQUEUE) ? " DQ" : "",
 		(tmp & ED_ISO) ? " ISO" : "",
@@ -356,7 +358,7 @@
 		0x000f & (le32_to_cpu (tmp) >> 7),
 		type,
 		0x007f & le32_to_cpu (tmp));
-	ohci_dbg (ohci, "  tds: head %08x %s%s tail %08x%s",
+	ohci_dbg (ohci, "  tds: head %08x %s%s tail %08x%s\n",
 		tmp = le32_to_cpup (&ed->hwHeadP),
 		(ed->hwHeadP & ED_C) ? data1 : data0,
 		(ed->hwHeadP & ED_H) ? " HALT" : "",
@@ -541,24 +543,29 @@
 			if (temp == seen_count) {
 				u32	info = ed->hwINFO;
 				u32	scratch = cpu_to_le32p (&ed->hwINFO);
+				struct list_head	*entry;
+				unsigned		qlen = 0;
+
+				/* qlen measured here in TDs, not urbs */
+				list_for_each (entry, &ed->td_list)
+					qlen++;
 
 				temp = snprintf (next, size,
-					" (%cs dev%d%s ep%d%s"
+					" (%cs dev%d ep%d%s-%s qlen %u"
 					" max %d %08x%s%s)",
 					(info & ED_LOWSPEED) ? 'l' : 'f',
 					scratch & 0x7f,
-					(info & ED_ISO) ? " iso" : "",
 					(scratch >> 7) & 0xf,
 					(info & ED_IN) ? "in" : "out",
+					(info & ED_ISO) ? "iso" : "int",
+					qlen,
 					0x03ff & (scratch >> 16),
 					scratch,
-					(info & ED_SKIP) ? " s" : "",
+					(info & ED_SKIP) ? " K" : "",
 					(ed->hwHeadP & ED_H) ? " H" : "");
 				size -= temp;
 				next += temp;
 
-				// FIXME some TD info too
-
 				if (seen_count < DBG_SCHED_LIMIT)
 					seen [seen_count++] = ed;
 
@@ -617,7 +624,7 @@
 	/* hcca */
 	if (ohci->hcca)
 		ohci_dbg_sw (ohci, &next, &size,
-			"hcca frame 0x%04x\n", ohci->hcca->frame_no);
+			"hcca frame 0x%04x\n", OHCI_FRAME_NO(ohci->hcca));
 
 	/* other registers mostly affect frame timings */
 	rdata = readl (&regs->fminterval);
diff -Nru a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
--- a/drivers/usb/host/ohci-hcd.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/host/ohci-hcd.c	Fri Dec 12 15:06:57 2003
@@ -226,7 +226,7 @@
 		if (retval < 0)
 			goto fail;
 		if (ed->type == PIPE_ISOCHRONOUS) {
-			u16	frame = le16_to_cpu (ohci->hcca->frame_no);
+			u16	frame = OHCI_FRAME_NO(ohci->hcca);
 
 			/* delay a few frames before the first TD */
 			frame += max_t (u16, 8, ed->interval);
@@ -281,7 +281,7 @@
 		urb_priv = urb->hcpriv;
 		if (urb_priv) {
 			if (urb_priv->ed->state == ED_OPER)
-				start_urb_unlink (ohci, urb_priv->ed);
+				start_ed_unlink (ohci, urb_priv->ed);
 		}
 	} else {
 		/*
@@ -363,7 +363,7 @@
 {
 	struct ohci_hcd		*ohci = hcd_to_ohci (hcd);
 
-	return le16_to_cpu (ohci->hcca->frame_no);
+	return OHCI_FRAME_NO(ohci->hcca);
 }
 
 /*-------------------------------------------------------------------------*
@@ -591,7 +591,7 @@
 	 */
 	spin_lock (&ohci->lock);
 	if (ohci->ed_rm_list)
-		finish_unlinks (ohci, le16_to_cpu (ohci->hcca->frame_no),
+		finish_unlinks (ohci, OHCI_FRAME_NO(ohci->hcca),
 				ptregs);
 	if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list
 			&& HCD_IS_RUNNING(ohci->hcd.state))
diff -Nru a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c
--- a/drivers/usb/host/ohci-q.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/host/ohci-q.c	Fri Dec 12 15:06:58 2003
@@ -430,7 +430,7 @@
  * put the ep on the rm_list
  * real work is done at the next start frame (SF) hardware interrupt
  */
-static void start_urb_unlink (struct ohci_hcd *ohci, struct ed *ed)
+static void start_ed_unlink (struct ohci_hcd *ohci, struct ed *ed)
 {    
 	ed->hwINFO |= ED_DEQUEUE;
 	ed->state = ED_UNLINK;
@@ -441,7 +441,7 @@
 	 * behave.  frame_no wraps every 2^16 msec, and changes right before
 	 * SF is triggered.
 	 */
-	ed->tick = le16_to_cpu (ohci->hcca->frame_no) + 1;
+	ed->tick = OHCI_FRAME_NO(ohci->hcca) + 1;
 
 	/* rm_list is just singly linked, for simplicity */
 	ed->ed_next = ohci->ed_rm_list;
@@ -479,7 +479,8 @@
 	 * and iso; other urbs rarely need more than one TD per urb.
 	 * this way, only final tds (or ones with an error) cause IRQs.
 	 * at least immediately; use DI=6 in case any control request is
-	 * tempted to die part way through.
+	 * tempted to die part way through.  (and to force the hc to flush
+	 * its donelist soonish, even on unlink paths.)
 	 *
 	 * NOTE: could delay interrupts even for the last TD, and get fewer
 	 * interrupts ... increasing per-urb latency by sharing interrupts.
@@ -879,12 +880,27 @@
 		u32			*prev;
 
 		/* only take off EDs that the HC isn't using, accounting for
-		 * frame counter wraps.
+		 * frame counter wraps and EDs with partially retired TDs
 		 */
-		if (tick_before (tick, ed->tick)
-				&& HCD_IS_RUNNING(ohci->hcd.state)) {
-			last = &ed->ed_next;
-			continue;
+		if (likely (HCD_IS_RUNNING(ohci->hcd.state))) {
+			if (tick_before (tick, ed->tick)) {
+skip_ed:
+				last = &ed->ed_next;
+				continue;
+			}
+
+			if (!list_empty (&ed->td_list)) {
+				struct td	*td;
+				u32		head;
+
+				td = list_entry (ed->td_list.next, struct td,
+							td_list);
+				head = cpu_to_le32 (ed->hwHeadP) & TD_MASK;
+
+				/* INTR_WDH may need to clean up first */
+				if (td->td_dma != head)
+					goto skip_ed;
+			}
 		}
 
 		/* reentrancy:  if we drop the schedule lock, someone might
diff -Nru a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h
--- a/drivers/usb/host/ohci.h	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/host/ohci.h	Fri Dec 12 15:06:58 2003
@@ -172,8 +172,14 @@
 struct ohci_hcca {
 #define NUM_INTS 32
 	__u32	int_table [NUM_INTS];	/* periodic schedule */
-	__u16	frame_no;		/* current frame number */
-	__u16	pad1;			/* set to 0 on each frame_no change */
+
+	/* 
+	 * OHCI defines u16 frame_no, followed by u16 zero pad.
+	 * Since some processors can't do 16 bit bus accesses,
+	 * portable access must be a 32 bit byteswapped access.
+	 */
+	u32	frame_no;		/* current frame number */
+#define OHCI_FRAME_NO(hccap) ((u16)le32_to_cpup(&(hccap)->frame_no))
 	__u32	done_head;		/* info returned for an interrupt */
 	u8	reserved_for_hc [116];
 	u8	what [4];		/* spec only identifies 252 bytes :) */
diff -Nru a/drivers/usb/image/scanner.c b/drivers/usb/image/scanner.c
--- a/drivers/usb/image/scanner.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/image/scanner.c	Fri Dec 12 15:06:57 2003
@@ -380,6 +380,10 @@
  *      Visioneer scanners.
  *    - Added test for USB_CLASS_CDC_DATA which is used by some fingerprint scanners.
  *
+ * 0.4.16  2003-11-04
+ *    - Added vendor/product ids for Epson, Genius, Microtek, Plustek, Reflecta, and
+ *      Visioneer scanners. Removed ids for HP PSC devices as these are supported by
+ *      the hpoj userspace driver.
  *
  * TODO
  *    - Performance
diff -Nru a/drivers/usb/image/scanner.h b/drivers/usb/image/scanner.h
--- a/drivers/usb/image/scanner.h	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/image/scanner.h	Fri Dec 12 15:06:57 2003
@@ -43,7 +43,7 @@
 
 // #define DEBUG
 
-#define DRIVER_VERSION "0.4.15"
+#define DRIVER_VERSION "0.4.16"
 #define DRIVER_DESC "USB Scanner Driver"
 
 #include <linux/usb.h>
@@ -146,7 +146,12 @@
 	{ USB_DEVICE(0x0458, 0x2015) }, /* ColorPage HR7LE */
 	{ USB_DEVICE(0x0458, 0x2016) }, /* ColorPage HR6X */
 	{ USB_DEVICE(0x0458, 0x2018) },	/* ColorPage HR7X */
+	{ USB_DEVICE(0x0458, 0x201b) },	/* Colorpage Vivid 4x */
 	/* Hewlett Packard */
+	/* IMPORTANT: Hewlett-Packard multi-function peripherals (OfficeJet, 
+	   Printer/Scanner/Copier (PSC), LaserJet, or PhotoSmart printer)
+	   should not be added to this table because they are accessed by a
+	   userspace driver (hpoj) */
 	{ USB_DEVICE(0x03f0, 0x0101) },	/* ScanJet 4100C */
 	{ USB_DEVICE(0x03f0, 0x0102) },	/* PhotoSmart S20 */
 	{ USB_DEVICE(0x03f0, 0x0105) },	/* ScanJet 4200C */
@@ -168,10 +173,10 @@
 	{ USB_DEVICE(0x03F0, 0x1105) },	/* ScanJet 5470C */
 	{ USB_DEVICE(0x03f0, 0x1205) }, /* ScanJet 5550C */
 	{ USB_DEVICE(0x03f0, 0x1305) },	/* Scanjet 4570c */
-	{ USB_DEVICE(0x03f0, 0x1411) }, /* PSC 750 */
+	//	{ USB_DEVICE(0x03f0, 0x1411) }, /* PSC 750 - NOT SUPPORTED - use hpoj userspace driver */
 	{ USB_DEVICE(0x03f0, 0x2005) },	/* ScanJet 3570c */
 	{ USB_DEVICE(0x03f0, 0x2205) },	/* ScanJet 3500c */
-	{ USB_DEVICE(0x03f0, 0x2f11) }, /* PSC 1210 */
+	//	{ USB_DEVICE(0x03f0, 0x2f11) }, /* PSC 1210 - NOT SUPPORTED - use hpoj userspace driver */
 	/* Lexmark */
 	{ USB_DEVICE(0x043d, 0x002d) }, /* X70/X73 */
 	{ USB_DEVICE(0x043d, 0x003d) }, /* X83 */
@@ -187,6 +192,7 @@
 	{ USB_DEVICE(0x05da, 0x30ce) },	/* ScanMaker 3800 */
 	{ USB_DEVICE(0x05da, 0x30cf) },	/* ScanMaker 4800 */
 	{ USB_DEVICE(0x05da, 0x30d4) },	/* ScanMaker 3830 + 3840 */
+	{ USB_DEVICE(0x05da, 0x30d8) },	/* ScanMaker 5900 */
 	{ USB_DEVICE(0x04a7, 0x0224) },	/* Scanport 3000 (actually Visioneer?)*/
 	/* The following SCSI-over-USB Microtek devices are supported by the
 	   microtek driver: Enable SCSI and USB Microtek in kernel config */
@@ -245,6 +251,7 @@
 	{ USB_DEVICE(0x07b3, 0x0400) }, /* OpticPro 1248U */
 	{ USB_DEVICE(0x07b3, 0x0401) }, /* OpticPro 1248U (another one) */
 	{ USB_DEVICE(0x07b3, 0x0403) },	/* U16B */
+	{ USB_DEVICE(0x07b3, 0x0413) },	/* OpticSlim 1200 */
 	/* Primax/Colorado */
 	{ USB_DEVICE(0x0461, 0x0300) },	/* G2-300 #1 */
 	{ USB_DEVICE(0x0461, 0x0301) },	/* G2E-300 #1 */
@@ -261,6 +268,8 @@
 	{ USB_DEVICE(0x0461, 0x0383) },	/* G2E-600 */
 	/* Prolink */
 	{ USB_DEVICE(0x06dc, 0x0014) }, /* Winscan Pro 2448U */
+	/* Reflecta  */
+	{ USB_DEVICE(0x05e3, 0x0120) },	/* iScan 1800 */
 	/* Relisis */
 	// { USB_DEVICE(0x0475, 0x0103) },	/* Episode - undetected endpoint */
 	{ USB_DEVICE(0x0475, 0x0210) }, /* Scorpio Ultra 3 */
@@ -285,6 +294,7 @@
 	{ USB_DEVICE(0x04b8, 0x011c) }, /* Perfection 3200 */
 	{ USB_DEVICE(0x04b8, 0x011d) }, /* Perfection 1260 */
 	{ USB_DEVICE(0x04b8, 0x011e) }, /* Perfection 1660 Photo */
+	{ USB_DEVICE(0x04b8, 0x011f) },	/* Perfection 1670 */
 	{ USB_DEVICE(0x04b8, 0x0801) }, /* Stylus CX5200 */
 	{ USB_DEVICE(0x04b8, 0x0802) }, /* Stylus CX3200 */
 	/* Siemens */
@@ -309,6 +319,7 @@
 	{ USB_DEVICE(0x04a7, 0x0221) },	/* OneTouch 5300 USB */
 	{ USB_DEVICE(0x04a7, 0x0224) },	/* OneTouch 4800 USB */
 	{ USB_DEVICE(0x04a7, 0x0226) },	/* OneTouch 5800 USB */
+	{ USB_DEVICE(0x04a7, 0x0229) }, /* OneTouch 7100 USB */
 	{ USB_DEVICE(0x04a7, 0x022c) },	/* OneTouch 9020 USB */
 	{ USB_DEVICE(0x04a7, 0x0231) },	/* 6100 USB */
 	{ USB_DEVICE(0x04a7, 0x0311) },	/* 6200 EPP/USB */
diff -Nru a/drivers/usb/media/Kconfig b/drivers/usb/media/Kconfig
--- a/drivers/usb/media/Kconfig	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/media/Kconfig	Fri Dec 12 15:06:57 2003
@@ -113,16 +113,17 @@
           webcams:
            * Philips PCA645, PCA646
            * Philips PCVC675, PCVC680, PCVC690
-           * Philips PCVC730, PCVC740, PCVC750
+           * Philips PCVC720/40, PCVC730, PCVC740, PCVC750
 	   * Askey VC010
-	   * Logitech QuickCam Pro 3000, 4000, 'Zoom' and 'Notebook'
-	   * Samsung MPC-C10, MPC-C30
-	   * Creative Webcam 5
-	   * SOTECT Afina Eye
+	   * Logitech QuickCam Pro 3000, 4000, 'Zoom', 'Notebook Pro' 
+             and 'Orbit'/'Sphere'
+           * Samsung MPC-C10, MPC-C30
+	   * Creative Webcam 5, Pro Ex
+	   * SOTEC Afina Eye
 	   * Visionite VCS-UC300, VCS-UM100
 	   
-	  The PCA635, PCVC665 and PCVC720 are not supported by this driver
-	  and never will be, but the 665 and 720 are supported by other 
+	  The PCA635, PCVC665 and PCVC720/20 are not supported by this driver
+	  and never will be, but the 665 and 720/20 are supported by other 
 	  drivers.
 
 	  This driver has an optional plugin (called PWCX), which is 
@@ -177,3 +178,30 @@
 	  To compile this driver as a module, choose M here: the
 	  module will be called stv680.
 
+config USB_W9968CF
+	tristate "USB W996[87]CF JPEG Dual Mode Camera support"
+	depends on USB && VIDEO_DEV && I2C
+	---help---
+	  Say Y here if you want support for cameras based on
+	  Winbond W9967CF/W9968CF JPEG USB Dual Mode Camera Chips.
+	
+	  This driver has an optional plugin, which is distributed as a
+	  separate module only (released under GPL). It contains code that 
+	  allows you to use higher resolutions and framerates, and can't
+	  be included into the official Linux kernel for performance 
+	  purposes.
+	  At the moment the driver needs a third-part module for the CMOS 
+	  sensors, which is available on internet: it is recommended to read
+	  <file:Documentation/usb/w9968cf.txt> for more informations and for
+	  a list of supported cameras.
+	
+	  This driver uses the Video For Linux and the I2C APIs. 
+	  You must say Y or M to both "Video For Linux" and 
+	  "I2C Support" to use this driver.
+	  Information on this API and pointers to "v4l" programs may be found
+	  on the WWW at <http://roadrunner.swansea.uk.linux.org/v4l.shtml>.
+	
+	  This code is also available as a module ( = code which can be
+	  inserted in and removed from the running kernel whenever you want).
+	  The module will be called w9968cf.o. If you want to compile it as a
+	  module, say M here and read <file:Documentation/modules.txt>.
diff -Nru a/drivers/usb/media/Makefile b/drivers/usb/media/Makefile
--- a/drivers/usb/media/Makefile	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/media/Makefile	Fri Dec 12 15:06:57 2003
@@ -13,3 +13,4 @@
 obj-$(CONFIG_USB_SE401)		+= se401.o
 obj-$(CONFIG_USB_STV680)	+= stv680.o
 obj-$(CONFIG_USB_VICAM)		+= vicam.o usbvideo.o
+obj-$(CONFIG_USB_W9968CF)	+= w9968cf.o
diff -Nru a/drivers/usb/media/pwc-ctrl.c b/drivers/usb/media/pwc-ctrl.c
--- a/drivers/usb/media/pwc-ctrl.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/media/pwc-ctrl.c	Fri Dec 12 15:06:58 2003
@@ -44,6 +44,8 @@
 #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
@@ -88,6 +90,11 @@
 /* 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 char *size2name[PSZ_MAX] =
 {
 	"subQCIF",
@@ -435,6 +442,7 @@
 		ret = set_video_mode_Timon(pdev, size, frames, compression, snapshot);
 		break;
 		
+	case 720:
 	case 730:
 	case 740:
 	case 750:
@@ -745,6 +753,7 @@
 			buf[1] = speed >> 8;
 			buf[0] = speed & 0xff;
 			break;
+		case 720:
 		case 730:
 		case 740:
 		case 750:
@@ -1243,6 +1252,46 @@
 	return buf;
 }
 
+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 devide 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)
 {
@@ -1512,8 +1561,128 @@
 		size->width = pdev->image.x;
 		size->height = pdev->image.y;
 		break;
-	}	
-
+ 	}
+ 	
+ 	case VIDIOCPWCMPTRESET:
+ 	{
+ 		int *flags = arg;
+ 		
+ 		if (pdev->features & FEATURE_MOTOR_PANTILT)
+ 		{
+			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)
+ 		{
+ 			memcpy(arg, &pdev->angle_range, sizeof(struct pwc_mpt_range));
+ 		}
+ 		else
+ 		{	
+ 			ret = -ENXIO;
+ 		}
+ 		break;
+ 	}
+ 	
+ 	case VIDIOCPWCMPTSANGLE:
+ 	{
+ 		struct pwc_mpt_angles *angles = arg;
+ 		int new_pan, new_tilt;
+ 		
+ 		if (pdev->features & FEATURE_MOTOR_PANTILT)
+ 		{
+			/* 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:
+ 	{
+ 		struct pwc_mpt_angles *angles = arg;
+ 		
+ 		if (pdev->features & FEATURE_MOTOR_PANTILT)
+ 		{
+ 			angles->absolute = 1;
+ 			angles->pan  = pdev->pan_angle;
+ 			angles->tilt = pdev->tilt_angle;
+ 		}
+ 		else
+ 		{
+ 			ret = -ENXIO;
+ 		}
+ 		break;
+ 	}
+ 
+ 	case VIDIOCPWCMPTSTATUS:
+ 	{
+ 		struct pwc_mpt_status *status = arg;
+ 	
+ 		if (pdev->features & FEATURE_MOTOR_PANTILT)
+ 		{
+ 			ret = pwc_mpt_get_status(pdev, status);
+ 		}
+ 		else
+ 		{
+ 			ret = -ENXIO;
+ 		}
+  		break;
+  	}
+  	
 	default:
 		ret = -ENOIOCTLCMD;
 		break;
diff -Nru a/drivers/usb/media/pwc-if.c b/drivers/usb/media/pwc-if.c
--- a/drivers/usb/media/pwc-if.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/media/pwc-if.c	Fri Dec 12 15:06:58 2003
@@ -79,9 +79,9 @@
 	{ 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 */
-	{ USB_DEVICE(0x046D, 0x08B4) }, /* Logitech (reserved) */
-	{ USB_DEVICE(0x046D, 0x08B5) }, /* Logitech (reserved) */
+	{ 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) */
@@ -129,6 +129,7 @@
 
 static int pwc_video_open(struct inode *inode, struct file *file);
 static int pwc_video_close(struct inode *inode, struct file *file);
+static int pwc_video_release(struct video_device *);			  
 static ssize_t pwc_video_read(struct file *file, char *buf,
 			  size_t count, loff_t *ppos);
 static unsigned int pwc_video_poll(struct file *file, poll_table *wait);
@@ -918,21 +919,32 @@
 
 int pwc_try_video_mode(struct pwc_device *pdev, int width, int height, int new_fps, int new_compression, int new_snapshot)
 {
-	int ret;
+	int ret, start;
 
 	/* 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);
-	if (!ret)
-		if (pwc_isoc_init(pdev) < 0)
-			Info("Failed to restart ISOC transfer in pwc_try_video_mode.\n");
+	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 ret; /* Return original error code */
 }
 
 
@@ -997,6 +1009,7 @@
 #if PWC_DEBUG	
 	Debug("Found decompressor for %d at 0x%p\n", pdev->type, pdev->decompressor);
 #endif
+	pwc_construct(pdev); /* set min/max sizes correct */
 
 	/* So far, so good. Allocate memory. */
 	i = pwc_allocate_buffers(pdev);
@@ -1018,6 +1031,7 @@
 #if PWC_DEBUG
 	pdev->sequence = 0;
 #endif
+	pwc_construct(pdev); /* set min/max sizes correct */
 
 	/* Set some defaults */
 	pdev->vsnapshot = 0;
@@ -1104,6 +1118,12 @@
 	return 0;
 }
 
+static int pwc_video_release(struct video_device *vfd)
+{
+	Trace(TRACE_OPEN, "pwc_video_release() called. Now what?\n");
+}
+		
+
 /*
  *	FIXME: what about two parallel reads ????
  *      ANSWER: Not supported. You can't open the device more than once,
@@ -1124,7 +1144,7 @@
 	int noblock = file->f_flags & O_NONBLOCK;
 	DECLARE_WAITQUEUE(wait, current);
 
-	Trace(TRACE_READ, "video_read(0x%p, %p, %Zd) called.\n", vdev, buf, count);
+	Trace(TRACE_READ, "video_read(0x%p, %p, %d) called.\n", vdev, buf, count);
 	if (vdev == NULL)
 		return -EFAULT;
 	pdev = vdev->priv;
@@ -1568,6 +1588,7 @@
 	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;
 
@@ -1677,8 +1698,17 @@
 			name = "Logitech QuickCam Zoom";
 			type_id = 740; /* CCD sensor */
 			break;
-		case 0x08b4:
+		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:
@@ -1776,9 +1806,22 @@
 	}
 	memset(pdev, 0, sizeof(struct pwc_device));
 	pdev->type = type_id;
-	pwc_construct(pdev);
 	pdev->vsize = default_size;
 	pdev->vframes = default_fps;
+	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;
+		pdev->angle_range.zoom_min = -1;
+		pdev->angle_range.zoom_max = -1;
+	}
 
 	init_MUTEX(&pdev->modlock);
 	pdev->ptrlock = SPIN_LOCK_UNLOCKED;
@@ -1791,7 +1834,7 @@
 	strcpy(pdev->vdev.name, name);
 	pdev->vdev.owner = THIS_MODULE;
 	pdev->vdev.priv = pdev;
-
+	
 	pdev->release = udev->descriptor.bcdDevice;
 	Trace(TRACE_PROBE, "Release: %04x\n", pdev->release);
 
@@ -1809,6 +1852,7 @@
 		}
 	}
 
+	pdev->vdev.release = pwc_video_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);
@@ -1818,6 +1862,7 @@
 	else {
 		Info("Registered as /dev/video%d.\n", pdev->vdev.minor & 0x3F);
 	}
+
 	/* occupy slot */
 	if (hint < MAX_DEV_HINTS) 
 		device_hint[hint].pdev = pdev;
diff -Nru a/drivers/usb/media/pwc-ioctl.h b/drivers/usb/media/pwc-ioctl.h
--- a/drivers/usb/media/pwc-ioctl.h	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/media/pwc-ioctl.h	Fri Dec 12 15:06:57 2003
@@ -112,6 +112,43 @@
 	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 = 1, 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 */
+	int zoom;		/* N/A, set to -1 */
+};
+
+/* Range of angles of the camera, both horizontally and vertically.
+   The zoom is not used, maybe in the future...
+
+ */
+struct pwc_mpt_range
+{
+	int pan_min, pan_max;		/* degrees * 100 */
+	int tilt_min, tilt_max;
+	int zoom_min, zoom_max;		/* -1, -1 */
+};
+
+struct pwc_mpt_status
+{
+	int status;
+	int time_pan;
+	int time_tilt;
+};
+
+
  /* Restore user settings */
 #define VIDIOCPWCRUSER		_IO('v', 192)
  /* Save user settings */
@@ -181,5 +218,12 @@
 
  /* 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)
  
 #endif
diff -Nru a/drivers/usb/media/pwc-misc.c b/drivers/usb/media/pwc-misc.c
--- a/drivers/usb/media/pwc-misc.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/media/pwc-misc.c	Fri Dec 12 15:06:57 2003
@@ -52,7 +52,7 @@
 	return find;
 }
 
-/* initialize variables depending on type */
+/* initialize variables depending on type and decompressor*/
 void pwc_construct(struct pwc_device *pdev)
 {
 	switch(pdev->type) {
@@ -73,9 +73,17 @@
 	case 690:
 		pdev->view_min.x = 128;
 		pdev->view_min.y =  96;
-		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;
+		/* Anthill bug #38: PWC always reports max size, even without PWCX */
+		if (pdev->decompressor != NULL) {
+			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;
+		}
+		else {
+			pdev->view_max.x = 352;
+			pdev->view_max.y = 288;
+			pdev->image_mask = 1 << PSZ_SQCIF | 1 << PSZ_QSIF | 1 << PSZ_QCIF | 1 << PSZ_SIF | 1 << PSZ_CIF;
+		}
 		pdev->vcinterface = 3;
 		pdev->vendpoint = 4;
 		pdev->frame_header_size = 0;
@@ -87,9 +95,18 @@
 	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;
+		/* Anthill bug #38: PWC always reports max size, even without PWCX */
+		if (pdev->decompressor != NULL) {
+			pdev->view_max.x = 640;
+			pdev->view_max.y = 480;
+			pdev->image_mask = 1 << PSZ_QSIF | 1 << PSZ_SIF | 1 << PSZ_VGA;
+		}
+		else {
+			/* Tell CIF, even though SIF really is the maximum, but some tools really need CIF */
+			pdev->view_max.x = 352;
+			pdev->view_max.y = 288;
+			pdev->image_mask = 1 << PSZ_QSIF | 1 << PSZ_SIF;
+		}
 		pdev->vcinterface = 3;
 		pdev->vendpoint = 5;
 		pdev->frame_header_size = TOUCAM_HEADER_SIZE;
diff -Nru a/drivers/usb/media/pwc.h b/drivers/usb/media/pwc.h
--- a/drivers/usb/media/pwc.h	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/media/pwc.h	Fri Dec 12 15:06:58 2003
@@ -18,17 +18,21 @@
 #ifndef PWC_H
 #define PWC_H
 
+#include <linux/version.h>
+
 #include <linux/config.h>
 #include <linux/module.h>
-#include <linux/usb.h>
+#include <linux/smp_lock.h>
 #include <linux/spinlock.h>
+#include <linux/usb.h>
 #include <linux/videodev.h>
 #include <linux/wait.h>
-#include <linux/smp_lock.h>
 
 #include <asm/semaphore.h>
 #include <asm/errno.h>
 
+#include "pwc-ioctl.h"
+
 /* Defines and structures for the Philips webcam */
 /* Used for checking memory corruption/pointer validation */
 #define PWC_MAGIC 0x89DC10ABUL
@@ -58,10 +62,12 @@
 #define TOUCAM_HEADER_SIZE		8
 #define TOUCAM_TRAILER_SIZE		4
 
+#define FEATURE_MOTOR_PANTILT		0x0001
+
 /* Version block */
 #define PWC_MAJOR	8
-#define PWC_MINOR	11
-#define PWC_VERSION 	"8.11"
+#define PWC_MINOR	12
+#define PWC_VERSION 	"8.12"
 #define PWC_NAME 	"pwc"
 
 /* Turn certain features on/off */
@@ -119,8 +125,9 @@
    /* Pointer to our usb_device */
    struct usb_device *udev;
    
-   int type;                    /* type of cam (645, 646, 675, 680, 690) */
+   int type;                    /* type of cam (645, 646, 675, 680, 690, 720, 730, 740, 750) */
    int release;			/* release number */
+   int features;		/* feature bits */
    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 */
 
@@ -193,6 +200,11 @@
 
    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... */
diff -Nru a/drivers/usb/media/w9968cf.c b/drivers/usb/media/w9968cf.c
--- /dev/null	Wed Dec 31 16:00:00 1969
+++ b/drivers/usb/media/w9968cf.c	Fri Dec 12 15:06:58 2003
@@ -0,0 +1,3712 @@
+/***************************************************************************
+ * Video4Linux driver for W996[87]CF JPEG USB Dual Mode Camera Chip.       *
+ *                                                                         *
+ * Copyright (C) 2002 2003 by Luca Risolia <luca_ing@libero.it>            *
+ *                                                                         *
+ * - Memory management code from bttv driver by Ralph Metzler,             *
+ *   Marcus Metzler and Gerd Knorr.                                        *
+ * - I2C interface to kernel, high-level CMOS sensor control routines and  *
+ *   some symbolic names from OV511 driver by Mark W. McClelland.          *
+ * - Low-level I2C fast write function by Piotr Czerczak.                  *
+ * - Low-level I2C read function by Frédéric Jouault.                      *
+ *                                                                         *
+ * This program is free software; you can redistribute it and/or modify    *
+ * it under the terms of the GNU General Public License as published by    *
+ * the Free Software Foundation; either version 2 of the License, or       *
+ * (at your option) any later version.                                     *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful,         *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License       *
+ * along with this program; if not, write to the Free Software             *
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.               *
+ ***************************************************************************/
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/ioctl.h>
+#include <linux/delay.h>
+#include <asm/page.h>
+#include <asm/uaccess.h>
+
+#include "w9968cf.h"
+#include "w9968cf_decoder.h"
+
+
+
+/****************************************************************************
+ * Modules paramaters                                                       *
+ ****************************************************************************/
+
+static u8 vppmod_load = W9968CF_VPPMOD_LOAD;
+static u8 simcams = W9968CF_SIMCAMS;
+static int video_nr[]={[0 ... W9968CF_MAX_DEVICES-1] = -1}; /* -1=first free */
+static u16 packet_size[] = {[0 ... W9968CF_MAX_DEVICES-1]=W9968CF_PACKET_SIZE};
+static u8 max_buffers[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_BUFFERS};
+static u8 double_buffer[] = {[0 ... W9968CF_MAX_DEVICES-1] = 
+                             W9968CF_DOUBLE_BUFFER};
+static u8 clamping[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_CLAMPING};
+static u8 filter_type[]= {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_FILTER_TYPE};
+static u8 largeview[]= {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_LARGEVIEW};
+static u8 decompression[] = {[0 ... W9968CF_MAX_DEVICES-1] = 
+                             W9968CF_DECOMPRESSION};
+static u8 upscaling[]= {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_UPSCALING};
+static u8 force_palette[] = {[0 ... W9968CF_MAX_DEVICES-1] = 0};
+static u8 force_rgb[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_FORCE_RGB};
+static u8 autobright[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_AUTOBRIGHT};
+static u8 autoexp[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_AUTOEXP};
+static u8 lightfreq[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_LIGHTFREQ};
+static u8 bandingfilter[] = {[0 ... W9968CF_MAX_DEVICES-1]=
+                             W9968CF_BANDINGFILTER};
+static int clockdiv[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_CLOCKDIV};
+static u8 backlight[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_BACKLIGHT};
+static u8 mirror[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_MIRROR};
+static u8 sensor_mono[] = {[0 ... W9968CF_MAX_DEVICES-1]=W9968CF_SENSOR_MONO};
+static u16 brightness[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_BRIGHTNESS};
+static u16 hue[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_HUE};
+static u16 colour[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_COLOUR};
+static u16 contrast[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_CONTRAST};
+static u16 whiteness[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_WHITENESS};
+#ifdef W9968CF_DEBUG
+static u8 debug = W9968CF_DEBUG_LEVEL;
+static u8 specific_debug = W9968CF_SPECIFIC_DEBUG;
+#endif
+
+MODULE_AUTHOR("Luca Risolia <luca_ing@libero.it>");
+
+MODULE_DESCRIPTION("Video4Linux driver for "
+                   "W996[87]CF JPEG USB Dual Mode Camera Chip");
+
+MODULE_SUPPORTED_DEVICE("Video");
+
+MODULE_LICENSE("GPL");
+
+MODULE_PARM(vppmod_load, "i");
+MODULE_PARM(simcams, "i");
+MODULE_PARM(video_nr, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i");
+MODULE_PARM(packet_size, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i");
+MODULE_PARM(max_buffers, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i");
+MODULE_PARM(double_buffer, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i");
+MODULE_PARM(clamping, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i");
+MODULE_PARM(filter_type, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i");
+MODULE_PARM(largeview, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i");
+MODULE_PARM(decompression, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i");
+MODULE_PARM(upscaling, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i");
+MODULE_PARM(force_palette, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i");
+MODULE_PARM(force_rgb, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "i");
+MODULE_PARM(autobright, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l");
+MODULE_PARM(autoexp, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l");
+MODULE_PARM(lightfreq, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l");
+MODULE_PARM(bandingfilter, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l");
+MODULE_PARM(clockdiv, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l");
+MODULE_PARM(backlight, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l");
+MODULE_PARM(mirror, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l");
+MODULE_PARM(sensor_mono, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l");
+MODULE_PARM(brightness, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l");
+MODULE_PARM(hue, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l");
+MODULE_PARM(colour, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l");
+MODULE_PARM(contrast, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l");
+MODULE_PARM(whiteness, "0-" __MODULE_STRING(W9968CF_MAX_DEVICES) "l");
+#ifdef W9968CF_DEBUG
+MODULE_PARM(debug, "i");
+MODULE_PARM(specific_debug, "i");
+#endif
+
+MODULE_PARM_DESC(vppmod_load, 
+                 "\n<0|1> Automatic 'w9968cf-vpp' module loading."
+                 "\n0 disable, 1 enable."
+                 "\nIf enabled, every time an application attempts to open a"
+                 "\ncamera, 'insmod' searches for the video post-processing"
+                 "\nmodule in the system and loads it automatically (if"
+                 "\npresent). The 'w9968cf-vpp' module adds extra image"
+                 "\nmanipulation functions to the 'w9968cf' module, like"
+                 "\nsoftware up-scaling,colour conversions and video decoding."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_VPPMOD_LOAD)"."
+                 "\n");
+MODULE_PARM_DESC(simcams, 
+                 "\n<n> Number of cameras allowed to stream simultaneously."
+                 "\nn may vary from 0 to "
+                 __MODULE_STRING(W9968CF_MAX_DEVICES)"."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_SIMCAMS)"."
+                 "\n");
+MODULE_PARM_DESC(video_nr,
+                 "\n<-1|n[,...]> Specify V4L minor mode number."
+                 "\n -1 = use next available (default)"
+                 "\n  n = use minor number n (integer >= 0)"
+                 "\nYou can specify " __MODULE_STRING(W9968CF_MAX_DEVICES)
+                 " cameras this way."
+                 "\nFor example:"
+                 "\nvideo_nr=-1,2,-1 would assign minor number 2 to"
+                 "\nthe second camera and use auto for the first"
+                 "\none and for every other camera."
+                 "\n");
+MODULE_PARM_DESC(packet_size,
+                 "\n<n[,...]> Specify the maximum data payload"
+                 "\nsize in bytes for alternate settings, for each device."
+                 "\nn is scaled between 63 and 1023 "
+                 "(default is "__MODULE_STRING(W9968CF_PACKET_SIZE)")."
+                 "\n");
+MODULE_PARM_DESC(max_buffers,
+                 "\n<n[,...]> Only for advanced users."
+                 "\nSpecify the maximum number of video frame buffers"
+                 "\nto allocate for each device, from 2 to "
+                 __MODULE_STRING(W9968CF_MAX_BUFFERS)
+                 ". (default is "__MODULE_STRING(W9968CF_BUFFERS)")."
+                 "\n");
+MODULE_PARM_DESC(double_buffer, 
+                 "\n<0|1[,...]> "
+                 "Hardware double buffering: 0 disabled, 1 enabled."
+                 "\nIt should be enabled if you want smooth video output: if"
+                 "\nyou obtain out of sync. video, disable it at all, or try"
+                 "\nto decrease the 'clockdiv' module paramater value."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_DOUBLE_BUFFER)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(clamping, 
+                 "\n<0|1[,...]> Video data clamping: 0 disabled, 1 enabled."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_CLAMPING)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(filter_type, 
+                 "\n<0|1|2[,...]> Video filter type."
+                 "\n0 none, 1 (1-2-1) 3-tap filter, "
+                 "2 (2-3-6-3-2) 5-tap filter."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_FILTER_TYPE)
+                 " for every device."
+                 "\nThe filter is used to reduce noise and aliasing artifacts"
+                 "\nproduced by the CCD or CMOS sensor, and the scaling"
+                 " process."
+                 "\n");
+MODULE_PARM_DESC(largeview, 
+                 "\n<0|1[,...]> Large view: 0 disabled, 1 enabled."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_LARGEVIEW)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(upscaling, 
+                 "\n<0|1[,...]> Software scaling (for non-compressed video):"
+                 "\n0 disabled, 1 enabled."
+                 "\nDisable it if you have a slow CPU or you don't have"
+                 " enough memory."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_UPSCALING)
+                 " for every device."
+                 "\nIf 'w9968cf-vpp' is not loaded, this paramater is"
+                 " set to 0.");
+MODULE_PARM_DESC(decompression,
+                 "\n<0|1|2[,...]> Software video decompression:"
+                 "\n- 0 disables decompression (doesn't allow formats needing"
+                 " decompression)"
+                 "\n- 1 forces decompression (allows formats needing"
+                 " decompression only);"
+                 "\n- 2 allows any permitted formats."
+                 "\nFormats supporting compressed video are YUV422P and"
+                 " YUV420P/YUV420 "
+                 "\nin any resolutions where both width and height are "
+                 "a multiple of 16."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_DECOMPRESSION)
+                 " for every device."
+                 "\nIf 'w9968cf-vpp' is not loaded, forcing decompression is "
+                 "\nnot allowed; in this case this paramater is set to 2.");
+MODULE_PARM_DESC(force_palette,
+                 "\n<0"
+                 "|" __MODULE_STRING(VIDEO_PALETTE_UYVY)
+                 "|" __MODULE_STRING(VIDEO_PALETTE_YUV420)
+                 "|" __MODULE_STRING(VIDEO_PALETTE_YUV422P)
+                 "|" __MODULE_STRING(VIDEO_PALETTE_YUV420P)
+                 "|" __MODULE_STRING(VIDEO_PALETTE_YUYV)
+                 "|" __MODULE_STRING(VIDEO_PALETTE_YUV422)
+                 "|" __MODULE_STRING(VIDEO_PALETTE_GREY)
+                 "|" __MODULE_STRING(VIDEO_PALETTE_RGB555)
+                 "|" __MODULE_STRING(VIDEO_PALETTE_RGB565)
+                 "|" __MODULE_STRING(VIDEO_PALETTE_RGB24)
+                 "|" __MODULE_STRING(VIDEO_PALETTE_RGB32)
+                 "[,...]>"
+                 " Force picture palette."
+                 "\nIn order:"
+                 "\n- 0 allows any of the following formats:"
+                 "\n- UYVY    16 bpp - Original video, compression disabled"
+                 "\n- YUV420  12 bpp - Original video, compression enabled"
+                 "\n- YUV422P 16 bpp - Original video, compression enabled"
+                 "\n- YUV420P 12 bpp - Original video, compression enabled"
+                 "\n- YUVY    16 bpp - Software conversion from UYVY"
+                 "\n- YUV422  16 bpp - Software conversion from UYVY"
+                 "\n- GREY     8 bpp - Software conversion from UYVY"
+                 "\n- RGB555  16 bpp - Software conversion from UYVY"
+                 "\n- RGB565  16 bpp - Software conversion from UYVY"
+                 "\n- RGB24   24 bpp - Software conversion from UYVY"
+                 "\n- RGB32   32 bpp - Software conversion from UYVY"
+                 "\nWhen not 0, this paramater will override 'decompression'."
+                 "\nDefault value is 0 for every device."
+                 "\nInitial palette is "
+                 __MODULE_STRING(W9968CF_PALETTE_DECOMP_ON)"."
+                 "\nIf 'w9968cf-vpp' is not loaded, this paramater is"
+                 " set to 9 (UYVY).");
+MODULE_PARM_DESC(force_rgb, 
+                 "\n<0|1[,...]> Read RGB video data instead of BGR:"
+                 "\n 1 = use RGB component ordering."
+                 "\n 0 = use BGR component ordering."
+                 "\nThis parameter has effect when using RGBX palettes only."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_FORCE_RGB)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(autobright,
+                 "\n<0|1[,...]> CMOS sensor automatically changes brightness:"
+                 "\n 0 = no, 1 = yes"
+                 "\nDefault value is "__MODULE_STRING(W9968CF_AUTOBRIGHT)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(autoexp,
+                 "\n<0|1[,...]> CMOS sensor automatically changes exposure:"
+                 "\n 0 = no, 1 = yes"
+                 "\nDefault value is "__MODULE_STRING(W9968CF_AUTOEXP)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(lightfreq,
+                 "\n<50|60[,...]> Light frequency in Hz:"
+                 "\n 50 for European and Asian lighting,"
+                 " 60 for American lighting."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_LIGHTFREQ)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(bandingfilter,
+                 "\n<0|1[,...]> Banding filter to reduce effects of"
+                 " fluorescent lighting:"
+                 "\n 0 disabled, 1 enabled."
+                 "\nThis filter tries to reduce the pattern of horizontal"
+                 "\nlight/dark bands caused by some (usually fluorescent)"
+                 " lighting."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_BANDINGFILTER)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(clockdiv,
+                 "\n<-1|n[,...]> "
+                 "Force pixel clock divisor to a specific value (for experts):"
+                 "\n  n may vary from 0 to 127."
+                 "\n -1 for automatic value."
+                 "\nSee also the 'double_buffer' module paramater."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_CLOCKDIV)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(backlight,
+                 "\n<0|1[,...]> Objects are lit from behind:"
+                 "\n 0 = no, 1 = yes"
+                 "\nDefault value is "__MODULE_STRING(W9968CF_BACKLIGHT)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(mirror,
+                 "\n<0|1[,...]> Reverse image horizontally:"
+                 "\n 0 = no, 1 = yes"
+                 "\nDefault value is "__MODULE_STRING(W9968CF_MIRROR)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(sensor_mono,
+                 "\n<0|1[,...]> The OV CMOS sensor is monochrome:"
+                 "\n 0 = no, 1 = yes"
+                 "\nDefault value is "__MODULE_STRING(W9968CF_SENSOR_MONO)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(brightness, 
+                 "\n<n[,...]> Set picture brightness (0-65535)."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_BRIGHTNESS)
+                 " for every device."
+                 "\nThis parameter has no effect if 'autobright' is enabled."
+                 "\n");
+MODULE_PARM_DESC(hue, 
+                 "\n<n[,...]> Set picture hue (0-65535)."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_HUE)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(colour, 
+                 "\n<n[,...]> Set picture saturation (0-65535)."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_COLOUR)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(contrast, 
+                 "\n<n[,...]> Set picture contrast (0-65535)."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_CONTRAST)
+                 " for every device."
+                 "\n");
+MODULE_PARM_DESC(whiteness, 
+                 "\n<n[,...]> Set picture whiteness (0-65535)."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_WHITENESS)
+                 " for every device."
+                 "\n");
+#ifdef W9968CF_DEBUG
+MODULE_PARM_DESC(debug,
+                 "\n<n> Debugging information level, from 0 to 6:"
+                 "\n0 = none (be cautious)"
+                 "\n1 = critical errors"
+                 "\n2 = significant informations"
+                 "\n3 = configuration or general messages"
+                 "\n4 = warnings"
+                 "\n5 = called functions"
+                 "\n6 = function internals"
+                 "\nLevel 5 and 6 are useful for testing only, when just "
+                 "one device is used."
+                 "\nDefault value is "__MODULE_STRING(W9968CF_DEBUG_LEVEL)"."
+                 "\n");
+MODULE_PARM_DESC(specific_debug,
+                 "\n<0|1> Enable or disable specific debugging messages:"
+                 "\n0 = print messages concerning every level"
+                 " <= 'debug' level."
+                 "\n1 = print messages concerning the level"
+                 " indicated by 'debug'."
+                 "\nDefault value is "
+                 __MODULE_STRING(W9968CF_SPECIFIC_DEBUG)"."
+                 "\n");
+#endif /* W9968CF_DEBUG */
+
+
+
+/****************************************************************************
+ * Some prototypes                                                          *
+ ****************************************************************************/
+
+/* Video4linux interface */
+static struct file_operations w9968cf_fops;
+static int w9968cf_open(struct inode*, struct file*);
+static int w9968cf_release(struct inode*, struct file*);
+static ssize_t w9968cf_read(struct file*, char*, size_t, loff_t*);
+static int w9968cf_mmap(struct file*, struct vm_area_struct*);
+static int w9968cf_ioctl(struct inode*, struct file*,
+                         unsigned int, unsigned long);
+static int w9968cf_do_ioctl(struct w9968cf_device*, unsigned int, void*);
+
+/* USB-specific */
+static void w9968cf_urb_complete(struct urb *urb, struct pt_regs *regs);
+static int w9968cf_start_transfer(struct w9968cf_device*);
+static int w9968cf_stop_transfer(struct w9968cf_device*);
+static int w9968cf_write_reg(struct w9968cf_device*, u16 value, u16 index);
+static int w9968cf_read_reg(struct w9968cf_device*, u16 index);
+static int w9968cf_write_fsb(struct w9968cf_device*, u16* data);
+static int w9968cf_write_sb(struct w9968cf_device*, u16 value);
+static int w9968cf_read_sb(struct w9968cf_device*);
+static int w9968cf_upload_quantizationtables(struct w9968cf_device*);
+
+/* Low-level I2C (SMBus) I/O */
+static int w9968cf_smbus_start(struct w9968cf_device*);
+static int w9968cf_smbus_stop(struct w9968cf_device*);
+static int w9968cf_smbus_write_byte(struct w9968cf_device*, u8 v);
+static int w9968cf_smbus_read_byte(struct w9968cf_device*, u8* v);
+static int w9968cf_smbus_write_ack(struct w9968cf_device*);
+static int w9968cf_smbus_read_ack(struct w9968cf_device*);
+static int w9968cf_i2c_adap_read_byte(struct w9968cf_device* cam,
+                                      u16 address, u8* value);
+static int w9968cf_i2c_adap_read_byte_data(struct w9968cf_device*, u16 address, 
+                                           u8 subaddress, u8* value);
+static int w9968cf_i2c_adap_write_byte(struct w9968cf_device*,
+                                       u16 address, u8 subaddress);
+static int w9968cf_i2c_adap_fastwrite_byte_data(struct w9968cf_device*,
+                                                u16 address, u8 subaddress,
+                                                u8 value);
+
+/* I2C interface to kernel */
+static int w9968cf_i2c_init(struct w9968cf_device*);
+static int w9968cf_i2c_smbus_xfer(struct i2c_adapter*, u16 addr, 
+                                  unsigned short flags, char read_write, 
+                                  u8 command, int size, union i2c_smbus_data*);
+static u32 w9968cf_i2c_func(struct i2c_adapter*);
+static int w9968cf_i2c_attach_inform(struct i2c_client*);
+static int w9968cf_i2c_detach_inform(struct i2c_client*);
+static int w9968cf_i2c_control(struct i2c_adapter*, unsigned int cmd,
+                               unsigned long arg);
+
+/* Memory management */
+static inline unsigned long kvirt_to_pa(unsigned long adr);
+static void* rvmalloc(unsigned long size);
+static void rvfree(void *mem, unsigned long size);
+static void w9968cf_deallocate_memory(struct w9968cf_device*);
+static int  w9968cf_allocate_memory(struct w9968cf_device*);
+static inline unsigned long w9968cf_get_max_bufsize(struct w9968cf_device*);
+
+/* High-level CMOS sensor control functions */
+static int w9968cf_sensor_set_control(struct w9968cf_device*,int cid,int val);
+static int w9968cf_sensor_get_control(struct w9968cf_device*,int cid,int *val);
+static inline int w9968cf_sensor_cmd(struct w9968cf_device*, 
+                                     unsigned int cmd, void *arg);
+static void w9968cf_sensor_configure(struct w9968cf_device*);
+static int w9968cf_sensor_change_settings(struct w9968cf_device*);
+static int w9968cf_sensor_get_picture(struct w9968cf_device*, 
+                                      struct video_picture*);
+static int w9968cf_sensor_set_picture(struct w9968cf_device*, 
+                                      struct video_picture pict);
+
+/* Other helper functions */
+static void w9968cf_configure_camera(struct w9968cf_device*,struct usb_device*,
+                                     enum w9968cf_model_id, 
+                                     const unsigned short dev_nr);
+static int w9968cf_turn_on_led(struct w9968cf_device*);
+static int w9968cf_init_chip(struct w9968cf_device*);
+static int w9968cf_set_picture(struct w9968cf_device*, struct video_picture);
+static int w9968cf_set_window(struct w9968cf_device*, struct video_window);
+static inline u16 w9968cf_valid_palette(u16 palette);
+static inline u16 w9968cf_valid_depth(u16 palette);
+static inline u8 w9968cf_need_decompression(u16 palette);
+static int w9968cf_postprocess_frame(struct w9968cf_device*, 
+                                     struct w9968cf_frame_t*);
+static int w9968cf_adjust_window_size(struct w9968cf_device*, u16* w, u16* h);
+static void w9968cf_init_framelist(struct w9968cf_device*);
+static void w9968cf_push_frame(struct w9968cf_device*, u8 f_num);
+static void w9968cf_pop_frame(struct w9968cf_device*,struct w9968cf_frame_t**);
+static void w9968cf_release_resources(struct w9968cf_device*);
+
+/* Intermodule communication */
+static int w9968cf_vppmod_detect(void);
+static void w9968cf_vppmod_release(void);
+
+/* Pointers to registered video post-processing functions */
+static void (*w9968cf_vpp_init_decoder)(void);
+static int (*w9968cf_vpp_check_headers)(const unsigned char*,
+                                        const unsigned long);
+static int (*w9968cf_vpp_decode)(const char*, const unsigned, 
+                                 const unsigned, const unsigned, char*);
+static void (*w9968cf_vpp_swap_yuvbytes)(void*, unsigned long);
+static void (*w9968cf_vpp_uyvy_to_rgbx)(u8*, unsigned long, u8*, u16, u8);
+static void (*w9968cf_vpp_scale_up)(u8*, u8*, u16, u16, u16, u16, u16);
+
+
+
+/****************************************************************************
+ * Symbolic names                                                           *
+ ****************************************************************************/
+
+/* Used to represent a list of values and their respective symbolic names */
+struct w9968cf_symbolic_list {
+	const int num;
+	const char *name;
+};
+
+/*-------------------------------------------------------------------------- 
+  Returns the name of the matching element in the symbolic_list array. The
+  end of the list must be marked with an element that has a NULL name.
+  --------------------------------------------------------------------------*/
+static inline const char * 
+symbolic(struct w9968cf_symbolic_list list[], const int num)
+{
+	int i;
+
+	for (i = 0; list[i].name != NULL; i++)
+		if (list[i].num == num)
+			return (list[i].name);
+
+	return "Unknown";
+}
+
+static struct w9968cf_symbolic_list camlist[] = {
+	{ W9968CF_MOD_GENERIC, "W996[87]CF JPEG USB Dual Mode Camera" },
+	{ W9968CF_MOD_CLVBWGP, "Creative Labs Video Blaster WebCam Go Plus" },
+
+	/* Other cameras (having the same descriptors as Generic W996[87]CF) */
+	{ W9968CF_MOD_ADPA5R, "Aroma Digi Pen ADG-5000 Refurbished" },
+	{ W9986CF_MOD_AU, "AVerTV USB" },
+	{ W9968CF_MOD_CLVBWG, "Creative Labs Video Blaster WebCam Go" },
+	{ W9968CF_MOD_DLLDK, "Die Lebon LDC-D35A Digital Kamera" },
+	{ W9968CF_MOD_EEEMC, "Ezonics EZ-802 EZMega Cam" },
+	{ W9968CF_MOD_ODPVDMPC, "OPCOM Digi Pen VGA Dual Mode Pen Camera" },
+
+	{  -1, NULL }
+};
+
+static struct w9968cf_symbolic_list senlist[] = {
+	{ CC_OV76BE,   "OV76BE" },
+	{ CC_OV7610,   "OV7610" },
+	{ CC_OV7620,   "OV7620" },
+	{ CC_OV7620AE, "OV7620AE" },
+	{ CC_OV6620,   "OV6620" },
+	{ CC_OV6630,   "OV6630" },
+	{ CC_OV6630AE, "OV6630AE" },
+	{ CC_OV6630AF, "OV6630AF" },
+	{ -1, NULL }
+};
+
+/* Video4Linux1 palettes */
+static struct w9968cf_symbolic_list v4l1_plist[] = {
+	{ VIDEO_PALETTE_GREY,    "GREY" },
+	{ VIDEO_PALETTE_HI240,   "HI240" },
+	{ VIDEO_PALETTE_RGB565,  "RGB565" },
+	{ VIDEO_PALETTE_RGB24,   "RGB24" },
+	{ VIDEO_PALETTE_RGB32,   "RGB32" },
+	{ VIDEO_PALETTE_RGB555,  "RGB555" },
+	{ VIDEO_PALETTE_YUV422,  "YUV422" },
+	{ VIDEO_PALETTE_YUYV,    "YUYV" },
+	{ VIDEO_PALETTE_UYVY,    "UYVY" },
+	{ VIDEO_PALETTE_YUV420,  "YUV420" },
+	{ VIDEO_PALETTE_YUV411,  "YUV411" },
+	{ VIDEO_PALETTE_RAW,     "RAW" },
+	{ VIDEO_PALETTE_YUV422P, "YUV422P" },
+	{ VIDEO_PALETTE_YUV411P, "YUV411P" },
+	{ VIDEO_PALETTE_YUV420P, "YUV420P" },
+	{ VIDEO_PALETTE_YUV410P, "YUV410P" },
+	{ -1, NULL }
+};
+
+/* Decoder error codes: */
+static struct w9968cf_symbolic_list decoder_errlist[] = {
+	{ W9968CF_DEC_ERR_CORRUPTED_DATA, "Corrupted data" },
+	{ W9968CF_DEC_ERR_BUF_OVERFLOW,   "Buffer overflow" },
+	{ W9968CF_DEC_ERR_NO_SOI,         "SOI marker not found" },     
+	{ W9968CF_DEC_ERR_NO_SOF0,        "SOF0 marker not found" },
+	{ W9968CF_DEC_ERR_NO_SOS,         "SOS marker not found" },
+	{ W9968CF_DEC_ERR_NO_EOI,         "EOI marker not found" },
+	{ -1, NULL }
+};
+
+/* URB error codes: */
+static struct w9968cf_symbolic_list urb_errlist[] = {
+	{ -ENOMEM,    "No memory for allocation of internal structures" },
+	{ -ENOSPC,    "The host controller's bandwidth is already consumed" },
+	{ -ENOENT,    "URB was canceled by unlink_urb" },
+	{ -EXDEV,     "ISO transfer only partially completed" },
+	{ -EAGAIN,    "Too match scheduled for the future" },
+	{ -ENXIO,     "URB already queued" },
+	{ -EFBIG,     "Too much ISO frames requested" },
+	{ -ENOSR,     "Buffer error (overrun)" },
+	{ -EPIPE,     "Specified endpoint is stalled (device not responding)"},
+	{ -EOVERFLOW, "Babble (bad cable?)" },
+	{ -EPROTO,    "Bit-stuff error (bad cable?)" },
+	{ -EILSEQ,    "CRC/Timeout" },
+	{ -ETIMEDOUT, "NAK (device does not respond)" },
+	{ -1, NULL }
+};
+
+
+
+/****************************************************************************
+ * Memory management functions                                              *
+ ****************************************************************************/
+
+/* Shameless copy from bttv-driver.c */
+
+/* Here we want the physical address of the memory.
+   This is used when initializing the contents of the area. */
+static inline unsigned long kvirt_to_pa(unsigned long adr)
+{
+	unsigned long kva, ret;
+
+	kva = (unsigned long) page_address(vmalloc_to_page((void *)adr));
+	kva |= adr & (PAGE_SIZE-1); /* restore the offset */
+	ret = __pa(kva);
+	return ret;
+}
+
+
+static void* rvmalloc(unsigned long size)
+{
+	void* mem;
+	unsigned long adr;
+
+	size = PAGE_ALIGN(size);
+	mem = vmalloc_32(size);
+	if (!mem)
+		return NULL;
+
+	memset(mem, 0, size); /* Clear the ram out, no junk to the user */
+	adr = (unsigned long) mem;
+	while (size > 0) {
+		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)
+		return;
+
+	adr = (unsigned long) mem;
+	while ((long) size > 0) {
+		ClearPageReserved(vmalloc_to_page((void *)adr));
+		adr += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+	vfree(mem);
+}
+/* End of shameless copy */
+
+
+/*--------------------------------------------------------------------------
+  Return the maximum size (in bytes) of a frame buffer.
+  --------------------------------------------------------------------------*/
+static inline unsigned long w9968cf_get_max_bufsize(struct w9968cf_device* cam)
+{
+	u8 bpp = (w9968cf_vppmod_present) ? 4 : 2;
+	return (cam->upscaling) ? W9968CF_MAX_WIDTH*W9968CF_MAX_HEIGHT*bpp :
+	                          cam->maxwidth*cam->maxheight*bpp;
+}
+
+
+/*--------------------------------------------------------------------------
+  Deallocate previously allocated memory.
+  --------------------------------------------------------------------------*/
+static void w9968cf_deallocate_memory(struct w9968cf_device* cam)
+{
+	u8 i;
+
+	/* Free the isochronous transfer buffers */
+	for (i = 0; i < W9968CF_URBS; i++) {
+		kfree(cam->transfer_buffer[i]);
+		cam->transfer_buffer[i] = NULL;
+	}
+
+	/* Free temporary frame buffer */
+	if (cam->frame_tmp.buffer) {
+		rvfree(cam->frame_tmp.buffer, W9968CF_HW_BUF_SIZE);
+		cam->frame_tmp.buffer = NULL;
+	}
+
+	/* Free helper buffer */
+	if (cam->vpp_buffer) {
+		rvfree(cam->vpp_buffer, w9968cf_get_max_bufsize(cam));
+		cam->vpp_buffer = NULL;
+	}
+	
+	/* Free video frame buffers */
+	if (cam->frame[0].buffer) {
+		rvfree(cam->frame[0].buffer, 
+		       cam->nbuffers * w9968cf_get_max_bufsize(cam));
+		cam->frame[0].buffer = NULL;
+	}
+
+	cam->nbuffers = 0;
+
+	DBG(5, "Memory successfully deallocated.")
+}
+
+
+/*--------------------------------------------------------------------------
+  Allocate memory buffers for USB transfers and video frames.
+  This function is called by open() only.
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int w9968cf_allocate_memory(struct w9968cf_device* cam)
+{
+	const unsigned long bufsize = w9968cf_get_max_bufsize(cam);
+	const u16 p_size = wMaxPacketSize[cam->altsetting-1];
+	void* buff = NULL;
+	u8 i;
+
+	/* NOTE: Deallocation is done elsewhere in case of error */
+
+	/* Allocate memory for the isochronous transfer buffers */
+	for (i = 0; i < W9968CF_URBS; i++) {
+		if (!(cam->transfer_buffer[i] =
+		      kmalloc(W9968CF_ISO_PACKETS*p_size, GFP_KERNEL))) {
+			DBG(1, "Couldn't allocate memory for the isochronous "
+			       "transfer buffers (%d bytes).", 
+			    p_size * W9968CF_ISO_PACKETS)
+			return -ENOMEM;
+		}
+		memset(cam->transfer_buffer[i], 0, W9968CF_ISO_PACKETS*p_size);
+	}
+
+	/* Allocate memory for the temporary frame buffer */
+	if (!(cam->frame_tmp.buffer = rvmalloc(W9968CF_HW_BUF_SIZE))) {
+		DBG(1, "Couldn't allocate memory for the temporary "
+		       "video frame buffer (%i bytes).", W9968CF_HW_BUF_SIZE)
+		return -ENOMEM;
+	}
+
+	/* Allocate memory for the helper buffer */
+	if (w9968cf_vppmod_present) {
+		if (!(cam->vpp_buffer = rvmalloc(bufsize))) {
+			DBG(1, "Couldn't allocate memory for the helper buffer"
+			       " (%li bytes).", bufsize)
+			return -ENOMEM;
+		}
+	} else
+		cam->vpp_buffer = NULL;
+
+	/* Allocate memory for video frame buffers */	
+	cam->nbuffers = cam->max_buffers;
+	while (cam->nbuffers >= 2) {
+		if ((buff = rvmalloc(cam->nbuffers * bufsize)))
+			break;
+		else
+			cam->nbuffers--;
+	}
+
+	if (!buff) {
+		DBG(1, "Couldn't allocate memory for the video frame buffers.")
+		cam->nbuffers = 0;
+		return -ENOMEM;
+	}
+
+	if (cam->nbuffers != cam->max_buffers)
+		DBG(2, "Couldn't allocate memory for %d video frame buffers. "
+		       "Only memory for %d buffers has been allocated.",
+		    cam->max_buffers, cam->nbuffers)
+
+	for (i = 0; i < cam->nbuffers; i++) {
+		cam->frame[i].buffer = buff + i*bufsize;
+		/* Circular list */
+		if (i != cam->nbuffers-1)
+			cam->frame[i].next = &cam->frame[i+1];
+		else
+			cam->frame[i].next = &cam->frame[0];
+		cam->frame[i].status = F_UNUSED;
+	}
+
+	DBG(5, "Memory successfully allocated.")
+	return 0;
+}
+
+
+
+/****************************************************************************
+ * USB-specific functions                                                   *
+ ****************************************************************************/
+
+/*--------------------------------------------------------------------------
+  This is an handler function which is called after the URBs are completed.
+  It collects multiple data packets coming from the camera by putting them
+  into frame buffers: one or more zero data length data packets are used to
+  mark the end of a video frame; the first non-zero data packet is the start
+  of the next video frame; if an error is encountered in a packet, the entire
+  video frame is discarded and grabbed again.
+  If there are no requested frames in the FIFO list, packets are collected into
+  a temporary buffer. 
+  --------------------------------------------------------------------------*/
+static void w9968cf_urb_complete(struct urb *urb, struct pt_regs *regs)
+{
+	struct w9968cf_device* cam = (struct w9968cf_device*)urb->context;
+	struct w9968cf_frame_t** f;
+	unsigned long maxbufsize;
+	unsigned int len, status;
+	void* pos;
+	u8 i;
+	int err = 0;
+
+	if ((!cam->streaming) || cam->disconnected) {
+		DBG(4, "Got interrupt, but not streaming.")
+		return;
+	}
+
+	maxbufsize = min( (unsigned long)W9968CF_HW_BUF_SIZE, 
+	                  w9968cf_get_max_bufsize(cam) );
+
+	/* "(*f)" will be used instead of "cam->frame_current" */
+	f = &cam->frame_current;
+
+	/* If a frame has been requested and we are grabbing into  
+	   the temporary frame, we'll switch to that requested frame */
+	if ((*f) == &cam->frame_tmp && *cam->requested_frame) {
+		if (cam->frame_tmp.status == F_GRABBING) {
+			w9968cf_pop_frame(cam, &cam->frame_current);
+			(*f)->status = F_GRABBING;
+			(*f)->length = cam->frame_tmp.length;
+			memcpy((*f)->buffer, cam->frame_tmp.buffer,
+			       (*f)->length);
+			DBG(6, "Switched from temp. frame to frame #%d", 
+			    (*f) - &cam->frame[0])
+		}
+	}
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		len    = urb->iso_frame_desc[i].actual_length;
+		status = urb->iso_frame_desc[i].status;
+		pos    = urb->iso_frame_desc[i].offset + urb->transfer_buffer;
+
+		if (status && len != 0) {
+			DBG(4, "URB failed, error in data packet "
+			       "(error #%d, %s).",
+			    status, symbolic(urb_errlist, status))
+			(*f)->status = F_ERROR;
+			continue;
+		}
+
+		if (len) { /* start of frame */
+
+			if ((*f)->status == F_UNUSED) {
+				(*f)->status = F_GRABBING;
+				(*f)->length = 0;
+			}
+
+			/* Buffer overflows shouldn't happen, however...*/
+			if ((*f)->length + len > maxbufsize) {
+				DBG(4, "Buffer overflow: bad data packets.")
+				(*f)->status = F_ERROR;
+			}
+
+			if ((*f)->status == F_GRABBING) {
+				memcpy((*f)->buffer + (*f)->length, pos, len);
+				(*f)->length += len;
+			}
+
+		} else if ((*f)->status == F_GRABBING) { /* end of frame */
+
+			DBG(6, "Frame #%d successfully grabbed.",
+			    ((*f)==&cam->frame_tmp ? -1 : (*f)-&cam->frame[0]))
+
+			if (cam->vpp_flag & VPP_DECOMPRESSION) {
+				err=(*w9968cf_vpp_check_headers)((*f)->buffer,
+				                                 (*f)->length);
+				if (err) {
+					DBG(4, "Skip corrupted frame: %s",
+					    symbolic(decoder_errlist, err))
+					(*f)->status = F_UNUSED;
+					continue; /* grab this frame again */
+				}
+			}
+
+			(*f)->status = F_READY;
+			(*f)->queued = 0;
+
+			/* Take a pointer to the new frame from the FIFO list.
+			   If the list is empty,we'll use the temporary frame*/
+			if (*cam->requested_frame)
+				w9968cf_pop_frame(cam, &cam->frame_current);
+			else {
+				cam->frame_current = &cam->frame_tmp;
+				(*f)->status = F_UNUSED;
+			}
+
+		} else if ((*f)->status == F_ERROR)
+			(*f)->status = F_UNUSED; /* grab it again */
+
+		PDBGG("Frame length %li | pack.#%d | pack.len. %d | state %d",
+		      (unsigned long)(*f)->length, i, len, (*f)->status)
+
+	} /* end for */
+
+	/* Resubmit this URB */
+	urb->dev = cam->usbdev;
+	urb->status = 0;
+	spin_lock(&cam->urb_lock);
+	if (cam->streaming)
+		if ((err = usb_submit_urb(urb, GFP_ATOMIC))) {
+			cam->misconfigured = 1;
+			DBG(1, "Couldn't resubmit the URB: error %d, %s",
+			    err, symbolic(urb_errlist, err));
+		}
+	spin_unlock(&cam->urb_lock);
+
+	/* Wake up the user process */
+	if (waitqueue_active(&cam->wait_queue))
+		wake_up_interruptible(&cam->wait_queue);
+}
+
+
+/*---------------------------------------------------------------------------
+  Setup the URB structures for the isochronous transfer.
+  Submit the URBs so that the data transfer begins.
+  Return 0 on success, a negative number otherwise.
+  ---------------------------------------------------------------------------*/
+static int w9968cf_start_transfer(struct w9968cf_device* cam)
+{
+	struct usb_device *udev = cam->usbdev;
+	struct urb* urb;
+	const u16 p_size = wMaxPacketSize[cam->altsetting-1];
+	u16 w, h, d;
+	int vidcapt;
+	u32 t_size;
+	int err = 0;
+	s8 i, j;
+
+	for (i = 0; i < W9968CF_URBS; i++) {
+		urb = usb_alloc_urb(W9968CF_ISO_PACKETS, GFP_KERNEL);
+		cam->urb[i] = urb;
+
+		if (!urb) {
+			for (j = 0; j < i; j++)
+				usb_free_urb(cam->urb[j]);
+			DBG(1, "Couldn't allocate the URB structures.")
+			return -ENOMEM;
+		}
+
+		urb->dev = udev;
+		urb->context = (void*)cam;
+		urb->pipe = usb_rcvisocpipe(udev, 1);
+		urb->transfer_flags = URB_ISO_ASAP;
+		urb->number_of_packets = W9968CF_ISO_PACKETS;
+		urb->complete = w9968cf_urb_complete;
+		urb->transfer_buffer = cam->transfer_buffer[i];
+		urb->transfer_buffer_length = p_size*W9968CF_ISO_PACKETS;
+		urb->interval = 1;
+		for (j = 0; j < W9968CF_ISO_PACKETS; j++) {
+			urb->iso_frame_desc[j].offset = p_size*j;
+			urb->iso_frame_desc[j].length = p_size;
+		}
+	}
+
+	/* Transfer size per frame, in WORD ! */
+	d = cam->hw_depth;
+	w = cam->hw_width;
+	h = cam->hw_height;
+
+	t_size = (w*h*d)/16;
+
+	err = w9968cf_write_reg(cam, 0xbf17, 0x00); /* reset  everything */
+	err += w9968cf_write_reg(cam, 0xbf10, 0x00); /* normal operation */
+
+	/* Transfer size */
+	err += w9968cf_write_reg(cam, t_size & 0xffff, 0x3d); /* low bits */
+	err += w9968cf_write_reg(cam, t_size >> 16, 0x3e);    /* high bits */
+
+	if (cam->vpp_flag & VPP_DECOMPRESSION)
+		err += w9968cf_upload_quantizationtables(cam);
+
+	vidcapt = w9968cf_read_reg(cam, 0x16); /* read picture settings */
+	err += w9968cf_write_reg(cam, vidcapt|0x8000, 0x16); /* capt. enable */
+
+	err += usb_set_interface(udev, 0, cam->altsetting);
+	err += w9968cf_write_reg(cam, 0x8a05, 0x3c); /* USB FIFO enable */
+
+	if (err || (vidcapt < 0)) {
+		for (i = 0; i < W9968CF_URBS; i++)
+			usb_free_urb(cam->urb[i]);
+		DBG(1, "Couldn't tell the camera to start the data transfer.")
+		return err;
+	}
+
+	w9968cf_init_framelist(cam);
+
+	/* Begin to grab into the temporary buffer */
+	cam->frame_tmp.status = F_UNUSED;
+	cam->frame_tmp.queued = 0;
+	cam->frame_current = &cam->frame_tmp;
+
+	if (!(cam->vpp_flag & VPP_DECOMPRESSION))
+		DBG(5, "Isochronous transfer size: %li bytes/frame.",
+		    (unsigned long)t_size*2)
+
+	DBG(5, "Starting the isochronous transfer...")
+
+	/* Submit the URBs */
+	for (i = 0; i < W9968CF_URBS; i++) {
+		err = usb_submit_urb(cam->urb[i], GFP_KERNEL);
+		if (err) {
+			for (j = i-1; j >= 0; j--)
+				if (!usb_unlink_urb(cam->urb[j]))
+					usb_free_urb(cam->urb[j]);
+			DBG(1, "Couldn't send a transfer request to the "
+			       "USB core (error #%d, %s).", err, 
+			    symbolic(urb_errlist, err))
+		}
+	}
+
+	cam->streaming = 1;
+
+	return 0;
+}
+
+
+/*--------------------------------------------------------------------------
+  Stop the isochronous transfer and set alternate setting to 0 (0Mb/s).
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int w9968cf_stop_transfer(struct w9968cf_device* cam)
+{
+	struct usb_device *udev = cam->usbdev;
+	unsigned long lock_flags;
+	int err = 0;
+	s8 i;
+
+	/* This avoids race conditions with usb_submit_urb() 
+	   in the URB completition handler */
+	spin_lock_irqsave(&cam->urb_lock, lock_flags);
+	cam->streaming = 0;
+	spin_unlock_irqrestore(&cam->urb_lock, lock_flags);
+
+	for (i = W9968CF_URBS-1; i >= 0; i--)
+		if (cam->urb[i])
+			if (!usb_unlink_urb(cam->urb[i])) {
+				usb_free_urb(cam->urb[i]);
+				cam->urb[i] = NULL;
+			}
+
+	if (cam->disconnected)
+		goto exit;
+
+	err = w9968cf_write_reg(cam, 0x0a05, 0x3c); /* stop USB transfer */
+	err += usb_set_interface(udev, 0, 0); /* 0 Mb/s */
+	err += w9968cf_write_reg(cam, 0x0000, 0x39); /* disable JPEG encoder */
+	err += w9968cf_write_reg(cam, 0x0000, 0x16); /* stop video capture */
+
+	if (err) {
+		DBG(2, "Failed to tell the camera to stop the isochronous "
+		       "transfer. However this is not a critical error.")
+		return -EIO;
+	}
+
+exit:
+	DBG(5, "Isochronous transfer stopped.")
+	return 0;
+}
+
+
+/*--------------------------------------------------------------------------
+  Write a W9968CF register. 
+  Return 0 on success, -1 otherwise.
+  --------------------------------------------------------------------------*/
+static int w9968cf_write_reg(struct w9968cf_device* cam, u16 value, u16 index)
+{
+	struct usb_device* udev = cam->usbdev;
+	int res;
+
+	res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0,
+	                      USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE,
+	                      value, index, NULL, 0, W9968CF_USB_CTRL_TIMEOUT);
+
+	if (res < 0)
+		DBG(4, "Failed to write a register "
+		       "(value 0x%04X, index 0x%02X, error #%d, %s).",
+		    value, index, res, symbolic(urb_errlist, res))
+
+	return (res >= 0) ? 0 : -1;
+}
+
+
+/*--------------------------------------------------------------------------
+  Read a W9968CF register. 
+  Return the register value on success, -1 otherwise.
+  --------------------------------------------------------------------------*/
+static int w9968cf_read_reg(struct w9968cf_device* cam, u16 index)
+{
+	struct usb_device* udev = cam->usbdev;
+	u16* buff = cam->control_buffer;
+	int res;
+
+	res = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 1,
+	                      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+	                      0, index, (void*)buff,
+	                      2, W9968CF_USB_CTRL_TIMEOUT);
+
+	if (res < 0)
+		DBG(4, "Failed to read a register "
+		       "(index 0x%02X, error #%d, %s).",
+		    index, res, symbolic(urb_errlist, res))
+
+	return (res >= 0) ? (int)(*buff) : -1;
+}
+
+
+/*--------------------------------------------------------------------------
+  Write data to the fast serial bus registers.
+  Return 0 on success, -1 otherwise.
+  --------------------------------------------------------------------------*/
+static int w9968cf_write_fsb(struct w9968cf_device* cam, u16* data)
+{
+	struct usb_device* udev = cam->usbdev;
+	u16 value;
+	int res;
+
+	value = *data++;
+
+	res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0,
+	                      USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE,
+	                      value, 0x06, (void*)data, 6,
+	                      W9968CF_USB_CTRL_TIMEOUT);
+
+	if (res < 0)
+		DBG(4, "Failed to write the FSB registers "
+		       "(error #%d, %s).", res, symbolic(urb_errlist, res))
+
+	return (res >= 0) ? 0 : -1;
+}
+
+
+/*--------------------------------------------------------------------------
+  Write data to the serial bus control register.
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int w9968cf_write_sb(struct w9968cf_device* cam, u16 value)
+{
+	int err = 0;
+
+	err = w9968cf_write_reg(cam, value, 0x01);
+	udelay(W9968CF_I2C_BUS_DELAY);
+
+	return err;
+}
+
+
+/*--------------------------------------------------------------------------
+  Read data from the serial bus control register.
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int w9968cf_read_sb(struct w9968cf_device* cam)
+{
+	int v = 0;
+
+	v = w9968cf_read_reg(cam, 0x01);
+	udelay(W9968CF_I2C_BUS_DELAY);
+
+	return v;
+}
+
+
+/*--------------------------------------------------------------------------
+  Upload quantization tables for the JPEG compression.
+  This function is called by w9968cf_start_transfer().
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int w9968cf_upload_quantizationtables(struct w9968cf_device* cam)
+{
+	u16 a, b;
+	int err = 0, i, j;
+
+	err += w9968cf_write_reg(cam, 0x0010, 0x39); /* JPEG clock enable */
+
+	for (i = 0, j = 0; i < 32; i++, j += 2) {
+		a = Y_QUANTABLE[j] | ((unsigned)(Y_QUANTABLE[j+1]) << 8);
+		b = UV_QUANTABLE[j] | ((unsigned)(UV_QUANTABLE[j+1]) << 8);
+		err += w9968cf_write_reg(cam, a, 0x40+i);
+		err += w9968cf_write_reg(cam, b, 0x60+i);
+	}
+	err += w9968cf_write_reg(cam, 0x0012, 0x39); /* JPEG encoder enable */
+
+	return err;
+}
+
+
+
+/****************************************************************************
+ * Low-level I2C I/O functions.                                             *
+ * The adapter supports the following I2C transfer functions:               *
+ * i2c_adap_fastwrite_byte_data() (at 400 kHz bit frequency only)           *
+ * i2c_adap_read_byte_data()                                                *
+ * i2c_adap_read_byte()                                                     *
+ ****************************************************************************/
+
+static int w9968cf_smbus_start(struct w9968cf_device* cam)
+{
+	int err = 0;
+
+	err += w9968cf_write_sb(cam, 0x0011); /* SDE=1, SDA=0, SCL=1 */
+	err += w9968cf_write_sb(cam, 0x0010); /* SDE=1, SDA=0, SCL=0 */
+
+	return err;
+}
+
+
+static int w9968cf_smbus_stop(struct w9968cf_device* cam)
+{
+	int err = 0;
+
+	err += w9968cf_write_sb(cam, 0x0011); /* SDE=1, SDA=0, SCL=1 */
+	err += w9968cf_write_sb(cam, 0x0013); /* SDE=1, SDA=1, SCL=1 */
+
+	return err;
+}
+
+
+static int w9968cf_smbus_write_byte(struct w9968cf_device* cam, u8 v)
+{
+	u8 bit;
+	int err = 0, sda;
+
+	for (bit = 0 ; bit < 8 ; bit++) {
+		sda = (v & 0x80) ? 2 : 0;
+		v <<= 1;
+		/* SDE=1, SDA=sda, SCL=0 */
+		err += w9968cf_write_sb(cam, 0x10 | sda);
+		/* SDE=1, SDA=sda, SCL=1 */
+		err += w9968cf_write_sb(cam, 0x11 | sda);
+		/* SDE=1, SDA=sda, SCL=0 */
+		err += w9968cf_write_sb(cam, 0x10 | sda);
+	}
+
+	return err;
+}
+
+
+static int w9968cf_smbus_read_byte(struct w9968cf_device* cam, u8* v)
+{
+	u8 bit;
+	int err = 0;
+
+	*v = 0;
+	for (bit = 0 ; bit < 8 ; bit++) {
+		*v <<= 1;
+		err += w9968cf_write_sb(cam, 0x0013);
+		*v |= (w9968cf_read_sb(cam) & 0x0008) ? 1 : 0;
+		err += w9968cf_write_sb(cam, 0x0012);
+	}
+
+	return err;
+}
+
+
+static int w9968cf_smbus_write_ack(struct w9968cf_device* cam)
+{
+	int err = 0;
+
+	err += w9968cf_write_sb(cam, 0x0010); /* SDE=1, SDA=0, SCL=0 */
+	err += w9968cf_write_sb(cam, 0x0011); /* SDE=1, SDA=0, SCL=1 */
+	err += w9968cf_write_sb(cam, 0x0010); /* SDE=1, SDA=0, SCL=0 */
+
+	return err;
+}
+
+
+static int w9968cf_smbus_read_ack(struct w9968cf_device* cam)
+{
+	int err = 0, sda;
+
+	err += w9968cf_write_sb(cam, 0x0013); /* SDE=1, SDA=1, SCL=1 */
+	sda = (w9968cf_read_sb(cam) & 0x08) ? 1 : 0; /* sda = SDA */
+	err += w9968cf_write_sb(cam, 0x0012); /* SDE=1, SDA=1, SCL=0 */
+	if (sda < 0)
+		err += sda;
+	if (sda == 1) {
+		DBG(6, "Couldn't receive the ACK.")
+		err += -1;
+	}
+
+	return err;
+}
+
+
+/* SMBus protocol: S Addr Wr [A] Subaddr [A] Value [A] P */
+static int 
+w9968cf_i2c_adap_fastwrite_byte_data(struct w9968cf_device* cam, 
+                                     u16 address, u8 subaddress,u8 value)
+{
+	u16* data = cam->data_buffer;
+	int err = 0;
+
+	 /* Enable SBUS outputs */
+	err += w9968cf_write_reg(cam, 0x0020, 0x01);
+
+	data[0] = 0x082f | ((address & 0x80) ? 0x1500 : 0x0);
+	data[0] |= (address & 0x40) ? 0x4000 : 0x0;
+	data[1] = 0x2082 | ((address & 0x40) ? 0x0005 : 0x0);
+	data[1] |= (address & 0x20) ? 0x0150 : 0x0;
+	data[1] |= (address & 0x10) ? 0x5400 : 0x0;
+	data[2] = 0x8208 | ((address & 0x08) ? 0x0015 : 0x0);
+	data[2] |= (address & 0x04) ? 0x0540 : 0x0;
+	data[2] |= (address & 0x02) ? 0x5000 : 0x0;
+	data[3] = 0x1d20 | ((address & 0x02) ? 0x0001 : 0x0);
+	data[3] |= (address & 0x01) ? 0x0054 : 0x0;
+
+	err += w9968cf_write_fsb(cam, data);
+
+	data[0] = 0x8208 | ((subaddress & 0x80) ? 0x0015 : 0x0);
+	data[0] |= (subaddress & 0x40) ? 0x0540 : 0x0;
+	data[0] |= (subaddress & 0x20) ? 0x5000 : 0x0;
+	data[1] = 0x0820 | ((subaddress & 0x20) ? 0x0001 : 0x0);
+	data[1] |= (subaddress & 0x10) ? 0x0054 : 0x0;
+	data[1] |= (subaddress & 0x08) ? 0x1500 : 0x0;
+	data[1] |= (subaddress & 0x04) ? 0x4000 : 0x0;
+	data[2] = 0x2082 | ((subaddress & 0x04) ? 0x0005 : 0x0);
+	data[2] |= (subaddress & 0x02) ? 0x0150 : 0x0;
+	data[2] |= (subaddress & 0x01) ? 0x5400 : 0x0;
+	data[3] = 0x001d;
+
+	err += w9968cf_write_fsb(cam, data);
+
+	data[0] = 0x8208 | ((value & 0x80) ? 0x0015 : 0x0);
+	data[0] |= (value & 0x40) ? 0x0540 : 0x0;
+	data[0] |= (value & 0x20) ? 0x5000 : 0x0;
+	data[1] = 0x0820 | ((value & 0x20) ? 0x0001 : 0x0);
+	data[1] |= (value & 0x10) ? 0x0054 : 0x0;
+	data[1] |= (value & 0x08) ? 0x1500 : 0x0;
+	data[1] |= (value & 0x04) ? 0x4000 : 0x0;
+	data[2] = 0x2082 | ((value & 0x04) ? 0x0005 : 0x0);
+	data[2] |= (value & 0x02) ? 0x0150 : 0x0;
+	data[2] |= (value & 0x01) ? 0x5400 : 0x0;
+	data[3] = 0xfe1d;
+
+	err += w9968cf_write_fsb(cam, data);
+
+	/* Disable SBUS outputs */
+	err += w9968cf_write_reg(cam, 0x0000, 0x01);
+
+	if (!err)
+		DBG(5, "I2C write byte data done, addr.0x%04X, subaddr.0x%02X "
+		       "value 0x%02X.", address, subaddress, value)
+	else
+		DBG(5, "I2C write byte data failed, addr.0x%04X, "
+		       "subaddr.0x%02X, value 0x%02X.", 
+	            address, subaddress, value)
+
+	return err;
+}
+
+
+/* SMBus protocol: S Addr Wr [A] Subaddr [A] P S Addr+1 Rd [A] [Value] NA P */
+static int 
+w9968cf_i2c_adap_read_byte_data(struct w9968cf_device* cam, 
+                                u16 address, u8 subaddress, 
+                                u8* value)
+{
+	int err = 0;
+
+	/* Serial data enable */
+	err += w9968cf_write_sb(cam, 0x0013); /* don't change ! */
+
+	err += w9968cf_smbus_start(cam);
+	err += w9968cf_smbus_write_byte(cam, address);
+	err += w9968cf_smbus_read_ack(cam);
+	err += w9968cf_smbus_write_byte(cam, subaddress);
+	err += w9968cf_smbus_read_ack(cam);
+	err += w9968cf_smbus_stop(cam);
+	err += w9968cf_smbus_start(cam);
+	err += w9968cf_smbus_write_byte(cam, address + 1);
+	err += w9968cf_smbus_read_ack(cam);
+	err += w9968cf_smbus_read_byte(cam, value);
+	err += w9968cf_smbus_write_ack(cam);
+	err += w9968cf_smbus_stop(cam);
+ 
+	/* Serial data disable */
+	err += w9968cf_write_sb(cam, 0x0000);
+
+	if (!err)
+		DBG(5, "I2C read byte data done, addr.0x%04X, "
+		       "subaddr.0x%02X, value 0x%02X.", 
+		    address, subaddress, *value)
+	else
+		DBG(5, "I2C read byte data failed, addr.0x%04X, "
+		       "subaddr.0x%02X, wrong value 0x%02X.",
+		    address, subaddress, *value)
+
+	return err;
+}
+
+
+/* SMBus protocol: S Addr+1 Rd [A] [Value] NA P */
+static int 
+w9968cf_i2c_adap_read_byte(struct w9968cf_device* cam,
+                           u16 address, u8* value)
+{
+	int err = 0;
+
+	/* Serial data enable */
+	err += w9968cf_write_sb(cam, 0x0013);
+
+	err += w9968cf_smbus_start(cam);
+	err += w9968cf_smbus_write_byte(cam, address + 1);
+	err += w9968cf_smbus_read_ack(cam);
+	err += w9968cf_smbus_read_byte(cam, value);
+	err += w9968cf_smbus_write_ack(cam);
+	err += w9968cf_smbus_stop(cam);
+ 
+	/* Serial data disable */
+	err += w9968cf_write_sb(cam, 0x0000);
+
+	if (!err)
+		DBG(5, "I2C read byte done, addr.0x%04X."
+		       "value 0x%02X.", address, *value)
+	else
+		DBG(5, "I2C read byte failed, addr.0x%04X."
+		       "wrong value 0x%02X.", address, *value)
+
+	return err;
+}
+
+
+/* SMBus protocol: S Addr Wr [A] Value [A] P */
+static int 
+w9968cf_i2c_adap_write_byte(struct w9968cf_device* cam,
+                            u16 address, u8 value)
+{
+	DBG(4, "i2c_write_byte() is an unsupported transfer mode.")
+	return -EINVAL;
+}
+
+
+
+/****************************************************************************
+ * I2C interface to kernel                                                  *
+ ****************************************************************************/
+
+static int
+w9968cf_i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, 
+                       unsigned short flags, char read_write, u8 command,
+                       int size, union i2c_smbus_data *data)
+{
+	struct w9968cf_device* cam = i2c_get_adapdata(adapter);
+	u8 i, j;
+	int rc = 0, err = 0; 
+
+	switch (addr) {
+		case OV6xx0_SID:
+		case OV7xx0_SID:
+			break;
+		default:
+			DBG(4, "Rejected slave ID 0x%04X", addr)
+			return -EINVAL;
+	}
+
+	if (size == I2C_SMBUS_BYTE) {
+		/* Why addr <<= 1? See OVXXX0_SID defines in ovcamchip.h */
+		addr <<= 1;
+
+		if (read_write == I2C_SMBUS_WRITE)
+ 			rc = w9968cf_i2c_adap_write_byte(cam, addr, command);
+		else if (read_write == I2C_SMBUS_READ) 
+			rc = w9968cf_i2c_adap_read_byte(cam,addr, &data->byte);
+
+	} else if (size == I2C_SMBUS_BYTE_DATA) {
+		addr <<= 1;
+
+		if (read_write == I2C_SMBUS_WRITE)
+ 			rc = w9968cf_i2c_adap_fastwrite_byte_data(cam, addr,
+			                                  command, data->byte);
+		else if (read_write == I2C_SMBUS_READ) {
+			for (i = 1; i <= W9968CF_I2C_RW_RETRIES; i++) {
+				rc = w9968cf_i2c_adap_read_byte_data(cam, addr, 
+				                         command, &data->byte);
+				if (rc < 0) {
+					/* Work around: this seems to wake up  
+					   the EEPROM from the stall state */
+					for (j = 0; j <= 10; j++) {
+					   err += w9968cf_write_sb(cam,0x0020);
+					   err += w9968cf_write_sb(cam,0x0000);
+					   if (err)
+					   	break;
+					}
+				}
+				else
+					break;
+			}
+
+		} else
+			return -EINVAL;
+
+	} else {
+		DBG(4, "Unsupported I2C transfer mode (%d)", size)
+		return -EINVAL;
+	}
+
+	/* This works around a bug in the I2C core */
+	if (rc > 0)
+		rc = 0;
+
+	return rc;
+}
+
+
+static u32 w9968cf_i2c_func(struct i2c_adapter* adap)
+{
+	return I2C_FUNC_SMBUS_READ_BYTE |
+	       I2C_FUNC_SMBUS_READ_BYTE_DATA  |
+	       I2C_FUNC_SMBUS_WRITE_BYTE_DATA;
+}
+
+
+static int w9968cf_i2c_attach_inform(struct i2c_client* client)
+{
+	struct w9968cf_device* cam = i2c_get_adapdata(client->adapter);
+	const char* clientname = i2c_clientname(client);
+	int id = client->driver->id;
+
+	if (id == I2C_DRIVERID_OVCAMCHIP) {
+		int rc = 0;
+
+		cam->sensor_client = client;
+
+		rc = w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_INITIALIZE, 
+		                        &cam->sensor_mono);
+		if (rc < 0) {
+			DBG(1, "CMOS sensor initialization failed (rc=%d)",rc);
+			cam->sensor_client = NULL;
+			return rc;
+		}
+
+		if (w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_Q_SUBTYPE, 
+		                       &cam->sensor) < 0)
+			rc = -EIO;
+		else if (client->addr==OV7xx0_SID || client->addr==OV6xx0_SID)
+			w9968cf_sensor_configure(cam);
+		else
+			rc = -EINVAL;
+
+		if (rc < 0) {
+			cam->sensor_client = NULL;
+			cam->sensor = CC_UNKNOWN;
+			return rc;
+		}
+	} else	{
+		DBG(4, "Rejected client [%s] with [%s]", 
+		    clientname, client->driver->name)
+		return -1;
+	}
+
+	DBG(2, "I2C attach client [%s] with [%s]",
+	    clientname, client->driver->name)
+
+	return 0;
+}
+
+
+static int w9968cf_i2c_detach_inform(struct i2c_client* client)
+{
+	struct w9968cf_device* cam = i2c_get_adapdata(client->adapter);
+	const char* clientname = i2c_clientname(client);
+
+	if (cam->sensor_client == client) {
+		cam->sensor_client = NULL;
+	}
+
+	DBG(2, "I2C detach [%s]", clientname)
+
+	return 0;
+}
+
+
+static int 
+w9968cf_i2c_control(struct i2c_adapter* adapter, unsigned int cmd,
+                    unsigned long arg)
+{
+	return 0;
+}
+
+
+static int w9968cf_i2c_init(struct w9968cf_device* cam)
+{
+	int rc = 0;
+
+	static struct i2c_algorithm algo = {
+		.name =          "W996[87]CF algorithm",
+		.id =            I2C_ALGO_SMBUS,
+		.smbus_xfer =    w9968cf_i2c_smbus_xfer,
+		.algo_control =  w9968cf_i2c_control,
+		.functionality = w9968cf_i2c_func,
+	};
+
+	static struct i2c_adapter adap = {
+		.id =                I2C_ALGO_SMBUS | I2C_HW_SMBUS_W9968CF,
+		.class =             I2C_ADAP_CLASS_CAM_DIGITAL,
+		.owner =             THIS_MODULE,
+		.client_register =   w9968cf_i2c_attach_inform,
+		.client_unregister = w9968cf_i2c_detach_inform,
+		.algo =              &algo,
+	};
+
+	memcpy(&cam->i2c_adapter, &adap, sizeof(struct i2c_adapter));
+	strcpy(cam->i2c_adapter.name, "w9968cf");
+	i2c_set_adapdata(&cam->i2c_adapter, cam);
+
+	DBG(6, "Registering I2C bus with kernel...")
+
+	rc = i2c_add_adapter(&cam->i2c_adapter);
+	if (rc)
+		DBG(5, "Failed to register the I2C bus.")
+	else
+		DBG(5, "I2C bus registered.")
+
+	return rc;
+}
+
+
+
+/****************************************************************************
+ * Helper functions                                                         *
+ ****************************************************************************/
+
+/*--------------------------------------------------------------------------
+  Turn on the LED on some webcams. A beep should be heard too.
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int w9968cf_turn_on_led(struct w9968cf_device* cam)
+{
+	int err = 0;
+
+	err += w9968cf_write_reg(cam, 0xff00, 0x00); /* power-down */
+	err += w9968cf_write_reg(cam, 0xbf17, 0x00); /* reset everything */
+	err += w9968cf_write_reg(cam, 0xbf10, 0x00); /* normal operation */
+	err += w9968cf_write_reg(cam, 0x0010, 0x01); /* serial bus, SDS high */
+	err += w9968cf_write_reg(cam, 0x0000, 0x01); /* serial bus, SDS low */
+	err += w9968cf_write_reg(cam, 0x0010, 0x01); /* ..high 'beep-beep' */
+
+	if (err)
+		DBG(2, "Couldn't turn on the LED.")
+
+	DBG(5, "LED turned on.")
+
+	return err;
+}
+
+
+/*--------------------------------------------------------------------------
+  Write some registers for the device initialization.
+  This function is called once on open().
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int w9968cf_init_chip(struct w9968cf_device* cam)
+{
+	int err = 0, rc = 0;
+
+	err += w9968cf_write_reg(cam, 0xff00, 0x00); /* power off */
+	err += w9968cf_write_reg(cam, 0xbf10, 0x00); /* power on */
+
+	err += w9968cf_write_reg(cam, 0x405d, 0x03); /* DRAM timings */
+	err += w9968cf_write_reg(cam, 0x0030, 0x04); /* SDRAM timings */
+
+	err += w9968cf_write_reg(cam, 0x0000, 0x20); /* Y frame buf.0, low */
+	err += w9968cf_write_reg(cam, 0x0000, 0x21); /* Y frame buf.0, high */
+	err += w9968cf_write_reg(cam, 0xb000, 0x22); /* Y frame buf.1, low */
+	err += w9968cf_write_reg(cam, 0x0004, 0x23); /* Y frame buf.1, high */
+	err += w9968cf_write_reg(cam, 0x5800, 0x24); /* U frame buf.0, low */
+	err += w9968cf_write_reg(cam, 0x0002, 0x25); /* U frame buf.0, high */
+	err += w9968cf_write_reg(cam, 0x0800, 0x26); /* U frame buf.1, low */
+	err += w9968cf_write_reg(cam, 0x0007, 0x27); /* U frame buf.1, high */
+	err += w9968cf_write_reg(cam, 0x8400, 0x28); /* V frame buf.0, low */
+	err += w9968cf_write_reg(cam, 0x0003, 0x29); /* V frame buf.0, high */
+	err += w9968cf_write_reg(cam, 0x3400, 0x2a); /* V frame buf.1, low */
+	err += w9968cf_write_reg(cam, 0x0008, 0x2b); /* V frame buf.1, high */
+
+	err += w9968cf_write_reg(cam, 0x6000, 0x32); /* JPEG bitstream buf 0 */
+	err += w9968cf_write_reg(cam, 0x0009, 0x33); /* JPEG bitstream buf 0 */
+	err += w9968cf_write_reg(cam, 0x2000, 0x34); /* JPEG bitstream buf 1 */
+	err += w9968cf_write_reg(cam, 0x000d, 0x35); /* JPEG bitstream buf 1 */
+
+	err += w9968cf_write_reg(cam, 0x0000, 0x36);/* JPEG restart interval */
+	err += w9968cf_write_reg(cam, 0x0804, 0x37);/*JPEG VLE FIFO threshold*/
+	err += w9968cf_write_reg(cam, 0x0000, 0x38);/* disable hw up-scaling */
+	err += w9968cf_write_reg(cam, 0x0000, 0x3f); /* JPEG/MCTL test data */
+
+	err += w9968cf_set_picture(cam, cam->picture); /* this before */
+	err += w9968cf_set_window(cam, cam->window);
+
+	if (err)
+		goto error;
+
+	rc = w9968cf_sensor_change_settings(cam);
+	if (rc)
+		goto error;
+
+	DBG(5, "Chip successfully initialized.");
+	
+	return 0;
+
+error:
+	DBG(1, "Chip initialization failed.")
+	if (err)
+		return err;
+	else
+		return rc;
+}
+
+
+/*--------------------------------------------------------------------------
+  Change the picture settings of the camera.
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int
+w9968cf_set_picture(struct w9968cf_device* cam, struct video_picture pict)
+{
+	u16 fmt, hw_depth, hw_palette, reg_v = 0x0000;
+	int err = 0, rc = 0;
+
+	/* Make sure we are using a valid depth */
+	pict.depth = w9968cf_valid_depth(pict.palette);
+
+	fmt = pict.palette;
+
+	hw_depth = pict.depth; /* depth used by the winbond chip */
+	hw_palette = pict.palette; /* palette used by the winbond chip */
+
+	/* VS & HS polarities */
+	reg_v = (cam->vs_polarity << 12) | (cam->hs_polarity << 11);
+
+	switch (fmt)
+	{
+		case VIDEO_PALETTE_UYVY:
+			reg_v |= 0x0000;
+			cam->vpp_flag = VPP_NONE;
+			break;
+		case VIDEO_PALETTE_YUV422P:
+			reg_v |= 0x0002;
+			cam->vpp_flag = VPP_DECOMPRESSION;
+			break;
+		case VIDEO_PALETTE_YUV420:
+		case VIDEO_PALETTE_YUV420P:
+			reg_v |= 0x0003;
+			cam->vpp_flag = VPP_DECOMPRESSION;
+			break;
+		case VIDEO_PALETTE_YUYV:
+		case VIDEO_PALETTE_YUV422:
+			reg_v |= 0x0000;
+			cam->vpp_flag = VPP_SWAP_YUV_BYTES;
+			hw_palette = VIDEO_PALETTE_UYVY;
+			break;
+		/* Original video is used instead of RGBX palettes. 
+		   Software conversion later. */
+		case VIDEO_PALETTE_GREY:
+		case VIDEO_PALETTE_RGB555:
+		case VIDEO_PALETTE_RGB565:
+		case VIDEO_PALETTE_RGB24:
+		case VIDEO_PALETTE_RGB32:
+			reg_v |= 0x0000; /* UYVY 16 bit is used */
+			hw_depth = 16;
+			hw_palette = VIDEO_PALETTE_UYVY;
+			cam->vpp_flag = VPP_UYVY_TO_RGBX;
+			break;
+	}
+
+	/* FIXME: 'hardware double buffer' doesn't work when compressed video
+	          is enabled (corrupted frames). */
+	if (cam->double_buffer && !(cam->vpp_flag & VPP_DECOMPRESSION))
+		reg_v |= 0x0080;
+
+	if (cam->clamping)
+		reg_v |= 0x0020;
+
+	if (cam->filter_type == 1)
+		reg_v |= 0x0008;
+	else if (cam->filter_type == 2)
+		reg_v |= 0x000c;
+
+	err = w9968cf_write_reg(cam, reg_v, 0x16);
+	if (err)
+		goto error;
+
+	rc = w9968cf_sensor_set_picture(cam, pict);
+	if (rc)
+		goto error;
+
+	/* If all went well, update the device data structure */
+	memcpy(&cam->picture, &pict, sizeof(pict));
+	cam->hw_depth = hw_depth;
+	cam->hw_palette = hw_palette;
+
+	/* Settings changed, so we clear the frame buffers */
+	memset(cam->frame[0].buffer, 0, 
+	       cam->nbuffers*w9968cf_get_max_bufsize(cam));
+
+	DBG(4, "Palette is %s, depth is %d bpp.",
+	    symbolic(v4l1_plist, pict.palette), pict.depth)
+
+	return 0;
+
+error:
+	DBG(1, "Failed to change picture settings.")
+	if (err)
+		return err;
+	else 
+		return rc;
+}
+
+
+/*--------------------------------------------------------------------------
+  Change the capture area size of the camera.
+  This function _must_ be called _after_ w9968cf_set_picture().
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int
+w9968cf_set_window(struct w9968cf_device* cam, struct video_window win)
+{
+	u16 x, y, w, h, scx, scy, cw, ch, ax, ay;
+	unsigned long fw, fh;
+	struct ovcamchip_window s_win;
+	int err=0, rc=0;
+
+	/* Work around to avoid FP arithmetics */
+	#define __SC(x) ((x) << 10)
+	#define __UNSC(x) ((x) >> 10)
+
+	/* Make sure we are using a supported resolution */
+	if ((err = w9968cf_adjust_window_size(cam, (u16*)&win.width, 
+	                                      (u16*)&win.height)))
+		goto error;
+
+	/* Scaling factors */
+	fw = __SC(win.width) / cam->maxwidth;
+	fh = __SC(win.height) / cam->maxheight;
+
+	/* Set up the width and height values used by the chip */
+	if ((win.width > cam->maxwidth) || (win.height > cam->maxheight)) {
+		cam->vpp_flag |= VPP_UPSCALE;
+		/* Calculate largest w,h mantaining the same w/h ratio */
+		w = (fw >= fh) ? cam->maxwidth : __SC(win.width)/fh;
+		h = (fw >= fh) ? __SC(win.height)/fw : cam->maxheight;
+		if (w < cam->minwidth) /* just in case */
+			w = cam->minwidth;
+		if (h < cam->minheight) /* just in case */
+			h = cam->minheight;
+	} else {
+		cam->vpp_flag &= ~VPP_UPSCALE;
+		w = win.width;
+		h = win.height;
+	}
+
+	/* x,y offsets of the cropped area */
+	scx = cam->start_cropx;
+	scy = cam->start_cropy;
+
+	/* Calculate cropped area manteining the right w/h ratio */
+	if (cam->largeview && !(cam->vpp_flag & VPP_UPSCALE)) {
+		cw = (fw >= fh) ? cam->maxwidth : __SC(win.width)/fh;
+		ch = (fw >= fh) ? __SC(win.height)/fw : cam->maxheight;
+	} else {
+		cw = w;
+		ch = h;
+	}
+
+	/* Setup the sensor window */
+	s_win.format = SENSOR_FORMAT;
+	s_win.width = cam->maxwidth;
+	s_win.height = cam->maxheight;
+	s_win.quarter = 0; /* full progressive video */
+
+	/* Center it */
+	s_win.x = (s_win.width - cw) / 2;
+	s_win.y = (s_win.height - ch) / 2;
+
+	/* Clock divisor */
+	if (cam->clockdiv >= 0)
+		s_win.clockdiv = cam->clockdiv; /* manual override */
+	else
+		switch (cam->sensor) {
+			case CC_OV6620:
+				s_win.clockdiv = 0;
+				break;
+			case CC_OV6630:
+				s_win.clockdiv = 0;
+				break;
+			case CC_OV76BE:
+			case CC_OV7610:
+			case CC_OV7620:
+				s_win.clockdiv = 0;
+				break;
+			default:
+				s_win.clockdiv = W9968CF_DEF_CLOCKDIVISOR;
+		}
+
+	/* We have to scale win.x and win.y offsets */	
+	if ( (cam->largeview && !(cam->vpp_flag & VPP_UPSCALE))
+	     || (cam->vpp_flag & VPP_UPSCALE) ) {
+		ax = __SC(win.x)/fw;
+		ay = __SC(win.y)/fh;
+	} else {
+		ax = win.x;
+		ay = win.y;
+	}
+
+	if ((ax + cw) > cam->maxwidth)
+		ax = cam->maxwidth - cw;
+
+	if ((ay + ch) > cam->maxheight)
+		ay = cam->maxheight - ch;
+
+	/* Adjust win.x, win.y */
+	if ( (cam->largeview && !(cam->vpp_flag & VPP_UPSCALE))
+	     || (cam->vpp_flag & VPP_UPSCALE) ) {
+		win.x = __UNSC(ax*fw);
+		win.y = __UNSC(ay*fh);
+	} else {
+		win.x = ax;
+		win.y = ay;
+	}
+
+	/* Offsets used by the chip */
+	x = ax + s_win.x;
+	y = ay + s_win.y;
+
+	/* Go ! */
+	if ((rc = w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_S_MODE, &s_win)))
+		goto error;
+
+	err += w9968cf_write_reg(cam, scx + x, 0x10);
+	err += w9968cf_write_reg(cam, scy + y, 0x11);
+	err += w9968cf_write_reg(cam, scx + x + cw, 0x12);
+	err += w9968cf_write_reg(cam, scy + y + ch, 0x13);
+	err += w9968cf_write_reg(cam, w, 0x14);
+	err += w9968cf_write_reg(cam, h, 0x15);
+
+	/* JPEG width & height */
+	err += w9968cf_write_reg(cam, w, 0x30);
+	err += w9968cf_write_reg(cam, h, 0x31);
+
+	/* Y & UV frame buffer strides (in WORD) */
+	if (cam->vpp_flag & VPP_DECOMPRESSION) {
+		err += w9968cf_write_reg(cam, w/2, 0x2c);
+		err += w9968cf_write_reg(cam, w/4, 0x2d);
+	} else
+		err += w9968cf_write_reg(cam, w, 0x2c);
+
+	if (err)
+		goto error;
+
+	/* If all went well, update the device data structure */
+	memcpy(&cam->window, &win, sizeof(win));
+	cam->hw_width = w;
+	cam->hw_height = h;
+
+	/* Settings changed, so we clear the frame buffers */
+	memset(cam->frame[0].buffer, 0, 
+	       cam->nbuffers*w9968cf_get_max_bufsize(cam));
+
+	DBG(4, "The capture area is %dx%d, Offset (x,y)=(%d,%d).", 
+	    win.width, win.height, win.x, win.y)
+
+	PDBGG("x=%d ,y=%d, w=%d, h=%d, ax=%d, ay=%d, s_win.x=%d, s_win.y=%d, "
+	      "cw=%d, ch=%d, win.x=%d ,win.y=%d, win.width=%d, win.height=%d",
+	      x, y, w, h, ax, ay, s_win.x, s_win.y, cw, ch, win.x, win.y,
+	      win.width, win.height)
+
+	return 0;
+
+error:
+	DBG(1, "Failed to change the capture area size.")
+	if (err)
+		return err;
+	else
+		return rc;
+}
+
+
+/*--------------------------------------------------------------------------
+  Return non-zero if the palette is supported, 0 otherwise.
+  --------------------------------------------------------------------------*/
+static inline u16 w9968cf_valid_palette(u16 palette)
+{
+	u8 i = 0;
+	while (w9968cf_formatlist[i].palette != 0) {
+		if (palette == w9968cf_formatlist[i].palette)
+			return palette;
+		i++;
+	}
+	return 0;
+}
+
+
+/*--------------------------------------------------------------------------
+  Return the depth corresponding to the given palette.
+  Palette _must_ be supported !
+  --------------------------------------------------------------------------*/
+static inline u16 w9968cf_valid_depth(u16 palette)
+{
+	u8 i=0;
+	while (w9968cf_formatlist[i].palette != palette)
+		i++;
+
+	return w9968cf_formatlist[i].depth;
+}
+
+
+/*--------------------------------------------------------------------------
+  Return non-zero if the format requires decompression, 0 otherwise.
+  --------------------------------------------------------------------------*/
+static inline u8 w9968cf_need_decompression(u16 palette)
+{
+	u8 i = 0;
+	while (w9968cf_formatlist[i].palette != 0) {
+		if (palette == w9968cf_formatlist[i].palette)
+			return w9968cf_formatlist[i].compression;
+		i++;
+	}
+	return 0;
+}
+
+
+/*-------------------------------------------------------------------------- 
+  Adjust the asked values for window width and height.
+  Return 0 on success, -1 otherwise.
+  --------------------------------------------------------------------------*/
+static int 
+w9968cf_adjust_window_size(struct w9968cf_device* cam, u16* width, u16* height)
+{
+	u16 maxw, maxh;
+
+	if ((*width < cam->minwidth) || (*height < cam->minheight))
+		return -ERANGE;
+
+	maxw = cam->upscaling && !(cam->vpp_flag & VPP_DECOMPRESSION)
+	       && w9968cf_vppmod_present ? W9968CF_MAX_WIDTH : cam->maxwidth;
+	maxh = cam->upscaling && !(cam->vpp_flag & VPP_DECOMPRESSION)
+	       && w9968cf_vppmod_present ? W9968CF_MAX_HEIGHT : cam->maxheight;
+
+	if (*width > maxw)
+		*width = maxw;
+	if (*height > maxh)
+		*height = maxh;
+
+	if (cam->vpp_flag & VPP_DECOMPRESSION) {
+		*width  &= ~15L; /* multiple of 16 */
+		*height &= ~15L;
+	}
+
+	PDBGG("Window size adjusted w=%d, h=%d ", *width, *height)
+
+	return 0;
+}
+
+
+/*--------------------------------------------------------------------------
+  Initialize the FIFO list of requested frames.
+  --------------------------------------------------------------------------*/
+static void w9968cf_init_framelist(struct w9968cf_device* cam)
+{
+	u8 i;
+
+	for (i = 0; i < cam->nbuffers; i++) {
+		cam->requested_frame[i] = NULL;
+		cam->frame[i].queued = 0;
+		cam->frame[i].status = F_UNUSED;
+	}
+}
+
+
+/*--------------------------------------------------------------------------
+  Add a frame in the FIFO list of requested frames.
+  This function is called in process context.
+  --------------------------------------------------------------------------*/
+static void w9968cf_push_frame(struct w9968cf_device* cam, u8 f_num)
+{
+	u8 f;
+	unsigned long lock_flags;
+
+	spin_lock_irqsave(&cam->flist_lock, lock_flags);
+
+	for (f=0; cam->requested_frame[f] != NULL; f++);
+	cam->requested_frame[f] = &cam->frame[f_num];
+	cam->frame[f_num].queued = 1;
+	cam->frame[f_num].status = F_UNUSED; /* clear the status */
+
+	spin_unlock_irqrestore(&cam->flist_lock, lock_flags);
+
+	DBG(6, "Frame #%d pushed into the FIFO list. Position %d.", f_num, f)
+}
+
+
+/*--------------------------------------------------------------------------
+  Read, store and remove the first pointer in the FIFO list of requested
+  frames. This function is called in interrupt context.
+  --------------------------------------------------------------------------*/
+static void 
+w9968cf_pop_frame(struct w9968cf_device* cam, struct w9968cf_frame_t** framep)
+{
+	u8 i;
+
+	spin_lock(&cam->flist_lock);
+
+	*framep = cam->requested_frame[0];
+
+	/* Shift the list of pointers */
+	for (i = 0; i < cam->nbuffers-1; i++)
+		cam->requested_frame[i] = cam->requested_frame[i+1];
+	cam->requested_frame[i] = NULL;
+
+	spin_unlock(&cam->flist_lock);
+
+	DBG(6,"Popped frame #%d from the list.",*framep-&cam->frame[0])
+}
+
+
+/*--------------------------------------------------------------------------
+  High-level video post-processing routine on grabbed frames.
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int 
+w9968cf_postprocess_frame(struct w9968cf_device* cam, 
+                          struct w9968cf_frame_t* fr)
+{
+	void *pIn = fr->buffer, *pOut = cam->vpp_buffer, *tmp;
+	u16 w = cam->window.width,
+	    h = cam->window.height,
+	    d = cam->picture.depth,
+	    fmt = cam->picture.palette,
+	    rgb = cam->force_rgb,
+	    hw_w = cam->hw_width,
+	    hw_h = cam->hw_height,
+	    hw_d = cam->hw_depth;
+	int err = 0;
+
+	#define _PSWAP(pIn, pOut) {tmp = (pIn); (pIn) = (pOut); (pOut) = tmp;}
+
+	if (cam->vpp_flag & VPP_DECOMPRESSION) {
+		memcpy(pOut, pIn, fr->length);
+		_PSWAP(pIn, pOut)
+		err = (*w9968cf_vpp_decode)(pIn, fr->length, hw_w, hw_h, pOut);
+		PDBGG("Compressed frame length: %li",(unsigned long)fr->length)
+		fr->length = (hw_w*hw_h*hw_d)/8;
+		_PSWAP(pIn, pOut)
+		if (err) {
+			DBG(4, "An error occurred while decoding the frame: "
+			       "%s.", symbolic(decoder_errlist, err))
+			return err;
+		} else
+			DBG(6, "Frame decoded")
+	}
+
+	if (cam->vpp_flag & VPP_SWAP_YUV_BYTES) {
+		(*w9968cf_vpp_swap_yuvbytes)(pIn, fr->length);
+		DBG(6, "Original UYVY component ordering changed.")
+	}
+
+	if (cam->vpp_flag & VPP_UPSCALE) {
+		(*w9968cf_vpp_scale_up)(pIn, pOut, hw_w, hw_h, hw_d, w, h);
+		fr->length = (w*h*hw_d)/8;
+		_PSWAP(pIn, pOut)
+		DBG(6, "Vertical up-scaling done: %d,%d,%dbpp->%d,%d",
+		    hw_w, hw_h, hw_d, w, h)
+	}
+
+	if (cam->vpp_flag & VPP_UYVY_TO_RGBX) {
+		(*w9968cf_vpp_uyvy_to_rgbx)(pIn, fr->length, pOut, fmt, rgb);
+		fr->length = (w*h*d)/8;
+		_PSWAP(pIn, pOut)
+		DBG(6, "UYVY-16bit to %s conversion done.", 
+		    symbolic(v4l1_plist, fmt))
+	}
+
+	if (pOut == fr->buffer)
+		memcpy(fr->buffer, cam->vpp_buffer, fr->length);
+
+	return 0;
+}
+
+
+
+/****************************************************************************
+ * CMOS sensor control routines                                             *
+ ****************************************************************************/
+
+static int 
+w9968cf_sensor_set_control(struct w9968cf_device* cam, int cid, int val)
+{
+	struct ovcamchip_control ctl;
+	int rc;
+
+	ctl.id = cid;
+	ctl.value = val;
+
+	rc = w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_S_CTRL, &ctl);
+
+	return rc;
+}
+
+static int 
+w9968cf_sensor_get_control(struct w9968cf_device* cam, int cid, int *val)
+{
+	struct ovcamchip_control ctl;
+	int rc;
+
+	ctl.id = cid;
+
+	rc = w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_G_CTRL, &ctl);
+	if (rc >= 0)
+		*val = ctl.value;
+
+	return rc;
+}
+
+
+static inline int
+w9968cf_sensor_cmd(struct w9968cf_device* cam, unsigned int cmd, void *arg)
+{
+	struct i2c_client* c = cam->sensor_client;
+
+	DBG(6, "Executing CMOS sensor command...")
+
+	if (c && c->driver->command)
+		return c->driver->command(cam->sensor_client, cmd, arg);
+	else
+		return -ENODEV;
+}
+
+
+/*--------------------------------------------------------------------------
+  Change some settings of the CMOS sensor.
+  Returns: 0 for success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int w9968cf_sensor_change_settings(struct w9968cf_device* cam)
+{
+	int rc;
+
+	/* Auto brightness */
+	rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_AUTOBRIGHT, 
+	                                cam->auto_brt);
+	if (SENSOR_FATAL_ERROR(rc))
+		return rc;
+
+	/* Auto exposure */
+	rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_AUTOEXP, 
+	                                cam->auto_exp);
+	if (SENSOR_FATAL_ERROR(rc))
+		return rc;
+
+	/* Banding filter */
+	rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_BANDFILT, 
+	                                cam->bandfilt);
+	if (SENSOR_FATAL_ERROR(rc))
+		return rc;
+
+	/* Light frequency */
+	rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_FREQ,
+	                                cam->lightfreq);
+	if (SENSOR_FATAL_ERROR(rc))
+		return rc;
+
+	/* Back light */
+	rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_BACKLIGHT,
+	                                cam->backlight);
+	if (SENSOR_FATAL_ERROR(rc))
+		return rc;
+
+	/* Mirror */
+	rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_MIRROR,
+	                                cam->mirror);
+	if (SENSOR_FATAL_ERROR(rc))
+		return rc;
+
+	return 0;
+}
+
+
+/*--------------------------------------------------------------------------
+  Get some current picture settings from the CMOS sensor.
+  Returns: 0 for success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int
+w9968cf_sensor_get_picture(struct w9968cf_device* cam, 
+                           struct video_picture* pict)
+{
+	int rc, v;
+
+	/* Don't return error if a setting is unsupported, or rest of settings
+	   will not be performed */
+
+	rc = w9968cf_sensor_get_control(cam, OVCAMCHIP_CID_CONT, &v);
+	if (SENSOR_FATAL_ERROR(rc))
+		return rc;
+	pict->contrast = v;
+
+	rc = w9968cf_sensor_get_control(cam, OVCAMCHIP_CID_BRIGHT, &v);
+	if (SENSOR_FATAL_ERROR(rc))
+		return rc;
+	pict->brightness = v;
+
+	rc = w9968cf_sensor_get_control(cam, OVCAMCHIP_CID_SAT, &v);
+	if (SENSOR_FATAL_ERROR(rc))
+		return rc;
+	pict->colour = v;
+
+	rc = w9968cf_sensor_get_control(cam, OVCAMCHIP_CID_HUE, &v);
+	if (SENSOR_FATAL_ERROR(rc))
+		return rc;
+	pict->hue = v;
+
+	pict->whiteness = W9968CF_WHITENESS; /* to do! */
+
+	DBG(5, "Got picture settings from the CMOS sensor.")
+
+	PDBGG("Brightness, contrast, hue, colour, whiteness are "
+	      "%d,%d,%d,%d,%d.", pict->brightness, pict->contrast,
+	      pict->hue, pict->colour, pict->whiteness)
+
+	return 0;
+}
+
+
+/*--------------------------------------------------------------------------
+  Change picture settings of the CMOS sensor.
+  Returns: 0 for success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int
+w9968cf_sensor_set_picture(struct w9968cf_device* cam, 
+                           struct video_picture pict)
+{
+	int rc;
+
+	rc = w9968cf_sensor_set_control(cam,OVCAMCHIP_CID_CONT, pict.contrast);
+	if (SENSOR_FATAL_ERROR(rc))
+		return rc;
+
+	if (!cam->auto_brt) {
+		rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_BRIGHT, 
+	                                        pict.brightness);
+		if (SENSOR_FATAL_ERROR(rc))
+			return rc;
+	}
+
+	rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_SAT, pict.colour);
+	if (SENSOR_FATAL_ERROR(rc))
+		return rc;
+
+	rc = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_HUE, pict.hue);
+	if (SENSOR_FATAL_ERROR(rc))
+		return rc;
+
+	PDBGG("Brightness, contrast, hue, colour, whiteness are "
+	      "%d,%d,%d,%d,%d.", pict.brightness, pict.contrast,
+	      pict.hue, pict.colour, pict.whiteness)
+
+	return 0;
+}
+
+
+
+/****************************************************************************
+ * Camera configuration                                                     *
+ ****************************************************************************/
+
+/*--------------------------------------------------------------------------
+  This function is called when the CMOS sensor is detected.
+  --------------------------------------------------------------------------*/
+static void w9968cf_sensor_configure(struct w9968cf_device* cam)
+{
+	/* NOTE: Make sure width and height are a multiple of 16 */
+
+	switch (cam->sensor_client->addr) {
+		case OV6xx0_SID:
+			cam->maxwidth = 352;
+			cam->maxheight = 288;
+			cam->minwidth = 64;
+			cam->minheight = 48;
+			break;
+		case OV7xx0_SID:
+			cam->maxwidth = 640;
+			cam->maxheight = 480;
+			cam->minwidth = 64;
+			cam->minheight = 48;
+			break;
+	}
+
+	/* These values depend on the ones in the ovxxx0.c sources */
+	switch (cam->sensor) {
+		case CC_OV7620:
+			cam->start_cropx = 287;
+			cam->start_cropy = 35;
+			/* Seems to work around a bug in the CMOS sensor */
+			cam->vs_polarity = 1;
+			cam->hs_polarity = 1;
+			break;
+		default:
+			cam->start_cropx = 320;
+			cam->start_cropy = 35;
+			cam->vs_polarity = 1;
+			cam->hs_polarity = 0;
+	}
+
+	DBG(5, "CMOS sensor %s configured.", symbolic(senlist, cam->sensor))
+}
+
+
+/*--------------------------------------------------------------------------
+  Fill some basic fields in the main device data structure.
+  This function is called once on w9968cf_usb_probe() for each recognized 
+  camera.
+  --------------------------------------------------------------------------*/
+static void
+w9968cf_configure_camera(struct w9968cf_device* cam,
+                         struct usb_device* udev,
+                         enum w9968cf_model_id mod_id,
+                         const unsigned short dev_nr)
+{
+	init_MUTEX(&cam->fileop_sem);
+	init_waitqueue_head(&cam->open);
+	spin_lock_init(&cam->urb_lock);
+	spin_lock_init(&cam->flist_lock);
+
+	cam->users = 0;
+	cam->disconnected = 0;
+	cam->usbdev = udev;
+	cam->id = mod_id;
+	cam->sensor = CC_UNKNOWN;
+
+	strcpy(cam->v4ldev.name, symbolic(camlist, mod_id));
+	cam->v4ldev.type = VID_TYPE_CAPTURE | VID_TYPE_SCALES;
+	cam->v4ldev.hardware = VID_HARDWARE_W9968CF;
+	cam->v4ldev.fops = &w9968cf_fops;
+	cam->v4ldev.priv = (void*)cam;
+	cam->v4ldev.minor = video_nr[dev_nr];
+
+	/* Calculate the alternate setting number (from 1 to 16)
+	   according to the 'packet_size' module parameter */
+	if (packet_size[dev_nr] < W9968CF_MIN_PACKET_SIZE)
+		packet_size[dev_nr] = W9968CF_MIN_PACKET_SIZE;
+	for (cam->altsetting = 1;
+	     packet_size[dev_nr] < wMaxPacketSize[cam->altsetting-1];
+	     cam->altsetting++);
+
+	cam->max_buffers = (max_buffers[dev_nr] < 2 || 
+	                    max_buffers[dev_nr] > W9968CF_MAX_BUFFERS)
+	                   ? W9968CF_BUFFERS : max_buffers[dev_nr];
+
+	cam->double_buffer = (double_buffer[dev_nr] == 0 || 
+	                      double_buffer[dev_nr] == 1)
+	                     ? double_buffer[dev_nr] : W9968CF_DOUBLE_BUFFER;
+
+	cam->clamping = (clamping[dev_nr] == 0 || clamping[dev_nr] == 1)
+	                ? clamping[dev_nr] : W9968CF_CLAMPING;
+	
+	cam->filter_type = (filter_type[dev_nr] == 0 ||
+	                    filter_type[dev_nr] == 1 ||
+	                    filter_type[dev_nr] == 2)
+	                   ? filter_type[dev_nr] : W9968CF_FILTER_TYPE;
+
+	cam->capture = 1;
+
+	cam->largeview = (largeview[dev_nr] == 0 || largeview[dev_nr] == 1)
+	                 ? largeview[dev_nr] : W9968CF_LARGEVIEW;
+
+	cam->decompression = (decompression[dev_nr] == 0 || 
+	                      decompression[dev_nr] == 1 ||
+	                      decompression[dev_nr] == 2)
+	                     ? decompression[dev_nr] : W9968CF_DECOMPRESSION;
+
+	cam->upscaling = (upscaling[dev_nr] == 0 || 
+	                  upscaling[dev_nr] == 1)
+	                 ? upscaling[dev_nr] : W9968CF_UPSCALING;
+
+	cam->auto_brt = (autobright[dev_nr] == 0 || autobright[dev_nr] == 1)
+	                ? autobright[dev_nr] : W9968CF_AUTOBRIGHT;
+
+	cam->auto_exp = (autoexp[dev_nr] == 0 || autoexp[dev_nr] == 1)
+	                ? autoexp[dev_nr] : W9968CF_AUTOEXP;
+
+	cam->lightfreq = (lightfreq[dev_nr] == 50 || lightfreq[dev_nr] == 60)
+	                 ? lightfreq[dev_nr] : W9968CF_LIGHTFREQ;
+
+	cam->bandfilt = (bandingfilter[dev_nr] == 0 || 
+	                 bandingfilter[dev_nr] == 1)
+	                ? bandingfilter[dev_nr] : W9968CF_BANDINGFILTER;
+
+	cam->backlight = (backlight[dev_nr] == 0 || backlight[dev_nr] == 1)
+	                 ? backlight[dev_nr] : W9968CF_BACKLIGHT;
+
+	cam->clockdiv = (clockdiv[dev_nr] == -1 || clockdiv[dev_nr] >= 0)
+	                ? clockdiv[dev_nr] : W9968CF_CLOCKDIV;
+
+	cam->mirror = (mirror[dev_nr] == 0 || mirror[dev_nr] == 1)
+	              ? mirror[dev_nr] : W9968CF_MIRROR;
+
+	cam->sensor_mono = (sensor_mono[dev_nr]==0 || sensor_mono[dev_nr]==1)
+	                   ? sensor_mono[dev_nr] : W9968CF_SENSOR_MONO;
+
+	cam->picture.brightness = brightness[dev_nr];
+	cam->picture.hue = hue[dev_nr];
+	cam->picture.colour = colour[dev_nr];
+	cam->picture.contrast = contrast[dev_nr];
+	cam->picture.whiteness = whiteness[dev_nr];
+	if (w9968cf_valid_palette(force_palette[dev_nr])) {
+		cam->picture.palette = force_palette[dev_nr];
+		cam->force_palette = 1;
+	} else {
+		cam->force_palette = 0;
+		if (cam->decompression == 0)
+			cam->picture.palette = W9968CF_PALETTE_DECOMP_OFF;
+		else if (cam->decompression == 1)
+			cam->picture.palette = W9968CF_PALETTE_DECOMP_FORCE;
+		else
+			cam->picture.palette = W9968CF_PALETTE_DECOMP_ON;
+	}
+
+	cam->force_rgb = (force_rgb[dev_nr] == 0 || force_rgb[dev_nr] == 1)
+	                 ? force_rgb[dev_nr] : W9968CF_FORCE_RGB;
+
+	cam->window.x = 0;
+	cam->window.y = 0;
+	cam->window.width = W9968CF_WIDTH;
+	cam->window.height = W9968CF_HEIGHT;
+	cam->window.chromakey = 0;
+	cam->window.clipcount = 0;
+	cam->window.flags = 0;
+
+	/* If the video post-processing module is not present, some paramaters
+	   must be overridden: */
+	if (!w9968cf_vppmod_present) {
+		if (cam->decompression == 1)
+			cam->decompression = 2;
+		cam->upscaling = 0;
+		if (cam->picture.palette != VIDEO_PALETTE_UYVY)
+			cam->force_palette = 0;
+		cam->picture.palette = VIDEO_PALETTE_UYVY;
+	}
+
+	cam->picture.depth = w9968cf_valid_depth(cam->picture.palette);
+
+	DBG(3, "%s configured with settings #%d:", 
+	    symbolic(camlist, cam->id), dev_nr)
+	
+	DBG(3, "- Data packet size for USB isochrnous transfer: %d bytes.",
+	    wMaxPacketSize[cam->altsetting-1])
+	
+	DBG(3, "- Number of requested video frame buffers: %d", 
+	    cam->max_buffers)
+
+	if (cam->double_buffer)
+		DBG(3, "- Hardware double buffering enabled.")
+	else 
+		DBG(3, "- Hardware double buffering disabled.")
+
+	if (cam->filter_type == 0)
+		DBG(3, "- Video filtering disabled.")
+	else if (cam->filter_type == 1)
+		DBG(3, "- Video filtering enabled: type 1-2-1.")
+	else if (cam->filter_type == 2)
+		DBG(3, "- Video filtering enabled: type 2-3-6-3-2.")
+
+	if (cam->clamping)
+		DBG(3, "- Video data clamping (CCIR-601 format) enabled.")
+	else
+		DBG(3, "- Video data clamping (CCIR-601 format) disabled.")
+
+	if (cam->largeview)
+		DBG(3, "- Large view enabled.")
+	else
+		DBG(3, "- Large view disabled.")
+
+	if ((cam->decompression) == 0 && (!cam->force_palette))
+		DBG(3, "- Decompression disabled.")
+	else if ((cam->decompression) == 1 && (!cam->force_palette))
+		DBG(3, "- Decompression forced.")
+	else if ((cam->decompression) == 2 && (!cam->force_palette))
+		DBG(3, "- Decompression allowed.")
+
+	if (cam->upscaling)
+		DBG(3, "- Software image scaling enabled.")
+	else
+		DBG(3, "- Software image scaling disabled.")
+
+	if (cam->force_palette)
+		DBG(3, "- Image palette forced to %s.",
+		    symbolic(v4l1_plist, cam->picture.palette))
+
+	if (cam->force_rgb)
+		DBG(3, "- RGB component ordering will be used instead of BGR.")
+
+	if (cam->auto_brt)
+		DBG(3, "- Auto brightness enabled.")
+	else
+		DBG(3, "- Auto brightness disabled.")
+
+	if (cam->auto_exp)
+		DBG(3, "- Auto exposure enabled.")
+	else
+		DBG(3, "- Auto exposure disabled.")
+
+	if (cam->backlight)
+		DBG(3, "- Backlight exposure algorithm enabled.")
+	else
+		DBG(3, "- Backlight exposure algorithm disabled.")
+
+	if (cam->mirror)
+		DBG(3, "- Mirror enabled.")
+	else
+		DBG(3, "- Mirror disabled.")
+
+	if (cam->bandfilt)
+		DBG(3, "- Banding filter enabled.")
+	else
+		DBG(3, "- Banding filter disabled.")
+
+	DBG(3, "- Power lighting frequency: %d", cam->lightfreq)
+
+	if (cam->clockdiv == -1)
+		DBG(3, "- Automatic clock divisor enabled.")
+	else
+		DBG(3, "- Clock divisor: %d", cam->clockdiv)
+
+	if (cam->sensor_mono)
+		DBG(3, "- CMOS sensor used as monochrome.")
+	else
+		DBG(3, "- CMOS sensor not used as monochrome.")
+}
+
+
+/*--------------------------------------------------------------------------
+  Release the resources used by the driver.
+  This function is called on disconnect 
+  (or on close if deallocation has been deferred)
+  --------------------------------------------------------------------------*/
+static void w9968cf_release_resources(struct w9968cf_device* cam)
+{
+	down(&w9968cf_devlist_sem);
+
+	DBG(2, "V4L device deregistered: /dev/video%d", cam->v4ldev.minor)
+
+	video_unregister_device(&cam->v4ldev);
+	list_del(&cam->v4llist);
+	i2c_del_adapter(&cam->i2c_adapter);
+	w9968cf_deallocate_memory(cam);
+	kfree(cam->control_buffer);
+	kfree(cam->data_buffer);
+
+	up(&w9968cf_devlist_sem);
+
+	DBG(5, "Resources released.")
+}
+
+
+
+/****************************************************************************
+ * Video4Linux interface                                                    *
+ ****************************************************************************/
+
+static int w9968cf_open(struct inode* inode, struct file* filp)
+{
+	struct w9968cf_device* cam =
+	  (struct w9968cf_device*)video_devdata(filp)->priv;    
+	int err;
+
+	down(&cam->dev_sem);
+
+	if (cam->sensor == CC_UNKNOWN) {
+		DBG(2, "No supported CMOS sensor has been detected by the "
+		       "'ovcamchip' module for the %s (/dev/video%d). Make "
+		       "sure it is loaded *before* the 'w9968cf' module.", 
+		    symbolic(camlist, cam->id),cam->v4ldev.minor)
+		up(&cam->dev_sem);
+		return -ENODEV;
+	}
+
+	if (cam->users) {
+		DBG(2, "%s (/dev/video%d) has been already occupied by '%s'.",
+		    symbolic(camlist, cam->id),cam->v4ldev.minor, cam->command)
+		if ((filp->f_flags & O_NONBLOCK)||(filp->f_flags & O_NDELAY)) {
+			up(&cam->dev_sem);
+			return -EWOULDBLOCK;
+		}
+		up(&cam->dev_sem);
+		err = wait_event_interruptible(cam->open, cam->disconnected ||
+		                               (cam->users == 0));
+		if (err)
+			return err;
+		if (cam->disconnected)
+			return -ENODEV;
+		down(&cam->dev_sem);
+	}
+
+	DBG(5, "Opening the %s, /dev/video%d ...",
+	    symbolic(camlist, cam->id), cam->v4ldev.minor)
+
+	cam->streaming = 0;
+	cam->misconfigured = 0;
+
+	if (!w9968cf_vppmod_present)
+		w9968cf_vppmod_detect();
+
+	if ((err = w9968cf_allocate_memory(cam)))
+		goto deallocate_memory;
+
+	if ((err = w9968cf_init_chip(cam)))
+		goto deallocate_memory;
+
+	if ((err = w9968cf_start_transfer(cam)))
+		goto deallocate_memory;
+
+	filp->private_data = (void*)cam;
+
+	cam->users++;
+	strcpy(cam->command, current->comm);
+
+	init_waitqueue_head(&cam->wait_queue);
+
+	up(&cam->dev_sem);
+
+	DBG(5, "Video device is open.")
+	return 0;
+
+deallocate_memory:
+	w9968cf_deallocate_memory(cam);
+	DBG(2, "Failed to open the video device.")
+	up(&cam->dev_sem);
+	return err;
+}
+
+
+static int w9968cf_release(struct inode* inode, struct file* filp)
+{
+	struct w9968cf_device* cam = 
+	  (struct w9968cf_device*)video_devdata(filp)->priv;
+
+	down(&cam->dev_sem); /* prevent disconnect() to be called */
+
+	w9968cf_stop_transfer(cam);
+
+	if (cam->disconnected) {
+		w9968cf_release_resources(cam);
+		up(&cam->dev_sem);
+		kfree(cam);
+		return 0;
+	}
+
+	cam->users--;
+	w9968cf_deallocate_memory(cam);
+
+	if (waitqueue_active(&cam->open))
+		wake_up_interruptible(&cam->open);
+
+	DBG(5, "Video device closed.")
+	up(&cam->dev_sem);
+	return 0;
+}
+
+
+static ssize_t
+w9968cf_read(struct file* filp, char* buf, size_t count, loff_t* f_pos)
+{
+	struct w9968cf_device* cam =
+	  (struct w9968cf_device*)video_devdata(filp)->priv;
+	struct w9968cf_frame_t* fr;
+	int err = 0;
+
+	if (filp->f_flags & O_NONBLOCK)
+		return -EWOULDBLOCK;
+
+	if (down_interruptible(&cam->fileop_sem))
+		return -ERESTARTSYS;
+
+	if (cam->disconnected) {
+		DBG(2, "Device not present.")
+		up(&cam->fileop_sem);
+		return -ENODEV;
+	}
+
+	if (cam->misconfigured) {
+		DBG(2, "The camera is misconfigured. Close and open it again.")
+		up(&cam->fileop_sem);
+		return -EIO;
+	}
+
+	if (!cam->frame[0].queued)
+		w9968cf_push_frame(cam, 0);
+
+	if (!cam->frame[1].queued)
+		w9968cf_push_frame(cam, 1);
+
+	err = wait_event_interruptible(cam->wait_queue,
+	                               cam->frame[0].status == F_READY ||
+	                               cam->frame[1].status == F_READY ||
+	                               cam->disconnected);
+	if (err) {
+		up(&cam->fileop_sem);
+		return err;
+	}
+	if (cam->disconnected) {
+		up(&cam->fileop_sem);
+		return -ENODEV;
+	}
+
+	fr = (cam->frame[0].status == F_READY) ? &cam->frame[0]:&cam->frame[1];
+
+	if (w9968cf_vppmod_present)
+		w9968cf_postprocess_frame(cam, fr);
+
+	if (count > fr->length)
+		count = fr->length;
+
+	if (copy_to_user(buf, fr->buffer, count)) {
+		fr->status = F_UNUSED;
+		up(&cam->fileop_sem);
+		return -EFAULT;
+	}
+	*f_pos += count;
+
+	fr->status = F_UNUSED;
+
+	DBG(5, "%d bytes read.", count)
+
+	up(&cam->fileop_sem);
+	return count;
+}
+
+
+static int w9968cf_mmap(struct file* filp, struct vm_area_struct *vma)
+{
+	struct w9968cf_device* cam =
+	  (struct w9968cf_device*)video_devdata(filp)->priv;
+
+	unsigned long vsize = vma->vm_end - vma->vm_start,
+	              psize = cam->nbuffers * w9968cf_get_max_bufsize(cam),
+	              start = vma->vm_start,
+	              pos = (unsigned long)cam->frame[0].buffer,
+	              page;
+
+	if (cam->disconnected) {
+		DBG(2, "Device not present.")
+		return -ENODEV;
+	}
+
+	if (cam->misconfigured) {
+		DBG(2, "The camera is misconfigured. Close and open it again.")
+		return -EIO;
+	}
+
+	PDBGG("mmapping %li bytes...", vsize)
+
+        if (vsize > psize - (vma->vm_pgoff << PAGE_SHIFT))
+		return -EAGAIN;
+
+	while (vsize > 0) {
+		page = kvirt_to_pa(pos) + vma->vm_pgoff;
+		if (remap_page_range(vma, start, page, PAGE_SIZE, 
+		                     vma->vm_page_prot))
+			return -EAGAIN;
+		start += PAGE_SIZE;
+		pos += PAGE_SIZE;
+		vsize = (vsize > PAGE_SIZE) ? vsize-PAGE_SIZE : 0;
+	}
+
+	DBG(5, "mmap method successfully called.")
+	return 0;
+}
+
+
+static int
+w9968cf_ioctl(struct inode* inode, struct file* filp,
+              unsigned int cmd, unsigned long arg)
+{
+	struct w9968cf_device* cam =
+	  (struct w9968cf_device*)video_devdata(filp)->priv;
+	int err;
+
+	if (down_interruptible(&cam->fileop_sem))
+		return -ERESTARTSYS;
+
+	if (cam->disconnected) {
+		DBG(2, "Device not present.")
+		up(&cam->fileop_sem);
+		return -ENODEV;
+	}
+
+	if (cam->misconfigured) {
+		DBG(2, "The camera is misconfigured. Close and open it again.")
+		up(&cam->fileop_sem);
+		return -EIO;
+	}
+
+	err = w9968cf_do_ioctl(cam, cmd, (void*)arg);
+
+	up(&cam->fileop_sem);
+	return err;
+}
+
+
+static int 
+w9968cf_do_ioctl(struct w9968cf_device* cam, unsigned cmd, void* arg)
+{
+	const char* v4l1_ioctls[] = {
+		"?", "CGAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", 
+		"GPICT", "SPICT", "CCAPTURE", "GWIN", "SWIN", "GFBUF",
+		"SFBUF", "KEY", "GFREQ", "SFREQ", "GAUDIO", "SAUDIO",
+		"SYNC", "MCAPTURE", "GMBUF", "GUNIT", "GCAPTURE", "SCAPTURE",
+		"SPLAYMODE", "SWRITEMODE", "GPLAYINFO", "SMICROCODE", 
+		"GVBIFMT", "SVBIFMT" 
+	};
+
+	#define V4L1_IOCTL(cmd) \
+	        ((_IOC_NR((cmd)) < sizeof(v4l1_ioctls)/sizeof(char*)) ? \
+	        v4l1_ioctls[_IOC_NR((cmd))] : "???")
+
+	switch (cmd) {
+
+	case VIDIOCGCAP: /* get video capability */
+	{
+		struct video_capability cap = {
+			.type = VID_TYPE_CAPTURE | VID_TYPE_SCALES,
+			.channels = 1,
+			.audios = 0,
+			.minwidth = cam->minwidth,
+			.minheight = cam->minheight,
+		};
+		sprintf(cap.name, "W996[87]CF USB Camera #%d", 
+		        cam->v4ldev.minor);
+		cap.maxwidth = (cam->upscaling && w9968cf_vppmod_present)
+		               ? W9968CF_MAX_WIDTH : cam->maxwidth;
+		cap.maxheight = (cam->upscaling && w9968cf_vppmod_present)
+		                ? W9968CF_MAX_HEIGHT : cam->maxheight;
+
+		if (copy_to_user(arg, &cap, sizeof(cap)))
+			return -EFAULT;
+
+		DBG(5, "VIDIOCGCAP successfully called.")
+		return 0;
+	}
+
+	case VIDIOCGCHAN: /* get video channel informations */
+	{
+		struct video_channel chan;
+		if (copy_from_user(&chan, arg, sizeof(chan)))
+			return -EFAULT;
+
+		if (chan.channel != 0)
+			return -EINVAL;
+
+		strcpy(chan.name, "Camera");
+		chan.tuners = 0;
+		chan.flags = 0;
+		chan.type = VIDEO_TYPE_CAMERA;
+		chan.norm = VIDEO_MODE_AUTO;
+
+		if (copy_to_user(arg, &chan, sizeof(chan)))
+			return -EFAULT;
+
+		DBG(5, "VIDIOCGCHAN successfully called.")
+		return 0;
+	}
+
+	case VIDIOCSCHAN: /* set active channel */
+	{
+		struct video_channel chan;
+
+		if (copy_from_user(&chan, arg, sizeof(chan)))
+			return -EFAULT;
+
+		if (chan.channel != 0)
+			return -EINVAL;
+
+		DBG(5, "VIDIOCSCHAN successfully called.")
+		return 0;
+	}
+
+	case VIDIOCGPICT: /* get image properties of the picture */
+	{
+		struct video_picture pict;
+
+		if (w9968cf_sensor_get_picture(cam, &pict))
+			return -EIO;
+
+		pict.depth = cam->picture.depth;
+		pict.palette = cam->picture.palette;
+
+		if (copy_to_user(arg, &pict, sizeof(pict)))
+			return -EFAULT;
+
+		DBG(5, "VIDIOCGPICT successfully called.")
+		return 0;
+	}
+
+	case VIDIOCSPICT: /* change picture settings */
+	{
+		struct video_picture pict;
+		int err = 0;
+
+		if (copy_from_user(&pict, arg, sizeof(pict)))
+			return -EFAULT;
+
+		if ( (cam->force_palette || !w9968cf_vppmod_present) 
+		     && pict.palette != cam->picture.palette ) {
+			DBG(4, "Palette %s rejected. Only %s is allowed.",
+			    symbolic(v4l1_plist, pict.palette),
+			    symbolic(v4l1_plist, cam->picture.palette))
+			return -EINVAL;
+		}
+
+		if (!w9968cf_valid_palette(pict.palette)) {
+			DBG(4, "Palette %s not supported. VIDIOCSPICT failed.",
+			    symbolic(v4l1_plist, pict.palette))
+			return -EINVAL;
+		}
+
+		if (pict.depth != w9968cf_valid_depth(pict.palette)) {
+			DBG(4, "Depth %d bpp is not supported for %s palette. "
+			       "VIDIOCSPICT failed.", 
+			    pict.depth, symbolic(v4l1_plist, pict.palette))
+			return -EINVAL;
+		}
+
+		if (!cam->force_palette) {
+		   if (cam->decompression == 0) {
+		      if (w9968cf_need_decompression(pict.palette)) {
+		         DBG(4, "Decompression disabled: palette %s is not "
+		                "allowed. VIDIOCSPICT failed.",
+		             symbolic(v4l1_plist, pict.palette))
+		         return -EINVAL;
+		      }
+		   } else if (cam->decompression == 1) {
+		      if (!w9968cf_need_decompression(pict.palette)) {
+		         DBG(4, "Decompression forced: palette %s is not "
+		                "allowed. VIDIOCSPICT failed.",
+		             symbolic(v4l1_plist, pict.palette))
+		         return -EINVAL;
+		      }
+		   }
+		}
+
+		if (pict.palette != cam->picture.palette ||
+		    pict.depth   != cam->picture.depth)
+		{
+			if(*cam->requested_frame
+			   || cam->frame_current->queued) {
+				err = wait_event_interruptible
+				      ( cam->wait_queue,
+				        cam->disconnected ||
+				        (!*cam->requested_frame &&
+				         !cam->frame_current->queued) );
+				if (err)
+					return err;
+				if (cam->disconnected)
+					return -ENODEV;
+			}
+
+			if (w9968cf_stop_transfer(cam))
+				goto ioctl_fail;
+
+			if (w9968cf_set_picture(cam, pict))
+				goto ioctl_fail;
+
+			if (w9968cf_start_transfer(cam))
+				goto ioctl_fail;
+
+		} else if ( ((pict.brightness != cam->picture.brightness) &&
+		            (!cam->auto_brt)) ||
+		            pict.hue != cam->picture.hue ||
+		            pict.colour != cam->picture.colour ||
+		            pict.contrast != cam->picture.contrast ||
+		            pict.whiteness != cam->picture.whiteness ) {
+			if (w9968cf_sensor_set_picture(cam, pict))
+				return -EIO;
+		}
+
+		DBG(5, "VIDIOCSPICT successfully called.")
+		return 0;
+	}
+
+	case VIDIOCSWIN: /* set capture area */
+	{
+		struct video_window win;
+		int err = 0;
+
+		if (copy_from_user(&win, arg, sizeof(win)))
+			return -EFAULT;
+
+		DBG(6, "VIDIOCSWIN called: clipcount=%d, flags=%d, "
+		       "x=%d, y=%d, %dx%d", win.clipcount, win.flags,
+		    win.x, win.y, win.width, win.height)
+
+		if (win.clipcount != 0 || win.flags != 0)
+			return -EINVAL;
+
+		if ((err = w9968cf_adjust_window_size(cam, (u16*)&win.width,
+		                                      (u16*)&win.height))) {
+			DBG(4, "Resolution not supported (%dx%d)."
+			       "VIDIOCSWIN failed.", win.width, win.height)
+			return err;
+		}
+
+		if (win.x != cam->window.x ||
+		    win.y != cam->window.y ||
+		    win.width != cam->window.width ||
+		    win.height != cam->window.height) {
+
+			if(*cam->requested_frame
+			   || cam->frame_current->queued) {
+				err = wait_event_interruptible
+				      ( cam->wait_queue,
+				        cam->disconnected ||
+				        (!*cam->requested_frame &&
+				         !cam->frame_current->queued) );
+				if (err)
+					return err;
+				if (cam->disconnected)
+					return -ENODEV;
+			}
+
+			if (w9968cf_stop_transfer(cam))
+				goto ioctl_fail;
+
+			/* This _must_ be called before set_window() */
+			if (w9968cf_set_picture(cam, cam->picture))
+				goto ioctl_fail;
+
+			if (w9968cf_set_window(cam, win))
+				goto ioctl_fail;
+
+			if (w9968cf_start_transfer(cam))
+				goto ioctl_fail;
+		}
+
+		DBG(5, "VIDIOCSWIN successfully called. ")
+		return 0;
+	}
+
+	case VIDIOCGWIN: /* get current window properties */
+	{
+		if (copy_to_user(arg,&cam->window,sizeof(struct video_window)))
+			return -EFAULT;
+
+		DBG(5, "VIDIOCGWIN successfully called.")
+		return 0;
+	}
+
+	case VIDIOCGMBUF: /* request for memory (mapped) buffer */
+	{
+		struct video_mbuf mbuf;
+		u8 i;
+
+		mbuf.size = cam->nbuffers * w9968cf_get_max_bufsize(cam);
+		mbuf.frames = cam->nbuffers;
+		for (i = 0; i < cam->nbuffers; i++)
+			mbuf.offsets[i] = (unsigned long)cam->frame[i].buffer -
+			                  (unsigned long)cam->frame[0].buffer;
+
+		if (copy_to_user(arg, &mbuf, sizeof(mbuf)))
+			return -EFAULT;
+
+		DBG(5, "VIDIOCGMBUF successfully called.")
+		return 0;
+	}
+
+	case VIDIOCMCAPTURE: /* start the capture to a frame */
+	{
+		struct video_mmap mmap;
+		struct w9968cf_frame_t* fr;
+		int err = 0;
+
+		if (copy_from_user(&mmap, arg, sizeof(mmap)))
+			return -EFAULT;
+
+		DBG(6, "VIDIOCMCAPTURE called: frame #%d, format=%s, %dx%d",
+		    mmap.frame, symbolic(v4l1_plist, mmap.format), 
+		    mmap.width, mmap.height)
+
+		if (mmap.frame >= cam->nbuffers) {
+			DBG(4, "Invalid frame number (%d). "
+			       "VIDIOCMCAPTURE failed.", mmap.frame)
+			return -EINVAL;
+		}
+
+		if (mmap.format!=cam->picture.palette && 
+		    (cam->force_palette || !w9968cf_vppmod_present)) {
+			DBG(4, "Palette %s rejected. Only %s is allowed.",
+			    symbolic(v4l1_plist, mmap.format),
+			    symbolic(v4l1_plist, cam->picture.palette))
+			return -EINVAL;
+		}
+
+		if (!w9968cf_valid_palette(mmap.format)) {
+			DBG(4, "Palette %s not supported. "
+			       "VIDIOCMCAPTURE failed.", 
+			    symbolic(v4l1_plist, mmap.format))
+			return -EINVAL;
+		}
+
+		if (!cam->force_palette) {
+		   if (cam->decompression == 0) {
+		      if (w9968cf_need_decompression(mmap.format)) {
+		         DBG(4, "Decompression disabled: palette %s is not "
+		                "allowed. VIDIOCSPICT failed.",
+		             symbolic(v4l1_plist, mmap.format))
+		         return -EINVAL;
+		      }
+		   } else if (cam->decompression == 1) {
+		      if (!w9968cf_need_decompression(mmap.format)) {
+		         DBG(4, "Decompression forced: palette %s is not "
+		                "allowed. VIDIOCSPICT failed.",
+		             symbolic(v4l1_plist, mmap.format))
+		         return -EINVAL;
+		      }
+		   }
+		}
+
+		if (w9968cf_adjust_window_size(cam, (u16*)&mmap.width, 
+		                               (u16*)&mmap.height)) {
+			DBG(4, "Resolution not supported (%dx%d). "
+			       "VIDIOCMCAPTURE failed.",
+			    mmap.width, mmap.height)
+			return -EINVAL;
+		}
+
+		fr = &cam->frame[mmap.frame];
+
+		if (mmap.width  != cam->window.width ||
+		    mmap.height != cam->window.height ||
+		    mmap.format != cam->picture.palette) {
+
+			struct video_window win;
+			struct video_picture pict;
+
+			if(*cam->requested_frame
+			   || cam->frame_current->queued) {
+				DBG(6, "VIDIOCMCAPTURE. Change settings for "
+				       "frame #%d: %dx%d, format %s. Wait...",
+				    mmap.frame, mmap.width, mmap.height,
+			            symbolic(v4l1_plist, mmap.format))
+				err = wait_event_interruptible
+				      ( cam->wait_queue,
+				        cam->disconnected ||
+				        (!*cam->requested_frame &&
+				         !cam->frame_current->queued) );
+				if (err)
+					return err;
+				if (cam->disconnected)
+					return -ENODEV;
+			}
+
+			memcpy(&win, &cam->window, sizeof(win));
+			memcpy(&pict, &cam->picture, sizeof(pict));
+			win.width = mmap.width;
+			win.height = mmap.height;
+			pict.palette = mmap.format;
+
+			if (w9968cf_stop_transfer(cam))
+				goto ioctl_fail;
+
+			/* This before set_window */
+			if (w9968cf_set_picture(cam, pict)) 
+				goto ioctl_fail;
+
+			if (w9968cf_set_window(cam, win))
+				goto ioctl_fail;
+
+			if (w9968cf_start_transfer(cam))
+				goto ioctl_fail;
+
+		} else 	if (fr->queued) {
+
+			DBG(6, "Wait until frame #%d is free.", mmap.frame)
+			
+			err = wait_event_interruptible(cam->wait_queue, 
+			                               cam->disconnected ||
+			                               (!fr->queued));
+			if (err)
+				return err;
+			if (cam->disconnected)
+				return -ENODEV;
+		}
+
+		w9968cf_push_frame(cam, mmap.frame);
+		DBG(5, "VIDIOCMCAPTURE(%d): successfully called.", mmap.frame)
+		return 0;
+	}
+
+	case VIDIOCSYNC: /* wait until the capture of a frame is finished */
+	{
+		unsigned int f_num = *((unsigned int *) arg);
+		struct w9968cf_frame_t* fr;
+		int err = 0;
+
+		if (f_num >= cam->nbuffers) {
+			DBG(4, "Invalid frame number (%d). "
+			       "VIDIOCMCAPTURE failed.", f_num)
+			return -EINVAL;
+		}
+
+		DBG(6, "VIDIOCSYNC called for frame #%d", f_num)
+
+		fr = &cam->frame[f_num];
+
+		switch (fr->status) {
+		case F_UNUSED:
+			if (!fr->queued) {
+				DBG(4, "VIDIOSYNC: Frame #%d not requested!",
+				    f_num)
+				return -EFAULT;
+			}
+		case F_ERROR:
+		case F_GRABBING:
+			err = wait_event_interruptible(cam->wait_queue, 
+			                               (fr->status == F_READY)
+			                               || cam->disconnected);
+			if (err)
+				return err;
+			if (cam->disconnected)
+				return -ENODEV;
+			break;
+		case F_READY:
+			break;
+		}
+
+		if (w9968cf_vppmod_present)
+			w9968cf_postprocess_frame(cam, fr);
+
+		fr->status = F_UNUSED;
+
+		DBG(5, "VIDIOCSYNC(%d) successfully called.", f_num)
+		return 0;
+	}
+
+	case VIDIOCGUNIT:/* report the unit numbers of the associated devices*/
+	{
+		struct video_unit unit = {
+			.video = cam->v4ldev.minor,
+			.vbi = VIDEO_NO_UNIT,
+			.radio = VIDEO_NO_UNIT,
+			.audio = VIDEO_NO_UNIT,
+			.teletext = VIDEO_NO_UNIT,
+		};
+
+		if (copy_to_user(arg, &unit, sizeof(unit)))
+			return -EFAULT;
+
+		DBG(5, "VIDIOCGUNIT successfully called.")
+		return 0;
+	}
+
+	case VIDIOCKEY:
+		return 0;
+
+	case VIDIOCGFBUF:
+	{
+		struct video_buffer* buffer = (struct video_buffer*)arg;
+
+		memset(buffer, 0, sizeof(struct video_buffer));
+
+		DBG(5, "VIDIOCGFBUF successfully called.")
+		return 0;
+	}
+
+	case VIDIOCGTUNER:
+	{
+		struct video_tuner tuner;
+		if (copy_from_user(&tuner, arg, sizeof(tuner)))
+			return -EFAULT;
+
+		if (tuner.tuner != 0);
+			return -EINVAL;
+
+		strcpy(tuner.name, "no_tuner");
+		tuner.rangelow = 0;
+		tuner.rangehigh = 0;
+		tuner.flags = VIDEO_TUNER_NORM;
+		tuner.mode = VIDEO_MODE_AUTO;
+		tuner.signal = 0xffff;
+
+		if (copy_to_user(arg, &tuner, sizeof(tuner)))
+			return -EFAULT;
+
+		DBG(5, "VIDIOCGTUNER successfully called.")
+		return 0;
+	}
+
+	case VIDIOCSTUNER:
+	{
+		struct video_tuner tuner;
+		if (copy_from_user(&tuner, arg, sizeof(tuner)))
+			return -EFAULT;
+
+		if (tuner.tuner != 0)
+			return -EINVAL;
+
+		if (tuner.mode != VIDEO_MODE_AUTO)
+			return -EINVAL;
+
+		DBG(5, "VIDIOCSTUNER successfully called.")
+		return 0;
+	}
+
+	case VIDIOCSFBUF:
+	case VIDIOCCAPTURE:
+	case VIDIOCGFREQ:
+	case VIDIOCSFREQ:
+	case VIDIOCGAUDIO:
+	case VIDIOCSAUDIO:
+	case VIDIOCSPLAYMODE:
+	case VIDIOCSWRITEMODE:
+	case VIDIOCGPLAYINFO:
+	case VIDIOCSMICROCODE:
+	case VIDIOCGVBIFMT:
+	case VIDIOCSVBIFMT:
+		DBG(4, "Unsupported V4L1 IOCtl: VIDIOC%s "
+		       "(type 0x%01X, "
+		       "n. 0x%01X, "
+		       "dir. 0x%01X, " 
+		       "size 0x%02X).",
+		    V4L1_IOCTL(cmd),
+		    _IOC_TYPE(cmd),_IOC_NR(cmd),_IOC_DIR(cmd),_IOC_SIZE(cmd))
+
+		return -EINVAL;
+
+	default:
+		DBG(4, "Invalid V4L1 IOCtl: VIDIOC%s "
+		       "type 0x%01X, "
+		       "n. 0x%01X, "
+		       "dir. 0x%01X, "
+		       "size 0x%02X.",
+		    V4L1_IOCTL(cmd),
+		    _IOC_TYPE(cmd),_IOC_NR(cmd),_IOC_DIR(cmd),_IOC_SIZE(cmd))
+
+		return -ENOIOCTLCMD;
+
+	} /* end of switch */
+
+ioctl_fail:
+	cam->misconfigured = 1;
+	DBG(1, "VIDIOC%s failed because of hardware problems. "
+	       "To use the camera, close and open it again.", V4L1_IOCTL(cmd))
+	return -EFAULT;
+}
+
+
+static struct file_operations w9968cf_fops = {
+	.owner =   THIS_MODULE,
+	.open =    w9968cf_open,
+	.release = w9968cf_release,
+	.read =    w9968cf_read,
+	.ioctl =   w9968cf_ioctl,
+	.mmap =    w9968cf_mmap,
+	.llseek =  no_llseek,
+};
+
+
+
+/****************************************************************************
+ * USB probe and V4L registration, disconnect and id_table[] definition     *
+ ****************************************************************************/
+
+static int
+w9968cf_usb_probe(struct usb_interface* intf, const struct usb_device_id* id)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct w9968cf_device* cam;
+	int err = 0;
+	enum w9968cf_model_id mod_id;
+	struct list_head* ptr;
+	u8 sc = 0; /* number of simultaneous cameras */
+	static unsigned short dev_nr = 0; /* we are handling device number n */
+
+	if (udev->descriptor.idVendor  == winbond_id_table[0].idVendor &&
+	    udev->descriptor.idProduct == winbond_id_table[0].idProduct)
+		mod_id = W9968CF_MOD_CLVBWGP; /* see camlist[] table */
+
+	else if (udev->descriptor.idVendor  == winbond_id_table[1].idVendor &&
+	         udev->descriptor.idProduct == winbond_id_table[1].idProduct)
+		mod_id = W9968CF_MOD_GENERIC; /* see camlist[] table */
+
+	else
+		return -ENODEV;
+
+	/* We don't handle multi-config cameras */
+	if (udev->descriptor.bNumConfigurations != 1)
+		return -ENODEV;
+
+	DBG(2, "%s detected.", symbolic(camlist, mod_id))
+
+	if (simcams > W9968CF_MAX_DEVICES)
+		simcams = W9968CF_SIMCAMS;
+
+	/* How many cameras are connected ? */
+	down(&w9968cf_devlist_sem);
+	list_for_each(ptr, &w9968cf_dev_list)
+		sc++;
+	up(&w9968cf_devlist_sem);
+
+	if (sc >= simcams) {
+		DBG(2, "Device rejected: too many connected cameras "
+		       "(max. %d)", simcams)
+		return -EPERM;
+	}
+
+	err = usb_set_configuration(udev, 1);
+	err += usb_set_interface(udev, 0, 0);
+
+	if (err) {
+		DBG(1, "Device configuration failed.")
+		return -EIO;
+	}
+
+	cam = (struct w9968cf_device*)
+	          kmalloc(sizeof(struct w9968cf_device), GFP_KERNEL);
+
+	if (!cam) {
+		DBG(1, "Couldn't allocate %d bytes of kernel memory.",
+		    sizeof(struct w9968cf_device))
+		err = -ENOMEM;
+		goto fail;
+	}
+	memset(cam, 0, sizeof(*cam));
+
+	init_MUTEX(&cam->dev_sem);
+	down(&cam->dev_sem);
+
+	/* Allocate 2 bytes of memory for camera control USB transfers */
+	if (!(cam->control_buffer = (u16*)kmalloc(2, GFP_KERNEL))) {
+		DBG(1,"Couldn't allocate memory for camera control transfers.")
+		err = -ENOMEM;
+		goto fail;
+	}
+	memset(cam->control_buffer, 0, 2);
+
+	/* Allocate 8 bytes of memory for USB data transfers to the FSB */
+	if (!(cam->data_buffer = (u16*)kmalloc(8, GFP_KERNEL))) {
+		DBG(1, "Couldn't allocate memory for data "
+		       "transfers to the FSB.")
+		err = -ENOMEM;
+		goto fail;
+	}
+	memset(cam->data_buffer, 0, 8);
+
+	/* Set some basic constants */
+	w9968cf_configure_camera(cam, udev, mod_id, dev_nr);
+
+	err = video_register_device(&cam->v4ldev, VFL_TYPE_GRABBER,
+	                            video_nr[dev_nr]);
+	if (err) {
+		DBG(1, "V4L device registration failed.")
+		if (err == -ENFILE && video_nr[dev_nr] == -1)
+			DBG(2, "Couldn't find a free /dev/videoX node.")
+		video_nr[dev_nr] = -1;
+		dev_nr = (dev_nr < W9968CF_MAX_DEVICES-1) ? dev_nr+1 : 0;
+		goto fail;
+	}
+
+	DBG(2, "V4L device registered as /dev/video%d", cam->v4ldev.minor)
+
+	/* Ok, add a new entry into the list of V4L registered devices */
+	down(&w9968cf_devlist_sem);
+	list_add(&cam->v4llist, &w9968cf_dev_list);
+	up(&w9968cf_devlist_sem);
+
+	dev_nr = (dev_nr < W9968CF_MAX_DEVICES-1) ? dev_nr+1 : 0;
+
+	w9968cf_turn_on_led(cam);
+
+	w9968cf_i2c_init(cam);
+
+	up(&cam->dev_sem);
+
+	dev_set_drvdata(&intf->dev, (void*)cam);
+
+	return 0;
+
+fail: /* Free unused memory */
+	if (cam) {
+		if (cam->control_buffer)
+			kfree(cam->control_buffer);
+		if (cam->data_buffer)
+			kfree(cam->data_buffer);
+		up(&cam->dev_sem);
+		kfree(cam);
+	}
+	return err;
+}
+
+
+static void w9968cf_usb_disconnect(struct usb_interface* intf)
+{
+	struct w9968cf_device* cam = 
+	   (struct w9968cf_device*)dev_get_drvdata(&intf->dev);
+
+	dev_set_drvdata(&intf->dev, NULL);
+
+	if (cam) {
+		/* Prevent concurrent accesses to data */
+		down(&cam->dev_sem); 
+
+		cam->streaming = 0;
+		cam->disconnected = 1;
+
+		DBG(2, "Disconnecting %s...", symbolic(camlist, cam->id))
+
+		if (waitqueue_active(&cam->open))
+			wake_up_interruptible(&cam->open);
+
+		if (cam->users) {
+			DBG(2, "The device is open (/dev/video%d)! "
+			       "Process name: %s. Deregistration and memory "
+			       "deallocation are deferred on close.",
+			    cam->v4ldev.minor, cam->command)
+
+			cam->misconfigured = 1;
+
+			if (waitqueue_active(&cam->wait_queue))
+				wake_up_interruptible(&cam->wait_queue);
+		} else
+			w9968cf_release_resources(cam);
+
+		up(&cam->dev_sem);
+
+		if (!cam->users)
+			kfree(cam);
+	}
+}
+
+
+static struct usb_driver w9968cf_usb_driver = {
+	.owner =      THIS_MODULE,
+	.name =       "w9968cf",
+	.id_table =   winbond_id_table,
+	.probe =      w9968cf_usb_probe,
+	.disconnect = w9968cf_usb_disconnect,
+};
+
+
+
+/****************************************************************************
+ * Module init, exit and intermodule communication                          *
+ ****************************************************************************/
+
+static int w9968cf_vppmod_detect(void)
+{
+	w9968cf_vpp_init_decoder = inter_module_get("w9968cf_init_decoder");
+
+	if (!w9968cf_vpp_init_decoder) {
+		if (vppmod_load)
+			w9968cf_vpp_init_decoder = inter_module_get_request
+		                                  ( "w9968cf_init_decoder",
+			                            "w9968cf-vpp" );
+		if (!w9968cf_vpp_init_decoder) {
+			w9968cf_vppmod_present = 0;
+			DBG(4, "Video post-processing module not detected.")
+			return -ENODEV;
+		}
+	}
+
+	w9968cf_vpp_check_headers = inter_module_get("w9968cf_check_headers");
+	w9968cf_vpp_decode = inter_module_get("w9968cf_decode");
+	w9968cf_vpp_swap_yuvbytes = inter_module_get("w9968cf_swap_yuvbytes");
+	w9968cf_vpp_uyvy_to_rgbx = inter_module_get("w9968cf_uyvy_to_rgbx");
+	w9968cf_vpp_scale_up = inter_module_get("w9968cf_scale_up");
+
+	w9968cf_vppmod_present = 1;
+
+	/* Initialization */
+	(*w9968cf_vpp_init_decoder)();
+
+	DBG(2, "Video post-processing module detected.")
+	return 0;
+}
+
+
+static void w9968cf_vppmod_release(void)
+{
+	inter_module_put("w9968cf_init_decoder");
+	inter_module_put("w9968cf_check_headers");
+	inter_module_put("w9968cf_decode");
+	inter_module_put("w9968cf_swap_yuvbytes");
+	inter_module_put("w9968cf_uyvy_to_rgbx");
+	inter_module_put("w9968cf_scale_up");
+
+	DBG(2, "Video post-processing module released.")
+}
+
+
+static int __init w9968cf_module_init(void)
+{
+	int err;
+
+	DBG(2, W9968CF_MODULE_NAME" "W9968CF_MODULE_VERSION)
+	DBG(3, W9968CF_MODULE_AUTHOR)
+
+	init_MUTEX(&w9968cf_devlist_sem);
+
+	w9968cf_vppmod_detect();
+
+	if ((err = usb_register(&w9968cf_usb_driver))) {
+		if (w9968cf_vppmod_present)
+			w9968cf_vppmod_release();
+		return err;
+	}
+
+	return 0;
+}
+
+
+static void __exit w9968cf_module_exit(void)
+{
+	/* w9968cf_usb_disconnect() will be called */
+	usb_deregister(&w9968cf_usb_driver);
+
+	if (w9968cf_vppmod_present)
+		w9968cf_vppmod_release();
+
+	DBG(2, W9968CF_MODULE_NAME" deregistered.")
+}
+
+
+module_init(w9968cf_module_init);
+module_exit(w9968cf_module_exit);
diff -Nru a/drivers/usb/media/w9968cf.h b/drivers/usb/media/w9968cf.h
--- /dev/null	Wed Dec 31 16:00:00 1969
+++ b/drivers/usb/media/w9968cf.h	Fri Dec 12 15:06:58 2003
@@ -0,0 +1,312 @@
+/***************************************************************************
+ * Video4Linux driver for W996[87]CF JPEG USB Dual Mode Camera Chip.       *
+ *                                                                         *
+ * Copyright (C) 2002 2003 by Luca Risolia <luca_ing@libero.it>            *
+ *                                                                         *
+ * This program is free software; you can redistribute it and/or modify    *
+ * it under the terms of the GNU General Public License as published by    *
+ * the Free Software Foundation; either version 2 of the License, or       *
+ * (at your option) any later version.                                     *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful,         *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License       *
+ * along with this program; if not, write to the Free Software             *
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.               *
+ ***************************************************************************/
+
+#ifndef _W9968CF_H_
+#define _W9968CF_H_
+
+#include <linux/videodev.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
+#include <linux/wait.h>
+#include <linux/config.h>
+#include <asm/semaphore.h>
+#include <asm/types.h>
+
+#include "w9968cf_externaldef.h"
+
+
+/****************************************************************************
+ * Default values                                                           *
+ ****************************************************************************/
+
+#define W9968CF_VPPMOD_LOAD     1  /* automatic 'w9968cf-vpp' module loading */
+
+/* Comment/uncomment the following line to enable/disable debugging messages */
+#define W9968CF_DEBUG
+
+/* These have effect only if W9968CF_DEBUG is defined */
+#define W9968CF_DEBUG_LEVEL    2 /* from 0 to 6. 0 for no debug informations */
+#define W9968CF_SPECIFIC_DEBUG 0 /* 0 or 1 */
+
+#define W9968CF_MAX_DEVICES    32
+#define W9968CF_SIMCAMS        W9968CF_MAX_DEVICES /* simultaneous cameras */
+
+#define W9968CF_MAX_BUFFERS   32
+#define W9968CF_BUFFERS       2 /* n. of frame buffers from 2 to MAX_BUFFERS */
+
+/* Maximum data payload sizes in bytes for alternate settings */
+static const u16 wMaxPacketSize[] = {1023, 959, 895, 831, 767, 703, 639, 575,
+                                      511, 447, 383, 319, 255, 191, 127,  63};
+#define W9968CF_PACKET_SIZE      1023 /* according to wMaxPacketSizes[] */
+#define W9968CF_MIN_PACKET_SIZE  63 /* minimum value */
+#define W9968CF_ISO_PACKETS      5 /* n.of packets for isochronous transfers */
+#define W9968CF_USB_CTRL_TIMEOUT HZ /* timeout for usb control commands */
+#define W9968CF_URBS             2 /* n. of scheduled URBs for ISO transfer */
+
+#define W9968CF_I2C_BUS_DELAY    4 /* delay in us for I2C bit r/w operations */
+#define W9968CF_I2C_RW_RETRIES   15 /* number of max I2C r/w retries */
+
+/* Available video formats */
+struct w9968cf_format {
+	const u16 palette;
+	const u16 depth;
+	const u8 compression;
+};
+
+static const struct w9968cf_format w9968cf_formatlist[] = {
+	{ VIDEO_PALETTE_UYVY,    16, 0 }, /* original video */
+	{ VIDEO_PALETTE_YUV422P, 16, 1 }, /* with JPEG compression */
+	{ VIDEO_PALETTE_YUV420P, 12, 1 }, /* with JPEG compression */
+	{ VIDEO_PALETTE_YUV420,  12, 1 }, /* same as YUV420P */
+	{ VIDEO_PALETTE_YUYV,    16, 0 }, /* software conversion */
+	{ VIDEO_PALETTE_YUV422,  16, 0 }, /* software conversion */
+	{ VIDEO_PALETTE_GREY,     8, 0 }, /* software conversion */
+	{ VIDEO_PALETTE_RGB555,  16, 0 }, /* software conversion */
+	{ VIDEO_PALETTE_RGB565,  16, 0 }, /* software conversion */
+	{ VIDEO_PALETTE_RGB24,   24, 0 }, /* software conversion */
+	{ VIDEO_PALETTE_RGB32,   32, 0 }, /* software conversion */
+	{ 0,                      0, 0 }  /* 0 is a terminating entry */
+};
+
+#define W9968CF_DECOMPRESSION    2 /* decomp:0=disable,1=force,2=any formats */
+#define W9968CF_PALETTE_DECOMP_OFF   VIDEO_PALETTE_UYVY    /* when decomp=0 */
+#define W9968CF_PALETTE_DECOMP_FORCE VIDEO_PALETTE_YUV420P /* when decomp=1 */
+#define W9968CF_PALETTE_DECOMP_ON    VIDEO_PALETTE_UYVY    /* when decomp=2 */
+
+#define W9968CF_FORCE_RGB        0  /* read RGB instead of BGR, yes=1/no=0 */
+
+#define W9968CF_MAX_WIDTH      800 /* should be >= 640 */
+#define W9968CF_MAX_HEIGHT     600 /* should be >= 480 */
+#define W9968CF_WIDTH          320 /* from 128 to 352, multiple of 16 */
+#define W9968CF_HEIGHT         240 /* from  96 to 288, multiple of 16 */
+
+#define W9968CF_CLAMPING       0 /* 0 disable, 1 enable video data clamping */
+#define W9968CF_FILTER_TYPE    0 /* 0 disable  1 (1-2-1), 2 (2-3-6-3-2) */
+#define W9968CF_DOUBLE_BUFFER  1 /* 0 disable, 1 enable double buffer */
+#define W9968CF_LARGEVIEW      1 /* 0 disable, 1 enable */
+#define W9968CF_UPSCALING      0 /* 0 disable, 1 enable */
+
+#define W9968CF_SENSOR_MONO    0 /* 0 not monochrome, 1 monochrome sensor */
+#define W9968CF_BRIGHTNESS     31000 /* from 0 to 65535 */
+#define W9968CF_HUE            32768 /* from 0 to 65535 */
+#define W9968CF_COLOUR         32768 /* from 0 to 65535 */
+#define W9968CF_CONTRAST       50000 /* from 0 to 65535 */
+#define W9968CF_WHITENESS      32768 /* from 0 to 65535 */
+
+#define W9968CF_AUTOBRIGHT     0 /* 0 disable, 1 enable automatic brightness */
+#define W9968CF_AUTOEXP        1 /* 0 disable, 1 enable automatic exposure */
+#define W9968CF_LIGHTFREQ      50 /* light frequency. 50Hz (Europe) or 60Hz */
+#define W9968CF_BANDINGFILTER  0 /* 0 disable, 1 enable banding filter */
+#define W9968CF_BACKLIGHT      0 /* 0 or 1, 1=object is lit from behind */
+#define W9968CF_MIRROR         0 /* 0 or 1 [don't] reverse image horizontally*/
+
+#define W9968CF_CLOCKDIV         -1 /* -1 = automatic clock divisor */
+#define W9968CF_DEF_CLOCKDIVISOR  0 /* default sensor clock divisor value */
+
+
+/****************************************************************************
+ * Globals                                                                  *
+ ****************************************************************************/
+
+#define W9968CF_MODULE_NAME     "V4L driver for W996[87]CF JPEG USB " \
+                                "Dual Mode Camera Chip"
+#define W9968CF_MODULE_VERSION  "v1.22"
+#define W9968CF_MODULE_AUTHOR   "(C) 2002 2003 Luca Risolia"
+#define W9968CF_AUTHOR_EMAIL    "<luca_ing@libero.it>"
+
+static u8 w9968cf_vppmod_present; /* status flag: yes=1, no=0 */
+
+static const struct usb_device_id winbond_id_table[] = {
+	{
+		/* Creative Labs Video Blaster WebCam Go Plus */
+		USB_DEVICE(0x041e, 0x4003),
+		.driver_info = (unsigned long)"w9968cf",
+	},
+	{
+		/* Generic W996[87]CF JPEG USB Dual Mode Camera */
+		USB_DEVICE(0x1046, 0x9967),
+		.driver_info = (unsigned long)"w9968cf",
+	},
+	{ } /* terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, winbond_id_table);
+
+/* W996[87]CF camera models, internal ids: */
+enum w9968cf_model_id {
+	W9968CF_MOD_GENERIC = 1, /* Generic W996[87]CF based device */
+	W9968CF_MOD_CLVBWGP = 11,/*Creative Labs Video Blaster WebCam Go Plus*/
+	W9968CF_MOD_ADPA5R = 21, /* Aroma Digi Pen ADG-5000 Refurbished */
+	W9986CF_MOD_AU = 31,     /* AVerTV USB */
+	W9968CF_MOD_CLVBWG = 34, /* Creative Labs Video Blaster WebCam Go */
+	W9968CF_MOD_DLLDK = 37,  /* Die Lebon LDC-D35A Digital Kamera */
+	W9968CF_MOD_EEEMC = 40,   /* Ezonics EZ-802 EZMega Cam */
+	W9968CF_MOD_ODPVDMPC = 43,/* OPCOM Digi Pen VGA Dual Mode Pen Camera */
+};
+
+enum w9968cf_frame_status {
+	F_READY,            /* finished grabbing & ready to be read/synced */
+	F_GRABBING,         /* in the process of being grabbed into */
+	F_ERROR,            /* something bad happened while processing */
+	F_UNUSED            /* unused (no VIDIOCMCAPTURE) */
+};
+
+struct w9968cf_frame_t {
+	void* buffer;
+	u32 length;
+	enum w9968cf_frame_status status;
+	struct w9968cf_frame_t* next;
+	u8 queued;
+};
+
+enum w9968cf_vpp_flag {
+	VPP_NONE = 0x00,
+	VPP_UPSCALE = 0x01,
+	VPP_SWAP_YUV_BYTES = 0x02,
+	VPP_DECOMPRESSION = 0x04,
+	VPP_UYVY_TO_RGBX = 0x08,
+};
+
+struct list_head w9968cf_dev_list; /* head of V4L registered cameras list */
+LIST_HEAD(w9968cf_dev_list);
+struct semaphore w9968cf_devlist_sem; /* semaphore for list traversal */
+
+/* Main device driver structure */
+struct w9968cf_device {
+	enum w9968cf_model_id id;   /* private device identifier */
+
+	struct video_device v4ldev; /* V4L structure */
+	struct list_head v4llist;   /* entry of the list of V4L cameras */
+
+	struct usb_device* usbdev;           /* -> main USB structure */
+	struct urb* urb[W9968CF_URBS];       /* -> USB request block structs */
+	void* transfer_buffer[W9968CF_URBS]; /* -> ISO transfer buffers */
+	u16* control_buffer;                 /* -> buffer for control req.*/
+	u16* data_buffer;                    /* -> data to send to the FSB */
+
+	struct w9968cf_frame_t frame[W9968CF_MAX_BUFFERS];
+	struct w9968cf_frame_t frame_tmp;  /* temporary frame */
+	struct w9968cf_frame_t* frame_current; /* -> frame being grabbed */
+	struct w9968cf_frame_t* requested_frame[W9968CF_MAX_BUFFERS];
+	void* vpp_buffer; /* -> helper buffer for post-processing routines */
+
+	u8 max_buffers,   /* number of requested buffers */	   
+	   force_palette, /* yes=1/no=0 */
+	   force_rgb,     /* read RGB instead of BGR, yes=1, no=0 */
+	   double_buffer, /* hardware double buffering yes=1/no=0 */
+	   clamping,      /* video data clamping yes=1/no=0 */
+	   filter_type,   /* 0=disabled, 1=3 tap, 2=5 tap filter */
+	   capture,       /* 0=disabled, 1=enabled */
+	   largeview,     /* 0=disabled, 1=enabled */
+	   decompression, /* 0=disabled, 1=forced, 2=allowed */
+	   upscaling;     /* software image scaling, 0=enabled, 1=disabled */
+
+	struct video_picture picture; /* current window settings */
+	struct video_window window;   /* current picture settings */
+
+	u16 hw_depth,    /* depth (used by the chip) */
+	    hw_palette,  /* palette (used by the chip) */
+	    hw_width,    /* width (used by the chip) */
+	    hw_height,   /* height (used by the chip) */
+	    hs_polarity, /* 0=negative sync pulse, 1=positive sync pulse */
+	    vs_polarity; /* 0=negative sync pulse, 1=positive sync pulse */
+
+	enum w9968cf_vpp_flag vpp_flag; /* post-processing routines in use */
+
+	u8 nbuffers,      /* number of allocated frame buffers */
+	   altsetting,    /* camera alternate setting */
+	   disconnected,  /* flag: yes=1, no=0 */
+	   misconfigured, /* flag: yes=1, no=0 */
+	   users,         /* flag: number of users holding the device */
+	   streaming;     /* flag: yes=1, no=0 */
+
+	int sensor; /* type of image CMOS sensor chip (CC_*) */
+
+	/* Determined by CMOS sensor type */
+	u16 maxwidth,
+	    maxheight,
+	    minwidth,
+	    minheight,
+	    start_cropx,
+	    start_cropy;
+
+	u8  auto_brt,     /* auto brightness enabled flag */
+	    auto_exp,     /* auto exposure enabled flag */
+	    backlight,    /* backlight exposure algorithm flag */
+	    mirror,       /* image is reversed horizontally */
+	    lightfreq,    /* power (lighting) frequency */
+	    bandfilt;     /* banding filter enabled flag */
+	s8  clockdiv;     /* clock divisor */
+	int sensor_mono;  /* CMOS sensor is (probably) monochrome */
+
+	/* I2C interface to kernel */
+	struct i2c_adapter i2c_adapter;
+	struct i2c_client* sensor_client;
+
+	/* Locks */
+	struct semaphore dev_sem,    /* for probe, disconnect,open and close */
+	                 fileop_sem; /* for read and ioctl */
+	spinlock_t urb_lock,   /* for submit_urb() and unlink_urb() */
+	           flist_lock; /* for requested frame list accesses */
+	char command[16]; /* name of the program holding the device */
+	wait_queue_head_t open, wait_queue;
+};
+
+#define W9968CF_HW_BUF_SIZE 640*480*2 /* buf. size for original video frames */
+
+#define SENSOR_FORMAT          VIDEO_PALETTE_UYVY
+#define SENSOR_FATAL_ERROR(rc) ((rc) < 0 && (rc) != -EPERM)
+
+
+/****************************************************************************
+ * Macros and other constants                                               *
+ ****************************************************************************/
+
+#undef DBG
+#ifdef W9968CF_DEBUG
+#	define DBG(level, fmt, args...) \
+{ \
+if ( ((specific_debug) && (debug == (level))) || \
+     ((!specific_debug) && (debug >= (level))) ) { \
+	if ((level) == 1) \
+		err(fmt, ## args); \
+	else if ((level) == 2 || (level) == 3) \
+		info(fmt, ## args); \
+	else if ((level) == 4) \
+		warn(fmt, ## args); \
+	else if ((level) >= 5) \
+		info("[%s,%d] " fmt, \
+		     __PRETTY_FUNCTION__, __LINE__ , ## args); \
+} \
+}
+#else
+	/* Not debugging: nothing */
+#	define DBG(level, fmt, args...) do {;} while(0);
+#endif
+
+#undef PDBG
+#undef PDBGG
+#define PDBG(fmt, args...) info("[%s, %d] "fmt, \
+	                        __PRETTY_FUNCTION__, __LINE__ , ## args);
+#define PDBGG(fmt, args...) do {;} while(0); /* nothing: it's a placeholder */
+
+#endif /* _W9968CF_H_ */
diff -Nru a/drivers/usb/media/w9968cf_decoder.h b/drivers/usb/media/w9968cf_decoder.h
--- /dev/null	Wed Dec 31 16:00:00 1969
+++ b/drivers/usb/media/w9968cf_decoder.h	Fri Dec 12 15:06:58 2003
@@ -0,0 +1,86 @@
+/***************************************************************************
+ * Video decoder for the W996[87]CF driver for Linux.                      *
+ *                                                                         *
+ * Copyright (C) 2003 by Luca Risolia <luca_ing@libero.it>                 *
+ *                                                                         *
+ * This program is free software; you can redistribute it and/or modify    *
+ * it under the terms of the GNU General Public License as published by    *
+ * the Free Software Foundation; either version 2 of the License, or       *
+ * (at your option) any later version.                                     *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful,         *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License       *
+ * along with this program; if not, write to the Free Software             *
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.               *
+ ***************************************************************************/
+
+#ifndef _W9968CF_DECODER_H_
+#define _W9968CF_DECODER_H_
+
+/* Comment/uncomment this for high/low quality of compressed video */
+#define W9968CF_DEC_FAST_LOWQUALITY_VIDEO
+
+#ifdef W9968CF_DEC_FAST_LOWQUALITY_VIDEO
+static const unsigned char Y_QUANTABLE[64] = {
+	16,  11,  10,  16,  24,  40,  51,  61,
+	12,  12,  14,  19,  26,  58,  60,  55,
+	14,  13,  16,  24,  40,  57,  69,  56,
+	14,  17,  22,  29,  51,  87,  80,  62,
+	18,  22,  37,  56,  68, 109, 103,  77,
+	24,  35,  55,  64,  81, 104, 113,  92,
+	49,  64,  78,  87, 103, 121, 120, 101,
+	72,  92,  95,  98, 112, 100, 103,  99
+};
+
+static const unsigned char UV_QUANTABLE[64] = {
+	17,  18,  24,  47,  99,  99,  99,  99,
+	18,  21,  26,  66,  99,  99,  99,  99,
+	24,  26,  56,  99,  99,  99,  99,  99,
+	47,  66,  99,  99,  99,  99,  99,  99,
+	99,  99,  99,  99,  99,  99,  99,  99,
+	99,  99,  99,  99,  99,  99,  99,  99,
+	99,  99,  99,  99,  99,  99,  99,  99,
+	99,  99,  99,  99,  99,  99,  99,  99
+};
+#else
+static const unsigned char Y_QUANTABLE[64] = {
+	 8,   5,   5,   8,  12,  20,  25,  30,
+	 6,   6,   7,   9,  13,  29,  30,  27,
+	 7,   6,   8,  12,  20,  28,  34,  28,
+	 7,   8,  11,  14,  25,  43,  40,  31,
+	 9,  11,  18,  28,  34,  54,  51,  38,
+	12,  17,  27,  32,  40,  52,  56,  46,
+	24,  32,  39,  43,  51,  60,  60,  50,
+	36,  46,  47,  49,  56,  50,  51,  49
+};
+
+static const unsigned char UV_QUANTABLE[64] = {
+	 8,   9,  12,  23,  49,  49,  49,  49,
+	 9,  10,  13,  33,  49,  49,  49,  49,
+	12,  13,  28,  49,  49,  49,  49,  49,
+	23,  33,  49,  49,  49,  49,  49,  49,
+	49,  49,  49,  49,  49,  49,  49,  49,
+	49,  49,  49,  49,  49,  49,  49,  49,
+	49,  49,  49,  49,  49,  49,  49,  49,
+	49,  49,  49,  49,  49,  49,  49,  49
+};
+#endif
+
+#define W9968CF_DEC_ERR_CORRUPTED_DATA  -1
+#define W9968CF_DEC_ERR_BUF_OVERFLOW    -2
+#define W9968CF_DEC_ERR_NO_SOI          -3
+#define W9968CF_DEC_ERR_NO_SOF0         -4
+#define W9968CF_DEC_ERR_NO_SOS          -5
+#define W9968CF_DEC_ERR_NO_EOI          -6
+
+extern void w9968cf_init_decoder(void);
+extern int w9968cf_check_headers(const unsigned char* Pin, 
+                                 const unsigned long BUF_SIZE);
+extern int w9968cf_decode(const char* Pin, const unsigned long BUF_SIZE, 
+                          const unsigned W, const unsigned H, char* Pout);
+
+#endif /* _W9968CF_DECODER_H_ */
diff -Nru a/drivers/usb/media/w9968cf_externaldef.h b/drivers/usb/media/w9968cf_externaldef.h
--- /dev/null	Wed Dec 31 16:00:00 1969
+++ b/drivers/usb/media/w9968cf_externaldef.h	Fri Dec 12 15:06:58 2003
@@ -0,0 +1,95 @@
+/***************************************************************************
+ * Various definitions for compatibility with external modules.            *
+ * This file is part of the W996[87]CF driver for Linux.                   *
+ *                                                                         *
+ * Copyright (C) 2002 2003 by Luca Risolia <luca_ing@libero.it>            *
+ *                                                                         *
+ * This program is free software; you can redistribute it and/or modify    *
+ * it under the terms of the GNU General Public License as published by    *
+ * the Free Software Foundation; either version 2 of the License, or       *
+ * (at your option) any later version.                                     *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful,         *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License       *
+ * along with this program; if not, write to the Free Software             *
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.               *
+ ***************************************************************************/
+
+#ifndef _W9968CF_EXTERNALDEF_H_
+#define _W9968CF_EXTERNALDEF_H_
+
+#include <linux/videodev.h>
+#include <linux/i2c.h>
+#include <asm/ioctl.h>
+#include <asm/types.h>
+
+/* The following values have been copied from the "ovcamchip" module. */
+
+#ifndef I2C_DRIVERID_OVCAMCHIP
+#	define I2C_DRIVERID_OVCAMCHIP   0xf00f
+#endif
+
+/* Controls */
+enum {
+	OVCAMCHIP_CID_CONT,       /* Contrast */
+	OVCAMCHIP_CID_BRIGHT,     /* Brightness */
+	OVCAMCHIP_CID_SAT,        /* Saturation */
+	OVCAMCHIP_CID_HUE,        /* Hue */
+	OVCAMCHIP_CID_EXP,        /* Exposure */
+	OVCAMCHIP_CID_FREQ,       /* Light frequency */
+	OVCAMCHIP_CID_BANDFILT,   /* Banding filter */
+	OVCAMCHIP_CID_AUTOBRIGHT, /* Auto brightness */
+	OVCAMCHIP_CID_AUTOEXP,    /* Auto exposure */
+	OVCAMCHIP_CID_BACKLIGHT,  /* Back light compensation */
+	OVCAMCHIP_CID_MIRROR,     /* Mirror horizontally */
+};
+
+/* I2C addresses */
+#define OV7xx0_SID   (0x42 >> 1)
+#define OV6xx0_SID   (0xC0 >> 1)
+
+/* Sensor types */
+enum {
+	CC_UNKNOWN,
+	CC_OV76BE,
+	CC_OV7610,
+	CC_OV7620,
+	CC_OV7620AE,
+	CC_OV6620,
+	CC_OV6630,
+	CC_OV6630AE,
+	CC_OV6630AF,
+};
+
+/* API */
+struct ovcamchip_control {
+	__u32 id;
+	__s32 value;
+};
+
+struct ovcamchip_window {
+	int x;
+	int y;
+	int width;
+	int height;
+	int format;
+	int quarter;   /* Scale width and height down 2x */
+
+	/* This stuff will be removed eventually */
+	int clockdiv;  /* Clock divisor setting */
+};
+
+/* Commands. 
+   You must call OVCAMCHIP_CMD_INITIALIZE before any of other commands */
+#define OVCAMCHIP_CMD_Q_SUBTYPE  _IOR  (0x88, 0x00, int)
+#define OVCAMCHIP_CMD_INITIALIZE _IOW  (0x88, 0x01, int)
+#define OVCAMCHIP_CMD_S_CTRL     _IOW  (0x88, 0x02, struct ovcamchip_control)
+#define OVCAMCHIP_CMD_G_CTRL     _IOWR (0x88, 0x03, struct ovcamchip_control)
+#define OVCAMCHIP_CMD_S_MODE     _IOW  (0x88, 0x04, struct ovcamchip_window)
+#define OVCAMCHIP_MAX_CMD        _IO   (0x88, 0x3f)
+
+#endif /* _W9968CF_EXTERNALDEF_H_ */
diff -Nru a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
--- a/drivers/usb/misc/Kconfig	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/misc/Kconfig	Fri Dec 12 15:06:58 2003
@@ -58,6 +58,18 @@
 	  To compile this driver as a module, choose M here: the
 	  module will be called rio500.
 
+config USB_LEGOTOWER
+	tristate "USB Lego Infrared Tower support (EXPERIMENTAL)"
+	depends on USB && EXPERIMENTAL
+	help
+	  Say Y here if you want to connect a USB Lego Infrared Tower to your
+	  computer's USB port.
+
+	  This code is also available as a module ( = code which can be
+	  inserted in and removed from the running kernel whenever you want).
+	  The module will be called legousbtower. If you want to compile it as
+	  a module, say M here and read <file:Documentation/modules.txt>.
+
 config USB_BRLVGER
 	tristate "Tieman Voyager USB Braille display support (EXPERIMENTAL)"
 	depends on USB && EXPERIMENTAL
diff -Nru a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
--- a/drivers/usb/misc/Makefile	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/misc/Makefile	Fri Dec 12 15:06:58 2003
@@ -12,3 +12,4 @@
 obj-$(CONFIG_USB_TEST)		+= usbtest.o
 obj-$(CONFIG_USB_TIGL)		+= tiglusb.o
 obj-$(CONFIG_USB_USS720)	+= uss720.o
+obj-$(CONFIG_USB_LEGOTOWER)	+= legousbtower.o
diff -Nru a/drivers/usb/misc/legousbtower.c b/drivers/usb/misc/legousbtower.c
--- /dev/null	Wed Dec 31 16:00:00 1969
+++ b/drivers/usb/misc/legousbtower.c	Fri Dec 12 15:06:58 2003
@@ -0,0 +1,878 @@
+/*
+ * LEGO USB Tower driver
+ *
+ * Copyright (C) 2003 David Glance <davidgsf@sourceforge.net> 
+ *               2001 Juergen Stuber <stuber@loria.fr>
+ *
+ *	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.
+ *
+ * derived from USB Skeleton driver - 0.5
+ * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com)
+ *
+ * History:
+ *
+ * 2001-10-13 - 0.1 js
+ *   - first version
+ * 2001-11-03 - 0.2 js
+ *   - simplified buffering, one-shot URBs for writing
+ * 2001-11-10 - 0.3 js
+ *   - removed IOCTL (setting power/mode is more complicated, postponed)
+ * 2001-11-28 - 0.4 js
+ *   - added vendor commands for mode of operation and power level in open
+ * 2001-12-04 - 0.5 js
+ *   - set IR mode by default (by oversight 0.4 set VLL mode)
+ * 2002-01-11 - 0.5? pcchan
+ *   - make read buffer reusable and work around bytes_to_write issue between
+ *     uhci and legusbtower
+ * 2002-09-23 - 0.52 david (david@csse.uwa.edu.au)
+ *   - imported into lejos project
+ *   - changed wake_up to wake_up_interruptible
+ *   - changed to use lego0 rather than tower0
+ *   - changed dbg() to use __func__ rather than deprecated __FUNCTION__
+ * 2003-01-12 - 0.53 david (david@csse.uwa.edu.au)
+ *   - changed read and write to write everything or timeout (from a patch by Chris Riesen and 
+ *     Brett Thaeler driver)
+ *   - added ioctl functionality to set timeouts
+ * 2003-07-18 - 0.54 davidgsf (david@csse.uwa.edu.au) 
+ *   - initial import into LegoUSB project
+ *   - merge of existing LegoUSB.c driver
+ * 2003-07-18 - 0.56 davidgsf (david@csse.uwa.edu.au) 
+ *   - port to 2.6 style driver
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/smp_lock.h>
+#include <linux/completion.h>
+#include <asm/uaccess.h>
+#include <linux/usb.h>
+
+
+#ifdef CONFIG_USB_DEBUG
+	static int debug = 4;
+#else
+	static int debug = 1;
+#endif
+
+/* Use our own dbg macro */
+#undef dbg
+#define dbg(lvl, format, arg...) do { if (debug >= lvl) printk(KERN_DEBUG  __FILE__ " : " format " \n", ## arg); } while (0)
+
+
+/* Version Information */
+#define DRIVER_VERSION "v0.56"
+#define DRIVER_AUTHOR "David Glance, davidgsf@sourceforge.net"
+#define DRIVER_DESC "LEGO USB Tower Driver"
+
+/* Module paramaters */
+MODULE_PARM(debug, "i");
+MODULE_PARM_DESC(debug, "Debug enabled or not");
+
+
+/* Define these values to match your device */
+#define LEGO_USB_TOWER_VENDOR_ID	0x0694
+#define LEGO_USB_TOWER_PRODUCT_ID	0x0001
+
+/* table of devices that work with this driver */
+static struct usb_device_id tower_table [] = {
+	{ USB_DEVICE(LEGO_USB_TOWER_VENDOR_ID, LEGO_USB_TOWER_PRODUCT_ID) },
+	{ }					/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, tower_table);
+
+#define LEGO_USB_TOWER_MINOR_BASE	160
+
+/* we can have up to this number of device plugged in at once */
+#define MAX_DEVICES		16
+
+#define COMMAND_TIMEOUT		(2*HZ)  /* 2 second timeout for a command */
+
+/* Structure to hold all of our device specific stuff */
+struct lego_usb_tower {
+	struct semaphore	sem;		/* locks this structure */
+	struct usb_device* 	udev;		/* save off the usb device pointer */
+	struct usb_interface*   interface;
+	unsigned char		minor;		/* the starting minor number for this device */
+
+	int			open_count;	/* number of times this port has been opened */
+
+	char*			read_buffer;
+	int			read_buffer_length;
+
+	wait_queue_head_t	read_wait;
+	wait_queue_head_t	write_wait;
+
+	char*			interrupt_in_buffer;
+	struct usb_endpoint_descriptor* interrupt_in_endpoint;
+	struct urb*		interrupt_in_urb;
+
+	char*			interrupt_out_buffer;
+	struct usb_endpoint_descriptor* interrupt_out_endpoint;
+	struct urb*		interrupt_out_urb;
+
+};
+
+/* Note that no locking is needed:
+ * read_buffer is arbitrated by read_buffer_length == 0
+ * interrupt_out_buffer is arbitrated by interrupt_out_urb->status == -EINPROGRESS
+ * interrupt_in_buffer belongs to urb alone and is overwritten on overflow
+ */
+
+/* local function prototypes */
+static ssize_t tower_read	(struct file *file, char *buffer, size_t count, loff_t *ppos);
+static ssize_t tower_write	(struct file *file, const char *buffer, size_t count, loff_t *ppos);
+static inline void tower_delete (struct lego_usb_tower *dev);
+static int tower_open		(struct inode *inode, struct file *file);
+static int tower_release	(struct inode *inode, struct file *file);
+static int tower_release_internal (struct lego_usb_tower *dev);
+static void tower_abort_transfers (struct lego_usb_tower *dev);
+static void tower_interrupt_in_callback (struct urb *urb, struct pt_regs *regs);
+static void tower_interrupt_out_callback (struct urb *urb, struct pt_regs *regs);
+
+static int  tower_probe	(struct usb_interface *interface, const struct usb_device_id *id);
+static void tower_disconnect	(struct usb_interface *interface);
+
+
+/* prevent races between open() and disconnect */
+static DECLARE_MUTEX (disconnect_sem);
+
+/* file operations needed when we register this driver */
+static struct file_operations tower_fops = {
+	.owner = 	THIS_MODULE,
+	.read  = 	tower_read,
+	.write =	tower_write,
+	.open =		tower_open,
+	.release = 	tower_release,
+};
+
+/* 
+ * usb class driver info in order to get a minor number from the usb core,
+ * and to have the device registered with devfs and the driver core
+ */
+static struct usb_class_driver tower_class = {
+	.name =		"usb/legousbtower%d",
+	.fops =		&tower_fops,
+	.mode =		S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH,
+	.minor_base =	LEGO_USB_TOWER_MINOR_BASE,
+};
+
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver tower_driver = {
+	.owner =        THIS_MODULE,
+	.name =	        "legousbtower",
+	.probe = 	tower_probe,
+	.disconnect = 	tower_disconnect,
+	.id_table = 	tower_table,
+};
+
+
+/**
+ *	lego_usb_tower_debug_data
+ */
+static inline void lego_usb_tower_debug_data (int level, const char *function, int size, const unsigned char *data)
+{
+	int i;
+
+	if (debug < level)
+		return; 
+	
+	printk (KERN_DEBUG __FILE__": %s - length = %d, data = ", function, size);
+	for (i = 0; i < size; ++i) {
+		printk ("%.2x ", data[i]);
+	}
+	printk ("\n");
+}
+
+
+/**
+ *	tower_delete
+ */
+static inline void tower_delete (struct lego_usb_tower *dev)
+{
+	dbg(2, "%s enter", __func__);
+
+	tower_abort_transfers (dev);
+
+	/* free data structures */
+	if (dev->interrupt_in_urb != NULL) {
+		usb_free_urb (dev->interrupt_in_urb);
+	}
+	if (dev->interrupt_out_urb != NULL) {
+		usb_free_urb (dev->interrupt_out_urb);
+	}
+	kfree (dev->read_buffer);
+	kfree (dev->interrupt_in_buffer);
+	kfree (dev->interrupt_out_buffer);
+	kfree (dev);
+
+	dbg(2, "%s : leave", __func__);
+}
+
+
+/**
+ *	tower_open
+ */
+static int tower_open (struct inode *inode, struct file *file)
+{
+	struct lego_usb_tower *dev = NULL;
+	int subminor;
+	int retval = 0;
+	struct usb_interface *interface;
+
+	dbg(2,"%s : enter", __func__);
+
+	subminor = iminor(inode);
+
+	down (&disconnect_sem);
+
+	interface = usb_find_interface (&tower_driver, subminor);
+
+	if (!interface) {
+		err ("%s - error, can't find device for minor %d",
+		     __FUNCTION__, subminor);
+		retval = -ENODEV;
+		goto exit_no_device;
+	}
+
+	dev = usb_get_intfdata(interface);
+
+	if (!dev) {
+		retval = -ENODEV;
+		goto exit_no_device;
+	}
+
+	/* lock this device */
+	down (&dev->sem);
+
+	
+	/* increment our usage count for the device */
+	++dev->open_count;
+
+	/* save device in the file's private structure */
+	file->private_data = dev;
+
+
+	/* initialize in direction */
+	dev->read_buffer_length = 0;
+
+	up (&dev->sem);
+
+exit_no_device:
+
+	up (&disconnect_sem);
+
+	dbg(2,"%s : leave, return value %d ", __func__, retval);
+
+	return retval;
+}
+
+/**
+ *	tower_release
+ */
+static int tower_release (struct inode *inode, struct file *file)
+{
+	struct lego_usb_tower *dev;
+	int retval = 0;
+
+	dbg(2," %s : enter", __func__);
+
+	dev = (struct lego_usb_tower *)file->private_data;
+
+	if (dev == NULL) {
+		dbg(1," %s : object is NULL", __func__);
+		retval = -ENODEV;
+		goto exit;
+	}
+
+
+	/* lock our device */
+	down (&dev->sem);
+
+ 	if (dev->open_count <= 0) {
+		dbg(1," %s : device not opened", __func__);
+		retval = -ENODEV;
+		goto exit;
+	}
+
+	/* do the work */
+	retval = tower_release_internal (dev);
+
+exit:
+	up (&dev->sem);
+	dbg(2," %s : leave, return value %d", __func__, retval);
+	return retval;
+}
+
+
+/**
+ *	tower_release_internal
+ */
+static int tower_release_internal (struct lego_usb_tower *dev)
+{
+	int retval = 0;
+
+	dbg(2," %s : enter", __func__);
+
+	if (dev->udev == NULL) {
+		/* the device was unplugged before the file was released */
+		tower_delete (dev);
+		goto exit;
+	}
+
+	/* decrement our usage count for the device */
+	--dev->open_count;
+	if (dev->open_count <= 0) {
+		tower_abort_transfers (dev);
+		dev->open_count = 0;
+	}
+
+exit:
+	dbg(2," %s : leave", __func__);
+	return retval;
+}
+
+
+/**
+ *	tower_abort_transfers
+ *      aborts transfers and frees associated data structures
+ */
+static void tower_abort_transfers (struct lego_usb_tower *dev)
+{
+	dbg(2," %s : enter", __func__);
+
+	if (dev == NULL) {
+		dbg(1," %s : dev is null", __func__);
+	        goto exit;
+	}
+
+	/* shutdown transfer */
+	if (dev->interrupt_in_urb != NULL) {
+		usb_unlink_urb (dev->interrupt_in_urb);
+	}
+	if (dev->interrupt_out_urb != NULL) {
+		usb_unlink_urb (dev->interrupt_out_urb);
+	}
+
+exit:
+	dbg(2," %s : leave", __func__);
+}
+
+
+/**
+ *	tower_read
+ */
+static ssize_t tower_read (struct file *file, char *buffer, size_t count, loff_t *ppos)
+{
+	struct lego_usb_tower *dev;
+	size_t bytes_read = 0;
+	size_t bytes_to_read;
+	int i;
+	int retval = 0;
+	int timeout = 0;
+
+	dbg(2," %s : enter, count = %Zd", __func__, count);
+
+	dev = (struct lego_usb_tower *)file->private_data;
+	
+	/* lock this object */
+	down (&dev->sem);
+
+	/* verify that the device wasn't unplugged */
+	if (dev->udev == NULL) {
+		retval = -ENODEV;
+		err("No device or device unplugged %d", retval);
+		goto exit;
+	}
+
+	/* verify that we actually have some data to read */
+	if (count == 0) {
+		dbg(1," %s : read request of 0 bytes", __func__);
+		goto exit;
+	}
+
+
+	timeout = COMMAND_TIMEOUT;
+
+	while (1) {
+		if (dev->read_buffer_length == 0) {
+
+			/* start reading */
+			usb_fill_int_urb (dev->interrupt_in_urb,dev->udev,
+					  usb_rcvintpipe(dev->udev, dev->interrupt_in_endpoint->bEndpointAddress),
+					  dev->interrupt_in_buffer,
+					  dev->interrupt_in_endpoint->wMaxPacketSize,
+					  tower_interrupt_in_callback,
+					  dev,
+					  dev->interrupt_in_endpoint->bInterval);
+			
+			retval = usb_submit_urb (dev->interrupt_in_urb, GFP_KERNEL);
+			
+			if (retval < 0) {
+				err("Couldn't submit interrupt_in_urb");
+				goto exit;
+			}
+
+			if (timeout <= 0) {
+			        retval = -ETIMEDOUT;
+			        goto exit;
+			}
+
+			if (signal_pending(current)) {
+				retval = -EINTR;
+				goto exit;
+			}
+
+			up (&dev->sem);
+			timeout = interruptible_sleep_on_timeout (&dev->read_wait, timeout);
+			down (&dev->sem);
+
+		} else {
+			/* copy the data from read_buffer into userspace */
+			bytes_to_read = count > dev->read_buffer_length ? dev->read_buffer_length : count;
+			if (copy_to_user (buffer, dev->read_buffer, bytes_to_read) != 0) {
+				retval = -EFAULT;
+				goto exit;
+			}
+			dev->read_buffer_length -= bytes_to_read;
+			for (i=0; i<dev->read_buffer_length; i++) {
+				dev->read_buffer[i] = dev->read_buffer[i+bytes_to_read];
+			}
+
+			buffer += bytes_to_read;
+			count -= bytes_to_read;
+			bytes_read += bytes_to_read;
+			if (count == 0) {
+				break;
+			}
+		}
+	}
+
+	retval = bytes_read;
+
+exit:
+	/* unlock the device */
+	up (&dev->sem);
+
+	dbg(2," %s : leave, return value %d", __func__, retval);
+	return retval;
+}
+
+
+/**
+ *	tower_write
+ */
+static ssize_t tower_write (struct file *file, const char *buffer, size_t count, loff_t *ppos)
+{
+	struct lego_usb_tower *dev;
+	size_t bytes_written = 0;
+	size_t bytes_to_write;
+	size_t buffer_size;
+	int retval = 0;
+	int timeout = 0;
+
+	dbg(2," %s : enter, count = %Zd", __func__, count);
+
+	dev = (struct lego_usb_tower *)file->private_data;
+
+	/* lock this object */
+	down (&dev->sem);
+
+	/* verify that the device wasn't unplugged */
+	if (dev->udev == NULL) {
+		retval = -ENODEV;
+		err("No device or device unplugged %d", retval);
+		goto exit;
+	}
+
+	/* verify that we actually have some data to write */
+	if (count == 0) {
+		dbg(1," %s : write request of 0 bytes", __func__);
+		goto exit;
+	}
+
+
+	while (count > 0) {
+		if (dev->interrupt_out_urb->status == -EINPROGRESS) {
+			timeout = COMMAND_TIMEOUT;
+
+			while (timeout > 0) {
+				if (signal_pending(current)) {
+					dbg(1," %s : interrupted", __func__);
+					retval = -EINTR;
+					goto exit;
+				}
+				up (&dev->sem);
+				timeout = interruptible_sleep_on_timeout (&dev->write_wait, timeout);
+				down (&dev->sem);
+				if (timeout > 0) {
+					break;
+				}
+				dbg(1," %s : interrupted timeout: %d", __func__, timeout);
+			}
+
+
+			dbg(1," %s : final timeout: %d", __func__, timeout);
+
+			if (timeout == 0) {
+				dbg(1, "%s - command timed out.", __func__);
+				retval = -ETIMEDOUT;
+				goto exit;
+			}
+
+			dbg(4," %s : in progress, count = %Zd", __func__, count);
+		} else {
+			dbg(4," %s : sending, count = %Zd", __func__, count);
+
+			/* write the data into interrupt_out_buffer from userspace */
+			buffer_size = dev->interrupt_out_endpoint->wMaxPacketSize;
+			bytes_to_write = count > buffer_size ? buffer_size : count;
+			dbg(4," %s : buffer_size = %Zd, count = %Zd, bytes_to_write = %Zd", __func__, buffer_size, count, bytes_to_write);
+
+			if (copy_from_user (dev->interrupt_out_buffer, buffer, bytes_to_write) != 0) {
+				retval = -EFAULT;
+				goto exit;
+			}
+
+			/* send off the urb */
+			usb_fill_int_urb(dev->interrupt_out_urb,
+					dev->udev, 
+					usb_sndintpipe(dev->udev, dev->interrupt_out_endpoint->bEndpointAddress),
+					dev->interrupt_out_buffer,
+					bytes_to_write,
+					tower_interrupt_out_callback,
+					dev,
+					dev->interrupt_in_endpoint->bInterval);
+
+			dev->interrupt_out_urb->actual_length = bytes_to_write;
+			retval = usb_submit_urb (dev->interrupt_out_urb, GFP_KERNEL);
+
+			if (retval < 0) {
+				err("Couldn't submit interrupt_out_urb %d", retval);
+				goto exit;
+			}
+
+			buffer += bytes_to_write;
+			count -= bytes_to_write;
+
+			bytes_written += bytes_to_write;
+		}
+	}
+
+	retval = bytes_written;
+
+exit:
+	/* unlock the device */
+	up (&dev->sem);
+
+	dbg(2," %s : leave, return value %d", __func__, retval);
+
+	return retval;
+}
+
+
+/**
+ *	tower_interrupt_in_callback
+ */
+static void tower_interrupt_in_callback (struct urb *urb, struct pt_regs *regs)
+{
+	struct lego_usb_tower *dev = (struct lego_usb_tower *)urb->context;
+
+	dbg(4," %s : enter, status %d", __func__, urb->status);
+
+	lego_usb_tower_debug_data(5,__func__, urb->actual_length, urb->transfer_buffer);
+
+	if (urb->status != 0) {
+		if ((urb->status != -ENOENT) && (urb->status != -ECONNRESET)) {
+			dbg(1," %s : nonzero status received: %d", __func__, urb->status);
+		}
+		goto exit;
+	}
+
+	down (&dev->sem);
+
+	if (urb->actual_length > 0) {
+		if (dev->read_buffer_length < (4 * dev->interrupt_in_endpoint->wMaxPacketSize) - (urb->actual_length)) {
+
+			memcpy (dev->read_buffer+dev->read_buffer_length, dev->interrupt_in_buffer, urb->actual_length);
+
+			dev->read_buffer_length += urb->actual_length;
+			dbg(1," %s reading  %d ", __func__, urb->actual_length);
+			wake_up_interruptible (&dev->read_wait);
+
+		} else {
+			dbg(1," %s : read_buffer overflow", __func__);
+		}
+	}
+
+	up (&dev->sem);
+
+exit:
+	lego_usb_tower_debug_data(5,__func__, urb->actual_length, urb->transfer_buffer);
+	dbg(4," %s : leave, status %d", __func__, urb->status);
+}
+
+
+/**
+ *	tower_interrupt_out_callback
+ */
+static void tower_interrupt_out_callback (struct urb *urb, struct pt_regs *regs)
+{
+	struct lego_usb_tower *dev = (struct lego_usb_tower *)urb->context;
+
+	dbg(4," %s : enter, status %d", __func__, urb->status);
+	lego_usb_tower_debug_data(5,__func__, urb->actual_length, urb->transfer_buffer);
+
+	if (urb->status != 0) {
+		if ((urb->status != -ENOENT) && 
+		    (urb->status != -ECONNRESET)) {
+			dbg(1, " %s :nonzero status received: %d", __func__, urb->status);
+		}
+		goto exit;
+	}                        
+
+	wake_up_interruptible(&dev->write_wait);
+exit:
+
+	lego_usb_tower_debug_data(5,__func__, urb->actual_length, urb->transfer_buffer);
+	dbg(4," %s : leave, status %d", __func__, urb->status);
+}
+
+
+/**
+ *	tower_probe
+ *
+ *	Called by the usb core when a new device is connected that it thinks
+ *	this driver might be interested in.
+ */
+static int tower_probe (struct usb_interface *interface, const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(interface);
+	struct lego_usb_tower *dev = NULL;
+	struct usb_host_interface *iface_desc;
+	struct usb_endpoint_descriptor* endpoint;
+	int i;
+	int retval = -ENOMEM;
+
+	dbg(2," %s : enter", __func__);
+
+	if (udev == NULL) {
+		info ("udev is NULL.");
+	}
+	
+	/* See if the device offered us matches what we can accept */
+	if ((udev->descriptor.idVendor != LEGO_USB_TOWER_VENDOR_ID) ||
+	    (udev->descriptor.idProduct != LEGO_USB_TOWER_PRODUCT_ID)) {
+		return -ENODEV;
+	}
+
+
+	/* allocate memory for our device state and intialize it */
+
+	dev = kmalloc (sizeof(struct lego_usb_tower), GFP_KERNEL);
+
+	if (dev == NULL) {
+		err ("Out of memory");
+		goto exit;
+	}
+
+	init_MUTEX (&dev->sem);
+
+	dev->udev = udev;
+	dev->open_count = 0;
+
+	dev->read_buffer = NULL;
+	dev->read_buffer_length = 0;
+
+	init_waitqueue_head (&dev->read_wait);
+	init_waitqueue_head (&dev->write_wait);
+
+	dev->interrupt_in_buffer = NULL;
+	dev->interrupt_in_endpoint = NULL;
+	dev->interrupt_in_urb = NULL;
+
+	dev->interrupt_out_buffer = NULL;
+	dev->interrupt_out_endpoint = NULL;
+	dev->interrupt_out_urb = NULL;
+
+
+	iface_desc = &interface->altsetting[0];
+
+	/* set up the endpoint information */
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+		endpoint = &iface_desc->endpoint[i].desc;
+
+		if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) &&
+		    ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)) {
+			dev->interrupt_in_endpoint = endpoint;
+		}
+		
+		if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT) &&
+		    ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)) {
+			dev->interrupt_out_endpoint = endpoint;
+		}
+	}
+	if(dev->interrupt_in_endpoint == NULL) {
+		err("interrupt in endpoint not found");
+		goto error;
+	}
+	if (dev->interrupt_out_endpoint == NULL) {
+		err("interrupt out endpoint not found");
+		goto error;
+	}
+
+	dev->read_buffer = kmalloc ((4*dev->interrupt_in_endpoint->wMaxPacketSize), GFP_KERNEL);
+	if (!dev->read_buffer) {
+		err("Couldn't allocate read_buffer");
+		goto error;
+	}
+	dev->interrupt_in_buffer = kmalloc (dev->interrupt_in_endpoint->wMaxPacketSize, GFP_KERNEL);
+	if (!dev->interrupt_in_buffer) {
+		err("Couldn't allocate interrupt_in_buffer");
+		goto error;
+	}
+	dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!dev->interrupt_in_urb) {
+		err("Couldn't allocate interrupt_in_urb");
+		goto error;
+	}
+	dev->interrupt_out_buffer = kmalloc (dev->interrupt_out_endpoint->wMaxPacketSize, GFP_KERNEL);
+	if (!dev->interrupt_out_buffer) {
+		err("Couldn't allocate interrupt_out_buffer");
+		goto error;
+	}
+	dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!dev->interrupt_out_urb) {
+		err("Couldn't allocate interrupt_out_urb");
+		goto error;
+	}                
+
+	/* we can register the device now, as it is ready */
+	usb_set_intfdata (interface, dev);
+
+	retval = usb_register_dev (interface, &tower_class);
+
+	if (retval) {
+		/* something prevented us from registering this driver */
+		err ("Not able to get a minor for this device.");
+		usb_set_intfdata (interface, NULL);
+		goto error;
+	}
+
+	dev->minor = interface->minor;
+
+	/* let the user know what node this device is now attached to */
+	info ("LEGO USB Tower device now attached to /dev/usb/lego%d", (dev->minor - LEGO_USB_TOWER_MINOR_BASE));
+
+
+
+exit:
+	dbg(2," %s : leave, return value 0x%.8lx (dev)", __func__, (long) dev);
+
+	return retval;
+
+error:
+	tower_delete(dev);
+	return retval;
+}
+
+
+/**
+ *	tower_disconnect
+ *
+ *	Called by the usb core when the device is removed from the system.
+ */
+static void tower_disconnect (struct usb_interface *interface)
+{
+	struct lego_usb_tower *dev;
+	int minor;
+
+	dbg(2," %s : enter", __func__);
+
+	down (&disconnect_sem);
+
+	dev = usb_get_intfdata (interface);
+	usb_set_intfdata (interface, NULL);
+
+
+	down (&dev->sem);
+
+	minor = dev->minor;
+
+	/* give back our minor */
+	usb_deregister_dev (interface, &tower_class);
+
+	/* if the device is not opened, then we clean up right now */
+	if (!dev->open_count) {
+		up (&dev->sem);
+		tower_delete (dev);
+	} else {
+		dev->udev = NULL;
+		up (&dev->sem);
+	}
+
+	up (&disconnect_sem);
+
+	info("LEGO USB Tower #%d now disconnected", (minor - LEGO_USB_TOWER_MINOR_BASE));
+
+	dbg(2," %s : leave", __func__);
+}
+
+
+
+/**
+ *	lego_usb_tower_init
+ */
+static int __init lego_usb_tower_init(void)
+{
+	int result;
+	int retval = 0;
+
+	dbg(2," %s : enter", __func__);
+
+	/* register this driver with the USB subsystem */
+	result = usb_register(&tower_driver);
+	if (result < 0) {
+		err("usb_register failed for the "__FILE__" driver. Error number %d", result);
+		retval = -1;
+		goto exit;
+	}
+
+	info(DRIVER_DESC " " DRIVER_VERSION);
+
+exit:
+	dbg(2," %s : leave, return value %d", __func__, retval);
+
+	return retval;
+}
+
+
+/**
+ *	lego_usb_tower_exit
+ */
+static void __exit lego_usb_tower_exit(void)
+{
+	dbg(2," %s : enter", __func__);
+
+	/* deregister this driver with the USB subsystem */
+	usb_deregister (&tower_driver);
+
+	dbg(2," %s : leave", __func__);
+}
+
+module_init (lego_usb_tower_init);
+module_exit (lego_usb_tower_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+#ifdef MODULE_LICENSE
+MODULE_LICENSE("GPL");
+#endif
diff -Nru a/drivers/usb/net/Kconfig b/drivers/usb/net/Kconfig
--- a/drivers/usb/net/Kconfig	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/net/Kconfig	Fri Dec 12 15:06:58 2003
@@ -247,7 +247,7 @@
 
 config USB_AX8817X
 	boolean "ASIX AX88172 Based USB 2.0 Ethernet Devices"
-	depends on USB_USBNET && EXPERIMENTAL
+	depends on USB_USBNET
 	default y
 	help
 
diff -Nru a/drivers/usb/net/pegasus.h b/drivers/usb/net/pegasus.h
--- a/drivers/usb/net/pegasus.h	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/net/pegasus.h	Fri Dec 12 15:06:58 2003
@@ -137,6 +137,7 @@
 #define	VENDOR_MELCO		0x0411
 #define	VENDOR_MOBILITY		0x1342
 #define	VENDOR_NETGEAR		0x0846
+#define	VENDOR_OCT		0x0b39
 #define	VENDOR_SMARTBRIDGES	0x08d1
 #define	VENDOR_SMC		0x0707
 #define	VENDOR_SOHOWARE		0x15e8
@@ -173,7 +174,7 @@
 		DEFAULT_GPIO_RESET | PEGASUS_II )
 PEGASUS_DEV( "ADMtek ADM8511 \"Pegasus II\" USB Ethernet",
 		VENDOR_ADMTEK, 0x8511,
-		DEFAULT_GPIO_RESET | PEGASUS_II )
+		DEFAULT_GPIO_RESET | PEGASUS_II | HAS_HOME_PNA )
 PEGASUS_DEV( "ADMtek ADM8513 \"Pegasus II\" USB Ethernet",
 		VENDOR_ADMTEK, 0x8513,
 		DEFAULT_GPIO_RESET | PEGASUS_II )
@@ -259,6 +260,8 @@
 PEGASUS_DEV( "MELCO/BUFFALO LUA2-TX", VENDOR_MELCO, 0x0009,
 		DEFAULT_GPIO_RESET | PEGASUS_II )
 PEGASUS_DEV( "NETGEAR FA101", VENDOR_NETGEAR, 0x1020,
+		DEFAULT_GPIO_RESET | PEGASUS_II )
+PEGASUS_DEV( "OCT Inc.", VENDOR_OCT, 0x0109,
 		DEFAULT_GPIO_RESET | PEGASUS_II )
 PEGASUS_DEV( "smartNIC 2 PnP Adapter", VENDOR_SMARTBRIDGES, 0x0003,
 		DEFAULT_GPIO_RESET | PEGASUS_II )
diff -Nru a/drivers/usb/net/usbnet.c b/drivers/usb/net/usbnet.c
--- a/drivers/usb/net/usbnet.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/net/usbnet.c	Fri Dec 12 15:06:58 2003
@@ -637,6 +637,27 @@
 	info->eedump_len = 0x3e;
 }
 
+static u32 ax8817x_get_link (struct net_device *net)
+{
+	struct usbnet *dev = (struct usbnet *)net->priv;
+
+	return (u32)mii_link_ok(&dev->mii);
+}
+
+static int ax8817x_get_settings(struct net_device *net, struct ethtool_cmd *cmd)
+{
+	struct usbnet *dev = (struct usbnet *)net->priv;
+
+	return mii_ethtool_gset(&dev->mii,cmd);
+}
+
+static int ax8817x_set_settings(struct net_device *net, struct ethtool_cmd *cmd)
+{
+	struct usbnet *dev = (struct usbnet *)net->priv;
+
+	return mii_ethtool_sset(&dev->mii,cmd);
+}
+
 static int ax8817x_bind(struct usbnet *dev, struct usb_interface *intf)
 {
 	int ret;
@@ -670,16 +691,6 @@
 	}
 	memcpy(dev->net->dev_addr, buf, ETH_ALEN);
 
-	/* Get IPG values */
-	if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_IPG012, 0, 0, 3, buf)) < 0) {
-		dbg("Error reading IPG values: %d", ret);
-		return ret;
-	}
-
-	for(i = 0;i < 3;i++) {
-		ax8817x_write_cmd(dev, AX_CMD_WRITE_IPG0 + i, 0, 0, 1, &buf[i]);
-	}
-
 	/* Get the PHY id */
 	if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_PHY_ID, 0, 0, 2, buf)) < 0) {
 		dbg("error on read AX_CMD_READ_PHY_ID: %02x", ret);
@@ -735,9 +746,12 @@
 	dev->net->set_multicast_list = ax8817x_set_multicast;
 
 	usbnet_ethtool_ops.get_drvinfo = &ax8817x_get_drvinfo;
+	usbnet_ethtool_ops.get_link = &ax8817x_get_link;
 	usbnet_ethtool_ops.get_wol = &ax8817x_get_wol;
 	usbnet_ethtool_ops.set_wol = &ax8817x_set_wol;
 	usbnet_ethtool_ops.get_eeprom = &ax8817x_get_eeprom;
+	usbnet_ethtool_ops.get_settings = &ax8817x_get_settings;
+	usbnet_ethtool_ops.set_settings = &ax8817x_set_settings;
 
 	return 0;
 }
diff -Nru a/drivers/usb/serial/cyberjack.c b/drivers/usb/serial/cyberjack.c
--- a/drivers/usb/serial/cyberjack.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/serial/cyberjack.c	Fri Dec 12 15:06:58 2003
@@ -295,7 +295,6 @@
 {
 	struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
 	struct cyberjack_private *priv = usb_get_serial_port_data(port);
-	unsigned long flags;
 	struct usb_serial *serial;
 	unsigned char *data = urb->transfer_buffer;
 	int result;
@@ -323,13 +322,13 @@
 		/* This is a announcement of coming bulk_ins. */
 		unsigned short size = ((unsigned short)data[3]<<8)+data[2]+3;
 
-		spin_lock_irqsave(&priv->lock, flags);
+		spin_lock(&priv->lock);
 
 		old_rdtodo = priv->rdtodo;
 
 		if( (old_rdtodo+size)<(old_rdtodo) ) {
 			dbg( "To many bulk_in urbs to do." );
-			spin_unlock_irqrestore(&priv->lock, flags);
+			spin_unlock(&priv->lock);
 			goto resubmit;
 		}
 
@@ -338,11 +337,11 @@
 
 		dbg("%s - rdtodo: %d", __FUNCTION__, priv->rdtodo);
 
-		spin_unlock_irqrestore(&priv->lock, flags);
+		spin_unlock(&priv->lock);
 
 		if( !old_rdtodo ) {
 			port->read_urb->dev = port->serial->dev;
-			result = usb_submit_urb(port->read_urb, GFP_KERNEL);
+			result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
 			if( result )
 				err("%s - failed resubmitting read urb, error %d", __FUNCTION__, result);
 			dbg("%s - usb_submit_urb(read urb)", __FUNCTION__);
@@ -351,7 +350,7 @@
 
 resubmit:
 	port->interrupt_in_urb->dev = port->serial->dev;
-	result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+	result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
 	if (result)
 		err(" usb_submit_urb(read int) failed");
 	dbg("%s - usb_submit_urb(int urb)", __FUNCTION__);
@@ -361,7 +360,6 @@
 {
 	struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
 	struct cyberjack_private *priv = usb_get_serial_port_data(port);
-	unsigned long flags;
 	struct usb_serial *serial = get_usb_serial (port, __FUNCTION__);
 	struct tty_struct *tty;
 	unsigned char *data = urb->transfer_buffer;
@@ -397,7 +395,7 @@
 	  	tty_flip_buffer_push(tty);
 	}
 
-	spin_lock_irqsave(&priv->lock, flags);
+	spin_lock(&priv->lock);
 
 	/* Reduce urbs to do by one. */
 	priv->rdtodo-=urb->actual_length;
@@ -405,14 +403,14 @@
 	if ( priv->rdtodo<0 ) priv->rdtodo = 0;
 	todo = priv->rdtodo;
 
-	spin_unlock_irqrestore(&priv->lock, flags);
+	spin_unlock(&priv->lock);
 
 	dbg("%s - rdtodo: %d", __FUNCTION__, todo);
 
 	/* Continue to read if we have still urbs to do. */
 	if( todo /* || (urb->actual_length==port->bulk_in_endpointAddress)*/ ) {
 		port->read_urb->dev = port->serial->dev;
-		result = usb_submit_urb(port->read_urb, GFP_KERNEL);
+		result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
 		if (result)
 			err("%s - failed resubmitting read urb, error %d", __FUNCTION__, result);
 		dbg("%s - usb_submit_urb(read urb)", __FUNCTION__);
@@ -423,7 +421,6 @@
 {
 	struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
 	struct cyberjack_private *priv = usb_get_serial_port_data(port);
-	unsigned long flags;
 	struct usb_serial *serial = get_usb_serial (port, __FUNCTION__);
 
 	dbg("%s - port %d", __FUNCTION__, port->number);
@@ -438,7 +435,7 @@
 		return;
 	}
 
-	spin_lock_irqsave(&priv->lock, flags);
+	spin_lock(&priv->lock);
 
 	/* only do something if we have more data to send */
 	if( priv->wrfilled ) {
@@ -446,7 +443,7 @@
 
 		if (port->write_urb->status == -EINPROGRESS) {
 			dbg("%s - already writing", __FUNCTION__);
-			spin_unlock_irqrestore(&priv->lock, flags);
+			spin_unlock(&priv->lock);
 			return;
 		}
 
@@ -492,7 +489,7 @@
 	}
 
 exit:
-	spin_unlock_irqrestore(&priv->lock, flags);
+	spin_unlock(&priv->lock);
 	schedule_work(&port->work);
 }
 
diff -Nru a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c
--- a/drivers/usb/serial/ftdi_sio.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/serial/ftdi_sio.c	Fri Dec 12 15:06:58 2003
@@ -342,6 +342,10 @@
 	{ USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_8_PID, 0, 0x3ff) },
 	{ USB_DEVICE_VER(IDTECH_VID, IDTECH_IDT1221U_PID, 0, 0x3ff) },
 	{ USB_DEVICE_VER(OCT_VID, OCT_US101_PID, 0, 0x3ff) },
+	{ USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_1, 0, 0x3ff) },
+	{ USB_DEVICE_VER(FTDI_VID, PROTEGO_R2X0, 0, 0x3ff) },
+	{ USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_3, 0, 0x3ff) },
+	{ USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_4, 0, 0x3ff) },
 	{ }						/* Terminating entry */
 };
 
@@ -416,6 +420,10 @@
 	{ USB_DEVICE_VER(SEALEVEL_VID, SEALEVEL_2803_8_PID, 0x400, 0xffff) },
 	{ USB_DEVICE_VER(IDTECH_VID, IDTECH_IDT1221U_PID, 0x400, 0xffff) },
 	{ USB_DEVICE_VER(OCT_VID, OCT_US101_PID, 0x400, 0xffff) },
+	{ USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_1, 0x400, 0xffff) },
+	{ USB_DEVICE_VER(FTDI_VID, PROTEGO_R2X0, 0x400, 0xffff) },
+	{ USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_3, 0x400, 0xffff) },
+	{ USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_4, 0x400, 0xffff) },
 	{ }						/* Terminating entry */
 };
 
@@ -505,6 +513,10 @@
 	{ USB_DEVICE(OCT_VID, OCT_US101_PID) },
 	{ USB_DEVICE_VER(FTDI_VID, FTDI_HE_TIRA1_PID, 0x400, 0xffff) },
 	{ USB_DEVICE(FTDI_VID, FTDI_USB_UIRT_PID) },
+	{ USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_1) },
+	{ USB_DEVICE(FTDI_VID, PROTEGO_R2X0) },
+	{ USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_3) },
+	{ USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_4) },
 	{ }						/* Terminating entry */
 };
 
diff -Nru a/drivers/usb/serial/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h
--- a/drivers/usb/serial/ftdi_sio.h	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/serial/ftdi_sio.h	Fri Dec 12 15:06:58 2003
@@ -145,6 +145,14 @@
 /* Note: OCT US101 is also rebadged as Dick Smith Electronics (NZ) XH6381 */
 #define OCT_US101_PID		0x0421	/* OCT US101 USB to RS-232 */
 
+/*
+ * Protego product ids
+ */
+#define PROTEGO_SPECIAL_1	0xFC70	/* special/unknown device */
+#define PROTEGO_R2X0		0xFC71	/* R200-USB TRNG unit (R210, R220, and R230) */
+#define PROTEGO_SPECIAL_3	0xFC72	/* special/unknown device */
+#define PROTEGO_SPECIAL_4	0xFC73	/* special/unknown device */ 
+
 /* Commands */
 #define FTDI_SIO_RESET 		0 /* Reset the port */
 #define FTDI_SIO_MODEM_CTRL 	1 /* Set the modem control register */
diff -Nru a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c
--- a/drivers/usb/serial/io_edgeport.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/serial/io_edgeport.c	Fri Dec 12 15:06:57 2003
@@ -1488,16 +1488,20 @@
 		       usb_sndbulkpipe(edge_serial->serial->dev, edge_serial->bulk_out_endpoint),
 		       buffer, count+2, edge_bulk_out_data_callback, edge_port);
 
+	/* decrement the number of credits we have by the number we just sent */
+	edge_port->txCredits -= count;
+	edge_port->icount.tx += count;
+
 	urb->dev = edge_serial->serial->dev;
 	status = usb_submit_urb(urb, GFP_ATOMIC);
 	if (status) {
 		/* something went wrong */
 		dbg("%s - usb_submit_urb(write bulk) failed", __FUNCTION__);
 		edge_port->write_in_progress = FALSE;
-	} else {
-		/* decrement the number of credits we have by the number we just sent */
-		edge_port->txCredits -= count;
-		edge_port->icount.tx += count;
+
+		/* revert the credits as something bad happened. */
+		edge_port->txCredits += count;
+		edge_port->icount.tx -= count;
 	}
 	dbg("%s wrote %d byte(s) TxCredit %d, Fifo %d", __FUNCTION__, count, edge_port->txCredits, fifo->count);
 }
diff -Nru a/drivers/usb/serial/io_fw_boot.h b/drivers/usb/serial/io_fw_boot.h
--- a/drivers/usb/serial/io_fw_boot.h	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/serial/io_fw_boot.h	Fri Dec 12 15:06:58 2003
@@ -17,7 +17,7 @@
 		unsigned short Addr;
 		unsigned short Len;
 		unsigned char  Data[0];
-	};
+	} __attribute__ ((packed));
 
 	struct edge_firmware_version_info {
 		unsigned char	 MajorVersion;
diff -Nru a/drivers/usb/serial/io_fw_boot2.h b/drivers/usb/serial/io_fw_boot2.h
--- a/drivers/usb/serial/io_fw_boot2.h	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/serial/io_fw_boot2.h	Fri Dec 12 15:06:57 2003
@@ -17,7 +17,7 @@
 		unsigned short Addr;
 		unsigned short Len;
 		unsigned char  Data[0];
-	};
+	} __attribute__ ((packed));
 
 	struct edge_firmware_version_info {
 		unsigned char	 MajorVersion;
diff -Nru a/drivers/usb/serial/io_fw_down.h b/drivers/usb/serial/io_fw_down.h
--- a/drivers/usb/serial/io_fw_down.h	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/serial/io_fw_down.h	Fri Dec 12 15:06:58 2003
@@ -17,7 +17,7 @@
 		unsigned short	Addr;
 		unsigned short	Len;
 		unsigned char	Data[0];
-	};
+	} __attribute ((packed));
 
 	struct edge_firmware_version_info {
 		unsigned char	MajorVersion;
diff -Nru a/drivers/usb/serial/io_fw_down2.h b/drivers/usb/serial/io_fw_down2.h
--- a/drivers/usb/serial/io_fw_down2.h	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/serial/io_fw_down2.h	Fri Dec 12 15:06:57 2003
@@ -17,7 +17,7 @@
 		unsigned short Addr;
 		unsigned short Len;
 		unsigned char  Data[0];
-	};
+	} __attribute__ ((packed));
 
 	struct edge_firmware_version_info {
 		unsigned char  MajorVersion;
diff -Nru a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c
--- a/drivers/usb/serial/pl2303.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/serial/pl2303.c	Fri Dec 12 15:06:57 2003
@@ -71,6 +71,7 @@
 	{ USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ2) },
 	{ USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) },
 	{ USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID) },
+	{ USB_DEVICE(ATEN_VENDOR_ID2, ATEN_PRODUCT_ID) },
 	{ USB_DEVICE(ELCOM_VENDOR_ID, ELCOM_PRODUCT_ID) },
 	{ USB_DEVICE(ITEGNO_VENDOR_ID, ITEGNO_PRODUCT_ID) },
 	{ USB_DEVICE(MA620_VENDOR_ID, MA620_PRODUCT_ID) },
@@ -169,6 +170,7 @@
 
 struct pl2303_private {
 	spinlock_t lock;
+	wait_queue_head_t delta_msr_wait;
 	u8 line_control;
 	u8 line_status;
 	u8 termios_initialized;
@@ -186,6 +188,7 @@
 			return -ENOMEM;
 		memset (priv, 0x00, sizeof (struct pl2303_private));
 		spin_lock_init(&priv->lock);
+		init_waitqueue_head(&priv->delta_msr_wait);
 		usb_set_serial_port_data(serial->port[i], priv);
 	}
 	return 0;
@@ -556,11 +559,51 @@
 	return result;
 }
 
+static int wait_modem_info(struct usb_serial_port *port, unsigned int arg)
+{
+	struct pl2303_private *priv = usb_get_serial_port_data(port);
+	unsigned long flags;
+	unsigned int prevstatus;
+	unsigned int status;
+	unsigned int changed;
+
+	spin_lock_irqsave (&priv->lock, flags);
+	prevstatus = priv->line_status;
+	spin_unlock_irqrestore (&priv->lock, flags);
+
+	while (1) {
+		interruptible_sleep_on(&priv->delta_msr_wait);
+		/* see if a signal did it */
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+		
+		spin_lock_irqsave (&priv->lock, flags);
+		status = priv->line_status;
+		spin_unlock_irqrestore (&priv->lock, flags);
+		
+		changed=prevstatus^status;
+		
+		if (((arg & TIOCM_RNG) && (changed & UART_RING)) ||
+		    ((arg & TIOCM_DSR) && (changed & UART_DSR)) ||
+		    ((arg & TIOCM_CD)  && (changed & UART_DCD)) ||
+		    ((arg & TIOCM_CTS) && (changed & UART_CTS)) ) {
+			return 0;
+		}
+		prevstatus = status;
+	}
+	/* NOTREACHED */
+	return 0;
+}
+
 static int pl2303_ioctl (struct usb_serial_port *port, struct file *file, unsigned int cmd, unsigned long arg)
 {
 	dbg("%s (%d) cmd = 0x%04x", __FUNCTION__, port->number, cmd);
 
 	switch (cmd) {
+		case TIOCMIWAIT:
+			dbg("%s (%d) TIOCMIWAIT", __FUNCTION__,  port->number);
+			return wait_modem_info(port, arg);
+
 		default:
 			dbg("%s not supported = 0x%04x", __FUNCTION__, cmd);
 			break;
@@ -703,6 +746,7 @@
 	spin_lock_irqsave(&priv->lock, flags);
 	status = priv->line_status;
 	spin_unlock_irqrestore(&priv->lock, flags);
+	wake_up_interruptible (&priv->delta_msr_wait);
 
 	/* break takes precedence over parity, */
 	/* which takes precedence over framing errors */
diff -Nru a/drivers/usb/serial/pl2303.h b/drivers/usb/serial/pl2303.h
--- a/drivers/usb/serial/pl2303.h	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/serial/pl2303.h	Fri Dec 12 15:06:58 2003
@@ -12,6 +12,7 @@
 #define PL2303_PRODUCT_ID_RSAQ2	0x04bb
 
 #define ATEN_VENDOR_ID		0x0557
+#define ATEN_VENDOR_ID2		0x0547
 #define ATEN_PRODUCT_ID		0x2008
 
 #define IODATA_VENDOR_ID	0x04bb
diff -Nru a/drivers/usb/serial/visor.c b/drivers/usb/serial/visor.c
--- a/drivers/usb/serial/visor.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/serial/visor.c	Fri Dec 12 15:06:57 2003
@@ -231,6 +231,8 @@
 		.driver_info = (kernel_ulong_t)&palm_os_4_probe },
 	{ USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NX60_ID),
 		.driver_info = (kernel_ulong_t)&palm_os_4_probe },
+	{ USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_UX50_ID),
+		.driver_info = (kernel_ulong_t)&palm_os_4_probe },
 	{ USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NZ90V_ID),
 		.driver_info = (kernel_ulong_t)&palm_os_4_probe },
 	{ USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_SCH_I330_ID), 
@@ -266,6 +268,7 @@
 	{ USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_4_1_ID) },
 	{ USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NX60_ID) },
 	{ USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_NZ90V_ID) },
+	{ USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_UX50_ID) },
 	{ USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_SCH_I330_ID) },
 	{ USB_DEVICE(GARMIN_VENDOR_ID, GARMIN_IQUE_3600_ID) },
 	{ },					/* optional parameter entry */
diff -Nru a/drivers/usb/serial/visor.h b/drivers/usb/serial/visor.h
--- a/drivers/usb/serial/visor.h	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/serial/visor.h	Fri Dec 12 15:06:58 2003
@@ -41,6 +41,7 @@
 #define SONY_CLIE_4_1_ID		0x009A
 #define SONY_CLIE_NX60_ID		0x00DA
 #define SONY_CLIE_NZ90V_ID		0x00E9
+#define SONY_CLIE_UX50_ID		0x0144
 
 #define SAMSUNG_VENDOR_ID		0x04E8
 #define SAMSUNG_SCH_I330_ID		0x8001
diff -Nru a/drivers/usb/storage/Makefile b/drivers/usb/storage/Makefile
--- a/drivers/usb/storage/Makefile	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/storage/Makefile	Fri Dec 12 15:06:58 2003
@@ -10,14 +10,14 @@
 obj-$(CONFIG_USB_STORAGE)	+= usb-storage.o
 
 usb-storage-obj-$(CONFIG_USB_STORAGE_DEBUG)	+= debug.o
-usb-storage-obj-$(CONFIG_USB_STORAGE_HP8200e)	+= shuttle_usbat.o raw_bulk.o
-usb-storage-obj-$(CONFIG_USB_STORAGE_SDDR09)	+= sddr09.o raw_bulk.o
-usb-storage-obj-$(CONFIG_USB_STORAGE_SDDR55)	+= sddr55.o raw_bulk.o
+usb-storage-obj-$(CONFIG_USB_STORAGE_HP8200e)	+= shuttle_usbat.o
+usb-storage-obj-$(CONFIG_USB_STORAGE_SDDR09)	+= sddr09.o
+usb-storage-obj-$(CONFIG_USB_STORAGE_SDDR55)	+= sddr55.o
 usb-storage-obj-$(CONFIG_USB_STORAGE_FREECOM)	+= freecom.o
 usb-storage-obj-$(CONFIG_USB_STORAGE_DPCM)	+= dpcm.o
 usb-storage-obj-$(CONFIG_USB_STORAGE_ISD200)	+= isd200.o
-usb-storage-obj-$(CONFIG_USB_STORAGE_DATAFAB)	+= datafab.o raw_bulk.o
-usb-storage-obj-$(CONFIG_USB_STORAGE_JUMPSHOT)	+= jumpshot.o raw_bulk.o
+usb-storage-obj-$(CONFIG_USB_STORAGE_DATAFAB)	+= datafab.o
+usb-storage-obj-$(CONFIG_USB_STORAGE_JUMPSHOT)	+= jumpshot.o
 
 usb-storage-objs :=	scsiglue.o protocol.o transport.o usb.o \
 			initializers.o $(usb-storage-obj-y)
diff -Nru a/drivers/usb/storage/datafab.c b/drivers/usb/storage/datafab.c
--- a/drivers/usb/storage/datafab.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/storage/datafab.c	Fri Dec 12 15:06:58 2003
@@ -51,7 +51,6 @@
  */
 
 #include "transport.h"
-#include "raw_bulk.h"
 #include "protocol.h"
 #include "usb.h"
 #include "debug.h"
@@ -91,16 +90,14 @@
 			     struct datafab_info *info,
 			     u32 sector,
 			     u32 sectors, 
-			     unsigned char *dest, 
+			     unsigned char *buffer, 
 			     int use_sg)
 {
 	unsigned char *command = us->iobuf;
-	unsigned char *buffer = NULL;
-	unsigned char *ptr;
 	unsigned char  thistime;
-	int totallen, len, result;
-	int sg_idx = 0, sg_offset = 0;
-	int rc;
+	unsigned int totallen, alloclen;
+	int len, result;
+	unsigned int sg_idx = 0, sg_offset = 0;
 
 	// we're working in LBA mode.  according to the ATA spec, 
 	// we can support up to 28-bit addressing.  I don't know if Datafab
@@ -111,23 +108,32 @@
 		return USB_STOR_TRANSPORT_ERROR;
 
 	if (info->lun == -1) {
-		rc = datafab_determine_lun(us, info);
-		if (rc != USB_STOR_TRANSPORT_GOOD)
-			return rc;
+		result = datafab_determine_lun(us, info);
+		if (result != USB_STOR_TRANSPORT_GOOD)
+			return result;
 	}
 
 	totallen = sectors * info->ssize;
 
+	// Since we don't read more than 64 KB at a time, we have to create
+	// a bounce buffer if the transfer uses scatter-gather.  We will
+	// move the data a piece at a time between the bounce buffer and
+	// the actual transfer buffer.  If we're not using scatter-gather,
+	// we can simply update the transfer buffer pointer to get the
+	// same effect.
+
+	alloclen = min(totallen, 65536u);
+	if (use_sg) {
+		buffer = kmalloc(alloclen, GFP_NOIO);
+		if (buffer == NULL)
+			return USB_STOR_TRANSPORT_ERROR;
+	}
+
 	do {
 		// loop, never allocate or transfer more than 64k at once
 		// (min(128k, 255*info->ssize) is the real limit)
 
-		len = min_t(int, totallen, 65536);
-
-		ptr = buffer = (use_sg ? kmalloc(len, GFP_NOIO) : dest);
-		if (buffer == NULL)
-			return USB_STOR_TRANSPORT_ERROR;
-
+		len = min(totallen, alloclen);
 		thistime = (len / info->ssize) & 0xff;
 
 		command[0] = 0;
@@ -135,7 +141,7 @@
 		command[2] = sector & 0xFF;
 		command[3] = (sector >> 8) & 0xFF;
 		command[4] = (sector >> 16) & 0xFF;
-	
+
 		command[5] = 0xE0 + (info->lun << 4);
 		command[5] |= (sector >> 24) & 0x0F;
 		command[6] = 0x20;
@@ -147,24 +153,23 @@
 			goto leave;
 
 		// read the result
-		result = datafab_bulk_read(us, ptr, len);
+		result = datafab_bulk_read(us, buffer, len);
 		if (result != USB_STOR_XFER_GOOD)
 			goto leave;
 
-		sectors -= thistime;
-		sector  += thistime;
-
-		if (use_sg) {
-			us_copy_to_sgbuf(buffer, len, dest,
-					 &sg_idx, &sg_offset, use_sg);
-			kfree(buffer);
-		} else {
-			dest += len;
-		}
+		// Store the data (s-g) or update the pointer (!s-g)
+		if (use_sg)
+			usb_stor_access_xfer_buf(buffer, len, us->srb,
+					 &sg_idx, &sg_offset, TO_XFER_BUF);
+		else
+			buffer += len;
 
+		sector += thistime;
 		totallen -= len;
 	} while (totallen > 0);
 
+	if (use_sg)
+		kfree(buffer);
 	return USB_STOR_TRANSPORT_GOOD;
 
  leave:
@@ -178,16 +183,15 @@
 			      struct datafab_info *info,
 			      u32 sector,
 			      u32 sectors, 
-			      unsigned char *src, 
+			      unsigned char *buffer, 
 			      int use_sg)
 {
 	unsigned char *command = us->iobuf;
 	unsigned char *reply = us->iobuf;
-	unsigned char *buffer = NULL;
-	unsigned char *ptr;
 	unsigned char thistime;
-	int totallen, len, result, rc;
-	int sg_idx = 0, sg_offset = 0;
+	unsigned int totallen, alloclen;
+	int len, result;
+	unsigned int sg_idx = 0, sg_offset = 0;
 
 	// we're working in LBA mode.  according to the ATA spec, 
 	// we can support up to 28-bit addressing.  I don't know if Datafab
@@ -198,38 +202,39 @@
 		return USB_STOR_TRANSPORT_ERROR;
 
 	if (info->lun == -1) {
-		rc = datafab_determine_lun(us, info);
-		if (rc != USB_STOR_TRANSPORT_GOOD)
-			return rc;
+		result = datafab_determine_lun(us, info);
+		if (result != USB_STOR_TRANSPORT_GOOD)
+			return result;
 	}
 
-	// If we're using scatter-gather, we have to create a new
-	// buffer to read all of the data in first, since a
-	// scatter-gather buffer could in theory start in the middle
-	// of a page, which would be bad. A developer who wants a
-	// challenge might want to write a limited-buffer
-	// version of this code.
-
 	totallen = sectors * info->ssize;
 
-	do {
-		// loop, never allocate or transfer more than 64k at once
-		// (min(128k, 255*info->ssize) is the real limit)
-
-		len = min_t(int, totallen, 65536);
-
-		// if we are using scatter-gather,
-		// first copy all to one big buffer
-
-		buffer = us_copy_from_sgbuf(src, len, &sg_idx,
-					    &sg_offset, use_sg);
+	// Since we don't write more than 64 KB at a time, we have to create
+	// a bounce buffer if the transfer uses scatter-gather.  We will
+	// move the data a piece at a time between the bounce buffer and
+	// the actual transfer buffer.  If we're not using scatter-gather,
+	// we can simply update the transfer buffer pointer to get the
+	// same effect.
+
+	alloclen = min(totallen, 65536u);
+	if (use_sg) {
+		buffer = kmalloc(alloclen, GFP_NOIO);
 		if (buffer == NULL)
 			return USB_STOR_TRANSPORT_ERROR;
+	}
 
-		ptr = buffer;
+	do {
+		// loop, never allocate or transfer more than 64k at once
+		// (min(128k, 255*info->ssize) is the real limit)
 
+		len = min(totallen, alloclen);
 		thistime = (len / info->ssize) & 0xff;
 
+		// Get the data from the transfer buffer (s-g)
+		if (use_sg)
+			usb_stor_access_xfer_buf(buffer, len, us->srb,
+					&sg_idx, &sg_offset, FROM_XFER_BUF);
+
 		command[0] = 0;
 		command[1] = thistime;
 		command[2] = sector & 0xFF;
@@ -247,7 +252,7 @@
 			goto leave;
 
 		// send the data
-		result = datafab_bulk_write(us, ptr, len);
+		result = datafab_bulk_write(us, buffer, len);
 		if (result != USB_STOR_XFER_GOOD)
 			goto leave;
 
@@ -264,17 +269,16 @@
 			goto leave;
 		}
 
-		sectors -= thistime;
-		sector  += thistime;
-
-		if (use_sg)
-			kfree(buffer);
-		else
-			src += len;
+		// Update the transfer buffer pointer (!s-g)
+		if (!use_sg)
+			buffer += len;
 
+		sector += thistime;
 		totallen -= len;
 	} while (totallen > 0);
 
+	if (use_sg)
+		kfree(buffer);
 	return USB_STOR_TRANSPORT_GOOD;
 
  leave:
@@ -435,7 +439,7 @@
 	// datafab reader doesn't present a SCSI interface so we
 	// fudge the SCSI commands...
 	//
-	
+
 	if (sense_6)
 		param_len = srb->cmnd[4];
 	else
diff -Nru a/drivers/usb/storage/debug.c b/drivers/usb/storage/debug.c
--- a/drivers/usb/storage/debug.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/storage/debug.c	Fri Dec 12 15:06:57 2003
@@ -150,65 +150,6 @@
 	US_DEBUGPX("\n");
 }
 
-void usb_stor_print_Scsi_Cmnd(Scsi_Cmnd *cmd)
-{
-	int i=0, bufferSize = cmd->request_bufflen;
-	u8 *buffer = cmd->request_buffer;
-	struct scatterlist *sg = (struct scatterlist*)cmd->request_buffer;
-
-	US_DEBUGP("Dumping information about %p.\n", cmd);
-	US_DEBUGP("cmd->cmnd[0] value is %d.\n", cmd->cmnd[0]);
-	US_DEBUGP("(MODE_SENSE is %d and MODE_SENSE_10 is %d)\n",
-		  MODE_SENSE, MODE_SENSE_10);
-
-	US_DEBUGP("buffer is %p with length %d.\n", buffer, bufferSize);
-	for (i=0; i<bufferSize; i+=16) {
-		US_DEBUGP("%02x %02x %02x %02x %02x %02x %02x %02x\n"
-			  "%02x %02x %02x %02x %02x %02x %02x %02x\n",
-			  buffer[i],
-			  buffer[i+1],
-			  buffer[i+2],
-			  buffer[i+3],
-			  buffer[i+4],
-			  buffer[i+5],
-			  buffer[i+6],
-			  buffer[i+7],
-			  buffer[i+8],
-			  buffer[i+9],
-			  buffer[i+10],
-			  buffer[i+11],
-			  buffer[i+12],
-			  buffer[i+13],
-			  buffer[i+14],
-			  buffer[i+15] );
-	}
-
-	US_DEBUGP("Buffer has %d scatterlists.\n", cmd->use_sg );
-	for (i=0; i<cmd->use_sg; i++) {
-		char *adr = sg_address(sg[i]);
-		
-		US_DEBUGP("Length of scatterlist %d is %d.\n",i,sg[i].length);
-		US_DEBUGP("%02x %02x %02x %02x %02x %02x %02x %02x\n"
-			  "%02x %02x %02x %02x %02x %02x %02x %02x\n",
-			  adr[0],
-			  adr[1],
-			  adr[2],
-			  adr[3],
-			  adr[4],
-			  adr[5],
-			  adr[6],
-			  adr[7],
-			  adr[8],
-			  adr[9],
-			  adr[10],
-			  adr[11],
-			  adr[12],
-			  adr[13],
-			  adr[14],
-			  adr[15]);
-	}
-}
-
 void usb_stor_show_sense(
 		unsigned char key,
 		unsigned char asc,
diff -Nru a/drivers/usb/storage/debug.h b/drivers/usb/storage/debug.h
--- a/drivers/usb/storage/debug.h	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/storage/debug.h	Fri Dec 12 15:06:58 2003
@@ -53,7 +53,6 @@
 
 #ifdef CONFIG_USB_STORAGE_DEBUG
 void usb_stor_show_command(Scsi_Cmnd *srb);
-void usb_stor_print_Scsi_Cmnd( Scsi_Cmnd* cmd );
 void usb_stor_show_sense( unsigned char key,
 		unsigned char asc, unsigned char ascq );
 #define US_DEBUGP(x...) printk( KERN_DEBUG USB_STORAGE x )
diff -Nru a/drivers/usb/storage/isd200.c b/drivers/usb/storage/isd200.c
--- a/drivers/usb/storage/isd200.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/storage/isd200.c	Fri Dec 12 15:06:58 2003
@@ -543,7 +543,6 @@
 	int result;
 
 	/* send the command to the transport layer */
-	srb->resid = 0;
 	memcpy(srb->cmnd, ataCdb, sizeof(ataCdb->generic));
 	srb->cmd_len = sizeof(ataCdb->generic);
 	transferStatus = usb_stor_Bulk_transport(srb, us);
@@ -1117,60 +1116,6 @@
 
 
 /**************************************************************************
- * isd200_data_copy
- *									 
- * Copy data into the srb request buffer.  Use scatter gather if required.
- *
- * RETURNS:
- *    void
- */
-void isd200_data_copy(Scsi_Cmnd *srb, char * src, int length)
-{
-	unsigned int len = length;
-	struct scatterlist *sg;
-
-	if (srb->use_sg) {
-		int i;
-		unsigned int total = 0;
-
-		/* Add up the sizes of all the sg segments */
-		sg = (struct scatterlist *) srb->request_buffer;
-		for (i = 0; i < srb->use_sg; i++)
-			total += sg[i].length;
-
-		if (length > total)
-			len = total;
-
-		total = 0;
-
-		/* Copy data into sg buffer(s) */
-		for (i = 0; i < srb->use_sg; i++) {
-			if ((len > total) && (len > 0)) {
-				/* transfer the lesser of the next buffer or the
-				 * remaining data */
-				if (len - total >= sg[i].length) {
-					memcpy(sg_address(sg[i]), src + total, sg[i].length);
-					total += sg[i].length;
-				} else {
-					memcpy(sg_address(sg[i]), src + total, len - total);
-					total = len;
-				}
-			} 
-			else
-				break;
-		}
-	} else	{
-		/* Make sure length does not exceed buffer length */
-		if (length > srb->request_bufflen)
-			len = srb->request_bufflen;
-
-		if (len > 0)
-			memcpy(srb->request_buffer, src, len);
-	}
-}
-
-
-/**************************************************************************
  * isd200_scsi_to_ata
  *									 
  * Translate SCSI commands to ATA commands.
@@ -1198,11 +1143,9 @@
 	case INQUIRY:
 		US_DEBUGP("   ATA OUT - INQUIRY\n");
 
-		if (srb->request_bufflen > sizeof(struct inquiry_data))
-			srb->request_bufflen = sizeof(struct inquiry_data);
-
 		/* copy InquiryData */
-		isd200_data_copy(srb, (char *) &info->InquiryData, srb->request_bufflen);
+		usb_stor_set_xfer_buf((unsigned char *) &info->InquiryData,
+				sizeof(info->InquiryData), srb);
 		srb->result = SAM_STAT_GOOD;
 		sendToTransport = FALSE;
 		break;
@@ -1211,7 +1154,7 @@
 		US_DEBUGP("   ATA OUT - SCSIOP_MODE_SENSE\n");
 
 		/* Initialize the return buffer */
-		isd200_data_copy(srb, (char *) &senseData, 8);
+		usb_stor_set_xfer_buf(senseData, sizeof(senseData), srb);
 
 		if (info->DeviceFlags & DF_MEDIA_STATUS_ENABLED)
 		{
@@ -1231,9 +1174,6 @@
 	case TEST_UNIT_READY:
 		US_DEBUGP("   ATA OUT - SCSIOP_TEST_UNIT_READY\n");
 
-		/* Initialize the return buffer */
-		isd200_data_copy(srb, (char *) &senseData, 8);
-
 		if (info->DeviceFlags & DF_MEDIA_STATUS_ENABLED)
 		{
 			ataCdb->generic.SignatureByte0 = info->ConfigData.ATAMajorCommand;
@@ -1266,10 +1206,8 @@
 		readCapacityData.LogicalBlockAddress = cpu_to_be32(capacity);
 		readCapacityData.BytesPerBlock = cpu_to_be32(0x200);
 
-		if (srb->request_bufflen > sizeof(struct read_capacity_data))
-			srb->request_bufflen = sizeof(struct read_capacity_data);
-
-		isd200_data_copy(srb, (char *) &readCapacityData, srb->request_bufflen);
+		usb_stor_set_xfer_buf((unsigned char *) &readCapacityData,
+				sizeof(readCapacityData), srb);
 		srb->result = SAM_STAT_GOOD;
 		sendToTransport = FALSE;
 	}
@@ -1363,9 +1301,6 @@
 		US_DEBUGP("   ATA OUT - SCSIOP_START_STOP_UNIT\n");
 		US_DEBUGP("   srb->cmnd[4] = 0x%X\n", srb->cmnd[4]);
 
-		/* Initialize the return buffer */
-		isd200_data_copy(srb, (char *) &senseData, 8);
-
 		if ((srb->cmnd[4] & 0x3) == 0x2) {
 			US_DEBUGP("   Media Eject\n");
 			ataCdb->generic.SignatureByte0 = info->ConfigData.ATAMajorCommand;
@@ -1500,6 +1435,7 @@
 		US_DEBUGP("ERROR Driver not initialized\n");
 
 	/* Convert command */
+	srb->resid = 0;
 	sendToTransport = isd200_scsi_to_ata(srb, us, &ataCdb);
 
 	/* send the command to the transport layer */
diff -Nru a/drivers/usb/storage/jumpshot.c b/drivers/usb/storage/jumpshot.c
--- a/drivers/usb/storage/jumpshot.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/storage/jumpshot.c	Fri Dec 12 15:06:57 2003
@@ -48,7 +48,6 @@
   */
 
 #include "transport.h"
-#include "raw_bulk.h"
 #include "protocol.h"
 #include "usb.h"
 #include "debug.h"
@@ -111,15 +110,14 @@
 			      struct jumpshot_info *info,
 			      u32 sector,
 			      u32 sectors, 
-			      unsigned char *dest, 
+			      unsigned char *buffer, 
 			      int use_sg)
 {
 	unsigned char *command = us->iobuf;
-	unsigned char *buffer = NULL;
-	unsigned char *ptr;
 	unsigned char  thistime;
-	int totallen, len, result;
-	int sg_idx = 0, current_sg_offset = 0;
+	unsigned int totallen, alloclen;
+	int len, result;
+	unsigned int sg_idx = 0, sg_offset = 0;
 
 	// we're working in LBA mode.  according to the ATA spec, 
 	// we can support up to 28-bit addressing.  I don't know if Jumpshot
@@ -131,20 +129,24 @@
 
 	totallen = sectors * info->ssize;
 
+	// Since we don't read more than 64 KB at a time, we have to create
+	// a bounce buffer if the transfer uses scatter-gather.  We will
+	// move the data a piece at a time between the bounce buffer and
+	// the actual transfer buffer.  If we're not using scatter-gather,
+	// we can simply update the transfer buffer pointer to get the
+	// same effect.
+
+	alloclen = min(totallen, 65536u);
+	if (use_sg) {
+		buffer = kmalloc(alloclen, GFP_NOIO);
+		if (buffer == NULL)
+			return USB_STOR_TRANSPORT_ERROR;
+	}
+
 	do {
 		// loop, never allocate or transfer more than 64k at once
 		// (min(128k, 255*info->ssize) is the real limit)
-		len = min_t(int, totallen, 65536);
-
-		if (use_sg) {
-			buffer = kmalloc(len, GFP_NOIO);
-			if (buffer == NULL)
-				return USB_STOR_TRANSPORT_ERROR;
-			ptr = buffer;
-		} else {
-			ptr = dest;
-		}
-
+		len = min(totallen, alloclen);
 		thistime = (len / info->ssize) & 0xff;
 
 		command[0] = 0;
@@ -163,26 +165,25 @@
 			goto leave;
 
 		// read the result
-		result = jumpshot_bulk_read(us, ptr, len);
+		result = jumpshot_bulk_read(us, buffer, len);
 		if (result != USB_STOR_XFER_GOOD)
 			goto leave;
 
 		US_DEBUGP("jumpshot_read_data:  %d bytes\n", len);
-	
-		sectors -= thistime;
-		sector  += thistime;
-
-		if (use_sg) {
-			us_copy_to_sgbuf(buffer, len, dest,
-					 &sg_idx, &current_sg_offset, use_sg);
-			kfree(buffer);
-		} else {
-			dest += len;
-		}
 
+		// Store the data (s-g) or update the pointer (!s-g)
+		if (use_sg)
+			usb_stor_access_xfer_buf(buffer, len, us->srb,
+					 &sg_idx, &sg_offset, TO_XFER_BUF);
+		else
+			buffer += len;
+
+		sector += thistime;
 		totallen -= len;
 	} while (totallen > 0);
 
+	if (use_sg)
+		kfree(buffer);
 	return USB_STOR_TRANSPORT_GOOD;
 
  leave:
@@ -196,15 +197,14 @@
 			       struct jumpshot_info *info,
 			       u32 sector,
 			       u32 sectors, 
-			       unsigned char *src, 
+			       unsigned char *buffer, 
 			       int use_sg)
 {
 	unsigned char *command = us->iobuf;
-	unsigned char *buffer = NULL;
-	unsigned char *ptr;
 	unsigned char  thistime;
-	int totallen, len, result, waitcount;
-	int sg_idx = 0, sg_offset = 0;
+	unsigned int totallen, alloclen;
+	int len, result, waitcount;
+	unsigned int sg_idx = 0, sg_offset = 0;
 
 	// we're working in LBA mode.  according to the ATA spec, 
 	// we can support up to 28-bit addressing.  I don't know if Jumpshot
@@ -216,24 +216,32 @@
 
 	totallen = sectors * info->ssize;
 
-	do {
-		// loop, never allocate or transfer more than 64k at once
-		// (min(128k, 255*info->ssize) is the real limit)
-
-		len = min_t(int, totallen, 65536);
-
-		// if we are using scatter-gather,
-		// first copy all to one big buffer
-
-		buffer = us_copy_from_sgbuf(src, len, &sg_idx,
-					    &sg_offset, use_sg);
+	// Since we don't write more than 64 KB at a time, we have to create
+	// a bounce buffer if the transfer uses scatter-gather.  We will
+	// move the data a piece at a time between the bounce buffer and
+	// the actual transfer buffer.  If we're not using scatter-gather,
+	// we can simply update the transfer buffer pointer to get the
+	// same effect.
+
+	alloclen = min(totallen, 65536u);
+	if (use_sg) {
+		buffer = kmalloc(alloclen, GFP_NOIO);
 		if (buffer == NULL)
 			return USB_STOR_TRANSPORT_ERROR;
+	}
 
-		ptr = buffer;
+	do {
+		// loop, never allocate or transfer more than 64k at once
+		// (min(128k, 255*info->ssize) is the real limit)
 
+		len = min(totallen, alloclen);
 		thistime = (len / info->ssize) & 0xff;
 
+		// Get the data from the transfer buffer (s-g)
+		if (use_sg)
+			usb_stor_access_xfer_buf(buffer, len, us->srb,
+					&sg_idx, &sg_offset, FROM_XFER_BUF);
+
 		command[0] = 0;
 		command[1] = thistime;
 		command[2] = sector & 0xFF;
@@ -250,7 +258,7 @@
 			goto leave;
 
 		// send the data
-		result = jumpshot_bulk_write(us, ptr, len);
+		result = jumpshot_bulk_write(us, buffer, len);
 		if (result != USB_STOR_XFER_GOOD)
 			goto leave;
 
@@ -269,18 +277,17 @@
 
 		if (result != USB_STOR_TRANSPORT_GOOD)
 			US_DEBUGP("jumpshot_write_data:  Gah!  Waitcount = 10.  Bad write!?\n");
-		
-		sectors -= thistime;
-		sector  += thistime;
 
-		if (use_sg)
-			kfree(buffer);
-		else
-			src += len;
+		// Update the transfer buffer pointer (!s-g)
+		if (!use_sg)
+			buffer += len;
 
+		sector += thistime;
 		totallen -= len;
 	} while (totallen > 0);
 
+	if (use_sg)
+		kfree(buffer);
 	return result;
 
  leave:
@@ -605,14 +612,14 @@
 		US_DEBUGP("jumpshot_transport:  MODE_SENSE_10 detected\n");
 		return jumpshot_handle_mode_sense(us, srb, ptr, FALSE);
 	}
-	
+
 	if (srb->cmnd[0] == ALLOW_MEDIUM_REMOVAL) {
 		// sure.  whatever.  not like we can stop the user from popping
 		// the media out of the device (no locking doors, etc)
 		//
 		return USB_STOR_TRANSPORT_GOOD;
 	}
-	
+
 	if (srb->cmnd[0] == START_STOP) {
 		/* this is used by sd.c'check_scsidisk_media_change to detect
 		   media change */
diff -Nru a/drivers/usb/storage/protocol.c b/drivers/usb/storage/protocol.c
--- a/drivers/usb/storage/protocol.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/storage/protocol.c	Fri Dec 12 15:06:57 2003
@@ -44,6 +44,7 @@
  * 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
+#include <linux/highmem.h>
 #include "protocol.h"
 #include "usb.h"
 #include "debug.h"
@@ -54,48 +55,36 @@
  * Helper routines
  ***********************************************************************/
 
-static void *
-find_data_location(Scsi_Cmnd *srb) {
-	if (srb->use_sg) {
-		/*
-		 * This piece of code only works if the first page is
-		 * big enough to hold more than 3 bytes -- which is
-		 * _very_ likely.
-		 */
-		struct scatterlist *sg;
-
-		sg = (struct scatterlist *) srb->request_buffer;
-		return (void *) sg_address(sg[0]);
-	} else
-		return (void *) srb->request_buffer;
-}
-
 /*
  * Fix-up the return data from an INQUIRY command to show 
  * ANSI SCSI rev 2 so we don't confuse the SCSI layers above us
  */
 static void fix_inquiry_data(Scsi_Cmnd *srb)
 {
-	unsigned char *data_ptr;
+	unsigned char databuf[3];
+	unsigned int index, offset;
 
 	/* verify that it's an INQUIRY command */
 	if (srb->cmnd[0] != INQUIRY)
 		return;
 
-	/* oddly short buffer -- bail out */
-	if (srb->request_bufflen < 3)
+	index = offset = 0;
+	if (usb_stor_access_xfer_buf(databuf, sizeof(databuf), srb,
+			&index, &offset, FROM_XFER_BUF) != sizeof(databuf))
 		return;
 
-	data_ptr = find_data_location(srb);
-
-	if ((data_ptr[2] & 7) == 2)
+	if ((databuf[2] & 7) == 2)
 		return;
 
 	US_DEBUGP("Fixing INQUIRY data to show SCSI rev 2 - was %d\n",
-		  data_ptr[2] & 7);
+		  databuf[2] & 7);
 
 	/* Change the SCSI revision number */
-	data_ptr[2] = (data_ptr[2] & ~7) | 2;
+	databuf[2] = (databuf[2] & ~7) | 2;
+
+	index = offset = 0;
+	usb_stor_access_xfer_buf(databuf, sizeof(databuf), srb,
+			&index, &offset, TO_XFER_BUF);
 }
 
 /*
@@ -104,23 +93,27 @@
  */
 static void fix_read_capacity(Scsi_Cmnd *srb)
 {
-	unsigned char *dp;
+	unsigned int index, offset;
+	u32 c;
 	unsigned long capacity;
 
 	/* verify that it's a READ CAPACITY command */
 	if (srb->cmnd[0] != READ_CAPACITY)
 		return;
 
-	dp = find_data_location(srb);
+	index = offset = 0;
+	if (usb_stor_access_xfer_buf((unsigned char *) &c, 4, srb,
+			&index, &offset, FROM_XFER_BUF) != 4)
+		return;
 
-	capacity = (dp[0]<<24) + (dp[1]<<16) + (dp[2]<<8) + (dp[3]);
+	capacity = be32_to_cpu(c);
 	US_DEBUGP("US: Fixing capacity: from %ld to %ld\n",
 	       capacity+1, capacity);
-	capacity--;
-	dp[0] = (capacity >> 24);
-	dp[1] = (capacity >> 16);
-	dp[2] = (capacity >> 8);
-	dp[3] = (capacity);
+	c = cpu_to_be32(capacity - 1);
+
+	index = offset = 0;
+	usb_stor_access_xfer_buf((unsigned char *) &c, 4, srb,
+			&index, &offset, TO_XFER_BUF);
 }
 
 /***********************************************************************
@@ -233,4 +226,113 @@
 		if (us->flags & US_FL_FIX_CAPACITY)
 			fix_read_capacity(srb);
 	}
+}
+
+/***********************************************************************
+ * Scatter-gather transfer buffer access routines
+ ***********************************************************************/
+
+/* Copy a buffer of length buflen to/from the srb's transfer buffer.
+ * (Note: for scatter-gather transfers (srb->use_sg > 0), srb->request_buffer
+ * points to a list of s-g entries and we ignore srb->request_bufflen.
+ * For non-scatter-gather transfers, srb->request_buffer points to the
+ * transfer buffer itself and srb->request_bufflen is the buffer's length.)
+ * Update the *index and *offset variables so that the next copy will
+ * pick up from where this one left off. */
+
+unsigned int usb_stor_access_xfer_buf(unsigned char *buffer,
+	unsigned int buflen, Scsi_Cmnd *srb, unsigned int *index,
+	unsigned int *offset, enum xfer_buf_dir dir)
+{
+	unsigned int cnt;
+
+	/* If not using scatter-gather, just transfer the data directly.
+	 * Make certain it will fit in the available buffer space. */
+	if (srb->use_sg == 0) {
+		if (*offset >= srb->request_bufflen)
+			return 0;
+		cnt = min(buflen, srb->request_bufflen - *offset);
+		if (dir == TO_XFER_BUF)
+			memcpy((unsigned char *) srb->request_buffer + *offset,
+					buffer, cnt);
+		else
+			memcpy(buffer, (unsigned char *) srb->request_buffer +
+					*offset, cnt);
+		*offset += cnt;
+
+	/* Using scatter-gather.  We have to go through the list one entry
+	 * at a time.  Each s-g entry contains some number of pages, and
+	 * each page has to be kmap()'ed separately.  If the page is already
+	 * in kernel-addressable memory then kmap() will return its address.
+	 * If the page is not directly accessible -- such as a user buffer
+	 * located in high memory -- then kmap() will map it to a temporary
+	 * position in the kernel's virtual address space. */
+	} else {
+		struct scatterlist *sg =
+				(struct scatterlist *) srb->request_buffer
+				+ *index;
+
+		/* This loop handles a single s-g list entry, which may
+		 * include multiple pages.  Find the initial page structure
+		 * and the starting offset within the page, and update
+		 * the *offset and *index values for the next loop. */
+		cnt = 0;
+		while (cnt < buflen && *index < srb->use_sg) {
+			struct page *page = sg->page +
+					((sg->offset + *offset) >> PAGE_SHIFT);
+			unsigned int poff =
+					(sg->offset + *offset) & (PAGE_SIZE-1);
+			unsigned int sglen = sg->length - *offset;
+
+			if (sglen > buflen - cnt) {
+
+				/* Transfer ends within this s-g entry */
+				sglen = buflen - cnt;
+				*offset += sglen;
+			} else {
+
+				/* Transfer continues to next s-g entry */
+				*offset = 0;
+				++*index;
+				++sg;
+			}
+
+			/* Transfer the data for all the pages in this
+			 * s-g entry.  For each page: call kmap(), do the
+			 * transfer, and call kunmap() immediately after. */
+			while (sglen > 0) {
+				unsigned int plen = min(sglen, (unsigned int)
+						PAGE_SIZE - poff);
+				unsigned char *ptr = kmap(page);
+
+				if (dir == TO_XFER_BUF)
+					memcpy(ptr + poff, buffer + cnt, plen);
+				else
+					memcpy(buffer + cnt, ptr + poff, plen);
+				kunmap(page);
+
+				/* Start at the beginning of the next page */
+				poff = 0;
+				++page;
+				cnt += plen;
+				sglen -= plen;
+			}
+		}
+	}
+
+	/* Return the amount actually transferred */
+	return cnt;
+}
+
+/* Store the contents of buffer into srb's transfer buffer and set the
+ * SCSI residue. */
+void usb_stor_set_xfer_buf(unsigned char *buffer,
+	unsigned int buflen, Scsi_Cmnd *srb)
+{
+	unsigned int index = 0, offset = 0;
+
+	usb_stor_access_xfer_buf(buffer, buflen, srb, &index, &offset,
+			TO_XFER_BUF);
+	if (buflen < srb->request_bufflen)
+		srb->resid = srb->request_bufflen - buflen;
 }
diff -Nru a/drivers/usb/storage/protocol.h b/drivers/usb/storage/protocol.h
--- a/drivers/usb/storage/protocol.h	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/storage/protocol.h	Fri Dec 12 15:06:57 2003
@@ -59,9 +59,19 @@
 
 #define US_SC_DEVICE	0xff		/* Use device's value */
 
+/* Protocol handling routines */
 extern void usb_stor_ATAPI_command(Scsi_Cmnd*, struct us_data*);
 extern void usb_stor_qic157_command(Scsi_Cmnd*, struct us_data*);
 extern void usb_stor_ufi_command(Scsi_Cmnd*, struct us_data*);
 extern void usb_stor_transparent_scsi_command(Scsi_Cmnd*, struct us_data*);
 
+/* Scsi_Cmnd transfer buffer access utilities */
+enum xfer_buf_dir	{TO_XFER_BUF, FROM_XFER_BUF};
+
+extern unsigned int usb_stor_access_xfer_buf(unsigned char *buffer,
+	unsigned int buflen, Scsi_Cmnd *srb, unsigned int *index,
+	unsigned int *offset, enum xfer_buf_dir dir);
+
+extern void usb_stor_set_xfer_buf(unsigned char *buffer,
+	unsigned int buflen, Scsi_Cmnd *srb);
 #endif
diff -Nru a/drivers/usb/storage/raw_bulk.c b/drivers/usb/storage/raw_bulk.c
--- a/drivers/usb/storage/raw_bulk.c	Fri Dec 12 15:06:58 2003
+++ /dev/null	Wed Dec 31 16:00:00 1969
@@ -1,116 +0,0 @@
-/*
- * Common routines for a handful of drivers.
- * Unrelated to CF/SM - just scatter-gather stuff.
- */
-
-#include "usb.h"
-#include "raw_bulk.h"
-
-/*
- * The routines below convert scatter-gather to single buffer.
- * Some drivers claim this is necessary.
- * Nothing is done when use_sg is zero.
- */
-
-/*
- * Copy from scatter-gather buffer into a newly allocated single buffer,
- * starting at a given index and offset.
- * When done, update index and offset.
- * Return a pointer to the single buffer.
- */
-unsigned char *
-us_copy_from_sgbuf(unsigned char *content, int len,
-		   int *index, int *offset, int use_sg) {
-	struct scatterlist *sg;
-	unsigned char *buffer;
-	int transferred, i;
-
-	if (!use_sg)
-		return content;
-
-	sg = (struct scatterlist *)content;
-	buffer = kmalloc(len, GFP_NOIO);
-	if (buffer == NULL)
-		return NULL;
-
-	transferred = 0;
-	i = *index;
-	while (i < use_sg && transferred < len) {
-		unsigned char *ptr;
-		unsigned int length, room;
-
-		ptr = sg_address(sg[i]) + *offset;
-
-		room = sg[i].length - *offset;
-		length = len - transferred;
-		if (length > room)
-			length = room;
-
-		memcpy(buffer+transferred, ptr, length);
-		transferred += length;
-		*offset += length;
-		if (length == room) {
-			i++;
-			*offset = 0;
-		}
-	}
-	*index = i;
-
-	return buffer;
-}
-
-unsigned char *
-us_copy_from_sgbuf_all(unsigned char *content, int len, int use_sg) {
-	int index, offset;
-
-	index = offset = 0;
-	return us_copy_from_sgbuf(content, len, &index, &offset, use_sg);
-}
-
-/*
- * Copy from a single buffer into a scatter-gather buffer,
- * starting at a given index and offset.
- * When done, update index and offset.
- */
-void
-us_copy_to_sgbuf(unsigned char *buffer, int buflen,
-		 void *content, int *index, int *offset, int use_sg) {
-	struct scatterlist *sg;
-	int i, transferred;
-
-	if (!use_sg)
-		return;
-
-	transferred = 0;
-	sg = content;
-	i = *index;
-	while (i < use_sg && transferred < buflen) {
-		unsigned char *ptr;
-		unsigned int length, room;
-
-		ptr = sg_address(sg[i]) + *offset;
-
-		room = sg[i].length - *offset;
-		length = buflen - transferred;
-		if (length > room)
-			length = room;
-		
-		memcpy(ptr, buffer+transferred, length);
-		transferred += sg[i].length;
-		*offset += length;
-		if (length == room) {
-			i++;
-			*offset = 0;
-		}
-	}
-	*index = i;
-}
-
-void
-us_copy_to_sgbuf_all(unsigned char *buffer, int buflen,
-		     void *content, int use_sg) {
-	int index, offset;
-
-	index = offset = 0;
-	us_copy_to_sgbuf(buffer, buflen, content, &index, &offset, use_sg);
-}
diff -Nru a/drivers/usb/storage/raw_bulk.h b/drivers/usb/storage/raw_bulk.h
--- a/drivers/usb/storage/raw_bulk.h	Fri Dec 12 15:06:57 2003
+++ /dev/null	Wed Dec 31 16:00:00 1969
@@ -1,20 +0,0 @@
-#ifndef _USB_STORAGE_RAW_BULK_H_
-#define _USB_STORAGE_RAW_BULK_H_
-
-/* scatter-gather */
-extern unsigned char *us_copy_from_sgbuf(
-	unsigned char *content, int buflen,
-	int *index, int *offset, int use_sg);
-
-extern unsigned char *us_copy_from_sgbuf_all(
-	unsigned char *content, int len, int use_sg);
-
-extern void us_copy_to_sgbuf(
-	unsigned char *buffer, int buflen,
-	void *content, int *index, int *offset, int use_sg);
-
-extern void us_copy_to_sgbuf_all(
-	unsigned char *buffer, int buflen,
-	void *content, int use_sg);
-
-#endif
diff -Nru a/drivers/usb/storage/scsiglue.c b/drivers/usb/storage/scsiglue.c
--- a/drivers/usb/storage/scsiglue.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/storage/scsiglue.c	Fri Dec 12 15:06:58 2003
@@ -325,7 +325,8 @@
 	.emulated =			TRUE,
 
 	/* modify scsi_device bits on probe */
-	.flags = (BLIST_MS_SKIP_PAGE_08 | BLIST_USE_10_BYTE_MS),
+	.flags = (BLIST_MS_SKIP_PAGE_08 | BLIST_MS_SKIP_PAGE_3F |
+		  BLIST_USE_10_BYTE_MS),
 
 	/* module management */
 	.module =			THIS_MODULE
diff -Nru a/drivers/usb/storage/sddr09.c b/drivers/usb/storage/sddr09.c
--- a/drivers/usb/storage/sddr09.c	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/storage/sddr09.c	Fri Dec 12 15:06:58 2003
@@ -28,7 +28,6 @@
  */
 
 #include "transport.h"
-#include "raw_bulk.h"
 #include "protocol.h"
 #include "usb.h"
 #include "debug.h"
@@ -66,7 +65,7 @@
  * NAND Flash Manufacturer ID Codes
  */
 #define NAND_MFR_AMD		0x01
-#define NAND_MFR_NS		0x8f
+#define NAND_MFR_NATSEMI	0x8f
 #define NAND_MFR_TOSHIBA	0x98
 #define NAND_MFR_SAMSUNG	0xec
 
@@ -74,8 +73,8 @@
 	switch(manuf_id) {
 	case NAND_MFR_AMD:
 		return "AMD";
-	case NAND_MFR_NS:
-		return "NS";
+	case NAND_MFR_NATSEMI:
+		return "NATSEMI";
 	case NAND_MFR_TOSHIBA:
 		return "Toshiba";
 	case NAND_MFR_SAMSUNG:
@@ -302,8 +301,7 @@
 	if (result != USB_STOR_XFER_GOOD) {
 		US_DEBUGP("request sense bulk in failed\n");
 		return USB_STOR_TRANSPORT_ERROR;
-	}
-	else {
+	} else {
 		US_DEBUGP("request sense worked\n");
 		return USB_STOR_TRANSPORT_GOOD;
 	}
@@ -469,6 +467,8 @@
 	unsigned char *command = us->iobuf;
 	int result;
 
+	US_DEBUGP("sddr09_erase: erase address %lu\n", Eaddress);
+
 	memset(command, 0, 12);
 	command[0] = 0xEA;
 	command[1] = LUNBITS;
@@ -663,30 +663,31 @@
 sddr09_read_data(struct us_data *us,
 		 unsigned long address,
 		 unsigned int sectors,
-		 unsigned char *content,
+		 unsigned char *buffer,
 		 int use_sg) {
 
 	struct sddr09_card_info *info = (struct sddr09_card_info *) us->extra;
 	unsigned int lba, maxlba, pba;
 	unsigned int page, pages;
-	unsigned char *buffer = NULL;
-	unsigned char *ptr;
-	int result, len;
-
-	// If we're using scatter-gather, we have to create a new
-	// buffer to read all of the data in first, since a
-	// scatter-gather buffer could in theory start in the middle
-	// of a page, which would be bad. A developer who wants a
-	// challenge might want to write a limited-buffer
-	// version of this code.
-
-	len = sectors*info->pagesize;
-
-	buffer = (use_sg ? kmalloc(len, GFP_NOIO) : content);
-	if (buffer == NULL)
-		return USB_STOR_TRANSPORT_ERROR;
+	unsigned int len, index, offset;
+	int result;
 
-	ptr = buffer;
+	// Since we only read in one block at a time, we have to create
+	// a bounce buffer if the transfer uses scatter-gather.  We will
+	// move the data a piece at a time between the bounce buffer and
+	// the actual transfer buffer.  If we're not using scatter-gather,
+	// we can simply update the transfer buffer pointer to get the
+	// same effect.
+
+	if (use_sg) {
+		len = min(sectors, (unsigned int) info->blocksize) *
+				info->pagesize;
+		buffer = kmalloc(len, GFP_NOIO);
+		if (buffer == NULL) {
+			printk("sddr09_read_data: Out of memory\n");
+			return USB_STOR_TRANSPORT_ERROR;
+		}
+	}
 
 	// Figure out the initial LBA and page
 	lba = address >> info->blockshift;
@@ -697,13 +698,13 @@
 	// contiguous LBA's. Another exercise left to the student.
 
 	result = USB_STOR_TRANSPORT_GOOD;
+	index = offset = 0;
 
 	while (sectors > 0) {
 
 		/* Find number of pages we can read in this block */
-		pages = info->blocksize - page;
-		if (pages > sectors)
-			pages = sectors;
+		pages = min(sectors, info->blocksize - page);
+		len = pages << info->pageshift;
 
 		/* Not overflowing capacity? */
 		if (lba >= maxlba) {
@@ -726,7 +727,7 @@
 			   Instead of returning USB_STOR_TRANSPORT_ERROR
 			   it is better to return all zero data. */
 
-			memset(ptr, 0, pages << info->pageshift);
+			memset(buffer, 0, len);
 
 		} else {
 			US_DEBUGP("Read %d pages, from PBA %d"
@@ -737,37 +738,50 @@
 				info->pageshift;
 
 			result = sddr09_read20(us, address>>1,
-					       pages, info->pageshift, ptr, 0);
+					pages, info->pageshift, buffer, 0);
 			if (result != USB_STOR_TRANSPORT_GOOD)
 				break;
 		}
 
+		// Store the data (s-g) or update the pointer (!s-g)
+		if (use_sg)
+			usb_stor_access_xfer_buf(buffer, len, us->srb,
+					&index, &offset, TO_XFER_BUF);
+		else
+			buffer += len;
+
 		page = 0;
 		lba++;
 		sectors -= pages;
-		ptr += (pages << info->pageshift);
 	}
 
-	if (use_sg && result == USB_STOR_TRANSPORT_GOOD)
-		us_copy_to_sgbuf_all(buffer, len, content, use_sg);
-
 	if (use_sg)
 		kfree(buffer);
 
 	return result;
 }
 
-/* we never free blocks, so lastpba can only increase */
 static unsigned int
-sddr09_find_unused_pba(struct sddr09_card_info *info) {
+sddr09_find_unused_pba(struct sddr09_card_info *info, unsigned int lba) {
 	static unsigned int lastpba = 1;
-	int numblocks = info->capacity >> (info->blockshift + info->pageshift);
-	int i;
+	int zonestart, end, i;
 
-	for (i = lastpba+1; i < numblocks; i++) {
-		if (info->pba_to_lba[i] == UNDEF) {
+	zonestart = (lba/1000) << 10;
+	end = info->capacity >> (info->blockshift + info->pageshift);
+	end -= zonestart;
+	if (end > 1024)
+		end = 1024;
+
+	for (i = lastpba+1; i < end; i++) {
+		if (info->pba_to_lba[zonestart+i] == UNDEF) {
+			lastpba = i;
+			return zonestart+i;
+		}
+	}
+	for (i = 0; i <= lastpba; i++) {
+		if (info->pba_to_lba[zonestart+i] == UNDEF) {
 			lastpba = i;
-			return i;
+			return zonestart+i;
 		}
 	}
 	return 0;
@@ -776,29 +790,31 @@
 static int
 sddr09_write_lba(struct us_data *us, unsigned int lba,
 		 unsigned int page, unsigned int pages,
-		 unsigned char *ptr) {
+		 unsigned char *ptr, unsigned char *blockbuffer) {
 
 	struct sddr09_card_info *info = (struct sddr09_card_info *) us->extra;
 	unsigned long address;
 	unsigned int pba, lbap;
-	unsigned int pagelen, blocklen;
-	unsigned char *blockbuffer, *bptr, *cptr, *xptr;
+	unsigned int pagelen;
+	unsigned char *bptr, *cptr, *xptr;
 	unsigned char ecc[3];
-	int i, result;
+	int i, result, isnew;
 
-	lbap = ((lba & 0x3ff) << 1) | 0x1000;
+	lbap = ((lba % 1000) << 1) | 0x1000;
 	if (parity[MSB_of(lbap) ^ LSB_of(lbap)])
 		lbap ^= 1;
 	pba = info->lba_to_pba[lba];
+	isnew = 0;
 
 	if (pba == UNDEF) {
-		pba = sddr09_find_unused_pba(info);
+		pba = sddr09_find_unused_pba(info, lba);
 		if (!pba) {
 			printk("sddr09_write_lba: Out of unused blocks\n");
 			return USB_STOR_TRANSPORT_ERROR;
 		}
 		info->pba_to_lba[pba] = lba;
 		info->lba_to_pba[lba] = pba;
+		isnew = 1;
 	}
 
 	if (pba == 1) {
@@ -809,22 +825,16 @@
 	}
 
 	pagelen = (1 << info->pageshift) + (1 << CONTROL_SHIFT);
-	blocklen = (pagelen << info->blockshift);
-	blockbuffer = kmalloc(blocklen, GFP_NOIO);
-	if (!blockbuffer) {
-		printk("sddr09_write_lba: Out of memory\n");
-		return USB_STOR_TRANSPORT_ERROR;
-	}
 
 	/* read old contents */
 	address = (pba << (info->pageshift + info->blockshift));
 	result = sddr09_read22(us, address>>1, info->blocksize,
 			       info->pageshift, blockbuffer, 0);
 	if (result != USB_STOR_TRANSPORT_GOOD)
-		goto err;
+		return result;
 
-	/* check old contents */
-	for (i = 0; i < info->blockshift; i++) {
+	/* check old contents and fill lba */
+	for (i = 0; i < info->blocksize; i++) {
 		bptr = blockbuffer + i*pagelen;
 		cptr = bptr + info->pagesize;
 		nand_compute_ecc(bptr, ecc);
@@ -839,6 +849,8 @@
 				  i, pba);
 			nand_store_ecc(cptr+8, ecc);
 		}
+		cptr[6] = cptr[11] = MSB_of(lbap);
+		cptr[7] = cptr[12] = LSB_of(lbap);
 	}
 
 	/* copy in new stuff and compute ECC */
@@ -852,8 +864,6 @@
 		nand_store_ecc(cptr+13, ecc);
 		nand_compute_ecc(bptr+(info->pagesize / 2), ecc);
 		nand_store_ecc(cptr+8, ecc);
-		cptr[6] = cptr[11] = MSB_of(lbap);
-		cptr[7] = cptr[12] = LSB_of(lbap);
 	}
 
 	US_DEBUGP("Rewrite PBA %d (LBA %d)\n", pba, lba);
@@ -880,11 +890,6 @@
 		int result2 = sddr09_test_unit_ready(us);
 	}
 #endif
- err:
-	kfree(blockbuffer);
-
-	/* TODO: instead of doing kmalloc/kfree for each block,
-	   add a bufferpointer to the info structure */
 
 	return result;
 }
@@ -893,49 +898,84 @@
 sddr09_write_data(struct us_data *us,
 		  unsigned long address,
 		  unsigned int sectors,
-		  unsigned char *content,
+		  unsigned char *buffer,
 		  int use_sg) {
 
 	struct sddr09_card_info *info = (struct sddr09_card_info *) us->extra;
 	unsigned int lba, page, pages;
-	unsigned char *buffer = NULL;
-	unsigned char *ptr;
-	int result, len;
+	unsigned int pagelen, blocklen;
+	unsigned char *blockbuffer;
+	unsigned int len, index, offset;
+	int result;
 
-	len = sectors*info->pagesize;
+	// blockbuffer is used for reading in the old data, overwriting
+	// with the new data, and performing ECC calculations
 
-	buffer = us_copy_from_sgbuf_all(content, len, use_sg);
-	if (buffer == NULL)
+	/* TODO: instead of doing kmalloc/kfree for each write,
+	   add a bufferpointer to the info structure */
+
+	pagelen = (1 << info->pageshift) + (1 << CONTROL_SHIFT);
+	blocklen = (pagelen << info->blockshift);
+	blockbuffer = kmalloc(blocklen, GFP_NOIO);
+	if (!blockbuffer) {
+		printk("sddr09_write_data: Out of memory\n");
 		return USB_STOR_TRANSPORT_ERROR;
+	}
 
-	ptr = buffer;
+	// Since we don't write the user data directly to the device,
+	// we have to create a bounce buffer if the transfer uses
+	// scatter-gather.  We will move the data a piece at a time
+	// between the bounce buffer and the actual transfer buffer.
+	// If we're not using scatter-gather, we can simply update
+	// the transfer buffer pointer to get the same effect.
+
+	if (use_sg) {
+		len = min(sectors, (unsigned int) info->blocksize) *
+				info->pagesize;
+		buffer = kmalloc(len, GFP_NOIO);
+		if (buffer == NULL) {
+			printk("sddr09_write_data: Out of memory\n");
+			kfree(blockbuffer);
+			return USB_STOR_TRANSPORT_ERROR;
+		}
+	}
 
 	// Figure out the initial LBA and page
 	lba = address >> info->blockshift;
 	page = (address & info->blockmask);
 
 	result = USB_STOR_TRANSPORT_GOOD;
+	index = offset = 0;
 
 	while (sectors > 0) {
 
 		// Write as many sectors as possible in this block
 
-		pages = info->blocksize - page;
-		if (pages > sectors)
-			pages = sectors;
+		pages = min(sectors, info->blocksize - page);
+		len = (pages << info->pageshift);
+
+		// Get the data from the transfer buffer (s-g)
+		if (use_sg)
+			usb_stor_access_xfer_buf(buffer, len, us->srb,
+					&index, &offset, FROM_XFER_BUF);
 
-		result = sddr09_write_lba(us, lba, page, pages, ptr);
+		result = sddr09_write_lba(us, lba, page, pages,
+				buffer, blockbuffer);
 		if (result != USB_STOR_TRANSPORT_GOOD)
 			break;
 
+		// Update the transfer buffer pointer (!s-g)
+		if (!use_sg)
+			buffer += len;
+
 		page = 0;
 		lba++;
 		sectors -= pages;
-		ptr += (pages << info->pageshift);
 	}
 
 	if (use_sg)
 		kfree(buffer);
+	kfree(blockbuffer);
 
 	return result;
 }
@@ -947,10 +987,11 @@
 		unsigned char *content,
 		int use_sg) {
 
-	US_DEBUGP("Read control address %08lX blocks %04X\n",
+	US_DEBUGP("Read control address %lu, blocks %d\n",
 		address, blocks);
 
-	return sddr09_read21(us, address, blocks, CONTROL_SHIFT, content, use_sg);
+	return sddr09_read21(us, address, blocks,
+			     CONTROL_SHIFT, content, use_sg);
 }
 
 /*
@@ -997,7 +1038,7 @@
 		US_DEBUGP("sddr09_get_wp: read_status fails\n");
 		return result;
 	}
-	US_DEBUGP("sddr09_get_wp: status %02X", status);
+	US_DEBUGP("sddr09_get_wp: status 0x%02X", status);
 	if ((status & 0x80) == 0) {
 		info->flags |= SDDR09_WP;	/* write protected */
 		US_DEBUGP(" WP");
@@ -1092,69 +1133,36 @@
 static int
 sddr09_read_map(struct us_data *us) {
 
-	struct scatterlist *sg;
 	struct sddr09_card_info *info = (struct sddr09_card_info *) us->extra;
 	int numblocks, alloc_len, alloc_blocks;
 	int i, j, result;
-	unsigned char *ptr;
+	unsigned char *buffer, *buffer_end, *ptr;
 	unsigned int lba, lbact;
 
 	if (!info->capacity)
 		return -1;
 
-	// read 64 (1<<6) bytes for every block 
-	// ( 1 << ( blockshift + pageshift ) bytes)
-	//	 of capacity:
-	// (1<<6)*capacity/(1<<(b+p)) =
-	// ((1<<6)*capacity)>>(b+p) =
-	// capacity>>(b+p-6)
-
-	alloc_len = info->capacity >> 
-		(info->blockshift + info->pageshift - CONTROL_SHIFT);
-
-	// Allocate a number of scatterlist structures according to
-	// the number of 128k blocks in the alloc_len. Adding 128k-1
-	// and then dividing by 128k gives the correct number of blocks.
-	// 128k = 1<<17
-
-	alloc_blocks = (alloc_len + (1<<17) - 1) >> 17;
-	sg = kmalloc(alloc_blocks*sizeof(struct scatterlist),
-		     GFP_NOIO);
-	if (sg == NULL)
-		return 0;
-
-	for (i=0; i<alloc_blocks; i++) {
-		int alloc_req = (i < alloc_blocks-1 ? 1 << 17 : alloc_len);
-		char *vaddr = kmalloc(alloc_req, GFP_NOIO);
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,3)
-		sg[i].page = virt_to_page(vaddr);
-		sg[i].offset = offset_in_page(vaddr);
-#else
-		sg[i].address = vaddr;
-#endif
-		sg[i].length = alloc_req;
-		alloc_len -= alloc_req;
-	}
-
-	for (i=0; i<alloc_blocks; i++)
-		if (sg[i].page == NULL) {
-			for (i=0; i<alloc_blocks; i++)
-				if (sg[i].page != NULL)
-					kfree(sg_address(sg[i]));
-			kfree(sg);
-			return 0;
-		}
+	// size of a block is 1 << (blockshift + pageshift) bytes
+	// divide into the total capacity to get the number of blocks
 
 	numblocks = info->capacity >> (info->blockshift + info->pageshift);
 
-	result = sddr09_read_control(us, 0, numblocks,
-				     (unsigned char *)sg, alloc_blocks);
-	if (result != USB_STOR_TRANSPORT_GOOD) {
-		for (i=0; i<alloc_blocks; i++)
-			kfree(sg_address(sg[i]));
-		kfree(sg);
-		return -1;
+	// read 64 bytes for every block (actually 1 << CONTROL_SHIFT)
+	// but only use a 64 KB buffer
+	// buffer size used must be a multiple of (1 << CONTROL_SHIFT)
+#define SDDR09_READ_MAP_BUFSZ 65536
+
+	alloc_blocks = min(numblocks, SDDR09_READ_MAP_BUFSZ >> CONTROL_SHIFT);
+	alloc_len = (alloc_blocks << CONTROL_SHIFT);
+	buffer = kmalloc(alloc_len, GFP_NOIO);
+	if (buffer == NULL) {
+		printk("sddr09_read_map: out of memory\n");
+		result = -1;
+		goto done;
 	}
+	buffer_end = buffer + alloc_len;
+
+#undef SDDR09_READ_MAP_BUFSZ
 
 	kfree(info->lba_to_pba);
 	kfree(info->pba_to_lba);
@@ -1162,29 +1170,35 @@
 	info->pba_to_lba = kmalloc(numblocks*sizeof(int), GFP_NOIO);
 
 	if (info->lba_to_pba == NULL || info->pba_to_lba == NULL) {
-		kfree(info->lba_to_pba);
-		kfree(info->pba_to_lba);
-		info->lba_to_pba = NULL;
-		info->pba_to_lba = NULL;
-		for (i=0; i<alloc_blocks; i++)
-			kfree(sg_address(sg[i]));
-		kfree(sg);
-		return 0;
+		printk("sddr09_read_map: out of memory\n");
+		result = -1;
+		goto done;
 	}
 
 	for (i = 0; i < numblocks; i++)
 		info->lba_to_pba[i] = info->pba_to_lba[i] = UNDEF;
 
-	ptr = sg_address(sg[0]);
-
 	/*
 	 * Define lba-pba translation table
 	 */
-	// Each block is 64 bytes of control data, so block i is located in
-	// scatterlist block i*64/128k = i*(2^6)*(2^-17) = i*(2^-11)
 
-	for (i=0; i<numblocks; i++) {
-		ptr = sg_address(sg[i>>11]) + ((i&0x7ff)<<6);
+	ptr = buffer_end;
+	for (i = 0; i < numblocks; i++) {
+		ptr += (1 << CONTROL_SHIFT);
+		if (ptr >= buffer_end) {
+			unsigned long address;
+
+			address = i << (info->pageshift + info->blockshift);
+			result = sddr09_read_control(
+				us, address>>1,
+				min(alloc_blocks, numblocks - i),
+				buffer, 0);
+			if (result != USB_STOR_TRANSPORT_GOOD) {
+				result = -1;
+				goto done;
+			}
+			ptr = buffer;
+		}
 
 		if (i == 0 || i == 1) {
 			info->pba_to_lba[i] = UNUSABLE;
@@ -1196,7 +1210,7 @@
 			if (ptr[j] != 0)
 				goto nonz;
 		info->pba_to_lba[i] = UNUSABLE;
-		printk("sddr09: PBA %04X has no logical mapping\n", i);
+		printk("sddr09: PBA %d has no logical mapping\n", i);
 		continue;
 
 	nonz:
@@ -1209,7 +1223,7 @@
 	nonff:
 		/* normal PBAs start with six FFs */
 		if (j < 6) {
-			printk("sddr09: PBA %04X has no logical mapping: "
+			printk("sddr09: PBA %d has no logical mapping: "
 			       "reserved area = %02X%02X%02X%02X "
 			       "data status %02X block status %02X\n",
 			       i, ptr[0], ptr[1], ptr[2], ptr[3],
@@ -1219,7 +1233,7 @@
 		}
 
 		if ((ptr[6] >> 4) != 0x01) {
-			printk("sddr09: PBA %04X has invalid address field "
+			printk("sddr09: PBA %d has invalid address field "
 			       "%02X%02X/%02X%02X\n",
 			       i, ptr[6], ptr[7], ptr[11], ptr[12]);
 			info->pba_to_lba[i] = UNUSABLE;
@@ -1228,7 +1242,7 @@
 
 		/* check even parity */
 		if (parity[ptr[6] ^ ptr[7]]) {
-			printk("sddr09: Bad parity in LBA for block %04X"
+			printk("sddr09: Bad parity in LBA for block %d"
 			       " (%02X %02X)\n", i, ptr[6], ptr[7]);
 			info->pba_to_lba[i] = UNUSABLE;
 			continue;
@@ -1247,27 +1261,32 @@
 		 */
 
 		if (lba >= 1000) {
-			unsigned long address;
-
-			printk("sddr09: Bad LBA %04X for block %04X\n",
+			printk("sddr09: Bad low LBA %d for block %d\n",
 			       lba, i);
-			info->pba_to_lba[i] = UNDEF /* UNUSABLE */;
-			if (erase_bad_lba_entries) {
-				/* some cameras cannot erase a card if it has
-				   bad entries, so we supply this function */
-				address = (i << (info->pageshift + info->blockshift));
-				sddr09_erase(us, address>>1);
-			}
-			continue;
+			goto possibly_erase;
 		}
 
 		lba += 1000*(i/0x400);
 
-		if (lba<0x10 || (lba >= 0x3E0 && lba < 0x3EF))
-			US_DEBUGP("LBA %04X <-> PBA %04X\n", lba, i);
+		if (info->lba_to_pba[lba] != UNDEF) {
+			printk("sddr09: LBA %d seen for PBA %d and %d\n",
+			       lba, info->lba_to_pba[lba], i);
+			goto possibly_erase;
+		}
 
 		info->pba_to_lba[i] = lba;
 		info->lba_to_pba[lba] = i;
+		continue;
+
+	possibly_erase:
+		if (erase_bad_lba_entries) {
+			unsigned long address;
+
+			address = (i << (info->pageshift + info->blockshift));
+			sddr09_erase(us, address>>1);
+			info->pba_to_lba[i] = UNDEF;
+		} else
+			info->pba_to_lba[i] = UNUSABLE;
 	}
 
 	/*
@@ -1292,11 +1311,17 @@
 	}
 	info->lbact = lbact;
 	US_DEBUGP("Found %d LBA's\n", lbact);
+	result = 0;
 
-	for (i=0; i<alloc_blocks; i++)
-		kfree(sg_address(sg[i]));
-	kfree(sg);
-	return 0;
+ done:
+	if (result != 0) {
+		kfree(info->lba_to_pba);
+		kfree(info->pba_to_lba);
+		info->lba_to_pba = NULL;
+		info->pba_to_lba = NULL;
+	}
+	kfree(buffer);
+	return result;
 }
 
 static void
@@ -1438,6 +1463,7 @@
 		cardinfo = sddr09_get_cardinfo(us, info->flags);
 		if (!cardinfo) {
 			/* probably no media */
+		init_error:
 			sensekey = 0x02;	/* not ready */
 			sensecode = 0x3a;	/* medium not present */
 			return USB_STOR_TRANSPORT_FAILED;
@@ -1451,7 +1477,10 @@
 		info->blockmask = info->blocksize - 1;
 
 		// map initialization, must follow get_cardinfo()
-		sddr09_read_map(us);
+		if (sddr09_read_map(us)) {
+			/* probably out of memory */
+			goto init_error;
+		}
 
 		// Report capacity
 
@@ -1472,12 +1501,13 @@
 		return USB_STOR_TRANSPORT_GOOD;
 	}
 
-	if (srb->cmnd[0] == MODE_SENSE) {
+	if (srb->cmnd[0] == MODE_SENSE || srb->cmnd[0] == MODE_SENSE_10) {
 		int modepage = (srb->cmnd[2] & 0x3F);
 		int len;
 
 		/* They ask for the Read/Write error recovery page,
 		   or for all pages. Give as much as they have room for. */
+		/* %% We should check DBD %% */
 		if (modepage == 0x01 || modepage == 0x3F) {
 
 			US_DEBUGP("SDDR09: Dummy up request for "
@@ -1496,20 +1526,14 @@
 			return USB_STOR_TRANSPORT_GOOD;
 		}
 
-		return USB_STOR_TRANSPORT_ERROR;
+		sensekey = 0x05;	/* illegal request */
+		sensecode = 0x24;	/* invalid field in CDB */
+		return USB_STOR_TRANSPORT_FAILED;
 	}
 
-	if (srb->cmnd[0] == ALLOW_MEDIUM_REMOVAL) {
-
-		US_DEBUGP(
-			"SDDR09: %s medium removal. Not that I can do"
-			" anything about it...\n",
-			(srb->cmnd[4]&0x03) ? "Prevent" : "Allow");
-
+	if (srb->cmnd[0] == ALLOW_MEDIUM_REMOVAL)
 		return USB_STOR_TRANSPORT_GOOD;
 
-	}
-
 	havefakesense = 0;
 
 	if (srb->cmnd[0] == READ_10) {
@@ -1542,8 +1566,10 @@
 
 	if (srb->cmnd[0] != TEST_UNIT_READY &&
 	    srb->cmnd[0] != REQUEST_SENSE) {
+		sensekey = 0x05;	/* illegal request */
+		sensecode = 0x20;	/* invalid command */
 		havefakesense = 1;
-		return USB_STOR_TRANSPORT_ERROR;
+		return USB_STOR_TRANSPORT_FAILED;
 	}
 
 	for (; srb->cmd_len<12; srb->cmd_len++)
@@ -1555,8 +1581,7 @@
 	for (i=0; i<12; i++)
 		sprintf(string+strlen(string), "%02X ", srb->cmnd[i]);
 
-	US_DEBUGP("SDDR09: Send control for command %s\n",
-		  string);
+	US_DEBUGP("SDDR09: Send control for command %s\n", string);
 
 	result = sddr09_send_scsi_command(us, srb->cmnd, 12);
 	if (result != USB_STOR_TRANSPORT_GOOD) {
diff -Nru a/drivers/usb/storage/sddr55.c b/drivers/usb/storage/sddr55.c
--- a/drivers/usb/storage/sddr55.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/storage/sddr55.c	Fri Dec 12 15:06:57 2003
@@ -25,7 +25,6 @@
  */
 
 #include "transport.h"
-#include "raw_bulk.h"
 #include "protocol.h"
 #include "usb.h"
 #include "debug.h"
@@ -155,7 +154,7 @@
 		unsigned int lba,
 		unsigned int page,
 		unsigned short sectors,
-		unsigned char *content,
+		unsigned char *buffer,
 		int use_sg) {
 
 	int result = USB_STOR_TRANSPORT_GOOD;
@@ -167,17 +166,24 @@
 	unsigned long address;
 
 	unsigned short pages;
-	unsigned char *buffer = NULL;
-	unsigned char *ptr;
-	int len;
+	unsigned int len, index, offset;
 
-	len = sectors * PAGESIZE;
-
-	buffer = (use_sg ? kmalloc(len, GFP_NOIO) : content);
-	if (buffer == NULL)
-		return USB_STOR_TRANSPORT_ERROR; /* out of memory */
-
-	ptr = buffer;
+	// Since we only read in one block at a time, we have to create
+	// a bounce buffer if the transfer uses scatter-gather.  We will
+	// move the data a piece at a time between the bounce buffer and
+	// the actual transfer buffer.  If we're not using scatter-gather,
+	// we can simply update the transfer buffer pointer to get the
+	// same effect.
+
+	if (use_sg) {
+		len = min((unsigned int) sectors,
+				(unsigned int) info->blocksize >>
+					info->smallpageshift) * PAGESIZE;
+		buffer = kmalloc(len, GFP_NOIO);
+		if (buffer == NULL)
+			return USB_STOR_TRANSPORT_ERROR; /* out of memory */
+	}
+	index = offset = 0;
 
 	while (sectors>0) {
 
@@ -189,9 +195,9 @@
 
 		// Read as many sectors as possible in this block
 
-		pages = info->blocksize - page;
-		if (pages > (sectors << info->smallpageshift))
-			pages = (sectors << info->smallpageshift);
+		pages = min((unsigned int) sectors << info->smallpageshift,
+				info->blocksize - page);
+		len = pages << info->pageshift;
 
 		US_DEBUGP("Read %02X pages, from PBA %04X"
 			" (LBA %04X) page %02X\n",
@@ -199,7 +205,7 @@
 
 		if (pba == NOT_ALLOCATED) {
 			/* no pba for this lba, fill with zeroes */
-			memset (ptr, 0, pages << info->pageshift);
+			memset (buffer, 0, len);
 		} else {
 
 			address = (pba << info->blockshift) + page;
@@ -228,8 +234,7 @@
 
 			/* read data */
 			result = sddr55_bulk_transport(us,
-				SCSI_DATA_READ, ptr,
-				pages<<info->pageshift);
+				SCSI_DATA_READ, buffer, len);
 
 			if (result != USB_STOR_XFER_GOOD) {
 				result = USB_STOR_TRANSPORT_ERROR;
@@ -253,13 +258,18 @@
 			}
 		}
 
+		// Store the data (s-g) or update the pointer (!s-g)
+		if (use_sg)
+			usb_stor_access_xfer_buf(buffer, len, us->srb,
+					&index, &offset, TO_XFER_BUF);
+		else
+			buffer += len;
+
 		page = 0;
 		lba++;
 		sectors -= pages >> info->smallpageshift;
-		ptr += (pages << info->pageshift);
 	}
 
-	us_copy_to_sgbuf_all(buffer, len, content, use_sg);
 	result = USB_STOR_TRANSPORT_GOOD;
 
 leave:
@@ -273,7 +283,7 @@
 		unsigned int lba,
 		unsigned int page,
 		unsigned short sectors,
-		unsigned char *content,
+		unsigned char *buffer,
 		int use_sg) {
 
 	int result = USB_STOR_TRANSPORT_GOOD;
@@ -286,9 +296,8 @@
 	unsigned long address;
 
 	unsigned short pages;
-	unsigned char *buffer = NULL;
-	unsigned char *ptr;
-	int i, len;
+	int i;
+	unsigned int len, index, offset;
 
 	/* check if we are allowed to write */
 	if (info->read_only || info->force_read_only) {
@@ -296,13 +305,22 @@
 		return USB_STOR_TRANSPORT_FAILED;
 	}
 
-	len = sectors * PAGESIZE;
-
-	buffer = us_copy_from_sgbuf_all(content, len, use_sg);
-	if (buffer == NULL)
-		return USB_STOR_TRANSPORT_ERROR;
-
-	ptr = buffer;
+	// Since we only write one block at a time, we have to create
+	// a bounce buffer if the transfer uses scatter-gather.  We will
+	// move the data a piece at a time between the bounce buffer and
+	// the actual transfer buffer.  If we're not using scatter-gather,
+	// we can simply update the transfer buffer pointer to get the
+	// same effect.
+
+	if (use_sg) {
+		len = min((unsigned int) sectors,
+				(unsigned int) info->blocksize >>
+					info->smallpageshift) * PAGESIZE;
+		buffer = kmalloc(len, GFP_NOIO);
+		if (buffer == NULL)
+			return USB_STOR_TRANSPORT_ERROR;
+	}
+	index = offset = 0;
 
 	while (sectors > 0) {
 
@@ -314,9 +332,14 @@
 
 		// Write as many sectors as possible in this block
 
-		pages = info->blocksize - page;
-		if (pages > (sectors << info->smallpageshift))
-			pages = (sectors << info->smallpageshift);
+		pages = min((unsigned int) sectors << info->smallpageshift,
+				info->blocksize - page);
+		len = pages << info->pageshift;
+
+		// Get the data from the transfer buffer (s-g)
+		if (use_sg)
+			usb_stor_access_xfer_buf(buffer, len, us->srb,
+					&index, &offset, FROM_XFER_BUF);
 
 		US_DEBUGP("Write %02X pages, to PBA %04X"
 			" (LBA %04X) page %02X\n",
@@ -400,8 +423,7 @@
 
 		/* send the data */
 		result = sddr55_bulk_transport(us,
-			SCSI_DATA_WRITE, ptr,
-			pages<<info->pageshift);
+			SCSI_DATA_WRITE, buffer, len);
 
 		if (result != USB_STOR_XFER_GOOD) {
 			US_DEBUGP("Result for send_data in write_data %d\n",
@@ -458,10 +480,12 @@
 		/* update the pba<->lba maps for new_pba */
 		info->pba_to_lba[new_pba] = lba % 1000;
 
+		// Update the transfer buffer pointer (!s-g)
+		if (!use_sg)
+			buffer += len;
 		page = 0;
 		lba++;
 		sectors -= pages >> info->smallpageshift;
-		ptr += (pages << info->pageshift);
 	}
 	result = USB_STOR_TRANSPORT_GOOD;
 
diff -Nru a/drivers/usb/storage/shuttle_usbat.c b/drivers/usb/storage/shuttle_usbat.c
--- a/drivers/usb/storage/shuttle_usbat.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/storage/shuttle_usbat.c	Fri Dec 12 15:06:57 2003
@@ -40,7 +40,6 @@
  */
 
 #include "transport.h"
-#include "raw_bulk.h"
 #include "protocol.h"
 #include "usb.h"
 #include "debug.h"
@@ -529,9 +528,8 @@
 	unsigned char *buffer;
 	unsigned int len;
 	unsigned int sector;
-	struct scatterlist *sg = NULL;
-	int sg_segment = 0;
-	int sg_offset = 0;
+	unsigned int sg_segment = 0;
+	unsigned int sg_offset = 0;
 
 	US_DEBUGP("handle_read10: transfersize %d\n",
 		srb->transfersize);
@@ -570,21 +568,29 @@
 			srb->transfersize);
 	}
 
+	// Since we only read in one block at a time, we have to create
+	// a bounce buffer if the transfer uses scatter-gather.  We will
+	// move the data a piece at a time between the bounce buffer and
+	// the actual transfer buffer.  If we're not using scatter-gather,
+	// we can simply update the transfer buffer pointer to get the
+	// same effect.
+
 	len = (65535/srb->transfersize) * srb->transfersize;
 	US_DEBUGP("Max read is %d bytes\n", len);
-	buffer = kmalloc(len, GFP_NOIO);
-	if (buffer == NULL) // bloody hell!
-		return USB_STOR_TRANSPORT_FAILED;
+	len = min(len, srb->request_bufflen);
+	if (srb->use_sg) {
+		buffer = kmalloc(len, GFP_NOIO);
+		if (buffer == NULL) // bloody hell!
+			return USB_STOR_TRANSPORT_FAILED;
+	} else
+		buffer = srb->request_buffer;
 	sector = short_pack(data[7+3], data[7+2]);
 	sector <<= 16;
 	sector |= short_pack(data[7+5], data[7+4]);
 	transferred = 0;
 
-	if (srb->use_sg) {
-		sg = (struct scatterlist *)srb->request_buffer;
-		sg_segment = 0; // for keeping track of where we are in
-		sg_offset = 0;  // the scatter/gather list
-	}
+	sg_segment = 0; // for keeping track of where we are in
+	sg_offset = 0;  // the scatter/gather list
 
 	while (transferred != srb->request_bufflen) {
 
@@ -615,13 +621,12 @@
 		if (result != USB_STOR_TRANSPORT_GOOD)
 			break;
 
-		// Transfer the received data into the srb buffer
-
+		// Store the data (s-g) or update the pointer (!s-g)
 		if (srb->use_sg)
-			us_copy_to_sgbuf(buffer, len, sg,
-					 &sg_segment, &sg_offset, srb->use_sg);
+			usb_stor_access_xfer_buf(buffer, len, srb,
+					 &sg_segment, &sg_offset, TO_XFER_BUF);
 		else
-			memcpy(srb->request_buffer+transferred, buffer, len);
+			buffer += len;
 
 		// Update the amount transferred and the sector number
 
@@ -630,7 +635,8 @@
 
 	} // while transferred != srb->request_bufflen
 
-	kfree(buffer);
+	if (srb->use_sg)
+		kfree(buffer);
 	return result;
 }
 
diff -Nru a/drivers/usb/storage/transport.c b/drivers/usb/storage/transport.c
--- a/drivers/usb/storage/transport.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/storage/transport.c	Fri Dec 12 15:06:57 2003
@@ -760,6 +760,7 @@
 int usb_stor_CBI_transport(Scsi_Cmnd *srb, struct us_data *us)
 {
 	unsigned int transfer_length = srb->request_bufflen;
+	unsigned int pipe = 0;
 	int result;
 
 	/* COMMAND STAGE */
@@ -785,7 +786,7 @@
 	/* DATA STAGE */
 	/* transfer the data payload for this command, if one exists*/
 	if (transfer_length) {
-		unsigned int pipe = srb->sc_data_direction == SCSI_DATA_READ ? 
+		pipe = srb->sc_data_direction == SCSI_DATA_READ ? 
 				us->recv_bulk_pipe : us->send_bulk_pipe;
 		result = usb_stor_bulk_transfer_sg(us, pipe,
 					srb->request_buffer, transfer_length,
@@ -813,12 +814,9 @@
 		if (srb->cmnd[0] == REQUEST_SENSE ||
 		    srb->cmnd[0] == INQUIRY)
 			return USB_STOR_TRANSPORT_GOOD;
-		else {
-			if (us->iobuf[0])
-				return USB_STOR_TRANSPORT_FAILED;
-			else
-				return USB_STOR_TRANSPORT_GOOD;
-		}
+		if (us->iobuf[0])
+			goto Failed;
+		return USB_STOR_TRANSPORT_GOOD;
 	}
 
 	/* If not UFI, we interpret the data as a result code 
@@ -835,13 +833,17 @@
 		case 0x00: 
 			return USB_STOR_TRANSPORT_GOOD;
 		case 0x01: 
-			return USB_STOR_TRANSPORT_FAILED;
-		default: 
-			return USB_STOR_TRANSPORT_ERROR;
+			goto Failed;
 	}
-
-	/* we should never get here, but if we do, we're in trouble */
 	return USB_STOR_TRANSPORT_ERROR;
+
+	/* the CBI spec requires that the bulk pipe must be cleared
+	 * following any data-in/out command failure (section 2.4.3.1.3)
+	 */
+  Failed:
+	if (pipe)
+		usb_stor_clear_halt(us, pipe);
+	return USB_STOR_TRANSPORT_FAILED;
 }
 
 /*
@@ -924,6 +926,7 @@
 	struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf;
 	struct bulk_cs_wrap *bcs = (struct bulk_cs_wrap *) us->iobuf;
 	unsigned int transfer_length = srb->request_bufflen;
+	unsigned int residue;
 	int result;
 	int fake_sense = 0;
 
@@ -999,9 +1002,10 @@
 		return USB_STOR_TRANSPORT_ERROR;
 
 	/* check bulk status */
-	US_DEBUGP("Bulk Status S 0x%x T 0x%x R %d Stat 0x%x\n",
+	residue = le32_to_cpu(bcs->Residue);
+	US_DEBUGP("Bulk Status S 0x%x T 0x%x R %u Stat 0x%x\n",
 			le32_to_cpu(bcs->Signature), bcs->Tag, 
-			bcs->Residue, bcs->Status);
+			residue, bcs->Status);
 	if ((bcs->Signature != cpu_to_le32(US_BULK_CS_SIGN) &&
 		    bcs->Signature != cpu_to_le32(US_BULK_CS_OLYMPUS_SIGN)) ||
 			bcs->Tag != srb->serial_number || 
@@ -1009,6 +1013,11 @@
 		US_DEBUGP("Bulk logical error\n");
 		return USB_STOR_TRANSPORT_ERROR;
 	}
+
+	/* try to compute the actual residue, based on how much data
+	 * was really transferred and what the device tells us */
+	residue = min(residue, transfer_length);
+	srb->resid = max(srb->resid, (int) residue);
 
 	/* based on the status code, we report good or bad */
 	switch (bcs->Status) {
diff -Nru a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h
--- a/drivers/usb/storage/unusual_devs.h	Fri Dec 12 15:06:58 2003
+++ b/drivers/usb/storage/unusual_devs.h	Fri Dec 12 15:06:58 2003
@@ -45,6 +45,13 @@
  *
  */
 
+/* Patch submitted by Martin Berentsen <berentsen at sent5 dot uni-duisburg dot de> */
+#define US_FL_START_STOP  0x00000004   /* ignore START_STOP commands     */
+UNUSUAL_DEV(  0x0686, 0x4014, 0x0001, 0x0001, 
+		"Minolta",
+		"Dimage S414",
+		US_SC_SCSI, US_PR_BULK, NULL, US_FL_START_STOP), 
+
 UNUSUAL_DEV(  0x03ee, 0x0000, 0x0000, 0x0245, 
 		"Mitsumi",
 		"CD-R/RW Drive",
@@ -53,7 +60,7 @@
 UNUSUAL_DEV(  0x03ee, 0x6901, 0x0000, 0x0100,
 		"Mitsumi",
 		"USB FDD",
-		US_SC_UFI, US_PR_CBI, NULL,
+		US_SC_DEVICE, US_PR_DEVICE, NULL,
 		US_FL_SINGLE_LUN ),
 
 UNUSUAL_DEV(  0x03f0, 0x0107, 0x0200, 0x0200, 
@@ -102,6 +109,12 @@
 		"Finecam S4",
 		US_SC_8070, US_PR_CB, NULL, US_FL_FIX_INQUIRY),
 
+/* Patch submitted by Stephane Galles <stephane.galles@free.fr> */
+UNUSUAL_DEV(  0x0482, 0x0103, 0x0100, 0x0100,
+		"Kyocera",
+		"Finecam S5",
+		US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY),
+
 /* Reported by Paul Stewart <stewart@wetlogic.net>
  * This entry is needed because the device reports Sub=ff */
 UNUSUAL_DEV(  0x04a4, 0x0004, 0x0001, 0x0001,
@@ -257,7 +270,7 @@
 UNUSUAL_DEV(  0x054c, 0x002d, 0x0100, 0x0100, 
 		"Sony",
 		"Memorystick MSAC-US1",
-		US_SC_UFI, US_PR_CB, NULL,
+		US_SC_DEVICE, US_PR_DEVICE, NULL,
 		US_FL_SINGLE_LUN ),
 
 /* Submitted by Klaus Mueller <k.mueller@intershop.de> */
@@ -298,6 +311,12 @@
 		US_SC_DEVICE,  US_PR_DEVICE, NULL,
 		US_FL_SINGLE_LUN),
 
+/* Fabrizio Fellini <fello@libero.it> */
+UNUSUAL_DEV(  0x0595, 0x4343, 0x0000, 0x2210,
+		"Fujifilm",
+		"Digital Camera EX-20 DSC",
+		US_SC_8070, US_PR_CBI, NULL, 0 ),
+
 UNUSUAL_DEV(  0x059f, 0xa601, 0x0200, 0x0200, 
 		"LaCie",
 		"USB Hard Disk",
@@ -354,6 +373,13 @@
 		US_SC_DEVICE, US_PR_DEVICE, NULL,
 		US_FL_FIX_INQUIRY ),
 
+/* Submitted Alexander Oltu <alexander@all-2.com> */
+UNUSUAL_DEV(  0x05e3, 0x0701, 0x0000, 0xffff, 
+		"", 
+		"USB TO IDE",
+		US_SC_SCSI, US_PR_BULK, NULL,
+		US_FL_MODE_XLATE ), 
+
 /* Reported by Peter Marks <peter.marks@turner.com>
  * Like the SIIG unit above, this unit needs an INQUIRY to ask for exactly
  * 36 bytes of data.  No more, no less. That is the only reason this entry
@@ -400,6 +426,28 @@
                 "DIMAGE E223",
                 US_SC_SCSI, US_PR_DEVICE, NULL, 0 ),
 
+/* Following three Minolta cameras reported by Martin Pool
+ * <mbp@sourcefrog.net>.  Originally discovered by Kedar Petankar,
+ * Matthew Geier, Mikael Lofj"ard, Marcel de Boer.
+ */
+UNUSUAL_DEV( 0x0686, 0x4006, 0x0001, 0x0001,
+             "Minolta",
+             "DiMAGE 7",
+             US_SC_SCSI, US_PR_DEVICE, NULL,
+             0 ),
+
+UNUSUAL_DEV( 0x0686, 0x400b, 0x0001, 0x0001,
+             "Minolta",
+             "DiMAGE 7i",
+             US_SC_SCSI, US_PR_DEVICE, NULL,
+             0 ),
+
+UNUSUAL_DEV( 0x0686, 0x400f, 0x0001, 0x0001,
+             "Minolta",
+             "DiMAGE 7Hi",
+             US_SC_SCSI, US_PR_DEVICE, NULL,
+             0 ),
+
 UNUSUAL_DEV(  0x0693, 0x0002, 0x0100, 0x0100, 
 		"Hagiwara",
 		"FlashGate SmartMedia",
@@ -441,6 +489,11 @@
 		"Freecom",
 		"USB-IDE",
 		US_SC_QIC, US_PR_FREECOM, freecom_init, 0),
+
+UNUSUAL_DEV(  0x07ab, 0xfc84, 0x0000, 0x9999,
+		"Freecom",
+		"FX-5/FX-50",
+		US_SC_QIC, US_PR_FREECOM, freecom_init, 0),
 #endif
 
 UNUSUAL_DEV(  0x07af, 0x0004, 0x0100, 0x0133, 
@@ -528,6 +581,14 @@
 		US_SC_SCSI, US_PR_DATAFAB, NULL,
 		US_FL_MODE_XLATE ),
 #endif
+#ifdef CONFIG_USB_STORAGE_SDDR55
+/* SM part - aeb <Andries.Brouwer@cwi.nl> */
+UNUSUAL_DEV(  0x07c4, 0xa109, 0x0000, 0xffff,
+		"Datafab Systems, Inc.",
+		"USB to CF + SM Combo (LC1)",
+		US_SC_SCSI, US_PR_SDDR55, NULL,
+		US_FL_SINGLE_LUN ),
+#endif
 
 /* Datafab KECF-USB / Sagatek DCS-CF / Simpletech Flashlink UCF-100
  * Only revision 1.13 tested (same for all of the above devices,
@@ -570,6 +631,14 @@
 		US_SC_DEVICE, US_PR_DEVICE, NULL,
 		US_FL_MODE_XLATE ),
 
+/*Medion 6047 Digital Camera
+Davide Andrian <_nessuno_@katamail.com>
+*/
+UNUSUAL_DEV( 0x08ca, 0x2011, 0x0001, 0x0001,
+		"3MegaCam",
+		"3MegaCam",
+		US_SC_DEVICE, US_PR_BULK, NULL,
+		US_FL_MODE_XLATE ),
 /* aeb */
 UNUSUAL_DEV( 0x090c, 0x1132, 0x0000, 0xffff,
 		"Feiya",
@@ -605,8 +674,8 @@
 /* Submitted by Per Winkvist <per.winkvist@uk.com> */
 UNUSUAL_DEV( 0x0a17, 0x006, 0x1000, 0x9009,
                 "Pentax",
-                "Optio S",
-                US_SC_8070, US_PR_CBI, NULL,
+                "Optio S/S4",
+                US_SC_DEVICE, US_PR_DEVICE, NULL,
                 US_FL_FIX_INQUIRY ),
 		
 #ifdef CONFIG_USB_STORAGE_ISD200
@@ -617,13 +686,6 @@
 		0 ),
 #endif
 
-/* Submitted by Antoine Mairesse <antoine.mairesse@free.fr> */
-UNUSUAL_DEV( 0x0ed1, 0x6660, 0x0100, 0x0300,
-		"USB",
-		"Solid state disk",
-		US_SC_DEVICE, US_PR_DEVICE, NULL,
-		US_FL_FIX_INQUIRY ),
-
 /* Submitted by Joris Struyve <joris@struyve.be> */
 UNUSUAL_DEV( 0x0d96, 0x410a, 0x0001, 0xffff,
 		"Medion",
@@ -640,6 +702,13 @@
 		"Jenoptik",
 		"JD 5200 z3",
 		US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY),
+
+/* Submitted by Antoine Mairesse <antoine.mairesse@free.fr> */
+UNUSUAL_DEV( 0x0ed1, 0x6660, 0x0100, 0x0300,
+		"USB",
+		"Solid state disk",
+		US_SC_DEVICE, US_PR_DEVICE, NULL,
+		US_FL_FIX_INQUIRY ),
 		
 /* Reported by Kevin Cernekee <kpc-usbdev@gelato.uiuc.edu>
  * Tested on hardware version 1.10.
diff -Nru a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c
--- a/drivers/usb/storage/usb.c	Fri Dec 12 15:06:57 2003
+++ b/drivers/usb/storage/usb.c	Fri Dec 12 15:06:57 2003
@@ -234,16 +234,8 @@
  */
 
 void fill_inquiry_response(struct us_data *us, unsigned char *data,
-		unsigned int data_len) {
-
-	int i;
-	struct scatterlist *sg;
-	int len =
-		us->srb->request_bufflen > data_len ? data_len :
-		us->srb->request_bufflen;
-	int transferred;
-	int amt;
-
+		unsigned int data_len)
+{
 	if (data_len<36) // You lose.
 		return;
 
@@ -270,22 +262,7 @@
 		data[35] = 0x30 + ((us->pusb_dev->descriptor.bcdDevice) & 0x0F);
 	}
 
-	if (us->srb->use_sg) {
-		sg = (struct scatterlist *)us->srb->request_buffer;
-		for (i=0; i<us->srb->use_sg; i++)
-			memset(sg_address(sg[i]), 0, sg[i].length);
-		for (i=0, transferred=0; 
-				i<us->srb->use_sg && transferred < len;
-				i++) {
-			amt = sg[i].length > len-transferred ? 
-					len-transferred : sg[i].length;
-			memcpy(sg_address(sg[i]), data+transferred, amt);
-			transferred -= amt;
-		}
-	} else {
-		memset(us->srb->request_buffer, 0, us->srb->request_bufflen);
-		memcpy(us->srb->request_buffer, data, len);
-	}
+	usb_stor_set_xfer_buf(data, data_len, us->srb);
 }
 
 static int usb_stor_control_thread(void * __us)
diff -Nru a/include/linux/i2c-id.h b/include/linux/i2c-id.h
--- a/include/linux/i2c-id.h	Fri Dec 12 15:06:57 2003
+++ b/include/linux/i2c-id.h	Fri Dec 12 15:06:57 2003
@@ -258,6 +258,7 @@
 #define I2C_HW_SMBUS_AMD8111	0x0a
 #define I2C_HW_SMBUS_SCX200	0x0b
 #define I2C_HW_SMBUS_NFORCE2	0x0c
+#define I2C_HW_SMBUS_W9968CF	0x0d
 
 /* --- ISA pseudo-adapter						*/
 #define I2C_HW_ISA 0x00
diff -Nru a/include/linux/usb.h b/include/linux/usb.h
--- a/include/linux/usb.h	Fri Dec 12 15:06:57 2003
+++ b/include/linux/usb.h	Fri Dec 12 15:06:57 2003
@@ -414,9 +414,6 @@
  *	Export this with MODULE_DEVICE_TABLE(usb,...).  This must be set
  *	or your driver's probe function will never get called.
  * @driver: the driver model core driver structure.
- * @serialize: a semaphore used to serialize access to this driver.  Used
- * 	in the probe and disconnect functions.  Only the USB core should use
- * 	this lock.
  *
  * USB drivers must provide a name, probe() and disconnect() methods,
  * and an id_table.  Other driver fields are optional.
@@ -451,8 +448,6 @@
 	const struct usb_device_id *id_table;
 
 	struct device_driver driver;
-
-	struct semaphore serialize;
 };
 #define	to_usb_driver(d) container_of(d, struct usb_driver, driver)
 
diff -Nru a/include/linux/videodev.h b/include/linux/videodev.h
--- a/include/linux/videodev.h	Fri Dec 12 15:06:58 2003
+++ b/include/linux/videodev.h	Fri Dec 12 15:06:58 2003
@@ -429,6 +429,7 @@
 #define VID_HARDWARE_CPIA2	33
 #define VID_HARDWARE_VICAM      34
 #define VID_HARDWARE_SF16FMR2	35
+#define VID_HARDWARE_W9968CF	36	/* W996[87]CF JPEG USB Dual Mode Cam */
 #endif /* __LINUX_VIDEODEV_H */
 
 /*
