summaryrefslogtreecommitdiffstats
path: root/freed-ora/current/f13/linux-2.6-usb-wwan-update.patch
diff options
context:
space:
mode:
Diffstat (limited to 'freed-ora/current/f13/linux-2.6-usb-wwan-update.patch')
-rw-r--r--freed-ora/current/f13/linux-2.6-usb-wwan-update.patch1637
1 files changed, 1637 insertions, 0 deletions
diff --git a/freed-ora/current/f13/linux-2.6-usb-wwan-update.patch b/freed-ora/current/f13/linux-2.6-usb-wwan-update.patch
new file mode 100644
index 000000000..fbb5ad073
--- /dev/null
+++ b/freed-ora/current/f13/linux-2.6-usb-wwan-update.patch
@@ -0,0 +1,1637 @@
+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 950cb31..10a9276 100644
+--- a/drivers/usb/serial/option.c
++++ b/drivers/usb/serial/option.c
+@@ -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
+@@ -746,22 +725,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
+ };
+
+@@ -774,13 +753,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];
+@@ -837,8 +809,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 &&
+@@ -851,11 +822,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;
+ }
+
+@@ -876,194 +849,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;
+@@ -1120,183 +905,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
+@@ -1305,15 +913,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;
+ }
+@@ -1330,224 +939,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..9e55ef5 100644
+--- a/drivers/usb/serial/qcserial.c
++++ b/drivers/usb/serial/qcserial.c
+@@ -15,6 +15,7 @@
+ #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"
+@@ -76,6 +77,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 +95,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 +105,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 +178,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..1ccf852
+--- /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");
OpenPOWER on IntegriCloud