788 lines
17 KiB
C
788 lines
17 KiB
C
/*
|
|
* 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;
|
|
|
|
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);
|
|
|
|
for (i = 0; i < MAX_NR_SDU_BUF; i++) {
|
|
t = alloc_tx_struct(tx);
|
|
if (t == NULL) {
|
|
ret = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
list_add(&t->list, &tx->free_list);
|
|
}
|
|
|
|
r = alloc_rx_struct(rx);
|
|
if (r == NULL) {
|
|
ret = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
list_add(&r->list, &rx->free_list);
|
|
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;
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (!udev->usbdev) {
|
|
printk(KERN_ERR "%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;
|
|
|
|
list_for_each_entry(r, &rx->used_list, list)
|
|
usb_unlink_urb(r->urb);
|
|
udev->bw_switch = 1;
|
|
|
|
spin_lock(&k_lock);
|
|
list_add_tail(&udev->list, &k_list);
|
|
spin_unlock(&k_lock);
|
|
|
|
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;
|
|
|
|
#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(&rx->lock);
|
|
put_rx_struct(rx, r);
|
|
spin_unlock(&rx->lock);
|
|
|
|
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) {
|
|
printk(KERN_ERR "%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);
|
|
|
|
printk(KERN_INFO "Found GDM USB VID = 0x%04x PID = 0x%04x...\n",
|
|
idVendor, idProduct);
|
|
printk(KERN_INFO "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);
|
|
}
|
|
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;
|
|
|
|
phy_dev = usb_get_intfdata(intf);
|
|
udev = phy_dev->priv_dev;
|
|
rx = &udev->rx;
|
|
|
|
list_for_each_entry(r, &rx->used_list, list)
|
|
usb_unlink_urb(r->urb);
|
|
|
|
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;
|
|
|
|
phy_dev = usb_get_intfdata(intf);
|
|
udev = phy_dev->priv_dev;
|
|
rx = &udev->rx;
|
|
|
|
list_for_each_entry(r, &rx->used_list, list)
|
|
usb_submit_urb(r->urb, GFP_ATOMIC);
|
|
|
|
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;
|
|
|
|
daemonize("k_mode_wimax");
|
|
|
|
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);
|
|
|
|
list_for_each_entry(r, &rx->used_list, list)
|
|
usb_submit_urb(r->urb, GFP_ATOMIC);
|
|
|
|
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, "WiMax_thread");
|
|
#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");
|