[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#585661: linux-2.6: support for gobi 2000 WWAN devices



Package: linux-2.6
Version: 2.6.32-15
Severity: wishlist
Tags: patch

Hi,

Would it be possible to have the patches for Qualcomm Gobi 2000 USB WWAN
support merged in both 2.6.32 and 2.6.34?  The patch breaks out code from the
option driver into a generic wwan set of code and then makes both the option
and qcserial drivers use it. I've built and tested both versions on my Thinkpad
X201 (based on 2.6.32-15 and 2.6.34-1~experimental.2) and the necessary code
for loading the firmware (gobi-loader) is in NEW.

The patches originally came from:

http://www.codon.org.uk/~mjg59/gobi_loader/kernel_patches/

and I've rebased them against the current trees to remove any offsets when
stuck at the end of the relevant series files.  Also, in the 2.6.32 kernel,
some of the USB IDs added in the patch seem to have been merged in a stable
bugfix update, but the code changes and other IDs haven't, so I had to clean
that up.

The code has been merged upstream in the following commits, so will not be a
long term maintanence headache:

commit e07896e62abbf7a741a5cd5b25ba7637bdf91ad0
Author: Anssi Hannula <anssi.hannula@gmail.com>
Date:   Thu Apr 1 12:31:10 2010 -0400

    USB: qcserial: Add support for Qualcomm Gobi 2000 devices
    
    Add ids for Qualcomm Gobi 2000 QDL and Modem modes. Gobi 2000 has a
    single altsetting in QDL mode, so adapt code to handle that.
    
    Firmware upload protocol is also slightly different, with an
    additional firmware file. However, qcserial doesn't handle firmware
    uploading.
    
    Tested on Lenovo Thinkpad T510.
    
    Signed-off-by: Anssi Hannula <anssi.hannula@gmail.com>
    Signed-off-by: Matthew Garrett <mjg@redhat.com>
    Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

commit 3d7e59ad88fdb6bc50ae9b7e822d4bb5f68b68f9
Author: Matthew Garrett <mjg@redhat.com>
Date:   Thu Apr 1 12:31:09 2010 -0400

    USB: qcserial: Use generic USB wwan code
    
    Make qcserial use the generic USB wwan code. This should result in a
    performance improvement.
    
    Signed-off-by: Matthew Garrett <mjg@redhat.com>
    Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

commit 8b4c6a3ab596961b784659c71dc24b341f938a1a
Author: Matthew Garrett <mjg@redhat.com>
Date:   Thu Apr 1 12:31:08 2010 -0400

    USB: option: Use generic USB wwan code
    
    As this code was simply factored out of option, this is a simple
    conversion.
    
    Signed-off-by: Matthew Garrett <mjg@redhat.com>
    Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

commit 0d4561947b8ddd5d944bdbbdc1ea1d6fd9a06041
Author: Matthew Garrett <mjg@redhat.com>
Date:   Thu Apr 1 12:31:07 2010 -0400

    usb serial: Add generic USB wwan support
    
    The generic USB serial code is ill-suited for high-speed USB wwan devices,
    resulting in the option driver. However, other non-option devices may also
    gain similar benefits from not using the generic code. Factorise out the
    non-option specific code from the option driver and make it available to
    other users.
    
    Signed-off-by: Matthew Garrett <mjg@redhat.com>
    Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>


Thanks,

Mark

-- System Information:
Debian Release: 5.0.4
  APT prefers stable
  APT policy: (500, 'stable')
Architecture: i386 (i686)

Kernel: Linux 2.6.26-2-686 (SMP w/1 CPU core)
Locale: LANG=en_GB.UTF-8, LC_CTYPE=en_GB.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/bash
diff -Naur source-orig//drivers/usb/serial/Kconfig source//drivers/usb/serial/Kconfig
--- source-orig//drivers/usb/serial/Kconfig	2010-06-11 21:45:31.191834288 +0100
+++ source//drivers/usb/serial/Kconfig	2010-06-11 21:50:52.087338327 +0100
@@ -565,8 +565,12 @@
 	  To compile this driver as a module, choose M here: the
 	  module will be called keyspan_pda.
 
+config USB_SERIAL_WWAN
+       tristate
+
 config USB_SERIAL_OPTION
 	tristate "USB driver for GSM and CDMA modems"
+	select USB_SERIAL_WWAN
 	help
 	  Say Y here if you have a GSM or CDMA modem that's connected to USB.
 
diff -Naur source-orig//drivers/usb/serial/Makefile source//drivers/usb/serial/Makefile
--- source-orig//drivers/usb/serial/Makefile	2010-06-11 21:45:31.191834288 +0100
+++ source//drivers/usb/serial/Makefile	2010-06-11 21:50:52.087338327 +0100
@@ -51,6 +51,7 @@
 obj-$(CONFIG_USB_SERIAL_SIERRAWIRELESS)		+= sierra.o
 obj-$(CONFIG_USB_SERIAL_SPCP8X5)		+= spcp8x5.o
 obj-$(CONFIG_USB_SERIAL_SYMBOL)			+= symbolserial.o
+obj-$(CONFIG_USB_SERIAL_WWAN)			+= usb_wwan.o
 obj-$(CONFIG_USB_SERIAL_TI)			+= ti_usb_3410_5052.o
 obj-$(CONFIG_USB_SERIAL_VISOR)			+= visor.o
 obj-$(CONFIG_USB_SERIAL_WHITEHEAT)		+= whiteheat.o
diff -Naur source-orig//drivers/usb/serial/option.c source//drivers/usb/serial/option.c
--- source-orig//drivers/usb/serial/option.c	2010-06-11 21:49:02.422835222 +0100
+++ source//drivers/usb/serial/option.c	2010-06-11 21:50:52.094842050 +0100
@@ -41,35 +41,14 @@
 #include <linux/bitops.h>
 #include <linux/usb.h>
 #include <linux/usb/serial.h>
+#include "usb-wwan.h"
 
 /* Function prototypes */
 static int  option_probe(struct usb_serial *serial,
 			const struct usb_device_id *id);
-static int  option_open(struct tty_struct *tty, struct usb_serial_port *port);
-static void option_close(struct usb_serial_port *port);
-static void option_dtr_rts(struct usb_serial_port *port, int on);
-
-static int  option_startup(struct usb_serial *serial);
-static void option_disconnect(struct usb_serial *serial);
-static void option_release(struct usb_serial *serial);
-static int  option_write_room(struct tty_struct *tty);
-
+static int option_send_setup(struct usb_serial_port *port);
 static void option_instat_callback(struct urb *urb);
 
-static int option_write(struct tty_struct *tty, struct usb_serial_port *port,
-			const unsigned char *buf, int count);
-static int  option_chars_in_buffer(struct tty_struct *tty);
-static void option_set_termios(struct tty_struct *tty,
-			struct usb_serial_port *port, struct ktermios *old);
-static int  option_tiocmget(struct tty_struct *tty, struct file *file);
-static int  option_tiocmset(struct tty_struct *tty, struct file *file,
-				unsigned int set, unsigned int clear);
-static int  option_send_setup(struct usb_serial_port *port);
-#ifdef CONFIG_PM
-static int  option_suspend(struct usb_serial *serial, pm_message_t message);
-static int  option_resume(struct usb_serial *serial);
-#endif
-
 /* Vendor and product IDs */
 #define OPTION_VENDOR_ID			0x0AF0
 #define OPTION_PRODUCT_COLT			0x5000
@@ -680,22 +659,22 @@
 	.id_table          = option_ids,
 	.num_ports         = 1,
 	.probe             = option_probe,
-	.open              = option_open,
-	.close             = option_close,
-	.dtr_rts	   = option_dtr_rts,
-	.write             = option_write,
-	.write_room        = option_write_room,
-	.chars_in_buffer   = option_chars_in_buffer,
-	.set_termios       = option_set_termios,
-	.tiocmget          = option_tiocmget,
-	.tiocmset          = option_tiocmset,
-	.attach            = option_startup,
-	.disconnect        = option_disconnect,
-	.release           = option_release,
+	.open              = usb_wwan_open,
+	.close             = usb_wwan_close,
+	.dtr_rts	   = usb_wwan_dtr_rts,
+	.write             = usb_wwan_write,
+	.write_room        = usb_wwan_write_room,
+	.chars_in_buffer   = usb_wwan_chars_in_buffer,
+	.set_termios       = usb_wwan_set_termios,
+	.tiocmget          = usb_wwan_tiocmget,
+	.tiocmset          = usb_wwan_tiocmset,
+	.attach            = usb_wwan_startup,
+	.disconnect        = usb_wwan_disconnect,
+	.release           = usb_wwan_release,
 	.read_int_callback = option_instat_callback,
 #ifdef CONFIG_PM
-	.suspend           = option_suspend,
-	.resume            = option_resume,
+	.suspend           = usb_wwan_suspend,
+	.resume            = usb_wwan_resume,
 #endif
 };
 
@@ -708,12 +687,6 @@
 #define IN_BUFLEN 4096
 #define OUT_BUFLEN 4096
 
-struct option_intf_private {
-	spinlock_t susp_lock;
-	unsigned int suspended:1;
-	int in_flight;
-};
-
 struct option_port_private {
 	/* Input endpoints and buffer for this port */
 	struct urb *in_urbs[N_IN_URB];
@@ -770,209 +743,21 @@
 static int option_probe(struct usb_serial *serial,
 			const struct usb_device_id *id)
 {
-	struct option_intf_private *data;
+	struct usb_wwan_intf_private *data;
 	/* D-Link DWM 652 still exposes CD-Rom emulation interface in modem mode */
 	if (serial->dev->descriptor.idVendor == DLINK_VENDOR_ID &&
 		serial->dev->descriptor.idProduct == DLINK_PRODUCT_DWM_652 &&
 		serial->interface->cur_altsetting->desc.bInterfaceClass == 0x8)
 		return -ENODEV;
 
-	data = serial->private = kzalloc(sizeof(struct option_intf_private), GFP_KERNEL);
+	data = serial->private = kzalloc(sizeof(struct usb_wwan_intf_private), GFP_KERNEL);
 	if (!data)
 		return -ENOMEM;
+	data->send_setup = option_send_setup;
 	spin_lock_init(&data->susp_lock);
 	return 0;
 }
 
-static void option_set_termios(struct tty_struct *tty,
-		struct usb_serial_port *port, struct ktermios *old_termios)
-{
-	dbg("%s", __func__);
-	/* Doesn't support option setting */
-	tty_termios_copy_hw(tty->termios, old_termios);
-	option_send_setup(port);
-}
-
-static int option_tiocmget(struct tty_struct *tty, struct file *file)
-{
-	struct usb_serial_port *port = tty->driver_data;
-	unsigned int value;
-	struct option_port_private *portdata;
-
-	portdata = usb_get_serial_port_data(port);
-
-	value = ((portdata->rts_state) ? TIOCM_RTS : 0) |
-		((portdata->dtr_state) ? TIOCM_DTR : 0) |
-		((portdata->cts_state) ? TIOCM_CTS : 0) |
-		((portdata->dsr_state) ? TIOCM_DSR : 0) |
-		((portdata->dcd_state) ? TIOCM_CAR : 0) |
-		((portdata->ri_state) ? TIOCM_RNG : 0);
-
-	return value;
-}
-
-static int option_tiocmset(struct tty_struct *tty, struct file *file,
-			unsigned int set, unsigned int clear)
-{
-	struct usb_serial_port *port = tty->driver_data;
-	struct option_port_private *portdata;
-
-	portdata = usb_get_serial_port_data(port);
-
-	/* FIXME: what locks portdata fields ? */
-	if (set & TIOCM_RTS)
-		portdata->rts_state = 1;
-	if (set & TIOCM_DTR)
-		portdata->dtr_state = 1;
-
-	if (clear & TIOCM_RTS)
-		portdata->rts_state = 0;
-	if (clear & TIOCM_DTR)
-		portdata->dtr_state = 0;
-	return option_send_setup(port);
-}
-
-/* Write */
-static int option_write(struct tty_struct *tty, struct usb_serial_port *port,
-			const unsigned char *buf, int count)
-{
-	struct option_port_private *portdata;
-	struct option_intf_private *intfdata;
-	int i;
-	int left, todo;
-	struct urb *this_urb = NULL; /* spurious */
-	int err;
-	unsigned long flags;
-
-	portdata = usb_get_serial_port_data(port);
-	intfdata = port->serial->private;
-
-	dbg("%s: write (%d chars)", __func__, count);
-
-	i = 0;
-	left = count;
-	for (i = 0; left > 0 && i < N_OUT_URB; i++) {
-		todo = left;
-		if (todo > OUT_BUFLEN)
-			todo = OUT_BUFLEN;
-
-		this_urb = portdata->out_urbs[i];
-		if (test_and_set_bit(i, &portdata->out_busy)) {
-			if (time_before(jiffies,
-					portdata->tx_start_time[i] + 10 * HZ))
-				continue;
-			usb_unlink_urb(this_urb);
-			continue;
-		}
-		dbg("%s: endpoint %d buf %d", __func__,
-			usb_pipeendpoint(this_urb->pipe), i);
-
-		err = usb_autopm_get_interface_async(port->serial->interface);
-		if (err < 0)
-			break;
-
-		/* send the data */
-		memcpy(this_urb->transfer_buffer, buf, todo);
-		this_urb->transfer_buffer_length = todo;
-
-		spin_lock_irqsave(&intfdata->susp_lock, flags);
-		if (intfdata->suspended) {
-			usb_anchor_urb(this_urb, &portdata->delayed);
-			spin_unlock_irqrestore(&intfdata->susp_lock, flags);
-		} else {
-			intfdata->in_flight++;
-			spin_unlock_irqrestore(&intfdata->susp_lock, flags);
-			err = usb_submit_urb(this_urb, GFP_ATOMIC);
-			if (err) {
-				dbg("usb_submit_urb %p (write bulk) failed "
-					"(%d)", this_urb, err);
-				clear_bit(i, &portdata->out_busy);
-				spin_lock_irqsave(&intfdata->susp_lock, flags);
-				intfdata->in_flight--;
-				spin_unlock_irqrestore(&intfdata->susp_lock, flags);
-				continue;
-			}
-		}
-
-		portdata->tx_start_time[i] = jiffies;
-		buf += todo;
-		left -= todo;
-	}
-
-	count -= left;
-	dbg("%s: wrote (did %d)", __func__, count);
-	return count;
-}
-
-static void option_indat_callback(struct urb *urb)
-{
-	int err;
-	int endpoint;
-	struct usb_serial_port *port;
-	struct tty_struct *tty;
-	unsigned char *data = urb->transfer_buffer;
-	int status = urb->status;
-
-	dbg("%s: %p", __func__, urb);
-
-	endpoint = usb_pipeendpoint(urb->pipe);
-	port =  urb->context;
-
-	if (status) {
-		dbg("%s: nonzero status: %d on endpoint %02x.",
-		    __func__, status, endpoint);
-	} else {
-		tty = tty_port_tty_get(&port->port);
-		if (urb->actual_length) {
-			tty_buffer_request_room(tty, urb->actual_length);
-			tty_insert_flip_string(tty, data, urb->actual_length);
-			tty_flip_buffer_push(tty);
-		} else 
-			dbg("%s: empty read urb received", __func__);
-		tty_kref_put(tty);
-
-		/* Resubmit urb so we continue receiving */
-		if (port->port.count && status != -ESHUTDOWN) {
-			err = usb_submit_urb(urb, GFP_ATOMIC);
-			if (err)
-				printk(KERN_ERR "%s: resubmit read urb failed. "
-					"(%d)", __func__, err);
-			else
-				usb_mark_last_busy(port->serial->dev);
-		}
-
-	}
-	return;
-}
-
-static void option_outdat_callback(struct urb *urb)
-{
-	struct usb_serial_port *port;
-	struct option_port_private *portdata;
-	struct option_intf_private *intfdata;
-	int i;
-
-	dbg("%s", __func__);
-
-	port =  urb->context;
-	intfdata = port->serial->private;
-
-	usb_serial_port_softint(port);
-	usb_autopm_put_interface_async(port->serial->interface);
-	portdata = usb_get_serial_port_data(port);
-	spin_lock(&intfdata->susp_lock);
-	intfdata->in_flight--;
-	spin_unlock(&intfdata->susp_lock);
-
-	for (i = 0; i < N_OUT_URB; ++i) {
-		if (portdata->out_urbs[i] == urb) {
-			smp_mb__before_clear_bit();
-			clear_bit(i, &portdata->out_busy);
-			break;
-		}
-	}
-}
-
 static void option_instat_callback(struct urb *urb)
 {
 	int err;
@@ -1029,183 +814,6 @@
 	}
 }
 
