From: Kazuhiro Ondo Date: Tue, 20 Jul 2010 19:36:21 +0000 (-0500) Subject: msm6600_modem: Add TS27.010 MDM6600 modem driver. X-Git-Tag: firefly_0821_release~9834^2~773 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=468f2727385309fafd8583b776b32f43106815b0;p=firefly-linux-kernel-4.4.55.git msm6600_modem: Add TS27.010 MDM6600 modem driver. Change-Id: Ide01d600124ade39544800cce35f09fd35918e27 Signed-off-by: Nick Pelly --- diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index 916b2b6d765f..8547d3e5d497 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -458,6 +458,15 @@ config USB_SERIAL_MOTOROLA To compile this driver as a module, choose M here: the module will be called moto_modem. If unsure, choose N. +config USB_MDM6600_CDMA_MODEM + tristate "USB Motorola Phone modem driver" + ---help--- + Say Y here if you want to use a Motorola phone with a USB + connector as a modem link. + + To compile this driver as a module, choose M here: the + module will be called mdm6600_modem. If unsure, choose N. + config USB_SERIAL_NAVMAN tristate "USB Navman GPS device" help diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index 40ebe17b6ea8..5b50ed1d9dfe 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_USB_SERIAL_MCT_U232) += mct_u232.o obj-$(CONFIG_USB_SERIAL_MOS7720) += mos7720.o obj-$(CONFIG_USB_SERIAL_MOS7840) += mos7840.o obj-$(CONFIG_USB_SERIAL_MOTOROLA) += moto_modem.o +obj-$(CONFIG_USB_MDM6600_CDMA_MODEM) += mdm6600_modem.o obj-$(CONFIG_USB_SERIAL_NAVMAN) += navman.o obj-$(CONFIG_USB_SERIAL_OMNINET) += omninet.o obj-$(CONFIG_USB_SERIAL_OPTICON) += opticon.o diff --git a/drivers/usb/serial/mdm6600_modem.c b/drivers/usb/serial/mdm6600_modem.c new file mode 100644 index 000000000000..712541d17dae --- /dev/null +++ b/drivers/usb/serial/mdm6600_modem.c @@ -0,0 +1,1045 @@ +/* + * Copyright (C) 2010 Motorola, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WDR_TIMEOUT (HZ * 5) + +#define BP_MODEM_STATUS 0x20a1 +#define BP_RSP_AVAIL 0x01a1 +#define BP_SPEED_CHANGE 0x2aa1 + +#define BP_CAR 0x01 +#define BP_DSR 0x02 +#define BP_BREAK 0x04 +#define BP_RNG 0x08 + +#define BULKOUT_SIZE 1280 +#define MODEM_INTERFACE_NUM 4 + +#define AP_NW 16 +#define AP_NR 16 + +struct ap_wb { + unsigned char *buf; + dma_addr_t dmah; + int len; + int use; + struct urb *urb; + struct modem_port *instance; +}; + +struct ap_rb { + struct list_head list; + int size; + unsigned char *base; + dma_addr_t dma; +}; + +struct ap_ru { + struct list_head list; + struct ap_rb *buffer; + struct urb *urb; + struct modem_port *instance; +}; + +struct modem_port { + __u16 modem_status; /* only used for data modem port */ + __u8 wakeup_gpio; + struct ap_ru ru[AP_NR]; + struct ap_rb rb[AP_NR]; + struct ap_wb wb[AP_NW]; + struct ap_wb *delayed_wb; + int rx_buflimit; + int rx_endpoint; + unsigned int susp_count; + unsigned int resuming; + struct tasklet_struct urb_task; + struct usb_serial_port *port; + spinlock_t read_lock; + spinlock_t write_lock; + spinlock_t last_traffic_lock; + unsigned long last_traffic; + unsigned int readsize; + unsigned int writesize; + struct list_head spare_read_urbs; + struct list_head spare_read_bufs; + struct list_head filled_read_bufs; + int processing; + int sending; + int opened; + struct work_struct wake_and_write; + struct work_struct usb_wkup_work; +}; + +static struct usb_device_id id_table[] = { + {USB_DEVICE(0x22b8, 0x2a70)}, /* Olympus MDM6600 BP modem */ + {}, +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +static uint32_t cdma_modem_debug; +module_param_named(cdma_mdm_debug, cdma_modem_debug, uint, 0664); + +static int modem_wb_alloc(struct modem_port *modem_ptr) +{ + int i; + struct ap_wb *wb; + + for (i = 0; i < AP_NW; i++) { + wb = &modem_ptr->wb[i]; + if (!wb->use) { + wb->use = 1; + return i; + } + } + return -1; +} + +static void modem_write_buffers_free( + struct modem_port *modem_ptr, + struct usb_serial *serial) +{ + int i; + struct ap_wb *wb; + struct usb_device *usb_dev = serial->dev; + + for (wb = &modem_ptr->wb[0], i = 0; i < AP_NW; i++, wb++) + usb_free_coherent(usb_dev, modem_ptr->writesize, + wb->buf, wb->dmah); +} + +static int modem_write_buffers_alloc( + struct modem_port *modem_ptr, + struct usb_serial *serial) +{ + int i; + struct ap_wb *wb; + + for (wb = &modem_ptr->wb[0], i = 0; i < AP_NW; i++, wb++) { + wb->buf = usb_alloc_coherent(serial->dev, modem_ptr->writesize, + GFP_KERNEL, &wb->dmah); + if (!wb->buf) { + while (i != 0) { + --i; + --wb; + usb_free_coherent(serial->dev, + modem_ptr->writesize, + wb->buf, wb->dmah); + } + return -ENOMEM; + } + } + return 0; +} + +static void stop_data_traffic(struct modem_port *modem_port_ptr) +{ + int i; + struct usb_serial_port *port = modem_port_ptr->port; + + if (port == NULL) + return; + if (cdma_modem_debug) + dev_info(&port->dev, "%s() port %d\n", + __func__, port->number); + + tasklet_disable(&modem_port_ptr->urb_task); + + for (i = 0; i < AP_NW; i++) + usb_kill_urb(modem_port_ptr->wb[i].urb); + for (i = 0; i < modem_port_ptr->rx_buflimit; i++) + usb_kill_urb(modem_port_ptr->ru[i].urb); + + usb_kill_urb(modem_port_ptr->port->interrupt_in_urb); + + tasklet_enable(&modem_port_ptr->urb_task); + + cancel_work_sync(&port->work); + cancel_work_sync(&modem_port_ptr->usb_wkup_work); +} + +static void modem_read_buffers_free( + struct modem_port *modem_ptr, + struct usb_serial *serial) +{ + struct usb_device *usb_dev = serial->dev; + int i; + int n = modem_ptr->rx_buflimit; + + for (i = 0; i < n; i++) + usb_free_coherent(usb_dev, modem_ptr->readsize, + modem_ptr->rb[i].base, + modem_ptr->rb[i].dma); +} + +static int modem_dtr_control(struct usb_serial *serial, int ctrl) +{ + uint8_t bRequesttype = + (USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT); + uint16_t wLength = 0; + uint8_t bRequest = 0x22; + uint16_t wValue = ctrl; + uint16_t wIndex = MODEM_INTERFACE_NUM; + unsigned int pipe; + int status; + + status = usb_autopm_get_interface(serial->interface); + if (status < 0) { + dev_err(&serial->dev->dev, "%s %s autopm failed %d", + dev_driver_string + (&serial->interface->dev), + dev_name(&serial->interface->dev), status); + return status; + } + + pipe = usb_sndctrlpipe(serial->dev, 0); + status = usb_control_msg(serial->dev, pipe, + bRequest, bRequesttype, + wValue, wIndex, NULL, wLength, + WDR_TIMEOUT); + usb_autopm_put_interface(serial->interface); + + return status; +} + +static int modem_tiocmget(struct tty_struct *tty, struct file *file) +{ + struct usb_serial_port *port = tty->driver_data; + struct modem_port *modem_port_ptr = usb_get_serial_data(port->serial); + + if (modem_port_ptr == NULL) + return 0; + + return (int)modem_port_ptr->modem_status; +} + +static int modem_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + int status = 0; + + if (cdma_modem_debug) + dev_info(&port->dev, "%s: Enter. clear is %d, set is %d\n", + __func__, clear, set); + + if (port->number == MODEM_INTERFACE_NUM) { + + if (clear & TIOCM_DTR) + status = modem_dtr_control(port->serial, 0); + + if (set & TIOCM_DTR) + status = modem_dtr_control(port->serial, 1); + } + + if (cdma_modem_debug) + dev_info(&port->dev, "%s: Exit. Status %d\n", + __func__, status); + + return status; +} + +static void modem_read_bulk_callback(struct urb *urb) +{ + struct ap_rb *buf; + struct ap_ru *rcv = urb->context; + struct modem_port *modem_port_ptr; + int status = urb->status; + + modem_port_ptr = rcv->instance; + if (modem_port_ptr == NULL) + return; + + if (modem_port_ptr->port == NULL) + return; + + buf = rcv->buffer; + buf->size = urb->actual_length; + + spin_lock(&modem_port_ptr->read_lock); + list_add_tail(&rcv->list, &modem_port_ptr->spare_read_urbs); + + if (likely(status == 0)) { + modem_port_ptr->processing++; + list_add_tail(&buf->list, &modem_port_ptr->filled_read_bufs); + } else { + if (cdma_modem_debug) + dev_info(&modem_port_ptr->port->dev, + "%s: bulk rx err %d\n", __func__, status); + /* we drop the buffer due to an error */ + list_add(&buf->list, &modem_port_ptr->spare_read_bufs); + /* nevertheless the tasklet must be kicked unconditionally + so the queue cannot dry up */ + } + + if (modem_port_ptr->opened != 1) { + spin_unlock(&modem_port_ptr->read_lock); + return; + } + + if (likely(modem_port_ptr->susp_count == 0)) + tasklet_schedule(&modem_port_ptr->urb_task); + spin_unlock(&modem_port_ptr->read_lock); + +} + +static void modem_update_modem_status(struct usb_serial_port *port, + __u8 modem_status) +{ + struct modem_port *modem_port_ptr; + + if (port->number == MODEM_INTERFACE_NUM) { + modem_port_ptr = usb_get_serial_data(port->serial); + if (modem_port_ptr == NULL) { + dev_err(&port->dev, + "%s: null modem port pointer.\n", + __func__); + return; + } + + if (modem_status & BP_CAR) + modem_port_ptr->modem_status |= TIOCM_CAR; + else + modem_port_ptr->modem_status &= ~TIOCM_CAR; + + if (modem_status & BP_DSR) + modem_port_ptr->modem_status |= TIOCM_DSR; + else + modem_port_ptr->modem_status &= ~TIOCM_DSR; + + if (modem_status & BP_RNG) + modem_port_ptr->modem_status |= TIOCM_RNG; + else + modem_port_ptr->modem_status &= ~TIOCM_RNG; + + if (cdma_modem_debug) + dev_info(&port->dev, "%s: modem status is now %d\n", + __func__, + modem_port_ptr->modem_status); + } +} + +static void modem_interrupt_callback(struct urb *urb) +{ + int status = urb->status; + uint16_t request_and_type; + uint8_t modem_status; + uint8_t *data; + int length; + int retval; + unsigned long flags; + + struct usb_serial_port *port = (struct usb_serial_port *)urb->context; + struct modem_port *modem_port_ptr = + usb_get_serial_data(port->serial); + + if (modem_port_ptr->port == NULL) + return; + + if (port->number != MODEM_INTERFACE_NUM) { + if (cdma_modem_debug) + dev_info(&port->dev, + "%s: Not Modem port.\n", __func__); + goto exit; + } + + switch (status) { + case 0: + if (cdma_modem_debug) + dev_info(&port->dev, "%s: usb -inter_cbk\n", __func__); + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + if (cdma_modem_debug) + dev_info(&port->dev, "%s: urb shutting down\n", + __func__); + return; + default: + dev_err(&port->dev, "%s: nonzero urb status, %d.\n", + __func__, status); + goto exit; + } + + spin_lock_irqsave(&modem_port_ptr->read_lock, flags); + modem_port_ptr->processing++; + spin_unlock_irqrestore(&modem_port_ptr->read_lock, flags); + + length = urb->actual_length; + data = (__u8 *) urb->transfer_buffer; + + request_and_type = *((__u16 *) data); + + if (cdma_modem_debug) + dev_info(&port->dev, "%s: request and type is %d\n", + __func__, request_and_type); + + switch (request_and_type) { + case BP_MODEM_STATUS: + modem_status = data[8]; + if (cdma_modem_debug) + dev_info(&port->dev, "%s: MODEM status %d\n", + __func__, modem_status); + modem_update_modem_status(port, modem_status); + break; + + case BP_RSP_AVAIL: + if (cdma_modem_debug) + dev_info(&port->dev, "%s: BP_RSP_AVAIL\n", + __func__); + break; + + case BP_SPEED_CHANGE: + if (cdma_modem_debug) + dev_info(&port->dev, "%s: BP_SPEED_CHANGE\n", + __func__); + break; + + default: + if (cdma_modem_debug) + dev_info(&port->dev, + "%s: undefined BP request type %d\n", + __func__, request_and_type); + break; + } + +exit: + spin_lock_irqsave(&modem_port_ptr->read_lock, flags); + if (modem_port_ptr->susp_count == 0) { + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) { + dev_err(&port->dev, + "%s: submit int usb failed. ret = %d\n", + __func__, retval); + } + } + modem_port_ptr->processing--; + spin_unlock_irqrestore(&modem_port_ptr->read_lock, flags); + return; +} + +static int modem_open(struct tty_struct *tty, + struct usb_serial_port *port) +{ + struct modem_port *modem_port_ptr = + usb_get_serial_data(port->serial); + int retval = 0; + int i; + unsigned long flags; + + if (cdma_modem_debug) + dev_info(&port->dev, "%s: Enter. Open Port %d\n", + __func__, port->number); + + /* clear the throttle flags */ + port->throttled = 0; + port->throttle_req = 0; + + if (modem_port_ptr == NULL) { + dev_err(&port->dev, + "%s: null modem port pointer.\n", + __func__); + return -ENODEV; + } + + port->serial->interface->needs_remote_wakeup = 1; + + modem_port_ptr->port = port; + + INIT_LIST_HEAD(&modem_port_ptr->spare_read_urbs); + INIT_LIST_HEAD(&modem_port_ptr->spare_read_bufs); + INIT_LIST_HEAD(&modem_port_ptr->filled_read_bufs); + + for (i = 0; i < modem_port_ptr->rx_buflimit; i++) { + list_add(&(modem_port_ptr->ru[i].list), + &modem_port_ptr->spare_read_urbs); + } + + for (i = 0; i < modem_port_ptr->rx_buflimit; i++) { + list_add(&(modem_port_ptr->rb[i].list), + &modem_port_ptr->spare_read_bufs); + } + + spin_lock_irqsave(&modem_port_ptr->read_lock, flags); + if (modem_port_ptr->susp_count == 0) + tasklet_schedule(&modem_port_ptr->urb_task); + spin_unlock_irqrestore(&modem_port_ptr->read_lock, flags); + + if (port->number == MODEM_INTERFACE_NUM) { + spin_lock_irqsave(&modem_port_ptr->read_lock, flags); + if (port->interrupt_in_urb) { + /* start to read INT EP data */ + port->interrupt_in_urb->dev = port->serial->dev; + retval = + usb_submit_urb(port->interrupt_in_urb, + GFP_KERNEL); + if (retval) { + usb_kill_urb(port->interrupt_in_urb); + dev_err(&port->dev, + "%s: retval is %d\n", + __func__, retval); + } + } else { + dev_err(&port->dev, + "%s: no interrupt endpoint\n", + __func__); + } + + spin_unlock_irqrestore(&modem_port_ptr->read_lock, + flags); + + /* clean up the modem status data */ + modem_port_ptr->modem_status = 0; + } + + if (cdma_modem_debug) + dev_info(&port->dev, "%s: Exit. retval = %d\n", + __func__, retval); + + modem_port_ptr->opened = 1; + + return retval; +} + +static void modem_rx_tasklet(unsigned long _modem_port) +{ + struct modem_port *modem_port_ptr = (void *)_modem_port; + struct ap_rb *buf; + struct tty_struct *tty; + struct usb_serial_port *port; + struct ap_ru *rcv; + unsigned long flags; + unsigned char throttled; + + if (!modem_port_ptr) + return; + + port = modem_port_ptr->port; + if (!port) + return; + + tty = port->port.tty; + if (!tty) + return; + + spin_lock_irqsave(&modem_port_ptr->port->lock, flags); + throttled = modem_port_ptr->port->throttle_req; + spin_unlock_irqrestore(&modem_port_ptr->port->lock, flags); + if (throttled) { + dev_err(&port->dev, "%s: throttled.\n", __func__); + return; + } + +next_buffer: + spin_lock_irqsave(&modem_port_ptr->read_lock, flags); + if (list_empty(&modem_port_ptr->filled_read_bufs)) { + spin_unlock_irqrestore(&modem_port_ptr->read_lock, flags); + goto urbs; + } + buf = list_entry(modem_port_ptr->filled_read_bufs.next, + struct ap_rb, list); + list_del(&buf->list); + spin_unlock_irqrestore(&modem_port_ptr->read_lock, flags); + + tty_buffer_request_room(tty, buf->size); + spin_lock_irqsave(&modem_port_ptr->port->lock, flags); + throttled = modem_port_ptr->port->throttle_req; + spin_unlock_irqrestore(&modem_port_ptr->port->lock, flags); + if (!throttled) + tty_insert_flip_string(tty, buf->base, buf->size); + tty_flip_buffer_push(tty); + + if (throttled) { + dev_err(&port->dev, "%s: Throttling noticed.\n", __func__); + spin_lock_irqsave(&modem_port_ptr->read_lock, flags); + list_add(&buf->list, &modem_port_ptr->filled_read_bufs); + spin_unlock_irqrestore(&modem_port_ptr->read_lock, flags); + return; + } + + spin_lock_irqsave(&modem_port_ptr->read_lock, flags); + list_add(&buf->list, &modem_port_ptr->spare_read_bufs); + spin_unlock_irqrestore(&modem_port_ptr->read_lock, flags); + goto next_buffer; + +urbs: + while (!list_empty(&modem_port_ptr->spare_read_bufs)) { + spin_lock_irqsave(&modem_port_ptr->read_lock, flags); + if (list_empty(&modem_port_ptr->spare_read_urbs)) { + modem_port_ptr->processing = 0; + spin_unlock_irqrestore(&modem_port_ptr->read_lock, + flags); + if (cdma_modem_debug) + dev_info(&port->dev, + "%s: no urb to create.\n", __func__); + return; + } + rcv = list_entry(modem_port_ptr->spare_read_urbs.next, + struct ap_ru, list); + list_del(&rcv->list); + + buf = list_entry(modem_port_ptr->spare_read_bufs.next, + struct ap_rb, list); + list_del(&buf->list); + + spin_unlock_irqrestore(&modem_port_ptr->read_lock, flags); + + rcv->buffer = buf; + + usb_fill_bulk_urb(rcv->urb, modem_port_ptr->port->serial->dev, + modem_port_ptr->rx_endpoint, + buf->base, + modem_port_ptr->readsize, + modem_read_bulk_callback, rcv); + rcv->urb->transfer_dma = buf->dma; + rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + spin_lock_irqsave(&modem_port_ptr->read_lock, flags); + if ((modem_port_ptr->susp_count > 0) || + usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) { + list_add(&buf->list, &modem_port_ptr->spare_read_bufs); + list_add(&rcv->list, &modem_port_ptr->spare_read_urbs); + modem_port_ptr->processing = 0; + dev_err(&port->dev, "%s: submit bulk in urb failed.\n", + __func__); + spin_unlock_irqrestore(&modem_port_ptr->read_lock, + flags); + return; + } else { + spin_unlock_irqrestore(&modem_port_ptr->read_lock, + flags); + } + } + spin_lock_irqsave(&modem_port_ptr->read_lock, flags); + modem_port_ptr->processing = 0; + spin_unlock_irqrestore(&modem_port_ptr->read_lock, flags); + +} + +static void modem_close(struct usb_serial_port *port) +{ + struct modem_port *modem_port_ptr; + + if (cdma_modem_debug) + dev_info(&port->dev, "%s: Enter. Close Port %d\n", + __func__, port->number); + + modem_port_ptr = usb_get_serial_data(port->serial); + if (!modem_port_ptr) { + dev_err(&port->dev, + "%s: null modem port pointer.\n", + __func__); + return; + } + + modem_port_ptr->opened = 0; + + stop_data_traffic(modem_port_ptr); + cancel_work_sync(&modem_port_ptr->wake_and_write); + modem_port_ptr->port = 0; + modem_port_ptr->modem_status = 0; + if (modem_port_ptr->delayed_wb) + modem_port_ptr->delayed_wb->use = 0; + + if (cdma_modem_debug) + dev_info(&port->dev, "%s: Exit.\n", __func__); +} + +/* caller hold modem_port_ptr->write_lock */ +static void modem_write_done(struct modem_port *modem_port_ptr, + struct ap_wb *wb) +{ + wb->use = 0; + modem_port_ptr->sending--; +} + +static int modem_start_wb(struct modem_port *modem_port_ptr, + struct ap_wb *wb) +{ + int result = 0; + struct usb_serial_port *port = modem_port_ptr->port; + unsigned long flags; + + if (port == NULL) + return -ENODEV; + + spin_lock_irqsave(&modem_port_ptr->write_lock, flags); + modem_port_ptr->sending++; + + wb->urb->transfer_buffer = wb->buf; + wb->urb->transfer_dma = wb->dmah; + wb->urb->transfer_buffer_length = wb->len; + wb->urb->dev = modem_port_ptr->port->serial->dev; + + result = usb_submit_urb(wb->urb, GFP_ATOMIC); + if (result < 0) { + dev_err(&port->dev, + "%s: Submit bulk out URB failed. ret = %d\n", + __func__, result); + modem_write_done(modem_port_ptr, wb); + } + + spin_unlock_irqrestore(&modem_port_ptr->write_lock, flags); + + return result; +} + +static void modem_write_bulk_callback(struct urb *urb) +{ + struct ap_wb *wb = urb->context; + int status = urb->status; + struct modem_port *modem_port_ptr = wb->instance; + struct usb_serial_port *port = modem_port_ptr->port; + unsigned long flags; + + if (port == NULL) + return; + + spin_lock_irqsave(&modem_port_ptr->write_lock, flags); + modem_write_done(modem_port_ptr, wb); + spin_unlock_irqrestore(&modem_port_ptr->write_lock, flags); + if (status) { + dev_err(&port->dev, "%s: status non-zero. status = %d\n", + __func__, status); + return; + } + usb_serial_port_softint(port); +} + +static int modem_write(struct tty_struct *tty, + struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + struct usb_serial *serial = port->serial; + int result, wbn; + struct ap_wb *wb; + struct modem_port *modem_port_ptr = + usb_get_serial_data(port->serial); + + + if (count == 0) { + if (cdma_modem_debug) + dev_info(&port->dev, "%s: Exit1: %s count = 0\n", + __func__, dev_name(&port->dev)); + return 0; + } + + if (serial->num_bulk_out) { + unsigned long flags; + spin_lock_irqsave(&modem_port_ptr->write_lock, flags); + + if ((modem_port_ptr->susp_count > 0) && + (modem_port_ptr->resuming != 0)) { + spin_unlock_irqrestore(&modem_port_ptr->write_lock, + flags); + return 0; + } + + wbn = modem_wb_alloc(modem_port_ptr); + if (wbn < 0) { + spin_unlock_irqrestore(&modem_port_ptr->write_lock, + flags); + if (cdma_modem_debug) + dev_info(&port->dev, + "%s: all buffers busy!\n", __func__); + return 0; + } + wb = &modem_port_ptr->wb[wbn]; + + count = min((int)(modem_port_ptr->writesize), count); + + if (cdma_modem_debug) + dev_info(&port->dev, "%s: Get %d bytes.\n", + __func__, count); + memcpy(wb->buf, buf, count); + wb->len = count; + + /* start sending */ + if (modem_port_ptr->susp_count > 0) { + modem_port_ptr->resuming = 1; + modem_port_ptr->delayed_wb = wb; + spin_unlock_irqrestore(&modem_port_ptr->write_lock, + flags); + schedule_work(&modem_port_ptr->wake_and_write); + return count; + } + spin_unlock_irqrestore(&modem_port_ptr->write_lock, flags); + result = modem_start_wb(modem_port_ptr, wb); + if (result >= 0) + result = count; + return result; + } + + /* no bulk out, so return 0 bytes written */ + return 0; +} + +static int modem_startup(struct usb_serial *serial) +{ + struct usb_serial_port *port = serial->port[0]; + struct modem_port *modem_port_ptr = NULL; + struct usb_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct usb_endpoint_descriptor *epread = NULL; + struct usb_endpoint_descriptor *epwrite = NULL; + struct usb_host_interface *iface_desc; + int readsize; + int num_rx_buf; + int i; + + interface = serial->interface; + iface_desc = interface->cur_altsetting; + + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + if (usb_endpoint_is_bulk_in(endpoint)) + epread = endpoint; + if (usb_endpoint_is_bulk_out(endpoint)) + epwrite = endpoint; + } + + if (epread == NULL) { + dev_err(&serial->dev->dev, + "%s: No Bulk In Endpoint for this Interface\n", + __func__); + return -EPERM; + } + if (epwrite == NULL) { + dev_err(&serial->dev->dev, + "%s: No Bulk Out Endpoint for this Interface\n", + __func__); + return -EPERM; + } + + num_rx_buf = AP_NR; + readsize = le16_to_cpu(epread->wMaxPacketSize) * 2; + + /* setup a buffer to store interface data */ + modem_port_ptr = + kzalloc(sizeof(struct modem_port), GFP_KERNEL); + if (modem_port_ptr == NULL) { + dev_err(&serial->dev->dev, + "%s: error -- no memory on start up.\n", + __func__); + return -ENOMEM; + } + + /* init tasklet for rx processing */ + tasklet_init(&modem_port_ptr->urb_task, modem_rx_tasklet, + (unsigned long)modem_port_ptr); + modem_port_ptr->rx_buflimit = num_rx_buf; + modem_port_ptr->rx_endpoint = + usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress); + spin_lock_init(&modem_port_ptr->read_lock); + spin_lock_init(&modem_port_ptr->write_lock); + spin_lock_init(&modem_port_ptr->last_traffic_lock); + + modem_port_ptr->susp_count = 0; + modem_port_ptr->resuming = 0; + modem_port_ptr->port = 0; + modem_port_ptr->last_traffic = 0; + modem_port_ptr->readsize = readsize; + modem_port_ptr->writesize = le16_to_cpu(epwrite->wMaxPacketSize) * 20; + + if (modem_write_buffers_alloc(modem_port_ptr, serial) < 0) { + dev_err(&serial->dev->dev, + "%s: out of memory\n", __func__); + goto alloc_write_buf_fail; + } + + /* allocate multiple receive urb pool */ + for (i = 0; i < num_rx_buf; i++) { + struct ap_ru *rcv = &(modem_port_ptr->ru[i]); + + rcv->urb = usb_alloc_urb(0, GFP_KERNEL); + if (rcv->urb == NULL) { + dev_err(&serial->dev->dev, + "%s: out of memory\n", __func__); + goto alloc_rb_urb_fail; + } + + rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + rcv->instance = modem_port_ptr; + } + + /* allocate multiple receive buffer */ + for (i = 0; i < num_rx_buf; i++) { + struct ap_rb *rb = &(modem_port_ptr->rb[i]); + + rb->base = usb_alloc_coherent(serial->dev, readsize, + GFP_KERNEL, &rb->dma); + if (!rb->base) { + dev_err(&serial->dev->dev, + "%s : out of memory\n", + __func__); + goto alloc_rb_buffer_fail; + } + } + for (i = 0; i < AP_NW; i++) { + struct ap_wb *snd = &(modem_port_ptr->wb[i]); + + snd->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!snd->urb) { + dev_err(&serial->dev->dev, "%s : out of memory " + "(write urbs usb_alloc_urb)\n", __func__); + goto alloc_wb_urb_fail; + } + usb_fill_bulk_urb(snd->urb, serial->dev, + usb_sndbulkpipe(serial->dev, + epwrite->bEndpointAddress), + NULL, modem_port_ptr->writesize, + modem_write_bulk_callback, snd); + snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + snd->instance = modem_port_ptr; + } + + modem_port_ptr->modem_status = 0; + + /* install serial private data */ + usb_set_serial_data(serial, modem_port_ptr); + + return 0; + +alloc_wb_urb_fail: + for (i = 0; i < AP_NW; i++) + usb_free_urb(modem_port_ptr->wb[i].urb); +alloc_rb_buffer_fail: + modem_read_buffers_free(modem_port_ptr, serial); +alloc_rb_urb_fail: + for (i = 0; i < num_rx_buf; i++) + usb_free_urb(modem_port_ptr->ru[i].urb); +alloc_write_buf_fail: + modem_write_buffers_free(modem_port_ptr, serial); + if (modem_port_ptr != NULL) { + kfree(modem_port_ptr); + usb_set_serial_data(serial, NULL); + } + return -ENOMEM; +} + +static void modem_disconnect(struct usb_serial *serial) +{ + struct modem_port *modem_port_ptr = + usb_get_serial_data(serial); + + uint8_t interface_num = + serial->interface->cur_altsetting->desc.bInterfaceNumber; + + if (cdma_modem_debug) + dev_info(&serial->dev->dev, + "%s: Shutdown Interface %d\n", __func__, + interface_num); + + stop_data_traffic(modem_port_ptr); + cancel_work_sync(&modem_port_ptr->wake_and_write); +} + +static void modem_release(struct usb_serial *serial) +{ + struct modem_port *modem_port_ptr = + usb_get_serial_data(serial); + int i; + + modem_write_buffers_free(modem_port_ptr, serial); + modem_read_buffers_free(modem_port_ptr, serial); + + for (i = 0; i < AP_NW; i++) + usb_free_urb(modem_port_ptr->wb[i].urb); + + for (i = 0; i < modem_port_ptr->rx_buflimit; i++) + usb_free_urb(modem_port_ptr->ru[i].urb); + + if (modem_port_ptr) { + /* free private structure allocated for serial device */ + kfree(modem_port_ptr); + usb_set_serial_data(serial, NULL); + } +} + + +static struct usb_driver modem_driver = { + .name = "cdma-modem", + .probe = usb_serial_probe, + .disconnect = usb_serial_disconnect, + .id_table = id_table, + .no_dynamic_id = 1, +#ifdef CONFIG_PM + .supports_autosuspend = 1, +#endif +}; + +static struct usb_serial_driver modem_device = { + .driver = { + .owner = THIS_MODULE, + .name = "mdm6600-modem", + }, + .description = "MDM 6600 Modem Driver", + .usb_driver = &modem_driver, + .id_table = id_table, + .num_ports = 1, + .write = modem_write, + .write_bulk_callback = modem_write_bulk_callback, + .read_int_callback = modem_interrupt_callback, + .tiocmset = modem_tiocmset, + .tiocmget = modem_tiocmget, + .open = modem_open, + .close = modem_close, + .attach = modem_startup, + .disconnect = modem_disconnect, + .release = modem_release, +}; + +static void __exit modem_exit(void) +{ + usb_deregister(&modem_driver); + usb_serial_deregister(&modem_device); +} + +static int __init modem_init(void) +{ + int retval; + + retval = usb_serial_register(&modem_device); + if (retval) + return retval; + retval = usb_register(&modem_driver); + if (retval) + usb_serial_deregister(&modem_device); + return retval; +} + +module_init(modem_init); +module_exit(modem_exit); + +MODULE_DESCRIPTION("USB IPC Driver for MDM 6600"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL");