diff options
Diffstat (limited to 'kernel/drivers/staging/gdm724x/gdm_usb.c')
-rw-r--r-- | kernel/drivers/staging/gdm724x/gdm_usb.c | 1041 |
1 files changed, 1041 insertions, 0 deletions
diff --git a/kernel/drivers/staging/gdm724x/gdm_usb.c b/kernel/drivers/staging/gdm724x/gdm_usb.c new file mode 100644 index 000000000..ed1a12f50 --- /dev/null +++ b/kernel/drivers/staging/gdm724x/gdm_usb.c @@ -0,0 +1,1041 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/usb.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/usb/cdc.h> +#include <linux/wait.h> +#include <linux/if_ether.h> +#include <linux/pm_runtime.h> + +#include "gdm_usb.h" +#include "gdm_lte.h" +#include "hci.h" +#include "hci_packet.h" +#include "gdm_endian.h" + +#define USB_DEVICE_CDC_DATA(vid, pid) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_INT_CLASS | \ + USB_DEVICE_ID_MATCH_INT_SUBCLASS,\ + .idVendor = vid,\ + .idProduct = pid,\ + .bInterfaceClass = USB_CLASS_COMM,\ + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET + +#define USB_DEVICE_MASS_DATA(vid, pid) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_INT_INFO,\ + .idVendor = vid,\ + .idProduct = pid,\ + .bInterfaceSubClass = USB_SC_SCSI, \ + .bInterfaceClass = USB_CLASS_MASS_STORAGE,\ + .bInterfaceProtocol = USB_PR_BULK + +static const struct usb_device_id id_table[] = { + { USB_DEVICE_CDC_DATA(VID_GCT, PID_GDM7240) }, /* GCT GDM7240 */ + { USB_DEVICE_CDC_DATA(VID_GCT, PID_GDM7243) }, /* GCT GDM7243 */ + { } +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +static struct workqueue_struct *usb_tx_wq; +static struct workqueue_struct *usb_rx_wq; + +static void do_tx(struct work_struct *work); +static void do_rx(struct work_struct *work); + +static int gdm_usb_recv(void *priv_dev, + int (*cb)(void *cb_data, + void *data, int len, int context), + void *cb_data, + int context); + +static int request_mac_address(struct lte_udev *udev) +{ + u8 buf[16] = {0,}; + struct hci_packet *hci = (struct hci_packet *)buf; + struct usb_device *usbdev = udev->usbdev; + int actual; + int ret = -1; + + hci->cmd_evt = gdm_cpu_to_dev16(&udev->gdm_ed, LTE_GET_INFORMATION); + hci->len = gdm_cpu_to_dev16(&udev->gdm_ed, 1); + hci->data[0] = MAC_ADDRESS; + + ret = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 2), buf, 5, + &actual, 1000); + + udev->request_mac_addr = 1; + + return ret; +} + +static struct usb_tx *alloc_tx_struct(int len) +{ + struct usb_tx *t = NULL; + int ret = 0; + + t = kzalloc(sizeof(struct usb_tx), GFP_ATOMIC); + if (!t) { + ret = -ENOMEM; + goto out; + } + + t->urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!(len % 512)) + len++; + + t->buf = kmalloc(len, GFP_ATOMIC); + if (!t->urb || !t->buf) { + ret = -ENOMEM; + goto out; + } + +out: + if (ret < 0) { + if (t) { + usb_free_urb(t->urb); + kfree(t->buf); + kfree(t); + } + return NULL; + } + + return t; +} + +static struct usb_tx_sdu *alloc_tx_sdu_struct(void) +{ + struct usb_tx_sdu *t_sdu; + + t_sdu = kzalloc(sizeof(struct usb_tx_sdu), GFP_KERNEL); + if (!t_sdu) + return NULL; + + t_sdu->buf = kmalloc(SDU_BUF_SIZE, GFP_KERNEL); + if (!t_sdu->buf) { + kfree(t_sdu); + return NULL; + } + + return t_sdu; +} + +static void free_tx_struct(struct usb_tx *t) +{ + if (t) { + usb_free_urb(t->urb); + kfree(t->buf); + kfree(t); + } +} + +static void free_tx_sdu_struct(struct usb_tx_sdu *t_sdu) +{ + if (t_sdu) { + kfree(t_sdu->buf); + kfree(t_sdu); + } +} + +static struct usb_tx_sdu *get_tx_sdu_struct(struct tx_cxt *tx, int *no_spc) +{ + struct usb_tx_sdu *t_sdu; + + if (list_empty(&tx->free_list)) + return NULL; + + t_sdu = list_entry(tx->free_list.next, struct usb_tx_sdu, list); + list_del(&t_sdu->list); + + tx->avail_count--; + + *no_spc = list_empty(&tx->free_list) ? 1 : 0; + + return t_sdu; +} + +static void put_tx_struct(struct tx_cxt *tx, struct usb_tx_sdu *t_sdu) +{ + list_add_tail(&t_sdu->list, &tx->free_list); + tx->avail_count++; +} + +static struct usb_rx *alloc_rx_struct(void) +{ + struct usb_rx *r = NULL; + int ret = 0; + + r = kmalloc(sizeof(struct usb_rx), GFP_KERNEL); + if (!r) { + ret = -ENOMEM; + goto out; + } + + r->urb = usb_alloc_urb(0, GFP_KERNEL); + r->buf = kmalloc(RX_BUF_SIZE, GFP_KERNEL); + if (!r->urb || !r->buf) { + ret = -ENOMEM; + goto out; + } +out: + + if (ret < 0) { + if (r) { + usb_free_urb(r->urb); + kfree(r->buf); + kfree(r); + } + return NULL; + } + + return r; +} + +static void free_rx_struct(struct usb_rx *r) +{ + if (r) { + usb_free_urb(r->urb); + kfree(r->buf); + kfree(r); + } +} + +static struct usb_rx *get_rx_struct(struct rx_cxt *rx, int *no_spc) +{ + struct usb_rx *r; + unsigned long flags; + + spin_lock_irqsave(&rx->rx_lock, flags); + + if (list_empty(&rx->free_list)) { + spin_unlock_irqrestore(&rx->rx_lock, flags); + return NULL; + } + + r = list_entry(rx->free_list.next, struct usb_rx, free_list); + list_del(&r->free_list); + + rx->avail_count--; + + *no_spc = list_empty(&rx->free_list) ? 1 : 0; + + spin_unlock_irqrestore(&rx->rx_lock, flags); + + return r; +} + +static void put_rx_struct(struct rx_cxt *rx, struct usb_rx *r) +{ + unsigned long flags; + + spin_lock_irqsave(&rx->rx_lock, flags); + + list_add_tail(&r->free_list, &rx->free_list); + rx->avail_count++; + + spin_unlock_irqrestore(&rx->rx_lock, flags); +} + +static void release_usb(struct lte_udev *udev) +{ + struct rx_cxt *rx = &udev->rx; + struct tx_cxt *tx = &udev->tx; + struct usb_tx *t, *t_next; + struct usb_rx *r, *r_next; + struct usb_tx_sdu *t_sdu, *t_sdu_next; + unsigned long flags; + + spin_lock_irqsave(&tx->lock, flags); + list_for_each_entry_safe(t_sdu, t_sdu_next, &tx->sdu_list, list) { + list_del(&t_sdu->list); + free_tx_sdu_struct(t_sdu); + } + + list_for_each_entry_safe(t, t_next, &tx->hci_list, list) { + list_del(&t->list); + free_tx_struct(t); + } + + list_for_each_entry_safe(t_sdu, t_sdu_next, &tx->free_list, list) { + list_del(&t_sdu->list); + free_tx_sdu_struct(t_sdu); + } + spin_unlock_irqrestore(&tx->lock, flags); + + spin_lock_irqsave(&rx->submit_lock, flags); + list_for_each_entry_safe(r, r_next, &rx->rx_submit_list, + rx_submit_list) { + spin_unlock_irqrestore(&rx->submit_lock, flags); + usb_kill_urb(r->urb); + spin_lock_irqsave(&rx->submit_lock, flags); + } + spin_unlock_irqrestore(&rx->submit_lock, flags); + + spin_lock_irqsave(&rx->rx_lock, flags); + list_for_each_entry_safe(r, r_next, &rx->free_list, free_list) { + list_del(&r->free_list); + free_rx_struct(r); + } + spin_unlock_irqrestore(&rx->rx_lock, flags); + + spin_lock_irqsave(&rx->to_host_lock, flags); + list_for_each_entry_safe(r, r_next, &rx->to_host_list, to_host_list) { + if (r->index == (void *)udev) { + list_del(&r->to_host_list); + free_rx_struct(r); + } + } + spin_unlock_irqrestore(&rx->to_host_lock, flags); +} + +static int init_usb(struct lte_udev *udev) +{ + int ret = 0; + int i; + struct tx_cxt *tx = &udev->tx; + struct rx_cxt *rx = &udev->rx; + struct usb_tx_sdu *t_sdu = NULL; + struct usb_rx *r = NULL; + + udev->send_complete = 1; + udev->tx_stop = 0; + udev->request_mac_addr = 0; + udev->usb_state = PM_NORMAL; + + INIT_LIST_HEAD(&tx->sdu_list); + INIT_LIST_HEAD(&tx->hci_list); + INIT_LIST_HEAD(&tx->free_list); + INIT_LIST_HEAD(&rx->rx_submit_list); + INIT_LIST_HEAD(&rx->free_list); + INIT_LIST_HEAD(&rx->to_host_list); + spin_lock_init(&tx->lock); + spin_lock_init(&rx->rx_lock); + spin_lock_init(&rx->submit_lock); + spin_lock_init(&rx->to_host_lock); + + tx->avail_count = 0; + rx->avail_count = 0; + + udev->rx_cb = NULL; + + for (i = 0; i < MAX_NUM_SDU_BUF; i++) { + t_sdu = alloc_tx_sdu_struct(); + if (t_sdu == NULL) { + ret = -ENOMEM; + goto fail; + } + + list_add(&t_sdu->list, &tx->free_list); + tx->avail_count++; + } + + for (i = 0; i < MAX_RX_SUBMIT_COUNT*2; i++) { + r = alloc_rx_struct(); + if (r == NULL) { + ret = -ENOMEM; + goto fail; + } + + list_add(&r->free_list, &rx->free_list); + rx->avail_count++; + } + INIT_DELAYED_WORK(&udev->work_tx, do_tx); + INIT_DELAYED_WORK(&udev->work_rx, do_rx); + return 0; +fail: + release_usb(udev); + return ret; +} + +static int set_mac_address(u8 *data, void *arg) +{ + struct phy_dev *phy_dev = (struct phy_dev *)arg; + struct lte_udev *udev = phy_dev->priv_dev; + struct tlv *tlv = (struct tlv *)data; + u8 mac_address[ETH_ALEN] = {0, }; + + if (tlv->type == MAC_ADDRESS && udev->request_mac_addr) { + memcpy(mac_address, tlv->data, tlv->len); + + if (register_lte_device(phy_dev, + &udev->intf->dev, mac_address) < 0) + pr_err("register lte device failed\n"); + + udev->request_mac_addr = 0; + + return 1; + } + + return 0; +} + +static void do_rx(struct work_struct *work) +{ + struct lte_udev *udev = + container_of(work, struct lte_udev, work_rx.work); + struct rx_cxt *rx = &udev->rx; + struct usb_rx *r; + struct hci_packet *hci; + struct phy_dev *phy_dev; + u16 cmd_evt; + int ret; + unsigned long flags; + + while (1) { + spin_lock_irqsave(&rx->to_host_lock, flags); + if (list_empty(&rx->to_host_list)) { + spin_unlock_irqrestore(&rx->to_host_lock, flags); + break; + } + r = list_entry(rx->to_host_list.next, + struct usb_rx, to_host_list); + list_del(&r->to_host_list); + spin_unlock_irqrestore(&rx->to_host_lock, flags); + + phy_dev = (struct phy_dev *)r->cb_data; + udev = (struct lte_udev *)phy_dev->priv_dev; + hci = (struct hci_packet *)r->buf; + cmd_evt = gdm_dev16_to_cpu(&udev->gdm_ed, hci->cmd_evt); + + switch (cmd_evt) { + case LTE_GET_INFORMATION_RESULT: + if (set_mac_address(hci->data, r->cb_data) == 0) { + ret = r->callback(r->cb_data, + r->buf, + r->urb->actual_length, + KERNEL_THREAD); + } + break; + + default: + if (r->callback) { + ret = r->callback(r->cb_data, + r->buf, + r->urb->actual_length, + KERNEL_THREAD); + + if (ret == -EAGAIN) + pr_err("failed to send received data\n"); + } + break; + } + + put_rx_struct(rx, r); + + gdm_usb_recv(udev, + r->callback, + r->cb_data, + USB_COMPLETE); + } +} + +static void remove_rx_submit_list(struct usb_rx *r, struct rx_cxt *rx) +{ + unsigned long flags; + struct usb_rx *r_remove, *r_remove_next; + + spin_lock_irqsave(&rx->submit_lock, flags); + list_for_each_entry_safe(r_remove, r_remove_next, + &rx->rx_submit_list, rx_submit_list) { + if (r == r_remove) { + list_del(&r->rx_submit_list); + break; + } + } + spin_unlock_irqrestore(&rx->submit_lock, flags); +} + +static void gdm_usb_rcv_complete(struct urb *urb) +{ + struct usb_rx *r = urb->context; + struct rx_cxt *rx = r->rx; + unsigned long flags; + struct lte_udev *udev = container_of(r->rx, struct lte_udev, rx); + struct usb_device *usbdev = udev->usbdev; + + remove_rx_submit_list(r, rx); + + if (!urb->status && r->callback) { + spin_lock_irqsave(&rx->to_host_lock, flags); + list_add_tail(&r->to_host_list, &rx->to_host_list); + queue_work(usb_rx_wq, &udev->work_rx.work); + spin_unlock_irqrestore(&rx->to_host_lock, flags); + } else { + if (urb->status && udev->usb_state == PM_NORMAL) + dev_err(&urb->dev->dev, "%s: urb status error %d\n", + __func__, urb->status); + + put_rx_struct(rx, r); + } + + usb_mark_last_busy(usbdev); +} + +static int gdm_usb_recv(void *priv_dev, + int (*cb)(void *cb_data, + void *data, int len, int context), + void *cb_data, + int context) +{ + struct lte_udev *udev = priv_dev; + struct usb_device *usbdev = udev->usbdev; + struct rx_cxt *rx = &udev->rx; + struct usb_rx *r; + int no_spc; + int ret; + unsigned long flags; + + if (!udev->usbdev) { + pr_err("invalid device\n"); + return -ENODEV; + } + + r = get_rx_struct(rx, &no_spc); + if (!r) { + pr_err("Out of Memory\n"); + return -ENOMEM; + } + + udev->rx_cb = cb; + r->callback = cb; + r->cb_data = cb_data; + r->index = (void *)udev; + r->rx = rx; + + usb_fill_bulk_urb(r->urb, + usbdev, + usb_rcvbulkpipe(usbdev, 0x83), + r->buf, + RX_BUF_SIZE, + gdm_usb_rcv_complete, + r); + + spin_lock_irqsave(&rx->submit_lock, flags); + list_add_tail(&r->rx_submit_list, &rx->rx_submit_list); + spin_unlock_irqrestore(&rx->submit_lock, flags); + + if (context == KERNEL_THREAD) + ret = usb_submit_urb(r->urb, GFP_KERNEL); + else + ret = usb_submit_urb(r->urb, GFP_ATOMIC); + + if (ret) { + spin_lock_irqsave(&rx->submit_lock, flags); + list_del(&r->rx_submit_list); + spin_unlock_irqrestore(&rx->submit_lock, flags); + + pr_err("usb_submit_urb failed (%p)\n", r); + put_rx_struct(rx, r); + } + + return ret; +} + +static void gdm_usb_send_complete(struct urb *urb) +{ + struct usb_tx *t = urb->context; + struct tx_cxt *tx = t->tx; + struct lte_udev *udev = container_of(tx, struct lte_udev, tx); + unsigned long flags; + + if (urb->status == -ECONNRESET) { + dev_info(&urb->dev->dev, "CONNRESET\n"); + return; + } + + if (t->callback) + t->callback(t->cb_data); + + free_tx_struct(t); + + spin_lock_irqsave(&tx->lock, flags); + udev->send_complete = 1; + queue_work(usb_tx_wq, &udev->work_tx.work); + spin_unlock_irqrestore(&tx->lock, flags); +} + +static int send_tx_packet(struct usb_device *usbdev, struct usb_tx *t, u32 len) +{ + int ret = 0; + + if (!(len%512)) + len++; + + usb_fill_bulk_urb(t->urb, + usbdev, + usb_sndbulkpipe(usbdev, 2), + t->buf, + len, + gdm_usb_send_complete, + t); + + ret = usb_submit_urb(t->urb, GFP_ATOMIC); + + if (ret) + dev_err(&usbdev->dev, "usb_submit_urb failed: %d\n", + ret); + + usb_mark_last_busy(usbdev); + + return ret; +} + +static u32 packet_aggregation(struct lte_udev *udev, u8 *send_buf) +{ + struct tx_cxt *tx = &udev->tx; + struct usb_tx_sdu *t_sdu = NULL; + struct multi_sdu *multi_sdu = (struct multi_sdu *)send_buf; + u16 send_len = 0; + u16 num_packet = 0; + unsigned long flags; + + multi_sdu->cmd_evt = gdm_cpu_to_dev16(&udev->gdm_ed, LTE_TX_MULTI_SDU); + + while (num_packet < MAX_PACKET_IN_MULTI_SDU) { + spin_lock_irqsave(&tx->lock, flags); + if (list_empty(&tx->sdu_list)) { + spin_unlock_irqrestore(&tx->lock, flags); + break; + } + + t_sdu = list_entry(tx->sdu_list.next, struct usb_tx_sdu, list); + if (send_len + t_sdu->len > MAX_SDU_SIZE) { + spin_unlock_irqrestore(&tx->lock, flags); + break; + } + + list_del(&t_sdu->list); + spin_unlock_irqrestore(&tx->lock, flags); + + memcpy(multi_sdu->data + send_len, t_sdu->buf, t_sdu->len); + + send_len += (t_sdu->len + 3) & 0xfffc; + num_packet++; + + if (tx->avail_count > 10) + t_sdu->callback(t_sdu->cb_data); + + spin_lock_irqsave(&tx->lock, flags); + put_tx_struct(tx, t_sdu); + spin_unlock_irqrestore(&tx->lock, flags); + } + + multi_sdu->len = gdm_cpu_to_dev16(&udev->gdm_ed, send_len); + multi_sdu->num_packet = gdm_cpu_to_dev16(&udev->gdm_ed, num_packet); + + return send_len + offsetof(struct multi_sdu, data); +} + +static void do_tx(struct work_struct *work) +{ + struct lte_udev *udev = + container_of(work, struct lte_udev, work_tx.work); + struct usb_device *usbdev = udev->usbdev; + struct tx_cxt *tx = &udev->tx; + struct usb_tx *t = NULL; + int is_send = 0; + u32 len = 0; + unsigned long flags; + + if (!usb_autopm_get_interface(udev->intf)) + usb_autopm_put_interface(udev->intf); + + if (udev->usb_state == PM_SUSPEND) + return; + + spin_lock_irqsave(&tx->lock, flags); + if (!udev->send_complete) { + spin_unlock_irqrestore(&tx->lock, flags); + return; + } + udev->send_complete = 0; + + if (!list_empty(&tx->hci_list)) { + t = list_entry(tx->hci_list.next, struct usb_tx, list); + list_del(&t->list); + len = t->len; + t->is_sdu = 0; + is_send = 1; + } else if (!list_empty(&tx->sdu_list)) { + if (udev->tx_stop) { + udev->send_complete = 1; + spin_unlock_irqrestore(&tx->lock, flags); + return; + } + + t = alloc_tx_struct(TX_BUF_SIZE); + if (t == NULL) { + spin_unlock_irqrestore(&tx->lock, flags); + return; + } + t->callback = NULL; + t->tx = tx; + t->is_sdu = 1; + is_send = 1; + } + + if (!is_send) { + udev->send_complete = 1; + spin_unlock_irqrestore(&tx->lock, flags); + return; + } + spin_unlock_irqrestore(&tx->lock, flags); + + if (t->is_sdu) + len = packet_aggregation(udev, t->buf); + + if (send_tx_packet(usbdev, t, len)) { + pr_err("send_tx_packet failed\n"); + t->callback = NULL; + gdm_usb_send_complete(t->urb); + } +} + +#define SDU_PARAM_LEN 12 +static int gdm_usb_sdu_send(void *priv_dev, void *data, int len, + unsigned int dftEpsId, unsigned int epsId, + void (*cb)(void *data), void *cb_data, + int dev_idx, int nic_type) +{ + struct lte_udev *udev = priv_dev; + struct tx_cxt *tx = &udev->tx; + struct usb_tx_sdu *t_sdu; + struct sdu *sdu = NULL; + unsigned long flags; + int no_spc = 0; + u16 send_len; + + if (!udev->usbdev) { + pr_err("sdu send - invalid device\n"); + return TX_NO_DEV; + } + + spin_lock_irqsave(&tx->lock, flags); + t_sdu = get_tx_sdu_struct(tx, &no_spc); + spin_unlock_irqrestore(&tx->lock, flags); + + if (t_sdu == NULL) { + pr_err("sdu send - free list empty\n"); + return TX_NO_SPC; + } + + sdu = (struct sdu *)t_sdu->buf; + sdu->cmd_evt = gdm_cpu_to_dev16(&udev->gdm_ed, LTE_TX_SDU); + if (nic_type == NIC_TYPE_ARP) { + send_len = len + SDU_PARAM_LEN; + memcpy(sdu->data, data, len); + } else { + send_len = len - ETH_HLEN; + send_len += SDU_PARAM_LEN; + memcpy(sdu->data, data+ETH_HLEN, len-ETH_HLEN); + } + + sdu->len = gdm_cpu_to_dev16(&udev->gdm_ed, send_len); + sdu->dftEpsId = gdm_cpu_to_dev32(&udev->gdm_ed, dftEpsId); + sdu->bearer_ID = gdm_cpu_to_dev32(&udev->gdm_ed, epsId); + sdu->nic_type = gdm_cpu_to_dev32(&udev->gdm_ed, nic_type); + + t_sdu->len = send_len + HCI_HEADER_SIZE; + t_sdu->callback = cb; + t_sdu->cb_data = cb_data; + + spin_lock_irqsave(&tx->lock, flags); + list_add_tail(&t_sdu->list, &tx->sdu_list); + queue_work(usb_tx_wq, &udev->work_tx.work); + spin_unlock_irqrestore(&tx->lock, flags); + + if (no_spc) + return TX_NO_BUFFER; + + return 0; +} + +static int gdm_usb_hci_send(void *priv_dev, void *data, int len, + void (*cb)(void *data), void *cb_data) +{ + struct lte_udev *udev = priv_dev; + struct tx_cxt *tx = &udev->tx; + struct usb_tx *t; + unsigned long flags; + + if (!udev->usbdev) { + pr_err("hci send - invalid device\n"); + return -ENODEV; + } + + t = alloc_tx_struct(len); + if (t == NULL) { + pr_err("hci_send - out of memory\n"); + return -ENOMEM; + } + + memcpy(t->buf, data, len); + t->callback = cb; + t->cb_data = cb_data; + t->len = len; + t->tx = tx; + t->is_sdu = 0; + + spin_lock_irqsave(&tx->lock, flags); + list_add_tail(&t->list, &tx->hci_list); + queue_work(usb_tx_wq, &udev->work_tx.work); + spin_unlock_irqrestore(&tx->lock, flags); + + return 0; +} + +static struct gdm_endian *gdm_usb_get_endian(void *priv_dev) +{ + struct lte_udev *udev = priv_dev; + + return &udev->gdm_ed; +} + +static int gdm_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int ret = 0; + struct phy_dev *phy_dev = NULL; + struct lte_udev *udev = NULL; + u16 idVendor, idProduct; + int bInterfaceNumber; + struct usb_device *usbdev = interface_to_usbdev(intf); + + bInterfaceNumber = intf->cur_altsetting->desc.bInterfaceNumber; + idVendor = __le16_to_cpu(usbdev->descriptor.idVendor); + idProduct = __le16_to_cpu(usbdev->descriptor.idProduct); + + pr_info("net vid = 0x%04x pid = 0x%04x\n", idVendor, idProduct); + + if (bInterfaceNumber > NETWORK_INTERFACE) { + pr_info("not a network device\n"); + return -ENODEV; + } + + phy_dev = kzalloc(sizeof(struct phy_dev), GFP_KERNEL); + if (!phy_dev) + return -ENOMEM; + + udev = kzalloc(sizeof(struct lte_udev), GFP_KERNEL); + if (!udev) { + ret = -ENOMEM; + goto err_udev; + } + + phy_dev->priv_dev = (void *)udev; + phy_dev->send_hci_func = gdm_usb_hci_send; + phy_dev->send_sdu_func = gdm_usb_sdu_send; + phy_dev->rcv_func = gdm_usb_recv; + phy_dev->get_endian = gdm_usb_get_endian; + + udev->usbdev = usbdev; + ret = init_usb(udev); + if (ret < 0) { + dev_err(intf->usb_dev, "init_usb func failed\n"); + goto err_init_usb; + } + udev->intf = intf; + + intf->needs_remote_wakeup = 1; + usb_enable_autosuspend(usbdev); + pm_runtime_set_autosuspend_delay(&usbdev->dev, AUTO_SUSPEND_TIMER); + + /* List up hosts with big endians, otherwise, + * defaults to little endian + */ + if (idProduct == PID_GDM7243) + gdm_set_endian(&udev->gdm_ed, ENDIANNESS_BIG); + else + gdm_set_endian(&udev->gdm_ed, ENDIANNESS_LITTLE); + + ret = request_mac_address(udev); + if (ret < 0) { + dev_err(intf->usb_dev, "request Mac address failed\n"); + goto err_mac_address; + } + + start_rx_proc(phy_dev); + usb_get_dev(usbdev); + usb_set_intfdata(intf, phy_dev); + + return 0; + +err_mac_address: + release_usb(udev); +err_init_usb: + kfree(udev); +err_udev: + kfree(phy_dev); + + return ret; +} + +static void gdm_usb_disconnect(struct usb_interface *intf) +{ + struct phy_dev *phy_dev; + struct lte_udev *udev; + u16 idVendor, idProduct; + struct usb_device *usbdev; + + usbdev = interface_to_usbdev(intf); + + idVendor = __le16_to_cpu(usbdev->descriptor.idVendor); + idProduct = __le16_to_cpu(usbdev->descriptor.idProduct); + + phy_dev = usb_get_intfdata(intf); + + udev = phy_dev->priv_dev; + unregister_lte_device(phy_dev); + + release_usb(udev); + + kfree(udev); + udev = NULL; + + kfree(phy_dev); + phy_dev = NULL; + + usb_put_dev(usbdev); +} + +static int gdm_usb_suspend(struct usb_interface *intf, pm_message_t pm_msg) +{ + struct phy_dev *phy_dev; + struct lte_udev *udev; + struct rx_cxt *rx; + struct usb_rx *r; + struct usb_rx *r_next; + unsigned long flags; + + phy_dev = usb_get_intfdata(intf); + udev = phy_dev->priv_dev; + rx = &udev->rx; + if (udev->usb_state != PM_NORMAL) { + dev_err(intf->usb_dev, "usb suspend - invalid state\n"); + return -1; + } + + udev->usb_state = PM_SUSPEND; + + spin_lock_irqsave(&rx->submit_lock, flags); + list_for_each_entry_safe(r, r_next, &rx->rx_submit_list, + rx_submit_list) { + spin_unlock_irqrestore(&rx->submit_lock, flags); + usb_kill_urb(r->urb); + spin_lock_irqsave(&rx->submit_lock, flags); + } + spin_unlock_irqrestore(&rx->submit_lock, flags); + + return 0; +} + +static int gdm_usb_resume(struct usb_interface *intf) +{ + struct phy_dev *phy_dev; + struct lte_udev *udev; + struct tx_cxt *tx; + struct rx_cxt *rx; + unsigned long flags; + int issue_count; + int i; + + phy_dev = usb_get_intfdata(intf); + udev = phy_dev->priv_dev; + rx = &udev->rx; + + if (udev->usb_state != PM_SUSPEND) { + dev_err(intf->usb_dev, "usb resume - invalid state\n"); + return -1; + } + udev->usb_state = PM_NORMAL; + + spin_lock_irqsave(&rx->rx_lock, flags); + issue_count = rx->avail_count - MAX_RX_SUBMIT_COUNT; + spin_unlock_irqrestore(&rx->rx_lock, flags); + + if (issue_count >= 0) { + for (i = 0; i < issue_count; i++) + gdm_usb_recv(phy_dev->priv_dev, + udev->rx_cb, + phy_dev, + USB_COMPLETE); + } + + tx = &udev->tx; + spin_lock_irqsave(&tx->lock, flags); + queue_work(usb_tx_wq, &udev->work_tx.work); + spin_unlock_irqrestore(&tx->lock, flags); + + return 0; +} + +static struct usb_driver gdm_usb_lte_driver = { + .name = "gdm_lte", + .probe = gdm_usb_probe, + .disconnect = gdm_usb_disconnect, + .id_table = id_table, + .supports_autosuspend = 1, + .suspend = gdm_usb_suspend, + .resume = gdm_usb_resume, + .reset_resume = gdm_usb_resume, +}; + +static int __init gdm_usb_lte_init(void) +{ + if (gdm_lte_event_init() < 0) { + pr_err("error creating event\n"); + return -1; + } + + usb_tx_wq = create_workqueue("usb_tx_wq"); + if (usb_tx_wq == NULL) + return -1; + + usb_rx_wq = create_workqueue("usb_rx_wq"); + if (usb_rx_wq == NULL) + return -1; + + return usb_register(&gdm_usb_lte_driver); +} + +static void __exit gdm_usb_lte_exit(void) +{ + gdm_lte_event_exit(); + + usb_deregister(&gdm_usb_lte_driver); + + if (usb_tx_wq) { + flush_workqueue(usb_tx_wq); + destroy_workqueue(usb_tx_wq); + } + + if (usb_rx_wq) { + flush_workqueue(usb_rx_wq); + destroy_workqueue(usb_rx_wq); + } +} + +module_init(gdm_usb_lte_init); +module_exit(gdm_usb_lte_exit); + +MODULE_VERSION(DRIVER_VERSION); +MODULE_DESCRIPTION("GCT LTE USB Device Driver"); +MODULE_LICENSE("GPL"); |