ath9k_htc: Support for AR9271 chipset.

Features:

 * Station mode
 * IBSS mode
 * Monitor mode
 * Legacy support
 * HT support
 * TX/RX 11n Aggregation
 * HW encryption
 * LED
 * Suspend/Resume

For more information: http://wireless.kernel.org/en/users/Drivers/ath9k_htc

Signed-off-by: Sujith <Sujith.Manoharan@atheros.com>
Signed-off-by: Vasanthakumar Thiagarajan <vasanth@atheros.com>
Signed-off-by: Senthil Balasubramanian <senthilkumar@atheros.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c
new file mode 100644
index 0000000..fc4f6e8
--- /dev/null
+++ b/drivers/net/wireless/ath/ath9k/hif_usb.c
@@ -0,0 +1,993 @@
+/*
+ * Copyright (c) 2010 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 "htc.h"
+
+#define ATH9K_FW_USB_DEV(devid, fw)					\
+	{ USB_DEVICE(0x0cf3, devid), .driver_info = (unsigned long) fw }
+
+static struct usb_device_id ath9k_hif_usb_ids[] = {
+	ATH9K_FW_USB_DEV(0x9271, "ar9271.fw"),
+	{ },
+};
+
+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;
+	struct hif_device_usb *hif_dev = cmd->hif_dev;
+
+	if (!hif_dev) {
+		usb_free_urb(urb);
+		if (cmd) {
+			if (cmd->skb)
+				dev_kfree_skb_any(cmd->skb);
+			kfree(cmd);
+		}
+		return;
+	}
+
+	switch (urb->status) {
+	case 0:
+		break;
+	case -ENOENT:
+	case -ECONNRESET:
+		break;
+	case -ENODEV:
+	case -ESHUTDOWN:
+		return;
+	default:
+		break;
+	}
+
+	if (cmd) {
+		ath9k_htc_txcompletion_cb(cmd->hif_dev->htc_handle,
+					  cmd->skb, 1);
+		kfree(cmd);
+		usb_free_urb(urb);
+	}
+}
+
+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);
+
+	ret = usb_submit_urb(urb, GFP_KERNEL);
+	if (ret) {
+		usb_free_urb(urb);
+		kfree(cmd);
+	}
+
+	return ret;
+}
+
+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 = tx_buf->hif_dev;
+	struct sk_buff *skb;
+	bool drop, flush;
+
+	if (!hif_dev)
+		return;
+
+	switch (urb->status) {
+	case 0:
+		break;
+	case -ENOENT:
+	case -ECONNRESET:
+		break;
+	case -ENODEV:
+	case -ESHUTDOWN:
+		return;
+	default:
+		break;
+	}
+
+	if (tx_buf) {
+		spin_lock(&hif_dev->tx.tx_lock);
+		drop = !!(hif_dev->tx.flags & HIF_USB_TX_STOP);
+		flush = !!(hif_dev->tx.flags & HIF_USB_TX_FLUSH);
+		spin_unlock(&hif_dev->tx.tx_lock);
+
+		while ((skb = __skb_dequeue(&tx_buf->skb_queue)) != NULL) {
+			if (!drop && !flush) {
+				ath9k_htc_txcompletion_cb(hif_dev->htc_handle,
+							  skb, 1);
+				TX_STAT_INC(skb_completed);
+			} else {
+				dev_kfree_skb_any(skb);
+			}
+		}
+
+		if (flush)
+			return;
+
+		tx_buf->len = tx_buf->offset = 0;
+		__skb_queue_head_init(&tx_buf->skb_queue);
+
+		spin_lock(&hif_dev->tx.tx_lock);
+		list_del(&tx_buf->list);
+		list_add_tail(&tx_buf->list, &hif_dev->tx.tx_buf);
+		hif_dev->tx.tx_buf_cnt++;
+		if (!drop)
+			__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 *hdr, tx_skb_cnt = 0;
+	u8 *buf;
+
+	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_del(&tx_buf->list);
+	list_add_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 = (u16 *)buf;
+		*hdr++ = nskb->len;
+		*hdr++ = 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;
+		__skb_queue_purge(&tx_buf->skb_queue);
+		__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;
+
+	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;
+	}
+
+	__skb_queue_tail(&hif_dev->tx.tx_skb_queue, skb);
+	hif_dev->tx.tx_skb_cnt++;
+
+	/* Send normal frames immediately */
+	if (!tx_ctl || (tx_ctl && (tx_ctl->type == ATH9K_HTC_NORMAL)))
+		__hif_usb_tx(hif_dev);
+
+	/* Check if AMPDUs have to be sent immediately */
+	if (tx_ctl && (tx_ctl->type == ATH9K_HTC_AMPDU) &&
+	    (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 0;
+}
+
+static void hif_usb_start(void *hif_handle, u8 pipe_id)
+{
+	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, u8 pipe_id)
+{
+	struct hif_device_usb *hif_dev = (struct hif_device_usb *)hif_handle;
+	unsigned long flags;
+
+	spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
+	__skb_queue_purge(&hif_dev->tx.tx_skb_queue);
+	hif_dev->tx.tx_skb_cnt = 0;
+	hif_dev->tx.flags |= HIF_USB_TX_STOP;
+	spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
+}
+
+static int hif_usb_send(void *hif_handle, u8 pipe_id, struct sk_buff *skb,
+			struct ath9k_htc_tx_ctl *tx_ctl)
+{
+	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, tx_ctl);
+		break;
+	case USB_REG_OUT_PIPE:
+		ret = hif_usb_send_regout(hif_dev, skb);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+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,
+	.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[8];
+	int index = 0, i = 0, chk_idx, len = skb->len;
+	int rx_remain_len = 0, rx_pkt_len = 0;
+	u16 pkt_len, pkt_tag, pool_index = 0;
+	u8 *ptr;
+
+	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;
+		}
+	}
+
+	while (index < len) {
+		ptr = (u8 *) skb->data;
+
+		pkt_len = ptr[index] + (ptr[index+1] << 8);
+		pkt_tag = ptr[index+2] + (ptr[index+3] << 8);
+
+		if (pkt_tag == ATH_USB_RX_STREAM_MODE_TAG) {
+			u16 pad_len;
+
+			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) {
+				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");
+					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;
+			} 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;
+			}
+		} else {
+			RX_STAT_INC(skb_dropped);
+			dev_kfree_skb_any(skb);
+			return;
+		}
+	}
+
+err:
+	dev_kfree_skb_any(skb);
+
+	for (i = 0; i < pool_index; i++) {
+		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 sk_buff *nskb;
+	struct hif_device_usb *hif_dev = (struct hif_device_usb *)
+		usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0));
+	int ret;
+
+	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);
+
+		nskb = __dev_alloc_skb(MAX_RX_BUF_SIZE, GFP_ATOMIC);
+		if (!nskb)
+			goto resubmit;
+
+		usb_fill_bulk_urb(urb, hif_dev->udev,
+				  usb_rcvbulkpipe(hif_dev->udev,
+						  USB_WLAN_RX_PIPE),
+				  nskb->data, MAX_RX_BUF_SIZE,
+				  ath9k_hif_usb_rx_cb, nskb);
+
+		ret = usb_submit_urb(urb, GFP_ATOMIC);
+		if (ret) {
+			dev_kfree_skb_any(nskb);
+			goto free;
+		}
+
+		ath9k_hif_usb_rx_stream(hif_dev, skb);
+		return;
+	}
+
+resubmit:
+	skb_reset_tail_pointer(skb);
+	skb_trim(skb, 0);
+
+	ret = usb_submit_urb(urb, GFP_ATOMIC);
+	if (ret)
+		goto free;
+
+	return;
+free:
+	dev_kfree_skb_any(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 = (struct hif_device_usb *)
+		usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0));
+	int ret;
+
+	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);
+
+		nskb = __dev_alloc_skb(MAX_REG_IN_BUF_SIZE, GFP_ATOMIC);
+		if (!nskb)
+			goto resubmit;
+
+		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);
+
+		ret = usb_submit_urb(urb, GFP_ATOMIC);
+		if (ret) {
+			dev_kfree_skb_any(nskb);
+			goto free;
+		}
+
+		ath9k_htc_rx_msg(hif_dev->htc_handle, skb,
+				 skb->len, USB_REG_IN_PIPE);
+
+		return;
+	}
+
+resubmit:
+	skb_reset_tail_pointer(skb);
+	skb_trim(skb, 0);
+
+	ret = usb_submit_urb(urb, GFP_ATOMIC);
+	if (ret)
+		goto free;
+
+	return;
+free:
+	dev_kfree_skb_any(skb);
+}
+
+static void ath9k_hif_usb_dealloc_tx_urbs(struct hif_device_usb *hif_dev)
+{
+	unsigned long flags;
+	struct tx_buf *tx_buf = NULL, *tx_buf_tmp = NULL;
+
+	list_for_each_entry_safe(tx_buf, tx_buf_tmp, &hif_dev->tx.tx_buf, list) {
+		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);
+	}
+
+	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);
+}
+
+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);
+
+	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:
+	ath9k_hif_usb_dealloc_tx_urbs(hif_dev);
+	return -ENOMEM;
+}
+
+static void ath9k_hif_usb_dealloc_rx_skbs(struct hif_device_usb *hif_dev)
+{
+	int i;
+
+	for (i = 0; i < MAX_RX_URB_NUM; i++) {
+		if (hif_dev->wlan_rx_data_urb[i]) {
+			if (hif_dev->wlan_rx_data_urb[i]->transfer_buffer)
+				dev_kfree_skb_any((void *)
+					  hif_dev->wlan_rx_data_urb[i]->context);
+		}
+	}
+}
+
+static void ath9k_hif_usb_dealloc_rx_urbs(struct hif_device_usb *hif_dev)
+{
+	int i;
+
+	for (i = 0; i < MAX_RX_URB_NUM; i++) {
+		if (hif_dev->wlan_rx_data_urb[i]) {
+			usb_kill_urb(hif_dev->wlan_rx_data_urb[i]);
+			usb_free_urb(hif_dev->wlan_rx_data_urb[i]);
+			hif_dev->wlan_rx_data_urb[i] = NULL;
+		}
+	}
+}
+
+static int ath9k_hif_usb_prep_rx_urb(struct hif_device_usb *hif_dev,
+				     struct urb *urb)
+{
+	struct sk_buff *skb;
+
+	skb = __dev_alloc_skb(MAX_RX_BUF_SIZE, GFP_KERNEL);
+	if (!skb)
+		return -ENOMEM;
+
+	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);
+	return 0;
+}
+
+static int ath9k_hif_usb_alloc_rx_urbs(struct hif_device_usb *hif_dev)
+{
+	int i, ret;
+
+	for (i = 0; i < MAX_RX_URB_NUM; i++) {
+
+		/* Allocate URB */
+		hif_dev->wlan_rx_data_urb[i] = usb_alloc_urb(0, GFP_KERNEL);
+		if (hif_dev->wlan_rx_data_urb[i] == NULL) {
+			ret = -ENOMEM;
+			goto err_rx_urb;
+		}
+
+		/* Allocate buffer */
+		ret = ath9k_hif_usb_prep_rx_urb(hif_dev,
+						hif_dev->wlan_rx_data_urb[i]);
+		if (ret)
+			goto err_rx_urb;
+
+		/* Submit URB */
+		ret = usb_submit_urb(hif_dev->wlan_rx_data_urb[i], GFP_KERNEL);
+		if (ret)
+			goto err_rx_urb;
+
+	}
+
+	return 0;
+
+err_rx_urb:
+	ath9k_hif_usb_dealloc_rx_skbs(hif_dev);
+	ath9k_hif_usb_dealloc_rx_urbs(hif_dev);
+	return ret;
+}
+
+static void ath9k_hif_usb_dealloc_reg_in_urb(struct hif_device_usb *hif_dev)
+{
+	if (hif_dev->reg_in_urb) {
+		usb_kill_urb(hif_dev->reg_in_urb);
+		usb_free_urb(hif_dev->reg_in_urb);
+		hif_dev->reg_in_urb = NULL;
+	}
+}
+
+static int ath9k_hif_usb_alloc_reg_in_urb(struct hif_device_usb *hif_dev)
+{
+	struct sk_buff *skb;
+
+	hif_dev->reg_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (hif_dev->reg_in_urb == NULL)
+		return -ENOMEM;
+
+	skb = __dev_alloc_skb(MAX_REG_IN_BUF_SIZE, GFP_KERNEL);
+	if (!skb)
+		goto err;
+
+	usb_fill_int_urb(hif_dev->reg_in_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);
+
+	if (usb_submit_urb(hif_dev->reg_in_urb, GFP_KERNEL) != 0)
+		goto err_skb;
+
+	return 0;
+
+err_skb:
+	dev_kfree_skb_any(skb);
+err:
+	ath9k_hif_usb_dealloc_reg_in_urb(hif_dev);
+	return -ENOMEM;
+}
+
+static int ath9k_hif_usb_alloc_urbs(struct hif_device_usb *hif_dev)
+{
+	/* 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;
+
+	/* Register Read/Write */
+	if (ath9k_hif_usb_alloc_reg_in_urb(hif_dev) < 0)
+		goto err;
+
+	return 0;
+err:
+	return -ENOMEM;
+}
+
+static int ath9k_hif_usb_download_fw(struct hif_device_usb *hif_dev)
+{
+	int transfer, err;
+	const void *data = hif_dev->firmware->data;
+	size_t len = hif_dev->firmware->size;
+	u32 addr = AR9271_FIRMWARE;
+	u8 *buf = kzalloc(4096, GFP_KERNEL);
+
+	if (!buf)
+		return -ENOMEM;
+
+	while (len) {
+		transfer = min_t(int, 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);
+
+	/*
+	 * 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,
+			      AR9271_FIRMWARE_TEXT >> 8, 0, NULL, 0, HZ);
+	if (err)
+		return -EIO;
+
+	dev_info(&hif_dev->udev->dev, "ath9k_htc: Transferred FW: %s, size: %ld\n",
+		 "ar9271.fw", (unsigned long) hif_dev->firmware->size);
+
+	return 0;
+}
+
+static int ath9k_hif_usb_dev_init(struct hif_device_usb *hif_dev,
+				  const char *fw_name)
+{
+	int ret;
+
+	/* Request firmware */
+	ret = request_firmware(&hif_dev->firmware, fw_name, &hif_dev->udev->dev);
+	if (ret) {
+		dev_err(&hif_dev->udev->dev,
+			"ath9k_htc: Firmware - %s not found\n", fw_name);
+		goto err_fw_req;
+	}
+
+	/* Download firmware */
+	ret = ath9k_hif_usb_download_fw(hif_dev);
+	if (ret) {
+		dev_err(&hif_dev->udev->dev,
+			"ath9k_htc: Firmware - %s download failed\n", fw_name);
+		goto err_fw_download;
+	}
+
+	/* 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");
+		goto err_urb;
+	}
+
+	return 0;
+
+err_urb:
+	/* Nothing */
+err_fw_download:
+	release_firmware(hif_dev->firmware);
+err_fw_req:
+	hif_dev->firmware = NULL;
+	return ret;
+}
+
+static void ath9k_hif_usb_dealloc_urbs(struct hif_device_usb *hif_dev)
+{
+	ath9k_hif_usb_dealloc_reg_in_urb(hif_dev);
+	ath9k_hif_usb_dealloc_tx_urbs(hif_dev);
+	ath9k_hif_usb_dealloc_rx_urbs(hif_dev);
+}
+
+static void ath9k_hif_usb_dev_deinit(struct hif_device_usb *hif_dev)
+{
+	ath9k_hif_usb_dealloc_urbs(hif_dev);
+	if (hif_dev->firmware)
+		release_firmware(hif_dev->firmware);
+}
+
+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;
+	const char *fw_name = (const char *) id->driver_info;
+	int ret = 0;
+
+	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->device_id = id->idProduct;
+#ifdef CONFIG_PM
+	udev->reset_resume = 1;
+#endif
+	usb_set_intfdata(interface, hif_dev);
+
+	ret = ath9k_hif_usb_dev_init(hif_dev, fw_name);
+	if (ret) {
+		ret = -EINVAL;
+		goto err_hif_init_usb;
+	}
+
+	hif_dev->htc_handle = ath9k_htc_hw_alloc(hif_dev);
+	if (hif_dev->htc_handle == NULL) {
+		ret = -ENOMEM;
+		goto err_htc_hw_alloc;
+	}
+
+	ret = ath9k_htc_hw_init(&hif_usb, hif_dev->htc_handle, hif_dev,
+				&hif_dev->udev->dev, hif_dev->device_id,
+				ATH9K_HIF_USB);
+	if (ret) {
+		ret = -EINVAL;
+		goto err_htc_hw_init;
+	}
+
+	dev_info(&hif_dev->udev->dev, "ath9k_htc: USB layer initialized\n");
+
+	return 0;
+
+err_htc_hw_init:
+	ath9k_htc_hw_free(hif_dev->htc_handle);
+err_htc_hw_alloc:
+	ath9k_hif_usb_dev_deinit(hif_dev);
+err_hif_init_usb:
+	usb_set_intfdata(interface, NULL);
+	kfree(hif_dev);
+	usb_put_dev(udev);
+err_alloc:
+	return ret;
+}
+
+static void ath9k_hif_usb_disconnect(struct usb_interface *interface)
+{
+	struct usb_device *udev = interface_to_usbdev(interface);
+	struct hif_device_usb *hif_dev =
+		(struct hif_device_usb *) usb_get_intfdata(interface);
+
+	if (hif_dev) {
+		ath9k_htc_hw_deinit(hif_dev->htc_handle, true);
+		ath9k_htc_hw_free(hif_dev->htc_handle);
+		ath9k_hif_usb_dev_deinit(hif_dev);
+		usb_set_intfdata(interface, NULL);
+	}
+
+	if (hif_dev->flags & HIF_USB_START)
+		usb_reset_device(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 =
+		(struct hif_device_usb *) usb_get_intfdata(interface);
+
+	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 =
+		(struct hif_device_usb *) usb_get_intfdata(interface);
+	int ret;
+
+	ret = ath9k_hif_usb_alloc_urbs(hif_dev);
+	if (ret)
+		return ret;
+
+	if (hif_dev->firmware) {
+		ret = ath9k_hif_usb_download_fw(hif_dev);
+		if (ret)
+			goto fail_resume;
+	} else {
+		ath9k_hif_usb_dealloc_urbs(hif_dev);
+		return -EIO;
+	}
+
+	mdelay(100);
+
+	ret = ath9k_htc_resume(hif_dev->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 = "ath9k_hif_usb",
+	.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,
+};
+
+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);
+}