-static int option_write_room(struct tty_struct *tty)
-{
-	struct usb_serial_port *port = tty->driver_data;
-	struct option_port_private *portdata;
-	int i;
-	int data_len = 0;
-	struct urb *this_urb;
-
-	portdata = usb_get_serial_port_data(port);
-
-	for (i = 0; i < N_OUT_URB; i++) {
-		this_urb = portdata->out_urbs[i];
-		if (this_urb && !test_bit(i, &portdata->out_busy))
-			data_len += OUT_BUFLEN;
-	}
-
-	dbg("%s: %d", __func__, data_len);
-	return data_len;
-}
-
-static int option_chars_in_buffer(struct tty_struct *tty)
-{
-	struct usb_serial_port *port = tty->driver_data;
-	struct option_port_private *portdata;
-	int i;
-	int data_len = 0;
-	struct urb *this_urb;
-
-	portdata = usb_get_serial_port_data(port);
-
-	for (i = 0; i < N_OUT_URB; i++) {
-		this_urb = portdata->out_urbs[i];
-		/* FIXME: This locking is insufficient as this_urb may
-		   go unused during the test */
-		if (this_urb && test_bit(i, &portdata->out_busy))
-			data_len += this_urb->transfer_buffer_length;
-	}
-	dbg("%s: %d", __func__, data_len);
-	return data_len;
-}
-
-static int option_open(struct tty_struct *tty, struct usb_serial_port *port)
-{
-	struct option_port_private *portdata;
-	struct option_intf_private *intfdata;
-	struct usb_serial *serial = port->serial;
-	int i, err;
-	struct urb *urb;
-
-	portdata = usb_get_serial_port_data(port);
-	intfdata = serial->private;
-
-	dbg("%s", __func__);
-
-	/* Start reading from the IN endpoint */
-	for (i = 0; i < N_IN_URB; i++) {
-		urb = portdata->in_urbs[i];
-		if (!urb)
-			continue;
-		err = usb_submit_urb(urb, GFP_KERNEL);
-		if (err) {
-			dbg("%s: submit urb %d failed (%d) %d",
-				__func__, i, err,
-				urb->transfer_buffer_length);
-		}
-	}
-
-	option_send_setup(port);
-
-	serial->interface->needs_remote_wakeup = 1;
-	spin_lock_irq(&intfdata->susp_lock);
-	portdata->opened = 1;
-	spin_unlock_irq(&intfdata->susp_lock);
-	usb_autopm_put_interface(serial->interface);
-
-	return 0;
-}
-
-static void option_dtr_rts(struct usb_serial_port *port, int on)
-{
-	struct usb_serial *serial = port->serial;
-	struct option_port_private *portdata;
-
-	dbg("%s", __func__);
-	portdata = usb_get_serial_port_data(port);
-	mutex_lock(&serial->disc_mutex);
-	portdata->rts_state = on;
-	portdata->dtr_state = on;
-	if (serial->dev)
-		option_send_setup(port);
-	mutex_unlock(&serial->disc_mutex);
-}
-
-
-static void option_close(struct usb_serial_port *port)
-{
-	int i;
-	struct usb_serial *serial = port->serial;
-	struct option_port_private *portdata;
-	struct option_intf_private *intfdata = port->serial->private;
-
-	dbg("%s", __func__);
-	portdata = usb_get_serial_port_data(port);
-
-	if (serial->dev) {
-		/* Stop reading/writing urbs */
-		spin_lock_irq(&intfdata->susp_lock);
-		portdata->opened = 0;
-		spin_unlock_irq(&intfdata->susp_lock);
-
-		for (i = 0; i < N_IN_URB; i++)
-			usb_kill_urb(portdata->in_urbs[i]);
-		for (i = 0; i < N_OUT_URB; i++)
-			usb_kill_urb(portdata->out_urbs[i]);
-		usb_autopm_get_interface(serial->interface);
-		serial->interface->needs_remote_wakeup = 0;
-	}
-}
-
-/* Helper functions used by option_setup_urbs */
-static struct urb *option_setup_urb(struct usb_serial *serial, int endpoint,
-		int dir, void *ctx, char *buf, int len,
-		void (*callback)(struct urb *))
-{
-	struct urb *urb;
-
-	if (endpoint == -1)
-		return NULL;		/* endpoint not needed */
-
-	urb = usb_alloc_urb(0, GFP_KERNEL);		/* No ISO */
-	if (urb == NULL) {
-		dbg("%s: alloc for endpoint %d failed.", __func__, endpoint);
-		return NULL;
-	}
-
-		/* Fill URB using supplied data. */
-	usb_fill_bulk_urb(urb, serial->dev,
-		      usb_sndbulkpipe(serial->dev, endpoint) | dir,
-		      buf, len, callback, ctx);
-
-	return urb;
-}
-
-/* Setup urbs */
-static void option_setup_urbs(struct usb_serial *serial)
-{
-	int i, j;
-	struct usb_serial_port *port;
-	struct option_port_private *portdata;
-
-	dbg("%s", __func__);
-
-	for (i = 0; i < serial->num_ports; i++) {
-		port = serial->port[i];
-		portdata = usb_get_serial_port_data(port);
-
-		/* Do indat endpoints first */
-		for (j = 0; j < N_IN_URB; ++j) {
-			portdata->in_urbs[j] = option_setup_urb(serial,
-					port->bulk_in_endpointAddress,
-					USB_DIR_IN, port,
-					portdata->in_buffer[j],
-					IN_BUFLEN, option_indat_callback);
-		}
-
-		/* outdat endpoints */
-		for (j = 0; j < N_OUT_URB; ++j) {
-			portdata->out_urbs[j] = option_setup_urb(serial,
-					port->bulk_out_endpointAddress,
-					USB_DIR_OUT, port,
-					portdata->out_buffer[j],
-					OUT_BUFLEN, option_outdat_callback);
-		}
-	}
-}
-
-
 /** send RTS/DTR state to the port.
  *
  * This is exactly the same as SET_CONTROL_LINE_STATE from the PSTN
@@ -1231,224 +839,6 @@
 		0x22, 0x21, val, ifNum, NULL, 0, USB_CTRL_SET_TIMEOUT);
 }
 
-static int option_startup(struct usb_serial *serial)
-{
-	int i, j, err;
-	struct usb_serial_port *port;
-	struct option_port_private *portdata;
-	u8 *buffer;
-
-	dbg("%s", __func__);
-
-	/* Now setup per port private data */
-	for (i = 0; i < serial->num_ports; i++) {
-		port = serial->port[i];
-		portdata = kzalloc(sizeof(*portdata), GFP_KERNEL);
-		if (!portdata) {
-			dbg("%s: kmalloc for option_port_private (%d) failed!.",
-					__func__, i);
-			return 1;
-		}
-		init_usb_anchor(&portdata->delayed);
-
-		for (j = 0; j < N_IN_URB; j++) {
-			buffer = (u8 *)__get_free_page(GFP_KERNEL);
-			if (!buffer)
-				goto bail_out_error;
-			portdata->in_buffer[j] = buffer;
-		}
-
-		for (j = 0; j < N_OUT_URB; j++) {
-			buffer = kmalloc(OUT_BUFLEN, GFP_KERNEL);
-			if (!buffer)
-				goto bail_out_error2;
-			portdata->out_buffer[j] = buffer;
-		}
-
-		usb_set_serial_port_data(port, portdata);
-
-		if (!port->interrupt_in_urb)
-			continue;
-		err = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
-		if (err)
-			dbg("%s: submit irq_in urb failed %d",
-				__func__, err);
-	}
-	option_setup_urbs(serial);
-	return 0;
-
-bail_out_error2:
-	for (j = 0; j < N_OUT_URB; j++)
-		kfree(portdata->out_buffer[j]);
-bail_out_error:
-	for (j = 0; j < N_IN_URB; j++)
-		if (portdata->in_buffer[j])
-			free_page((unsigned long)portdata->in_buffer[j]);
-	kfree(portdata);
-	return 1;
-}
-
-static void stop_read_write_urbs(struct usb_serial *serial)
-{
-	int i, j;
-	struct usb_serial_port *port;
-	struct option_port_private *portdata;
-
-	/* Stop reading/writing urbs */
-	for (i = 0; i < serial->num_ports; ++i) {
-		port = serial->port[i];
-		portdata = usb_get_serial_port_data(port);
-		for (j = 0; j < N_IN_URB; j++)
-			usb_kill_urb(portdata->in_urbs[j]);
-		for (j = 0; j < N_OUT_URB; j++)
-			usb_kill_urb(portdata->out_urbs[j]);
-	}
-}
-
-static void option_disconnect(struct usb_serial *serial)
-{
-	dbg("%s", __func__);
-
-	stop_read_write_urbs(serial);
-}
-
-static void option_release(struct usb_serial *serial)
-{
-	int i, j;
-	struct usb_serial_port *port;
-	struct option_port_private *portdata;
-
-	dbg("%s", __func__);
-
-	/* Now free them */
-	for (i = 0; i < serial->num_ports; ++i) {
-		port = serial->port[i];
-		portdata = usb_get_serial_port_data(port);
-
-		for (j = 0; j < N_IN_URB; j++) {
-			if (portdata->in_urbs[j]) {
-				usb_free_urb(portdata->in_urbs[j]);
-				free_page((unsigned long)
-					portdata->in_buffer[j]);
-				portdata->in_urbs[j] = NULL;
-			}
-		}
-		for (j = 0; j < N_OUT_URB; j++) {
-			if (portdata->out_urbs[j]) {
-				usb_free_urb(portdata->out_urbs[j]);
-				kfree(portdata->out_buffer[j]);
-				portdata->out_urbs[j] = NULL;
-			}
-		}
-	}
-
-	/* Now free per port private data */
-	for (i = 0; i < serial->num_ports; i++) {
-		port = serial->port[i];
-		kfree(usb_get_serial_port_data(port));
-	}
-}
-
-#ifdef CONFIG_PM
-static int option_suspend(struct usb_serial *serial, pm_message_t message)
-{
-	struct option_intf_private *intfdata = serial->private;
-	int b;
-
-	dbg("%s entered", __func__);
-
-	if (serial->dev->auto_pm) {
-		spin_lock_irq(&intfdata->susp_lock);
-		b = intfdata->in_flight;
-		spin_unlock_irq(&intfdata->susp_lock);
-
-		if (b)
-			return -EBUSY;
-	}
-
-	spin_lock_irq(&intfdata->susp_lock);
-	intfdata->suspended = 1;
-	spin_unlock_irq(&intfdata->susp_lock);
-	stop_read_write_urbs(serial);
-
-	return 0;
-}
-
-static void play_delayed(struct usb_serial_port *port)
-{
-	struct option_intf_private *data;
-	struct option_port_private *portdata;
-	struct urb *urb;
-	int err;
-
-	portdata = usb_get_serial_port_data(port);
-	data = port->serial->private;
-	while ((urb = usb_get_from_anchor(&portdata->delayed))) {
-		err = usb_submit_urb(urb, GFP_ATOMIC);
-		if (!err)
-			data->in_flight++;
-	}
-}
-
-static int option_resume(struct usb_serial *serial)
-{
-	int i, j;
-	struct usb_serial_port *port;
-	struct option_intf_private *intfdata = serial->private;
-	struct option_port_private *portdata;
-	struct urb *urb;
-	int err = 0;
-
-	dbg("%s entered", __func__);
-	/* get the interrupt URBs resubmitted unconditionally */
-	for (i = 0; i < serial->num_ports; i++) {
-		port = serial->port[i];
-		if (!port->interrupt_in_urb) {
-			dbg("%s: No interrupt URB for port %d\n", __func__, i);
-			continue;
-		}
-		err = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO);
-		dbg("Submitted interrupt URB for port %d (result %d)", i, err);
-		if (err < 0) {
-			err("%s: Error %d for interrupt URB of port%d",
-				 __func__, err, i);
-			goto err_out;
-		}
-	}
-
-	for (i = 0; i < serial->num_ports; i++) {
-		/* walk all ports */
-		port = serial->port[i];
-		portdata = usb_get_serial_port_data(port);
-
-		/* skip closed ports */
-		spin_lock_irq(&intfdata->susp_lock);
-		if (!portdata->opened) {
-			spin_unlock_irq(&intfdata->susp_lock);
-			continue;
-		}
-
-		for (j = 0; j < N_IN_URB; j++) {
-			urb = portdata->in_urbs[j];
-			err = usb_submit_urb(urb, GFP_ATOMIC);
-			if (err < 0) {
-				err("%s: Error %d for bulk URB %d",
-					 __func__, err, i);
-				spin_unlock_irq(&intfdata->susp_lock);
-				goto err_out;
-			}
-		}
-		play_delayed(port);
-		spin_unlock_irq(&intfdata->susp_lock);
-	}
-	spin_lock_irq(&intfdata->susp_lock);
-	intfdata->suspended = 0;
-	spin_unlock_irq(&intfdata->susp_lock);
-err_out:
-	return err;
-}
-#endif
-
 MODULE_AUTHOR(DRIVER_AUTHOR);
 MODULE_DESCRIPTION(DRIVER_DESC);
 MODULE_VERSION(DRIVER_VERSION);
