Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / net / wireless / ath / ath9k / hif_usb.c
diff --git a/kernel/drivers/net/wireless/ath/ath9k/hif_usb.c b/kernel/drivers/net/wireless/ath/ath9k/hif_usb.c
new file mode 100644 (file)
index 0000000..10c02f5
--- /dev/null
@@ -0,0 +1,1383 @@
+/*
+ * Copyright (c) 2010-2011 Atheros Communications Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <asm/unaligned.h>
+#include "htc.h"
+
+/* identify firmware images */
+#define FIRMWARE_AR7010_1_1     "htc_7010.fw"
+#define FIRMWARE_AR9271         "htc_9271.fw"
+
+MODULE_FIRMWARE(FIRMWARE_AR7010_1_1);
+MODULE_FIRMWARE(FIRMWARE_AR9271);
+
+static struct usb_device_id ath9k_hif_usb_ids[] = {
+       { USB_DEVICE(0x0cf3, 0x9271) }, /* Atheros */
+       { USB_DEVICE(0x0cf3, 0x1006) }, /* Atheros */
+       { USB_DEVICE(0x0846, 0x9030) }, /* Netgear N150 */
+       { USB_DEVICE(0x07D1, 0x3A10) }, /* Dlink Wireless 150 */
+       { USB_DEVICE(0x13D3, 0x3327) }, /* Azurewave */
+       { USB_DEVICE(0x13D3, 0x3328) }, /* Azurewave */
+       { USB_DEVICE(0x13D3, 0x3346) }, /* IMC Networks */
+       { USB_DEVICE(0x13D3, 0x3348) }, /* Azurewave */
+       { USB_DEVICE(0x13D3, 0x3349) }, /* Azurewave */
+       { USB_DEVICE(0x13D3, 0x3350) }, /* Azurewave */
+       { USB_DEVICE(0x04CA, 0x4605) }, /* Liteon */
+       { USB_DEVICE(0x040D, 0x3801) }, /* VIA */
+       { USB_DEVICE(0x0cf3, 0xb003) }, /* Ubiquiti WifiStation Ext */
+       { USB_DEVICE(0x0cf3, 0xb002) }, /* Ubiquiti WifiStation */
+       { USB_DEVICE(0x057c, 0x8403) }, /* AVM FRITZ!WLAN 11N v2 USB */
+       { USB_DEVICE(0x0471, 0x209e) }, /* Philips (or NXP) PTA01 */
+
+       { USB_DEVICE(0x0cf3, 0x7015),
+         .driver_info = AR9287_USB },  /* Atheros */
+       { USB_DEVICE(0x1668, 0x1200),
+         .driver_info = AR9287_USB },  /* Verizon */
+
+       { USB_DEVICE(0x0cf3, 0x7010),
+         .driver_info = AR9280_USB },  /* Atheros */
+       { USB_DEVICE(0x0846, 0x9018),
+         .driver_info = AR9280_USB },  /* Netgear WNDA3200 */
+       { USB_DEVICE(0x083A, 0xA704),
+         .driver_info = AR9280_USB },  /* SMC Networks */
+       { USB_DEVICE(0x0411, 0x017f),
+         .driver_info = AR9280_USB },  /* Sony UWA-BR100 */
+       { USB_DEVICE(0x0411, 0x0197),
+         .driver_info = AR9280_USB },  /* Buffalo WLI-UV-AG300P */
+       { USB_DEVICE(0x04da, 0x3904),
+         .driver_info = AR9280_USB },
+
+       { USB_DEVICE(0x0cf3, 0x20ff),
+         .driver_info = STORAGE_DEVICE },
+
+       { },
+};
+
+MODULE_DEVICE_TABLE(usb, ath9k_hif_usb_ids);
+
+static int __hif_usb_tx(struct hif_device_usb *hif_dev);
+
+static void hif_usb_regout_cb(struct urb *urb)
+{
+       struct cmd_buf *cmd = (struct cmd_buf *)urb->context;
+
+       switch (urb->status) {
+       case 0:
+               break;
+       case -ENOENT:
+       case -ECONNRESET:
+       case -ENODEV:
+       case -ESHUTDOWN:
+               goto free;
+       default:
+               break;
+       }
+
+       if (cmd) {
+               ath9k_htc_txcompletion_cb(cmd->hif_dev->htc_handle,
+                                         cmd->skb, true);
+               kfree(cmd);
+       }
+
+       return;
+free:
+       kfree_skb(cmd->skb);
+       kfree(cmd);
+}
+
+static int hif_usb_send_regout(struct hif_device_usb *hif_dev,
+                              struct sk_buff *skb)
+{
+       struct urb *urb;
+       struct cmd_buf *cmd;
+       int ret = 0;
+
+       urb = usb_alloc_urb(0, GFP_KERNEL);
+       if (urb == NULL)
+               return -ENOMEM;
+
+       cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+       if (cmd == NULL) {
+               usb_free_urb(urb);
+               return -ENOMEM;
+       }
+
+       cmd->skb = skb;
+       cmd->hif_dev = hif_dev;
+
+       usb_fill_int_urb(urb, hif_dev->udev,
+                        usb_sndintpipe(hif_dev->udev, USB_REG_OUT_PIPE),
+                        skb->data, skb->len,
+                        hif_usb_regout_cb, cmd, 1);
+
+       usb_anchor_urb(urb, &hif_dev->regout_submitted);
+       ret = usb_submit_urb(urb, GFP_KERNEL);
+       if (ret) {
+               usb_unanchor_urb(urb);
+               kfree(cmd);
+       }
+       usb_free_urb(urb);
+
+       return ret;
+}
+
+static void hif_usb_mgmt_cb(struct urb *urb)
+{
+       struct cmd_buf *cmd = (struct cmd_buf *)urb->context;
+       struct hif_device_usb *hif_dev;
+       bool txok = true;
+
+       if (!cmd || !cmd->skb || !cmd->hif_dev)
+               return;
+
+       hif_dev = cmd->hif_dev;
+
+       switch (urb->status) {
+       case 0:
+               break;
+       case -ENOENT:
+       case -ECONNRESET:
+       case -ENODEV:
+       case -ESHUTDOWN:
+               txok = false;
+
+               /*
+                * If the URBs are being flushed, no need to complete
+                * this packet.
+                */
+               spin_lock(&hif_dev->tx.tx_lock);
+               if (hif_dev->tx.flags & HIF_USB_TX_FLUSH) {
+                       spin_unlock(&hif_dev->tx.tx_lock);
+                       dev_kfree_skb_any(cmd->skb);
+                       kfree(cmd);
+                       return;
+               }
+               spin_unlock(&hif_dev->tx.tx_lock);
+
+               break;
+       default:
+               txok = false;
+               break;
+       }
+
+       skb_pull(cmd->skb, 4);
+       ath9k_htc_txcompletion_cb(cmd->hif_dev->htc_handle,
+                                 cmd->skb, txok);
+       kfree(cmd);
+}
+
+static int hif_usb_send_mgmt(struct hif_device_usb *hif_dev,
+                            struct sk_buff *skb)
+{
+       struct urb *urb;
+       struct cmd_buf *cmd;
+       int ret = 0;
+       __le16 *hdr;
+
+       urb = usb_alloc_urb(0, GFP_ATOMIC);
+       if (urb == NULL)
+               return -ENOMEM;
+
+       cmd = kzalloc(sizeof(*cmd), GFP_ATOMIC);
+       if (cmd == NULL) {
+               usb_free_urb(urb);
+               return -ENOMEM;
+       }
+
+       cmd->skb = skb;
+       cmd->hif_dev = hif_dev;
+
+       hdr = (__le16 *) skb_push(skb, 4);
+       *hdr++ = cpu_to_le16(skb->len - 4);
+       *hdr++ = cpu_to_le16(ATH_USB_TX_STREAM_MODE_TAG);
+
+       usb_fill_bulk_urb(urb, hif_dev->udev,
+                        usb_sndbulkpipe(hif_dev->udev, USB_WLAN_TX_PIPE),
+                        skb->data, skb->len,
+                        hif_usb_mgmt_cb, cmd);
+
+       usb_anchor_urb(urb, &hif_dev->mgmt_submitted);
+       ret = usb_submit_urb(urb, GFP_ATOMIC);
+       if (ret) {
+               usb_unanchor_urb(urb);
+               kfree(cmd);
+       }
+       usb_free_urb(urb);
+
+       return ret;
+}
+
+static inline void ath9k_skb_queue_purge(struct hif_device_usb *hif_dev,
+                                        struct sk_buff_head *list)
+{
+       struct sk_buff *skb;
+
+       while ((skb = __skb_dequeue(list)) != NULL) {
+               dev_kfree_skb_any(skb);
+       }
+}
+
+static inline void ath9k_skb_queue_complete(struct hif_device_usb *hif_dev,
+                                           struct sk_buff_head *queue,
+                                           bool txok)
+{
+       struct sk_buff *skb;
+
+       while ((skb = __skb_dequeue(queue)) != NULL) {
+#ifdef CONFIG_ATH9K_HTC_DEBUGFS
+               int ln = skb->len;
+#endif
+               ath9k_htc_txcompletion_cb(hif_dev->htc_handle,
+                                         skb, txok);
+               if (txok) {
+                       TX_STAT_INC(skb_success);
+                       TX_STAT_ADD(skb_success_bytes, ln);
+               }
+               else
+                       TX_STAT_INC(skb_failed);
+       }
+}
+
+static void hif_usb_tx_cb(struct urb *urb)
+{
+       struct tx_buf *tx_buf = (struct tx_buf *) urb->context;
+       struct hif_device_usb *hif_dev;
+       bool txok = true;
+
+       if (!tx_buf || !tx_buf->hif_dev)
+               return;
+
+       hif_dev = tx_buf->hif_dev;
+
+       switch (urb->status) {
+       case 0:
+               break;
+       case -ENOENT:
+       case -ECONNRESET:
+       case -ENODEV:
+       case -ESHUTDOWN:
+               txok = false;
+
+               /*
+                * If the URBs are being flushed, no need to add this
+                * URB to the free list.
+                */
+               spin_lock(&hif_dev->tx.tx_lock);
+               if (hif_dev->tx.flags & HIF_USB_TX_FLUSH) {
+                       spin_unlock(&hif_dev->tx.tx_lock);
+                       ath9k_skb_queue_purge(hif_dev, &tx_buf->skb_queue);
+                       return;
+               }
+               spin_unlock(&hif_dev->tx.tx_lock);
+
+               break;
+       default:
+               txok = false;
+               break;
+       }
+
+       ath9k_skb_queue_complete(hif_dev, &tx_buf->skb_queue, txok);
+
+       /* Re-initialize the SKB queue */
+       tx_buf->len = tx_buf->offset = 0;
+       __skb_queue_head_init(&tx_buf->skb_queue);
+
+       /* Add this TX buffer to the free list */
+       spin_lock(&hif_dev->tx.tx_lock);
+       list_move_tail(&tx_buf->list, &hif_dev->tx.tx_buf);
+       hif_dev->tx.tx_buf_cnt++;
+       if (!(hif_dev->tx.flags & HIF_USB_TX_STOP))
+               __hif_usb_tx(hif_dev); /* Check for pending SKBs */
+       TX_STAT_INC(buf_completed);
+       spin_unlock(&hif_dev->tx.tx_lock);
+}
+
+/* TX lock has to be taken */
+static int __hif_usb_tx(struct hif_device_usb *hif_dev)
+{
+       struct tx_buf *tx_buf = NULL;
+       struct sk_buff *nskb = NULL;
+       int ret = 0, i;
+       u16 tx_skb_cnt = 0;
+       u8 *buf;
+       __le16 *hdr;
+
+       if (hif_dev->tx.tx_skb_cnt == 0)
+               return 0;
+
+       /* Check if a free TX buffer is available */
+       if (list_empty(&hif_dev->tx.tx_buf))
+               return 0;
+
+       tx_buf = list_first_entry(&hif_dev->tx.tx_buf, struct tx_buf, list);
+       list_move_tail(&tx_buf->list, &hif_dev->tx.tx_pending);
+       hif_dev->tx.tx_buf_cnt--;
+
+       tx_skb_cnt = min_t(u16, hif_dev->tx.tx_skb_cnt, MAX_TX_AGGR_NUM);
+
+       for (i = 0; i < tx_skb_cnt; i++) {
+               nskb = __skb_dequeue(&hif_dev->tx.tx_skb_queue);
+
+               /* Should never be NULL */
+               BUG_ON(!nskb);
+
+               hif_dev->tx.tx_skb_cnt--;
+
+               buf = tx_buf->buf;
+               buf += tx_buf->offset;
+               hdr = (__le16 *)buf;
+               *hdr++ = cpu_to_le16(nskb->len);
+               *hdr++ = cpu_to_le16(ATH_USB_TX_STREAM_MODE_TAG);
+               buf += 4;
+               memcpy(buf, nskb->data, nskb->len);
+               tx_buf->len = nskb->len + 4;
+
+               if (i < (tx_skb_cnt - 1))
+                       tx_buf->offset += (((tx_buf->len - 1) / 4) + 1) * 4;
+
+               if (i == (tx_skb_cnt - 1))
+                       tx_buf->len += tx_buf->offset;
+
+               __skb_queue_tail(&tx_buf->skb_queue, nskb);
+               TX_STAT_INC(skb_queued);
+       }
+
+       usb_fill_bulk_urb(tx_buf->urb, hif_dev->udev,
+                         usb_sndbulkpipe(hif_dev->udev, USB_WLAN_TX_PIPE),
+                         tx_buf->buf, tx_buf->len,
+                         hif_usb_tx_cb, tx_buf);
+
+       ret = usb_submit_urb(tx_buf->urb, GFP_ATOMIC);
+       if (ret) {
+               tx_buf->len = tx_buf->offset = 0;
+               ath9k_skb_queue_complete(hif_dev, &tx_buf->skb_queue, false);
+               __skb_queue_head_init(&tx_buf->skb_queue);
+               list_move_tail(&tx_buf->list, &hif_dev->tx.tx_buf);
+               hif_dev->tx.tx_buf_cnt++;
+       }
+
+       if (!ret)
+               TX_STAT_INC(buf_queued);
+
+       return ret;
+}
+
+static int hif_usb_send_tx(struct hif_device_usb *hif_dev, struct sk_buff *skb)
+{
+       struct ath9k_htc_tx_ctl *tx_ctl;
+       unsigned long flags;
+       int ret = 0;
+
+       spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
+
+       if (hif_dev->tx.flags & HIF_USB_TX_STOP) {
+               spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
+               return -ENODEV;
+       }
+
+       /* Check if the max queue count has been reached */
+       if (hif_dev->tx.tx_skb_cnt > MAX_TX_BUF_NUM) {
+               spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
+               return -ENOMEM;
+       }
+
+       spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
+
+       tx_ctl = HTC_SKB_CB(skb);
+
+       /* Mgmt/Beacon frames don't use the TX buffer pool */
+       if ((tx_ctl->type == ATH9K_HTC_MGMT) ||
+           (tx_ctl->type == ATH9K_HTC_BEACON)) {
+               ret = hif_usb_send_mgmt(hif_dev, skb);
+       }
+
+       spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
+
+       if ((tx_ctl->type == ATH9K_HTC_NORMAL) ||
+           (tx_ctl->type == ATH9K_HTC_AMPDU)) {
+               __skb_queue_tail(&hif_dev->tx.tx_skb_queue, skb);
+               hif_dev->tx.tx_skb_cnt++;
+       }
+
+       /* Check if AMPDUs have to be sent immediately */
+       if ((hif_dev->tx.tx_buf_cnt == MAX_TX_URB_NUM) &&
+           (hif_dev->tx.tx_skb_cnt < 2)) {
+               __hif_usb_tx(hif_dev);
+       }
+
+       spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
+
+       return ret;
+}
+
+static void hif_usb_start(void *hif_handle)
+{
+       struct hif_device_usb *hif_dev = (struct hif_device_usb *)hif_handle;
+       unsigned long flags;
+
+       hif_dev->flags |= HIF_USB_START;
+
+       spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
+       hif_dev->tx.flags &= ~HIF_USB_TX_STOP;
+       spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
+}
+
+static void hif_usb_stop(void *hif_handle)
+{
+       struct hif_device_usb *hif_dev = (struct hif_device_usb *)hif_handle;
+       struct tx_buf *tx_buf = NULL, *tx_buf_tmp = NULL;
+       unsigned long flags;
+
+       spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
+       ath9k_skb_queue_complete(hif_dev, &hif_dev->tx.tx_skb_queue, false);
+       hif_dev->tx.tx_skb_cnt = 0;
+       hif_dev->tx.flags |= HIF_USB_TX_STOP;
+       spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
+
+       /* The pending URBs have to be canceled. */
+       list_for_each_entry_safe(tx_buf, tx_buf_tmp,
+                                &hif_dev->tx.tx_pending, list) {
+               usb_kill_urb(tx_buf->urb);
+       }
+
+       usb_kill_anchored_urbs(&hif_dev->mgmt_submitted);
+}
+
+static int hif_usb_send(void *hif_handle, u8 pipe_id, struct sk_buff *skb)
+{
+       struct hif_device_usb *hif_dev = (struct hif_device_usb *)hif_handle;
+       int ret = 0;
+
+       switch (pipe_id) {
+       case USB_WLAN_TX_PIPE:
+               ret = hif_usb_send_tx(hif_dev, skb);
+               break;
+       case USB_REG_OUT_PIPE:
+               ret = hif_usb_send_regout(hif_dev, skb);
+               break;
+       default:
+               dev_err(&hif_dev->udev->dev,
+                       "ath9k_htc: Invalid TX pipe: %d\n", pipe_id);
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+static inline bool check_index(struct sk_buff *skb, u8 idx)
+{
+       struct ath9k_htc_tx_ctl *tx_ctl;
+
+       tx_ctl = HTC_SKB_CB(skb);
+
+       if ((tx_ctl->type == ATH9K_HTC_AMPDU) &&
+           (tx_ctl->sta_idx == idx))
+               return true;
+
+       return false;
+}
+
+static void hif_usb_sta_drain(void *hif_handle, u8 idx)
+{
+       struct hif_device_usb *hif_dev = (struct hif_device_usb *)hif_handle;
+       struct sk_buff *skb, *tmp;
+       unsigned long flags;
+
+       spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
+
+       skb_queue_walk_safe(&hif_dev->tx.tx_skb_queue, skb, tmp) {
+               if (check_index(skb, idx)) {
+                       __skb_unlink(skb, &hif_dev->tx.tx_skb_queue);
+                       ath9k_htc_txcompletion_cb(hif_dev->htc_handle,
+                                                 skb, false);
+                       hif_dev->tx.tx_skb_cnt--;
+                       TX_STAT_INC(skb_failed);
+               }
+       }
+
+       spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
+}
+
+static struct ath9k_htc_hif hif_usb = {
+       .transport = ATH9K_HIF_USB,
+       .name = "ath9k_hif_usb",
+
+       .control_ul_pipe = USB_REG_OUT_PIPE,
+       .control_dl_pipe = USB_REG_IN_PIPE,
+
+       .start = hif_usb_start,
+       .stop = hif_usb_stop,
+       .sta_drain = hif_usb_sta_drain,
+       .send = hif_usb_send,
+};
+
+static void ath9k_hif_usb_rx_stream(struct hif_device_usb *hif_dev,
+                                   struct sk_buff *skb)
+{
+       struct sk_buff *nskb, *skb_pool[MAX_PKT_NUM_IN_TRANSFER];
+       int index = 0, i = 0, len = skb->len;
+       int rx_remain_len, rx_pkt_len;
+       u16 pool_index = 0;
+       u8 *ptr;
+
+       spin_lock(&hif_dev->rx_lock);
+
+       rx_remain_len = hif_dev->rx_remain_len;
+       rx_pkt_len = hif_dev->rx_transfer_len;
+
+       if (rx_remain_len != 0) {
+               struct sk_buff *remain_skb = hif_dev->remain_skb;
+
+               if (remain_skb) {
+                       ptr = (u8 *) remain_skb->data;
+
+                       index = rx_remain_len;
+                       rx_remain_len -= hif_dev->rx_pad_len;
+                       ptr += rx_pkt_len;
+
+                       memcpy(ptr, skb->data, rx_remain_len);
+
+                       rx_pkt_len += rx_remain_len;
+                       hif_dev->rx_remain_len = 0;
+                       skb_put(remain_skb, rx_pkt_len);
+
+                       skb_pool[pool_index++] = remain_skb;
+
+               } else {
+                       index = rx_remain_len;
+               }
+       }
+
+       spin_unlock(&hif_dev->rx_lock);
+
+       while (index < len) {
+               u16 pkt_len;
+               u16 pkt_tag;
+               u16 pad_len;
+               int chk_idx;
+
+               ptr = (u8 *) skb->data;
+
+               pkt_len = get_unaligned_le16(ptr + index);
+               pkt_tag = get_unaligned_le16(ptr + index + 2);
+
+               if (pkt_tag != ATH_USB_RX_STREAM_MODE_TAG) {
+                       RX_STAT_INC(skb_dropped);
+                       return;
+               }
+
+               pad_len = 4 - (pkt_len & 0x3);
+               if (pad_len == 4)
+                       pad_len = 0;
+
+               chk_idx = index;
+               index = index + 4 + pkt_len + pad_len;
+
+               if (index > MAX_RX_BUF_SIZE) {
+                       spin_lock(&hif_dev->rx_lock);
+                       hif_dev->rx_remain_len = index - MAX_RX_BUF_SIZE;
+                       hif_dev->rx_transfer_len =
+                               MAX_RX_BUF_SIZE - chk_idx - 4;
+                       hif_dev->rx_pad_len = pad_len;
+
+                       nskb = __dev_alloc_skb(pkt_len + 32, GFP_ATOMIC);
+                       if (!nskb) {
+                               dev_err(&hif_dev->udev->dev,
+                                       "ath9k_htc: RX memory allocation error\n");
+                               spin_unlock(&hif_dev->rx_lock);
+                               goto err;
+                       }
+                       skb_reserve(nskb, 32);
+                       RX_STAT_INC(skb_allocated);
+
+                       memcpy(nskb->data, &(skb->data[chk_idx+4]),
+                              hif_dev->rx_transfer_len);
+
+                       /* Record the buffer pointer */
+                       hif_dev->remain_skb = nskb;
+                       spin_unlock(&hif_dev->rx_lock);
+               } else {
+                       nskb = __dev_alloc_skb(pkt_len + 32, GFP_ATOMIC);
+                       if (!nskb) {
+                               dev_err(&hif_dev->udev->dev,
+                                       "ath9k_htc: RX memory allocation error\n");
+                               goto err;
+                       }
+                       skb_reserve(nskb, 32);
+                       RX_STAT_INC(skb_allocated);
+
+                       memcpy(nskb->data, &(skb->data[chk_idx+4]), pkt_len);
+                       skb_put(nskb, pkt_len);
+                       skb_pool[pool_index++] = nskb;
+               }
+       }
+
+err:
+       for (i = 0; i < pool_index; i++) {
+               RX_STAT_ADD(skb_completed_bytes, skb_pool[i]->len);
+               ath9k_htc_rx_msg(hif_dev->htc_handle, skb_pool[i],
+                                skb_pool[i]->len, USB_WLAN_RX_PIPE);
+               RX_STAT_INC(skb_completed);
+       }
+}
+
+static void ath9k_hif_usb_rx_cb(struct urb *urb)
+{
+       struct sk_buff *skb = (struct sk_buff *) urb->context;
+       struct hif_device_usb *hif_dev =
+               usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0));
+       int ret;
+
+       if (!skb)
+               return;
+
+       if (!hif_dev)
+               goto free;
+
+       switch (urb->status) {
+       case 0:
+               break;
+       case -ENOENT:
+       case -ECONNRESET:
+       case -ENODEV:
+       case -ESHUTDOWN:
+               goto free;
+       default:
+               goto resubmit;
+       }
+
+       if (likely(urb->actual_length != 0)) {
+               skb_put(skb, urb->actual_length);
+               ath9k_hif_usb_rx_stream(hif_dev, skb);
+       }
+
+resubmit:
+       skb_reset_tail_pointer(skb);
+       skb_trim(skb, 0);
+
+       usb_anchor_urb(urb, &hif_dev->rx_submitted);
+       ret = usb_submit_urb(urb, GFP_ATOMIC);
+       if (ret) {
+               usb_unanchor_urb(urb);
+               goto free;
+       }
+
+       return;
+free:
+       kfree_skb(skb);
+}
+
+static void ath9k_hif_usb_reg_in_cb(struct urb *urb)
+{
+       struct sk_buff *skb = (struct sk_buff *) urb->context;
+       struct sk_buff *nskb;
+       struct hif_device_usb *hif_dev =
+               usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0));
+       int ret;
+
+       if (!skb)
+               return;
+
+       if (!hif_dev)
+               goto free;
+
+       switch (urb->status) {
+       case 0:
+               break;
+       case -ENOENT:
+       case -ECONNRESET:
+       case -ENODEV:
+       case -ESHUTDOWN:
+               goto free;
+       default:
+               skb_reset_tail_pointer(skb);
+               skb_trim(skb, 0);
+
+               goto resubmit;
+       }
+
+       if (likely(urb->actual_length != 0)) {
+               skb_put(skb, urb->actual_length);
+
+               /* Process the command first */
+               ath9k_htc_rx_msg(hif_dev->htc_handle, skb,
+                                skb->len, USB_REG_IN_PIPE);
+
+
+               nskb = alloc_skb(MAX_REG_IN_BUF_SIZE, GFP_ATOMIC);
+               if (!nskb) {
+                       dev_err(&hif_dev->udev->dev,
+                               "ath9k_htc: REG_IN memory allocation failure\n");
+                       urb->context = NULL;
+                       return;
+               }
+
+               usb_fill_int_urb(urb, hif_dev->udev,
+                                usb_rcvintpipe(hif_dev->udev,
+                                                USB_REG_IN_PIPE),
+                                nskb->data, MAX_REG_IN_BUF_SIZE,
+                                ath9k_hif_usb_reg_in_cb, nskb, 1);
+       }
+
+resubmit:
+       usb_anchor_urb(urb, &hif_dev->reg_in_submitted);
+       ret = usb_submit_urb(urb, GFP_ATOMIC);
+       if (ret) {
+               usb_unanchor_urb(urb);
+               goto free;
+       }
+
+       return;
+free:
+       kfree_skb(skb);
+       urb->context = NULL;
+}
+
+static void ath9k_hif_usb_dealloc_tx_urbs(struct hif_device_usb *hif_dev)
+{
+       struct tx_buf *tx_buf = NULL, *tx_buf_tmp = NULL;
+       unsigned long flags;
+
+       list_for_each_entry_safe(tx_buf, tx_buf_tmp,
+                                &hif_dev->tx.tx_buf, list) {
+               usb_kill_urb(tx_buf->urb);
+               list_del(&tx_buf->list);
+               usb_free_urb(tx_buf->urb);
+               kfree(tx_buf->buf);
+               kfree(tx_buf);
+       }
+
+       spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
+       hif_dev->tx.flags |= HIF_USB_TX_FLUSH;
+       spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
+
+       list_for_each_entry_safe(tx_buf, tx_buf_tmp,
+                                &hif_dev->tx.tx_pending, list) {
+               usb_kill_urb(tx_buf->urb);
+               list_del(&tx_buf->list);
+               usb_free_urb(tx_buf->urb);
+               kfree(tx_buf->buf);
+               kfree(tx_buf);
+       }
+
+       usb_kill_anchored_urbs(&hif_dev->mgmt_submitted);
+}
+
+static int ath9k_hif_usb_alloc_tx_urbs(struct hif_device_usb *hif_dev)
+{
+       struct tx_buf *tx_buf;
+       int i;
+
+       INIT_LIST_HEAD(&hif_dev->tx.tx_buf);
+       INIT_LIST_HEAD(&hif_dev->tx.tx_pending);
+       spin_lock_init(&hif_dev->tx.tx_lock);
+       __skb_queue_head_init(&hif_dev->tx.tx_skb_queue);
+       init_usb_anchor(&hif_dev->mgmt_submitted);
+
+       for (i = 0; i < MAX_TX_URB_NUM; i++) {
+               tx_buf = kzalloc(sizeof(struct tx_buf), GFP_KERNEL);
+               if (!tx_buf)
+                       goto err;
+
+               tx_buf->buf = kzalloc(MAX_TX_BUF_SIZE, GFP_KERNEL);
+               if (!tx_buf->buf)
+                       goto err;
+
+               tx_buf->urb = usb_alloc_urb(0, GFP_KERNEL);
+               if (!tx_buf->urb)
+                       goto err;
+
+               tx_buf->hif_dev = hif_dev;
+               __skb_queue_head_init(&tx_buf->skb_queue);
+
+               list_add_tail(&tx_buf->list, &hif_dev->tx.tx_buf);
+       }
+
+       hif_dev->tx.tx_buf_cnt = MAX_TX_URB_NUM;
+
+       return 0;
+err:
+       if (tx_buf) {
+               kfree(tx_buf->buf);
+               kfree(tx_buf);
+       }
+       ath9k_hif_usb_dealloc_tx_urbs(hif_dev);
+       return -ENOMEM;
+}
+
+static void ath9k_hif_usb_dealloc_rx_urbs(struct hif_device_usb *hif_dev)
+{
+       usb_kill_anchored_urbs(&hif_dev->rx_submitted);
+}
+
+static int ath9k_hif_usb_alloc_rx_urbs(struct hif_device_usb *hif_dev)
+{
+       struct urb *urb = NULL;
+       struct sk_buff *skb = NULL;
+       int i, ret;
+
+       init_usb_anchor(&hif_dev->rx_submitted);
+       spin_lock_init(&hif_dev->rx_lock);
+
+       for (i = 0; i < MAX_RX_URB_NUM; i++) {
+
+               /* Allocate URB */
+               urb = usb_alloc_urb(0, GFP_KERNEL);
+               if (urb == NULL) {
+                       ret = -ENOMEM;
+                       goto err_urb;
+               }
+
+               /* Allocate buffer */
+               skb = alloc_skb(MAX_RX_BUF_SIZE, GFP_KERNEL);
+               if (!skb) {
+                       ret = -ENOMEM;
+                       goto err_skb;
+               }
+
+               usb_fill_bulk_urb(urb, hif_dev->udev,
+                                 usb_rcvbulkpipe(hif_dev->udev,
+                                                 USB_WLAN_RX_PIPE),
+                                 skb->data, MAX_RX_BUF_SIZE,
+                                 ath9k_hif_usb_rx_cb, skb);
+
+               /* Anchor URB */
+               usb_anchor_urb(urb, &hif_dev->rx_submitted);
+
+               /* Submit URB */
+               ret = usb_submit_urb(urb, GFP_KERNEL);
+               if (ret) {
+                       usb_unanchor_urb(urb);
+                       goto err_submit;
+               }
+
+               /*
+                * Drop reference count.
+                * This ensures that the URB is freed when killing them.
+                */
+               usb_free_urb(urb);
+       }
+
+       return 0;
+
+err_submit:
+       kfree_skb(skb);
+err_skb:
+       usb_free_urb(urb);
+err_urb:
+       ath9k_hif_usb_dealloc_rx_urbs(hif_dev);
+       return ret;
+}
+
+static void ath9k_hif_usb_dealloc_reg_in_urbs(struct hif_device_usb *hif_dev)
+{
+       usb_kill_anchored_urbs(&hif_dev->reg_in_submitted);
+}
+
+static int ath9k_hif_usb_alloc_reg_in_urbs(struct hif_device_usb *hif_dev)
+{
+       struct urb *urb = NULL;
+       struct sk_buff *skb = NULL;
+       int i, ret;
+
+       init_usb_anchor(&hif_dev->reg_in_submitted);
+
+       for (i = 0; i < MAX_REG_IN_URB_NUM; i++) {
+
+               /* Allocate URB */
+               urb = usb_alloc_urb(0, GFP_KERNEL);
+               if (urb == NULL) {
+                       ret = -ENOMEM;
+                       goto err_urb;
+               }
+
+               /* Allocate buffer */
+               skb = alloc_skb(MAX_REG_IN_BUF_SIZE, GFP_KERNEL);
+               if (!skb) {
+                       ret = -ENOMEM;
+                       goto err_skb;
+               }
+
+               usb_fill_int_urb(urb, hif_dev->udev,
+                                 usb_rcvintpipe(hif_dev->udev,
+                                                 USB_REG_IN_PIPE),
+                                 skb->data, MAX_REG_IN_BUF_SIZE,
+                                 ath9k_hif_usb_reg_in_cb, skb, 1);
+
+               /* Anchor URB */
+               usb_anchor_urb(urb, &hif_dev->reg_in_submitted);
+
+               /* Submit URB */
+               ret = usb_submit_urb(urb, GFP_KERNEL);
+               if (ret) {
+                       usb_unanchor_urb(urb);
+                       goto err_submit;
+               }
+
+               /*
+                * Drop reference count.
+                * This ensures that the URB is freed when killing them.
+                */
+               usb_free_urb(urb);
+       }
+
+       return 0;
+
+err_submit:
+       kfree_skb(skb);
+err_skb:
+       usb_free_urb(urb);
+err_urb:
+       ath9k_hif_usb_dealloc_reg_in_urbs(hif_dev);
+       return ret;
+}
+
+static int ath9k_hif_usb_alloc_urbs(struct hif_device_usb *hif_dev)
+{
+       /* Register Write */
+       init_usb_anchor(&hif_dev->regout_submitted);
+
+       /* TX */
+       if (ath9k_hif_usb_alloc_tx_urbs(hif_dev) < 0)
+               goto err;
+
+       /* RX */
+       if (ath9k_hif_usb_alloc_rx_urbs(hif_dev) < 0)
+               goto err_rx;
+
+       /* Register Read */
+       if (ath9k_hif_usb_alloc_reg_in_urbs(hif_dev) < 0)
+               goto err_reg;
+
+       return 0;
+err_reg:
+       ath9k_hif_usb_dealloc_rx_urbs(hif_dev);
+err_rx:
+       ath9k_hif_usb_dealloc_tx_urbs(hif_dev);
+err:
+       return -ENOMEM;
+}
+
+static void ath9k_hif_usb_dealloc_urbs(struct hif_device_usb *hif_dev)
+{
+       usb_kill_anchored_urbs(&hif_dev->regout_submitted);
+       ath9k_hif_usb_dealloc_reg_in_urbs(hif_dev);
+       ath9k_hif_usb_dealloc_tx_urbs(hif_dev);
+       ath9k_hif_usb_dealloc_rx_urbs(hif_dev);
+}
+
+static int ath9k_hif_usb_download_fw(struct hif_device_usb *hif_dev)
+{
+       int transfer, err;
+       const void *data = hif_dev->fw_data;
+       size_t len = hif_dev->fw_size;
+       u32 addr = AR9271_FIRMWARE;
+       u8 *buf = kzalloc(4096, GFP_KERNEL);
+       u32 firm_offset;
+
+       if (!buf)
+               return -ENOMEM;
+
+       while (len) {
+               transfer = min_t(size_t, len, 4096);
+               memcpy(buf, data, transfer);
+
+               err = usb_control_msg(hif_dev->udev,
+                                     usb_sndctrlpipe(hif_dev->udev, 0),
+                                     FIRMWARE_DOWNLOAD, 0x40 | USB_DIR_OUT,
+                                     addr >> 8, 0, buf, transfer, HZ);
+               if (err < 0) {
+                       kfree(buf);
+                       return err;
+               }
+
+               len -= transfer;
+               data += transfer;
+               addr += transfer;
+       }
+       kfree(buf);
+
+       if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info))
+               firm_offset = AR7010_FIRMWARE_TEXT;
+       else
+               firm_offset = AR9271_FIRMWARE_TEXT;
+
+       /*
+        * Issue FW download complete command to firmware.
+        */
+       err = usb_control_msg(hif_dev->udev, usb_sndctrlpipe(hif_dev->udev, 0),
+                             FIRMWARE_DOWNLOAD_COMP,
+                             0x40 | USB_DIR_OUT,
+                             firm_offset >> 8, 0, NULL, 0, HZ);
+       if (err)
+               return -EIO;
+
+       dev_info(&hif_dev->udev->dev, "ath9k_htc: Transferred FW: %s, size: %ld\n",
+                hif_dev->fw_name, (unsigned long) hif_dev->fw_size);
+
+       return 0;
+}
+
+static int ath9k_hif_usb_dev_init(struct hif_device_usb *hif_dev)
+{
+       int ret;
+
+       ret = ath9k_hif_usb_download_fw(hif_dev);
+       if (ret) {
+               dev_err(&hif_dev->udev->dev,
+                       "ath9k_htc: Firmware - %s download failed\n",
+                       hif_dev->fw_name);
+               return ret;
+       }
+
+       /* Alloc URBs */
+       ret = ath9k_hif_usb_alloc_urbs(hif_dev);
+       if (ret) {
+               dev_err(&hif_dev->udev->dev,
+                       "ath9k_htc: Unable to allocate URBs\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static void ath9k_hif_usb_dev_deinit(struct hif_device_usb *hif_dev)
+{
+       ath9k_hif_usb_dealloc_urbs(hif_dev);
+}
+
+/*
+ * If initialization fails or the FW cannot be retrieved,
+ * detach the device.
+ */
+static void ath9k_hif_usb_firmware_fail(struct hif_device_usb *hif_dev)
+{
+       struct device *dev = &hif_dev->udev->dev;
+       struct device *parent = dev->parent;
+
+       complete_all(&hif_dev->fw_done);
+
+       if (parent)
+               device_lock(parent);
+
+       device_release_driver(dev);
+
+       if (parent)
+               device_unlock(parent);
+}
+
+static void ath9k_hif_usb_firmware_cb(const struct firmware *fw, void *context)
+{
+       struct hif_device_usb *hif_dev = context;
+       int ret;
+
+       if (!fw) {
+               dev_err(&hif_dev->udev->dev,
+                       "ath9k_htc: Failed to get firmware %s\n",
+                       hif_dev->fw_name);
+               goto err_fw;
+       }
+
+       hif_dev->htc_handle = ath9k_htc_hw_alloc(hif_dev, &hif_usb,
+                                                &hif_dev->udev->dev);
+       if (hif_dev->htc_handle == NULL)
+               goto err_dev_alloc;
+
+       hif_dev->fw_data = fw->data;
+       hif_dev->fw_size = fw->size;
+
+       /* Proceed with initialization */
+
+       ret = ath9k_hif_usb_dev_init(hif_dev);
+       if (ret)
+               goto err_dev_init;
+
+       ret = ath9k_htc_hw_init(hif_dev->htc_handle,
+                               &hif_dev->interface->dev,
+                               hif_dev->usb_device_id->idProduct,
+                               hif_dev->udev->product,
+                               hif_dev->usb_device_id->driver_info);
+       if (ret) {
+               ret = -EINVAL;
+               goto err_htc_hw_init;
+       }
+
+       release_firmware(fw);
+       hif_dev->flags |= HIF_USB_READY;
+       complete_all(&hif_dev->fw_done);
+
+       return;
+
+err_htc_hw_init:
+       ath9k_hif_usb_dev_deinit(hif_dev);
+err_dev_init:
+       ath9k_htc_hw_free(hif_dev->htc_handle);
+err_dev_alloc:
+       release_firmware(fw);
+err_fw:
+       ath9k_hif_usb_firmware_fail(hif_dev);
+}
+
+/*
+ * An exact copy of the function from zd1211rw.
+ */
+static int send_eject_command(struct usb_interface *interface)
+{
+       struct usb_device *udev = interface_to_usbdev(interface);
+       struct usb_host_interface *iface_desc = &interface->altsetting[0];
+       struct usb_endpoint_descriptor *endpoint;
+       unsigned char *cmd;
+       u8 bulk_out_ep;
+       int r;
+
+       /* Find bulk out endpoint */
+       for (r = 1; r >= 0; r--) {
+               endpoint = &iface_desc->endpoint[r].desc;
+               if (usb_endpoint_dir_out(endpoint) &&
+                   usb_endpoint_xfer_bulk(endpoint)) {
+                       bulk_out_ep = endpoint->bEndpointAddress;
+                       break;
+               }
+       }
+       if (r == -1) {
+               dev_err(&udev->dev,
+                       "ath9k_htc: Could not find bulk out endpoint\n");
+               return -ENODEV;
+       }
+
+       cmd = kzalloc(31, GFP_KERNEL);
+       if (cmd == NULL)
+               return -ENODEV;
+
+       /* USB bulk command block */
+       cmd[0] = 0x55;  /* bulk command signature */
+       cmd[1] = 0x53;  /* bulk command signature */
+       cmd[2] = 0x42;  /* bulk command signature */
+       cmd[3] = 0x43;  /* bulk command signature */
+       cmd[14] = 6;    /* command length */
+
+       cmd[15] = 0x1b; /* SCSI command: START STOP UNIT */
+       cmd[19] = 0x2;  /* eject disc */
+
+       dev_info(&udev->dev, "Ejecting storage device...\n");
+       r = usb_bulk_msg(udev, usb_sndbulkpipe(udev, bulk_out_ep),
+               cmd, 31, NULL, 2000);
+       kfree(cmd);
+       if (r)
+               return r;
+
+       /* At this point, the device disconnects and reconnects with the real
+        * ID numbers. */
+
+       usb_set_intfdata(interface, NULL);
+       return 0;
+}
+
+static int ath9k_hif_usb_probe(struct usb_interface *interface,
+                              const struct usb_device_id *id)
+{
+       struct usb_device *udev = interface_to_usbdev(interface);
+       struct hif_device_usb *hif_dev;
+       int ret = 0;
+
+       if (id->driver_info == STORAGE_DEVICE)
+               return send_eject_command(interface);
+
+       hif_dev = kzalloc(sizeof(struct hif_device_usb), GFP_KERNEL);
+       if (!hif_dev) {
+               ret = -ENOMEM;
+               goto err_alloc;
+       }
+
+       usb_get_dev(udev);
+
+       hif_dev->udev = udev;
+       hif_dev->interface = interface;
+       hif_dev->usb_device_id = id;
+#ifdef CONFIG_PM
+       udev->reset_resume = 1;
+#endif
+       usb_set_intfdata(interface, hif_dev);
+
+       init_completion(&hif_dev->fw_done);
+
+       /* Find out which firmware to load */
+
+       if (IS_AR7010_DEVICE(id->driver_info))
+               hif_dev->fw_name = FIRMWARE_AR7010_1_1;
+       else
+               hif_dev->fw_name = FIRMWARE_AR9271;
+
+       ret = request_firmware_nowait(THIS_MODULE, true, hif_dev->fw_name,
+                                     &hif_dev->udev->dev, GFP_KERNEL,
+                                     hif_dev, ath9k_hif_usb_firmware_cb);
+       if (ret) {
+               dev_err(&hif_dev->udev->dev,
+                       "ath9k_htc: Async request for firmware %s failed\n",
+                       hif_dev->fw_name);
+               goto err_fw_req;
+       }
+
+       dev_info(&hif_dev->udev->dev, "ath9k_htc: Firmware %s requested\n",
+                hif_dev->fw_name);
+
+       return 0;
+
+err_fw_req:
+       usb_set_intfdata(interface, NULL);
+       kfree(hif_dev);
+       usb_put_dev(udev);
+err_alloc:
+       return ret;
+}
+
+static void ath9k_hif_usb_reboot(struct usb_device *udev)
+{
+       u32 reboot_cmd = 0xffffffff;
+       void *buf;
+       int ret;
+
+       buf = kmemdup(&reboot_cmd, 4, GFP_KERNEL);
+       if (!buf)
+               return;
+
+       ret = usb_interrupt_msg(udev, usb_sndintpipe(udev, USB_REG_OUT_PIPE),
+                          buf, 4, NULL, HZ);
+       if (ret)
+               dev_err(&udev->dev, "ath9k_htc: USB reboot failed\n");
+
+       kfree(buf);
+}
+
+static void ath9k_hif_usb_disconnect(struct usb_interface *interface)
+{
+       struct usb_device *udev = interface_to_usbdev(interface);
+       struct hif_device_usb *hif_dev = usb_get_intfdata(interface);
+       bool unplugged = (udev->state == USB_STATE_NOTATTACHED) ? true : false;
+
+       if (!hif_dev)
+               return;
+
+       wait_for_completion(&hif_dev->fw_done);
+
+       if (hif_dev->flags & HIF_USB_READY) {
+               ath9k_htc_hw_deinit(hif_dev->htc_handle, unplugged);
+               ath9k_htc_hw_free(hif_dev->htc_handle);
+               ath9k_hif_usb_dev_deinit(hif_dev);
+       }
+
+       usb_set_intfdata(interface, NULL);
+
+       /* If firmware was loaded we should drop it
+        * go back to first stage bootloader. */
+       if (!unplugged && (hif_dev->flags & HIF_USB_READY))
+               ath9k_hif_usb_reboot(udev);
+
+       kfree(hif_dev);
+       dev_info(&udev->dev, "ath9k_htc: USB layer deinitialized\n");
+       usb_put_dev(udev);
+}
+
+#ifdef CONFIG_PM
+static int ath9k_hif_usb_suspend(struct usb_interface *interface,
+                                pm_message_t message)
+{
+       struct hif_device_usb *hif_dev = usb_get_intfdata(interface);
+
+       /*
+        * The device has to be set to FULLSLEEP mode in case no
+        * interface is up.
+        */
+       if (!(hif_dev->flags & HIF_USB_START))
+               ath9k_htc_suspend(hif_dev->htc_handle);
+
+       wait_for_completion(&hif_dev->fw_done);
+
+       if (hif_dev->flags & HIF_USB_READY)
+               ath9k_hif_usb_dealloc_urbs(hif_dev);
+
+       return 0;
+}
+
+static int ath9k_hif_usb_resume(struct usb_interface *interface)
+{
+       struct hif_device_usb *hif_dev = usb_get_intfdata(interface);
+       struct htc_target *htc_handle = hif_dev->htc_handle;
+       int ret;
+       const struct firmware *fw;
+
+       ret = ath9k_hif_usb_alloc_urbs(hif_dev);
+       if (ret)
+               return ret;
+
+       if (hif_dev->flags & HIF_USB_READY) {
+               /* request cached firmware during suspend/resume cycle */
+               ret = request_firmware(&fw, hif_dev->fw_name,
+                                      &hif_dev->udev->dev);
+               if (ret)
+                       goto fail_resume;
+
+               hif_dev->fw_data = fw->data;
+               hif_dev->fw_size = fw->size;
+               ret = ath9k_hif_usb_download_fw(hif_dev);
+               release_firmware(fw);
+               if (ret)
+                       goto fail_resume;
+       } else {
+               ath9k_hif_usb_dealloc_urbs(hif_dev);
+               return -EIO;
+       }
+
+       mdelay(100);
+
+       ret = ath9k_htc_resume(htc_handle);
+
+       if (ret)
+               goto fail_resume;
+
+       return 0;
+
+fail_resume:
+       ath9k_hif_usb_dealloc_urbs(hif_dev);
+
+       return ret;
+}
+#endif
+
+static struct usb_driver ath9k_hif_usb_driver = {
+       .name = KBUILD_MODNAME,
+       .probe = ath9k_hif_usb_probe,
+       .disconnect = ath9k_hif_usb_disconnect,
+#ifdef CONFIG_PM
+       .suspend = ath9k_hif_usb_suspend,
+       .resume = ath9k_hif_usb_resume,
+       .reset_resume = ath9k_hif_usb_resume,
+#endif
+       .id_table = ath9k_hif_usb_ids,
+       .soft_unbind = 1,
+       .disable_hub_initiated_lpm = 1,
+};
+
+int ath9k_hif_usb_init(void)
+{
+       return usb_register(&ath9k_hif_usb_driver);
+}
+
+void ath9k_hif_usb_exit(void)
+{
+       usb_deregister(&ath9k_hif_usb_driver);
+}