diff options
Diffstat (limited to 'drivers/usb/serial/via_usb_wwan.c')
-rwxr-xr-x | drivers/usb/serial/via_usb_wwan.c | 1013 |
1 files changed, 1013 insertions, 0 deletions
diff --git a/drivers/usb/serial/via_usb_wwan.c b/drivers/usb/serial/via_usb_wwan.c new file mode 100755 index 00000000..56c11455 --- /dev/null +++ b/drivers/usb/serial/via_usb_wwan.c @@ -0,0 +1,1013 @@ +/* + 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/uaccess.h> +#include <linux/usb.h> +#include <linux/usb/serial.h> +#include <linux/serial.h> +#include <linux/wakelock.h> +#include "via_usb-wwan.h" + +#ifdef CONFIG_USB_ANDROID_RAWBULK +#include <linux/usb/rawbulk.h> +#endif + +#ifdef CONFIG_VIATELECOM_SYNC_CBP +#include <mach/viatel.h> +#define ASC_INTF_NUM (10) +atomic_t intf_pm_count[ASC_INTF_NUM]; + +#define EXPORT_SYMBOL(x) + + +static struct asc_config usb_tx_handle = { + .name = USB_TX_HD_NAME, + .gpio_wake = GPIO_VIATEL_USB_AP_WAKE_MDM, + .gpio_ready = GPIO_VIATEL_USB_MDM_RDY, + .polar = 1, +}; + +struct asc_config usb_rx_handle = { + .name = USB_RX_HD_NAME, + .gpio_wake = GPIO_VIATEL_USB_MDM_WAKE_AP, + .gpio_ready = GPIO_VIATEL_USB_AP_RDY, + .polar = 1, +}; + +static int usb_ap_sync_cbp_init(void) +{ + int i, ret = 0; + for(i = 0; i < ASC_INTF_NUM; i++){ + atomic_set(&intf_pm_count[i], 0); + } + asc_tx_register_handle(&usb_tx_handle); + asc_rx_register_handle(&usb_rx_handle); + return ret; +} + +//late_initcall(usb_ap_sync_cbp_init); + +static int cbp_usb_interface_notifier(int msg, void *data) + { + int index, ret = 0; + struct usb_interface * intf = (struct usb_interface *)data; + + if(!intf) { + printk(KERN_ERR "%s - usb find device interface error\n", __func__); + return -ENODEV; + } + + index = (int)intf->cur_altsetting->desc.bInterfaceNumber; + if(index < 0 || index > ASC_INTF_NUM){ + printk(KERN_ERR "%s - error interface index %d.\n", __func__, index); + return -ENODEV; + } + switch(msg){ + case ASC_NTF_RX_PREPARE: + //printk("%s:inft%d get cnt=%d", __FUNCTION__, index, atomic_read(&intf_pm_count[index])); + if(!atomic_read(&intf_pm_count[index])){ + if(!usb_autopm_get_interface(intf)){ + atomic_set(&intf_pm_count[index], 1); + } + } + asc_rx_confirm_ready(USB_RX_HD_NAME, !ret); + break; + + case ASC_NTF_RX_POST: + //printk("%s:inft%d put cnt=%d", __FUNCTION__, index, atomic_read(&intf_pm_count[index])); + if(atomic_read(&intf_pm_count[index])){ + usb_autopm_put_interface(intf); + atomic_set(&intf_pm_count[index], 0); + } + break; + default: + printk("%s unknow message %d\n", __FUNCTION__, msg); + } + + return ret; +} +static int cbp_usb_interface_add_user(struct usb_interface * intf) +{ + int index; + struct asc_infor user; + + if(!intf){ + return -EINVAL; + } + + index = (int)intf->cur_altsetting->desc.bInterfaceNumber; + memset(&user, 0, sizeof(user)); + user.notifier = cbp_usb_interface_notifier; + user.data = intf; + snprintf(user.name, ASC_NAME_LEN, "%s%d", USB_RX_USER_NAME, index); + return asc_rx_add_user(USB_RX_HD_NAME, &user); +} + +static void cbp_usb_interface_del_user(struct usb_interface * intf) +{ + int index; + char path[ASC_NAME_LEN]; + + if(!intf){ + return ; + } + + index = (int)intf->cur_altsetting->desc.bInterfaceNumber; + if(atomic_read(&intf_pm_count[index])){ + usb_autopm_put_interface_no_suspend(intf); + atomic_set(&intf_pm_count[index], 0); + } + memset(path, 0, ASC_NAME_LEN); + snprintf(path, ASC_NAME_LEN, "%s%d", ASC_PATH(USB_RX_HD_NAME, USB_RX_USER_NAME), index); + return asc_rx_del_user(path); +} +#endif + +static int debug; +extern struct wake_lock ets_wake_lock; + +static unsigned int dump_mask = 0; +module_param(dump_mask, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(dump_mask, "Set data dump mask for each interface"); + +static void usb_wwan_dump_data(struct usb_interface *interface, + const char *str, const unsigned char *data, int size) { + int i; + int no_chars = 0; + int n = interface->cur_altsetting->desc.bInterfaceNumber; + + if (!(dump_mask & (1 << n))) + return; + + printk(KERN_DEBUG "usb_wwan: dump on interface#%d, %s: len = %d, chars = \"", + n, str, size); + for (i = 0; i < size; ++i) { + char c = data[i]; + if (c > 0x20 && c < 0x7e) { + printk("%c", c); + } else { + printk("."); + no_chars ++; + } + } + printk("\", data = "); + for (i = 0; i < size; ++i) { + printk("%.2x ", data[i]); + if (i > 7) + break; + } + if (size < 8) { + printk("\n"); + return; + } else if (i < size - 8) { + printk("... "); + i = size - 8; + } + for (; i < size; ++i) + printk("%.2x ", data[i]); + printk("\n"); +} + +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 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, + 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); + +static int get_serial_info(struct usb_serial_port *port, + struct serial_struct __user *retinfo) +{ + struct serial_struct tmp; + + if (!retinfo) + return -EFAULT; + + memset(&tmp, 0, sizeof(tmp)); + tmp.line = port->serial->minor; + tmp.port = port->number; + tmp.baud_base = tty_get_baud_rate(port->port.tty); + tmp.close_delay = port->port.close_delay / 10; + tmp.closing_wait = port->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : + port->port.closing_wait / 10; + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int set_serial_info(struct usb_serial_port *port, + struct serial_struct __user *newinfo) +{ + struct serial_struct new_serial; + unsigned int closing_wait, close_delay; + int retval = 0; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + close_delay = new_serial.close_delay * 10; + closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10; + + mutex_lock(&port->port.mutex); + + if (!capable(CAP_SYS_ADMIN)) { + if ((close_delay != port->port.close_delay) || + (closing_wait != port->port.closing_wait)) + retval = -EPERM; + else + retval = -EOPNOTSUPP; + } else { + port->port.close_delay = close_delay; + port->port.closing_wait = closing_wait; + } + + mutex_unlock(&port->port.mutex); + return retval; +} + +int usb_wwan_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + + dbg("%s cmd 0x%04x", __func__, cmd); + + switch (cmd) { + case TIOCGSERIAL: + return get_serial_info(port, + (struct serial_struct __user *) arg); + case TIOCSSERIAL: + return set_serial_info(port, + (struct serial_struct __user *) arg); + default: + break; + } + + dbg("%s arg not supported", __func__); + + return -ENOIOCTLCMD; +} +EXPORT_SYMBOL(usb_wwan_ioctl); + +/* 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); + +#ifdef CONFIG_VIATELECOM_SYNC_CBP + asc_tx_auto_ready(USB_TX_HD_NAME, 0); +#endif + + i = 0; + left = count; + for (i = 0; left > 0 && i < portdata->n_out_urb; i++) { + todo = left; + if (todo > portdata->out_buflen) + todo = portdata->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); + usb_autopm_put_interface_async(port->serial->interface); + break; + } + } + + 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 { + int inception = 0; + struct usb_wwan_port_private *portdata = usb_get_serial_port_data(port); +#ifdef CONFIG_USB_ANDROID_RAWBULK + spin_lock(&portdata->incept_lock); + inception = portdata->inception; + spin_unlock(&portdata->incept_lock); +#endif + if (inception) { +#ifdef CONFIG_USB_ANDROID_RAWBULK + struct usb_interface *interface = port->serial->interface; + int tid = interface->cur_altsetting->desc.bInterfaceNumber; + err = rawbulk_push_upstream_buffer(tid, data, urb->actual_length); + if (err < 0) + printk(KERN_DEBUG "failed to push data to rawbulk(%d) %d\n", tid, err); +#endif + } else { + usb_wwan_dump_data(port->serial->interface, "from device", data, + urb->actual_length); + tty = tty_port_tty_get(&port->port); + if (tty) { + 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) { + if (err != -EPERM) { + printk(KERN_ERR "%s: resubmit read urb failed. " + "(%d)", __func__, err); + /* busy also in error unless we are killed */ + usb_mark_last_busy(port->serial->dev); + } + } else { + usb_mark_last_busy(port->serial->dev); + } + } + + } +} + +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_wwan_dump_data(port->serial->interface, urb->status < 0? + "failed sent to device": "to device", + urb->transfer_buffer, urb->transfer_buffer_length); + 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 < portdata->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 < portdata->n_out_urb; i++) { + this_urb = portdata->out_urbs[i]; + if (this_urb && !test_bit(i, &portdata->out_busy)) + data_len += portdata->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 < portdata->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); + +static int port_to_infnum(struct usb_serial_port *port) +{ + struct usb_interface *interface = port->serial->interface; + int tid = interface->cur_altsetting->desc.bInterfaceNumber; + return tid; +} + +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__); + +#if defined(CONFIG_VIATELECOM_SYNC_CBP) + cbp_usb_interface_add_user(serial->interface); +#endif + + /* Start reading from the IN endpoint */ + for (i = 0; i < portdata->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 = 0; + spin_lock_irq(&intfdata->susp_lock); + portdata->opened = 1; + spin_unlock_irq(&intfdata->susp_lock); + /* this balances a get in the generic USB serial code */ + if(port_to_infnum(port) == 1) + { + usb_autopm_get_interface(serial->interface); + wake_lock(&ets_wake_lock); + } + else + 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) { +#if defined(CONFIG_VIATELECOM_SYNC_CBP) + cbp_usb_interface_del_user(serial->interface); +#endif + /* Stop reading/writing urbs */ + spin_lock_irq(&intfdata->susp_lock); + portdata->opened = 0; + spin_unlock_irq(&intfdata->susp_lock); + + for (i = 0; i < portdata->n_in_urb; i++) + usb_kill_urb(portdata->in_urbs[i]); + for (i = 0; i < portdata->n_out_urb; i++) + usb_kill_urb(portdata->out_urbs[i]); + /* balancing - important as an error cannot be handled*/ + if(port_to_infnum(port) == 1) + { + usb_autopm_put_interface(serial->interface); + wake_unlock(&ets_wake_lock); + } + else + usb_autopm_get_interface_no_resume(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 < portdata->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], + portdata->in_buflen, + usb_wwan_indat_callback); + } + + /* outdat endpoints */ + for (j = 0; j < portdata->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], + portdata->out_buflen, + usb_wwan_outdat_callback); + } + } +} + +static struct _via_cbp_port_init_params { + unsigned int n_in_urb; + unsigned int n_out_urb; + unsigned int in_buflen; + unsigned int out_buflen; +} _cbp_init_params[] = { + { 16, 16, 4096, 4096 }, /* Data Port */ + { 4, 4, 4096, 4096 }, /* ETS */ + { 1, 1, 4096, 4096 }, /* AT Channel */ + { 1, 1, 4096, 4096 }, /* PCV */ + { 1, 1, 4096, 4096 }, /* GPS */ + { }, +}; + +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++) { + int nr; + 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); + + //if (__le16_to_cpu(serial->dev->descriptor.idVendor) == 0x15eb && + // __le16_to_cpu(serial->dev->descriptor.idProduct) == 0x0001) { + nr = serial->interface->cur_altsetting->desc.bInterfaceNumber; + portdata->n_in_urb = min((int)_cbp_init_params[nr].n_in_urb, MAX_IN_URBS); + portdata->n_out_urb = min((int)_cbp_init_params[nr].n_out_urb, MAX_OUT_URBS); + portdata->in_buflen = _cbp_init_params[nr].in_buflen; + portdata->out_buflen = _cbp_init_params[nr].out_buflen; + //} else { + // portdata->n_in_urb = N_IN_URB; + // portdata->n_out_urb = N_OUT_URB; + // portdata->in_buflen = IN_BUFLEN; + // portdata->out_buflen = OUT_BUFLEN; + //} + + for (j = 0; j < portdata->n_in_urb; j++) { + buffer = (u8 *) __get_free_page(GFP_KERNEL); + if (!buffer) + goto bail_out_error; + portdata->in_buffer[j] = buffer; + portdata->in_buflen = PAGE_SIZE; + } + + for (j = 0; j < portdata->n_out_urb; j++) { + buffer = kmalloc(portdata->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 < portdata->n_out_urb; j++) + kfree(portdata->out_buffer[j]); +bail_out_error: + for (j = 0; j < portdata->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 < portdata->n_in_urb; j++) + usb_kill_urb(portdata->in_urbs[j]); + for (j = 0; j < portdata->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); +#if defined(CONFIG_VIATELECOM_SYNC_CBP) + cbp_usb_interface_del_user(serial->interface); +#endif +} +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 < portdata->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 < portdata->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 (PMSG_IS_AUTO(message)) { + 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 unbusy_queued_urb(struct urb *urb, struct usb_wwan_port_private *portdata) +{ + int i; + + for (i = 0; i < portdata->n_out_urb; i++) { + if (urb == portdata->out_urbs[i]) { + clear_bit(i, &portdata->out_busy); + break; + } + } +} + +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++; + } else { + /* we have to throw away the rest */ + do { + unbusy_queued_urb(urb, portdata); + usb_autopm_put_interface_no_suspend(port->serial->interface); + } while ((urb = usb_get_from_anchor(&portdata->delayed))); + break; + } + } +} + +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 < portdata->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"); |