diff -Naur source-orig//drivers/usb/serial/qcserial.c source//drivers/usb/serial/qcserial.c
--- source-orig//drivers/usb/serial/qcserial.c	2010-06-11 21:49:02.422835222 +0100
+++ source//drivers/usb/serial/qcserial.c	2010-06-11 21:52:03.599336246 +0100
@@ -15,13 +15,14 @@
 #include <linux/tty_flip.h>
 #include <linux/usb.h>
 #include <linux/usb/serial.h>
+#include "usb-wwan.h"
 
 #define DRIVER_AUTHOR "Qualcomm Inc"
 #define DRIVER_DESC "Qualcomm USB Serial driver"
 
 static int debug;
 
-static struct usb_device_id id_table[] = {
+static const struct usb_device_id id_table[] = {
 	{USB_DEVICE(0x05c6, 0x9211)},	/* Acer Gobi QDL device */
 	{USB_DEVICE(0x05c6, 0x9212)},	/* Acer Gobi Modem Device */
 	{USB_DEVICE(0x03f0, 0x1f1d)},	/* HP un2400 Gobi Modem Device */
@@ -76,6 +77,8 @@
 	{USB_DEVICE(0x1199, 0x900a)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
 	{USB_DEVICE(0x16d8, 0x8001)},	/* CMDTech Gobi 2000 QDL device (VU922) */
 	{USB_DEVICE(0x16d8, 0x8002)},	/* CMDTech Gobi 2000 Modem device (VU922) */
+	{USB_DEVICE(0x05c6, 0x9204)},	/* Gobi 2000 QDL device */
+	{USB_DEVICE(0x05c6, 0x9205)},	/* Gobi 2000 Modem device */
 	{ }				/* Terminating entry */
 };
 MODULE_DEVICE_TABLE(usb, id_table);
@@ -92,6 +95,8 @@
 
 static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)
 {
+	struct usb_wwan_intf_private *data;
+	struct usb_host_interface *intf = serial->interface->cur_altsetting;
 	int retval = -ENODEV;
 	__u8 nintf;
 	__u8 ifnum;
@@ -100,33 +105,45 @@
 
 	nintf = serial->dev->actconfig->desc.bNumInterfaces;
 	dbg("Num Interfaces = %d", nintf);
-	ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber;
+	ifnum = intf->desc.bInterfaceNumber;
 	dbg("This Interface = %d", ifnum);
 
+	data = serial->private = kzalloc(sizeof(struct usb_wwan_intf_private),
+					 GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	spin_lock_init(&data->susp_lock);
+
 	switch (nintf) {
 	case 1:
 		/* QDL mode */
-		if (serial->interface->num_altsetting == 2) {
-			struct usb_host_interface *intf;
-
+		/* Gobi 2000 has a single altsetting, older ones have two */
+		if (serial->interface->num_altsetting == 2)
 			intf = &serial->interface->altsetting[1];
-			if (intf->desc.bNumEndpoints == 2) {
-				if (usb_endpoint_is_bulk_in(&intf->endpoint[0].desc) &&
-				    usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) {
-					dbg("QDL port found");
-					retval = usb_set_interface(serial->dev, ifnum, 1);
-					if (retval < 0) {
-						dev_err(&serial->dev->dev,
-							"Could not set interface, error %d\n",
-							retval);
-						retval = -ENODEV;
-					}
-					return retval;
-				}
+		else if (serial->interface->num_altsetting > 2)
+			break;
+
+		if (intf->desc.bNumEndpoints == 2 &&
+		    usb_endpoint_is_bulk_in(&intf->endpoint[0].desc) &&
+		    usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) {
+			dbg("QDL port found");
+
+			if (serial->interface->num_altsetting == 1)
+				return 0;
+
+			retval = usb_set_interface(serial->dev, ifnum, 1);
+			if (retval < 0) {
+				dev_err(&serial->dev->dev,
+					"Could not set interface, error %d\n",
+					retval);
+				retval = -ENODEV;
 			}
+			return retval;
 		}
 		break;
 
+	case 3:
 	case 4:
 		/* Composite mode */
 		if (ifnum == 2) {
@@ -161,6 +178,18 @@
 	.usb_driver          = &qcdriver,
 	.num_ports           = 1,
 	.probe               = qcprobe,
+	.open		     = usb_wwan_open,
+	.close		     = usb_wwan_close,
+	.write		     = usb_wwan_write,
+	.write_room	     = usb_wwan_write_room,
+	.chars_in_buffer     = usb_wwan_chars_in_buffer,
+	.attach		     = usb_wwan_startup,
+	.disconnect	     = usb_wwan_disconnect,
+	.release	     = usb_wwan_release,
+#ifdef CONFIG_PM
+	.suspend	     = usb_wwan_suspend,
+	.resume		     = usb_wwan_resume,
+#endif
 };
 
 static int __init qcinit(void)
diff -Naur source-orig//drivers/usb/serial/usb_wwan.c source//drivers/usb/serial/usb_wwan.c
--- source-orig//drivers/usb/serial/usb_wwan.c	1970-01-01 01:00:00.000000000 +0100
+++ source//drivers/usb/serial/usb_wwan.c	2010-06-11 21:50:52.110835306 +0100
@@ -0,0 +1,665 @@
+/*
+  USB Driver layer for GSM modems
+
+  Copyright (C) 2005  Matthias Urlichs <smurf@smurf.noris.de>
+
+  This driver is free software; you can redistribute it and/or modify
+  it under the terms of Version 2 of the GNU General Public License as
+  published by the Free Software Foundation.
+
+  Portions copied from the Keyspan driver by Hugh Blemings <hugh@blemings.org>
+
+  History: see the git log.
+
+  Work sponsored by: Sigos GmbH, Germany <info@sigos.de>
+
+  This driver exists because the "normal" serial driver doesn't work too well
+  with GSM modems. Issues:
+  - data loss -- one single Receive URB is not nearly enough
+  - controlling the baud rate doesn't make sense
+*/
+
+#define DRIVER_VERSION "v0.7.2"
+#define DRIVER_AUTHOR "Matthias Urlichs <smurf@smurf.noris.de>"
+#define DRIVER_DESC "USB Driver for GSM modems"
+
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include "usb-wwan.h"
+
+static int debug;
+
+void usb_wwan_dtr_rts(struct usb_serial_port *port, int on)
+{
+	struct usb_serial *serial = port->serial;
+	struct usb_wwan_port_private *portdata;
+
+	struct usb_wwan_intf_private *intfdata;
+
+	dbg("%s", __func__);
+
+	intfdata = port->serial->private;
+
+	if (!intfdata->send_setup)
+		return;
+
+	portdata = usb_get_serial_port_data(port);
+	mutex_lock(&serial->disc_mutex);
+	portdata->rts_state = on;
+	portdata->dtr_state = on;
+	if (serial->dev)
+		intfdata->send_setup(port);
+	mutex_unlock(&serial->disc_mutex);
+}
+EXPORT_SYMBOL(usb_wwan_dtr_rts);
+
+void usb_wwan_set_termios(struct tty_struct *tty,
+			  struct usb_serial_port *port,
+			  struct ktermios *old_termios)
+{
+	struct usb_wwan_intf_private *intfdata = port->serial->private;
+
+	dbg("%s", __func__);
+
+	/* Doesn't support option setting */
+	tty_termios_copy_hw(tty->termios, old_termios);
+
+	if (intfdata->send_setup)
+		intfdata->send_setup(port);
+}
+EXPORT_SYMBOL(usb_wwan_set_termios);
+
+int usb_wwan_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	unsigned int value;
+	struct usb_wwan_port_private *portdata;
+
+	portdata = usb_get_serial_port_data(port);
+
+	value = ((portdata->rts_state) ? TIOCM_RTS : 0) |
+	    ((portdata->dtr_state) ? TIOCM_DTR : 0) |
+	    ((portdata->cts_state) ? TIOCM_CTS : 0) |
+	    ((portdata->dsr_state) ? TIOCM_DSR : 0) |
+	    ((portdata->dcd_state) ? TIOCM_CAR : 0) |
+	    ((portdata->ri_state) ? TIOCM_RNG : 0);
+
+	return value;
+}
+EXPORT_SYMBOL(usb_wwan_tiocmget);
+
+int usb_wwan_tiocmset(struct tty_struct *tty, struct file *file,
+		      unsigned int set, unsigned int clear)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+
+	portdata = usb_get_serial_port_data(port);
+	intfdata = port->serial->private;
+
+	if (!intfdata->send_setup)
+		return -EINVAL;
+
+	/* FIXME: what locks portdata fields ? */
+	if (set & TIOCM_RTS)
+		portdata->rts_state = 1;
+	if (set & TIOCM_DTR)
+		portdata->dtr_state = 1;
+
+	if (clear & TIOCM_RTS)
+		portdata->rts_state = 0;
+	if (clear & TIOCM_DTR)
+		portdata->dtr_state = 0;
+	return intfdata->send_setup(port);
+}
+EXPORT_SYMBOL(usb_wwan_tiocmset);
+
+/* Write */
+int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port,
+		   const unsigned char *buf, int count)
+{
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+	int i;
+	int left, todo;
+	struct urb *this_urb = NULL;	/* spurious */
+	int err;
+	unsigned long flags;
+
+	portdata = usb_get_serial_port_data(port);
+	intfdata = port->serial->private;
+
+	dbg("%s: write (%d chars)", __func__, count);
+
+	i = 0;
+	left = count;
+	for (i = 0; left > 0 && i < N_OUT_URB; i++) {
+		todo = left;
+		if (todo > OUT_BUFLEN)
+			todo = OUT_BUFLEN;
+
+		this_urb = portdata->out_urbs[i];
+		if (test_and_set_bit(i, &portdata->out_busy)) {
+			if (time_before(jiffies,
+					portdata->tx_start_time[i] + 10 * HZ))
+				continue;
+			usb_unlink_urb(this_urb);
+			continue;
+		}
+		dbg("%s: endpoint %d buf %d", __func__,
+		    usb_pipeendpoint(this_urb->pipe), i);
+
+		err = usb_autopm_get_interface_async(port->serial->interface);
+		if (err < 0)
+			break;
+
+		/* send the data */
+		memcpy(this_urb->transfer_buffer, buf, todo);
+		this_urb->transfer_buffer_length = todo;
+
+		spin_lock_irqsave(&intfdata->susp_lock, flags);
+		if (intfdata->suspended) {
+			usb_anchor_urb(this_urb, &portdata->delayed);
+			spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+		} else {
+			intfdata->in_flight++;
+			spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+			err = usb_submit_urb(this_urb, GFP_ATOMIC);
+			if (err) {
+				dbg("usb_submit_urb %p (write bulk) failed "
+				    "(%d)", this_urb, err);
+				clear_bit(i, &portdata->out_busy);
+				spin_lock_irqsave(&intfdata->susp_lock, flags);
+				intfdata->in_flight--;
+				spin_unlock_irqrestore(&intfdata->susp_lock,
+						       flags);
+				continue;
+			}
+		}
+
+		portdata->tx_start_time[i] = jiffies;
+		buf += todo;
+		left -= todo;
+	}
+
+	count -= left;
+	dbg("%s: wrote (did %d)", __func__, count);
+	return count;
+}
+EXPORT_SYMBOL(usb_wwan_write);
+
+static void usb_wwan_indat_callback(struct urb *urb)
+{
+	int err;
+	int endpoint;
+	struct usb_serial_port *port;
+	struct tty_struct *tty;
+	unsigned char *data = urb->transfer_buffer;
+	int status = urb->status;
+
+	dbg("%s: %p", __func__, urb);
+
+	endpoint = usb_pipeendpoint(urb->pipe);
+	port = urb->context;
+
+	if (status) {
+		dbg("%s: nonzero status: %d on endpoint %02x.",
+		    __func__, status, endpoint);
+	} else {
+		tty = tty_port_tty_get(&port->port);
+		if (urb->actual_length) {
+			tty_buffer_request_room(tty, urb->actual_length);
+			tty_insert_flip_string(tty, data, urb->actual_length);
+			tty_flip_buffer_push(tty);
+		} else
+			dbg("%s: empty read urb received", __func__);
+		tty_kref_put(tty);
+
+		/* Resubmit urb so we continue receiving */
+		if (port->port.count && status != -ESHUTDOWN) {
+			err = usb_submit_urb(urb, GFP_ATOMIC);
+			if (err)
+				printk(KERN_ERR "%s: resubmit read urb failed. "
+				       "(%d)", __func__, err);
+			else
+				usb_mark_last_busy(port->serial->dev);
+		}
+
+	}
+	return;
+}
+
+static void usb_wwan_outdat_callback(struct urb *urb)
+{
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+	int i;
+
+	dbg("%s", __func__);
+
+	port = urb->context;
+	intfdata = port->serial->private;
+
+	usb_serial_port_softint(port);
+	usb_autopm_put_interface_async(port->serial->interface);
+	portdata = usb_get_serial_port_data(port);
+	spin_lock(&intfdata->susp_lock);
+	intfdata->in_flight--;
+	spin_unlock(&intfdata->susp_lock);
+
+	for (i = 0; i < N_OUT_URB; ++i) {
+		if (portdata->out_urbs[i] == urb) {
+			smp_mb__before_clear_bit();
+			clear_bit(i, &portdata->out_busy);
+			break;
+		}
+	}
+}
+
+int usb_wwan_write_room(struct tty_struct *tty)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct usb_wwan_port_private *portdata;
+	int i;
+	int data_len = 0;
+	struct urb *this_urb;
+
+	portdata = usb_get_serial_port_data(port);
+
+	for (i = 0; i < N_OUT_URB; i++) {
+		this_urb = portdata->out_urbs[i];
+		if (this_urb && !test_bit(i, &portdata->out_busy))
+			data_len += OUT_BUFLEN;
+	}
+
+	dbg("%s: %d", __func__, data_len);
+	return data_len;
+}
+EXPORT_SYMBOL(usb_wwan_write_room);
+
+int usb_wwan_chars_in_buffer(struct tty_struct *tty)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct usb_wwan_port_private *portdata;
+	int i;
+	int data_len = 0;
+	struct urb *this_urb;
+
+	portdata = usb_get_serial_port_data(port);
+
+	for (i = 0; i < N_OUT_URB; i++) {
+		this_urb = portdata->out_urbs[i];
+		/* FIXME: This locking is insufficient as this_urb may
+		   go unused during the test */
+		if (this_urb && test_bit(i, &portdata->out_busy))
+			data_len += this_urb->transfer_buffer_length;
+	}
+	dbg("%s: %d", __func__, data_len);
+	return data_len;
+}
+EXPORT_SYMBOL(usb_wwan_chars_in_buffer);
+
+int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+	struct usb_serial *serial = port->serial;
+	int i, err;
+	struct urb *urb;
+
+	portdata = usb_get_serial_port_data(port);
+	intfdata = serial->private;
+
+	dbg("%s", __func__);
+
+	/* Start reading from the IN endpoint */
+	for (i = 0; i < N_IN_URB; i++) {
+		urb = portdata->in_urbs[i];
+		if (!urb)
+			continue;
+		err = usb_submit_urb(urb, GFP_KERNEL);
+		if (err) {
+			dbg("%s: submit urb %d failed (%d) %d",
+			    __func__, i, err, urb->transfer_buffer_length);
+		}
+	}
+
+	if (intfdata->send_setup)
+		intfdata->send_setup(port);
+
+	serial->interface->needs_remote_wakeup = 1;
+	spin_lock_irq(&intfdata->susp_lock);
+	portdata->opened = 1;
+	spin_unlock_irq(&intfdata->susp_lock);
+	usb_autopm_put_interface(serial->interface);
+
+	return 0;
+}
+EXPORT_SYMBOL(usb_wwan_open);
+
+void usb_wwan_close(struct usb_serial_port *port)
+{
+	int i;
+	struct usb_serial *serial = port->serial;
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata = port->serial->private;
+
+	dbg("%s", __func__);
+	portdata = usb_get_serial_port_data(port);
+
+	if (serial->dev) {
+		/* Stop reading/writing urbs */
+		spin_lock_irq(&intfdata->susp_lock);
+		portdata->opened = 0;
+		spin_unlock_irq(&intfdata->susp_lock);
+
+		for (i = 0; i < N_IN_URB; i++)
+			usb_kill_urb(portdata->in_urbs[i]);
+		for (i = 0; i < N_OUT_URB; i++)
+			usb_kill_urb(portdata->out_urbs[i]);
+		usb_autopm_get_interface(serial->interface);
+		serial->interface->needs_remote_wakeup = 0;
+	}
+}
+EXPORT_SYMBOL(usb_wwan_close);
+
+/* Helper functions used by usb_wwan_setup_urbs */
+static struct urb *usb_wwan_setup_urb(struct usb_serial *serial, int endpoint,
+				      int dir, void *ctx, char *buf, int len,
+				      void (*callback) (struct urb *))
+{
+	struct urb *urb;
+
+	if (endpoint == -1)
+		return NULL;	/* endpoint not needed */
+
+	urb = usb_alloc_urb(0, GFP_KERNEL);	/* No ISO */
+	if (urb == NULL) {
+		dbg("%s: alloc for endpoint %d failed.", __func__, endpoint);
+		return NULL;
+	}
+
+	/* Fill URB using supplied data. */
+	usb_fill_bulk_urb(urb, serial->dev,
+			  usb_sndbulkpipe(serial->dev, endpoint) | dir,
+			  buf, len, callback, ctx);
+
+	return urb;
+}
+
+/* Setup urbs */
+static void usb_wwan_setup_urbs(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+
+	dbg("%s", __func__);
+
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+
+		/* Do indat endpoints first */
+		for (j = 0; j < N_IN_URB; ++j) {
+			portdata->in_urbs[j] = usb_wwan_setup_urb(serial,
+								  port->
+								  bulk_in_endpointAddress,
+								  USB_DIR_IN,
+								  port,
+								  portdata->
+								  in_buffer[j],
+								  IN_BUFLEN,
+								  usb_wwan_indat_callback);
+		}
+
+		/* outdat endpoints */
+		for (j = 0; j < N_OUT_URB; ++j) {
+			portdata->out_urbs[j] = usb_wwan_setup_urb(serial,
+								   port->
+								   bulk_out_endpointAddress,
+								   USB_DIR_OUT,
+								   port,
+								   portdata->
+								   out_buffer
+								   [j],
+								   OUT_BUFLEN,
+								   usb_wwan_outdat_callback);
+		}
+	}
+}
+
+int usb_wwan_startup(struct usb_serial *serial)
+{
+	int i, j, err;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+	u8 *buffer;
+
+	dbg("%s", __func__);
+
+	/* Now setup per port private data */
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		portdata = kzalloc(sizeof(*portdata), GFP_KERNEL);
+		if (!portdata) {
+			dbg("%s: kmalloc for usb_wwan_port_private (%d) failed!.",
+			    __func__, i);
+			return 1;
+		}
+		init_usb_anchor(&portdata->delayed);
+
+		for (j = 0; j < N_IN_URB; j++) {
+			buffer = (u8 *) __get_free_page(GFP_KERNEL);
+			if (!buffer)
+				goto bail_out_error;
+			portdata->in_buffer[j] = buffer;
+		}
+
+		for (j = 0; j < N_OUT_URB; j++) {
+			buffer = kmalloc(OUT_BUFLEN, GFP_KERNEL);
+			if (!buffer)
+				goto bail_out_error2;
+			portdata->out_buffer[j] = buffer;
+		}
+
+		usb_set_serial_port_data(port, portdata);
+
+		if (!port->interrupt_in_urb)
+			continue;
+		err = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+		if (err)
+			dbg("%s: submit irq_in urb failed %d", __func__, err);
+	}
+	usb_wwan_setup_urbs(serial);
+	return 0;
+
+bail_out_error2:
+	for (j = 0; j < N_OUT_URB; j++)
+		kfree(portdata->out_buffer[j]);
+bail_out_error:
+	for (j = 0; j < N_IN_URB; j++)
+		if (portdata->in_buffer[j])
+			free_page((unsigned long)portdata->in_buffer[j]);
+	kfree(portdata);
+	return 1;
+}
+EXPORT_SYMBOL(usb_wwan_startup);
+
+static void stop_read_write_urbs(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+
+	/* Stop reading/writing urbs */
+	for (i = 0; i < serial->num_ports; ++i) {
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+		for (j = 0; j < N_IN_URB; j++)
+			usb_kill_urb(portdata->in_urbs[j]);
+		for (j = 0; j < N_OUT_URB; j++)
+			usb_kill_urb(portdata->out_urbs[j]);
+	}
+}
+
+void usb_wwan_disconnect(struct usb_serial *serial)
+{
+	dbg("%s", __func__);
+
+	stop_read_write_urbs(serial);
+}
+EXPORT_SYMBOL(usb_wwan_disconnect);
+
+void usb_wwan_release(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+
+	dbg("%s", __func__);
+
+	/* Now free them */
+	for (i = 0; i < serial->num_ports; ++i) {
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+
+		for (j = 0; j < N_IN_URB; j++) {
+			usb_free_urb(portdata->in_urbs[j]);
+			free_page((unsigned long)
+				  portdata->in_buffer[j]);
+			portdata->in_urbs[j] = NULL;
+		}
+		for (j = 0; j < N_OUT_URB; j++) {
+			usb_free_urb(portdata->out_urbs[j]);
+			kfree(portdata->out_buffer[j]);
+			portdata->out_urbs[j] = NULL;
+		}
+	}
+
+	/* Now free per port private data */
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		kfree(usb_get_serial_port_data(port));
+	}
+}
+EXPORT_SYMBOL(usb_wwan_release);
+
+#ifdef CONFIG_PM
+int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message)
+{
+	struct usb_wwan_intf_private *intfdata = serial->private;
+	int b;
+
+	dbg("%s entered", __func__);
+
+	if (message.event & PM_EVENT_AUTO) {
+		spin_lock_irq(&intfdata->susp_lock);
+		b = intfdata->in_flight;
+		spin_unlock_irq(&intfdata->susp_lock);
+
+		if (b)
+			return -EBUSY;
+	}
+
+	spin_lock_irq(&intfdata->susp_lock);
+	intfdata->suspended = 1;
+	spin_unlock_irq(&intfdata->susp_lock);
+	stop_read_write_urbs(serial);
+
+	return 0;
+}
+EXPORT_SYMBOL(usb_wwan_suspend);
+
+static void play_delayed(struct usb_serial_port *port)
+{
+	struct usb_wwan_intf_private *data;
+	struct usb_wwan_port_private *portdata;
+	struct urb *urb;
+	int err;
+
+	portdata = usb_get_serial_port_data(port);
+	data = port->serial->private;
+	while ((urb = usb_get_from_anchor(&portdata->delayed))) {
+		err = usb_submit_urb(urb, GFP_ATOMIC);
+		if (!err)
+			data->in_flight++;
+	}
+}
+
+int usb_wwan_resume(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_intf_private *intfdata = serial->private;
+	struct usb_wwan_port_private *portdata;
+	struct urb *urb;
+	int err = 0;
+
+	dbg("%s entered", __func__);
+	/* get the interrupt URBs resubmitted unconditionally */
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		if (!port->interrupt_in_urb) {
+			dbg("%s: No interrupt URB for port %d\n", __func__, i);
+			continue;
+		}
+		err = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO);
+		dbg("Submitted interrupt URB for port %d (result %d)", i, err);
+		if (err < 0) {
+			err("%s: Error %d for interrupt URB of port%d",
+			    __func__, err, i);
+			goto err_out;
+		}
+	}
+
+	for (i = 0; i < serial->num_ports; i++) {
+		/* walk all ports */
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+
+		/* skip closed ports */
+		spin_lock_irq(&intfdata->susp_lock);
+		if (!portdata->opened) {
+			spin_unlock_irq(&intfdata->susp_lock);
+			continue;
+		}
+
+		for (j = 0; j < N_IN_URB; j++) {
+			urb = portdata->in_urbs[j];
+			err = usb_submit_urb(urb, GFP_ATOMIC);
+			if (err < 0) {
+				err("%s: Error %d for bulk URB %d",
+				    __func__, err, i);
+				spin_unlock_irq(&intfdata->susp_lock);
+				goto err_out;
+			}
+		}
+		play_delayed(port);
+		spin_unlock_irq(&intfdata->susp_lock);
+	}
+	spin_lock_irq(&intfdata->susp_lock);
+	intfdata->suspended = 0;
+	spin_unlock_irq(&intfdata->susp_lock);
+err_out:
+	return err;
+}
+EXPORT_SYMBOL(usb_wwan_resume);
+#endif
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug messages");
diff -Naur source-orig//drivers/usb/serial/usb-wwan.h source//drivers/usb/serial/usb-wwan.h
--- source-orig//drivers/usb/serial/usb-wwan.h	1970-01-01 01:00:00.000000000 +0100
+++ source//drivers/usb/serial/usb-wwan.h	2010-06-11 21:50:52.103759088 +0100
@@ -0,0 +1,66 @@
+/*
+ * Definitions for USB serial mobile broadband cards
+ */
+
+#ifndef __LINUX_USB_USB_WWAN
+#define __LINUX_USB_USB_WWAN
+
+extern void usb_wwan_dtr_rts(struct usb_serial_port *port, int on);
+extern int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port);
+extern void usb_wwan_close(struct usb_serial_port *port);
+extern int usb_wwan_startup(struct usb_serial *serial);
+extern void usb_wwan_disconnect(struct usb_serial *serial);
+extern void usb_wwan_release(struct usb_serial *serial);
+extern int usb_wwan_write_room(struct tty_struct *tty);
+extern void usb_wwan_set_termios(struct tty_struct *tty,
+				 struct usb_serial_port *port,
+				 struct ktermios *old);
+extern int usb_wwan_tiocmget(struct tty_struct *tty, struct file *file);
+extern int usb_wwan_tiocmset(struct tty_struct *tty, struct file *file,
+			     unsigned int set, unsigned int clear);
+extern int usb_wwan_send_setup(struct usb_serial_port *port);
+extern int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port,
+			  const unsigned char *buf, int count);
+extern int usb_wwan_chars_in_buffer(struct tty_struct *tty);
+#ifdef CONFIG_PM
+extern int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message);
+extern int usb_wwan_resume(struct usb_serial *serial);
+#endif
+
+/* per port private data */
+
+#define N_IN_URB 4
+#define N_OUT_URB 4
+#define IN_BUFLEN 4096
+#define OUT_BUFLEN 4096
+
+struct usb_wwan_intf_private {
+	spinlock_t susp_lock;
+	unsigned int suspended:1;
+	int in_flight;
+	int (*send_setup) (struct usb_serial_port *port);
+};
+
+struct usb_wwan_port_private {
+	/* Input endpoints and buffer for this port */
+	struct urb *in_urbs[N_IN_URB];
+	u8 *in_buffer[N_IN_URB];
+	/* Output endpoints and buffer for this port */
+	struct urb *out_urbs[N_OUT_URB];
+	u8 *out_buffer[N_OUT_URB];
+	unsigned long out_busy;	/* Bit vector of URBs in use */
+	int opened;
+	struct usb_anchor delayed;
+
+	/* Settings for the port */
+	int rts_state;		/* Handshaking pins (outputs) */
+	int dtr_state;
+	int cts_state;		/* Handshaking pins (inputs) */
+	int dsr_state;
+	int dcd_state;
+	int ri_state;
+
+	unsigned long tx_start_time[N_OUT_URB];
+};
+
+#endif /* __LINUX_USB_USB_WWAN */
diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig
index a0ecb42..71238de 100644
--- a/drivers/usb/serial/Kconfig
+++ b/drivers/usb/serial/Kconfig
@@ -485,6 +485,7 @@ config USB_SERIAL_QCAUX
 
 config USB_SERIAL_QUALCOMM
 	tristate "USB Qualcomm Serial modem"
