diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
commit | 57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch) | |
tree | 5e910f0e82173f4ef4f51111366a3f1299037a7b /drivers/staging/gdm72xx/gdm_usb.c |
Initial import
Diffstat (limited to 'drivers/staging/gdm72xx/gdm_usb.c')
-rw-r--r-- | drivers/staging/gdm72xx/gdm_usb.c | 789 |
1 files changed, 789 insertions, 0 deletions
diff --git a/drivers/staging/gdm72xx/gdm_usb.c b/drivers/staging/gdm72xx/gdm_usb.c new file mode 100644 index 000000000..eac2f3478 --- /dev/null +++ b/drivers/staging/gdm72xx/gdm_usb.c @@ -0,0 +1,789 @@ +/* + * 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 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 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 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 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 __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; + } + + if (len > TX_BUF_SIZE - padding - 1) + return -EINVAL; + + 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); + + dev_dbg(&usbdev->dev, "usb_send: %*ph\n", len + padding, t->buf); + +#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; + struct usb_device *dev = urb->dev; + + /* 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]); + + dev_dbg(&dev->dev, "usb_receive: %*ph\n", urb->actual_length, + r->buf); + + if (cmd_evt == WIMAX_SDU_TX_FLOW) { + if (r->buf[4] == 0) { + dev_dbg(&dev->dev, "WIMAX ==> STOP SDU TX\n"); + list_for_each_entry(t, &tx->sdu_list, list) + usb_unlink_urb(t->urb); + } else if (r->buf[4] == 1) { + dev_dbg(&dev->dev, "WIMAX ==> START SDU TX\n"); + 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 = le16_to_cpu(usbdev->descriptor.idVendor); + idProduct = le16_to_cpu(usbdev->descriptor.idProduct); + bcdDevice = le16_to_cpu(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); + if (ret) + release_usb(udev); + +out: + if (ret) { + kfree(phy_dev); + kfree(udev); + usb_put_dev(usbdev); + } 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 = le16_to_cpu(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 (time_before(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); + } + wait_event_interruptible_lock_irq(k_wait, + !list_empty(&k_list) || + k_mode_stop, k_lock); + spin_unlock_irqrestore(&k_lock, flags2); + } + 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"); |