/* * 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. */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/usb.h> #include <asm/byteorder.h> #include <linux/kthread.h> #include "gdm_usb.h" #include "gdm_wimax.h" #include "usb_boot.h" #include "hci.h" #include "usb_ids.h" MODULE_DEVICE_TABLE(usb, id_table); #define TX_BUF_SIZE 2048 #if defined(CONFIG_WIMAX_GDM72XX_WIMAX2) #define RX_BUF_SIZE (128*1024) /* For packet aggregation */ #else #define RX_BUF_SIZE 2048 #endif #define GDM7205_PADDING 256 #define H2B(x) __cpu_to_be16(x) #define B2H(x) __be16_to_cpu(x) #define DB2H(x) __be32_to_cpu(x) #define DOWNLOAD_CONF_VALUE 0x21 #ifdef CONFIG_WIMAX_GDM72XX_K_MODE static DECLARE_WAIT_QUEUE_HEAD(k_wait); static LIST_HEAD(k_list); static DEFINE_SPINLOCK(k_lock); static int k_mode_stop; #define K_WAIT_TIME (2 * HZ / 100) #endif /* CONFIG_WIMAX_GDM72XX_K_MODE */ static int init_usb(struct usbwm_dev *udev); static void release_usb(struct usbwm_dev *udev); /*#define DEBUG */ #ifdef DEBUG static void hexdump(char *title, u8 *data, int len) { int i; printk(KERN_DEBUG "%s: length = %d\n", title, len); for (i = 0; i < len; i++) { printk(KERN_DEBUG "%02x ", data[i]); if ((i & 0xf) == 0xf) printk(KERN_DEBUG "\n"); } printk(KERN_DEBUG "\n"); } #endif static struct usb_tx *alloc_tx_struct(struct tx_cxt *tx) { struct usb_tx *t = kzalloc(sizeof(*t), GFP_ATOMIC); if (!t) return NULL; t->urb = usb_alloc_urb(0, GFP_ATOMIC); t->buf = kmalloc(TX_BUF_SIZE, GFP_ATOMIC); if (!t->urb || !t->buf) { usb_free_urb(t->urb); kfree(t->buf); kfree(t); return NULL; } t->tx_cxt = tx; return t; } static void free_tx_struct(struct usb_tx *t) { if (t) { usb_free_urb(t->urb); kfree(t->buf); kfree(t); } } static struct usb_rx *alloc_rx_struct(struct rx_cxt *rx) { struct usb_rx *r = kzalloc(sizeof(*r), GFP_ATOMIC); if (!r) return NULL; r->urb = usb_alloc_urb(0, GFP_ATOMIC); r->buf = kmalloc(RX_BUF_SIZE, GFP_ATOMIC); if (!r->urb || !r->buf) { usb_free_urb(r->urb); kfree(r->buf); kfree(r); return NULL; } r->rx_cxt = rx; return r; } static void free_rx_struct(struct usb_rx *r) { if (r) { usb_free_urb(r->urb); kfree(r->buf); kfree(r); } } /* Before this function is called, spin lock should be locked. */ static struct usb_tx *get_tx_struct(struct tx_cxt *tx, int *no_spc) { struct usb_tx *t; if (list_empty(&tx->free_list)) { *no_spc = 1; return NULL; } t = list_entry(tx->free_list.next, struct usb_tx, list); list_del(&t->list); *no_spc = list_empty(&tx->free_list) ? 1 : 0; return t; } /* Before this function is called, spin lock should be locked. */ static void put_tx_struct(struct tx_cxt *tx, struct usb_tx *t) { list_add_tail(&t->list, &tx->free_list); } /* Before this function is called, spin lock should be locked. */ static struct usb_rx *get_rx_struct(struct rx_cxt *rx) { struct usb_rx *r; if (list_empty(&rx->free_list)) { r = alloc_rx_struct(rx); if (r == NULL) return NULL; list_add(&r->list, &rx->free_list); } r = list_entry(rx->free_list.next, struct usb_rx, list); list_move_tail(&r->list, &rx->used_list); return r; } /* Before this function is called, spin lock should be locked. */ static void put_rx_struct(struct rx_cxt *rx, struct usb_rx *r) { list_move(&r->list, &rx->free_list); } static int init_usb(struct usbwm_dev *udev) { int ret = 0, i; struct tx_cxt *tx = &udev->tx; struct rx_cxt *rx = &udev->rx; struct usb_tx *t; struct usb_rx *r; unsigned long flags; INIT_LIST_HEAD(&tx->free_list); INIT_LIST_HEAD(&tx->sdu_list); INIT_LIST_HEAD(&tx->hci_list); #if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE) INIT_LIST_HEAD(&tx->pending_list); #endif INIT_LIST_HEAD(&rx->free_list); INIT_LIST_HEAD(&rx->used_list); spin_lock_init(&tx->lock); spin_lock_init(&rx->lock); spin_lock_irqsave(&tx->lock, flags); for (i = 0; i < MAX_NR_SDU_BUF; i++) { t = alloc_tx_struct(tx); if (t == NULL) { spin_unlock_irqrestore(&tx->lock, flags); ret = -ENOMEM; goto fail; } list_add(&t->list, &tx->free_list); } spin_unlock_irqrestore(&tx->lock, flags); r = alloc_rx_struct(rx); if (r == NULL) { ret = -ENOMEM; goto fail; } spin_lock_irqsave(&rx->lock, flags); list_add(&r->list, &rx->free_list); spin_unlock_irqrestore(&rx->lock, flags); return ret; fail: release_usb(udev); return ret; } static void release_usb(struct usbwm_dev *udev) { struct tx_cxt *tx = &udev->tx; struct rx_cxt *rx = &udev->rx; struct usb_tx *t, *t_next; struct usb_rx *r, *r_next; unsigned long flags; spin_lock_irqsave(&tx->lock, flags); list_for_each_entry_safe(t, t_next, &tx->sdu_list, list) { list_del(&t->list); free_tx_struct(t); } 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, t_next, &tx->free_list, list) { list_del(&t->list); free_tx_struct(t); } spin_unlock_irqrestore(&tx->lock, flags); spin_lock_irqsave(&rx->lock, flags); list_for_each_entry_safe(r, r_next, &rx->free_list, list) { list_del(&r->list); free_rx_struct(r); } list_for_each_entry_safe(r, r_next, &rx->used_list, list) { list_del(&r->list); free_rx_struct(r); } spin_unlock_irqrestore(&rx->lock, flags); } static void __gdm_usb_send_complete(struct urb *urb) { struct usb_tx *t = urb->context; struct tx_cxt *tx = t->tx_cxt; u8 *pkt = t->buf; u16 cmd_evt; /* Completion by usb_unlink_urb */ if (urb->status == -ECONNRESET) return; if (t->callback) t->callback(t->cb_data); /* Delete from sdu list or hci list. */ list_del(&t->list); cmd_evt = (pkt[0] << 8) | pkt[1]; if (cmd_evt == WIMAX_TX_SDU) put_tx_struct(tx, t); else free_tx_struct(t); } static void gdm_usb_send_complete(struct urb *urb) { struct usb_tx *t = urb->context; struct tx_cxt *tx = t->tx_cxt; unsigned long flags; spin_lock_irqsave(&tx->lock, flags); __gdm_usb_send_complete(urb); spin_unlock_irqrestore(&tx->lock, flags); } static int gdm_usb_send(void *priv_dev, void *data, int len, void (*cb)(void *data), void *cb_data) { struct usbwm_dev *udev = priv_dev; struct usb_device *usbdev = udev->usbdev; struct tx_cxt *tx = &udev->tx; struct usb_tx *t; int padding = udev->padding; int no_spc = 0, ret; u8 *pkt = data; u16 cmd_evt; unsigned long flags; #ifdef CONFIG_WIMAX_GDM72XX_K_MODE unsigned long flags2; #endif /* CONFIG_WIMAX_GDM72XX_K_MODE */ if (!udev->usbdev) { dev_err(&usbdev->dev, "%s: No such device\n", __func__); return -ENODEV; } BUG_ON(len > TX_BUF_SIZE - padding - 1); spin_lock_irqsave(&tx->lock, flags); cmd_evt = (pkt[0] << 8) | pkt[1]; if (cmd_evt == WIMAX_TX_SDU) { t = get_tx_struct(tx, &no_spc); if (t == NULL) { /* This case must not happen. */ spin_unlock_irqrestore(&tx->lock, flags); return -ENOSPC; } list_add_tail(&t->list, &tx->sdu_list); } else { t = alloc_tx_struct(tx); if (t == NULL) { spin_unlock_irqrestore(&tx->lock, flags); return -ENOMEM; } list_add_tail(&t->list, &tx->hci_list); } memcpy(t->buf + padding, data, len); t->callback = cb; t->cb_data = cb_data; /* * In some cases, USB Module of WiMax is blocked when data size is * the multiple of 512. So, increment length by one in that case. */ if ((len % 512) == 0) len++; usb_fill_bulk_urb(t->urb, usbdev, usb_sndbulkpipe(usbdev, 1), t->buf, len + padding, gdm_usb_send_complete, t); #ifdef DEBUG hexdump("usb_send", t->buf, len + padding); #endif #ifdef CONFIG_WIMAX_GDM72XX_USB_PM if (usbdev->state & USB_STATE_SUSPENDED) { list_add_tail(&t->p_list, &tx->pending_list); schedule_work(&udev->pm_ws); goto out; } #endif /* CONFIG_WIMAX_GDM72XX_USB_PM */ #ifdef CONFIG_WIMAX_GDM72XX_K_MODE if (udev->bw_switch) { list_add_tail(&t->p_list, &tx->pending_list); goto out; } else if (cmd_evt == WIMAX_SCAN) { struct rx_cxt *rx; struct usb_rx *r; rx = &udev->rx; spin_lock_irqsave(&rx->lock, flags2); list_for_each_entry(r, &rx->used_list, list) usb_unlink_urb(r->urb); spin_unlock_irqrestore(&rx->lock, flags2); udev->bw_switch = 1; spin_lock_irqsave(&k_lock, flags2); list_add_tail(&udev->list, &k_list); spin_unlock_irqrestore(&k_lock, flags2); wake_up(&k_wait); } #endif /* CONFIG_WIMAX_GDM72XX_K_MODE */ ret = usb_submit_urb(t->urb, GFP_ATOMIC); if (ret) goto send_fail; #ifdef CONFIG_WIMAX_GDM72XX_USB_PM usb_mark_last_busy(usbdev); #endif /* CONFIG_WIMAX_GDM72XX_USB_PM */ #if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE) out: #endif spin_unlock_irqrestore(&tx->lock, flags); if (no_spc) return -ENOSPC; return 0; send_fail: t->callback = NULL; __gdm_usb_send_complete(t->urb); spin_unlock_irqrestore(&tx->lock, flags); return ret; } static void gdm_usb_rcv_complete(struct urb *urb) { struct usb_rx *r = urb->context; struct rx_cxt *rx = r->rx_cxt; struct usbwm_dev *udev = container_of(r->rx_cxt, struct usbwm_dev, rx); struct tx_cxt *tx = &udev->tx; struct usb_tx *t; u16 cmd_evt; unsigned long flags, flags2; #ifdef CONFIG_WIMAX_GDM72XX_USB_PM struct usb_device *dev = urb->dev; #endif /* Completion by usb_unlink_urb */ if (urb->status == -ECONNRESET) return; spin_lock_irqsave(&tx->lock, flags); if (!urb->status) { cmd_evt = (r->buf[0] << 8) | (r->buf[1]); #ifdef DEBUG hexdump("usb_receive", r->buf, urb->actual_length); #endif if (cmd_evt == WIMAX_SDU_TX_FLOW) { if (r->buf[4] == 0) { #ifdef DEBUG printk(KERN_DEBUG "WIMAX ==> STOP SDU TX\n"); #endif list_for_each_entry(t, &tx->sdu_list, list) usb_unlink_urb(t->urb); } else if (r->buf[4] == 1) { #ifdef DEBUG printk(KERN_DEBUG "WIMAX ==> START SDU TX\n"); #endif list_for_each_entry(t, &tx->sdu_list, list) { usb_submit_urb(t->urb, GFP_ATOMIC); } /* * If free buffer for sdu tx doesn't * exist, then tx queue should not be * woken. For this reason, don't pass * the command, START_SDU_TX. */ if (list_empty(&tx->free_list)) urb->actual_length = 0; } } } if (!urb->status && r->callback) r->callback(r->cb_data, r->buf, urb->actual_length); spin_lock_irqsave(&rx->lock, flags2); put_rx_struct(rx, r); spin_unlock_irqrestore(&rx->lock, flags2); spin_unlock_irqrestore(&tx->lock, flags); #ifdef CONFIG_WIMAX_GDM72XX_USB_PM usb_mark_last_busy(dev); #endif } static int gdm_usb_receive(void *priv_dev, void (*cb)(void *cb_data, void *data, int len), void *cb_data) { struct usbwm_dev *udev = priv_dev; struct usb_device *usbdev = udev->usbdev; struct rx_cxt *rx = &udev->rx; struct usb_rx *r; unsigned long flags; if (!udev->usbdev) { dev_err(&usbdev->dev, "%s: No such device\n", __func__); return -ENODEV; } spin_lock_irqsave(&rx->lock, flags); r = get_rx_struct(rx); spin_unlock_irqrestore(&rx->lock, flags); if (r == NULL) return -ENOMEM; r->callback = cb; r->cb_data = cb_data; usb_fill_bulk_urb(r->urb, usbdev, usb_rcvbulkpipe(usbdev, 0x82), r->buf, RX_BUF_SIZE, gdm_usb_rcv_complete, r); return usb_submit_urb(r->urb, GFP_ATOMIC); } #ifdef CONFIG_WIMAX_GDM72XX_USB_PM static void do_pm_control(struct work_struct *work) { struct usbwm_dev *udev = container_of(work, struct usbwm_dev, pm_ws); struct tx_cxt *tx = &udev->tx; int ret; unsigned long flags; ret = usb_autopm_get_interface(udev->intf); if (!ret) usb_autopm_put_interface(udev->intf); spin_lock_irqsave(&tx->lock, flags); if (!(udev->usbdev->state & USB_STATE_SUSPENDED) && (!list_empty(&tx->hci_list) || !list_empty(&tx->sdu_list))) { struct usb_tx *t, *temp; list_for_each_entry_safe(t, temp, &tx->pending_list, p_list) { list_del(&t->p_list); ret = usb_submit_urb(t->urb, GFP_ATOMIC); if (ret) { t->callback = NULL; __gdm_usb_send_complete(t->urb); } } } spin_unlock_irqrestore(&tx->lock, flags); } #endif /* CONFIG_WIMAX_GDM72XX_USB_PM */ static int gdm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) { int ret = 0; u8 bConfigurationValue; struct phy_dev *phy_dev = NULL; struct usbwm_dev *udev = NULL; u16 idVendor, idProduct, bcdDevice; struct usb_device *usbdev = interface_to_usbdev(intf); usb_get_dev(usbdev); bConfigurationValue = usbdev->actconfig->desc.bConfigurationValue; /*USB description is set up with Little-Endian*/ idVendor = L2H(usbdev->descriptor.idVendor); idProduct = L2H(usbdev->descriptor.idProduct); bcdDevice = L2H(usbdev->descriptor.bcdDevice); dev_info(&intf->dev, "Found GDM USB VID = 0x%04x PID = 0x%04x...\n", idVendor, idProduct); dev_info(&intf->dev, "GCT WiMax driver version %s\n", DRIVER_VERSION); if (idProduct == EMERGENCY_PID) { ret = usb_emergency(usbdev); goto out; } /* Support for EEPROM bootloader */ if (bConfigurationValue == DOWNLOAD_CONF_VALUE || idProduct & B_DOWNLOAD) { ret = usb_boot(usbdev, bcdDevice); goto out; } phy_dev = kzalloc(sizeof(*phy_dev), GFP_KERNEL); if (phy_dev == NULL) { ret = -ENOMEM; goto out; } udev = kzalloc(sizeof(*udev), GFP_KERNEL); if (udev == NULL) { ret = -ENOMEM; goto out; } if (idProduct == 0x7205 || idProduct == 0x7206) udev->padding = GDM7205_PADDING; else udev->padding = 0; phy_dev->priv_dev = (void *)udev; phy_dev->send_func = gdm_usb_send; phy_dev->rcv_func = gdm_usb_receive; ret = init_usb(udev); if (ret < 0) goto out; udev->usbdev = usbdev; #ifdef CONFIG_WIMAX_GDM72XX_USB_PM udev->intf = intf; intf->needs_remote_wakeup = 1; device_init_wakeup(&intf->dev, 1); pm_runtime_set_autosuspend_delay(&usbdev->dev, 10 * 1000); /* msec */ INIT_WORK(&udev->pm_ws, do_pm_control); #endif /* CONFIG_WIMAX_GDM72XX_USB_PM */ ret = register_wimax_device(phy_dev, &intf->dev); out: if (ret) { kfree(phy_dev); kfree(udev); } else { usb_set_intfdata(intf, phy_dev); } return ret; } static void gdm_usb_disconnect(struct usb_interface *intf) { u8 bConfigurationValue; struct phy_dev *phy_dev; struct usbwm_dev *udev; u16 idProduct; struct usb_device *usbdev = interface_to_usbdev(intf); bConfigurationValue = usbdev->actconfig->desc.bConfigurationValue; phy_dev = usb_get_intfdata(intf); /*USB description is set up with Little-Endian*/ idProduct = L2H(usbdev->descriptor.idProduct); if (idProduct != EMERGENCY_PID && bConfigurationValue != DOWNLOAD_CONF_VALUE && (idProduct & B_DOWNLOAD) == 0) { udev = phy_dev->priv_dev; udev->usbdev = NULL; unregister_wimax_device(phy_dev); release_usb(udev); kfree(udev); kfree(phy_dev); } usb_put_dev(usbdev); } #ifdef CONFIG_WIMAX_GDM72XX_USB_PM static int gdm_suspend(struct usb_interface *intf, pm_message_t pm_msg) { struct phy_dev *phy_dev; struct usbwm_dev *udev; struct rx_cxt *rx; struct usb_rx *r; unsigned long flags; phy_dev = usb_get_intfdata(intf); if (!phy_dev) return 0; udev = phy_dev->priv_dev; rx = &udev->rx; spin_lock_irqsave(&rx->lock, flags); list_for_each_entry(r, &rx->used_list, list) usb_unlink_urb(r->urb); spin_unlock_irqrestore(&rx->lock, flags); return 0; } static int gdm_resume(struct usb_interface *intf) { struct phy_dev *phy_dev; struct usbwm_dev *udev; struct rx_cxt *rx; struct usb_rx *r; unsigned long flags; phy_dev = usb_get_intfdata(intf); if (!phy_dev) return 0; udev = phy_dev->priv_dev; rx = &udev->rx; spin_lock_irqsave(&rx->lock, flags); list_for_each_entry(r, &rx->used_list, list) usb_submit_urb(r->urb, GFP_ATOMIC); spin_unlock_irqrestore(&rx->lock, flags); return 0; } #endif /* CONFIG_WIMAX_GDM72XX_USB_PM */ #ifdef CONFIG_WIMAX_GDM72XX_K_MODE static int k_mode_thread(void *arg) { struct usbwm_dev *udev; struct tx_cxt *tx; struct rx_cxt *rx; struct usb_tx *t, *temp; struct usb_rx *r; unsigned long flags, flags2, expire; int ret; while (!k_mode_stop) { spin_lock_irqsave(&k_lock, flags2); while (!list_empty(&k_list)) { udev = list_entry(k_list.next, struct usbwm_dev, list); tx = &udev->tx; rx = &udev->rx; list_del(&udev->list); spin_unlock_irqrestore(&k_lock, flags2); expire = jiffies + K_WAIT_TIME; while (jiffies < expire) schedule_timeout(K_WAIT_TIME); spin_lock_irqsave(&rx->lock, flags); list_for_each_entry(r, &rx->used_list, list) usb_submit_urb(r->urb, GFP_ATOMIC); spin_unlock_irqrestore(&rx->lock, flags); spin_lock_irqsave(&tx->lock, flags); list_for_each_entry_safe(t, temp, &tx->pending_list, p_list) { list_del(&t->p_list); ret = usb_submit_urb(t->urb, GFP_ATOMIC); if (ret) { t->callback = NULL; __gdm_usb_send_complete(t->urb); } } udev->bw_switch = 0; spin_unlock_irqrestore(&tx->lock, flags); spin_lock_irqsave(&k_lock, flags2); } spin_unlock_irqrestore(&k_lock, flags2); interruptible_sleep_on(&k_wait); } return 0; } #endif /* CONFIG_WIMAX_GDM72XX_K_MODE */ static struct usb_driver gdm_usb_driver = { .name = "gdm_wimax", .probe = gdm_usb_probe, .disconnect = gdm_usb_disconnect, .id_table = id_table, #ifdef CONFIG_WIMAX_GDM72XX_USB_PM .supports_autosuspend = 1, .suspend = gdm_suspend, .resume = gdm_resume, .reset_resume = gdm_resume, #endif }; static int __init usb_gdm_wimax_init(void) { #ifdef CONFIG_WIMAX_GDM72XX_K_MODE kthread_run(k_mode_thread, NULL, "k_mode_wimax"); #endif /* CONFIG_WIMAX_GDM72XX_K_MODE */ return usb_register(&gdm_usb_driver); } static void __exit usb_gdm_wimax_exit(void) { #ifdef CONFIG_WIMAX_GDM72XX_K_MODE k_mode_stop = 1; wake_up(&k_wait); #endif usb_deregister(&gdm_usb_driver); } module_init(usb_gdm_wimax_init); module_exit(usb_gdm_wimax_exit); MODULE_VERSION(DRIVER_VERSION); MODULE_DESCRIPTION("GCT WiMax Device Driver"); MODULE_AUTHOR("Ethan Park"); MODULE_LICENSE("GPL");