+	select USB_SERIAL_WWAN
 	help
 	  Say Y here if you have a Qualcomm USB modem device.  These are
 	  usually wireless cellular modems.
@@ -576,8 +577,12 @@ config USB_SERIAL_XIRCOM
 	  To compile this driver as a module, choose M here: the
 	  module will be called keyspan_pda.
 
+config USB_SERIAL_WWAN
+	tristate
+
 config USB_SERIAL_OPTION
 	tristate "USB driver for GSM and CDMA modems"
+	select USB_SERIAL_WWAN
 	help
 	  Say Y here if you have a GSM or CDMA modem that's connected to USB.
 
diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile
index 83c9e43..7928cf4 100644
--- a/drivers/usb/serial/Makefile
+++ b/drivers/usb/serial/Makefile
@@ -52,6 +52,7 @@ obj-$(CONFIG_USB_SERIAL_SIEMENS_MPI)		+= siemens_mpi.o
 obj-$(CONFIG_USB_SERIAL_SIERRAWIRELESS)		+= sierra.o
 obj-$(CONFIG_USB_SERIAL_SPCP8X5)		+= spcp8x5.o
 obj-$(CONFIG_USB_SERIAL_SYMBOL)			+= symbolserial.o
+obj-$(CONFIG_USB_SERIAL_WWAN)			+= usb_wwan.o
 obj-$(CONFIG_USB_SERIAL_TI)			+= ti_usb_3410_5052.o
 obj-$(CONFIG_USB_SERIAL_VISOR)			+= visor.o
 obj-$(CONFIG_USB_SERIAL_WHITEHEAT)		+= whiteheat.o
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index 84d0eda..1775e45 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -42,35 +42,14 @@
 #include <linux/bitops.h>
 #include <linux/usb.h>
 #include <linux/usb/serial.h>
