summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Hsu <mhsu@nvidia.com>2011-07-13 17:11:25 -0700
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:47:41 -0800
commit48d306c94c4d82d6c097d77fc0f2294221e5f137 (patch)
tree7b373653ad9801021756fb42725a54a5766e2026
parentfdd7ffe8e9e5f8fa5f3eb28414096fbddaef8099 (diff)
arm: tegra: comms: Add baseband character driver.
Add raw character device driver for baseband modems. Suitable for downloading modem software image to flashless modem, or sending AT modem commands after modem software is running. Original-Change-Id: I0de126c5eb8700db196e9d6838d65ccce1480c42 Reviewed-on: http://git-master/r/40936 Reviewed-by: Michael Hsu <mhsu@nvidia.com> Tested-by: Michael Hsu <mhsu@nvidia.com> Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com> Rebase-Id: R76607c0d7b47938fa49d4ec57e9962f2d683a5cb
-rw-r--r--drivers/usb/serial/Kconfig13
-rw-r--r--drivers/usb/serial/Makefile1
-rw-r--r--drivers/usb/serial/baseband_usb_chr.c1092
-rw-r--r--drivers/usb/serial/baseband_usb_chr.h106
4 files changed, 1212 insertions, 0 deletions
diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig
index b71e309116a3..f6a54356c29d 100644
--- a/drivers/usb/serial/Kconfig
+++ b/drivers/usb/serial/Kconfig
@@ -661,4 +661,17 @@ config USB_SERIAL_DEBUG
To compile this driver as a module, choose M here: the
module will be called usb-debug.
+config USB_SERIAL_BASEBAND
+ tristate "USB Baseband Driver"
+ help
+ Say Y here if you want to use USB baseband character driver.
+
+ This driver may be used as a raw character device driver for
+ USB modems. For example, downloading modem firmware upon
+ modem boot up (for flashless modems), or sending modem
+ AT commands after modem software is running on device.
+
+ To compile this driver as a module, choose M here: the
+ module will be called baseband_chr.
+
endif # USB_SERIAL
diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile
index 9e536eefb32c..2654c327aaf2 100644
--- a/drivers/usb/serial/Makefile
+++ b/drivers/usb/serial/Makefile
@@ -13,6 +13,7 @@ usbserial-$(CONFIG_USB_EZUSB) += ezusb.o
obj-$(CONFIG_USB_SERIAL_AIRCABLE) += aircable.o
obj-$(CONFIG_USB_SERIAL_ARK3116) += ark3116.o
+obj-$(CONFIG_USB_SERIAL_BASEBAND) += baseband_usb_chr.o
obj-$(CONFIG_USB_SERIAL_BELKIN) += belkin_sa.o
obj-$(CONFIG_USB_SERIAL_CH341) += ch341.o
obj-$(CONFIG_USB_SERIAL_CP210X) += cp210x.o
diff --git a/drivers/usb/serial/baseband_usb_chr.c b/drivers/usb/serial/baseband_usb_chr.c
new file mode 100644
index 000000000000..c0f45dafd4c8
--- /dev/null
+++ b/drivers/usb/serial/baseband_usb_chr.c
@@ -0,0 +1,1092 @@
+/*
+ * baseband_usb_chr.c
+ *
+ * USB character driver to communicate with baseband modems.
+ *
+ * Copyright (c) 2011, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/errno.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+#include <asm/ioctls.h>
+#include <asm/uaccess.h>
+#include "baseband_usb_chr.h"
+
+MODULE_LICENSE("GPL");
+
+unsigned long baseband_usb_chr_vid = 0x058b;
+unsigned long baseband_usb_chr_pid = 0x0041;
+unsigned long baseband_usb_chr_intf = 0x01;
+
+module_param(baseband_usb_chr_vid, ulong, 0644);
+MODULE_PARM_DESC(baseband_usb_chr_vid, "baseband (usb chr) - USB VID");
+module_param(baseband_usb_chr_pid, ulong, 0644);
+MODULE_PARM_DESC(baseband_usb_chr_pid, "baseband (usb chr) - USB PID");
+module_param(baseband_usb_chr_intf, ulong, 0644);
+MODULE_PARM_DESC(baseband_usb_chr_intf, "baseband (usb chr) - USB interface");
+
+static struct baseband_usb *baseband_usb_chr;
+
+/* baseband ipc functions */
+
+static void baseband_ipc_dump(const char *prefix, unsigned long int offset,
+ const void *buf, size_t bufsiz)
+{
+ size_t i;
+
+ for (i = 0; i < bufsiz; i += 16)
+ {
+ pr_debug("%s"
+ "[%lx+%x] %p "
+ "%02x %02x %02x %02x "
+ "%02x %02x %02x %02x "
+ "%02x %02x %02x %02x "
+ "%02x %02x %02x %02x\n",
+ prefix,
+ offset,
+ i,
+ ((const unsigned char *) buf) + i,
+ (i + 0 < bufsiz) ? ((const unsigned char *) buf)[i+0] : 0xff,
+ (i + 1 < bufsiz) ? ((const unsigned char *) buf)[i+1] : 0xff,
+ (i + 2 < bufsiz) ? ((const unsigned char *) buf)[i+2] : 0xff,
+ (i + 3 < bufsiz) ? ((const unsigned char *) buf)[i+3] : 0xff,
+ (i + 4 < bufsiz) ? ((const unsigned char *) buf)[i+4] : 0xff,
+ (i + 5 < bufsiz) ? ((const unsigned char *) buf)[i+5] : 0xff,
+ (i + 6 < bufsiz) ? ((const unsigned char *) buf)[i+6] : 0xff,
+ (i + 7 < bufsiz) ? ((const unsigned char *) buf)[i+7] : 0xff,
+ (i + 8 < bufsiz) ? ((const unsigned char *) buf)[i+8] : 0xff,
+ (i + 9 < bufsiz) ? ((const unsigned char *) buf)[i+9] : 0xff,
+ (i + 10 < bufsiz) ? ((const unsigned char *) buf)[i+10] : 0xff,
+ (i + 11 < bufsiz) ? ((const unsigned char *) buf)[i+11] : 0xff,
+ (i + 12 < bufsiz) ? ((const unsigned char *) buf)[i+12] : 0xff,
+ (i + 13 < bufsiz) ? ((const unsigned char *) buf)[i+13] : 0xff,
+ (i + 14 < bufsiz) ? ((const unsigned char *) buf)[i+14] : 0xff,
+ (i + 15 < bufsiz) ? ((const unsigned char *) buf)[i+15] : 0xff);
+ }
+
+}
+
+static size_t peek_ipc_tx_bufsiz(struct baseband_ipc *ipc,
+ size_t bufsiz)
+{
+ struct baseband_ipc_buf *ipc_buf, *ipc_buf_next;
+ size_t tx_bufsiz;
+
+ pr_debug("peek_ipc_tx_bufsiz\n");
+
+ /* check input */
+ if (!ipc) {
+ pr_err("!ipc\n");
+ return 0;
+ }
+
+ /* acquire tx buffer semaphores */
+ if (down_interruptible(&ipc->buf_sem)) {
+ pr_err("peek_ipc_tx_bufsiz - "
+ "cannot acquire buffer semaphore\n");
+ return -ERESTARTSYS;
+ }
+
+ /* calculate maximum number of tx buffers which can be sent */
+ tx_bufsiz = 0;
+ list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->tx.buf, list)
+ {
+ pr_debug("peek_ipc_tx_bufsiz - "
+ "ipc_buf %p ipc_buf->offset %x ipc_buf->count %x\n",
+ ipc_buf, ipc_buf->offset, ipc_buf->count);
+ if (ipc_buf->count > bufsiz - tx_bufsiz) {
+ break;
+ } else {
+ tx_bufsiz += ipc_buf->count;
+ }
+ }
+
+ /* release tx buffer semaphores */
+ up(&ipc->buf_sem);
+
+ return tx_bufsiz;
+}
+
+static size_t get_ipc_tx_buf(struct baseband_ipc *ipc,
+ void *buf, size_t bufsiz)
+{
+ struct baseband_ipc_buf *ipc_buf, *ipc_buf_next;
+ size_t tx_bufsiz;
+
+ pr_debug("get_ipc_tx_buf\n");
+
+ /* check input */
+ if (!ipc || !buf) {
+ pr_err("!ipc || !buf\n");
+ return 0;
+ }
+ if (!bufsiz) {
+ return 0;
+ }
+
+ /* acquire tx buffer semaphores */
+ if (down_interruptible(&ipc->buf_sem)) {
+ pr_err("get_ipc_tx_buf - "
+ "cannot acquire buffer semaphore\n");
+ return -ERESTARTSYS;
+ }
+
+ /* get tx data from tx linked list */
+ tx_bufsiz = 0;
+ list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->tx.buf, list)
+ {
+ pr_debug("get_ipc_tx_buf - "
+ "ipc_buf %p ipc_buf->offset %x ipc_buf->count %x\n",
+ ipc_buf, ipc_buf->offset, ipc_buf->count);
+ pr_debug("get_ipc_tx_buf - "
+ "ipc_buf->data [0] %x [1] %x [2] %x [3] %x\n",
+ ipc_buf->data[0],
+ ipc_buf->data[1],
+ ipc_buf->data[2],
+ ipc_buf->data[3]);
+ if (ipc_buf->count > bufsiz - tx_bufsiz) {
+ /* copy part of tx buffer */
+ memcpy(buf + tx_bufsiz,
+ ipc_buf->data + ipc_buf->offset,
+ bufsiz - tx_bufsiz);
+ ipc_buf->offset += bufsiz - tx_bufsiz;
+ ipc_buf->count -= bufsiz - tx_bufsiz;
+ tx_bufsiz = bufsiz;
+ } else {
+ /* copy all data from tx buffer */
+ memcpy(buf + tx_bufsiz,
+ ipc_buf->data + ipc_buf->offset,
+ ipc_buf->count);
+ tx_bufsiz += ipc_buf->count;
+ ipc_buf->offset = 0;
+ ipc_buf->count = 0;
+ /* add tx buffer to tx free list */
+ list_move_tail(&ipc_buf->list, &ipc->tx_free.buf);
+ wake_up(&ipc->tx_free.wait);
+ }
+ /* check if done */
+ if (tx_bufsiz == bufsiz)
+ break;
+ }
+
+ /* release tx buffer semaphores */
+ up(&ipc->buf_sem);
+
+ return tx_bufsiz;
+}
+
+static size_t put_ipc_rx_buf(struct baseband_ipc *ipc,
+ const void *buf, size_t bufsiz)
+{
+ struct baseband_ipc_buf *ipc_buf, *ipc_buf_next;
+ size_t rx_bufsiz;
+
+ pr_debug("put_ipc_rx_buf\n");
+
+ /* check input */
+ if (!ipc || !buf) {
+ pr_err("!ipc || !buf\n");
+ return 0;
+ }
+ if (!bufsiz) {
+ return 0;
+ }
+
+ /* acquire rx buffer semaphores */
+retry:
+ if (down_interruptible(&ipc->buf_sem)) {
+ pr_err("put_ipc_rx_buf - "
+ "cannot acquire buffer semaphore\n");
+ return -ERESTARTSYS;
+ }
+
+ /* put rx data in rx linked list */
+ rx_bufsiz = 0;
+ list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->rx_free.buf, list)
+ {
+ pr_debug("put_ipc_rx_buf - "
+ "ipc_buf %p ipc_buf->offset %x ipc_buf->count %x\n",
+ ipc_buf, ipc_buf->offset, ipc_buf->count);
+ if (sizeof(ipc_buf->data) > bufsiz - rx_bufsiz) {
+ /* partially fill rx free buffer */
+ memcpy(ipc_buf->data,
+ buf + rx_bufsiz,
+ bufsiz - rx_bufsiz);
+ ipc_buf->offset = 0;
+ ipc_buf->count = bufsiz - rx_bufsiz;
+ rx_bufsiz = bufsiz;
+ } else {
+ /* fill entire rx free buffer */
+ memcpy(ipc_buf->data,
+ buf + rx_bufsiz,
+ sizeof(ipc_buf->data));
+ ipc_buf->offset = 0;
+ ipc_buf->count = sizeof(ipc_buf->data);
+ rx_bufsiz += sizeof(ipc_buf->data);
+ }
+ /* add filled rx free buffer to rx linked list */
+ list_move_tail(&ipc_buf->list, &ipc->rx.buf);
+ wake_up(&ipc->rx.wait);
+ /* check if done */
+ if (rx_bufsiz == bufsiz)
+ break;
+ }
+
+ /* release rx buffer semaphores */
+ up(&ipc->buf_sem);
+
+ /* wait for rx free buffer available */
+ if (!rx_bufsiz) {
+ if (wait_event_interruptible(ipc->rx_free.wait,
+ !list_empty(&ipc->rx_free.buf))) {
+ pr_err("put_ipc_rx_buf - "
+ "interrupted wait\n");
+ return -ERESTARTSYS;
+ }
+ goto retry;
+ }
+
+ return rx_bufsiz;
+
+}
+
+static ssize_t baseband_ipc_file_read(struct baseband_ipc *ipc,
+ struct file *file, char *buf, size_t count, loff_t *pos)
+{
+ struct baseband_ipc_buf *ipc_buf, *ipc_buf_next;
+ size_t read_count;
+
+ pr_debug("baseband_ipc_file_read\n");
+
+ /* check input */
+ if (!ipc) {
+ pr_err("!ipc\n");
+ return -EIO;
+ }
+
+ /* acquire rx buffer semaphores */
+retry:
+ if (down_interruptible(&ipc->buf_sem)) {
+ pr_err("baseband_ipc_file_read - "
+ "cannot acquire buffer semaphore\n");
+ return -ERESTARTSYS;
+ }
+
+ /* get read data from rx linked list */
+ read_count = 0;
+ list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->rx.buf, list)
+ {
+ pr_debug("baseband_ipc_file_read - "
+ "ipc_buf %p ipc_buf->offset %x ipc_buf->count %x\n",
+ ipc_buf, ipc_buf->offset, ipc_buf->count);
+ pr_debug("baseband_ipc_file_read - "
+ "ipc_buf->data [0] %x [1] %x [2] %x [3] %x\n",
+ ipc_buf->data[0],
+ ipc_buf->data[1],
+ ipc_buf->data[2],
+ ipc_buf->data[3]);
+ if (ipc_buf->count > count - read_count) {
+ /* copy part of rx buffer */
+ if (copy_to_user(buf + read_count,
+ ipc_buf->data + ipc_buf->offset,
+ count - read_count)) {
+ pr_err("copy_to_user failed\n");
+ up(&ipc->buf_sem);
+ return -EFAULT;
+ }
+ ipc_buf->offset += count - read_count;
+ ipc_buf->count -= count - read_count;
+ read_count = count;
+ } else {
+ /* copy all data from rx buffer */
+ if (copy_to_user(buf + read_count,
+ ipc_buf->data + ipc_buf->offset,
+ ipc_buf->count)) {
+ pr_err("copy_to_user failed\n");
+ up(&ipc->buf_sem);
+ return -EFAULT;
+ }
+ read_count += ipc_buf->count;
+ ipc_buf->offset = 0;
+ ipc_buf->count = 0;
+ /* add rx buffer to rx free list */
+ list_move_tail(&ipc_buf->list, &ipc->rx_free.buf);
+ wake_up(&ipc->rx_free.wait);
+ }
+ /* check if done */
+ if (read_count == count)
+ break;
+ }
+
+ /* release rx buffer semaphores */
+ up(&ipc->buf_sem);
+
+ /* wait for rx buffer available */
+ if (!read_count) {
+ if (wait_event_interruptible(ipc->rx.wait,
+ !list_empty(&ipc->rx.buf))) {
+ pr_err("baseband_ipc_file_read - "
+ "interrupted wait\n");
+ return -ERESTARTSYS;
+ }
+ goto retry;
+ }
+
+ return read_count;
+}
+
+static ssize_t baseband_ipc_file_write(struct baseband_ipc *ipc,
+ struct file *file, const char *buf, size_t count, loff_t *pos)
+{
+ struct baseband_ipc_buf *ipc_buf, *ipc_buf_next;
+ size_t write_count;
+
+ pr_debug("baseband_ipc_file_write\n");
+
+ /* check input */
+ if (!ipc) {
+ pr_err("!ipc\n");
+ return -EIO;
+ }
+
+ /* acquire tx buffer semaphores */
+retry:
+ if (down_interruptible(&ipc->buf_sem)) {
+ pr_err("baseband_ipc_file_write - "
+ "cannot acquire buffer semaphore\n");
+ return -ERESTARTSYS;
+ }
+
+ /* put write data in tx linked list */
+ write_count = 0;
+ list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->tx_free.buf, list)
+ {
+ pr_debug("baseband_ipc_file_write - "
+ "ipc_buf %p ipc_buf->offset %x ipc_buf->count %x\n",
+ ipc_buf, ipc_buf->offset, ipc_buf->count);
+ if (sizeof(ipc_buf->data) > count - write_count) {
+ /* partially fill tx free buffer */
+ if (copy_from_user(ipc_buf->data,
+ buf + write_count,
+ count - write_count)) {
+ pr_err("copy_from_user failed\n");
+ up(&ipc->buf_sem);
+ return -EFAULT;
+ }
+ ipc_buf->offset = 0;
+ ipc_buf->count = count - write_count;
+ write_count = count;
+ } else {
+ /* fill entire tx free buffer */
+ if (copy_from_user(ipc_buf->data,
+ buf + write_count,
+ sizeof(ipc_buf->data))) {
+ pr_err("copy_from_user failed\n");
+ up(&ipc->buf_sem);
+ return -EFAULT;
+ }
+ ipc_buf->offset = 0;
+ ipc_buf->count = sizeof(ipc_buf->data);
+ write_count += sizeof(ipc_buf->data);
+ }
+ /* add filled tx free buffer to tx linked list */
+ pr_debug("baseband_ipc_file_write - "
+ "ipc_buf->data [0] %x [1] %x [2] %x [3] %x\n",
+ ipc_buf->data[0],
+ ipc_buf->data[1],
+ ipc_buf->data[2],
+ ipc_buf->data[3]);
+ list_move_tail(&ipc_buf->list, &ipc->tx.buf);
+ wake_up(&ipc->tx.wait);
+ /* check if done */
+ if (write_count == count)
+ break;
+ }
+
+ /* release tx buffer semaphores */
+ up(&ipc->buf_sem);
+
+ /* wait for tx buffer available */
+ if (!write_count) {
+ if (wait_event_interruptible(ipc->tx_free.wait,
+ !list_empty(&ipc->tx_free.buf))) {
+ pr_err("baseband_ipc_file_write - "
+ "interrupted wait\n");
+ return -ERESTARTSYS;
+ }
+ goto retry;
+ }
+
+ /* queue ipc transaction work */
+ queue_work(ipc->workqueue, &ipc->work);
+
+ return write_count;
+}
+
+static void baseband_ipc_close(struct baseband_ipc *ipc)
+{
+ struct baseband_ipc_buf *ipc_buf, *ipc_buf_next;
+
+ pr_debug("baseband_ipc_close {\n");
+
+ /* check input */
+ if (!ipc)
+ return;
+
+ /* destroy work queue */
+ if (ipc->workqueue) {
+ pr_debug("destroy workqueue {\n");
+ destroy_workqueue(ipc->workqueue);
+ ipc->workqueue = (struct workqueue_struct *) 0;
+ pr_debug("destroy workqueue }\n");
+ }
+ memset(&ipc->work, 0, sizeof(ipc->work));
+
+ /* destroy wait queues */
+ memset(&ipc->tx_free.wait, 0, sizeof(ipc->tx_free.wait));
+ memset(&ipc->rx_free.wait, 0, sizeof(ipc->rx_free.wait));
+ memset(&ipc->tx.wait, 0, sizeof(ipc->tx.wait));
+ memset(&ipc->rx.wait, 0, sizeof(ipc->rx.wait));
+
+ /* destroy data buffers */
+ if (ipc->ipc_tx) {
+ kfree(ipc->ipc_tx);
+ ipc->ipc_tx = (unsigned char *) 0;
+ }
+ if (ipc->ipc_rx) {
+ kfree(ipc->ipc_rx);
+ ipc->ipc_rx = (unsigned char *) 0;
+ }
+ list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->tx_free.buf, list)
+ {
+ kfree(ipc_buf);
+ }
+ list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->rx_free.buf, list)
+ {
+ kfree(ipc_buf);
+ }
+ list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->tx.buf, list)
+ {
+ kfree(ipc_buf);
+ }
+ list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->rx.buf, list)
+ {
+ kfree(ipc_buf);
+ }
+
+ /* destroy semaphores */
+ memset(&ipc->buf_sem, 0, sizeof(ipc->buf_sem));
+
+ /* free baseband ipc structure */
+ kfree(ipc);
+
+ pr_debug("baseband_ipc_close }\n");
+}
+
+static struct baseband_ipc *baseband_ipc_open(work_func_t work_func,
+ work_func_t rx_work_func,
+ work_func_t tx_work_func)
+{
+ struct baseband_ipc *ipc;
+ struct baseband_ipc_buf *ipc_buf;
+ int err;
+ int i;
+
+ pr_debug("baseband_ipc_open {\n");
+
+ /* allocate baseband ipc structure */
+ ipc = (struct baseband_ipc *) kzalloc(sizeof(struct baseband_ipc),
+ GFP_KERNEL);
+ if (!ipc)
+ return ((struct baseband_ipc *) 0);
+
+ /* create semaphores */
+ sema_init(&ipc->buf_sem, 1);
+
+ /* create data buffers */
+ INIT_LIST_HEAD(&ipc->rx.buf);
+ INIT_LIST_HEAD(&ipc->tx.buf);
+ INIT_LIST_HEAD(&ipc->rx_free.buf);
+ INIT_LIST_HEAD(&ipc->tx_free.buf);
+ for (i = 0; i < BASEBAND_IPC_NUM_RX_BUF; i++) {
+ ipc_buf = (struct baseband_ipc_buf *)
+ kzalloc(sizeof(struct baseband_ipc_buf), GFP_KERNEL);
+ if (!ipc_buf) {
+ pr_err("cannot allocate baseband ipc rx buffer #%d\n", i);
+ goto error_exit;
+ }
+ pr_debug("baseband_ipc_open - "
+ "rx_free: ipc_buf %p\n",
+ ipc_buf);
+ list_add_tail(&ipc_buf->list, &ipc->rx_free.buf);
+ }
+ for (i = 0; i < BASEBAND_IPC_NUM_TX_BUF; i++) {
+ ipc_buf = (struct baseband_ipc_buf *)
+ kzalloc(sizeof(struct baseband_ipc_buf), GFP_KERNEL);
+ if (!ipc_buf) {
+ pr_err("cannot allocate baseband ipc tx buffer #%d\n", i);
+ goto error_exit;
+ }
+ pr_debug("baseband_ipc_open - "
+ "tx_free: ipc_buf %p\n",
+ ipc_buf);
+ list_add_tail(&ipc_buf->list, &ipc->tx_free.buf);
+ }
+ ipc->ipc_rx = (unsigned char *) 0;
+ ipc->ipc_tx = (unsigned char *) 0;
+
+ /* create wait queues */
+ init_waitqueue_head(&ipc->rx.wait);
+ init_waitqueue_head(&ipc->tx.wait);
+ init_waitqueue_head(&ipc->rx_free.wait);
+ init_waitqueue_head(&ipc->tx_free.wait);
+
+ /* create work queue */
+ ipc->workqueue = create_singlethread_workqueue
+ ("baseband_usb_chr_ipc_workqueue");
+ if (!ipc->workqueue) {
+ pr_err("cannot create workqueue\n");
+ goto error_exit;
+ }
+ if (work_func) {
+ INIT_WORK(&ipc->work, work_func);
+ }
+ if (rx_work_func) {
+ INIT_WORK(&ipc->rx_work, rx_work_func);
+ }
+ if (tx_work_func) {
+ INIT_WORK(&ipc->tx_work, tx_work_func);
+ }
+
+ pr_debug("baseband_ipc_open }\n");
+ return ipc;
+
+error_exit:
+ baseband_ipc_close(ipc);
+ return ((struct baseband_ipc *) 0);
+}
+
+/* usb functions */
+
+static unsigned int g_usb_interface_index;
+static struct usb_interface *g_usb_interface;
+
+static int baseband_usb_driver_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ pr_debug("%s(%d) { intf %p id %p\n", __func__, __LINE__, intf, id);
+
+ pr_debug("intf->cur_altsetting->desc.bInterfaceNumber %02x\n",
+ intf->cur_altsetting->desc.bInterfaceNumber);
+ pr_debug("intf->cur_altsetting->desc.bAlternateSetting %02x\n",
+ intf->cur_altsetting->desc.bAlternateSetting);
+ pr_debug("intf->cur_altsetting->desc.bNumEndpoints %02x\n",
+ intf->cur_altsetting->desc.bNumEndpoints);
+ pr_debug("intf->cur_altsetting->desc.bInterfaceClass %02x\n",
+ intf->cur_altsetting->desc.bInterfaceClass);
+ pr_debug("intf->cur_altsetting->desc.bInterfaceSubClass %02x\n",
+ intf->cur_altsetting->desc.bInterfaceSubClass);
+ pr_debug("intf->cur_altsetting->desc.bInterfaceProtocol %02x\n",
+ intf->cur_altsetting->desc.bInterfaceProtocol);
+ pr_debug("intf->cur_altsetting->desc.iInterface %02x\n",
+ intf->cur_altsetting->desc.iInterface);
+
+ if (g_usb_interface_index !=
+ intf->cur_altsetting->desc.bInterfaceNumber) {
+ pr_debug("%s(%d) } -ENODEV\n", __func__, __LINE__);
+ return -ENODEV;
+ } else {
+ g_usb_interface = intf;
+ }
+
+ pr_debug("%s(%d) }\n", __func__, __LINE__);
+ return 0;
+}
+
+static void baseband_usb_driver_disconnect(struct usb_interface *intf)
+{
+ pr_debug("%s(%d) { intf %p\n", __func__, __LINE__, intf);
+ pr_debug("%s(%d) }\n", __func__, __LINE__);
+}
+
+static char baseband_usb_driver_name[32];
+
+static struct usb_device_id baseband_usb_driver_id_table[2];
+
+static struct usb_driver baseband_usb_driver = {
+ .name = baseband_usb_driver_name,
+ .probe = baseband_usb_driver_probe,
+ .disconnect = baseband_usb_driver_disconnect,
+ .id_table = baseband_usb_driver_id_table,
+};
+
+static void find_usb_pipe(struct baseband_usb *usb)
+{
+ struct usb_device *usbdev = usb->usb.device;
+ struct usb_interface *intf = usb->usb.interface;
+ unsigned char numendpoint = intf->cur_altsetting->desc.bNumEndpoints;
+ struct usb_host_endpoint *endpoint = intf->cur_altsetting->endpoint;
+ unsigned char n;
+
+ for (n = 0; n < numendpoint; n++) {
+ if (usb_endpoint_is_isoc_in(&endpoint[n].desc)) {
+ pr_debug("endpoint[%d] isochronous in\n", n);
+ usb->usb.pipe.isoch.in = usb_rcvisocpipe(usbdev,
+ endpoint[n].desc.bEndpointAddress);
+ } else if (usb_endpoint_is_isoc_out(&endpoint[n].desc)) {
+ pr_debug("endpoint[%d] isochronous out\n", n);
+ usb->usb.pipe.isoch.out = usb_sndisocpipe(usbdev,
+ endpoint[n].desc.bEndpointAddress);
+ } else if (usb_endpoint_is_bulk_in(&endpoint[n].desc)) {
+ pr_debug("endpoint[%d] bulk in\n", n);
+ usb->usb.pipe.bulk.in = usb_rcvbulkpipe(usbdev,
+ endpoint[n].desc.bEndpointAddress);
+ } else if (usb_endpoint_is_bulk_out(&endpoint[n].desc)) {
+ pr_debug("endpoint[%d] bulk out\n", n);
+ usb->usb.pipe.bulk.out = usb_sndbulkpipe(usbdev,
+ endpoint[n].desc.bEndpointAddress);
+ } else if (usb_endpoint_is_int_in(&endpoint[n].desc)) {
+ pr_debug("endpoint[%d] interrupt in\n", n);
+ usb->usb.pipe.interrupt.in = usb_rcvintpipe(usbdev,
+ endpoint[n].desc.bEndpointAddress);
+ } else if (usb_endpoint_is_int_out(&endpoint[n].desc)) {
+ pr_debug("endpoint[%d] interrupt out\n", n);
+ usb->usb.pipe.interrupt.out = usb_sndintpipe(usbdev,
+ endpoint[n].desc.bEndpointAddress);
+ } else {
+ pr_debug("endpoint[%d] skipped\n", n);
+ }
+ }
+}
+
+static void baseband_usb_chr_work(struct work_struct *work)
+{
+ struct baseband_usb *usb = baseband_usb_chr;
+ struct {
+ unsigned char *buf;
+ unsigned int bufsiz_byte;
+ } rx, tx;
+ int ipc_tx_byte;
+ int err;
+
+ pr_debug("baseband_usb_chr_work {\n");
+
+ /* check input */
+ if (!usb || !usb->ipc) {
+ pr_err("baseband_usb_chr_work - "
+ "usb not open\n");
+ return;
+ }
+
+ /* allocate buffers on first transaction (will be freed on close) */
+ if (!usb->ipc->ipc_rx) {
+ usb->ipc->ipc_rx = (unsigned char *) kzalloc(USB_CHR_RX_BUFSIZ, GFP_KERNEL);
+ if (!usb->ipc->ipc_rx) {
+ pr_err("baseband_usb_chr_work - "
+ "cannot allocate usb->ipc->ipc_rx\n");
+ return;
+ }
+ }
+ if (!usb->ipc->ipc_tx) {
+ usb->ipc->ipc_tx = (unsigned char *) kzalloc(USB_CHR_TX_BUFSIZ, GFP_KERNEL);
+ if (!usb->ipc->ipc_tx) {
+ pr_err("baseband_usb_chr_work - "
+ "cannot allocate usb->ipc->ipc_tx\n");
+ return;
+ }
+ }
+
+ /* usb transaction loop */
+ rx.buf = usb->ipc->ipc_rx;
+ tx.buf = usb->ipc->ipc_tx;
+ while ((tx.bufsiz_byte = peek_ipc_tx_bufsiz(usb->ipc, USB_CHR_TX_BUFSIZ))
+ != 0)
+ {
+ get_ipc_tx_buf(usb->ipc, tx.buf, tx.bufsiz_byte);
+ err = usb_bulk_msg(usb->usb.device, usb->usb.pipe.bulk.out,
+ tx.buf, tx.bufsiz_byte, &ipc_tx_byte, USB_CHR_TIMEOUT);
+ if (err < 0) {
+ pr_err("baseband_usb_chr_work - "
+ "usb_bulk_msg err %d\n", err);
+ continue;
+ }
+ if (tx.bufsiz_byte != ipc_tx_byte) {
+ pr_err("tx.bufsiz_byte %d != ipc_tx_byte %d\n", tx.bufsiz_byte, ipc_tx_byte);
+ continue;
+ }
+ }
+
+ pr_debug("baseband_usb_chr_work }\n");
+}
+
+/* usb character file operations */
+
+static atomic_t g_rx_count = ATOMIC_INIT(0);
+
+static int baseband_usb_chr_open(struct inode *inode, struct file *file)
+{
+ pr_debug("baseband_usb_chr_open\n");
+ return 0;
+}
+
+static int baseband_usb_chr_release(struct inode *inode, struct file *file)
+{
+ pr_debug("baseband_usb_chr_release\n");
+ return 0;
+}
+
+static ssize_t baseband_usb_chr_read(struct file *file, char *buf, size_t count, loff_t *pos)
+{
+ ssize_t ret;
+
+ pr_debug("baseband_usb_chr_read\n");
+
+ ret = baseband_ipc_file_read(baseband_usb_chr->ipc, file, buf, count, pos);
+ if (ret > 0) {
+ /* decrement count of available rx bytes */
+ int val = atomic_read(&g_rx_count);
+ pr_debug("baseband_usb_chr_read - read %d unread %d\n", ret, val - ret);
+ atomic_sub(ret, &g_rx_count);
+ }
+ return ret;
+}
+
+static ssize_t baseband_usb_chr_write(struct file *file, const char *buf, size_t count, loff_t *pos)
+{
+ pr_debug("baseband_usb_chr_write\n");
+ return baseband_ipc_file_write(baseband_usb_chr->ipc, file, buf, count, pos);
+}
+
+static long baseband_usb_chr_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ pr_debug("baseband_usb_chr_ioctl\n");
+ switch (cmd) {
+ case TCFLSH:
+ pr_debug("TCFLSH\n");
+ /* flush queued ipc transaction work */
+ flush_workqueue(baseband_usb_chr->ipc->workqueue);
+ return 0;
+ case FIONREAD:
+ pr_debug("FIONREAD\n");
+ /* return count of available rx bytes */
+ {
+ int __user *p = (int __user *) arg;
+ int val = atomic_read(&g_rx_count);
+ if (put_user(val, p))
+ break;
+ }
+ return 0;
+ default:
+ pr_err("unsupported ioctl cmd %x\n", cmd);
+ }
+ return -ENODEV;
+}
+
+static struct file_operations baseband_usb_chr_fops = {
+ open: baseband_usb_chr_open,
+ release: baseband_usb_chr_release,
+ read: baseband_usb_chr_read,
+ write: baseband_usb_chr_write,
+ unlocked_ioctl: baseband_usb_chr_ioctl,
+};
+
+/* usb device driver functions */
+
+static void baseband_usb_close(struct baseband_usb *usb)
+{
+ pr_debug("baseband_usb_close {\n");
+
+ /* check input */
+ if (!usb)
+ return;
+
+ /* close usb driver */
+ if (usb->usb.driver) {
+ pr_debug("close usb driver {\n");
+ usb_deregister(usb->usb.driver);
+ usb->usb.driver = (struct usb_driver *) 0;
+ pr_debug("close usb driver }\n");
+ }
+
+ /* close baseband ipc */
+ if (usb->ipc) {
+ baseband_ipc_close(usb->ipc);
+ usb->ipc = (struct baseband_ipc *) 0;
+ }
+
+ /* free baseband usb structure */
+ kfree(usb);
+
+ pr_debug("baseband_usb_close }\n");
+}
+
+static struct baseband_usb *baseband_usb_open(unsigned int vid,
+ unsigned int pid,
+ unsigned int intf,
+ work_func_t work_func,
+ work_func_t rx_work_func,
+ work_func_t tx_work_func)
+{
+ struct baseband_usb *usb;
+ int err;
+
+ pr_debug("baseband_usb_open {\n");
+
+ /* allocate baseband usb structure */
+ usb = (struct baseband_usb *) kzalloc(sizeof(struct baseband_usb),
+ GFP_KERNEL);
+ if (!usb)
+ return ((struct baseband_usb *) 0);
+
+ /* open baseband ipc */
+ usb->ipc = baseband_ipc_open(work_func,
+ rx_work_func,
+ tx_work_func);
+ if (!usb->ipc) {
+ pr_err("open baseband ipc failed\n");
+ goto error_exit;
+ }
+
+ /* open usb driver */
+ sprintf(baseband_usb_driver_name,
+ "baseband_usb_%x_%x_%x",
+ vid, pid, intf);
+ baseband_usb_driver_id_table[0].match_flags = USB_DEVICE_ID_MATCH_DEVICE;
+ baseband_usb_driver_id_table[0].idVendor = vid;
+ baseband_usb_driver_id_table[0].idProduct = pid;
+ g_usb_interface_index = intf;
+ g_usb_interface = (struct usb_interface *) 0;
+ err = usb_register(&baseband_usb_driver);
+ if (err < 0) {
+ pr_err("cannot open usb driver - err %d\n", err);
+ goto error_exit;
+ }
+ usb->usb.driver = &baseband_usb_driver;
+ if (!g_usb_interface) {
+ pr_err("cannot open usb driver - !g_usb_interface\n");
+ goto error_exit;
+ }
+ usb->usb.device = interface_to_usbdev(g_usb_interface);
+ usb->usb.interface = g_usb_interface;
+ find_usb_pipe(usb);
+ usb->usb.rx_urb = (struct urb *) 0;
+ usb->usb.tx_urb = (struct urb *) 0;
+ g_usb_interface_index = ~0U;
+ g_usb_interface = (struct usb_interface *) 0;
+ pr_debug("usb->usb.driver->name %s\n", usb->usb.driver->name);
+ pr_debug("usb->usb.device %p\n", usb->usb.device);
+ pr_debug("usb->usb.interface %p\n", usb->usb.interface);
+ pr_debug("usb->usb.pipe.isoch.in %x\n", usb->usb.pipe.isoch.in);
+ pr_debug("usb->usb.pipe.isoch.out %x\n", usb->usb.pipe.isoch.out);
+ pr_debug("usb->usb.pipe.bulk.in %x\n", usb->usb.pipe.bulk.in);
+ pr_debug("usb->usb.pipe.bulk.out %x\n", usb->usb.pipe.bulk.out);
+ pr_debug("usb->usb.pipe.interrupt.in %x\n", usb->usb.pipe.interrupt.in);
+ pr_debug("usb->usb.pipe.interrupt.out %x\n", usb->usb.pipe.interrupt.out);
+
+ pr_debug("baseband_usb_open }\n");
+ return usb;
+
+error_exit:
+ baseband_usb_close(usb);
+ return ((struct baseband_usb *) 0);
+}
+
+/* usb rx */
+
+static int baseband_usb_chr_rx_urb_submit(struct baseband_usb *usb);
+static void baseband_usb_chr_rx_urb_comp(struct urb *urb);
+static void baseband_usb_chr_rx_urb_comp_work(struct work_struct *work);
+
+static int baseband_usb_chr_rx_urb_submit(struct baseband_usb *usb)
+{
+ struct urb *urb;
+ void *buf;
+ int err;
+
+ pr_debug("baseband_usb_chr_rx_urb_submit { usb %p\n", usb);
+
+ /* check input */
+ if (usb->usb.rx_urb) {
+ pr_err("previous urb still active\n");
+ return -1;
+ }
+
+ /* allocate rx urb */
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!urb) {
+ pr_err("usb_alloc_urb() failed\n");
+ return -ENOMEM;
+ }
+ buf = kzalloc(USB_CHR_RX_BUFSIZ, GFP_ATOMIC);
+ if (!buf) {
+ pr_err("usb buffer kzalloc() failed\n");
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+ usb_fill_bulk_urb(urb, usb->usb.device, usb->usb.pipe.bulk.in,
+ buf, USB_CHR_RX_BUFSIZ,
+ baseband_usb_chr_rx_urb_comp,
+ usb);
+ urb->transfer_flags = 0;
+
+ /* submit rx urb */
+ usb->usb.rx_urb = urb;
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err < 0) {
+ pr_err("usb_submit_urb() failed - err %d\n", err);
+ usb->usb.rx_urb = (struct urb *) 0;
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ return err;
+ }
+
+ pr_debug("baseband_usb_chr_rx_urb_submit }\n");
+ return err;
+}
+
+static void baseband_usb_chr_rx_urb_comp(struct urb *urb)
+{
+ struct baseband_usb *usb = (struct baseband_usb *) urb->context;
+
+ pr_debug("baseband_usb_chr_rx_urb_comp { urb %p\n", urb);
+
+ /* queue rx urb completion work */
+ queue_work(usb->ipc->workqueue, &usb->ipc->rx_work);
+
+ pr_debug("baseband_usb_chr_rx_urb_comp }\n");
+}
+
+static void baseband_usb_chr_rx_urb_comp_work(struct work_struct *work)
+{
+ struct baseband_usb *usb = baseband_usb_chr;
+ struct urb *urb = usb->usb.rx_urb;
+ size_t len;
+
+ pr_debug("baseband_usb_chr_rx_urb_comp_work { work %p\n", work);
+
+ /* put rx urb data in rx buffer */
+ if (urb->actual_length) {
+ pr_debug("baseband_usb_chr_rx_urb_comp_work - "
+ "urb->actual_length %d\n", urb->actual_length);
+ len = put_ipc_rx_buf(usb->ipc,
+ urb->transfer_buffer, urb->actual_length);
+ baseband_ipc_dump("baseband_usb_chr_rx_urb_comp_work"
+ " - rx buf ", 0,
+ urb->transfer_buffer, len > 16 ? 16 : len);
+ if (len != urb->actual_length) {
+ pr_err("baseband_usb_chr_rx_urb_comp_work - "
+ "put_ipx_rx_buf() only put %d/%d bytes\n",
+ len, urb->actual_length);
+ }
+ /* increment count of available rx bytes */
+ atomic_add(len, &g_rx_count);
+ }
+
+ /* free rx urb */
+ if (urb->transfer_buffer) {
+ kfree(urb->transfer_buffer);
+ urb->transfer_buffer = (void *) 0;
+ }
+ usb_free_urb(urb);
+ usb->usb.rx_urb = (struct urb *) 0;
+
+ /* submit next rx urb */
+ baseband_usb_chr_rx_urb_submit(usb);
+
+ pr_debug("baseband_usb_chr_rx_urb_comp_work }\n");
+}
+
+/* module init / exit functions */
+
+static int baseband_usb_chr_init(void)
+{
+ int err;
+
+ pr_debug("baseband_usb_chr_init {\n");
+
+ /* open baseband usb */
+ baseband_usb_chr = baseband_usb_open
+ (baseband_usb_chr_vid,
+ baseband_usb_chr_pid,
+ baseband_usb_chr_intf,
+ baseband_usb_chr_work,
+ baseband_usb_chr_rx_urb_comp_work,
+ (work_func_t) 0);
+ if (!baseband_usb_chr) {
+ pr_err("cannot open baseband usb chr\n");
+ err = -1;
+ goto err1;
+ }
+
+ /* register character device */
+ err = register_chrdev(BASEBAND_USB_CHR_DEV_MAJOR,
+ BASEBAND_USB_CHR_DEV_NAME,
+ &baseband_usb_chr_fops);
+ if (err < 0) {
+ pr_err("cannot register character device - %d\n", err);
+ goto err2;
+ }
+ pr_debug("registered baseband usb character device - major %d\n",
+ BASEBAND_USB_CHR_DEV_MAJOR);
+
+ /* start usb rx */
+ err = baseband_usb_chr_rx_urb_submit(baseband_usb_chr);
+ if (err < 0) {
+ pr_err("submit rx failed - err %d\n", err);
+ goto err3;
+ }
+
+ pr_debug("baseband_usb_chr_init }\n");
+ return 0;
+err3: unregister_chrdev(BASEBAND_USB_CHR_DEV_MAJOR,
+ BASEBAND_USB_CHR_DEV_NAME);
+err2: baseband_usb_close(baseband_usb_chr);
+ baseband_usb_chr = (struct baseband_usb *) 0;
+err1: return err;
+}
+
+static void baseband_usb_chr_exit(void)
+{
+ pr_debug("baseband_usb_chr_exit {\n");
+
+ /* unregister character device */
+ unregister_chrdev(BASEBAND_USB_CHR_DEV_MAJOR,
+ BASEBAND_USB_CHR_DEV_NAME);
+
+ /* close baseband usb */
+ if (baseband_usb_chr) {
+ baseband_usb_close(baseband_usb_chr);
+ baseband_usb_chr = (struct baseband_usb *) 0;
+ }
+
+ pr_debug("baseband_usb_chr_exit }\n");
+}
+
+module_init(baseband_usb_chr_init)
+module_exit(baseband_usb_chr_exit)
+
diff --git a/drivers/usb/serial/baseband_usb_chr.h b/drivers/usb/serial/baseband_usb_chr.h
new file mode 100644
index 000000000000..7935e795a54d
--- /dev/null
+++ b/drivers/usb/serial/baseband_usb_chr.h
@@ -0,0 +1,106 @@
+/*
+ * baseband_usb_chr.h
+ *
+ * USB character driver to communicate with baseband modems.
+ *
+ * Copyright (c) 2011, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __BASEBAND_USB_CHR_H__
+#define __BASEBAND_USB_CHR_H__
+
+#define BASEBAND_USB_CHR_DEV_NAME "baseband_usb_chr"
+#define BASEBAND_USB_CHR_DEV_MAJOR 66
+
+#ifndef USB_CHR_RX_BUFSIZ
+#define USB_CHR_RX_BUFSIZ (128*1024)
+#endif /* USB_CHR_RX_BUFSIZ */
+
+#ifndef USB_CHR_TX_BUFSIZ
+#define USB_CHR_TX_BUFSIZ (128*1024)
+#endif /* USB_CHR_TX_BUFSIZ */
+
+#ifndef USB_CHR_TIMEOUT
+#define USB_CHR_TIMEOUT 5000 /* ms */
+#endif /* USB_CHR_TIMEOUT */
+
+#ifndef BASEBAND_IPC_NUM_RX_BUF
+#define BASEBAND_IPC_NUM_RX_BUF 32
+#endif /* BASEBAND_IPC_NUM_RX_BUF */
+
+#ifndef BASEBAND_IPC_NUM_TX_BUF
+#define BASEBAND_IPC_NUM_TX_BUF 16
+#endif /* BASEBAND_IPC_NUM_TX_BUF */
+
+#ifndef BASEBAND_IPC_BUFSIZ
+#define BASEBAND_IPC_BUFSIZ 65536
+#endif /* BASEBAND_IPC_BUFSIZ */
+
+struct baseband_ipc {
+ /* rx / tx data */
+ struct semaphore buf_sem;
+ struct {
+ /* linked list of data buffers */
+ struct list_head buf;
+ /* wait queue of processes trying to access data buffers */
+ wait_queue_head_t wait;
+ } rx, tx, rx_free, tx_free;
+ unsigned char *ipc_rx;
+ unsigned char *ipc_tx;
+ /* work queue
+ * - queued per ipc transaction
+ * - initiated by either:
+ * = interrupt on gpio line (rx data available)
+ * = tx data packet being added to tx linked list
+ */
+ struct workqueue_struct *workqueue;
+ struct work_struct work;
+ struct work_struct rx_work;
+ struct work_struct tx_work;
+};
+
+struct baseband_ipc_buf {
+ struct list_head list;
+ /* data buffer */
+ unsigned char data[BASEBAND_IPC_BUFSIZ];
+ /* offset of first data byte */
+ size_t offset;
+ /* number of valid data bytes */
+ size_t count;
+};
+
+struct baseband_usb {
+ struct baseband_ipc *ipc;
+ struct {
+ struct usb_driver *driver;
+ struct usb_device *device;
+ struct usb_interface *interface;
+ struct {
+ struct {
+ unsigned int in;
+ unsigned int out;
+ } isoch, bulk, interrupt;
+ } pipe;
+ /* currently active rx urb */
+ struct urb *rx_urb;
+ /* currently active tx urb */
+ struct urb *tx_urb;
+ } usb;
+};
+
+#endif /* __BASEBAND_USB_CHR_H__ */
+