+#include "usb-wwan.h"
 
 /* Function prototypes */
 static int  option_probe(struct usb_serial *serial,
 			const struct usb_device_id *id);
-static int  option_open(struct tty_struct *tty, struct usb_serial_port *port);
-static void option_close(struct usb_serial_port *port);
-static void option_dtr_rts(struct usb_serial_port *port, int on);
-
-static int  option_startup(struct usb_serial *serial);
-static void option_disconnect(struct usb_serial *serial);
-static void option_release(struct usb_serial *serial);
-static int  option_write_room(struct tty_struct *tty);
-
+static int option_send_setup(struct usb_serial_port *port);
 static void option_instat_callback(struct urb *urb);
 
-static int option_write(struct tty_struct *tty, struct usb_serial_port *port,
-			const unsigned char *buf, int count);
-static int  option_chars_in_buffer(struct tty_struct *tty);
-static void option_set_termios(struct tty_struct *tty,
-			struct usb_serial_port *port, struct ktermios *old);
-static int  option_tiocmget(struct tty_struct *tty, struct file *file);
-static int  option_tiocmset(struct tty_struct *tty, struct file *file,
-				unsigned int set, unsigned int clear);
-static int  option_send_setup(struct usb_serial_port *port);
-#ifdef CONFIG_PM
-static int  option_suspend(struct usb_serial *serial, pm_message_t message);
-static int  option_resume(struct usb_serial *serial);
-#endif
-
 /* Vendor and product IDs */
 #define OPTION_VENDOR_ID			0x0AF0
 #define OPTION_PRODUCT_COLT			0x5000
@@ -757,22 +736,22 @@ static struct usb_serial_driver option_1port_device = {
 	.id_table          = option_ids,
 	.num_ports         = 1,
 	.probe             = option_probe,
-	.open              = option_open,
-	.close             = option_close,
-	.dtr_rts	   = option_dtr_rts,
-	.write             = option_write,
-	.write_room        = option_write_room,
-	.chars_in_buffer   = option_chars_in_buffer,
-	.set_termios       = option_set_termios,
-	.tiocmget          = option_tiocmget,
-	.tiocmset          = option_tiocmset,
-	.attach            = option_startup,
-	.disconnect        = option_disconnect,
-	.release           = option_release,
+	.open              = usb_wwan_open,
+	.close             = usb_wwan_close,
+	.dtr_rts	   = usb_wwan_dtr_rts,
+	.write             = usb_wwan_write,
+	.write_room        = usb_wwan_write_room,
+	.chars_in_buffer   = usb_wwan_chars_in_buffer,
+	.set_termios       = usb_wwan_set_termios,
+	.tiocmget          = usb_wwan_tiocmget,
+	.tiocmset          = usb_wwan_tiocmset,
+	.attach            = usb_wwan_startup,
+	.disconnect        = usb_wwan_disconnect,
+	.release           = usb_wwan_release,
 	.read_int_callback = option_instat_callback,
 #ifdef CONFIG_PM
-	.suspend           = option_suspend,
-	.resume            = option_resume,
+	.suspend           = usb_wwan_suspend,
+	.resume            = usb_wwan_resume,
 #endif
 };
 
@@ -785,13 +764,6 @@ static int debug;
 #define IN_BUFLEN 4096
 #define OUT_BUFLEN 4096
 
-struct option_intf_private {
-	spinlock_t susp_lock;
-	unsigned int suspended:1;
-	int in_flight;
-	struct option_blacklist_info *blacklist_info;
-};
-
 struct option_port_private {
 	/* Input endpoints and buffer for this port */
 	struct urb *in_urbs[N_IN_URB];
@@ -848,8 +820,7 @@ module_exit(option_exit);
 static int option_probe(struct usb_serial *serial,
 			const struct usb_device_id *id)
 {
-	struct option_intf_private *data;
-
+	struct usb_wwan_intf_private *data;
 	/* D-Link DWM 652 still exposes CD-Rom emulation interface in modem mode */
 	if (serial->dev->descriptor.idVendor == DLINK_VENDOR_ID &&
 		serial->dev->descriptor.idProduct == DLINK_PRODUCT_DWM_652 &&
@@ -862,11 +833,13 @@ static int option_probe(struct usb_serial *serial,
 		serial->interface->cur_altsetting->desc.bInterfaceClass != 0xff)
 		return -ENODEV;
 
-	data = serial->private = kzalloc(sizeof(struct option_intf_private), GFP_KERNEL);
+	data = serial->private = kzalloc(sizeof(struct usb_wwan_intf_private), GFP_KERNEL);
+
 	if (!data)
 		return -ENOMEM;
+	data->send_setup = option_send_setup;
 	spin_lock_init(&data->susp_lock);
-	data->blacklist_info = (struct option_blacklist_info*) id->driver_info;
+	data->private = (void *)id->driver_info;
 	return 0;
 }
 
@@ -887,194 +860,6 @@ static enum option_blacklist_reason is_blacklisted(const u8 ifnum,
 	return OPTION_BLACKLIST_NONE;
 }
 
-static void option_set_termios(struct tty_struct *tty,
-		struct usb_serial_port *port, struct ktermios *old_termios)
-{
-	dbg("%s", __func__);
-	/* Doesn't support option setting */
-	tty_termios_copy_hw(tty->termios, old_termios);
-	option_send_setup(port);
-}
-
-static int option_tiocmget(struct tty_struct *tty, struct file *file)
-{
-	struct usb_serial_port *port = tty->driver_data;
-	unsigned int value;
-	struct option_port_private *portdata;
-
-	portdata = usb_get_serial_port_data(port);
-
-	value = ((portdata->rts_state) ? TIOCM_RTS : 0) |
-		((portdata->dtr_state) ? TIOCM_DTR : 0) |
-		((portdata->cts_state) ? TIOCM_CTS : 0) |
-		((portdata->dsr_state) ? TIOCM_DSR : 0) |
-		((portdata->dcd_state) ? TIOCM_CAR : 0) |
-		((portdata->ri_state) ? TIOCM_RNG : 0);
-
-	return value;
-}
-
-static int option_tiocmset(struct tty_struct *tty, struct file *file,
-			unsigned int set, unsigned int clear)
-{
-	struct usb_serial_port *port = tty->driver_data;
-	struct option_port_private *portdata;
-
-	portdata = usb_get_serial_port_data(port);
-
-	/* FIXME: what locks portdata fields ? */
-	if (set & TIOCM_RTS)
-		portdata->rts_state = 1;
-	if (set & TIOCM_DTR)
-		portdata->dtr_state = 1;
-
-	if (clear & TIOCM_RTS)
-		portdata->rts_state = 0;
-	if (clear & TIOCM_DTR)
-		portdata->dtr_state = 0;
-	return option_send_setup(port);
-}
-
-/* Write */
-static int option_write(struct tty_struct *tty, struct usb_serial_port *port,
-			const unsigned char *buf, int count)
-{
-	struct option_port_private *portdata;
-	struct option_intf_private *intfdata;
-	int i;
-	int left, todo;
-	struct urb *this_urb = NULL; /* spurious */
-	int err;
-	unsigned long flags;
-
-	portdata = usb_get_serial_port_data(port);
-	intfdata = port->serial->private;
-
-	dbg("%s: write (%d chars)", __func__, count);
-
-	i = 0;
-	left = count;
-	for (i = 0; left > 0 && i < N_OUT_URB; i++) {
-		todo = left;
-		if (todo > OUT_BUFLEN)
-			todo = OUT_BUFLEN;
-
-		this_urb = portdata->out_urbs[i];
-		if (test_and_set_bit(i, &portdata->out_busy)) {
-			if (time_before(jiffies,
-					portdata->tx_start_time[i] + 10 * HZ))
-				continue;
-			usb_unlink_urb(this_urb);
-			continue;
-		}
-		dbg("%s: endpoint %d buf %d", __func__,
-			usb_pipeendpoint(this_urb->pipe), i);
-
-		err = usb_autopm_get_interface_async(port->serial->interface);
-		if (err < 0)
-			break;
-
-		/* send the data */
-		memcpy(this_urb->transfer_buffer, buf, todo);
-		this_urb->transfer_buffer_length = todo;
-
-		spin_lock_irqsave(&intfdata->susp_lock, flags);
-		if (intfdata->suspended) {
-			usb_anchor_urb(this_urb, &portdata->delayed);
-			spin_unlock_irqrestore(&intfdata->susp_lock, flags);
-		} else {
-			intfdata->in_flight++;
-			spin_unlock_irqrestore(&intfdata->susp_lock, flags);
-			err = usb_submit_urb(this_urb, GFP_ATOMIC);
-			if (err) {
-				dbg("usb_submit_urb %p (write bulk) failed "
-					"(%d)", this_urb, err);
-				clear_bit(i, &portdata->out_busy);
-				spin_lock_irqsave(&intfdata->susp_lock, flags);
-				intfdata->in_flight--;
-				spin_unlock_irqrestore(&intfdata->susp_lock, flags);
-				continue;
-			}
-		}
-
-		portdata->tx_start_time[i] = jiffies;
-		buf += todo;
-		left -= todo;
-	}
-
-	count -= left;
-	dbg("%s: wrote (did %d)", __func__, count);
-	return count;
-}
-
-static void option_indat_callback(struct urb *urb)
-{
-	int err;
-	int endpoint;
-	struct usb_serial_port *port;
-	struct tty_struct *tty;
-	unsigned char *data = urb->transfer_buffer;
-	int status = urb->status;
-
-	dbg("%s: %p", __func__, urb);
-
-	endpoint = usb_pipeendpoint(urb->pipe);
-	port =  urb->context;
-
-	if (status) {
-		dbg("%s: nonzero status: %d on endpoint %02x.",
-		    __func__, status, endpoint);
-	} else {
-		tty = tty_port_tty_get(&port->port);
-		if (urb->actual_length) {
-			tty_insert_flip_string(tty, data, urb->actual_length);
-			tty_flip_buffer_push(tty);
-		} else 
-			dbg("%s: empty read urb received", __func__);
-		tty_kref_put(tty);
-
-		/* Resubmit urb so we continue receiving */
-		if (status != -ESHUTDOWN) {
-			err = usb_submit_urb(urb, GFP_ATOMIC);
-			if (err && err != -EPERM)
-				printk(KERN_ERR "%s: resubmit read urb failed. "
-					"(%d)", __func__, err);
-			else
-				usb_mark_last_busy(port->serial->dev);
-		}
-
-	}
-	return;
-}
-
-static void option_outdat_callback(struct urb *urb)
-{
-	struct usb_serial_port *port;
-	struct option_port_private *portdata;
-	struct option_intf_private *intfdata;
-	int i;
-
-	dbg("%s", __func__);
-
-	port =  urb->context;
-	intfdata = port->serial->private;
-
-	usb_serial_port_softint(port);
-	usb_autopm_put_interface_async(port->serial->interface);
-	portdata = usb_get_serial_port_data(port);
-	spin_lock(&intfdata->susp_lock);
-	intfdata->in_flight--;
-	spin_unlock(&intfdata->susp_lock);
-
-	for (i = 0; i < N_OUT_URB; ++i) {
-		if (portdata->out_urbs[i] == urb) {
-			smp_mb__before_clear_bit();
-			clear_bit(i, &portdata->out_busy);
-			break;
-		}
-	}
-}
-
 static void option_instat_callback(struct urb *urb)
 {
 	int err;
@@ -1131,183 +916,6 @@ static void option_instat_callback(struct urb *urb)
 	}
 }
 
-static int option_write_room(struct tty_struct *tty)
-{
-	struct usb_serial_port *port = tty->driver_data;
-	struct option_port_private *portdata;
-	int i;
-	int data_len = 0;
-	struct urb *this_urb;
-
-	portdata = usb_get_serial_port_data(port);
-
-	for (i = 0; i < N_OUT_URB; i++) {
-		this_urb = portdata->out_urbs[i];
-		if (this_urb && !test_bit(i, &portdata->out_busy))
-			data_len += OUT_BUFLEN;
-	}
-
-	dbg("%s: %d", __func__, data_len);
-	return data_len;
-}
-
-static int option_chars_in_buffer(struct tty_struct *tty)
-{
-	struct usb_serial_port *port = tty->driver_data;
-	struct option_port_private *portdata;
-	int i;
-	int data_len = 0;
-	struct urb *this_urb;
-
-	portdata = usb_get_serial_port_data(port);
-
-	for (i = 0; i < N_OUT_URB; i++) {
-		this_urb = portdata->out_urbs[i];
-		/* FIXME: This locking is insufficient as this_urb may
-		   go unused during the test */
-		if (this_urb && test_bit(i, &portdata->out_busy))
-			data_len += this_urb->transfer_buffer_length;
-	}
-	dbg("%s: %d", __func__, data_len);
-	return data_len;
-}
-
-static int option_open(struct tty_struct *tty, struct usb_serial_port *port)
-{
-	struct option_port_private *portdata;
-	struct option_intf_private *intfdata;
-	struct usb_serial *serial = port->serial;
-	int i, err;
-	struct urb *urb;
-
-	portdata = usb_get_serial_port_data(port);
-	intfdata = serial->private;
-
-	dbg("%s", __func__);
-
-	/* Start reading from the IN endpoint */
-	for (i = 0; i < N_IN_URB; i++) {
-		urb = portdata->in_urbs[i];
-		if (!urb)
-			continue;
-		err = usb_submit_urb(urb, GFP_KERNEL);
-		if (err) {
-			dbg("%s: submit urb %d failed (%d) %d",
-				__func__, i, err,
-				urb->transfer_buffer_length);
-		}
-	}
-
-	option_send_setup(port);
-
-	serial->interface->needs_remote_wakeup = 1;
-	spin_lock_irq(&intfdata->susp_lock);
-	portdata->opened = 1;
-	spin_unlock_irq(&intfdata->susp_lock);
-	usb_autopm_put_interface(serial->interface);
-
-	return 0;
-}
-
-static void option_dtr_rts(struct usb_serial_port *port, int on)
-{
-	struct usb_serial *serial = port->serial;
-	struct option_port_private *portdata;
-
-	dbg("%s", __func__);
-	portdata = usb_get_serial_port_data(port);
-	mutex_lock(&serial->disc_mutex);
-	portdata->rts_state = on;
-	portdata->dtr_state = on;
-	if (serial->dev)
-		option_send_setup(port);
-	mutex_unlock(&serial->disc_mutex);
-}
-
-
-static void option_close(struct usb_serial_port *port)
-{
-	int i;
-	struct usb_serial *serial = port->serial;
-	struct option_port_private *portdata;
-	struct option_intf_private *intfdata = port->serial->private;
-
-	dbg("%s", __func__);
-	portdata = usb_get_serial_port_data(port);
-
-	if (serial->dev) {
-		/* Stop reading/writing urbs */
-		spin_lock_irq(&intfdata->susp_lock);
-		portdata->opened = 0;
-		spin_unlock_irq(&intfdata->susp_lock);
-
-		for (i = 0; i < N_IN_URB; i++)
-			usb_kill_urb(portdata->in_urbs[i]);
-		for (i = 0; i < N_OUT_URB; i++)
-			usb_kill_urb(portdata->out_urbs[i]);
-		usb_autopm_get_interface(serial->interface);
-		serial->interface->needs_remote_wakeup = 0;
-	}
-}
-
-/* Helper functions used by option_setup_urbs */
-static struct urb *option_setup_urb(struct usb_serial *serial, int endpoint,
-		int dir, void *ctx, char *buf, int len,
-		void (*callback)(struct urb *))
-{
-	struct urb *urb;
-
-	if (endpoint == -1)
-		return NULL;		/* endpoint not needed */
-
-	urb = usb_alloc_urb(0, GFP_KERNEL);		/* No ISO */
-	if (urb == NULL) {
-		dbg("%s: alloc for endpoint %d failed.", __func__, endpoint);
-		return NULL;
-	}
-
-		/* Fill URB using supplied data. */
-	usb_fill_bulk_urb(urb, serial->dev,
-		      usb_sndbulkpipe(serial->dev, endpoint) | dir,
-		      buf, len, callback, ctx);
-
-	return urb;
-}
-
-/* Setup urbs */
-static void option_setup_urbs(struct usb_serial *serial)
-{
-	int i, j;
-	struct usb_serial_port *port;
-	struct option_port_private *portdata;
-
-	dbg("%s", __func__);
-
-	for (i = 0; i < serial->num_ports; i++) {
-		port = serial->port[i];
-		portdata = usb_get_serial_port_data(port);
-
-		/* Do indat endpoints first */
-		for (j = 0; j < N_IN_URB; ++j) {
-			portdata->in_urbs[j] = option_setup_urb(serial,
-					port->bulk_in_endpointAddress,
-					USB_DIR_IN, port,
-					portdata->in_buffer[j],
-					IN_BUFLEN, option_indat_callback);
-		}
-
-		/* outdat endpoints */
-		for (j = 0; j < N_OUT_URB; ++j) {
-			portdata->out_urbs[j] = option_setup_urb(serial,
-					port->bulk_out_endpointAddress,
-					USB_DIR_OUT, port,
-					portdata->out_buffer[j],
-					OUT_BUFLEN, option_outdat_callback);
-		}
-	}
-}
-
-
 /** send RTS/DTR state to the port.
  *
  * This is exactly the same as SET_CONTROL_LINE_STATE from the PSTN
@@ -1316,15 +924,16 @@ static void option_setup_urbs(struct usb_serial *serial)
 static int option_send_setup(struct usb_serial_port *port)
 {
 	struct usb_serial *serial = port->serial;
-	struct option_intf_private *intfdata =
-		(struct option_intf_private *) serial->private;
+	struct usb_wwan_intf_private *intfdata =
+		(struct usb_wwan_intf_private *) serial->private;
 	struct option_port_private *portdata;
 	int ifNum = serial->interface->cur_altsetting->desc.bInterfaceNumber;
 	int val = 0;
 	dbg("%s", __func__);
 
-	if (is_blacklisted(ifNum, intfdata->blacklist_info) ==
-						OPTION_BLACKLIST_SENDSETUP) {
+	if (is_blacklisted(ifNum,
+			   (struct option_blacklist_info *) intfdata->private)
+	    == OPTION_BLACKLIST_SENDSETUP) {
 		dbg("No send_setup on blacklisted interface #%d\n", ifNum);
 		return -EIO;
 	}
@@ -1341,224 +950,6 @@ static int option_send_setup(struct usb_serial_port *port)
 		0x22, 0x21, val, ifNum, NULL, 0, USB_CTRL_SET_TIMEOUT);
 }
 
-static int option_startup(struct usb_serial *serial)
-{
-	int i, j, err;
-	struct usb_serial_port *port;
-	struct option_port_private *portdata;
-	u8 *buffer;
-
-	dbg("%s", __func__);
-
-	/* Now setup per port private data */
-	for (i = 0; i < serial->num_ports; i++) {
-		port = serial->port[i];
-		portdata = kzalloc(sizeof(*portdata), GFP_KERNEL);
-		if (!portdata) {
-			dbg("%s: kmalloc for option_port_private (%d) failed!.",
-					__func__, i);
-			return 1;
-		}
-		init_usb_anchor(&portdata->delayed);
-
-		for (j = 0; j < N_IN_URB; j++) {
-			buffer = (u8 *)__get_free_page(GFP_KERNEL);
-			if (!buffer)
-				goto bail_out_error;
-			portdata->in_buffer[j] = buffer;
-		}
-
-		for (j = 0; j < N_OUT_URB; j++) {
-			buffer = kmalloc(OUT_BUFLEN, GFP_KERNEL);
-			if (!buffer)
-				goto bail_out_error2;
-			portdata->out_buffer[j] = buffer;
-		}
-
-		usb_set_serial_port_data(port, portdata);
-
-		if (!port->interrupt_in_urb)
-			continue;
-		err = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
-		if (err)
-			dbg("%s: submit irq_in urb failed %d",
-				__func__, err);
-	}
-	option_setup_urbs(serial);
-	return 0;
-
-bail_out_error2:
-	for (j = 0; j < N_OUT_URB; j++)
-		kfree(portdata->out_buffer[j]);
-bail_out_error:
-	for (j = 0; j < N_IN_URB; j++)
-		if (portdata->in_buffer[j])
-			free_page((unsigned long)portdata->in_buffer[j]);
-	kfree(portdata);
-	return 1;
-}
-
-static void stop_read_write_urbs(struct usb_serial *serial)
-{
-	int i, j;
-	struct usb_serial_port *port;
-	struct option_port_private *portdata;
-
-	/* Stop reading/writing urbs */
-	for (i = 0; i < serial->num_ports; ++i) {
-		port = serial->port[i];
-		portdata = usb_get_serial_port_data(port);
-		for (j = 0; j < N_IN_URB; j++)
-			usb_kill_urb(portdata->in_urbs[j]);
-		for (j = 0; j < N_OUT_URB; j++)
-			usb_kill_urb(portdata->out_urbs[j]);
-	}
-}
-
-static void option_disconnect(struct usb_serial *serial)
-{
-	dbg("%s", __func__);
-
-	stop_read_write_urbs(serial);
-}
-
-static void option_release(struct usb_serial *serial)
-{
-	int i, j;
-	struct usb_serial_port *port;
-	struct option_port_private *portdata;
-
-	dbg("%s", __func__);
-
-	/* Now free them */
-	for (i = 0; i < serial->num_ports; ++i) {
-		port = serial->port[i];
-		portdata = usb_get_serial_port_data(port);
-
-		for (j = 0; j < N_IN_URB; j++) {
-			if (portdata->in_urbs[j]) {
-				usb_free_urb(portdata->in_urbs[j]);
-				free_page((unsigned long)
-					portdata->in_buffer[j]);
-				portdata->in_urbs[j] = NULL;
-			}
-		}
-		for (j = 0; j < N_OUT_URB; j++) {
-			if (portdata->out_urbs[j]) {
-				usb_free_urb(portdata->out_urbs[j]);
-				kfree(portdata->out_buffer[j]);
-				portdata->out_urbs[j] = NULL;
-			}
-		}
-	}
-
-	/* Now free per port private data */
-	for (i = 0; i < serial->num_ports; i++) {
-		port = serial->port[i];
-		kfree(usb_get_serial_port_data(port));
-	}
-}
-
-#ifdef CONFIG_PM
-static int option_suspend(struct usb_serial *serial, pm_message_t message)
-{
-	struct option_intf_private *intfdata = serial->private;
-	int b;
-
-	dbg("%s entered", __func__);
-
-	if (message.event & PM_EVENT_AUTO) {
-		spin_lock_irq(&intfdata->susp_lock);
-		b = intfdata->in_flight;
-		spin_unlock_irq(&intfdata->susp_lock);
-
-		if (b)
-			return -EBUSY;
-	}
-
-	spin_lock_irq(&intfdata->susp_lock);
-	intfdata->suspended = 1;
-	spin_unlock_irq(&intfdata->susp_lock);
-	stop_read_write_urbs(serial);
-
-	return 0;
-}
-
-static void play_delayed(struct usb_serial_port *port)
-{
-	struct option_intf_private *data;
-	struct option_port_private *portdata;
-	struct urb *urb;
-	int err;
-
-	portdata = usb_get_serial_port_data(port);
-	data = port->serial->private;
-	while ((urb = usb_get_from_anchor(&portdata->delayed))) {
-		err = usb_submit_urb(urb, GFP_ATOMIC);
-		if (!err)
-			data->in_flight++;
-	}
-}
-
-static int option_resume(struct usb_serial *serial)
-{
-	int i, j;
-	struct usb_serial_port *port;
-	struct option_intf_private *intfdata = serial->private;
-	struct option_port_private *portdata;
-	struct urb *urb;
-	int err = 0;
-
-	dbg("%s entered", __func__);
-	/* get the interrupt URBs resubmitted unconditionally */
-	for (i = 0; i < serial->num_ports; i++) {
-		port = serial->port[i];
-		if (!port->interrupt_in_urb) {
-			dbg("%s: No interrupt URB for port %d", __func__, i);
-			continue;
-		}
-		err = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO);
-		dbg("Submitted interrupt URB for port %d (result %d)", i, err);
-		if (err < 0) {
-			err("%s: Error %d for interrupt URB of port%d",
-				 __func__, err, i);
-			goto err_out;
-		}
-	}
-
-	for (i = 0; i < serial->num_ports; i++) {
-		/* walk all ports */
-		port = serial->port[i];
-		portdata = usb_get_serial_port_data(port);
-
-		/* skip closed ports */
-		spin_lock_irq(&intfdata->susp_lock);
-		if (!portdata->opened) {
-			spin_unlock_irq(&intfdata->susp_lock);
-			continue;
-		}
-
-		for (j = 0; j < N_IN_URB; j++) {
-			urb = portdata->in_urbs[j];
-			err = usb_submit_urb(urb, GFP_ATOMIC);
-			if (err < 0) {
-				err("%s: Error %d for bulk URB %d",
-					 __func__, err, i);
-				spin_unlock_irq(&intfdata->susp_lock);
-				goto err_out;
-			}
-		}
-		play_delayed(port);
-		spin_unlock_irq(&intfdata->susp_lock);
-	}
-	spin_lock_irq(&intfdata->susp_lock);
-	intfdata->suspended = 0;
-	spin_unlock_irq(&intfdata->susp_lock);
-err_out:
-	return err;
-}
-#endif
-
 MODULE_AUTHOR(DRIVER_AUTHOR);
 MODULE_DESCRIPTION(DRIVER_DESC);
 MODULE_VERSION(DRIVER_VERSION);
diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c
index 53a2d5a..350b235 100644
--- a/drivers/usb/serial/qcserial.c
+++ b/drivers/usb/serial/qcserial.c
@@ -13,8 +13,10 @@
 
 #include <linux/tty.h>
 #include <linux/tty_flip.h>
+#include <linux/slab.h>
 #include <linux/usb.h>
 #include <linux/usb/serial.h>
+#include "usb-wwan.h"
 
 #define DRIVER_AUTHOR "Qualcomm Inc"
 #define DRIVER_DESC "Qualcomm USB Serial driver"
@@ -76,6 +78,8 @@ static const struct usb_device_id id_table[] = {
 	{USB_DEVICE(0x1199, 0x900a)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
 	{USB_DEVICE(0x16d8, 0x8001)},	/* CMDTech Gobi 2000 QDL device (VU922) */
 	{USB_DEVICE(0x16d8, 0x8002)},	/* CMDTech Gobi 2000 Modem device (VU922) */
+	{USB_DEVICE(0x05c6, 0x9204)},	/* Gobi 2000 QDL device */
+	{USB_DEVICE(0x05c6, 0x9205)},	/* Gobi 2000 Modem device */
 	{ }				/* Terminating entry */
 };
 MODULE_DEVICE_TABLE(usb, id_table);
@@ -92,6 +96,8 @@ static struct usb_driver qcdriver = {
 
 static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)
 {
+	struct usb_wwan_intf_private *data;
+	struct usb_host_interface *intf = serial->interface->cur_altsetting;
 	int retval = -ENODEV;
 	__u8 nintf;
 	__u8 ifnum;
@@ -100,33 +106,45 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)
 
 	nintf = serial->dev->actconfig->desc.bNumInterfaces;
 	dbg("Num Interfaces = %d", nintf);
-	ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber;
+	ifnum = intf->desc.bInterfaceNumber;
 	dbg("This Interface = %d", ifnum);
 
+	data = serial->private = kzalloc(sizeof(struct usb_wwan_intf_private),
+					 GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	spin_lock_init(&data->susp_lock);
+
 	switch (nintf) {
 	case 1:
 		/* QDL mode */
-		if (serial->interface->num_altsetting == 2) {
-			struct usb_host_interface *intf;
-
+		/* Gobi 2000 has a single altsetting, older ones have two */
+		if (serial->interface->num_altsetting == 2)
 			intf = &serial->interface->altsetting[1];
-			if (intf->desc.bNumEndpoints == 2) {
-				if (usb_endpoint_is_bulk_in(&intf->endpoint[0].desc) &&
-				    usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) {
-					dbg("QDL port found");
-					retval = usb_set_interface(serial->dev, ifnum, 1);
-					if (retval < 0) {
-						dev_err(&serial->dev->dev,
-							"Could not set interface, error %d\n",
-							retval);
-						retval = -ENODEV;
-					}
-					return retval;
-				}
+		else if (serial->interface->num_altsetting > 2)
+			break;
+
+		if (intf->desc.bNumEndpoints == 2 &&
+		    usb_endpoint_is_bulk_in(&intf->endpoint[0].desc) &&
+		    usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) {
+			dbg("QDL port found");
+
+			if (serial->interface->num_altsetting == 1)
+				return 0;
+
+			retval = usb_set_interface(serial->dev, ifnum, 1);
+			if (retval < 0) {
+				dev_err(&serial->dev->dev,
+					"Could not set interface, error %d\n",
+					retval);
+				retval = -ENODEV;
 			}
+			return retval;
 		}
 		break;
 
+	case 3:
 	case 4:
 		/* Composite mode */
 		if (ifnum == 2) {
@@ -161,6 +179,18 @@ static struct usb_serial_driver qcdevice = {
 	.usb_driver          = &qcdriver,
 	.num_ports           = 1,
 	.probe               = qcprobe,
+	.open		     = usb_wwan_open,
+	.close		     = usb_wwan_close,
+	.write		     = usb_wwan_write,
+	.write_room	     = usb_wwan_write_room,
+	.chars_in_buffer     = usb_wwan_chars_in_buffer,
+	.attach		     = usb_wwan_startup,
+	.disconnect	     = usb_wwan_disconnect,
+	.release	     = usb_wwan_release,
+#ifdef CONFIG_PM
+	.suspend	     = usb_wwan_suspend,
+	.resume		     = usb_wwan_resume,
+#endif
 };
 
 static int __init qcinit(void)
diff --git a/drivers/usb/serial/usb-wwan.h b/drivers/usb/serial/usb-wwan.h
new file mode 100644
index 0000000..2be298a
--- /dev/null
+++ b/drivers/usb/serial/usb-wwan.h
@@ -0,0 +1,67 @@
+/*
+ * Definitions for USB serial mobile broadband cards
+ */
+
+#ifndef __LINUX_USB_USB_WWAN
+#define __LINUX_USB_USB_WWAN
+
+extern void usb_wwan_dtr_rts(struct usb_serial_port *port, int on);
+extern int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port);
+extern void usb_wwan_close(struct usb_serial_port *port);
+extern int usb_wwan_startup(struct usb_serial *serial);
+extern void usb_wwan_disconnect(struct usb_serial *serial);
+extern void usb_wwan_release(struct usb_serial *serial);
+extern int usb_wwan_write_room(struct tty_struct *tty);
+extern void usb_wwan_set_termios(struct tty_struct *tty,
+				 struct usb_serial_port *port,
+				 struct ktermios *old);
+extern int usb_wwan_tiocmget(struct tty_struct *tty, struct file *file);
+extern int usb_wwan_tiocmset(struct tty_struct *tty, struct file *file,
+			     unsigned int set, unsigned int clear);
+extern int usb_wwan_send_setup(struct usb_serial_port *port);
+extern int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port,
+			  const unsigned char *buf, int count);
+extern int usb_wwan_chars_in_buffer(struct tty_struct *tty);
+#ifdef CONFIG_PM
+extern int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message);
+extern int usb_wwan_resume(struct usb_serial *serial);
+#endif
+
+/* per port private data */
+
+#define N_IN_URB 4
+#define N_OUT_URB 4
+#define IN_BUFLEN 4096
+#define OUT_BUFLEN 4096
+
+struct usb_wwan_intf_private {
+	spinlock_t susp_lock;
+	unsigned int suspended:1;
+	int in_flight;
+	int (*send_setup) (struct usb_serial_port *port);
+	void *private;
+};
+
+struct usb_wwan_port_private {
+	/* Input endpoints and buffer for this port */
+	struct urb *in_urbs[N_IN_URB];
+	u8 *in_buffer[N_IN_URB];
+	/* Output endpoints and buffer for this port */
+	struct urb *out_urbs[N_OUT_URB];
+	u8 *out_buffer[N_OUT_URB];
+	unsigned long out_busy;	/* Bit vector of URBs in use */
+	int opened;
+	struct usb_anchor delayed;
+
+	/* Settings for the port */
+	int rts_state;		/* Handshaking pins (outputs) */
+	int dtr_state;
+	int cts_state;		/* Handshaking pins (inputs) */
+	int dsr_state;
+	int dcd_state;
+	int ri_state;
+
+	unsigned long tx_start_time[N_OUT_URB];
+};
+
+#endif /* __LINUX_USB_USB_WWAN */
diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c
new file mode 100644
index 0000000..0c70b4a
--- /dev/null
+++ b/drivers/usb/serial/usb_wwan.c
@@ -0,0 +1,665 @@
+/*
+  USB Driver layer for GSM modems
+
+  Copyright (C) 2005  Matthias Urlichs <smurf@smurf.noris.de>
+
+  This driver is free software; you can redistribute it and/or modify
+  it under the terms of Version 2 of the GNU General Public License as
+  published by the Free Software Foundation.
+
+  Portions copied from the Keyspan driver by Hugh Blemings <hugh@blemings.org>
+
+  History: see the git log.
+
+  Work sponsored by: Sigos GmbH, Germany <info@sigos.de>
+
+  This driver exists because the "normal" serial driver doesn't work too well
+  with GSM modems. Issues:
+  - data loss -- one single Receive URB is not nearly enough
+  - controlling the baud rate doesn't make sense
+*/
+
+#define DRIVER_VERSION "v0.7.2"
+#define DRIVER_AUTHOR "Matthias Urlichs <smurf@smurf.noris.de>"
+#define DRIVER_DESC "USB Driver for GSM modems"
+
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include "usb-wwan.h"
+
+static int debug;
+
+void usb_wwan_dtr_rts(struct usb_serial_port *port, int on)
+{
+	struct usb_serial *serial = port->serial;
+	struct usb_wwan_port_private *portdata;
+
+	struct usb_wwan_intf_private *intfdata;
+
+	dbg("%s", __func__);
+
+	intfdata = port->serial->private;
+
+	if (!intfdata->send_setup)
+		return;
+
+	portdata = usb_get_serial_port_data(port);
+	mutex_lock(&serial->disc_mutex);
+	portdata->rts_state = on;
+	portdata->dtr_state = on;
+	if (serial->dev)
+		intfdata->send_setup(port);
+	mutex_unlock(&serial->disc_mutex);
+}
+EXPORT_SYMBOL(usb_wwan_dtr_rts);
+
+void usb_wwan_set_termios(struct tty_struct *tty,
+			  struct usb_serial_port *port,
+			  struct ktermios *old_termios)
+{
+	struct usb_wwan_intf_private *intfdata = port->serial->private;
+
+	dbg("%s", __func__);
+
+	/* Doesn't support option setting */
+	tty_termios_copy_hw(tty->termios, old_termios);
+
+	if (intfdata->send_setup)
+		intfdata->send_setup(port);
+}
+EXPORT_SYMBOL(usb_wwan_set_termios);
+
+int usb_wwan_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	unsigned int value;
+	struct usb_wwan_port_private *portdata;
+
+	portdata = usb_get_serial_port_data(port);
+
+	value = ((portdata->rts_state) ? TIOCM_RTS : 0) |
+	    ((portdata->dtr_state) ? TIOCM_DTR : 0) |
+	    ((portdata->cts_state) ? TIOCM_CTS : 0) |
+	    ((portdata->dsr_state) ? TIOCM_DSR : 0) |
+	    ((portdata->dcd_state) ? TIOCM_CAR : 0) |
+	    ((portdata->ri_state) ? TIOCM_RNG : 0);
+
+	return value;
+}
+EXPORT_SYMBOL(usb_wwan_tiocmget);
+
+int usb_wwan_tiocmset(struct tty_struct *tty, struct file *file,
+		      unsigned int set, unsigned int clear)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+
+	portdata = usb_get_serial_port_data(port);
+	intfdata = port->serial->private;
+
+	if (!intfdata->send_setup)
+		return -EINVAL;
+
+	/* FIXME: what locks portdata fields ? */
+	if (set & TIOCM_RTS)
+		portdata->rts_state = 1;
+	if (set & TIOCM_DTR)
+		portdata->dtr_state = 1;
+
+	if (clear & TIOCM_RTS)
+		portdata->rts_state = 0;
+	if (clear & TIOCM_DTR)
+		portdata->dtr_state = 0;
+	return intfdata->send_setup(port);
+}
+EXPORT_SYMBOL(usb_wwan_tiocmset);
+
+/* Write */
+int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port,
+		   const unsigned char *buf, int count)
+{
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+	int i;
+	int left, todo;
+	struct urb *this_urb = NULL;	/* spurious */
+	int err;
+	unsigned long flags;
+
+	portdata = usb_get_serial_port_data(port);
+	intfdata = port->serial->private;
+
+	dbg("%s: write (%d chars)", __func__, count);
+
+	i = 0;
+	left = count;
+	for (i = 0; left > 0 && i < N_OUT_URB; i++) {
+		todo = left;
+		if (todo > OUT_BUFLEN)
+			todo = OUT_BUFLEN;
+
+		this_urb = portdata->out_urbs[i];
+		if (test_and_set_bit(i, &portdata->out_busy)) {
+			if (time_before(jiffies,
+					portdata->tx_start_time[i] + 10 * HZ))
+				continue;
+			usb_unlink_urb(this_urb);
+			continue;
+		}
+		dbg("%s: endpoint %d buf %d", __func__,
+		    usb_pipeendpoint(this_urb->pipe), i);
+
+		err = usb_autopm_get_interface_async(port->serial->interface);
+		if (err < 0)
+			break;
+
+		/* send the data */
+		memcpy(this_urb->transfer_buffer, buf, todo);
+		this_urb->transfer_buffer_length = todo;
+
+		spin_lock_irqsave(&intfdata->susp_lock, flags);
+		if (intfdata->suspended) {
+			usb_anchor_urb(this_urb, &portdata->delayed);
+			spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+		} else {
+			intfdata->in_flight++;
+			spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+			err = usb_submit_urb(this_urb, GFP_ATOMIC);
+			if (err) {
+				dbg("usb_submit_urb %p (write bulk) failed "
+				    "(%d)", this_urb, err);
+				clear_bit(i, &portdata->out_busy);
+				spin_lock_irqsave(&intfdata->susp_lock, flags);
+				intfdata->in_flight--;
+				spin_unlock_irqrestore(&intfdata->susp_lock,
+						       flags);
+				continue;
+			}
+		}
+
+		portdata->tx_start_time[i] = jiffies;
+		buf += todo;
+		left -= todo;
+	}
+
+	count -= left;
+	dbg("%s: wrote (did %d)", __func__, count);
+	return count;
+}
+EXPORT_SYMBOL(usb_wwan_write);
+
+static void usb_wwan_indat_callback(struct urb *urb)
+{
+	int err;
+	int endpoint;
+	struct usb_serial_port *port;
+	struct tty_struct *tty;
+	unsigned char *data = urb->transfer_buffer;
+	int status = urb->status;
+
+	dbg("%s: %p", __func__, urb);
+
+	endpoint = usb_pipeendpoint(urb->pipe);
+	port = urb->context;
+
+	if (status) {
+		dbg("%s: nonzero status: %d on endpoint %02x.",
+		    __func__, status, endpoint);
+	} else {
+		tty = tty_port_tty_get(&port->port);
+		if (urb->actual_length) {
+			tty_insert_flip_string(tty, data, urb->actual_length);
+			tty_flip_buffer_push(tty);
+		} else
+			dbg("%s: empty read urb received", __func__);
+		tty_kref_put(tty);
+
+		/* Resubmit urb so we continue receiving */
+		if (status != -ESHUTDOWN) {
+			err = usb_submit_urb(urb, GFP_ATOMIC);
+			if (err && err != -EPERM)
+				printk(KERN_ERR "%s: resubmit read urb failed. "
+				       "(%d)", __func__, err);
+			else
+				usb_mark_last_busy(port->serial->dev);
+		}
+
+	}
+	return;
+}
+
+static void usb_wwan_outdat_callback(struct urb *urb)
+{
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+	int i;
+
+	dbg("%s", __func__);
+
+	port = urb->context;
+	intfdata = port->serial->private;
+
+	usb_serial_port_softint(port);
+	usb_autopm_put_interface_async(port->serial->interface);
+	portdata = usb_get_serial_port_data(port);
+	spin_lock(&intfdata->susp_lock);
+	intfdata->in_flight--;
+	spin_unlock(&intfdata->susp_lock);
+
+	for (i = 0; i < N_OUT_URB; ++i) {
+		if (portdata->out_urbs[i] == urb) {
+			smp_mb__before_clear_bit();
+			clear_bit(i, &portdata->out_busy);
+			break;
+		}
+	}
+}
+
+int usb_wwan_write_room(struct tty_struct *tty)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct usb_wwan_port_private *portdata;
+	int i;
+	int data_len = 0;
+	struct urb *this_urb;
+
+	portdata = usb_get_serial_port_data(port);
+
+	for (i = 0; i < N_OUT_URB; i++) {
+		this_urb = portdata->out_urbs[i];
+		if (this_urb && !test_bit(i, &portdata->out_busy))
+			data_len += OUT_BUFLEN;
+	}
+
+	dbg("%s: %d", __func__, data_len);
+	return data_len;
+}
+EXPORT_SYMBOL(usb_wwan_write_room);
+
+int usb_wwan_chars_in_buffer(struct tty_struct *tty)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct usb_wwan_port_private *portdata;
+	int i;
+	int data_len = 0;
+	struct urb *this_urb;
+
+	portdata = usb_get_serial_port_data(port);
+
+	for (i = 0; i < N_OUT_URB; i++) {
+		this_urb = portdata->out_urbs[i];
+		/* FIXME: This locking is insufficient as this_urb may
+		   go unused during the test */
+		if (this_urb && test_bit(i, &portdata->out_busy))
+			data_len += this_urb->transfer_buffer_length;
+	}
+	dbg("%s: %d", __func__, data_len);
+	return data_len;
+}
+EXPORT_SYMBOL(usb_wwan_chars_in_buffer);
+
+int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+	struct usb_serial *serial = port->serial;
+	int i, err;
+	struct urb *urb;
+
+	portdata = usb_get_serial_port_data(port);
+	intfdata = serial->private;
+
+	dbg("%s", __func__);
+
+	/* Start reading from the IN endpoint */
+	for (i = 0; i < N_IN_URB; i++) {
+		urb = portdata->in_urbs[i];
+		if (!urb)
+			continue;
+		err = usb_submit_urb(urb, GFP_KERNEL);
+		if (err) {
+			dbg("%s: submit urb %d failed (%d) %d",
+			    __func__, i, err, urb->transfer_buffer_length);
+		}
+	}
+
+	if (intfdata->send_setup)
+		intfdata->send_setup(port);
+
+	serial->interface->needs_remote_wakeup = 1;
+	spin_lock_irq(&intfdata->susp_lock);
+	portdata->opened = 1;
+	spin_unlock_irq(&intfdata->susp_lock);
+	usb_autopm_put_interface(serial->interface);
+
+	return 0;
+}
+EXPORT_SYMBOL(usb_wwan_open);
+
+void usb_wwan_close(struct usb_serial_port *port)
+{
+	int i;
+	struct usb_serial *serial = port->serial;
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata = port->serial->private;
+
+	dbg("%s", __func__);
+	portdata = usb_get_serial_port_data(port);
+
+	if (serial->dev) {
+		/* Stop reading/writing urbs */
+		spin_lock_irq(&intfdata->susp_lock);
+		portdata->opened = 0;
+		spin_unlock_irq(&intfdata->susp_lock);
+
+		for (i = 0; i < N_IN_URB; i++)
+			usb_kill_urb(portdata->in_urbs[i]);
+		for (i = 0; i < N_OUT_URB; i++)
+			usb_kill_urb(portdata->out_urbs[i]);
+		usb_autopm_get_interface(serial->interface);
+		serial->interface->needs_remote_wakeup = 0;
+	}
+}
+EXPORT_SYMBOL(usb_wwan_close);
+
+/* Helper functions used by usb_wwan_setup_urbs */
+static struct urb *usb_wwan_setup_urb(struct usb_serial *serial, int endpoint,
+				      int dir, void *ctx, char *buf, int len,
+				      void (*callback) (struct urb *))
+{
+	struct urb *urb;
+
+	if (endpoint == -1)
+		return NULL;	/* endpoint not needed */
+
+	urb = usb_alloc_urb(0, GFP_KERNEL);	/* No ISO */
+	if (urb == NULL) {
+		dbg("%s: alloc for endpoint %d failed.", __func__, endpoint);
+		return NULL;
+	}
+
+	/* Fill URB using supplied data. */
+	usb_fill_bulk_urb(urb, serial->dev,
+			  usb_sndbulkpipe(serial->dev, endpoint) | dir,
+			  buf, len, callback, ctx);
+
+	return urb;
+}
+
+/* Setup urbs */
+static void usb_wwan_setup_urbs(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+
+	dbg("%s", __func__);
+
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+
+		/* Do indat endpoints first */
+		for (j = 0; j < N_IN_URB; ++j) {
+			portdata->in_urbs[j] = usb_wwan_setup_urb(serial,
+								  port->
+								  bulk_in_endpointAddress,
+								  USB_DIR_IN,
+								  port,
+								  portdata->
+								  in_buffer[j],
+								  IN_BUFLEN,
+								  usb_wwan_indat_callback);
+		}
+
+		/* outdat endpoints */
+		for (j = 0; j < N_OUT_URB; ++j) {
+			portdata->out_urbs[j] = usb_wwan_setup_urb(serial,
+								   port->
+								   bulk_out_endpointAddress,
+								   USB_DIR_OUT,
+								   port,
+								   portdata->
+								   out_buffer
+								   [j],
+								   OUT_BUFLEN,
+								   usb_wwan_outdat_callback);
+		}
+	}
+}
+
+int usb_wwan_startup(struct usb_serial *serial)
+{
+	int i, j, err;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+	u8 *buffer;
+
+	dbg("%s", __func__);
+
+	/* Now setup per port private data */
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		portdata = kzalloc(sizeof(*portdata), GFP_KERNEL);
+		if (!portdata) {
+			dbg("%s: kmalloc for usb_wwan_port_private (%d) failed!.",
+			    __func__, i);
+			return 1;
+		}
+		init_usb_anchor(&portdata->delayed);
+
+		for (j = 0; j < N_IN_URB; j++) {
+			buffer = (u8 *) __get_free_page(GFP_KERNEL);
+			if (!buffer)
+				goto bail_out_error;
+			portdata->in_buffer[j] = buffer;
+		}
+
+		for (j = 0; j < N_OUT_URB; j++) {
+			buffer = kmalloc(OUT_BUFLEN, GFP_KERNEL);
+			if (!buffer)
+				goto bail_out_error2;
+			portdata->out_buffer[j] = buffer;
+		}
+
+		usb_set_serial_port_data(port, portdata);
+
+		if (!port->interrupt_in_urb)
+			continue;
+		err = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+		if (err)
+			dbg("%s: submit irq_in urb failed %d", __func__, err);
+	}
+	usb_wwan_setup_urbs(serial);
+	return 0;
+
+bail_out_error2:
+	for (j = 0; j < N_OUT_URB; j++)
+		kfree(portdata->out_buffer[j]);
+bail_out_error:
+	for (j = 0; j < N_IN_URB; j++)
+		if (portdata->in_buffer[j])
+			free_page((unsigned long)portdata->in_buffer[j]);
+	kfree(portdata);
+	return 1;
+}
+EXPORT_SYMBOL(usb_wwan_startup);
+
+static void stop_read_write_urbs(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+
+	/* Stop reading/writing urbs */
+	for (i = 0; i < serial->num_ports; ++i) {
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+		for (j = 0; j < N_IN_URB; j++)
+			usb_kill_urb(portdata->in_urbs[j]);
+		for (j = 0; j < N_OUT_URB; j++)
+			usb_kill_urb(portdata->out_urbs[j]);
+	}
+}
+
+void usb_wwan_disconnect(struct usb_serial *serial)
+{
+	dbg("%s", __func__);
+
+	stop_read_write_urbs(serial);
+}
+EXPORT_SYMBOL(usb_wwan_disconnect);
+
+void usb_wwan_release(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+
+	dbg("%s", __func__);
+
+	/* Now free them */
+	for (i = 0; i < serial->num_ports; ++i) {
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+
+		for (j = 0; j < N_IN_URB; j++) {
+			usb_free_urb(portdata->in_urbs[j]);
+			free_page((unsigned long)
+				  portdata->in_buffer[j]);
+			portdata->in_urbs[j] = NULL;
+		}
+		for (j = 0; j < N_OUT_URB; j++) {
+			usb_free_urb(portdata->out_urbs[j]);
+			kfree(portdata->out_buffer[j]);
+			portdata->out_urbs[j] = NULL;
+		}
+	}
+
+	/* Now free per port private data */
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		kfree(usb_get_serial_port_data(port));
+	}
+}
+EXPORT_SYMBOL(usb_wwan_release);
+
+#ifdef CONFIG_PM
+int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message)
+{
+	struct usb_wwan_intf_private *intfdata = serial->private;
+	int b;
+
+	dbg("%s entered", __func__);
+
+	if (message.event & PM_EVENT_AUTO) {
+		spin_lock_irq(&intfdata->susp_lock);
+		b = intfdata->in_flight;
+		spin_unlock_irq(&intfdata->susp_lock);
+
+		if (b)
+			return -EBUSY;
+	}
+
+	spin_lock_irq(&intfdata->susp_lock);
+	intfdata->suspended = 1;
+	spin_unlock_irq(&intfdata->susp_lock);
+	stop_read_write_urbs(serial);
+
+	return 0;
+}
+EXPORT_SYMBOL(usb_wwan_suspend);
+
+static void play_delayed(struct usb_serial_port *port)
+{
+	struct usb_wwan_intf_private *data;
+	struct usb_wwan_port_private *portdata;
+	struct urb *urb;
+	int err;
+
+	portdata = usb_get_serial_port_data(port);
+	data = port->serial->private;
+	while ((urb = usb_get_from_anchor(&portdata->delayed))) {
+		err = usb_submit_urb(urb, GFP_ATOMIC);
+		if (!err)
+			data->in_flight++;
+	}
+}
+
+int usb_wwan_resume(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_intf_private *intfdata = serial->private;
+	struct usb_wwan_port_private *portdata;
+	struct urb *urb;
+	int err = 0;
+
+	dbg("%s entered", __func__);
+	/* get the interrupt URBs resubmitted unconditionally */
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		if (!port->interrupt_in_urb) {
+			dbg("%s: No interrupt URB for port %d", __func__, i);
+			continue;
+		}
+		err = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO);
+		dbg("Submitted interrupt URB for port %d (result %d)", i, err);
+		if (err < 0) {
+			err("%s: Error %d for interrupt URB of port%d",
+			    __func__, err, i);
+			goto err_out;
+		}
+	}
+
+	for (i = 0; i < serial->num_ports; i++) {
+		/* walk all ports */
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+
+		/* skip closed ports */
+		spin_lock_irq(&intfdata->susp_lock);
+		if (!portdata->opened) {
+			spin_unlock_irq(&intfdata->susp_lock);
+			continue;
+		}
+
+		for (j = 0; j < N_IN_URB; j++) {
+			urb = portdata->in_urbs[j];
+			err = usb_submit_urb(urb, GFP_ATOMIC);
+			if (err < 0) {
+				err("%s: Error %d for bulk URB %d",
+				    __func__, err, i);
+				spin_unlock_irq(&intfdata->susp_lock);
+				goto err_out;
+			}
+		}
+		play_delayed(port);
+		spin_unlock_irq(&intfdata->susp_lock);
+	}
+	spin_lock_irq(&intfdata->susp_lock);
+	intfdata->suspended = 0;
+	spin_unlock_irq(&intfdata->susp_lock);
+err_out:
+	return err;
+}
+EXPORT_SYMBOL(usb_wwan_resume);
+#endif
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug messages");

Reply to: