ath9k_htc: Support for AR9271 chipset.
authorSujith <Sujith.Manoharan@atheros.com>
Wed, 17 Mar 2010 08:55:25 +0000 (14:25 +0530)
committerJohn W. Linville <linville@tuxdriver.com>
Tue, 23 Mar 2010 20:50:17 +0000 (16:50 -0400)
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>
18 files changed:
drivers/net/wireless/ath/Kconfig
drivers/net/wireless/ath/ath9k/Kconfig
drivers/net/wireless/ath/ath9k/Makefile
drivers/net/wireless/ath/ath9k/common.c
drivers/net/wireless/ath/ath9k/common.h
drivers/net/wireless/ath/ath9k/hif_usb.c [new file with mode: 0644]
drivers/net/wireless/ath/ath9k/hif_usb.h [new file with mode: 0644]
drivers/net/wireless/ath/ath9k/htc.h [new file with mode: 0644]
drivers/net/wireless/ath/ath9k/htc_drv_beacon.c [new file with mode: 0644]
drivers/net/wireless/ath/ath9k/htc_drv_init.c [new file with mode: 0644]
drivers/net/wireless/ath/ath9k/htc_drv_main.c [new file with mode: 0644]
drivers/net/wireless/ath/ath9k/htc_drv_txrx.c [new file with mode: 0644]
drivers/net/wireless/ath/ath9k/htc_hst.c [new file with mode: 0644]
drivers/net/wireless/ath/ath9k/htc_hst.h [new file with mode: 0644]
drivers/net/wireless/ath/ath9k/mac.h
drivers/net/wireless/ath/ath9k/wmi.c [new file with mode: 0644]
drivers/net/wireless/ath/ath9k/wmi.h [new file with mode: 0644]
drivers/net/wireless/ath/debug.h

index 4e7a7fd..0a75be0 100644 (file)
@@ -3,7 +3,7 @@ menuconfig ATH_COMMON
        depends on CFG80211
        ---help---
          This will enable the support for the Atheros wireless drivers.
-         ath5k, ath9k and ar9170 drivers share some common code, this option
+         ath5k, ath9k, ath9k_htc and ar9170 drivers share some common code, this option
          enables the common ath.ko module which shares common helpers.
 
          For more information and documentation on this module you can visit:
index 5774cea..35f23bd 100644 (file)
@@ -32,3 +32,24 @@ config ATH9K_DEBUGFS
 
          Also required for changing debug message flags at run time.
 
+config ATH9K_HTC
+       tristate "Atheros HTC based wireless cards support"
+       depends on USB && MAC80211
+       select ATH9K_HW
+       select MAC80211_LEDS
+       select LEDS_CLASS
+       select NEW_LEDS
+       select ATH9K_COMMON
+       ---help---
+        Support for Atheros HTC based cards.
+        Chipsets supported: AR9271
+
+        For more information: http://wireless.kernel.org/en/users/Drivers/ath9k_htc
+
+        The built module will be ath9k_htc.
+
+config ATH9K_HTC_DEBUGFS
+       bool "Atheros ath9k_htc debugging"
+       depends on ATH9K_HTC && DEBUG_FS
+       ---help---
+         Say Y, if you need access to ath9k_htc's statistics.
index 6b50d5e..97133be 100644 (file)
@@ -28,3 +28,13 @@ obj-$(CONFIG_ATH9K_HW) += ath9k_hw.o
 
 obj-$(CONFIG_ATH9K_COMMON) += ath9k_common.o
 ath9k_common-y:=       common.o
+
+ath9k_htc-y += htc_hst.o \
+               hif_usb.o \
+               wmi.o \
+               htc_drv_txrx.o \
+               htc_drv_main.o \
+               htc_drv_beacon.o \
+               htc_drv_init.o
+
+obj-$(CONFIG_ATH9K_HTC) += ath9k_htc.o
index 4d775ae..7902d28 100644 (file)
@@ -286,6 +286,427 @@ int ath9k_cmn_padpos(__le16 frame_control)
 }
 EXPORT_SYMBOL(ath9k_cmn_padpos);
 
+int ath9k_cmn_get_hw_crypto_keytype(struct sk_buff *skb)
+{
+       struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
+
+       if (tx_info->control.hw_key) {
+               if (tx_info->control.hw_key->alg == ALG_WEP)
+                       return ATH9K_KEY_TYPE_WEP;
+               else if (tx_info->control.hw_key->alg == ALG_TKIP)
+                       return ATH9K_KEY_TYPE_TKIP;
+               else if (tx_info->control.hw_key->alg == ALG_CCMP)
+                       return ATH9K_KEY_TYPE_AES;
+       }
+
+       return ATH9K_KEY_TYPE_CLEAR;
+}
+EXPORT_SYMBOL(ath9k_cmn_get_hw_crypto_keytype);
+
+/*
+ * Calculate the RX filter to be set in the HW.
+ */
+u32 ath9k_cmn_calcrxfilter(struct ieee80211_hw *hw, struct ath_hw *ah,
+                          unsigned int rxfilter)
+{
+#define        RX_FILTER_PRESERVE (ATH9K_RX_FILTER_PHYERR | ATH9K_RX_FILTER_PHYRADAR)
+
+       u32 rfilt;
+
+       rfilt = (ath9k_hw_getrxfilter(ah) & RX_FILTER_PRESERVE)
+               | ATH9K_RX_FILTER_UCAST | ATH9K_RX_FILTER_BCAST
+               | ATH9K_RX_FILTER_MCAST;
+
+       /* If not a STA, enable processing of Probe Requests */
+       if (ah->opmode != NL80211_IFTYPE_STATION)
+               rfilt |= ATH9K_RX_FILTER_PROBEREQ;
+
+       /*
+        * Set promiscuous mode when FIF_PROMISC_IN_BSS is enabled for station
+        * mode interface or when in monitor mode. AP mode does not need this
+        * since it receives all in-BSS frames anyway.
+        */
+       if (((ah->opmode != NL80211_IFTYPE_AP) &&
+            (rxfilter & FIF_PROMISC_IN_BSS)) ||
+           (ah->opmode == NL80211_IFTYPE_MONITOR))
+               rfilt |= ATH9K_RX_FILTER_PROM;
+
+       if (rxfilter & FIF_CONTROL)
+               rfilt |= ATH9K_RX_FILTER_CONTROL;
+
+       if ((ah->opmode == NL80211_IFTYPE_STATION) &&
+           !(rxfilter & FIF_BCN_PRBRESP_PROMISC))
+               rfilt |= ATH9K_RX_FILTER_MYBEACON;
+       else
+               rfilt |= ATH9K_RX_FILTER_BEACON;
+
+       if ((AR_SREV_9280_10_OR_LATER(ah) ||
+           AR_SREV_9285_10_OR_LATER(ah)) &&
+           (ah->opmode == NL80211_IFTYPE_AP) &&
+           (rxfilter & FIF_PSPOLL))
+               rfilt |= ATH9K_RX_FILTER_PSPOLL;
+
+       if (conf_is_ht(&hw->conf))
+               rfilt |= ATH9K_RX_FILTER_COMP_BAR;
+
+       return rfilt;
+
+#undef RX_FILTER_PRESERVE
+}
+EXPORT_SYMBOL(ath9k_cmn_calcrxfilter);
+
+/*
+ * Recv initialization for opmode change.
+ */
+void ath9k_cmn_opmode_init(struct ieee80211_hw *hw, struct ath_hw *ah,
+                          unsigned int rxfilter)
+{
+       struct ath_common *common = ath9k_hw_common(ah);
+
+       u32 rfilt, mfilt[2];
+
+       /* configure rx filter */
+       rfilt = ath9k_cmn_calcrxfilter(hw, ah, rxfilter);
+       ath9k_hw_setrxfilter(ah, rfilt);
+
+       /* configure bssid mask */
+       if (ah->caps.hw_caps & ATH9K_HW_CAP_BSSIDMASK)
+               ath_hw_setbssidmask(common);
+
+       /* configure operational mode */
+       ath9k_hw_setopmode(ah);
+
+       /* Handle any link-level address change. */
+       ath9k_hw_setmac(ah, common->macaddr);
+
+       /* calculate and install multicast filter */
+       mfilt[0] = mfilt[1] = ~0;
+       ath9k_hw_setmcastfilter(ah, mfilt[0], mfilt[1]);
+}
+EXPORT_SYMBOL(ath9k_cmn_opmode_init);
+
+static u32 ath9k_get_extchanmode(struct ieee80211_channel *chan,
+                                enum nl80211_channel_type channel_type)
+{
+       u32 chanmode = 0;
+
+       switch (chan->band) {
+       case IEEE80211_BAND_2GHZ:
+               switch (channel_type) {
+               case NL80211_CHAN_NO_HT:
+               case NL80211_CHAN_HT20:
+                       chanmode = CHANNEL_G_HT20;
+                       break;
+               case NL80211_CHAN_HT40PLUS:
+                       chanmode = CHANNEL_G_HT40PLUS;
+                       break;
+               case NL80211_CHAN_HT40MINUS:
+                       chanmode = CHANNEL_G_HT40MINUS;
+                       break;
+               }
+               break;
+       case IEEE80211_BAND_5GHZ:
+               switch (channel_type) {
+               case NL80211_CHAN_NO_HT:
+               case NL80211_CHAN_HT20:
+                       chanmode = CHANNEL_A_HT20;
+                       break;
+               case NL80211_CHAN_HT40PLUS:
+                       chanmode = CHANNEL_A_HT40PLUS;
+                       break;
+               case NL80211_CHAN_HT40MINUS:
+                       chanmode = CHANNEL_A_HT40MINUS;
+                       break;
+               }
+               break;
+       default:
+               break;
+       }
+
+       return chanmode;
+}
+
+/*
+ * Update internal channel flags.
+ */
+void ath9k_cmn_update_ichannel(struct ieee80211_hw *hw,
+                              struct ath9k_channel *ichan)
+{
+       struct ieee80211_channel *chan = hw->conf.channel;
+       struct ieee80211_conf *conf = &hw->conf;
+
+       ichan->channel = chan->center_freq;
+       ichan->chan = chan;
+
+       if (chan->band == IEEE80211_BAND_2GHZ) {
+               ichan->chanmode = CHANNEL_G;
+               ichan->channelFlags = CHANNEL_2GHZ | CHANNEL_OFDM | CHANNEL_G;
+       } else {
+               ichan->chanmode = CHANNEL_A;
+               ichan->channelFlags = CHANNEL_5GHZ | CHANNEL_OFDM;
+       }
+
+       if (conf_is_ht(conf))
+               ichan->chanmode = ath9k_get_extchanmode(chan,
+                                                       conf->channel_type);
+}
+EXPORT_SYMBOL(ath9k_cmn_update_ichannel);
+
+/*
+ * Get the internal channel reference.
+ */
+struct ath9k_channel *ath9k_cmn_get_curchannel(struct ieee80211_hw *hw,
+                                              struct ath_hw *ah)
+{
+       struct ieee80211_channel *curchan = hw->conf.channel;
+       struct ath9k_channel *channel;
+       u8 chan_idx;
+
+       chan_idx = curchan->hw_value;
+       channel = &ah->channels[chan_idx];
+       ath9k_cmn_update_ichannel(hw, channel);
+
+       return channel;
+}
+EXPORT_SYMBOL(ath9k_cmn_get_curchannel);
+
+static int ath_setkey_tkip(struct ath_common *common, u16 keyix, const u8 *key,
+                          struct ath9k_keyval *hk, const u8 *addr,
+                          bool authenticator)
+{
+       struct ath_hw *ah = common->ah;
+       const u8 *key_rxmic;
+       const u8 *key_txmic;
+
+       key_txmic = key + NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY;
+       key_rxmic = key + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY;
+
+       if (addr == NULL) {
+               /*
+                * Group key installation - only two key cache entries are used
+                * regardless of splitmic capability since group key is only
+                * used either for TX or RX.
+                */
+               if (authenticator) {
+                       memcpy(hk->kv_mic, key_txmic, sizeof(hk->kv_mic));
+                       memcpy(hk->kv_txmic, key_txmic, sizeof(hk->kv_mic));
+               } else {
+                       memcpy(hk->kv_mic, key_rxmic, sizeof(hk->kv_mic));
+                       memcpy(hk->kv_txmic, key_rxmic, sizeof(hk->kv_mic));
+               }
+               return ath9k_hw_set_keycache_entry(ah, keyix, hk, addr);
+       }
+       if (!common->splitmic) {
+               /* TX and RX keys share the same key cache entry. */
+               memcpy(hk->kv_mic, key_rxmic, sizeof(hk->kv_mic));
+               memcpy(hk->kv_txmic, key_txmic, sizeof(hk->kv_txmic));
+               return ath9k_hw_set_keycache_entry(ah, keyix, hk, addr);
+       }
+
+       /* Separate key cache entries for TX and RX */
+
+       /* TX key goes at first index, RX key at +32. */
+       memcpy(hk->kv_mic, key_txmic, sizeof(hk->kv_mic));
+       if (!ath9k_hw_set_keycache_entry(ah, keyix, hk, NULL)) {
+               /* TX MIC entry failed. No need to proceed further */
+               ath_print(common, ATH_DBG_FATAL,
+                         "Setting TX MIC Key Failed\n");
+               return 0;
+       }
+
+       memcpy(hk->kv_mic, key_rxmic, sizeof(hk->kv_mic));
+       /* XXX delete tx key on failure? */
+       return ath9k_hw_set_keycache_entry(ah, keyix + 32, hk, addr);
+}
+
+static int ath_reserve_key_cache_slot_tkip(struct ath_common *common)
+{
+       int i;
+
+       for (i = IEEE80211_WEP_NKID; i < common->keymax / 2; i++) {
+               if (test_bit(i, common->keymap) ||
+                   test_bit(i + 64, common->keymap))
+                       continue; /* At least one part of TKIP key allocated */
+               if (common->splitmic &&
+                   (test_bit(i + 32, common->keymap) ||
+                    test_bit(i + 64 + 32, common->keymap)))
+                       continue; /* At least one part of TKIP key allocated */
+
+               /* Found a free slot for a TKIP key */
+               return i;
+       }
+       return -1;
+}
+
+static int ath_reserve_key_cache_slot(struct ath_common *common)
+{
+       int i;
+
+       /* First, try to find slots that would not be available for TKIP. */
+       if (common->splitmic) {
+               for (i = IEEE80211_WEP_NKID; i < common->keymax / 4; i++) {
+                       if (!test_bit(i, common->keymap) &&
+                           (test_bit(i + 32, common->keymap) ||
+                            test_bit(i + 64, common->keymap) ||
+                            test_bit(i + 64 + 32, common->keymap)))
+                               return i;
+                       if (!test_bit(i + 32, common->keymap) &&
+                           (test_bit(i, common->keymap) ||
+                            test_bit(i + 64, common->keymap) ||
+                            test_bit(i + 64 + 32, common->keymap)))
+                               return i + 32;
+                       if (!test_bit(i + 64, common->keymap) &&
+                           (test_bit(i , common->keymap) ||
+                            test_bit(i + 32, common->keymap) ||
+                            test_bit(i + 64 + 32, common->keymap)))
+                               return i + 64;
+                       if (!test_bit(i + 64 + 32, common->keymap) &&
+                           (test_bit(i, common->keymap) ||
+                            test_bit(i + 32, common->keymap) ||
+                            test_bit(i + 64, common->keymap)))
+                               return i + 64 + 32;
+               }
+       } else {
+               for (i = IEEE80211_WEP_NKID; i < common->keymax / 2; i++) {
+                       if (!test_bit(i, common->keymap) &&
+                           test_bit(i + 64, common->keymap))
+                               return i;
+                       if (test_bit(i, common->keymap) &&
+                           !test_bit(i + 64, common->keymap))
+                               return i + 64;
+               }
+       }
+
+       /* No partially used TKIP slots, pick any available slot */
+       for (i = IEEE80211_WEP_NKID; i < common->keymax; i++) {
+               /* Do not allow slots that could be needed for TKIP group keys
+                * to be used. This limitation could be removed if we know that
+                * TKIP will not be used. */
+               if (i >= 64 && i < 64 + IEEE80211_WEP_NKID)
+                       continue;
+               if (common->splitmic) {
+                       if (i >= 32 && i < 32 + IEEE80211_WEP_NKID)
+                               continue;
+                       if (i >= 64 + 32 && i < 64 + 32 + IEEE80211_WEP_NKID)
+                               continue;
+               }
+
+               if (!test_bit(i, common->keymap))
+                       return i; /* Found a free slot for a key */
+       }
+
+       /* No free slot found */
+       return -1;
+}
+
+/*
+ * Configure encryption in the HW.
+ */
+int ath9k_cmn_key_config(struct ath_common *common,
+                        struct ieee80211_vif *vif,
+                        struct ieee80211_sta *sta,
+                        struct ieee80211_key_conf *key)
+{
+       struct ath_hw *ah = common->ah;
+       struct ath9k_keyval hk;
+       const u8 *mac = NULL;
+       int ret = 0;
+       int idx;
+
+       memset(&hk, 0, sizeof(hk));
+
+       switch (key->alg) {
+       case ALG_WEP:
+               hk.kv_type = ATH9K_CIPHER_WEP;
+               break;
+       case ALG_TKIP:
+               hk.kv_type = ATH9K_CIPHER_TKIP;
+               break;
+       case ALG_CCMP:
+               hk.kv_type = ATH9K_CIPHER_AES_CCM;
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       hk.kv_len = key->keylen;
+       memcpy(hk.kv_val, key->key, key->keylen);
+
+       if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) {
+               /* For now, use the default keys for broadcast keys. This may
+                * need to change with virtual interfaces. */
+               idx = key->keyidx;
+       } else if (key->keyidx) {
+               if (WARN_ON(!sta))
+                       return -EOPNOTSUPP;
+               mac = sta->addr;
+
+               if (vif->type != NL80211_IFTYPE_AP) {
+                       /* Only keyidx 0 should be used with unicast key, but
+                        * allow this for client mode for now. */
+                       idx = key->keyidx;
+               } else
+                       return -EIO;
+       } else {
+               if (WARN_ON(!sta))
+                       return -EOPNOTSUPP;
+               mac = sta->addr;
+
+               if (key->alg == ALG_TKIP)
+                       idx = ath_reserve_key_cache_slot_tkip(common);
+               else
+                       idx = ath_reserve_key_cache_slot(common);
+               if (idx < 0)
+                       return -ENOSPC; /* no free key cache entries */
+       }
+
+       if (key->alg == ALG_TKIP)
+               ret = ath_setkey_tkip(common, idx, key->key, &hk, mac,
+                                     vif->type == NL80211_IFTYPE_AP);
+       else
+               ret = ath9k_hw_set_keycache_entry(ah, idx, &hk, mac);
+
+       if (!ret)
+               return -EIO;
+
+       set_bit(idx, common->keymap);
+       if (key->alg == ALG_TKIP) {
+               set_bit(idx + 64, common->keymap);
+               if (common->splitmic) {
+                       set_bit(idx + 32, common->keymap);
+                       set_bit(idx + 64 + 32, common->keymap);
+               }
+       }
+
+       return idx;
+}
+EXPORT_SYMBOL(ath9k_cmn_key_config);
+
+/*
+ * Delete Key.
+ */
+void ath9k_cmn_key_delete(struct ath_common *common,
+                         struct ieee80211_key_conf *key)
+{
+       struct ath_hw *ah = common->ah;
+
+       ath9k_hw_keyreset(ah, key->hw_key_idx);
+       if (key->hw_key_idx < IEEE80211_WEP_NKID)
+               return;
+
+       clear_bit(key->hw_key_idx, common->keymap);
+       if (key->alg != ALG_TKIP)
+               return;
+
+       clear_bit(key->hw_key_idx + 64, common->keymap);
+       if (common->splitmic) {
+               ath9k_hw_keyreset(ah, key->hw_key_idx + 32);
+               clear_bit(key->hw_key_idx + 32, common->keymap);
+               clear_bit(key->hw_key_idx + 64 + 32, common->keymap);
+       }
+}
+EXPORT_SYMBOL(ath9k_cmn_key_delete);
+
 static int __init ath9k_cmn_init(void)
 {
        return 0;
index 042999c..bbcc57f 100644 (file)
@@ -23,6 +23,8 @@
 
 /* Common header for Atheros 802.11n base driver cores */
 
+#define IEEE80211_WEP_NKID 4
+
 #define WME_NUM_TID             16
 #define WME_BA_BMP_SIZE         64
 #define WME_MAX_BA              WME_BA_BMP_SIZE
@@ -125,3 +127,18 @@ void ath9k_cmn_rx_skb_postprocess(struct ath_common *common,
                                  bool decrypt_error);
 
 int ath9k_cmn_padpos(__le16 frame_control);
+int ath9k_cmn_get_hw_crypto_keytype(struct sk_buff *skb);
+u32 ath9k_cmn_calcrxfilter(struct ieee80211_hw *hw, struct ath_hw *ah,
+                          unsigned int rxfilter);
+void ath9k_cmn_opmode_init(struct ieee80211_hw *hw, struct ath_hw *ah,
+                          unsigned int rxfilter);
+void ath9k_cmn_update_ichannel(struct ieee80211_hw *hw,
+                              struct ath9k_channel *ichan);
+struct ath9k_channel *ath9k_cmn_get_curchannel(struct ieee80211_hw *hw,
+                                              struct ath_hw *ah);
+int ath9k_cmn_key_config(struct ath_common *common,
+                        struct ieee80211_vif *vif,
+                        struct ieee80211_sta *sta,
+                        struct ieee80211_key_conf *key);
+void ath9k_cmn_key_delete(struct ath_common *common,
+                         struct ieee80211_key_conf *key);
diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c
new file mode 100644 (file)
index 0000000..fc4f6e8
--- /dev/null
@@ -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);
+}
diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.h b/drivers/net/wireless/ath/ath9k/hif_usb.h
new file mode 100644 (file)
index 0000000..7cc3762
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+
+#ifndef HTC_USB_H
+#define HTC_USB_H
+
+#define AR9271_FIRMWARE       0x501000
+#define AR9271_FIRMWARE_TEXT  0x903000
+
+#define FIRMWARE_DOWNLOAD       0x30
+#define FIRMWARE_DOWNLOAD_COMP  0x31
+
+#define ATH_USB_RX_STREAM_MODE_TAG 0x4e00
+#define ATH_USB_TX_STREAM_MODE_TAG 0x697e
+
+/* FIXME: Verify these numbers (with Windows) */
+#define MAX_TX_URB_NUM  8
+#define MAX_TX_BUF_NUM  1024
+#define MAX_TX_BUF_SIZE 32768
+#define MAX_TX_AGGR_NUM 20
+
+#define MAX_RX_URB_NUM  8
+#define MAX_RX_BUF_SIZE 16384
+
+#define MAX_REG_OUT_URB_NUM  1
+#define MAX_REG_OUT_BUF_NUM  8
+
+#define MAX_REG_IN_BUF_SIZE 64
+
+/* USB Endpoint definition */
+#define USB_WLAN_TX_PIPE  1
+#define USB_WLAN_RX_PIPE  2
+#define USB_REG_IN_PIPE   3
+#define USB_REG_OUT_PIPE  4
+
+#define HIF_USB_MAX_RXPIPES 2
+#define HIF_USB_MAX_TXPIPES 4
+
+struct tx_buf {
+       u8 *buf;
+       u16 len;
+       u16 offset;
+       struct urb *urb;
+       struct sk_buff_head skb_queue;
+       struct hif_device_usb *hif_dev;
+       struct list_head list;
+};
+
+#define HIF_USB_TX_STOP  BIT(0)
+#define HIF_USB_TX_FLUSH BIT(1)
+
+struct hif_usb_tx {
+       u8 flags;
+       u8 tx_buf_cnt;
+       u16 tx_skb_cnt;
+       struct sk_buff_head tx_skb_queue;
+       struct list_head tx_buf;
+       struct list_head tx_pending;
+       spinlock_t tx_lock;
+};
+
+struct cmd_buf {
+       struct sk_buff *skb;
+       struct hif_device_usb *hif_dev;
+};
+
+#define HIF_USB_START BIT(0)
+
+struct hif_device_usb {
+       u16 device_id;
+       struct usb_device *udev;
+       struct usb_interface *interface;
+       const struct firmware *firmware;
+       struct htc_target *htc_handle;
+       u8 flags;
+
+       struct hif_usb_tx tx;
+
+       struct urb *wlan_rx_data_urb[MAX_RX_URB_NUM];
+       struct urb *reg_in_urb;
+
+       struct sk_buff *remain_skb;
+       int rx_remain_len;
+       int rx_pkt_len;
+       int rx_transfer_len;
+       int rx_pad_len;
+};
+
+int ath9k_hif_usb_init(void);
+void ath9k_hif_usb_exit(void);
+
+#endif /* HTC_USB_H */
diff --git a/drivers/net/wireless/ath/ath9k/htc.h b/drivers/net/wireless/ath/ath9k/htc.h
new file mode 100644 (file)
index 0000000..ab09fe3
--- /dev/null
@@ -0,0 +1,441 @@
+/*
+ * 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.
+ */
+
+#ifndef HTC_H
+#define HTC_H
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/leds.h>
+#include <net/mac80211.h>
+
+#include "common.h"
+#include "htc_hst.h"
+#include "hif_usb.h"
+#include "wmi.h"
+
+#define ATH_STA_SHORT_CALINTERVAL 1000    /* 1 second */
+#define ATH_ANI_POLLINTERVAL      100     /* 100 ms */
+#define ATH_LONG_CALINTERVAL      30000   /* 30 seconds */
+#define ATH_RESTART_CALINTERVAL   1200000 /* 20 minutes */
+
+#define ATH_DEFAULT_BMISS_LIMIT 10
+#define IEEE80211_MS_TO_TU(x)   (((x) * 1000) / 1024)
+#define TSF_TO_TU(_h, _l) \
+       ((((u32)(_h)) << 22) | (((u32)(_l)) >> 10))
+
+extern struct ieee80211_ops ath9k_htc_ops;
+extern int modparam_nohwcrypt;
+
+enum htc_phymode {
+       HTC_MODE_AUTO           = 0,
+       HTC_MODE_11A            = 1,
+       HTC_MODE_11B            = 2,
+       HTC_MODE_11G            = 3,
+       HTC_MODE_FH             = 4,
+       HTC_MODE_TURBO_A        = 5,
+       HTC_MODE_TURBO_G        = 6,
+       HTC_MODE_11NA           = 7,
+       HTC_MODE_11NG           = 8
+};
+
+enum htc_opmode {
+       HTC_M_STA       = 1,
+       HTC_M_IBSS      = 0,
+       HTC_M_AHDEMO    = 3,
+       HTC_M_HOSTAP    = 6,
+       HTC_M_MONITOR   = 8,
+       HTC_M_WDS       = 2
+};
+
+#define ATH9K_HTC_HDRSPACE sizeof(struct htc_frame_hdr)
+#define ATH9K_HTC_AMPDU        1
+#define ATH9K_HTC_NORMAL 2
+
+#define ATH9K_HTC_TX_CTSONLY      0x1
+#define ATH9K_HTC_TX_RTSCTS       0x2
+#define ATH9K_HTC_TX_USE_MIN_RATE 0x100
+
+struct tx_frame_hdr {
+       u8 data_type;
+       u8 node_idx;
+       u8 vif_idx;
+       u8 tidno;
+       u32 flags; /* ATH9K_HTC_TX_* */
+       u8 key_type;
+       u8 keyix;
+       u8 reserved[26];
+} __packed;
+
+struct tx_mgmt_hdr {
+       u8 node_idx;
+       u8 vif_idx;
+       u8 tidno;
+       u8 flags;
+       u8 key_type;
+       u8 keyix;
+       u16 reserved;
+} __packed;
+
+struct tx_beacon_header {
+       u8 len_changed;
+       u8 vif_index;
+       u16 rev;
+} __packed;
+
+struct ath9k_htc_target_hw {
+       u32 flags;
+       u32 flags_ext;
+       u32 ampdu_limit;
+       u8 ampdu_subframes;
+       u8 tx_chainmask;
+       u8 tx_chainmask_legacy;
+       u8 rtscts_ratecode;
+       u8 protmode;
+} __packed;
+
+struct ath9k_htc_cap_target {
+       u32 flags;
+       u32 flags_ext;
+       u32 ampdu_limit;
+       u8 ampdu_subframes;
+       u8 tx_chainmask;
+       u8 tx_chainmask_legacy;
+       u8 rtscts_ratecode;
+       u8 protmode;
+} __packed;
+
+struct ath9k_htc_target_vif {
+       u8 index;
+       u8 des_bssid[ETH_ALEN];
+       enum htc_opmode opmode;
+       u8 myaddr[ETH_ALEN];
+       u8 bssid[ETH_ALEN];
+       u32 flags;
+       u32 flags_ext;
+       u16 ps_sta;
+       u16 rtsthreshold;
+       u8 ath_cap;
+       u8 node;
+       s8 mcast_rate;
+} __packed;
+
+#define ATH_HTC_STA_AUTH  0x0001
+#define ATH_HTC_STA_QOS   0x0002
+#define ATH_HTC_STA_ERP   0x0004
+#define ATH_HTC_STA_HT    0x0008
+
+/* FIXME: UAPSD variables */
+struct ath9k_htc_target_sta {
+       u16 associd;
+       u16 txpower;
+       u32 ucastkey;
+       u8 macaddr[ETH_ALEN];
+       u8 bssid[ETH_ALEN];
+       u8 sta_index;
+       u8 vif_index;
+       u8 vif_sta;
+       u16 flags; /* ATH_HTC_STA_* */
+       u16 htcap;
+       u8 valid;
+       u16 capinfo;
+       struct ath9k_htc_target_hw *hw;
+       struct ath9k_htc_target_vif *vif;
+       u16 txseqmgmt;
+       u8 is_vif_sta;
+       u16 maxampdu;
+       u16 iv16;
+       u32 iv32;
+} __packed;
+
+struct ath9k_htc_target_aggr {
+       u8 sta_index;
+       u8 tidno;
+       u8 aggr_enable;
+       u8 padding;
+} __packed;
+
+#define ATH_HTC_RATE_MAX 30
+
+#define WLAN_RC_DS_FLAG  0x01
+#define WLAN_RC_40_FLAG  0x02
+#define WLAN_RC_SGI_FLAG 0x04
+#define WLAN_RC_HT_FLAG  0x08
+
+struct ath9k_htc_rateset {
+       u8 rs_nrates;
+       u8 rs_rates[ATH_HTC_RATE_MAX];
+};
+
+struct ath9k_htc_rate {
+       struct ath9k_htc_rateset legacy_rates;
+       struct ath9k_htc_rateset ht_rates;
+} __packed;
+
+struct ath9k_htc_target_rate {
+       u8 sta_index;
+       u8 isnew;
+       u32 capflags;
+       struct ath9k_htc_rate rates;
+};
+
+struct ath9k_htc_target_stats {
+       u32 tx_shortretry;
+       u32 tx_longretry;
+       u32 tx_xretries;
+       u32 ht_txunaggr_xretry;
+       u32 ht_tx_xretries;
+} __packed;
+
+struct ath9k_htc_vif {
+       u8 index;
+};
+
+#define ATH9K_HTC_MAX_STA 8
+#define ATH9K_HTC_MAX_TID 8
+
+enum tid_aggr_state {
+       AGGR_STOP = 0,
+       AGGR_PROGRESS,
+       AGGR_START,
+       AGGR_OPERATIONAL
+};
+
+struct ath9k_htc_sta {
+       u8 index;
+       enum tid_aggr_state tid_state[ATH9K_HTC_MAX_TID];
+};
+
+struct ath9k_htc_aggr_work {
+       u16 tid;
+       u8 sta_addr[ETH_ALEN];
+       struct ieee80211_hw *hw;
+       struct ieee80211_vif *vif;
+       enum ieee80211_ampdu_mlme_action action;
+       struct mutex mutex;
+};
+
+#define ATH9K_HTC_RXBUF 256
+#define HTC_RX_FRAME_HEADER_SIZE 40
+
+struct ath9k_htc_rxbuf {
+       bool in_process;
+       struct sk_buff *skb;
+       struct ath_htc_rx_status rxstatus;
+       struct list_head list;
+};
+
+struct ath9k_htc_rx {
+       int last_rssi; /* FIXME: per-STA */
+       struct list_head rxbuf;
+       spinlock_t rxbuflock;
+};
+
+struct ath9k_htc_tx_ctl {
+       u8 type; /* ATH9K_HTC_* */
+};
+
+#ifdef CONFIG_ATH9K_HTC_DEBUGFS
+
+#define TX_STAT_INC(c) (hif_dev->htc_handle->drv_priv->debug.tx_stats.c++)
+#define RX_STAT_INC(c) (hif_dev->htc_handle->drv_priv->debug.rx_stats.c++)
+
+struct ath_tx_stats {
+       u32 buf_queued;
+       u32 buf_completed;
+       u32 skb_queued;
+       u32 skb_completed;
+};
+
+struct ath_rx_stats {
+       u32 skb_allocated;
+       u32 skb_completed;
+       u32 skb_dropped;
+};
+
+struct ath9k_debug {
+       struct dentry *debugfs_phy;
+       struct dentry *debugfs_tgt_stats;
+       struct dentry *debugfs_xmit;
+       struct dentry *debugfs_recv;
+       struct ath_tx_stats tx_stats;
+       struct ath_rx_stats rx_stats;
+       u32 txrate;
+};
+
+#else
+
+#define TX_STAT_INC(c) do { } while (0)
+#define RX_STAT_INC(c) do { } while (0)
+
+#endif /* CONFIG_ATH9K_HTC_DEBUGFS */
+
+#define ATH_LED_PIN_DEF             1
+#define ATH_LED_PIN_9287            8
+#define ATH_LED_PIN_9271            15
+#define ATH_LED_ON_DURATION_IDLE    350        /* in msecs */
+#define ATH_LED_OFF_DURATION_IDLE   250        /* in msecs */
+
+enum ath_led_type {
+       ATH_LED_RADIO,
+       ATH_LED_ASSOC,
+       ATH_LED_TX,
+       ATH_LED_RX
+};
+
+struct ath_led {
+       struct ath9k_htc_priv *priv;
+       struct led_classdev led_cdev;
+       enum ath_led_type led_type;
+       struct delayed_work brightness_work;
+       char name[32];
+       bool registered;
+       int brightness;
+};
+
+#define OP_INVALID        BIT(0)
+#define OP_SCANNING       BIT(1)
+#define OP_FULL_RESET     BIT(2)
+#define OP_LED_ASSOCIATED BIT(3)
+#define OP_LED_ON         BIT(4)
+#define OP_PREAMBLE_SHORT BIT(5)
+#define OP_PROTECT_ENABLE BIT(6)
+#define OP_TXAGGR         BIT(7)
+#define OP_ASSOCIATED     BIT(8)
+#define OP_ENABLE_BEACON  BIT(9)
+#define OP_LED_DEINIT     BIT(10)
+
+struct ath9k_htc_priv {
+       struct device *dev;
+       struct ieee80211_hw *hw;
+       struct ath_hw *ah;
+       struct htc_target *htc;
+       struct wmi *wmi;
+
+       enum htc_endpoint_id wmi_cmd_ep;
+       enum htc_endpoint_id beacon_ep;
+       enum htc_endpoint_id cab_ep;
+       enum htc_endpoint_id uapsd_ep;
+       enum htc_endpoint_id mgmt_ep;
+       enum htc_endpoint_id data_be_ep;
+       enum htc_endpoint_id data_bk_ep;
+       enum htc_endpoint_id data_vi_ep;
+       enum htc_endpoint_id data_vo_ep;
+
+       u16 op_flags;
+       u16 curtxpow;
+       u16 txpowlimit;
+       u16 nvifs;
+       u16 nstations;
+       u16 seq_no;
+       u32 bmiss_cnt;
+
+       struct sk_buff *beacon;
+       spinlock_t beacon_lock;
+
+       struct ieee80211_vif *vif;
+       unsigned int rxfilter;
+       struct tasklet_struct wmi_tasklet;
+       struct tasklet_struct rx_tasklet;
+       struct ieee80211_supported_band sbands[IEEE80211_NUM_BANDS];
+       struct ath9k_htc_rx rx;
+       struct tasklet_struct tx_tasklet;
+       struct sk_buff_head tx_queue;
+       struct ath9k_htc_aggr_work aggr_work;
+       struct delayed_work ath9k_aggr_work;
+       struct delayed_work ath9k_ani_work;
+
+       struct ath_led radio_led;
+       struct ath_led assoc_led;
+       struct ath_led tx_led;
+       struct ath_led rx_led;
+       struct delayed_work ath9k_led_blink_work;
+       int led_on_duration;
+       int led_off_duration;
+       int led_on_cnt;
+       int led_off_cnt;
+       int hwq_map[ATH9K_WME_AC_VO+1];
+
+#ifdef CONFIG_ATH9K_HTC_DEBUGFS
+       struct ath9k_debug debug;
+#endif
+       struct ath9k_htc_target_rate tgt_rate;
+
+       struct mutex mutex;
+};
+
+static inline void ath_read_cachesize(struct ath_common *common, int *csz)
+{
+       common->bus_ops->read_cachesize(common, csz);
+}
+
+void ath9k_htc_beacon_config(struct ath9k_htc_priv *priv,
+                            struct ieee80211_vif *vif,
+                            struct ieee80211_bss_conf *bss_conf);
+void ath9k_htc_swba(struct ath9k_htc_priv *priv, u8 beacon_pending);
+void ath9k_htc_beacon_update(struct ath9k_htc_priv *priv,
+                            struct ieee80211_vif *vif);
+
+void ath9k_htc_rxep(void *priv, struct sk_buff *skb,
+                   enum htc_endpoint_id ep_id);
+void ath9k_htc_txep(void *priv, struct sk_buff *skb, enum htc_endpoint_id ep_id,
+                   bool txok);
+
+void ath9k_htc_station_work(struct work_struct *work);
+void ath9k_htc_aggr_work(struct work_struct *work);
+void ath9k_ani_work(struct work_struct *work);;
+
+int ath9k_tx_init(struct ath9k_htc_priv *priv);
+void ath9k_tx_tasklet(unsigned long data);
+int ath9k_htc_tx_start(struct ath9k_htc_priv *priv, struct sk_buff *skb);
+void ath9k_tx_cleanup(struct ath9k_htc_priv *priv);
+bool ath9k_htc_txq_setup(struct ath9k_htc_priv *priv,
+                        enum ath9k_tx_queue_subtype qtype);
+int get_hw_qnum(u16 queue, int *hwq_map);
+int ath_txq_update(struct ath9k_htc_priv *priv, int qnum,
+                  struct ath9k_tx_queue_info *qinfo);
+
+int ath9k_rx_init(struct ath9k_htc_priv *priv);
+void ath9k_rx_cleanup(struct ath9k_htc_priv *priv);
+void ath9k_host_rx_init(struct ath9k_htc_priv *priv);
+void ath9k_rx_tasklet(unsigned long data);
+
+void ath9k_start_rfkill_poll(struct ath9k_htc_priv *priv);
+void ath9k_init_leds(struct ath9k_htc_priv *priv);
+void ath9k_deinit_leds(struct ath9k_htc_priv *priv);
+
+int ath9k_htc_probe_device(struct htc_target *htc_handle, struct device *dev,
+                          u16 devid);
+void ath9k_htc_disconnect_device(struct htc_target *htc_handle, bool hotunplug);
+#ifdef CONFIG_PM
+int ath9k_htc_resume(struct htc_target *htc_handle);
+#endif
+#ifdef CONFIG_ATH9K_HTC_DEBUGFS
+int ath9k_debug_create_root(void);
+void ath9k_debug_remove_root(void);
+int ath9k_init_debug(struct ath_hw *ah);
+void ath9k_exit_debug(struct ath_hw *ah);
+#else
+static inline int ath9k_debug_create_root(void) { return 0; };
+static inline void ath9k_debug_remove_root(void) {};
+static inline int ath9k_init_debug(struct ath_hw *ah) { return 0; };
+static inline void ath9k_exit_debug(struct ath_hw *ah) {};
+#endif /* CONFIG_ATH9K_HTC_DEBUGFS */
+
+#endif /* HTC_H */
diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_beacon.c b/drivers/net/wireless/ath/ath9k/htc_drv_beacon.c
new file mode 100644 (file)
index 0000000..25f5b53
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * 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 FUDGE 2
+
+static void ath9k_htc_beacon_config_sta(struct ath9k_htc_priv *priv,
+                                       struct ieee80211_bss_conf *bss_conf)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       struct ath9k_beacon_state bs;
+       enum ath9k_int imask = 0;
+       int dtimperiod, dtimcount, sleepduration;
+       int cfpperiod, cfpcount, bmiss_timeout;
+       u32 nexttbtt = 0, intval, tsftu, htc_imask = 0;
+       u64 tsf;
+       int num_beacons, offset, dtim_dec_count, cfp_dec_count;
+       int ret;
+       u8 cmd_rsp;
+
+       memset(&bs, 0, sizeof(bs));
+
+       intval = bss_conf->beacon_int & ATH9K_BEACON_PERIOD;
+       bmiss_timeout = (ATH_DEFAULT_BMISS_LIMIT * bss_conf->beacon_int);
+
+       /*
+        * Setup dtim and cfp parameters according to
+        * last beacon we received (which may be none).
+        */
+       dtimperiod = bss_conf->dtim_period;
+       if (dtimperiod <= 0)            /* NB: 0 if not known */
+               dtimperiod = 1;
+       dtimcount = 1;
+       if (dtimcount >= dtimperiod)    /* NB: sanity check */
+               dtimcount = 0;
+       cfpperiod = 1;                  /* NB: no PCF support yet */
+       cfpcount = 0;
+
+       sleepduration = intval;
+       if (sleepduration <= 0)
+               sleepduration = intval;
+
+       /*
+        * Pull nexttbtt forward to reflect the current
+        * TSF and calculate dtim+cfp state for the result.
+        */
+       tsf = ath9k_hw_gettsf64(priv->ah);
+       tsftu = TSF_TO_TU(tsf>>32, tsf) + FUDGE;
+
+       num_beacons = tsftu / intval + 1;
+       offset = tsftu % intval;
+       nexttbtt = tsftu - offset;
+       if (offset)
+               nexttbtt += intval;
+
+       /* DTIM Beacon every dtimperiod Beacon */
+       dtim_dec_count = num_beacons % dtimperiod;
+       /* CFP every cfpperiod DTIM Beacon */
+       cfp_dec_count = (num_beacons / dtimperiod) % cfpperiod;
+       if (dtim_dec_count)
+               cfp_dec_count++;
+
+       dtimcount -= dtim_dec_count;
+       if (dtimcount < 0)
+               dtimcount += dtimperiod;
+
+       cfpcount -= cfp_dec_count;
+       if (cfpcount < 0)
+               cfpcount += cfpperiod;
+
+       bs.bs_intval = intval;
+       bs.bs_nexttbtt = nexttbtt;
+       bs.bs_dtimperiod = dtimperiod*intval;
+       bs.bs_nextdtim = bs.bs_nexttbtt + dtimcount*intval;
+       bs.bs_cfpperiod = cfpperiod*bs.bs_dtimperiod;
+       bs.bs_cfpnext = bs.bs_nextdtim + cfpcount*bs.bs_dtimperiod;
+       bs.bs_cfpmaxduration = 0;
+
+       /*
+        * Calculate the number of consecutive beacons to miss* before taking
+        * a BMISS interrupt. The configuration is specified in TU so we only
+        * need calculate based on the beacon interval.  Note that we clamp the
+        * result to at most 15 beacons.
+        */
+       if (sleepduration > intval) {
+               bs.bs_bmissthreshold = ATH_DEFAULT_BMISS_LIMIT / 2;
+       } else {
+               bs.bs_bmissthreshold = DIV_ROUND_UP(bmiss_timeout, intval);
+               if (bs.bs_bmissthreshold > 15)
+                       bs.bs_bmissthreshold = 15;
+               else if (bs.bs_bmissthreshold <= 0)
+                       bs.bs_bmissthreshold = 1;
+       }
+
+       /*
+        * Calculate sleep duration. The configuration is given in ms.
+        * We ensure a multiple of the beacon period is used. Also, if the sleep
+        * duration is greater than the DTIM period then it makes senses
+        * to make it a multiple of that.
+        *
+        * XXX fixed at 100ms
+        */
+
+       bs.bs_sleepduration = roundup(IEEE80211_MS_TO_TU(100), sleepduration);
+       if (bs.bs_sleepduration > bs.bs_dtimperiod)
+               bs.bs_sleepduration = bs.bs_dtimperiod;
+
+       /* TSF out of range threshold fixed at 1 second */
+       bs.bs_tsfoor_threshold = ATH9K_TSFOOR_THRESHOLD;
+
+       ath_print(common, ATH_DBG_BEACON, "tsf: %llu tsftu: %u\n", tsf, tsftu);
+       ath_print(common, ATH_DBG_BEACON,
+                 "bmiss: %u sleep: %u cfp-period: %u maxdur: %u next: %u\n",
+                 bs.bs_bmissthreshold, bs.bs_sleepduration,
+                 bs.bs_cfpperiod, bs.bs_cfpmaxduration, bs.bs_cfpnext);
+
+       /* Set the computed STA beacon timers */
+
+       WMI_CMD(WMI_DISABLE_INTR_CMDID);
+       ath9k_hw_set_sta_beacon_timers(priv->ah, &bs);
+       imask |= ATH9K_INT_BMISS;
+       htc_imask = cpu_to_be32(imask);
+       WMI_CMD_BUF(WMI_ENABLE_INTR_CMDID, &htc_imask);
+}
+
+static void ath9k_htc_beacon_config_adhoc(struct ath9k_htc_priv *priv,
+                                         struct ieee80211_bss_conf *bss_conf)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       enum ath9k_int imask = 0;
+       u32 nexttbtt, intval, htc_imask = 0;
+       int ret;
+       u8 cmd_rsp;
+
+       intval = bss_conf->beacon_int & ATH9K_BEACON_PERIOD;
+       nexttbtt = intval;
+       intval |= ATH9K_BEACON_ENA;
+       if (priv->op_flags & OP_ENABLE_BEACON)
+               imask |= ATH9K_INT_SWBA;
+
+       ath_print(common, ATH_DBG_BEACON,
+                 "IBSS Beacon config, intval: %d, imask: 0x%x\n",
+                 bss_conf->beacon_int, imask);
+
+       WMI_CMD(WMI_DISABLE_INTR_CMDID);
+       ath9k_hw_beaconinit(priv->ah, nexttbtt, intval);
+       priv->bmiss_cnt = 0;
+       htc_imask = cpu_to_be32(imask);
+       WMI_CMD_BUF(WMI_ENABLE_INTR_CMDID, &htc_imask);
+}
+
+void ath9k_htc_beacon_update(struct ath9k_htc_priv *priv,
+                            struct ieee80211_vif *vif)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+
+       spin_lock_bh(&priv->beacon_lock);
+
+       if (priv->beacon)
+               dev_kfree_skb_any(priv->beacon);
+
+       priv->beacon = ieee80211_beacon_get(priv->hw, vif);
+       if (!priv->beacon)
+               ath_print(common, ATH_DBG_BEACON,
+                         "Unable to allocate beacon\n");
+
+       spin_unlock_bh(&priv->beacon_lock);
+}
+
+void ath9k_htc_swba(struct ath9k_htc_priv *priv, u8 beacon_pending)
+{
+       struct ath9k_htc_vif *avp = (void *)priv->vif->drv_priv;
+       struct tx_beacon_header beacon_hdr;
+       struct ath9k_htc_tx_ctl tx_ctl;
+       struct ieee80211_tx_info *info;
+       u8 *tx_fhdr;
+
+       memset(&beacon_hdr, 0, sizeof(struct tx_beacon_header));
+       memset(&tx_ctl, 0, sizeof(struct ath9k_htc_tx_ctl));
+
+       /* FIXME: Handle BMISS */
+       if (beacon_pending != 0) {
+               priv->bmiss_cnt++;
+               return;
+       }
+
+       spin_lock_bh(&priv->beacon_lock);
+
+       if (unlikely(priv->op_flags & OP_SCANNING)) {
+               spin_unlock_bh(&priv->beacon_lock);
+               return;
+       }
+
+       if (unlikely(priv->beacon == NULL)) {
+               spin_unlock_bh(&priv->beacon_lock);
+               return;
+       }
+
+       /* Free the old SKB first */
+       dev_kfree_skb_any(priv->beacon);
+
+       /* Get a new beacon */
+       priv->beacon = ieee80211_beacon_get(priv->hw, priv->vif);
+       if (!priv->beacon) {
+               spin_unlock_bh(&priv->beacon_lock);
+               return;
+       }
+
+       info = IEEE80211_SKB_CB(priv->beacon);
+       if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) {
+               struct ieee80211_hdr *hdr =
+                       (struct ieee80211_hdr *) priv->beacon->data;
+               priv->seq_no += 0x10;
+               hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG);
+               hdr->seq_ctrl |= cpu_to_le16(priv->seq_no);
+       }
+
+       tx_ctl.type = ATH9K_HTC_NORMAL;
+       beacon_hdr.vif_index = avp->index;
+       tx_fhdr = skb_push(priv->beacon, sizeof(beacon_hdr));
+       memcpy(tx_fhdr, (u8 *) &beacon_hdr, sizeof(beacon_hdr));
+
+       htc_send(priv->htc, priv->beacon, priv->beacon_ep, &tx_ctl);
+
+       spin_unlock_bh(&priv->beacon_lock);
+}
+
+void ath9k_htc_beacon_config(struct ath9k_htc_priv *priv,
+                            struct ieee80211_vif *vif,
+                            struct ieee80211_bss_conf *bss_conf)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+
+       switch (vif->type) {
+       case NL80211_IFTYPE_STATION:
+               ath9k_htc_beacon_config_sta(priv, bss_conf);
+               break;
+       case NL80211_IFTYPE_ADHOC:
+               ath9k_htc_beacon_config_adhoc(priv, bss_conf);
+               break;
+       default:
+               ath_print(common, ATH_DBG_CONFIG,
+                         "Unsupported beaconing mode\n");
+               return;
+       }
+}
diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_init.c b/drivers/net/wireless/ath/ath9k/htc_drv_init.c
new file mode 100644 (file)
index 0000000..a653dec
--- /dev/null
@@ -0,0 +1,713 @@
+/*
+ * 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"
+
+MODULE_AUTHOR("Atheros Communications");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("Atheros driver 802.11n HTC based wireless devices");
+
+static unsigned int ath9k_debug = ATH_DBG_DEFAULT;
+module_param_named(debug, ath9k_debug, uint, 0);
+MODULE_PARM_DESC(debug, "Debugging mask");
+
+int modparam_nohwcrypt;
+module_param_named(nohwcrypt, modparam_nohwcrypt, int, 0444);
+MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption");
+
+#define CHAN2G(_freq, _idx)  { \
+       .center_freq = (_freq), \
+       .hw_value = (_idx), \
+       .max_power = 20, \
+}
+
+static struct ieee80211_channel ath9k_2ghz_channels[] = {
+       CHAN2G(2412, 0), /* Channel 1 */
+       CHAN2G(2417, 1), /* Channel 2 */
+       CHAN2G(2422, 2), /* Channel 3 */
+       CHAN2G(2427, 3), /* Channel 4 */
+       CHAN2G(2432, 4), /* Channel 5 */
+       CHAN2G(2437, 5), /* Channel 6 */
+       CHAN2G(2442, 6), /* Channel 7 */
+       CHAN2G(2447, 7), /* Channel 8 */
+       CHAN2G(2452, 8), /* Channel 9 */
+       CHAN2G(2457, 9), /* Channel 10 */
+       CHAN2G(2462, 10), /* Channel 11 */
+       CHAN2G(2467, 11), /* Channel 12 */
+       CHAN2G(2472, 12), /* Channel 13 */
+       CHAN2G(2484, 13), /* Channel 14 */
+};
+
+/* Atheros hardware rate code addition for short premble */
+#define SHPCHECK(__hw_rate, __flags) \
+       ((__flags & IEEE80211_RATE_SHORT_PREAMBLE) ? (__hw_rate | 0x04) : 0)
+
+#define RATE(_bitrate, _hw_rate, _flags) {             \
+       .bitrate        = (_bitrate),                   \
+       .flags          = (_flags),                     \
+       .hw_value       = (_hw_rate),                   \
+       .hw_value_short = (SHPCHECK(_hw_rate, _flags))  \
+}
+
+static struct ieee80211_rate ath9k_legacy_rates[] = {
+       RATE(10, 0x1b, 0),
+       RATE(20, 0x1a, IEEE80211_RATE_SHORT_PREAMBLE), /* shortp : 0x1e */
+       RATE(55, 0x19, IEEE80211_RATE_SHORT_PREAMBLE), /* shortp: 0x1d */
+       RATE(110, 0x18, IEEE80211_RATE_SHORT_PREAMBLE), /* short: 0x1c */
+       RATE(60, 0x0b, 0),
+       RATE(90, 0x0f, 0),
+       RATE(120, 0x0a, 0),
+       RATE(180, 0x0e, 0),
+       RATE(240, 0x09, 0),
+       RATE(360, 0x0d, 0),
+       RATE(480, 0x08, 0),
+       RATE(540, 0x0c, 0),
+};
+
+static int ath9k_htc_wait_for_target(struct ath9k_htc_priv *priv)
+{
+       int time_left;
+
+       /* Firmware can take up to 50ms to get ready, to be safe use 1 second */
+       time_left = wait_for_completion_timeout(&priv->htc->target_wait, HZ);
+       if (!time_left) {
+               dev_err(priv->dev, "ath9k_htc: Target is unresponsive\n");
+               return -ETIMEDOUT;
+       }
+
+       return 0;
+}
+
+static void ath9k_deinit_priv(struct ath9k_htc_priv *priv)
+{
+       ath9k_exit_debug(priv->ah);
+       ath9k_hw_deinit(priv->ah);
+       tasklet_kill(&priv->wmi_tasklet);
+       tasklet_kill(&priv->rx_tasklet);
+       tasklet_kill(&priv->tx_tasklet);
+       kfree(priv->ah);
+       priv->ah = NULL;
+}
+
+static void ath9k_deinit_device(struct ath9k_htc_priv *priv)
+{
+       struct ieee80211_hw *hw = priv->hw;
+
+       wiphy_rfkill_stop_polling(hw->wiphy);
+       ath9k_deinit_leds(priv);
+       ieee80211_unregister_hw(hw);
+       ath9k_rx_cleanup(priv);
+       ath9k_tx_cleanup(priv);
+       ath9k_deinit_priv(priv);
+}
+
+static inline int ath9k_htc_connect_svc(struct ath9k_htc_priv *priv,
+                                       u16 service_id,
+                                       void (*tx) (void *,
+                                                   struct sk_buff *,
+                                                   enum htc_endpoint_id,
+                                                   bool txok),
+                                       enum htc_endpoint_id *ep_id)
+{
+       struct htc_service_connreq req;
+
+       memset(&req, 0, sizeof(struct htc_service_connreq));
+
+       req.service_id = service_id;
+       req.ep_callbacks.priv = priv;
+       req.ep_callbacks.rx = ath9k_htc_rxep;
+       req.ep_callbacks.tx = tx;
+
+       return htc_connect_service(priv->htc, &req, ep_id);
+}
+
+static int ath9k_init_htc_services(struct ath9k_htc_priv *priv)
+{
+       int ret;
+
+       /* WMI CMD*/
+       ret = ath9k_wmi_connect(priv->htc, priv->wmi, &priv->wmi_cmd_ep);
+       if (ret)
+               goto err;
+
+       /* Beacon */
+       ret = ath9k_htc_connect_svc(priv, WMI_BEACON_SVC, NULL,
+                                   &priv->beacon_ep);
+       if (ret)
+               goto err;
+
+       /* CAB */
+       ret = ath9k_htc_connect_svc(priv, WMI_CAB_SVC, ath9k_htc_txep,
+                                   &priv->cab_ep);
+       if (ret)
+               goto err;
+
+
+       /* UAPSD */
+       ret = ath9k_htc_connect_svc(priv, WMI_UAPSD_SVC, ath9k_htc_txep,
+                                   &priv->uapsd_ep);
+       if (ret)
+               goto err;
+
+       /* MGMT */
+       ret = ath9k_htc_connect_svc(priv, WMI_MGMT_SVC, ath9k_htc_txep,
+                                   &priv->mgmt_ep);
+       if (ret)
+               goto err;
+
+       /* DATA BE */
+       ret = ath9k_htc_connect_svc(priv, WMI_DATA_BE_SVC, ath9k_htc_txep,
+                                   &priv->data_be_ep);
+       if (ret)
+               goto err;
+
+       /* DATA BK */
+       ret = ath9k_htc_connect_svc(priv, WMI_DATA_BK_SVC, ath9k_htc_txep,
+                                   &priv->data_bk_ep);
+       if (ret)
+               goto err;
+
+       /* DATA VI */
+       ret = ath9k_htc_connect_svc(priv, WMI_DATA_VI_SVC, ath9k_htc_txep,
+                                   &priv->data_vi_ep);
+       if (ret)
+               goto err;
+
+       /* DATA VO */
+       ret = ath9k_htc_connect_svc(priv, WMI_DATA_VO_SVC, ath9k_htc_txep,
+                                   &priv->data_vo_ep);
+       if (ret)
+               goto err;
+
+       ret = htc_init(priv->htc);
+       if (ret)
+               goto err;
+
+       return 0;
+
+err:
+       dev_err(priv->dev, "ath9k_htc: Unable to initialize HTC services\n");
+       return ret;
+}
+
+static int ath9k_reg_notifier(struct wiphy *wiphy,
+                             struct regulatory_request *request)
+{
+       struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
+       struct ath9k_htc_priv *priv = hw->priv;
+
+       return ath_reg_notifier_apply(wiphy, request,
+                                     ath9k_hw_regulatory(priv->ah));
+}
+
+static unsigned int ath9k_ioread32(void *hw_priv, u32 reg_offset)
+{
+       struct ath_hw *ah = (struct ath_hw *) hw_priv;
+       struct ath_common *common = ath9k_hw_common(ah);
+       struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv;
+       __be32 val, reg = cpu_to_be32(reg_offset);
+       int r;
+
+       r = ath9k_wmi_cmd(priv->wmi, WMI_REG_READ_CMDID,
+                         (u8 *) &reg, sizeof(reg),
+                         (u8 *) &val, sizeof(val),
+                         100);
+       if (unlikely(r)) {
+               ath_print(common, ATH_DBG_WMI,
+                         "REGISTER READ FAILED: (0x%04x, %d)\n",
+                          reg_offset, r);
+               return -EIO;
+       }
+
+       return be32_to_cpu(val);
+}
+
+static void ath9k_iowrite32(void *hw_priv, u32 val, u32 reg_offset)
+{
+       struct ath_hw *ah = (struct ath_hw *) hw_priv;
+       struct ath_common *common = ath9k_hw_common(ah);
+       struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv;
+       __be32 buf[2] = {
+               cpu_to_be32(reg_offset),
+               cpu_to_be32(val),
+       };
+       int r;
+
+       r = ath9k_wmi_cmd(priv->wmi, WMI_REG_WRITE_CMDID,
+                         (u8 *) &buf, sizeof(buf),
+                         (u8 *) &val, sizeof(val),
+                         100);
+       if (unlikely(r)) {
+               ath_print(common, ATH_DBG_WMI,
+                         "REGISTER WRITE FAILED:(0x%04x, %d)\n",
+                         reg_offset, r);
+       }
+}
+
+static const struct ath_ops ath9k_common_ops = {
+       .read = ath9k_ioread32,
+       .write = ath9k_iowrite32,
+};
+
+static void ath_usb_read_cachesize(struct ath_common *common, int *csz)
+{
+       *csz = L1_CACHE_BYTES >> 2;
+}
+
+static bool ath_usb_eeprom_read(struct ath_common *common, u32 off, u16 *data)
+{
+       struct ath_hw *ah = (struct ath_hw *) common->ah;
+
+       (void)REG_READ(ah, AR5416_EEPROM_OFFSET + (off << AR5416_EEPROM_S));
+
+       if (!ath9k_hw_wait(ah,
+                          AR_EEPROM_STATUS_DATA,
+                          AR_EEPROM_STATUS_DATA_BUSY |
+                          AR_EEPROM_STATUS_DATA_PROT_ACCESS, 0,
+                          AH_WAIT_TIMEOUT))
+               return false;
+
+       *data = MS(REG_READ(ah, AR_EEPROM_STATUS_DATA),
+                  AR_EEPROM_STATUS_DATA_VAL);
+
+       return true;
+}
+
+static const struct ath_bus_ops ath9k_usb_bus_ops = {
+       .read_cachesize = ath_usb_read_cachesize,
+       .eeprom_read = ath_usb_eeprom_read,
+};
+
+static void setup_ht_cap(struct ath9k_htc_priv *priv,
+                        struct ieee80211_sta_ht_cap *ht_info)
+{
+       ht_info->ht_supported = true;
+       ht_info->cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 |
+                      IEEE80211_HT_CAP_SM_PS |
+                      IEEE80211_HT_CAP_SGI_40 |
+                      IEEE80211_HT_CAP_DSSSCCK40;
+
+       ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K;
+       ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_8;
+
+       memset(&ht_info->mcs, 0, sizeof(ht_info->mcs));
+       ht_info->mcs.rx_mask[0] = 0xff;
+       ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_DEFINED;
+}
+
+static int ath9k_init_queues(struct ath9k_htc_priv *priv)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(priv->hwq_map); i++)
+               priv->hwq_map[i] = -1;
+
+       if (!ath9k_htc_txq_setup(priv, ATH9K_WME_AC_BE)) {
+               ath_print(common, ATH_DBG_FATAL,
+                         "Unable to setup xmit queue for BE traffic\n");
+               goto err;
+       }
+
+       if (!ath9k_htc_txq_setup(priv, ATH9K_WME_AC_BK)) {
+               ath_print(common, ATH_DBG_FATAL,
+                         "Unable to setup xmit queue for BK traffic\n");
+               goto err;
+       }
+       if (!ath9k_htc_txq_setup(priv, ATH9K_WME_AC_VI)) {
+               ath_print(common, ATH_DBG_FATAL,
+                         "Unable to setup xmit queue for VI traffic\n");
+               goto err;
+       }
+       if (!ath9k_htc_txq_setup(priv, ATH9K_WME_AC_VO)) {
+               ath_print(common, ATH_DBG_FATAL,
+                         "Unable to setup xmit queue for VO traffic\n");
+               goto err;
+       }
+
+       return 0;
+
+err:
+       return -EINVAL;
+}
+
+static void ath9k_init_crypto(struct ath9k_htc_priv *priv)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       int i = 0;
+
+       /* Get the hardware key cache size. */
+       common->keymax = priv->ah->caps.keycache_size;
+       if (common->keymax > ATH_KEYMAX) {
+               ath_print(common, ATH_DBG_ANY,
+                         "Warning, using only %u entries in %u key cache\n",
+                         ATH_KEYMAX, common->keymax);
+               common->keymax = ATH_KEYMAX;
+       }
+
+       /*
+        * Reset the key cache since some parts do not
+        * reset the contents on initial power up.
+        */
+       for (i = 0; i < common->keymax; i++)
+               ath9k_hw_keyreset(priv->ah, (u16) i);
+
+       if (ath9k_hw_getcapability(priv->ah, ATH9K_CAP_CIPHER,
+                                  ATH9K_CIPHER_TKIP, NULL)) {
+               /*
+                * Whether we should enable h/w TKIP MIC.
+                * XXX: if we don't support WME TKIP MIC, then we wouldn't
+                * report WMM capable, so it's always safe to turn on
+                * TKIP MIC in this case.
+                */
+               ath9k_hw_setcapability(priv->ah, ATH9K_CAP_TKIP_MIC, 0, 1, NULL);
+       }
+
+       /*
+        * Check whether the separate key cache entries
+        * are required to handle both tx+rx MIC keys.
+        * With split mic keys the number of stations is limited
+        * to 27 otherwise 59.
+        */
+       if (ath9k_hw_getcapability(priv->ah, ATH9K_CAP_CIPHER,
+                                  ATH9K_CIPHER_TKIP, NULL)
+           && ath9k_hw_getcapability(priv->ah, ATH9K_CAP_CIPHER,
+                                     ATH9K_CIPHER_MIC, NULL)
+           && ath9k_hw_getcapability(priv->ah, ATH9K_CAP_TKIP_SPLIT,
+                                     0, NULL))
+               common->splitmic = 1;
+
+       /* turn on mcast key search if possible */
+       if (!ath9k_hw_getcapability(priv->ah, ATH9K_CAP_MCAST_KEYSRCH, 0, NULL))
+               (void)ath9k_hw_setcapability(priv->ah, ATH9K_CAP_MCAST_KEYSRCH,
+                                            1, 1, NULL);
+}
+
+static void ath9k_init_channels_rates(struct ath9k_htc_priv *priv)
+{
+       if (test_bit(ATH9K_MODE_11G, priv->ah->caps.wireless_modes)) {
+               priv->sbands[IEEE80211_BAND_2GHZ].channels =
+                       ath9k_2ghz_channels;
+               priv->sbands[IEEE80211_BAND_2GHZ].band = IEEE80211_BAND_2GHZ;
+               priv->sbands[IEEE80211_BAND_2GHZ].n_channels =
+                       ARRAY_SIZE(ath9k_2ghz_channels);
+               priv->sbands[IEEE80211_BAND_2GHZ].bitrates = ath9k_legacy_rates;
+               priv->sbands[IEEE80211_BAND_2GHZ].n_bitrates =
+                       ARRAY_SIZE(ath9k_legacy_rates);
+       }
+}
+
+static void ath9k_init_misc(struct ath9k_htc_priv *priv)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+
+       common->tx_chainmask = priv->ah->caps.tx_chainmask;
+       common->rx_chainmask = priv->ah->caps.rx_chainmask;
+
+       if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_BSSIDMASK)
+               memcpy(common->bssidmask, ath_bcast_mac, ETH_ALEN);
+
+       priv->op_flags |= OP_TXAGGR;
+}
+
+static int ath9k_init_priv(struct ath9k_htc_priv *priv, u16 devid)
+{
+       struct ath_hw *ah = NULL;
+       struct ath_common *common;
+       int ret = 0, csz = 0;
+
+       priv->op_flags |= OP_INVALID;
+
+       ah = kzalloc(sizeof(struct ath_hw), GFP_KERNEL);
+       if (!ah)
+               return -ENOMEM;
+
+       ah->hw_version.devid = devid;
+       ah->hw_version.subsysid = 0; /* FIXME */
+       priv->ah = ah;
+
+       common = ath9k_hw_common(ah);
+       common->ops = &ath9k_common_ops;
+       common->bus_ops = &ath9k_usb_bus_ops;
+       common->ah = ah;
+       common->hw = priv->hw;
+       common->priv = priv;
+       common->debug_mask = ath9k_debug;
+
+       spin_lock_init(&priv->wmi->wmi_lock);
+       spin_lock_init(&priv->beacon_lock);
+       mutex_init(&priv->mutex);
+       mutex_init(&priv->aggr_work.mutex);
+       tasklet_init(&priv->wmi_tasklet, ath9k_wmi_tasklet,
+                    (unsigned long)priv);
+       tasklet_init(&priv->rx_tasklet, ath9k_rx_tasklet,
+                    (unsigned long)priv);
+       tasklet_init(&priv->tx_tasklet, ath9k_tx_tasklet, (unsigned long)priv);
+       INIT_DELAYED_WORK(&priv->ath9k_aggr_work, ath9k_htc_aggr_work);
+       INIT_DELAYED_WORK(&priv->ath9k_ani_work, ath9k_ani_work);
+
+       /*
+        * Cache line size is used to size and align various
+        * structures used to communicate with the hardware.
+        */
+       ath_read_cachesize(common, &csz);
+       common->cachelsz = csz << 2; /* convert to bytes */
+
+       ret = ath9k_hw_init(ah);
+       if (ret) {
+               ath_print(common, ATH_DBG_FATAL,
+                         "Unable to initialize hardware; "
+                         "initialization status: %d\n", ret);
+               goto err_hw;
+       }
+
+       ret = ath9k_init_debug(ah);
+       if (ret) {
+               ath_print(common, ATH_DBG_FATAL,
+                         "Unable to create debugfs files\n");
+               goto err_debug;
+       }
+
+       ret = ath9k_init_queues(priv);
+       if (ret)
+               goto err_queues;
+
+       ath9k_init_crypto(priv);
+       ath9k_init_channels_rates(priv);
+       ath9k_init_misc(priv);
+
+       return 0;
+
+err_queues:
+       ath9k_exit_debug(ah);
+err_debug:
+       ath9k_hw_deinit(ah);
+err_hw:
+
+       kfree(ah);
+       priv->ah = NULL;
+
+       return ret;
+}
+
+static void ath9k_set_hw_capab(struct ath9k_htc_priv *priv,
+                              struct ieee80211_hw *hw)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+
+       hw->flags = IEEE80211_HW_SIGNAL_DBM |
+               IEEE80211_HW_AMPDU_AGGREGATION |
+               IEEE80211_HW_SPECTRUM_MGMT |
+               IEEE80211_HW_HAS_RATE_CONTROL;
+
+       hw->wiphy->interface_modes =
+               BIT(NL80211_IFTYPE_STATION) |
+               BIT(NL80211_IFTYPE_ADHOC);
+
+       hw->queues = 4;
+       hw->channel_change_time = 5000;
+       hw->max_listen_interval = 10;
+       hw->vif_data_size = sizeof(struct ath9k_htc_vif);
+       hw->sta_data_size = sizeof(struct ath9k_htc_sta);
+
+       /* tx_frame_hdr is larger than tx_mgmt_hdr anyway */
+       hw->extra_tx_headroom = sizeof(struct tx_frame_hdr) +
+               sizeof(struct htc_frame_hdr) + 4;
+
+       if (test_bit(ATH9K_MODE_11G, priv->ah->caps.wireless_modes))
+               hw->wiphy->bands[IEEE80211_BAND_2GHZ] =
+                       &priv->sbands[IEEE80211_BAND_2GHZ];
+
+       if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_HT) {
+               if (test_bit(ATH9K_MODE_11G, priv->ah->caps.wireless_modes))
+                       setup_ht_cap(priv,
+                                    &priv->sbands[IEEE80211_BAND_2GHZ].ht_cap);
+       }
+
+       SET_IEEE80211_PERM_ADDR(hw, common->macaddr);
+}
+
+static int ath9k_init_device(struct ath9k_htc_priv *priv, u16 devid)
+{
+       struct ieee80211_hw *hw = priv->hw;
+       struct ath_common *common;
+       struct ath_hw *ah;
+       int error = 0;
+       struct ath_regulatory *reg;
+
+       /* Bring up device */
+       error = ath9k_init_priv(priv, devid);
+       if (error != 0)
+               goto err_init;
+
+       ah = priv->ah;
+       common = ath9k_hw_common(ah);
+       ath9k_set_hw_capab(priv, hw);
+
+       /* Initialize regulatory */
+       error = ath_regd_init(&common->regulatory, priv->hw->wiphy,
+                             ath9k_reg_notifier);
+       if (error)
+               goto err_regd;
+
+       reg = &common->regulatory;
+
+       /* Setup TX */
+       error = ath9k_tx_init(priv);
+       if (error != 0)
+               goto err_tx;
+
+       /* Setup RX */
+       error = ath9k_rx_init(priv);
+       if (error != 0)
+               goto err_rx;
+
+       /* Register with mac80211 */
+       error = ieee80211_register_hw(hw);
+       if (error)
+               goto err_register;
+
+       /* Handle world regulatory */
+       if (!ath_is_world_regd(reg)) {
+               error = regulatory_hint(hw->wiphy, reg->alpha2);
+               if (error)
+                       goto err_world;
+       }
+
+       ath9k_init_leds(priv);
+       ath9k_start_rfkill_poll(priv);
+
+       return 0;
+
+err_world:
+       ieee80211_unregister_hw(hw);
+err_register:
+       ath9k_rx_cleanup(priv);
+err_rx:
+       ath9k_tx_cleanup(priv);
+err_tx:
+       /* Nothing */
+err_regd:
+       ath9k_deinit_priv(priv);
+err_init:
+       return error;
+}
+
+int ath9k_htc_probe_device(struct htc_target *htc_handle, struct device *dev,
+                          u16 devid)
+{
+       struct ieee80211_hw *hw;
+       struct ath9k_htc_priv *priv;
+       int ret;
+
+       hw = ieee80211_alloc_hw(sizeof(struct ath9k_htc_priv), &ath9k_htc_ops);
+       if (!hw)
+               return -ENOMEM;
+
+       priv = hw->priv;
+       priv->hw = hw;
+       priv->htc = htc_handle;
+       priv->dev = dev;
+       htc_handle->drv_priv = priv;
+       SET_IEEE80211_DEV(hw, priv->dev);
+
+       ret = ath9k_htc_wait_for_target(priv);
+       if (ret)
+               goto err_free;
+
+       priv->wmi = ath9k_init_wmi(priv);
+       if (!priv->wmi) {
+               ret = -EINVAL;
+               goto err_free;
+       }
+
+       ret = ath9k_init_htc_services(priv);
+       if (ret)
+               goto err_init;
+
+       ret = ath9k_init_device(priv, devid);
+       if (ret)
+               goto err_init;
+
+       return 0;
+
+err_init:
+       ath9k_deinit_wmi(priv);
+err_free:
+       ieee80211_free_hw(hw);
+       return ret;
+}
+
+void ath9k_htc_disconnect_device(struct htc_target *htc_handle, bool hotunplug)
+{
+       if (htc_handle->drv_priv) {
+               ath9k_deinit_device(htc_handle->drv_priv);
+               ath9k_deinit_wmi(htc_handle->drv_priv);
+               ieee80211_free_hw(htc_handle->drv_priv->hw);
+       }
+}
+
+#ifdef CONFIG_PM
+int ath9k_htc_resume(struct htc_target *htc_handle)
+{
+       int ret;
+
+       ret = ath9k_htc_wait_for_target(htc_handle->drv_priv);
+       if (ret)
+               return ret;
+
+       ret = ath9k_init_htc_services(htc_handle->drv_priv);
+       return ret;
+}
+#endif
+
+static int __init ath9k_htc_init(void)
+{
+       int error;
+
+       error = ath9k_debug_create_root();
+       if (error < 0) {
+               printk(KERN_ERR
+                       "ath9k_htc: Unable to create debugfs root: %d\n",
+                       error);
+               goto err_dbg;
+       }
+
+       error = ath9k_hif_usb_init();
+       if (error < 0) {
+               printk(KERN_ERR
+                       "ath9k_htc: No USB devices found,"
+                       " driver not installed.\n");
+               error = -ENODEV;
+               goto err_usb;
+       }
+
+       return 0;
+
+err_usb:
+       ath9k_debug_remove_root();
+err_dbg:
+       return error;
+}
+module_init(ath9k_htc_init);
+
+static void __exit ath9k_htc_exit(void)
+{
+       ath9k_hif_usb_exit();
+       ath9k_debug_remove_root();
+       printk(KERN_INFO "ath9k_htc: Driver unloaded\n");
+}
+module_exit(ath9k_htc_exit);
diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_main.c b/drivers/net/wireless/ath/ath9k/htc_drv_main.c
new file mode 100644 (file)
index 0000000..3184a2a
--- /dev/null
@@ -0,0 +1,1626 @@
+/*
+ * 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"
+
+#ifdef CONFIG_ATH9K_HTC_DEBUGFS
+static struct dentry *ath9k_debugfs_root;
+#endif
+
+/*************/
+/* Utilities */
+/*************/
+
+static void ath_update_txpow(struct ath9k_htc_priv *priv)
+{
+       struct ath_hw *ah = priv->ah;
+       u32 txpow;
+
+       if (priv->curtxpow != priv->txpowlimit) {
+               ath9k_hw_set_txpowerlimit(ah, priv->txpowlimit);
+               /* read back in case value is clamped */
+               ath9k_hw_getcapability(ah, ATH9K_CAP_TXPOW, 1, &txpow);
+               priv->curtxpow = txpow;
+       }
+}
+
+/* HACK Alert: Use 11NG for 2.4, use 11NA for 5 */
+static enum htc_phymode ath9k_htc_get_curmode(struct ath9k_htc_priv *priv,
+                                             struct ath9k_channel *ichan)
+{
+       enum htc_phymode mode;
+
+       mode = HTC_MODE_AUTO;
+
+       switch (ichan->chanmode) {
+       case CHANNEL_G:
+       case CHANNEL_G_HT20:
+       case CHANNEL_G_HT40PLUS:
+       case CHANNEL_G_HT40MINUS:
+               mode = HTC_MODE_11NG;
+               break;
+       case CHANNEL_A:
+       case CHANNEL_A_HT20:
+       case CHANNEL_A_HT40PLUS:
+       case CHANNEL_A_HT40MINUS:
+               mode = HTC_MODE_11NA;
+               break;
+       default:
+               break;
+       }
+
+       return mode;
+}
+
+static int ath9k_htc_set_channel(struct ath9k_htc_priv *priv,
+                                struct ieee80211_hw *hw,
+                                struct ath9k_channel *hchan)
+{
+       struct ath_hw *ah = priv->ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       struct ieee80211_conf *conf = &common->hw->conf;
+       bool fastcc = true;
+       struct ieee80211_channel *channel = hw->conf.channel;
+       enum htc_phymode mode;
+       u16 htc_mode;
+       u8 cmd_rsp;
+       int ret;
+
+       if (priv->op_flags & OP_INVALID)
+               return -EIO;
+
+       if (priv->op_flags & OP_FULL_RESET)
+               fastcc = false;
+
+       /* Fiddle around with fastcc later on, for now just use full reset */
+       fastcc = false;
+
+       htc_stop(priv->htc);
+       WMI_CMD(WMI_DISABLE_INTR_CMDID);
+       WMI_CMD(WMI_DRAIN_TXQ_ALL_CMDID);
+       WMI_CMD(WMI_STOP_RECV_CMDID);
+
+       ath_print(common, ATH_DBG_CONFIG,
+                 "(%u MHz) -> (%u MHz), HT: %d, HT40: %d\n",
+                 priv->ah->curchan->channel,
+                 channel->center_freq, conf_is_ht(conf), conf_is_ht40(conf));
+
+       ret = ath9k_hw_reset(ah, hchan, fastcc);
+       if (ret) {
+               ath_print(common, ATH_DBG_FATAL,
+                         "Unable to reset channel (%u Mhz) "
+                         "reset status %d\n", channel->center_freq, ret);
+               goto err;
+       }
+
+       ath_update_txpow(priv);
+
+       WMI_CMD(WMI_START_RECV_CMDID);
+       if (ret)
+               goto err;
+
+       ath9k_host_rx_init(priv);
+
+       mode = ath9k_htc_get_curmode(priv, hchan);
+       htc_mode = cpu_to_be16(mode);
+       WMI_CMD_BUF(WMI_SET_MODE_CMDID, &htc_mode);
+       if (ret)
+               goto err;
+
+       WMI_CMD(WMI_ENABLE_INTR_CMDID);
+       if (ret)
+               goto err;
+
+       htc_start(priv->htc);
+
+       priv->op_flags &= ~OP_FULL_RESET;
+err:
+       return ret;
+}
+
+static int ath9k_htc_add_monitor_interface(struct ath9k_htc_priv *priv)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       struct ath9k_htc_target_vif hvif;
+       int ret = 0;
+       u8 cmd_rsp;
+
+       if (priv->nvifs > 0)
+               return -ENOBUFS;
+
+       memset(&hvif, 0, sizeof(struct ath9k_htc_target_vif));
+       memcpy(&hvif.myaddr, common->macaddr, ETH_ALEN);
+
+       hvif.opmode = cpu_to_be32(HTC_M_MONITOR);
+       priv->ah->opmode = NL80211_IFTYPE_MONITOR;
+       hvif.index = priv->nvifs;
+
+       WMI_CMD_BUF(WMI_VAP_CREATE_CMDID, &hvif);
+       if (ret)
+               return ret;
+
+       priv->nvifs++;
+       return 0;
+}
+
+static int ath9k_htc_remove_monitor_interface(struct ath9k_htc_priv *priv)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       struct ath9k_htc_target_vif hvif;
+       int ret = 0;
+       u8 cmd_rsp;
+
+       memset(&hvif, 0, sizeof(struct ath9k_htc_target_vif));
+       memcpy(&hvif.myaddr, common->macaddr, ETH_ALEN);
+       hvif.index = 0; /* Should do for now */
+       WMI_CMD_BUF(WMI_VAP_REMOVE_CMDID, &hvif);
+       priv->nvifs--;
+
+       return ret;
+}
+
+static int ath9k_htc_add_station(struct ath9k_htc_priv *priv,
+                                struct ieee80211_vif *vif,
+                                struct ieee80211_sta *sta)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       struct ath9k_htc_target_sta tsta;
+       struct ath9k_htc_vif *avp = (struct ath9k_htc_vif *) vif->drv_priv;
+       struct ath9k_htc_sta *ista;
+       int ret;
+       u8 cmd_rsp;
+
+       if (priv->nstations >= ATH9K_HTC_MAX_STA)
+               return -ENOBUFS;
+
+       memset(&tsta, 0, sizeof(struct ath9k_htc_target_sta));
+
+       if (sta) {
+               ista = (struct ath9k_htc_sta *) sta->drv_priv;
+               memcpy(&tsta.macaddr, sta->addr, ETH_ALEN);
+               memcpy(&tsta.bssid, common->curbssid, ETH_ALEN);
+               tsta.associd = common->curaid;
+               tsta.is_vif_sta = 0;
+               tsta.valid = true;
+               ista->index = priv->nstations;
+       } else {
+               memcpy(&tsta.macaddr, vif->addr, ETH_ALEN);
+               tsta.is_vif_sta = 1;
+       }
+
+       tsta.sta_index = priv->nstations;
+       tsta.vif_index = avp->index;
+       tsta.maxampdu = 0xffff;
+       if (sta && sta->ht_cap.ht_supported)
+               tsta.flags = cpu_to_be16(ATH_HTC_STA_HT);
+
+       WMI_CMD_BUF(WMI_NODE_CREATE_CMDID, &tsta);
+       if (ret) {
+               if (sta)
+                       ath_print(common, ATH_DBG_FATAL,
+                         "Unable to add station entry for: %pM\n", sta->addr);
+               return ret;
+       }
+
+       if (sta)
+               ath_print(common, ATH_DBG_CONFIG,
+                         "Added a station entry for: %pM (idx: %d)\n",
+                         sta->addr, tsta.sta_index);
+
+       priv->nstations++;
+       return 0;
+}
+
+static int ath9k_htc_remove_station(struct ath9k_htc_priv *priv,
+                                   struct ieee80211_vif *vif,
+                                   struct ieee80211_sta *sta)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       struct ath9k_htc_sta *ista;
+       int ret;
+       u8 cmd_rsp, sta_idx;
+
+       if (sta) {
+               ista = (struct ath9k_htc_sta *) sta->drv_priv;
+               sta_idx = ista->index;
+       } else {
+               sta_idx = 0;
+       }
+
+       WMI_CMD_BUF(WMI_NODE_REMOVE_CMDID, &sta_idx);
+       if (ret) {
+               if (sta)
+                       ath_print(common, ATH_DBG_FATAL,
+                         "Unable to remove station entry for: %pM\n",
+                         sta->addr);
+               return ret;
+       }
+
+       if (sta)
+               ath_print(common, ATH_DBG_CONFIG,
+                         "Removed a station entry for: %pM (idx: %d)\n",
+                         sta->addr, sta_idx);
+
+       priv->nstations--;
+       return 0;
+}
+
+static int ath9k_htc_update_cap_target(struct ath9k_htc_priv *priv)
+{
+       struct ath9k_htc_cap_target tcap;
+       int ret;
+       u8 cmd_rsp;
+
+       memset(&tcap, 0, sizeof(struct ath9k_htc_cap_target));
+
+       /* FIXME: Values are hardcoded */
+       tcap.flags = 0x240c40;
+       tcap.flags_ext = 0x80601000;
+       tcap.ampdu_limit = 0xffff0000;
+       tcap.ampdu_subframes = 20;
+       tcap.tx_chainmask_legacy = 1;
+       tcap.protmode = 1;
+       tcap.tx_chainmask = 1;
+
+       WMI_CMD_BUF(WMI_TARGET_IC_UPDATE_CMDID, &tcap);
+
+       return ret;
+}
+
+static int ath9k_htc_init_rate(struct ath9k_htc_priv *priv,
+                                struct ieee80211_vif *vif,
+                                struct ieee80211_sta *sta)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       struct ath9k_htc_sta *ista = (struct ath9k_htc_sta *) sta->drv_priv;
+       struct ieee80211_supported_band *sband;
+       struct ath9k_htc_target_rate trate;
+       u32 caps = 0;
+       u8 cmd_rsp;
+       int i, j, ret;
+
+       memset(&trate, 0, sizeof(trate));
+
+       /* Only 2GHz is supported */
+       sband = priv->hw->wiphy->bands[IEEE80211_BAND_2GHZ];
+
+       for (i = 0, j = 0; i < sband->n_bitrates; i++) {
+               if (sta->supp_rates[sband->band] & BIT(i)) {
+                       priv->tgt_rate.rates.legacy_rates.rs_rates[j]
+                               = (sband->bitrates[i].bitrate * 2) / 10;
+                       j++;
+               }
+       }
+       priv->tgt_rate.rates.legacy_rates.rs_nrates = j;
+
+       if (sta->ht_cap.ht_supported) {
+               for (i = 0, j = 0; i < 77; i++) {
+                       if (sta->ht_cap.mcs.rx_mask[i/8] & (1<<(i%8)))
+                               priv->tgt_rate.rates.ht_rates.rs_rates[j++] = i;
+                       if (j == ATH_HTC_RATE_MAX)
+                               break;
+               }
+               priv->tgt_rate.rates.ht_rates.rs_nrates = j;
+
+               caps = WLAN_RC_HT_FLAG;
+               if (sta->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)
+                       caps |= WLAN_RC_40_FLAG;
+               if (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40)
+                       caps |= WLAN_RC_SGI_FLAG;
+
+       }
+
+       priv->tgt_rate.sta_index = ista->index;
+       priv->tgt_rate.isnew = 1;
+       trate = priv->tgt_rate;
+       priv->tgt_rate.capflags = caps;
+       trate.capflags = cpu_to_be32(caps);
+
+       WMI_CMD_BUF(WMI_RC_RATE_UPDATE_CMDID, &trate);
+       if (ret) {
+               ath_print(common, ATH_DBG_FATAL,
+                         "Unable to initialize Rate information on target\n");
+               return ret;
+       }
+
+       ath_print(common, ATH_DBG_CONFIG,
+                 "Updated target STA: %pM (caps: 0x%x)\n", sta->addr, caps);
+       return 0;
+}
+
+static bool check_rc_update(struct ieee80211_hw *hw, bool *cw40)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       struct ieee80211_conf *conf = &hw->conf;
+
+       if (!conf_is_ht(conf))
+               return false;
+
+       if (!(priv->op_flags & OP_ASSOCIATED) ||
+           (priv->op_flags & OP_SCANNING))
+               return false;
+
+       if (conf_is_ht40(conf)) {
+               if (priv->ah->curchan->chanmode &
+                       (CHANNEL_HT40PLUS | CHANNEL_HT40MINUS)) {
+                       return false;
+               } else {
+                       *cw40 = true;
+                       return true;
+               }
+       } else {  /* ht20 */
+               if (priv->ah->curchan->chanmode & CHANNEL_HT20)
+                       return false;
+               else
+                       return true;
+       }
+}
+
+static void ath9k_htc_rc_update(struct ath9k_htc_priv *priv, bool is_cw40)
+{
+       struct ath9k_htc_target_rate trate;
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       int ret;
+       u8 cmd_rsp;
+
+       memset(&trate, 0, sizeof(trate));
+
+       trate = priv->tgt_rate;
+
+       if (is_cw40)
+               priv->tgt_rate.capflags |= WLAN_RC_40_FLAG;
+       else
+               priv->tgt_rate.capflags &= ~WLAN_RC_40_FLAG;
+
+       trate.capflags = cpu_to_be32(priv->tgt_rate.capflags);
+
+       WMI_CMD_BUF(WMI_RC_RATE_UPDATE_CMDID, &trate);
+       if (ret) {
+               ath_print(common, ATH_DBG_FATAL,
+                         "Unable to update Rate information on target\n");
+               return;
+       }
+
+       ath_print(common, ATH_DBG_CONFIG, "Rate control updated with "
+                 "caps:0x%x on target\n", priv->tgt_rate.capflags);
+}
+
+static int ath9k_htc_aggr_oper(struct ath9k_htc_priv *priv,
+                              struct ieee80211_vif *vif,
+                              u8 *sta_addr, u8 tid, bool oper)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       struct ath9k_htc_target_aggr aggr;
+       struct ieee80211_sta *sta = NULL;
+       struct ath9k_htc_sta *ista = (struct ath9k_htc_sta *) sta->drv_priv;
+       int ret = 0;
+       u8 cmd_rsp;
+
+       if (tid > ATH9K_HTC_MAX_TID)
+               return -EINVAL;
+
+       rcu_read_lock();
+       sta = ieee80211_find_sta(vif, sta_addr);
+       if (sta) {
+               ista = (struct ath9k_htc_sta *) sta->drv_priv;
+       } else {
+               rcu_read_unlock();
+               return -EINVAL;
+       }
+
+       if (!ista) {
+               rcu_read_unlock();
+               return -EINVAL;
+       }
+
+       memset(&aggr, 0, sizeof(struct ath9k_htc_target_aggr));
+
+       aggr.sta_index = ista->index;
+       rcu_read_unlock();
+       aggr.tidno = tid;
+       aggr.aggr_enable = oper;
+
+       if (oper)
+               ista->tid_state[tid] = AGGR_START;
+       else
+               ista->tid_state[tid] = AGGR_STOP;
+
+       WMI_CMD_BUF(WMI_TX_AGGR_ENABLE_CMDID, &aggr);
+       if (ret)
+               ath_print(common, ATH_DBG_CONFIG,
+                         "Unable to %s TX aggregation for (%pM, %d)\n",
+                         (oper) ? "start" : "stop", sta->addr, tid);
+       else
+               ath_print(common, ATH_DBG_CONFIG,
+                         "%s aggregation for (%pM, %d)\n",
+                         (oper) ? "Starting" : "Stopping", sta->addr, tid);
+
+       return ret;
+}
+
+void ath9k_htc_aggr_work(struct work_struct *work)
+{
+       int ret = 0;
+       struct ath9k_htc_priv *priv =
+               container_of(work, struct ath9k_htc_priv,
+                            ath9k_aggr_work.work);
+       struct ath9k_htc_aggr_work *wk = &priv->aggr_work;
+
+       mutex_lock(&wk->mutex);
+
+       switch (wk->action) {
+       case IEEE80211_AMPDU_TX_START:
+               ret = ath9k_htc_aggr_oper(priv, wk->vif, wk->sta_addr,
+                                         wk->tid, true);
+               if (!ret)
+                       ieee80211_start_tx_ba_cb(wk->vif, wk->sta_addr,
+                                                wk->tid);
+               break;
+       case IEEE80211_AMPDU_TX_STOP:
+               ath9k_htc_aggr_oper(priv, wk->vif, wk->sta_addr,
+                                   wk->tid, false);
+               ieee80211_stop_tx_ba_cb(wk->vif, wk->sta_addr, wk->tid);
+               break;
+       default:
+               ath_print(ath9k_hw_common(priv->ah), ATH_DBG_FATAL,
+                         "Unknown AMPDU action\n");
+       }
+
+       mutex_unlock(&wk->mutex);
+}
+
+/*********/
+/* DEBUG */
+/*********/
+
+#ifdef CONFIG_ATH9K_HTC_DEBUGFS
+
+static int ath9k_debugfs_open(struct inode *inode, struct file *file)
+{
+       file->private_data = inode->i_private;
+       return 0;
+}
+
+static ssize_t read_file_tgt_stats(struct file *file, char __user *user_buf,
+                                  size_t count, loff_t *ppos)
+{
+       struct ath9k_htc_priv *priv =
+               (struct ath9k_htc_priv *) file->private_data;
+       struct ath9k_htc_target_stats cmd_rsp;
+       char buf[512];
+       unsigned int len = 0;
+       int ret = 0;
+
+       memset(&cmd_rsp, 0, sizeof(cmd_rsp));
+
+       WMI_CMD(WMI_TGT_STATS_CMDID);
+       if (ret)
+               return -EINVAL;
+
+
+       len += snprintf(buf + len, sizeof(buf) - len,
+                       "%19s : %10u\n", "TX Short Retries",
+                       be32_to_cpu(cmd_rsp.tx_shortretry));
+       len += snprintf(buf + len, sizeof(buf) - len,
+                       "%19s : %10u\n", "TX Long Retries",
+                       be32_to_cpu(cmd_rsp.tx_longretry));
+       len += snprintf(buf + len, sizeof(buf) - len,
+                       "%19s : %10u\n", "TX Xretries",
+                       be32_to_cpu(cmd_rsp.tx_xretries));
+       len += snprintf(buf + len, sizeof(buf) - len,
+                       "%19s : %10u\n", "TX Unaggr. Xretries",
+                       be32_to_cpu(cmd_rsp.ht_txunaggr_xretry));
+       len += snprintf(buf + len, sizeof(buf) - len,
+                       "%19s : %10u\n", "TX Xretries (HT)",
+                       be32_to_cpu(cmd_rsp.ht_tx_xretries));
+       len += snprintf(buf + len, sizeof(buf) - len,
+                       "%19s : %10u\n", "TX Rate", priv->debug.txrate);
+
+       return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static const struct file_operations fops_tgt_stats = {
+       .read = read_file_tgt_stats,
+       .open = ath9k_debugfs_open,
+       .owner = THIS_MODULE
+};
+
+static ssize_t read_file_xmit(struct file *file, char __user *user_buf,
+                             size_t count, loff_t *ppos)
+{
+       struct ath9k_htc_priv *priv =
+               (struct ath9k_htc_priv *) file->private_data;
+       char buf[512];
+       unsigned int len = 0;
+
+       len += snprintf(buf + len, sizeof(buf) - len,
+                       "%20s : %10u\n", "Buffers queued",
+                       priv->debug.tx_stats.buf_queued);
+       len += snprintf(buf + len, sizeof(buf) - len,
+                       "%20s : %10u\n", "Buffers completed",
+                       priv->debug.tx_stats.buf_completed);
+       len += snprintf(buf + len, sizeof(buf) - len,
+                       "%20s : %10u\n", "SKBs queued",
+                       priv->debug.tx_stats.skb_queued);
+       len += snprintf(buf + len, sizeof(buf) - len,
+                       "%20s : %10u\n", "SKBs completed",
+                       priv->debug.tx_stats.skb_completed);
+
+       return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static const struct file_operations fops_xmit = {
+       .read = read_file_xmit,
+       .open = ath9k_debugfs_open,
+       .owner = THIS_MODULE
+};
+
+static ssize_t read_file_recv(struct file *file, char __user *user_buf,
+                             size_t count, loff_t *ppos)
+{
+       struct ath9k_htc_priv *priv =
+               (struct ath9k_htc_priv *) file->private_data;
+       char buf[512];
+       unsigned int len = 0;
+
+       len += snprintf(buf + len, sizeof(buf) - len,
+                       "%20s : %10u\n", "SKBs allocated",
+                       priv->debug.rx_stats.skb_allocated);
+       len += snprintf(buf + len, sizeof(buf) - len,
+                       "%20s : %10u\n", "SKBs completed",
+                       priv->debug.rx_stats.skb_completed);
+       len += snprintf(buf + len, sizeof(buf) - len,
+                       "%20s : %10u\n", "SKBs Dropped",
+                       priv->debug.rx_stats.skb_dropped);
+
+       return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static const struct file_operations fops_recv = {
+       .read = read_file_recv,
+       .open = ath9k_debugfs_open,
+       .owner = THIS_MODULE
+};
+
+int ath9k_init_debug(struct ath_hw *ah)
+{
+       struct ath_common *common = ath9k_hw_common(ah);
+       struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv;
+
+       if (!ath9k_debugfs_root)
+               return -ENOENT;
+
+       priv->debug.debugfs_phy = debugfs_create_dir(wiphy_name(priv->hw->wiphy),
+                                                    ath9k_debugfs_root);
+       if (!priv->debug.debugfs_phy)
+               goto err;
+
+       priv->debug.debugfs_tgt_stats = debugfs_create_file("tgt_stats", S_IRUSR,
+                                                   priv->debug.debugfs_phy,
+                                                   priv, &fops_tgt_stats);
+       if (!priv->debug.debugfs_tgt_stats)
+               goto err;
+
+
+       priv->debug.debugfs_xmit = debugfs_create_file("xmit", S_IRUSR,
+                                                      priv->debug.debugfs_phy,
+                                                      priv, &fops_xmit);
+       if (!priv->debug.debugfs_xmit)
+               goto err;
+
+       priv->debug.debugfs_recv = debugfs_create_file("recv", S_IRUSR,
+                                                      priv->debug.debugfs_phy,
+                                                      priv, &fops_recv);
+       if (!priv->debug.debugfs_recv)
+               goto err;
+
+       return 0;
+
+err:
+       ath9k_exit_debug(ah);
+       return -ENOMEM;
+}
+
+void ath9k_exit_debug(struct ath_hw *ah)
+{
+       struct ath_common *common = ath9k_hw_common(ah);
+       struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv;
+
+       debugfs_remove(priv->debug.debugfs_recv);
+       debugfs_remove(priv->debug.debugfs_xmit);
+       debugfs_remove(priv->debug.debugfs_tgt_stats);
+       debugfs_remove(priv->debug.debugfs_phy);
+}
+
+int ath9k_debug_create_root(void)
+{
+       ath9k_debugfs_root = debugfs_create_dir(KBUILD_MODNAME, NULL);
+       if (!ath9k_debugfs_root)
+               return -ENOENT;
+
+       return 0;
+}
+
+void ath9k_debug_remove_root(void)
+{
+       debugfs_remove(ath9k_debugfs_root);
+       ath9k_debugfs_root = NULL;
+}
+
+#endif /* CONFIG_ATH9K_HTC_DEBUGFS */
+
+/*******/
+/* ANI */
+/*******/
+
+static void ath_start_ani(struct ath9k_htc_priv *priv)
+{
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       unsigned long timestamp = jiffies_to_msecs(jiffies);
+
+       common->ani.longcal_timer = timestamp;
+       common->ani.shortcal_timer = timestamp;
+       common->ani.checkani_timer = timestamp;
+
+       ieee80211_queue_delayed_work(common->hw, &priv->ath9k_ani_work,
+                                    msecs_to_jiffies(ATH_ANI_POLLINTERVAL));
+}
+
+void ath9k_ani_work(struct work_struct *work)
+{
+       struct ath9k_htc_priv *priv =
+               container_of(work, struct ath9k_htc_priv,
+                            ath9k_ani_work.work);
+       struct ath_hw *ah = priv->ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       bool longcal = false;
+       bool shortcal = false;
+       bool aniflag = false;
+       unsigned int timestamp = jiffies_to_msecs(jiffies);
+       u32 cal_interval, short_cal_interval;
+
+       short_cal_interval = ATH_STA_SHORT_CALINTERVAL;
+
+       /* Long calibration runs independently of short calibration. */
+       if ((timestamp - common->ani.longcal_timer) >= ATH_LONG_CALINTERVAL) {
+               longcal = true;
+               ath_print(common, ATH_DBG_ANI, "longcal @%lu\n", jiffies);
+               common->ani.longcal_timer = timestamp;
+       }
+
+       /* Short calibration applies only while caldone is false */
+       if (!common->ani.caldone) {
+               if ((timestamp - common->ani.shortcal_timer) >=
+                   short_cal_interval) {
+                       shortcal = true;
+                       ath_print(common, ATH_DBG_ANI,
+                                 "shortcal @%lu\n", jiffies);
+                       common->ani.shortcal_timer = timestamp;
+                       common->ani.resetcal_timer = timestamp;
+               }
+       } else {
+               if ((timestamp - common->ani.resetcal_timer) >=
+                   ATH_RESTART_CALINTERVAL) {
+                       common->ani.caldone = ath9k_hw_reset_calvalid(ah);
+                       if (common->ani.caldone)
+                               common->ani.resetcal_timer = timestamp;
+               }
+       }
+
+       /* Verify whether we must check ANI */
+       if ((timestamp - common->ani.checkani_timer) >= ATH_ANI_POLLINTERVAL) {
+               aniflag = true;
+               common->ani.checkani_timer = timestamp;
+       }
+
+       /* Skip all processing if there's nothing to do. */
+       if (longcal || shortcal || aniflag) {
+               /* Call ANI routine if necessary */
+               if (aniflag)
+                       ath9k_hw_ani_monitor(ah, ah->curchan);
+
+               /* Perform calibration if necessary */
+               if (longcal || shortcal) {
+                       common->ani.caldone =
+                               ath9k_hw_calibrate(ah, ah->curchan,
+                                                  common->rx_chainmask,
+                                                  longcal);
+
+                       if (longcal)
+                               common->ani.noise_floor =
+                                       ath9k_hw_getchan_noise(ah, ah->curchan);
+
+                       ath_print(common, ATH_DBG_ANI,
+                                 " calibrate chan %u/%x nf: %d\n",
+                                 ah->curchan->channel,
+                                 ah->curchan->channelFlags,
+                                 common->ani.noise_floor);
+               }
+       }
+
+       /*
+       * Set timer interval based on previous results.
+       * The interval must be the shortest necessary to satisfy ANI,
+       * short calibration and long calibration.
+       */
+       cal_interval = ATH_LONG_CALINTERVAL;
+       if (priv->ah->config.enable_ani)
+               cal_interval = min(cal_interval, (u32)ATH_ANI_POLLINTERVAL);
+       if (!common->ani.caldone)
+               cal_interval = min(cal_interval, (u32)short_cal_interval);
+
+       ieee80211_queue_delayed_work(common->hw, &priv->ath9k_ani_work,
+                                    msecs_to_jiffies(cal_interval));
+}
+
+/*******/
+/* LED */
+/*******/
+
+static void ath9k_led_blink_work(struct work_struct *work)
+{
+       struct ath9k_htc_priv *priv = container_of(work, struct ath9k_htc_priv,
+                                                  ath9k_led_blink_work.work);
+
+       if (!(priv->op_flags & OP_LED_ASSOCIATED))
+               return;
+
+       if ((priv->led_on_duration == ATH_LED_ON_DURATION_IDLE) ||
+           (priv->led_off_duration == ATH_LED_OFF_DURATION_IDLE))
+               ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin, 0);
+       else
+               ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin,
+                                 (priv->op_flags & OP_LED_ON) ? 1 : 0);
+
+       ieee80211_queue_delayed_work(priv->hw,
+                                    &priv->ath9k_led_blink_work,
+                                    (priv->op_flags & OP_LED_ON) ?
+                                    msecs_to_jiffies(priv->led_off_duration) :
+                                    msecs_to_jiffies(priv->led_on_duration));
+
+       priv->led_on_duration = priv->led_on_cnt ?
+               max((ATH_LED_ON_DURATION_IDLE - priv->led_on_cnt), 25) :
+               ATH_LED_ON_DURATION_IDLE;
+       priv->led_off_duration = priv->led_off_cnt ?
+               max((ATH_LED_OFF_DURATION_IDLE - priv->led_off_cnt), 10) :
+               ATH_LED_OFF_DURATION_IDLE;
+       priv->led_on_cnt = priv->led_off_cnt = 0;
+
+       if (priv->op_flags & OP_LED_ON)
+               priv->op_flags &= ~OP_LED_ON;
+       else
+               priv->op_flags |= OP_LED_ON;
+}
+
+static void ath9k_led_brightness_work(struct work_struct *work)
+{
+       struct ath_led *led = container_of(work, struct ath_led,
+                                          brightness_work.work);
+       struct ath9k_htc_priv *priv = led->priv;
+
+       switch (led->brightness) {
+       case LED_OFF:
+               if (led->led_type == ATH_LED_ASSOC ||
+                   led->led_type == ATH_LED_RADIO) {
+                       ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin,
+                                         (led->led_type == ATH_LED_RADIO));
+                       priv->op_flags &= ~OP_LED_ASSOCIATED;
+                       if (led->led_type == ATH_LED_RADIO)
+                               priv->op_flags &= ~OP_LED_ON;
+               } else {
+                       priv->led_off_cnt++;
+               }
+               break;
+       case LED_FULL:
+               if (led->led_type == ATH_LED_ASSOC) {
+                       priv->op_flags |= OP_LED_ASSOCIATED;
+                       ieee80211_queue_delayed_work(priv->hw,
+                                            &priv->ath9k_led_blink_work, 0);
+               } else if (led->led_type == ATH_LED_RADIO) {
+                       ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin, 0);
+                       priv->op_flags |= OP_LED_ON;
+               } else {
+                       priv->led_on_cnt++;
+               }
+               break;
+       default:
+               break;
+       }
+}
+
+static void ath9k_led_brightness(struct led_classdev *led_cdev,
+                                enum led_brightness brightness)
+{
+       struct ath_led *led = container_of(led_cdev, struct ath_led, led_cdev);
+       struct ath9k_htc_priv *priv = led->priv;
+
+       led->brightness = brightness;
+       if (!(priv->op_flags & OP_LED_DEINIT))
+               ieee80211_queue_delayed_work(priv->hw,
+                                            &led->brightness_work, 0);
+}
+
+static void ath9k_led_stop_brightness(struct ath9k_htc_priv *priv)
+{
+       cancel_delayed_work_sync(&priv->radio_led.brightness_work);
+       cancel_delayed_work_sync(&priv->assoc_led.brightness_work);
+       cancel_delayed_work_sync(&priv->tx_led.brightness_work);
+       cancel_delayed_work_sync(&priv->rx_led.brightness_work);
+}
+
+static int ath9k_register_led(struct ath9k_htc_priv *priv, struct ath_led *led,
+                             char *trigger)
+{
+       int ret;
+
+       led->priv = priv;
+       led->led_cdev.name = led->name;
+       led->led_cdev.default_trigger = trigger;
+       led->led_cdev.brightness_set = ath9k_led_brightness;
+
+       ret = led_classdev_register(wiphy_dev(priv->hw->wiphy), &led->led_cdev);
+       if (ret)
+               ath_print(ath9k_hw_common(priv->ah), ATH_DBG_FATAL,
+                         "Failed to register led:%s", led->name);
+       else
+               led->registered = 1;
+
+       INIT_DELAYED_WORK(&led->brightness_work, ath9k_led_brightness_work);
+
+       return ret;
+}
+
+static void ath9k_unregister_led(struct ath_led *led)
+{
+       if (led->registered) {
+               led_classdev_unregister(&led->led_cdev);
+               led->registered = 0;
+       }
+}
+
+void ath9k_deinit_leds(struct ath9k_htc_priv *priv)
+{
+       priv->op_flags |= OP_LED_DEINIT;
+       ath9k_unregister_led(&priv->assoc_led);
+       priv->op_flags &= ~OP_LED_ASSOCIATED;
+       ath9k_unregister_led(&priv->tx_led);
+       ath9k_unregister_led(&priv->rx_led);
+       ath9k_unregister_led(&priv->radio_led);
+       ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin, 1);
+}
+
+void ath9k_init_leds(struct ath9k_htc_priv *priv)
+{
+       char *trigger;
+       int ret;
+
+       if (AR_SREV_9287(priv->ah))
+               priv->ah->led_pin = ATH_LED_PIN_9287;
+       else if (AR_SREV_9271(priv->ah))
+               priv->ah->led_pin = ATH_LED_PIN_9271;
+       else
+               priv->ah->led_pin = ATH_LED_PIN_DEF;
+
+       /* Configure gpio 1 for output */
+       ath9k_hw_cfg_output(priv->ah, priv->ah->led_pin,
+                           AR_GPIO_OUTPUT_MUX_AS_OUTPUT);
+       /* LED off, active low */
+       ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin, 1);
+
+       INIT_DELAYED_WORK(&priv->ath9k_led_blink_work, ath9k_led_blink_work);
+
+       trigger = ieee80211_get_radio_led_name(priv->hw);
+       snprintf(priv->radio_led.name, sizeof(priv->radio_led.name),
+               "ath9k-%s::radio", wiphy_name(priv->hw->wiphy));
+       ret = ath9k_register_led(priv, &priv->radio_led, trigger);
+       priv->radio_led.led_type = ATH_LED_RADIO;
+       if (ret)
+               goto fail;
+
+       trigger = ieee80211_get_assoc_led_name(priv->hw);
+       snprintf(priv->assoc_led.name, sizeof(priv->assoc_led.name),
+               "ath9k-%s::assoc", wiphy_name(priv->hw->wiphy));
+       ret = ath9k_register_led(priv, &priv->assoc_led, trigger);
+       priv->assoc_led.led_type = ATH_LED_ASSOC;
+       if (ret)
+               goto fail;
+
+       trigger = ieee80211_get_tx_led_name(priv->hw);
+       snprintf(priv->tx_led.name, sizeof(priv->tx_led.name),
+               "ath9k-%s::tx", wiphy_name(priv->hw->wiphy));
+       ret = ath9k_register_led(priv, &priv->tx_led, trigger);
+       priv->tx_led.led_type = ATH_LED_TX;
+       if (ret)
+               goto fail;
+
+       trigger = ieee80211_get_rx_led_name(priv->hw);
+       snprintf(priv->rx_led.name, sizeof(priv->rx_led.name),
+               "ath9k-%s::rx", wiphy_name(priv->hw->wiphy));
+       ret = ath9k_register_led(priv, &priv->rx_led, trigger);
+       priv->rx_led.led_type = ATH_LED_RX;
+       if (ret)
+               goto fail;
+
+       priv->op_flags &= ~OP_LED_DEINIT;
+
+       return;
+
+fail:
+       cancel_delayed_work_sync(&priv->ath9k_led_blink_work);
+       ath9k_deinit_leds(priv);
+}
+
+/*******************/
+/*     Rfkill     */
+/*******************/
+
+static bool ath_is_rfkill_set(struct ath9k_htc_priv *priv)
+{
+       return ath9k_hw_gpio_get(priv->ah, priv->ah->rfkill_gpio) ==
+               priv->ah->rfkill_polarity;
+}
+
+static void ath9k_htc_rfkill_poll_state(struct ieee80211_hw *hw)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       bool blocked = !!ath_is_rfkill_set(priv);
+
+       wiphy_rfkill_set_hw_state(hw->wiphy, blocked);
+}
+
+void ath9k_start_rfkill_poll(struct ath9k_htc_priv *priv)
+{
+       if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT)
+               wiphy_rfkill_start_polling(priv->hw->wiphy);
+}
+
+/**********************/
+/* mac80211 Callbacks */
+/**********************/
+
+static int ath9k_htc_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
+{
+       struct ieee80211_hdr *hdr;
+       struct ath9k_htc_priv *priv = hw->priv;
+       int padpos, padsize;
+
+       hdr = (struct ieee80211_hdr *) skb->data;
+
+       /* Add the padding after the header if this is not already done */
+       padpos = ath9k_cmn_padpos(hdr->frame_control);
+       padsize = padpos & 3;
+       if (padsize && skb->len > padpos) {
+               if (skb_headroom(skb) < padsize)
+                       return -1;
+               skb_push(skb, padsize);
+               memmove(skb->data, skb->data + padsize, padpos);
+       }
+
+       if (ath9k_htc_tx_start(priv, skb) != 0) {
+               ath_print(ath9k_hw_common(priv->ah), ATH_DBG_XMIT, "Tx failed");
+               goto fail_tx;
+       }
+
+       return 0;
+
+fail_tx:
+       dev_kfree_skb_any(skb);
+       return 0;
+}
+
+static int ath9k_htc_start(struct ieee80211_hw *hw)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       struct ath_hw *ah = priv->ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       struct ieee80211_channel *curchan = hw->conf.channel;
+       struct ath9k_channel *init_channel;
+       int ret = 0;
+       enum htc_phymode mode;
+       u16 htc_mode;
+       u8 cmd_rsp;
+
+       ath_print(common, ATH_DBG_CONFIG,
+                 "Starting driver with initial channel: %d MHz\n",
+                 curchan->center_freq);
+
+       mutex_lock(&priv->mutex);
+
+       /* setup initial channel */
+       init_channel = ath9k_cmn_get_curchannel(hw, ah);
+
+       /* Reset SERDES registers */
+       ath9k_hw_configpcipowersave(ah, 0, 0);
+
+       ath9k_hw_htc_resetinit(ah);
+       ret = ath9k_hw_reset(ah, init_channel, false);
+       if (ret) {
+               ath_print(common, ATH_DBG_FATAL,
+                         "Unable to reset hardware; reset status %d "
+                         "(freq %u MHz)\n", ret, curchan->center_freq);
+               goto mutex_unlock;
+       }
+
+       ath_update_txpow(priv);
+
+       mode = ath9k_htc_get_curmode(priv, init_channel);
+       htc_mode = cpu_to_be16(mode);
+       WMI_CMD_BUF(WMI_SET_MODE_CMDID, &htc_mode);
+       if (ret)
+               goto mutex_unlock;
+
+       WMI_CMD(WMI_ATH_INIT_CMDID);
+       if (ret)
+               goto mutex_unlock;
+
+       WMI_CMD(WMI_START_RECV_CMDID);
+       if (ret)
+               goto mutex_unlock;
+
+       ath9k_host_rx_init(priv);
+
+       priv->op_flags &= ~OP_INVALID;
+       htc_start(priv->htc);
+
+mutex_unlock:
+       mutex_unlock(&priv->mutex);
+       return ret;
+}
+
+static void ath9k_htc_stop(struct ieee80211_hw *hw)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       struct ath_hw *ah = priv->ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       int ret = 0;
+       u8 cmd_rsp;
+
+       mutex_lock(&priv->mutex);
+
+       if (priv->op_flags & OP_INVALID) {
+               ath_print(common, ATH_DBG_ANY, "Device not present\n");
+               mutex_unlock(&priv->mutex);
+               return;
+       }
+
+       htc_stop(priv->htc);
+       WMI_CMD(WMI_DISABLE_INTR_CMDID);
+       WMI_CMD(WMI_DRAIN_TXQ_ALL_CMDID);
+       WMI_CMD(WMI_STOP_RECV_CMDID);
+       ath9k_hw_phy_disable(ah);
+       ath9k_hw_disable(ah);
+       ath9k_hw_configpcipowersave(ah, 1, 1);
+       ath9k_hw_setpower(ah, ATH9K_PM_FULL_SLEEP);
+
+       cancel_delayed_work_sync(&priv->ath9k_ani_work);
+       cancel_delayed_work_sync(&priv->ath9k_aggr_work);
+       cancel_delayed_work_sync(&priv->ath9k_led_blink_work);
+       ath9k_led_stop_brightness(priv);
+       skb_queue_purge(&priv->tx_queue);
+
+       /* Remove monitor interface here */
+       if (ah->opmode == NL80211_IFTYPE_MONITOR) {
+               if (ath9k_htc_remove_monitor_interface(priv))
+                       ath_print(common, ATH_DBG_FATAL,
+                                 "Unable to remove monitor interface\n");
+               else
+                       ath_print(common, ATH_DBG_CONFIG,
+                                 "Monitor interface removed\n");
+       }
+
+       priv->op_flags |= OP_INVALID;
+       mutex_unlock(&priv->mutex);
+
+       ath_print(common, ATH_DBG_CONFIG, "Driver halt\n");
+}
+
+static int ath9k_htc_add_interface(struct ieee80211_hw *hw,
+                                  struct ieee80211_vif *vif)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       struct ath9k_htc_vif *avp = (void *)vif->drv_priv;
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       struct ath9k_htc_target_vif hvif;
+       int ret = 0;
+       u8 cmd_rsp;
+
+       mutex_lock(&priv->mutex);
+
+       /* Only one interface for now */
+       if (priv->nvifs > 0) {
+               ret = -ENOBUFS;
+               goto out;
+       }
+
+       memset(&hvif, 0, sizeof(struct ath9k_htc_target_vif));
+       memcpy(&hvif.myaddr, vif->addr, ETH_ALEN);
+
+       switch (vif->type) {
+       case NL80211_IFTYPE_STATION:
+               hvif.opmode = cpu_to_be32(HTC_M_STA);
+               break;
+       case NL80211_IFTYPE_ADHOC:
+               hvif.opmode = cpu_to_be32(HTC_M_IBSS);
+               break;
+       default:
+               ath_print(common, ATH_DBG_FATAL,
+                       "Interface type %d not yet supported\n", vif->type);
+               ret = -EOPNOTSUPP;
+               goto out;
+       }
+
+       ath_print(common, ATH_DBG_CONFIG,
+                 "Attach a VIF of type: %d\n", vif->type);
+
+       priv->ah->opmode = vif->type;
+
+       /* Index starts from zero on the target */
+       avp->index = hvif.index = priv->nvifs;
+       hvif.rtsthreshold = cpu_to_be16(2304);
+       WMI_CMD_BUF(WMI_VAP_CREATE_CMDID, &hvif);
+       if (ret)
+               goto out;
+
+       priv->nvifs++;
+
+       /*
+        * We need a node in target to tx mgmt frames
+        * before association.
+        */
+       ret = ath9k_htc_add_station(priv, vif, NULL);
+       if (ret)
+               goto out;
+
+       ret = ath9k_htc_update_cap_target(priv);
+       if (ret)
+               ath_print(common, ATH_DBG_CONFIG, "Failed to update"
+                         " capability in target \n");
+
+       priv->vif = vif;
+out:
+       mutex_unlock(&priv->mutex);
+       return ret;
+}
+
+static void ath9k_htc_remove_interface(struct ieee80211_hw *hw,
+                                      struct ieee80211_vif *vif)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       struct ath9k_htc_vif *avp = (void *)vif->drv_priv;
+       struct ath9k_htc_target_vif hvif;
+       int ret = 0;
+       u8 cmd_rsp;
+
+       ath_print(common, ATH_DBG_CONFIG, "Detach Interface\n");
+
+       mutex_lock(&priv->mutex);
+
+       memset(&hvif, 0, sizeof(struct ath9k_htc_target_vif));
+       memcpy(&hvif.myaddr, vif->addr, ETH_ALEN);
+       hvif.index = avp->index;
+       WMI_CMD_BUF(WMI_VAP_REMOVE_CMDID, &hvif);
+       priv->nvifs--;
+
+       ath9k_htc_remove_station(priv, vif, NULL);
+
+       if (vif->type == NL80211_IFTYPE_ADHOC) {
+               spin_lock_bh(&priv->beacon_lock);
+               if (priv->beacon)
+                       dev_kfree_skb_any(priv->beacon);
+               priv->beacon = NULL;
+               spin_unlock_bh(&priv->beacon_lock);
+       }
+
+       priv->vif = NULL;
+
+       mutex_unlock(&priv->mutex);
+}
+
+static int ath9k_htc_config(struct ieee80211_hw *hw, u32 changed)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       struct ieee80211_conf *conf = &hw->conf;
+
+       mutex_lock(&priv->mutex);
+
+       if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
+               struct ieee80211_channel *curchan = hw->conf.channel;
+               int pos = curchan->hw_value;
+               bool is_cw40 = false;
+
+               ath_print(common, ATH_DBG_CONFIG, "Set channel: %d MHz\n",
+                         curchan->center_freq);
+
+               if (check_rc_update(hw, &is_cw40))
+                       ath9k_htc_rc_update(priv, is_cw40);
+
+               ath9k_cmn_update_ichannel(hw, &priv->ah->channels[pos]);
+
+               if (ath9k_htc_set_channel(priv, hw, &priv->ah->channels[pos]) < 0) {
+                       ath_print(common, ATH_DBG_FATAL,
+                                 "Unable to set channel\n");
+                       mutex_unlock(&priv->mutex);
+                       return -EINVAL;
+               }
+
+       }
+
+       if (changed & IEEE80211_CONF_CHANGE_MONITOR) {
+               if (conf->flags & IEEE80211_CONF_MONITOR) {
+                       if (ath9k_htc_add_monitor_interface(priv))
+                               ath_print(common, ATH_DBG_FATAL,
+                                         "Failed to set monitor mode\n");
+                       else
+                               ath_print(common, ATH_DBG_CONFIG,
+                                         "HW opmode set to Monitor mode\n");
+               }
+       }
+
+       mutex_unlock(&priv->mutex);
+
+       return 0;
+}
+
+#define SUPPORTED_FILTERS                      \
+       (FIF_PROMISC_IN_BSS |                   \
+       FIF_ALLMULTI |                          \
+       FIF_CONTROL |                           \
+       FIF_PSPOLL |                            \
+       FIF_OTHER_BSS |                         \
+       FIF_BCN_PRBRESP_PROMISC |               \
+       FIF_FCSFAIL)
+
+static void ath9k_htc_configure_filter(struct ieee80211_hw *hw,
+                                      unsigned int changed_flags,
+                                      unsigned int *total_flags,
+                                      u64 multicast)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       u32 rfilt;
+
+       mutex_lock(&priv->mutex);
+
+       changed_flags &= SUPPORTED_FILTERS;
+       *total_flags &= SUPPORTED_FILTERS;
+
+       priv->rxfilter = *total_flags;
+       rfilt = ath9k_cmn_calcrxfilter(hw, priv->ah, priv->rxfilter);
+       ath9k_hw_setrxfilter(priv->ah, rfilt);
+
+       ath_print(ath9k_hw_common(priv->ah), ATH_DBG_CONFIG,
+                 "Set HW RX filter: 0x%x\n", rfilt);
+
+       mutex_unlock(&priv->mutex);
+}
+
+static void ath9k_htc_sta_notify(struct ieee80211_hw *hw,
+                                struct ieee80211_vif *vif,
+                                enum sta_notify_cmd cmd,
+                                struct ieee80211_sta *sta)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       int ret;
+
+       switch (cmd) {
+       case STA_NOTIFY_ADD:
+               ret = ath9k_htc_add_station(priv, vif, sta);
+               if (!ret)
+                       ath9k_htc_init_rate(priv, vif, sta);
+               break;
+       case STA_NOTIFY_REMOVE:
+               ath9k_htc_remove_station(priv, vif, sta);
+               break;
+       default:
+               break;
+       }
+}
+
+static int ath9k_htc_conf_tx(struct ieee80211_hw *hw, u16 queue,
+                            const struct ieee80211_tx_queue_params *params)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       struct ath9k_tx_queue_info qi;
+       int ret = 0, qnum;
+
+       if (queue >= WME_NUM_AC)
+               return 0;
+
+       mutex_lock(&priv->mutex);
+
+       memset(&qi, 0, sizeof(struct ath9k_tx_queue_info));
+
+       qi.tqi_aifs = params->aifs;
+       qi.tqi_cwmin = params->cw_min;
+       qi.tqi_cwmax = params->cw_max;
+       qi.tqi_burstTime = params->txop;
+
+       qnum = get_hw_qnum(queue, priv->hwq_map);
+
+       ath_print(common, ATH_DBG_CONFIG,
+                 "Configure tx [queue/hwq] [%d/%d],  "
+                 "aifs: %d, cw_min: %d, cw_max: %d, txop: %d\n",
+                 queue, qnum, params->aifs, params->cw_min,
+                 params->cw_max, params->txop);
+
+       ret = ath_txq_update(priv, qnum, &qi);
+       if (ret)
+               ath_print(common, ATH_DBG_FATAL, "TXQ Update failed\n");
+
+       mutex_unlock(&priv->mutex);
+
+       return ret;
+}
+
+static int ath9k_htc_set_key(struct ieee80211_hw *hw,
+                            enum set_key_cmd cmd,
+                            struct ieee80211_vif *vif,
+                            struct ieee80211_sta *sta,
+                            struct ieee80211_key_conf *key)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       int ret = 0;
+
+       if (modparam_nohwcrypt)
+               return -ENOSPC;
+
+       mutex_lock(&priv->mutex);
+       ath_print(common, ATH_DBG_CONFIG, "Set HW Key\n");
+
+       switch (cmd) {
+       case SET_KEY:
+               ret = ath9k_cmn_key_config(common, vif, sta, key);
+               if (ret >= 0) {
+                       key->hw_key_idx = ret;
+                       /* push IV and Michael MIC generation to stack */
+                       key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV;
+                       if (key->alg == ALG_TKIP)
+                               key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC;
+                       if (priv->ah->sw_mgmt_crypto && key->alg == ALG_CCMP)
+                               key->flags |= IEEE80211_KEY_FLAG_SW_MGMT;
+                       ret = 0;
+               }
+               break;
+       case DISABLE_KEY:
+               ath9k_cmn_key_delete(common, key);
+               break;
+       default:
+               ret = -EINVAL;
+       }
+
+       mutex_unlock(&priv->mutex);
+
+       return ret;
+}
+
+static void ath9k_htc_bss_info_changed(struct ieee80211_hw *hw,
+                                      struct ieee80211_vif *vif,
+                                      struct ieee80211_bss_conf *bss_conf,
+                                      u32 changed)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       struct ath_hw *ah = priv->ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+
+       mutex_lock(&priv->mutex);
+
+       if (changed & BSS_CHANGED_ASSOC) {
+               common->curaid = bss_conf->assoc ?
+                                bss_conf->aid : 0;
+               ath_print(common, ATH_DBG_CONFIG, "BSS Changed ASSOC %d\n",
+                       bss_conf->assoc);
+
+               if (bss_conf->assoc) {
+                       priv->op_flags |= OP_ASSOCIATED;
+                       ath_start_ani(priv);
+               } else {
+                       priv->op_flags &= ~OP_ASSOCIATED;
+                       cancel_delayed_work_sync(&priv->ath9k_ani_work);
+               }
+       }
+
+       if (changed & BSS_CHANGED_BSSID) {
+               /* Set BSSID */
+               memcpy(common->curbssid, bss_conf->bssid, ETH_ALEN);
+               ath9k_hw_write_associd(ah);
+
+               ath_print(common, ATH_DBG_CONFIG,
+                         "BSSID: %pM aid: 0x%x\n",
+                         common->curbssid, common->curaid);
+       }
+
+       if ((changed & BSS_CHANGED_BEACON_INT) ||
+           (changed & BSS_CHANGED_BEACON) ||
+           ((changed & BSS_CHANGED_BEACON_ENABLED) &&
+           bss_conf->enable_beacon)) {
+               priv->op_flags |= OP_ENABLE_BEACON;
+               ath9k_htc_beacon_config(priv, vif, bss_conf);
+       }
+
+       if (changed & BSS_CHANGED_BEACON)
+               ath9k_htc_beacon_update(priv, vif);
+
+       if ((changed & BSS_CHANGED_BEACON_ENABLED) &&
+           !bss_conf->enable_beacon) {
+               priv->op_flags &= ~OP_ENABLE_BEACON;
+               ath9k_htc_beacon_config(priv, vif, bss_conf);
+       }
+
+       if (changed & BSS_CHANGED_ERP_PREAMBLE) {
+               ath_print(common, ATH_DBG_CONFIG, "BSS Changed PREAMBLE %d\n",
+                         bss_conf->use_short_preamble);
+               if (bss_conf->use_short_preamble)
+                       priv->op_flags |= OP_PREAMBLE_SHORT;
+               else
+                       priv->op_flags &= ~OP_PREAMBLE_SHORT;
+       }
+
+       if (changed & BSS_CHANGED_ERP_CTS_PROT) {
+               ath_print(common, ATH_DBG_CONFIG, "BSS Changed CTS PROT %d\n",
+                         bss_conf->use_cts_prot);
+               if (bss_conf->use_cts_prot &&
+                   hw->conf.channel->band != IEEE80211_BAND_5GHZ)
+                       priv->op_flags |= OP_PROTECT_ENABLE;
+               else
+                       priv->op_flags &= ~OP_PROTECT_ENABLE;
+       }
+
+       if (changed & BSS_CHANGED_ERP_SLOT) {
+               if (bss_conf->use_short_slot)
+                       ah->slottime = 9;
+               else
+                       ah->slottime = 20;
+
+               ath9k_hw_init_global_settings(ah);
+       }
+
+       mutex_unlock(&priv->mutex);
+}
+
+static u64 ath9k_htc_get_tsf(struct ieee80211_hw *hw)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       u64 tsf;
+
+       mutex_lock(&priv->mutex);
+       tsf = ath9k_hw_gettsf64(priv->ah);
+       mutex_unlock(&priv->mutex);
+
+       return tsf;
+}
+
+static void ath9k_htc_set_tsf(struct ieee80211_hw *hw, u64 tsf)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+
+       mutex_lock(&priv->mutex);
+       ath9k_hw_settsf64(priv->ah, tsf);
+       mutex_unlock(&priv->mutex);
+}
+
+static void ath9k_htc_reset_tsf(struct ieee80211_hw *hw)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+
+       mutex_lock(&priv->mutex);
+       ath9k_hw_reset_tsf(priv->ah);
+       mutex_unlock(&priv->mutex);
+}
+
+static int ath9k_htc_ampdu_action(struct ieee80211_hw *hw,
+                                 struct ieee80211_vif *vif,
+                                 enum ieee80211_ampdu_mlme_action action,
+                                 struct ieee80211_sta *sta,
+                                 u16 tid, u16 *ssn)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+       struct ath9k_htc_aggr_work *work = &priv->aggr_work;
+       struct ath9k_htc_sta *ista;
+
+       switch (action) {
+       case IEEE80211_AMPDU_RX_START:
+               break;
+       case IEEE80211_AMPDU_RX_STOP:
+               break;
+       case IEEE80211_AMPDU_TX_START:
+       case IEEE80211_AMPDU_TX_STOP:
+               if (!(priv->op_flags & OP_TXAGGR))
+                       return -ENOTSUPP;
+               memcpy(work->sta_addr, sta->addr, ETH_ALEN);
+               work->hw = hw;
+               work->vif = vif;
+               work->action = action;
+               work->tid = tid;
+               ieee80211_queue_delayed_work(hw, &priv->ath9k_aggr_work, 0);
+               break;
+       case IEEE80211_AMPDU_TX_OPERATIONAL:
+               ista = (struct ath9k_htc_sta *) sta->drv_priv;
+               ista->tid_state[tid] = AGGR_OPERATIONAL;
+               break;
+       default:
+               ath_print(ath9k_hw_common(priv->ah), ATH_DBG_FATAL,
+                         "Unknown AMPDU action\n");
+       }
+
+       return 0;
+}
+
+static void ath9k_htc_sw_scan_start(struct ieee80211_hw *hw)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+
+       mutex_lock(&priv->mutex);
+       spin_lock_bh(&priv->beacon_lock);
+       priv->op_flags |= OP_SCANNING;
+       spin_unlock_bh(&priv->beacon_lock);
+       cancel_delayed_work_sync(&priv->ath9k_ani_work);
+       mutex_unlock(&priv->mutex);
+}
+
+static void ath9k_htc_sw_scan_complete(struct ieee80211_hw *hw)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+
+       mutex_lock(&priv->mutex);
+       spin_lock_bh(&priv->beacon_lock);
+       priv->op_flags &= ~OP_SCANNING;
+       spin_unlock_bh(&priv->beacon_lock);
+       priv->op_flags |= OP_FULL_RESET;
+       ath_start_ani(priv);
+       mutex_unlock(&priv->mutex);
+}
+
+static int ath9k_htc_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
+{
+       return 0;
+}
+
+static void ath9k_htc_set_coverage_class(struct ieee80211_hw *hw,
+                                        u8 coverage_class)
+{
+       struct ath9k_htc_priv *priv = hw->priv;
+
+       mutex_lock(&priv->mutex);
+       priv->ah->coverage_class = coverage_class;
+       ath9k_hw_init_global_settings(priv->ah);
+       mutex_unlock(&priv->mutex);
+}
+
+struct ieee80211_ops ath9k_htc_ops = {
+       .tx                 = ath9k_htc_tx,
+       .start              = ath9k_htc_start,
+       .stop               = ath9k_htc_stop,
+       .add_interface      = ath9k_htc_add_interface,
+       .remove_interface   = ath9k_htc_remove_interface,
+       .config             = ath9k_htc_config,
+       .configure_filter   = ath9k_htc_configure_filter,
+       .sta_notify         = ath9k_htc_sta_notify,
+       .conf_tx            = ath9k_htc_conf_tx,
+       .bss_info_changed   = ath9k_htc_bss_info_changed,
+       .set_key            = ath9k_htc_set_key,
+       .get_tsf            = ath9k_htc_get_tsf,
+       .set_tsf            = ath9k_htc_set_tsf,
+       .reset_tsf          = ath9k_htc_reset_tsf,
+       .ampdu_action       = ath9k_htc_ampdu_action,
+       .sw_scan_start      = ath9k_htc_sw_scan_start,
+       .sw_scan_complete   = ath9k_htc_sw_scan_complete,
+       .set_rts_threshold  = ath9k_htc_set_rts_threshold,
+       .rfkill_poll        = ath9k_htc_rfkill_poll_state,
+       .set_coverage_class = ath9k_htc_set_coverage_class,
+};
diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
new file mode 100644 (file)
index 0000000..dba22d3
--- /dev/null
@@ -0,0 +1,604 @@
+/*
+ * 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"
+
+/******/
+/* TX */
+/******/
+
+int get_hw_qnum(u16 queue, int *hwq_map)
+{
+       switch (queue) {
+       case 0:
+               return hwq_map[ATH9K_WME_AC_VO];
+       case 1:
+               return hwq_map[ATH9K_WME_AC_VI];
+       case 2:
+               return hwq_map[ATH9K_WME_AC_BE];
+       case 3:
+               return hwq_map[ATH9K_WME_AC_BK];
+       default:
+               return hwq_map[ATH9K_WME_AC_BE];
+       }
+}
+
+int ath_txq_update(struct ath9k_htc_priv *priv, int qnum,
+                  struct ath9k_tx_queue_info *qinfo)
+{
+       struct ath_hw *ah = priv->ah;
+       int error = 0;
+       struct ath9k_tx_queue_info qi;
+
+       ath9k_hw_get_txq_props(ah, qnum, &qi);
+
+       qi.tqi_aifs = qinfo->tqi_aifs;
+       qi.tqi_cwmin = qinfo->tqi_cwmin / 2; /* XXX */
+       qi.tqi_cwmax = qinfo->tqi_cwmax;
+       qi.tqi_burstTime = qinfo->tqi_burstTime;
+       qi.tqi_readyTime = qinfo->tqi_readyTime;
+
+       if (!ath9k_hw_set_txq_props(ah, qnum, &qi)) {
+               ath_print(ath9k_hw_common(ah), ATH_DBG_FATAL,
+                         "Unable to update hardware queue %u!\n", qnum);
+               error = -EIO;
+       } else {
+               ath9k_hw_resettxqueue(ah, qnum);
+       }
+
+       return error;
+}
+
+int ath9k_htc_tx_start(struct ath9k_htc_priv *priv, struct sk_buff *skb)
+{
+       struct ieee80211_hdr *hdr;
+       struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
+       struct ieee80211_sta *sta = tx_info->control.sta;
+       struct ath9k_htc_sta *ista;
+       struct ath9k_htc_vif *avp;
+       struct ath9k_htc_tx_ctl tx_ctl;
+       enum htc_endpoint_id epid;
+       u16 qnum, hw_qnum;
+       __le16 fc;
+       u8 *tx_fhdr;
+       u8 sta_idx;
+
+       hdr = (struct ieee80211_hdr *) skb->data;
+       fc = hdr->frame_control;
+
+       avp = (struct ath9k_htc_vif *) tx_info->control.vif->drv_priv;
+       if (sta) {
+               ista = (struct ath9k_htc_sta *) sta->drv_priv;
+               sta_idx = ista->index;
+       } else {
+               sta_idx = 0;
+       }
+
+       memset(&tx_ctl, 0, sizeof(struct ath9k_htc_tx_ctl));
+
+       if (ieee80211_is_data(fc)) {
+               struct tx_frame_hdr tx_hdr;
+               u8 *qc;
+
+               memset(&tx_hdr, 0, sizeof(struct tx_frame_hdr));
+
+               tx_hdr.node_idx = sta_idx;
+               tx_hdr.vif_idx = avp->index;
+
+               if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) {
+                       tx_ctl.type = ATH9K_HTC_AMPDU;
+                       tx_hdr.data_type = ATH9K_HTC_AMPDU;
+               } else {
+                       tx_ctl.type = ATH9K_HTC_NORMAL;
+                       tx_hdr.data_type = ATH9K_HTC_NORMAL;
+               }
+
+               if (ieee80211_is_data(fc)) {
+                       qc = ieee80211_get_qos_ctl(hdr);
+                       tx_hdr.tidno = qc[0] & IEEE80211_QOS_CTL_TID_MASK;
+               }
+
+               /* Check for RTS protection */
+               if (priv->hw->wiphy->rts_threshold != (u32) -1)
+                       if (skb->len > priv->hw->wiphy->rts_threshold)
+                               tx_hdr.flags |= ATH9K_HTC_TX_RTSCTS;
+
+               /* CTS-to-self */
+               if (!(tx_hdr.flags & ATH9K_HTC_TX_RTSCTS) &&
+                   (priv->op_flags & OP_PROTECT_ENABLE))
+                       tx_hdr.flags |= ATH9K_HTC_TX_CTSONLY;
+
+               tx_hdr.key_type = ath9k_cmn_get_hw_crypto_keytype(skb);
+               if (tx_hdr.key_type == ATH9K_KEY_TYPE_CLEAR)
+                       tx_hdr.keyix = (u8) ATH9K_TXKEYIX_INVALID;
+               else
+                       tx_hdr.keyix = tx_info->control.hw_key->hw_key_idx;
+
+               tx_fhdr = skb_push(skb, sizeof(tx_hdr));
+               memcpy(tx_fhdr, (u8 *) &tx_hdr, sizeof(tx_hdr));
+
+               qnum = skb_get_queue_mapping(skb);
+               hw_qnum = get_hw_qnum(qnum, priv->hwq_map);
+
+               switch (hw_qnum) {
+               case 0:
+                       epid = priv->data_be_ep;
+                       break;
+               case 2:
+                       epid = priv->data_vi_ep;
+                       break;
+               case 3:
+                       epid = priv->data_vo_ep;
+                       break;
+               case 1:
+               default:
+                       epid = priv->data_bk_ep;
+                       break;
+               }
+       } else {
+               struct tx_mgmt_hdr mgmt_hdr;
+
+               memset(&mgmt_hdr, 0, sizeof(struct tx_mgmt_hdr));
+
+               tx_ctl.type = ATH9K_HTC_NORMAL;
+
+               mgmt_hdr.node_idx = sta_idx;
+               mgmt_hdr.vif_idx = avp->index;
+               mgmt_hdr.tidno = 0;
+               mgmt_hdr.flags = 0;
+
+               mgmt_hdr.key_type = ath9k_cmn_get_hw_crypto_keytype(skb);
+               if (mgmt_hdr.key_type == ATH9K_KEY_TYPE_CLEAR)
+                       mgmt_hdr.keyix = (u8) ATH9K_TXKEYIX_INVALID;
+               else
+                       mgmt_hdr.keyix = tx_info->control.hw_key->hw_key_idx;
+
+               tx_fhdr = skb_push(skb, sizeof(mgmt_hdr));
+               memcpy(tx_fhdr, (u8 *) &mgmt_hdr, sizeof(mgmt_hdr));
+               epid = priv->mgmt_ep;
+       }
+
+       return htc_send(priv->htc, skb, epid, &tx_ctl);
+}
+
+void ath9k_tx_tasklet(unsigned long data)
+{
+       struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)data;
+       struct ieee80211_sta *sta;
+       struct ieee80211_hdr *hdr;
+       struct ieee80211_tx_info *tx_info;
+       struct sk_buff *skb = NULL;
+       __le16 fc;
+
+       while ((skb = skb_dequeue(&priv->tx_queue)) != NULL) {
+
+               hdr = (struct ieee80211_hdr *) skb->data;
+               fc = hdr->frame_control;
+               tx_info = IEEE80211_SKB_CB(skb);
+               sta = tx_info->control.sta;
+
+               rcu_read_lock();
+
+               if (sta && conf_is_ht(&priv->hw->conf) &&
+                   (priv->op_flags & OP_TXAGGR)
+                   && !(skb->protocol == cpu_to_be16(ETH_P_PAE))) {
+                       if (ieee80211_is_data_qos(fc)) {
+                               u8 *qc, tid;
+                               struct ath9k_htc_sta *ista;
+
+                               qc = ieee80211_get_qos_ctl(hdr);
+                               tid = qc[0] & 0xf;
+                               ista = (struct ath9k_htc_sta *)sta->drv_priv;
+
+                               if ((tid < ATH9K_HTC_MAX_TID) &&
+                                   ista->tid_state[tid] == AGGR_STOP) {
+                                       ieee80211_start_tx_ba_session(sta, tid);
+                                       ista->tid_state[tid] = AGGR_PROGRESS;
+                               }
+                       }
+               }
+
+               rcu_read_unlock();
+
+               memset(&tx_info->status, 0, sizeof(tx_info->status));
+               ieee80211_tx_status(priv->hw, skb);
+       }
+}
+
+void ath9k_htc_txep(void *drv_priv, struct sk_buff *skb,
+                   enum htc_endpoint_id ep_id, bool txok)
+{
+       struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) drv_priv;
+       struct ieee80211_tx_info *tx_info;
+
+       if (!skb)
+               return;
+
+       if (ep_id == priv->mgmt_ep)
+               skb_pull(skb, sizeof(struct tx_mgmt_hdr));
+       else
+               /* TODO: Check for cab/uapsd/data */
+               skb_pull(skb, sizeof(struct tx_frame_hdr));
+
+       tx_info = IEEE80211_SKB_CB(skb);
+
+       if (txok)
+               tx_info->flags |= IEEE80211_TX_STAT_ACK;
+
+       skb_queue_tail(&priv->tx_queue, skb);
+       tasklet_schedule(&priv->tx_tasklet);
+}
+
+int ath9k_tx_init(struct ath9k_htc_priv *priv)
+{
+       skb_queue_head_init(&priv->tx_queue);
+       return 0;
+}
+
+void ath9k_tx_cleanup(struct ath9k_htc_priv *priv)
+{
+
+}
+
+bool ath9k_htc_txq_setup(struct ath9k_htc_priv *priv,
+                        enum ath9k_tx_queue_subtype subtype)
+{
+       struct ath_hw *ah = priv->ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       struct ath9k_tx_queue_info qi;
+       int qnum;
+
+       memset(&qi, 0, sizeof(qi));
+
+       qi.tqi_subtype = subtype;
+       qi.tqi_aifs = ATH9K_TXQ_USEDEFAULT;
+       qi.tqi_cwmin = ATH9K_TXQ_USEDEFAULT;
+       qi.tqi_cwmax = ATH9K_TXQ_USEDEFAULT;
+       qi.tqi_physCompBuf = 0;
+       qi.tqi_qflags = TXQ_FLAG_TXEOLINT_ENABLE | TXQ_FLAG_TXDESCINT_ENABLE;
+
+       qnum = ath9k_hw_setuptxqueue(priv->ah, ATH9K_TX_QUEUE_DATA, &qi);
+       if (qnum == -1)
+               return false;
+
+       if (qnum >= ARRAY_SIZE(priv->hwq_map)) {
+               ath_print(common, ATH_DBG_FATAL,
+                         "qnum %u out of range, max %u!\n",
+                         qnum, (unsigned int)ARRAY_SIZE(priv->hwq_map));
+               ath9k_hw_releasetxqueue(ah, qnum);
+               return false;
+       }
+
+       priv->hwq_map[subtype] = qnum;
+       return true;
+}
+
+/******/
+/* RX */
+/******/
+
+void ath9k_host_rx_init(struct ath9k_htc_priv *priv)
+{
+       ath9k_hw_rxena(priv->ah);
+       ath9k_cmn_opmode_init(priv->hw, priv->ah, priv->rxfilter);
+       ath9k_hw_startpcureceive(priv->ah);
+       priv->rx.last_rssi = ATH_RSSI_DUMMY_MARKER;
+}
+
+static void ath9k_process_rate(struct ieee80211_hw *hw,
+                              struct ieee80211_rx_status *rxs,
+                              u8 rx_rate, u8 rs_flags)
+{
+       struct ieee80211_supported_band *sband;
+       enum ieee80211_band band;
+       unsigned int i = 0;
+
+       if (rx_rate & 0x80) {
+               /* HT rate */
+               rxs->flag |= RX_FLAG_HT;
+               if (rs_flags & ATH9K_RX_2040)
+                       rxs->flag |= RX_FLAG_40MHZ;
+               if (rs_flags & ATH9K_RX_GI)
+                       rxs->flag |= RX_FLAG_SHORT_GI;
+               rxs->rate_idx = rx_rate & 0x7f;
+               return;
+       }
+
+       band = hw->conf.channel->band;
+       sband = hw->wiphy->bands[band];
+
+       for (i = 0; i < sband->n_bitrates; i++) {
+               if (sband->bitrates[i].hw_value == rx_rate) {
+                       rxs->rate_idx = i;
+                       return;
+               }
+               if (sband->bitrates[i].hw_value_short == rx_rate) {
+                       rxs->rate_idx = i;
+                       rxs->flag |= RX_FLAG_SHORTPRE;
+                       return;
+               }
+       }
+
+}
+
+static bool ath9k_rx_prepare(struct ath9k_htc_priv *priv,
+                            struct ath9k_htc_rxbuf *rxbuf,
+                            struct ieee80211_rx_status *rx_status)
+
+{
+       struct ieee80211_hdr *hdr;
+       struct ieee80211_hw *hw = priv->hw;
+       struct sk_buff *skb = rxbuf->skb;
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       int hdrlen, padpos, padsize;
+       int last_rssi = ATH_RSSI_DUMMY_MARKER;
+       __le16 fc;
+
+       hdr = (struct ieee80211_hdr *)skb->data;
+       fc = hdr->frame_control;
+       hdrlen = ieee80211_get_hdrlen_from_skb(skb);
+
+       padpos = ath9k_cmn_padpos(fc);
+
+       padsize = padpos & 3;
+       if (padsize && skb->len >= padpos+padsize) {
+               memmove(skb->data + padsize, skb->data, padpos);
+               skb_pull(skb, padsize);
+       }
+
+       memset(rx_status, 0, sizeof(struct ieee80211_rx_status));
+
+       if (rxbuf->rxstatus.rs_status != 0) {
+               if (rxbuf->rxstatus.rs_status & ATH9K_RXERR_CRC)
+                       rx_status->flag |= RX_FLAG_FAILED_FCS_CRC;
+               if (rxbuf->rxstatus.rs_status & ATH9K_RXERR_PHY)
+                       goto rx_next;
+
+               if (rxbuf->rxstatus.rs_status & ATH9K_RXERR_DECRYPT) {
+                       /* FIXME */
+               } else if (rxbuf->rxstatus.rs_status & ATH9K_RXERR_MIC) {
+                       if (ieee80211_is_ctl(fc))
+                               /*
+                                * Sometimes, we get invalid
+                                * MIC failures on valid control frames.
+                                * Remove these mic errors.
+                                */
+                               rxbuf->rxstatus.rs_status &= ~ATH9K_RXERR_MIC;
+                       else
+                               rx_status->flag |= RX_FLAG_MMIC_ERROR;
+               }
+
+               /*
+                * Reject error frames with the exception of
+                * decryption and MIC failures. For monitor mode,
+                * we also ignore the CRC error.
+                */
+               if (priv->ah->opmode == NL80211_IFTYPE_MONITOR) {
+                       if (rxbuf->rxstatus.rs_status &
+                           ~(ATH9K_RXERR_DECRYPT | ATH9K_RXERR_MIC |
+                             ATH9K_RXERR_CRC))
+                               goto rx_next;
+               } else {
+                       if (rxbuf->rxstatus.rs_status &
+                           ~(ATH9K_RXERR_DECRYPT | ATH9K_RXERR_MIC)) {
+                               goto rx_next;
+                       }
+               }
+       }
+
+       if (!(rxbuf->rxstatus.rs_status & ATH9K_RXERR_DECRYPT)) {
+               u8 keyix;
+               keyix = rxbuf->rxstatus.rs_keyix;
+               if (keyix != ATH9K_RXKEYIX_INVALID) {
+                       rx_status->flag |= RX_FLAG_DECRYPTED;
+               } else if (ieee80211_has_protected(fc) &&
+                          skb->len >= hdrlen + 4) {
+                       keyix = skb->data[hdrlen + 3] >> 6;
+                       if (test_bit(keyix, common->keymap))
+                               rx_status->flag |= RX_FLAG_DECRYPTED;
+               }
+       }
+
+       ath9k_process_rate(hw, rx_status, rxbuf->rxstatus.rs_rate,
+                          rxbuf->rxstatus.rs_flags);
+
+       if (priv->op_flags & OP_ASSOCIATED) {
+               if (rxbuf->rxstatus.rs_rssi != ATH9K_RSSI_BAD &&
+                   !rxbuf->rxstatus.rs_moreaggr)
+                       ATH_RSSI_LPF(priv->rx.last_rssi,
+                                    rxbuf->rxstatus.rs_rssi);
+
+               last_rssi = priv->rx.last_rssi;
+
+               if (likely(last_rssi != ATH_RSSI_DUMMY_MARKER))
+                       rxbuf->rxstatus.rs_rssi = ATH_EP_RND(last_rssi,
+                                                            ATH_RSSI_EP_MULTIPLIER);
+
+               if (rxbuf->rxstatus.rs_rssi < 0)
+                       rxbuf->rxstatus.rs_rssi = 0;
+
+               if (ieee80211_is_beacon(fc))
+                       priv->ah->stats.avgbrssi = rxbuf->rxstatus.rs_rssi;
+       }
+
+       rx_status->mactime = rxbuf->rxstatus.rs_tstamp;
+       rx_status->band = hw->conf.channel->band;
+       rx_status->freq = hw->conf.channel->center_freq;
+       rx_status->signal =  rxbuf->rxstatus.rs_rssi + ATH_DEFAULT_NOISE_FLOOR;
+       rx_status->antenna = rxbuf->rxstatus.rs_antenna;
+       rx_status->flag |= RX_FLAG_TSFT;
+
+       return true;
+
+rx_next:
+       return false;
+}
+
+/*
+ * FIXME: Handle FLUSH later on.
+ */
+void ath9k_rx_tasklet(unsigned long data)
+{
+       struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)data;
+       struct ath9k_htc_rxbuf *rxbuf = NULL, *tmp_buf = NULL;
+       struct ieee80211_rx_status rx_status;
+       struct sk_buff *skb;
+       unsigned long flags;
+
+
+       do {
+               spin_lock_irqsave(&priv->rx.rxbuflock, flags);
+               list_for_each_entry(tmp_buf, &priv->rx.rxbuf, list) {
+                       if (tmp_buf->in_process) {
+                               rxbuf = tmp_buf;
+                               break;
+                       }
+               }
+
+               if (rxbuf == NULL) {
+                       spin_unlock_irqrestore(&priv->rx.rxbuflock, flags);
+                       break;
+               }
+
+               if (!rxbuf->skb)
+                       goto requeue;
+
+               if (!ath9k_rx_prepare(priv, rxbuf, &rx_status)) {
+                       dev_kfree_skb_any(rxbuf->skb);
+                       goto requeue;
+               }
+
+               memcpy(IEEE80211_SKB_RXCB(rxbuf->skb), &rx_status,
+                      sizeof(struct ieee80211_rx_status));
+               skb = rxbuf->skb;
+               spin_unlock_irqrestore(&priv->rx.rxbuflock, flags);
+
+               ieee80211_rx(priv->hw, skb);
+
+               spin_lock_irqsave(&priv->rx.rxbuflock, flags);
+requeue:
+               rxbuf->in_process = false;
+               rxbuf->skb = NULL;
+               list_move_tail(&rxbuf->list, &priv->rx.rxbuf);
+               rxbuf = NULL;
+               spin_unlock_irqrestore(&priv->rx.rxbuflock, flags);
+       } while (1);
+
+}
+
+void ath9k_htc_rxep(void *drv_priv, struct sk_buff *skb,
+                   enum htc_endpoint_id ep_id)
+{
+       struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)drv_priv;
+       struct ath_hw *ah = priv->ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       struct ath9k_htc_rxbuf *rxbuf = NULL, *tmp_buf = NULL;
+       struct ath_htc_rx_status *rxstatus;
+       u32 len = 0;
+
+       spin_lock(&priv->rx.rxbuflock);
+       list_for_each_entry(tmp_buf, &priv->rx.rxbuf, list) {
+               if (!tmp_buf->in_process) {
+                       rxbuf = tmp_buf;
+                       break;
+               }
+       }
+       spin_unlock(&priv->rx.rxbuflock);
+
+       if (rxbuf == NULL) {
+               ath_print(common, ATH_DBG_ANY,
+                         "No free RX buffer\n");
+               goto err;
+       }
+
+       len = skb->len;
+       if (len <= HTC_RX_FRAME_HEADER_SIZE) {
+               ath_print(common, ATH_DBG_FATAL,
+                         "Corrupted RX frame, dropping\n");
+               goto err;
+       }
+
+       rxstatus = (struct ath_htc_rx_status *)skb->data;
+
+       rxstatus->rs_tstamp = be64_to_cpu(rxstatus->rs_tstamp);
+       rxstatus->rs_datalen = be16_to_cpu(rxstatus->rs_datalen);
+       rxstatus->evm0 = be32_to_cpu(rxstatus->evm0);
+       rxstatus->evm1 = be32_to_cpu(rxstatus->evm1);
+       rxstatus->evm2 = be32_to_cpu(rxstatus->evm2);
+
+       if (rxstatus->rs_datalen - (len - HTC_RX_FRAME_HEADER_SIZE) != 0) {
+               ath_print(common, ATH_DBG_FATAL,
+                         "Corrupted RX data len, dropping "
+                         "(epid: %d, dlen: %d, skblen: %d)\n",
+                         ep_id, rxstatus->rs_datalen, len);
+               goto err;
+       }
+
+       spin_lock(&priv->rx.rxbuflock);
+       memcpy(&rxbuf->rxstatus, rxstatus, HTC_RX_FRAME_HEADER_SIZE);
+       skb_pull(skb, HTC_RX_FRAME_HEADER_SIZE);
+       skb->len = rxstatus->rs_datalen;
+       rxbuf->skb = skb;
+       rxbuf->in_process = true;
+       spin_unlock(&priv->rx.rxbuflock);
+
+       tasklet_schedule(&priv->rx_tasklet);
+       return;
+err:
+       dev_kfree_skb_any(skb);
+       return;
+}
+
+/* FIXME: Locking for cleanup/init */
+
+void ath9k_rx_cleanup(struct ath9k_htc_priv *priv)
+{
+       struct ath9k_htc_rxbuf *rxbuf, *tbuf;
+
+       list_for_each_entry_safe(rxbuf, tbuf, &priv->rx.rxbuf, list) {
+               list_del(&rxbuf->list);
+               if (rxbuf->skb)
+                       dev_kfree_skb_any(rxbuf->skb);
+               kfree(rxbuf);
+       }
+}
+
+int ath9k_rx_init(struct ath9k_htc_priv *priv)
+{
+       struct ath_hw *ah = priv->ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       struct ath9k_htc_rxbuf *rxbuf;
+       int i = 0;
+
+       INIT_LIST_HEAD(&priv->rx.rxbuf);
+       spin_lock_init(&priv->rx.rxbuflock);
+
+       for (i = 0; i < ATH9K_HTC_RXBUF; i++) {
+               rxbuf = kzalloc(sizeof(struct ath9k_htc_rxbuf), GFP_KERNEL);
+               if (rxbuf == NULL) {
+                       ath_print(common, ATH_DBG_FATAL,
+                                 "Unable to allocate RX buffers\n");
+                       goto err;
+               }
+               list_add_tail(&rxbuf->list, &priv->rx.rxbuf);
+       }
+
+       return 0;
+
+err:
+       ath9k_rx_cleanup(priv);
+       return -ENOMEM;
+}
diff --git a/drivers/net/wireless/ath/ath9k/htc_hst.c b/drivers/net/wireless/ath/ath9k/htc_hst.c
new file mode 100644 (file)
index 0000000..9a48999
--- /dev/null
@@ -0,0 +1,463 @@
+/*
+ * 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"
+
+static int htc_issue_send(struct htc_target *target, struct sk_buff* skb,
+                         u16 len, u8 flags, u8 epid,
+                         struct ath9k_htc_tx_ctl *tx_ctl)
+{
+       struct htc_frame_hdr *hdr;
+       struct htc_endpoint *endpoint = &target->endpoint[epid];
+       int status;
+
+       hdr = (struct htc_frame_hdr *)
+               skb_push(skb, sizeof(struct htc_frame_hdr));
+       hdr->endpoint_id = epid;
+       hdr->flags = flags;
+       hdr->payload_len = cpu_to_be16(len);
+
+       status = target->hif->send(target->hif_dev, endpoint->ul_pipeid, skb,
+                                  tx_ctl);
+       return status;
+}
+
+static struct htc_endpoint *get_next_avail_ep(struct htc_endpoint *endpoint)
+{
+       enum htc_endpoint_id avail_epid;
+
+       for (avail_epid = ENDPOINT_MAX; avail_epid > ENDPOINT0; avail_epid--)
+               if (endpoint[avail_epid].service_id == 0)
+                       return &endpoint[avail_epid];
+       return NULL;
+}
+
+static u8 service_to_ulpipe(u16 service_id)
+{
+       switch (service_id) {
+       case WMI_CONTROL_SVC:
+               return 4;
+       case WMI_BEACON_SVC:
+       case WMI_CAB_SVC:
+       case WMI_UAPSD_SVC:
+       case WMI_MGMT_SVC:
+       case WMI_DATA_VO_SVC:
+       case WMI_DATA_VI_SVC:
+       case WMI_DATA_BE_SVC:
+       case WMI_DATA_BK_SVC:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+static u8 service_to_dlpipe(u16 service_id)
+{
+       switch (service_id) {
+       case WMI_CONTROL_SVC:
+               return 3;
+       case WMI_BEACON_SVC:
+       case WMI_CAB_SVC:
+       case WMI_UAPSD_SVC:
+       case WMI_MGMT_SVC:
+       case WMI_DATA_VO_SVC:
+       case WMI_DATA_VI_SVC:
+       case WMI_DATA_BE_SVC:
+       case WMI_DATA_BK_SVC:
+               return 2;
+       default:
+               return 0;
+       }
+}
+
+static void htc_process_target_rdy(struct htc_target *target,
+                                  void *buf)
+{
+       struct htc_endpoint *endpoint;
+       struct htc_ready_msg *htc_ready_msg = (struct htc_ready_msg *) buf;
+
+       target->credits = be16_to_cpu(htc_ready_msg->credits);
+       target->credit_size = be16_to_cpu(htc_ready_msg->credit_size);
+
+       endpoint = &target->endpoint[ENDPOINT0];
+       endpoint->service_id = HTC_CTRL_RSVD_SVC;
+       endpoint->max_msglen = HTC_MAX_CONTROL_MESSAGE_LENGTH;
+       complete(&target->target_wait);
+}
+
+static void htc_process_conn_rsp(struct htc_target *target,
+                                struct htc_frame_hdr *htc_hdr)
+{
+       struct htc_conn_svc_rspmsg *svc_rspmsg;
+       struct htc_endpoint *endpoint, *tmp_endpoint = NULL;
+       u16 service_id;
+       u16 max_msglen;
+       enum htc_endpoint_id epid, tepid;
+
+       svc_rspmsg = (struct htc_conn_svc_rspmsg *)
+               ((void *) htc_hdr + sizeof(struct htc_frame_hdr));
+
+       if (svc_rspmsg->status == HTC_SERVICE_SUCCESS) {
+               epid = svc_rspmsg->endpoint_id;
+               service_id = be16_to_cpu(svc_rspmsg->service_id);
+               max_msglen = be16_to_cpu(svc_rspmsg->max_msg_len);
+               endpoint = &target->endpoint[epid];
+
+               for (tepid = ENDPOINT_MAX; tepid > ENDPOINT0; tepid--) {
+                       tmp_endpoint = &target->endpoint[tepid];
+                       if (tmp_endpoint->service_id == service_id) {
+                               tmp_endpoint->service_id = 0;
+                               break;
+                       }
+               }
+
+               if (!tmp_endpoint)
+                       return;
+
+               endpoint->service_id = service_id;
+               endpoint->max_txqdepth = tmp_endpoint->max_txqdepth;
+               endpoint->ep_callbacks = tmp_endpoint->ep_callbacks;
+               endpoint->ul_pipeid = tmp_endpoint->ul_pipeid;
+               endpoint->dl_pipeid = tmp_endpoint->dl_pipeid;
+               endpoint->max_msglen = max_msglen;
+               target->conn_rsp_epid = epid;
+               complete(&target->cmd_wait);
+       } else {
+               target->conn_rsp_epid = ENDPOINT_UNUSED;
+       }
+}
+
+static int htc_config_pipe_credits(struct htc_target *target)
+{
+       struct sk_buff *skb;
+       struct htc_config_pipe_msg *cp_msg;
+       int ret, time_left;
+
+       skb = dev_alloc_skb(50 + sizeof(struct htc_frame_hdr));
+       if (!skb) {
+               dev_err(target->dev, "failed to allocate send buffer\n");
+               return -ENOMEM;
+       }
+       skb_reserve(skb, sizeof(struct htc_frame_hdr));
+
+       cp_msg = (struct htc_config_pipe_msg *)
+               skb_put(skb, sizeof(struct htc_config_pipe_msg));
+
+       cp_msg->message_id = cpu_to_be16(HTC_MSG_CONFIG_PIPE_ID);
+       cp_msg->pipe_id = USB_WLAN_TX_PIPE;
+       cp_msg->credits = 28;
+
+       target->htc_flags |= HTC_OP_CONFIG_PIPE_CREDITS;
+
+       ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0, NULL);
+       if (ret)
+               goto err;
+
+       time_left = wait_for_completion_timeout(&target->cmd_wait, HZ);
+       if (!time_left) {
+               dev_err(target->dev, "HTC credit config timeout\n");
+               return -ETIMEDOUT;
+       }
+
+       return 0;
+err:
+       dev_kfree_skb(skb);
+       return -EINVAL;
+}
+
+static int htc_setup_complete(struct htc_target *target)
+{
+       struct sk_buff *skb;
+       struct htc_comp_msg *comp_msg;
+       int ret = 0, time_left;
+
+       skb = dev_alloc_skb(50 + sizeof(struct htc_frame_hdr));
+       if (!skb) {
+               dev_err(target->dev, "failed to allocate send buffer\n");
+               return -ENOMEM;
+       }
+       skb_reserve(skb, sizeof(struct htc_frame_hdr));
+
+       comp_msg = (struct htc_comp_msg *)
+               skb_put(skb, sizeof(struct htc_comp_msg));
+       comp_msg->msg_id = cpu_to_be16(HTC_MSG_SETUP_COMPLETE_ID);
+
+       target->htc_flags |= HTC_OP_START_WAIT;
+
+       ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0, NULL);
+       if (ret)
+               goto err;
+
+       time_left = wait_for_completion_timeout(&target->cmd_wait, HZ);
+       if (!time_left) {
+               dev_err(target->dev, "HTC start timeout\n");
+               return -ETIMEDOUT;
+       }
+
+       return 0;
+
+err:
+       dev_kfree_skb(skb);
+       return -EINVAL;
+}
+
+/* HTC APIs */
+
+int htc_init(struct htc_target *target)
+{
+       int ret;
+
+       ret = htc_config_pipe_credits(target);
+       if (ret)
+               return ret;
+
+       return htc_setup_complete(target);
+}
+
+int htc_connect_service(struct htc_target *target,
+                    struct htc_service_connreq *service_connreq,
+                    enum htc_endpoint_id *conn_rsp_epid)
+{
+       struct sk_buff *skb;
+       struct htc_endpoint *endpoint;
+       struct htc_conn_svc_msg *conn_msg;
+       int ret, time_left;
+
+       /* Find an available endpoint */
+       endpoint = get_next_avail_ep(target->endpoint);
+       if (!endpoint) {
+               dev_err(target->dev, "Endpoint is not available for"
+                       "service %d\n", service_connreq->service_id);
+               return -EINVAL;
+       }
+
+       endpoint->service_id = service_connreq->service_id;
+       endpoint->max_txqdepth = service_connreq->max_send_qdepth;
+       endpoint->ul_pipeid = service_to_ulpipe(service_connreq->service_id);
+       endpoint->dl_pipeid = service_to_dlpipe(service_connreq->service_id);
+       endpoint->ep_callbacks = service_connreq->ep_callbacks;
+
+       skb = dev_alloc_skb(sizeof(struct htc_conn_svc_msg) +
+                           sizeof(struct htc_frame_hdr));
+       if (!skb) {
+               dev_err(target->dev, "Failed to allocate buf to send"
+                       "service connect req\n");
+               return -ENOMEM;
+       }
+
+       skb_reserve(skb, sizeof(struct htc_frame_hdr));
+
+       conn_msg = (struct htc_conn_svc_msg *)
+                       skb_put(skb, sizeof(struct htc_conn_svc_msg));
+       conn_msg->service_id = cpu_to_be16(service_connreq->service_id);
+       conn_msg->msg_id = cpu_to_be16(HTC_MSG_CONNECT_SERVICE_ID);
+       conn_msg->con_flags = cpu_to_be16(service_connreq->con_flags);
+       conn_msg->dl_pipeid = endpoint->dl_pipeid;
+       conn_msg->ul_pipeid = endpoint->ul_pipeid;
+
+       ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0, NULL);
+       if (ret)
+               goto err;
+
+       time_left = wait_for_completion_timeout(&target->cmd_wait, HZ);
+       if (!time_left) {
+               dev_err(target->dev, "Service connection timeout for: %d\n",
+                       service_connreq->service_id);
+               return -ETIMEDOUT;
+       }
+
+       *conn_rsp_epid = target->conn_rsp_epid;
+       return 0;
+err:
+       dev_kfree_skb(skb);
+       return ret;
+}
+
+int htc_send(struct htc_target *target, struct sk_buff *skb,
+            enum htc_endpoint_id epid, struct ath9k_htc_tx_ctl *tx_ctl)
+{
+       return htc_issue_send(target, skb, skb->len, 0, epid, tx_ctl);
+}
+
+void htc_stop(struct htc_target *target)
+{
+       enum htc_endpoint_id epid;
+       struct htc_endpoint *endpoint;
+
+       for (epid = ENDPOINT0; epid <= ENDPOINT_MAX; epid++) {
+               endpoint = &target->endpoint[epid];
+               if (endpoint->service_id != 0)
+                       target->hif->stop(target->hif_dev, endpoint->ul_pipeid);
+       }
+}
+
+void htc_start(struct htc_target *target)
+{
+       enum htc_endpoint_id epid;
+       struct htc_endpoint *endpoint;
+
+       for (epid = ENDPOINT0; epid <= ENDPOINT_MAX; epid++) {
+               endpoint = &target->endpoint[epid];
+               if (endpoint->service_id != 0)
+                       target->hif->start(target->hif_dev,
+                                          endpoint->ul_pipeid);
+       }
+}
+
+void ath9k_htc_txcompletion_cb(struct htc_target *htc_handle,
+                              struct sk_buff *skb, bool txok)
+{
+       struct htc_endpoint *endpoint;
+       struct htc_frame_hdr *htc_hdr;
+
+       if (htc_handle->htc_flags & HTC_OP_CONFIG_PIPE_CREDITS) {
+               complete(&htc_handle->cmd_wait);
+               htc_handle->htc_flags &= ~HTC_OP_CONFIG_PIPE_CREDITS;
+       }
+
+       if (htc_handle->htc_flags & HTC_OP_START_WAIT) {
+               complete(&htc_handle->cmd_wait);
+               htc_handle->htc_flags &= ~HTC_OP_START_WAIT;
+       }
+
+       if (skb) {
+               htc_hdr = (struct htc_frame_hdr *) skb->data;
+               endpoint = &htc_handle->endpoint[htc_hdr->endpoint_id];
+               skb_pull(skb, sizeof(struct htc_frame_hdr));
+
+               if (endpoint->ep_callbacks.tx) {
+                       endpoint->ep_callbacks.tx(htc_handle->drv_priv, skb,
+                                                 htc_hdr->endpoint_id, txok);
+               }
+       }
+}
+
+/*
+ * HTC Messages are handled directly here and the obtained SKB
+ * is freed.
+ *
+ * Sevice messages (Data, WMI) passed to the corresponding
+ * endpoint RX handlers, which have to free the SKB.
+ */
+void ath9k_htc_rx_msg(struct htc_target *htc_handle,
+                     struct sk_buff *skb, u32 len, u8 pipe_id)
+{
+       struct htc_frame_hdr *htc_hdr;
+       enum htc_endpoint_id epid;
+       struct htc_endpoint *endpoint;
+       u16 *msg_id;
+
+       if (!htc_handle || !skb)
+               return;
+
+       htc_hdr = (struct htc_frame_hdr *) skb->data;
+       epid = htc_hdr->endpoint_id;
+
+       if (epid >= ENDPOINT_MAX) {
+               dev_kfree_skb_any(skb);
+               return;
+       }
+
+       if (epid == ENDPOINT0) {
+
+               /* Handle trailer */
+               if (htc_hdr->flags & HTC_FLAGS_RECV_TRAILER) {
+                       if (be32_to_cpu(*(u32 *) skb->data) == 0x00C60000)
+                               /* Move past the Watchdog pattern */
+                               htc_hdr = (struct htc_frame_hdr *) skb->data + 4;
+               }
+
+               /* Get the message ID */
+               msg_id = (u16 *) ((void *) htc_hdr +
+                                          sizeof(struct htc_frame_hdr));
+
+               /* Now process HTC messages */
+               switch (be16_to_cpu(*msg_id)) {
+               case HTC_MSG_READY_ID:
+                       htc_process_target_rdy(htc_handle, htc_hdr);
+                       break;
+               case HTC_MSG_CONNECT_SERVICE_RESPONSE_ID:
+                       htc_process_conn_rsp(htc_handle, htc_hdr);
+                       break;
+               default:
+                       break;
+               }
+
+               dev_kfree_skb_any(skb);
+
+       } else {
+               if (htc_hdr->flags & HTC_FLAGS_RECV_TRAILER)
+                       skb_trim(skb, len - htc_hdr->control[0]);
+
+               skb_pull(skb, sizeof(struct htc_frame_hdr));
+
+               endpoint = &htc_handle->endpoint[epid];
+               if (endpoint->ep_callbacks.rx)
+                       endpoint->ep_callbacks.rx(endpoint->ep_callbacks.priv,
+                                                 skb, epid);
+       }
+}
+
+struct htc_target *ath9k_htc_hw_alloc(void *hif_handle)
+{
+       struct htc_target *target;
+
+       target = kzalloc(sizeof(struct htc_target), GFP_KERNEL);
+       if (!target)
+               printk(KERN_ERR "Unable to allocate memory for"
+                       "target device\n");
+
+       return target;
+}
+
+void ath9k_htc_hw_free(struct htc_target *htc)
+{
+       kfree(htc);
+}
+
+int ath9k_htc_hw_init(struct ath9k_htc_hif *hif, struct htc_target *target,
+                     void *hif_handle, struct device *dev, u16 devid,
+                     enum ath9k_hif_transports transport)
+{
+       struct htc_endpoint *endpoint;
+       int err = 0;
+
+       init_completion(&target->target_wait);
+       init_completion(&target->cmd_wait);
+
+       target->hif = hif;
+       target->hif_dev = hif_handle;
+       target->dev = dev;
+
+       /* Assign control endpoint pipe IDs */
+       endpoint = &target->endpoint[ENDPOINT0];
+       endpoint->ul_pipeid = hif->control_ul_pipe;
+       endpoint->dl_pipeid = hif->control_dl_pipe;
+
+       err = ath9k_htc_probe_device(target, dev, devid);
+       if (err) {
+               printk(KERN_ERR "Failed to initialize the device\n");
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+void ath9k_htc_hw_deinit(struct htc_target *target, bool hot_unplug)
+{
+       if (target)
+               ath9k_htc_disconnect_device(target, hot_unplug);
+}
diff --git a/drivers/net/wireless/ath/ath9k/htc_hst.h b/drivers/net/wireless/ath/ath9k/htc_hst.h
new file mode 100644 (file)
index 0000000..cd7048f
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * 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.
+ */
+
+#ifndef HTC_HST_H
+#define HTC_HST_H
+
+struct ath9k_htc_priv;
+struct htc_target;
+struct ath9k_htc_tx_ctl;
+
+enum ath9k_hif_transports {
+       ATH9K_HIF_USB,
+};
+
+struct ath9k_htc_hif {
+       struct list_head list;
+       const enum ath9k_hif_transports transport;
+       const char *name;
+
+       u8 control_dl_pipe;
+       u8 control_ul_pipe;
+
+       void (*start) (void *hif_handle, u8 pipe);
+       void (*stop) (void *hif_handle, u8 pipe);
+       int (*send) (void *hif_handle, u8 pipe, struct sk_buff *buf,
+                    struct ath9k_htc_tx_ctl *tx_ctl);
+};
+
+enum htc_endpoint_id {
+       ENDPOINT_UNUSED = -1,
+       ENDPOINT0 = 0,
+       ENDPOINT1 = 1,
+       ENDPOINT2 = 2,
+       ENDPOINT3 = 3,
+       ENDPOINT4 = 4,
+       ENDPOINT5 = 5,
+       ENDPOINT6 = 6,
+       ENDPOINT7 = 7,
+       ENDPOINT8 = 8,
+       ENDPOINT_MAX = 22
+};
+
+/* Htc frame hdr flags */
+#define HTC_FLAGS_RECV_TRAILER (1 << 1)
+
+struct htc_frame_hdr {
+       u8 endpoint_id;
+       u8 flags;
+       u16 payload_len;
+       u8 control[4];
+} __packed;
+
+struct htc_ready_msg {
+       u16 message_id;
+       u16 credits;
+       u16 credit_size;
+       u8 max_endpoints;
+       u8 pad;
+} __packed;
+
+struct htc_config_pipe_msg {
+       u16 message_id;
+       u8 pipe_id;
+       u8 credits;
+} __packed;
+
+struct htc_packet {
+       void *pktcontext;
+       u8 *buf;
+       u8 *buf_payload;
+       u32 buflen;
+       u32 payload_len;
+
+       int endpoint;
+       int status;
+
+       void *context;
+       u32 reserved;
+};
+
+struct htc_ep_callbacks {
+       void *priv;
+       void (*tx) (void *, struct sk_buff *, enum htc_endpoint_id, bool txok);
+       void (*rx) (void *, struct sk_buff *, enum htc_endpoint_id);
+};
+
+#define HTC_TX_QUEUE_SIZE 256
+
+struct htc_txq {
+       struct sk_buff *buf[HTC_TX_QUEUE_SIZE];
+       u32 txqdepth;
+       u16 txbuf_cnt;
+       u16 txq_head;
+       u16 txq_tail;
+};
+
+struct htc_endpoint {
+       u16 service_id;
+
+       struct htc_ep_callbacks ep_callbacks;
+       struct htc_txq htc_txq;
+       u32 max_txqdepth;
+       int max_msglen;
+
+       u8 ul_pipeid;
+       u8 dl_pipeid;
+};
+
+#define HTC_MAX_CONTROL_MESSAGE_LENGTH 255
+#define HTC_CONTROL_BUFFER_SIZE        \
+       (HTC_MAX_CONTROL_MESSAGE_LENGTH + sizeof(struct htc_frame_hdr))
+
+#define NUM_CONTROL_BUFFERS 8
+#define HST_ENDPOINT_MAX 8
+
+struct htc_control_buf {
+       struct htc_packet htc_pkt;
+       u8 buf[HTC_CONTROL_BUFFER_SIZE];
+};
+
+#define HTC_OP_START_WAIT           BIT(0)
+#define HTC_OP_CONFIG_PIPE_CREDITS  BIT(1)
+
+struct htc_target {
+       void *hif_dev;
+       struct ath9k_htc_priv *drv_priv;
+       struct device *dev;
+       struct ath9k_htc_hif *hif;
+       struct htc_endpoint endpoint[HST_ENDPOINT_MAX];
+       struct completion target_wait;
+       struct completion cmd_wait;
+       struct list_head list;
+       enum htc_endpoint_id conn_rsp_epid;
+       u16 credits;
+       u16 credit_size;
+       u8 htc_flags;
+};
+
+enum htc_msg_id {
+       HTC_MSG_READY_ID = 1,
+       HTC_MSG_CONNECT_SERVICE_ID,
+       HTC_MSG_CONNECT_SERVICE_RESPONSE_ID,
+       HTC_MSG_SETUP_COMPLETE_ID,
+       HTC_MSG_CONFIG_PIPE_ID,
+       HTC_MSG_CONFIG_PIPE_RESPONSE_ID,
+};
+
+struct htc_service_connreq {
+       u16 service_id;
+       u16 con_flags;
+       u32 max_send_qdepth;
+       struct htc_ep_callbacks ep_callbacks;
+};
+
+/* Current service IDs */
+
+enum htc_service_group_ids{
+       RSVD_SERVICE_GROUP = 0,
+       WMI_SERVICE_GROUP = 1,
+
+       HTC_SERVICE_GROUP_LAST = 255
+};
+
+#define MAKE_SERVICE_ID(group, index)          \
+       (int)(((int)group << 8) | (int)(index))
+
+/* NOTE: service ID of 0x0000 is reserved and should never be used */
+#define HTC_CTRL_RSVD_SVC MAKE_SERVICE_ID(RSVD_SERVICE_GROUP, 1)
+#define HTC_LOOPBACK_RSVD_SVC MAKE_SERVICE_ID(RSVD_SERVICE_GROUP, 2)
+
+#define WMI_CONTROL_SVC   MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 0)
+#define WMI_BEACON_SVC   MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 1)
+#define WMI_CAB_SVC      MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 2)
+#define WMI_UAPSD_SVC    MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 3)
+#define WMI_MGMT_SVC     MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 4)
+#define WMI_DATA_VO_SVC   MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 5)
+#define WMI_DATA_VI_SVC   MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 6)
+#define WMI_DATA_BE_SVC   MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 7)
+#define WMI_DATA_BK_SVC   MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 8)
+
+struct htc_conn_svc_msg {
+       u16 msg_id;
+       u16 service_id;
+       u16 con_flags;
+       u8 dl_pipeid;
+       u8 ul_pipeid;
+       u8 svc_meta_len;
+       u8 pad;
+} __packed;
+
+/* connect response status codes */
+#define HTC_SERVICE_SUCCESS      0
+#define HTC_SERVICE_NOT_FOUND    1
+#define HTC_SERVICE_FAILED       2
+#define HTC_SERVICE_NO_RESOURCES 3
+#define HTC_SERVICE_NO_MORE_EP   4
+
+struct htc_conn_svc_rspmsg {
+       u16 msg_id;
+       u16 service_id;
+       u8 status;
+       u8 endpoint_id;
+       u16 max_msg_len;
+       u8 svc_meta_len;
+       u8 pad;
+} __packed;
+
+struct htc_comp_msg {
+       u16 msg_id;
+} __packed;
+
+int htc_init(struct htc_target *target);
+int htc_connect_service(struct htc_target *target,
+                         struct htc_service_connreq *service_connreq,
+                         enum htc_endpoint_id *conn_rsp_eid);
+int htc_send(struct htc_target *target, struct sk_buff *skb,
+            enum htc_endpoint_id eid, struct ath9k_htc_tx_ctl *tx_ctl);
+void htc_stop(struct htc_target *target);
+void htc_start(struct htc_target *target);
+
+void ath9k_htc_rx_msg(struct htc_target *htc_handle,
+                     struct sk_buff *skb, u32 len, u8 pipe_id);
+void ath9k_htc_txcompletion_cb(struct htc_target *htc_handle,
+                              struct sk_buff *skb, bool txok);
+
+struct htc_target *ath9k_htc_hw_alloc(void *hif_handle);
+void ath9k_htc_hw_free(struct htc_target *htc);
+int ath9k_htc_hw_init(struct ath9k_htc_hif *hif, struct htc_target *target,
+                     void *hif_handle, struct device *dev, u16 devid,
+                     enum ath9k_hif_transports transport);
+void ath9k_htc_hw_deinit(struct htc_target *target, bool hot_unplug);
+
+#endif /* HTC_HST_H */
index 29851e6..a5e543b 100644 (file)
@@ -150,6 +150,32 @@ struct ath_rx_status {
        u32 evm2;
 };
 
+struct ath_htc_rx_status {
+       u64 rs_tstamp;
+       u16 rs_datalen;
+       u8 rs_status;
+       u8 rs_phyerr;
+       int8_t rs_rssi;
+       int8_t rs_rssi_ctl0;
+       int8_t rs_rssi_ctl1;
+       int8_t rs_rssi_ctl2;
+       int8_t rs_rssi_ext0;
+       int8_t rs_rssi_ext1;
+       int8_t rs_rssi_ext2;
+       u8 rs_keyix;
+       u8 rs_rate;
+       u8 rs_antenna;
+       u8 rs_more;
+       u8 rs_isaggr;
+       u8 rs_moreaggr;
+       u8 rs_num_delims;
+       u8 rs_flags;
+       u8 rs_dummy;
+       u32 evm0;
+       u32 evm1;
+       u32 evm2;
+};
+
 #define ATH9K_RXERR_CRC           0x01
 #define ATH9K_RXERR_PHY           0x02
 #define ATH9K_RXERR_FIFO          0x04
diff --git a/drivers/net/wireless/ath/ath9k/wmi.c b/drivers/net/wireless/ath/ath9k/wmi.c
new file mode 100644 (file)
index 0000000..818dea0
--- /dev/null
@@ -0,0 +1,319 @@
+/*
+ * 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"
+
+static const char *wmi_cmd_to_name(enum wmi_cmd_id wmi_cmd)
+{
+       switch (wmi_cmd) {
+       case WMI_ECHO_CMDID:
+               return "WMI_ECHO_CMDID";
+       case WMI_ACCESS_MEMORY_CMDID:
+               return "WMI_ACCESS_MEMORY_CMDID";
+       case WMI_DISABLE_INTR_CMDID:
+               return "WMI_DISABLE_INTR_CMDID";
+       case WMI_ENABLE_INTR_CMDID:
+               return "WMI_ENABLE_INTR_CMDID";
+       case WMI_RX_LINK_CMDID:
+               return "WMI_RX_LINK_CMDID";
+       case WMI_ATH_INIT_CMDID:
+               return "WMI_ATH_INIT_CMDID";
+       case WMI_ABORT_TXQ_CMDID:
+               return "WMI_ABORT_TXQ_CMDID";
+       case WMI_STOP_TX_DMA_CMDID:
+               return "WMI_STOP_TX_DMA_CMDID";
+       case WMI_STOP_DMA_RECV_CMDID:
+               return "WMI_STOP_DMA_RECV_CMDID";
+       case WMI_ABORT_TX_DMA_CMDID:
+               return "WMI_ABORT_TX_DMA_CMDID";
+       case WMI_DRAIN_TXQ_CMDID:
+               return "WMI_DRAIN_TXQ_CMDID";
+       case WMI_DRAIN_TXQ_ALL_CMDID:
+               return "WMI_DRAIN_TXQ_ALL_CMDID";
+       case WMI_START_RECV_CMDID:
+               return "WMI_START_RECV_CMDID";
+       case WMI_STOP_RECV_CMDID:
+               return "WMI_STOP_RECV_CMDID";
+       case WMI_FLUSH_RECV_CMDID:
+               return "WMI_FLUSH_RECV_CMDID";
+       case WMI_SET_MODE_CMDID:
+               return "WMI_SET_MODE_CMDID";
+       case WMI_RESET_CMDID:
+               return "WMI_RESET_CMDID";
+       case WMI_NODE_CREATE_CMDID:
+               return "WMI_NODE_CREATE_CMDID";
+       case WMI_NODE_REMOVE_CMDID:
+               return "WMI_NODE_REMOVE_CMDID";
+       case WMI_VAP_REMOVE_CMDID:
+               return "WMI_VAP_REMOVE_CMDID";
+       case WMI_VAP_CREATE_CMDID:
+               return "WMI_VAP_CREATE_CMDID";
+       case WMI_BEACON_UPDATE_CMDID:
+               return "WMI_BEACON_UPDATE_CMDID";
+       case WMI_REG_READ_CMDID:
+               return "WMI_REG_READ_CMDID";
+       case WMI_REG_WRITE_CMDID:
+               return "WMI_REG_WRITE_CMDID";
+       case WMI_RC_STATE_CHANGE_CMDID:
+               return "WMI_RC_STATE_CHANGE_CMDID";
+       case WMI_RC_RATE_UPDATE_CMDID:
+               return "WMI_RC_RATE_UPDATE_CMDID";
+       case WMI_DEBUG_INFO_CMDID:
+               return "WMI_DEBUG_INFO_CMDID";
+       case WMI_HOST_ATTACH:
+               return "WMI_HOST_ATTACH";
+       case WMI_TARGET_IC_UPDATE_CMDID:
+               return "WMI_TARGET_IC_UPDATE_CMDID";
+       case WMI_TGT_STATS_CMDID:
+               return "WMI_TGT_STATS_CMDID";
+       case WMI_TX_AGGR_ENABLE_CMDID:
+               return "WMI_TX_AGGR_ENABLE_CMDID";
+       case WMI_TGT_DETACH_CMDID:
+               return "WMI_TGT_DETACH_CMDID";
+       case WMI_TGT_TXQ_ENABLE_CMDID:
+               return "WMI_TGT_TXQ_ENABLE_CMDID";
+       }
+
+       return "Bogus";
+}
+
+struct wmi *ath9k_init_wmi(struct ath9k_htc_priv *priv)
+{
+       struct wmi *wmi;
+
+       wmi = kzalloc(sizeof(struct wmi), GFP_KERNEL);
+       if (!wmi)
+               return NULL;
+
+       wmi->drv_priv = priv;
+       wmi->stopped = false;
+       mutex_init(&wmi->op_mutex);
+       init_completion(&wmi->cmd_wait);
+
+       return wmi;
+}
+
+void ath9k_deinit_wmi(struct ath9k_htc_priv *priv)
+{
+       struct wmi *wmi = priv->wmi;
+
+       mutex_lock(&wmi->op_mutex);
+       wmi->stopped = true;
+       mutex_unlock(&wmi->op_mutex);
+
+       kfree(priv->wmi);
+}
+
+void ath9k_wmi_tasklet(unsigned long data)
+{
+       struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)data;
+       struct ath_common *common = ath9k_hw_common(priv->ah);
+       struct wmi_cmd_hdr *hdr;
+       struct wmi_swba *swba_hdr;
+       enum wmi_event_id event;
+       struct sk_buff *skb;
+       void *wmi_event;
+       unsigned long flags;
+#ifdef CONFIG_ATH9K_HTC_DEBUGFS
+       u32 txrate;
+#endif
+
+       spin_lock_irqsave(&priv->wmi->wmi_lock, flags);
+       skb = priv->wmi->wmi_skb;
+       spin_unlock_irqrestore(&priv->wmi->wmi_lock, flags);
+
+       hdr = (struct wmi_cmd_hdr *) skb->data;
+       event = be16_to_cpu(hdr->command_id);
+       wmi_event = skb_pull(skb, sizeof(struct wmi_cmd_hdr));
+
+       ath_print(common, ATH_DBG_WMI,
+                 "WMI Event: 0x%x\n", event);
+
+       switch (event) {
+       case WMI_TGT_RDY_EVENTID:
+               break;
+       case WMI_SWBA_EVENTID:
+               swba_hdr = (struct wmi_swba *) wmi_event;
+               ath9k_htc_swba(priv, swba_hdr->beacon_pending);
+               break;
+       case WMI_FATAL_EVENTID:
+               break;
+       case WMI_TXTO_EVENTID:
+               break;
+       case WMI_BMISS_EVENTID:
+               break;
+       case WMI_WLAN_TXCOMP_EVENTID:
+               break;
+       case WMI_DELBA_EVENTID:
+               break;
+       case WMI_TXRATE_EVENTID:
+#ifdef CONFIG_ATH9K_HTC_DEBUGFS
+               txrate = ((struct wmi_event_txrate *)wmi_event)->txrate;
+               priv->debug.txrate = be32_to_cpu(txrate);
+#endif
+               break;
+       default:
+               break;
+       }
+
+       dev_kfree_skb_any(skb);
+}
+
+static void ath9k_wmi_rsp_callback(struct wmi *wmi, struct sk_buff *skb)
+{
+       skb_pull(skb, sizeof(struct wmi_cmd_hdr));
+
+       if (wmi->cmd_rsp_buf != NULL && wmi->cmd_rsp_len != 0)
+               memcpy(wmi->cmd_rsp_buf, skb->data, wmi->cmd_rsp_len);
+
+       complete(&wmi->cmd_wait);
+}
+
+static void ath9k_wmi_ctrl_rx(void *priv, struct sk_buff *skb,
+                             enum htc_endpoint_id epid)
+{
+       struct wmi *wmi = (struct wmi *) priv;
+       struct wmi_cmd_hdr *hdr;
+       u16 cmd_id;
+
+       if (unlikely(wmi->stopped))
+               goto free_skb;
+
+       hdr = (struct wmi_cmd_hdr *) skb->data;
+       cmd_id = be16_to_cpu(hdr->command_id);
+
+       if (cmd_id & 0x1000) {
+               spin_lock(&wmi->wmi_lock);
+               wmi->wmi_skb = skb;
+               spin_unlock(&wmi->wmi_lock);
+               tasklet_schedule(&wmi->drv_priv->wmi_tasklet);
+               return;
+       }
+
+       /* WMI command response */
+       ath9k_wmi_rsp_callback(wmi, skb);
+
+free_skb:
+       dev_kfree_skb_any(skb);
+}
+
+static void ath9k_wmi_ctrl_tx(void *priv, struct sk_buff *skb,
+                             enum htc_endpoint_id epid, bool txok)
+{
+       dev_kfree_skb_any(skb);
+}
+
+int ath9k_wmi_connect(struct htc_target *htc, struct wmi *wmi,
+                     enum htc_endpoint_id *wmi_ctrl_epid)
+{
+       struct htc_service_connreq connect;
+       int ret;
+
+       wmi->htc = htc;
+
+       memset(&connect, 0, sizeof(connect));
+
+       connect.ep_callbacks.priv = wmi;
+       connect.ep_callbacks.tx = ath9k_wmi_ctrl_tx;
+       connect.ep_callbacks.rx = ath9k_wmi_ctrl_rx;
+       connect.service_id = WMI_CONTROL_SVC;
+
+       ret = htc_connect_service(htc, &connect, &wmi->ctrl_epid);
+       if (ret)
+               return ret;
+
+       *wmi_ctrl_epid = wmi->ctrl_epid;
+
+       return 0;
+}
+
+static int ath9k_wmi_cmd_issue(struct wmi *wmi,
+                              struct sk_buff *skb,
+                              enum wmi_cmd_id cmd, u16 len)
+{
+       struct wmi_cmd_hdr *hdr;
+
+       hdr = (struct wmi_cmd_hdr *) skb_push(skb, sizeof(struct wmi_cmd_hdr));
+       hdr->command_id = cpu_to_be16(cmd);
+       hdr->seq_no = cpu_to_be16(++wmi->tx_seq_id);
+
+       return htc_send(wmi->htc, skb, wmi->ctrl_epid, NULL);
+}
+
+int ath9k_wmi_cmd(struct wmi *wmi, enum wmi_cmd_id cmd_id,
+                 u8 *cmd_buf, u32 cmd_len,
+                 u8 *rsp_buf, u32 rsp_len,
+                 u32 timeout)
+{
+       struct ath_hw *ah = wmi->drv_priv->ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       u16 headroom = sizeof(struct htc_frame_hdr) +
+                      sizeof(struct wmi_cmd_hdr);
+       struct sk_buff *skb;
+       u8 *data;
+       int time_left, ret = 0;
+
+       if (!wmi)
+               return -EINVAL;
+
+       skb = dev_alloc_skb(headroom + cmd_len);
+       if (!skb)
+               return -ENOMEM;
+
+       skb_reserve(skb, headroom);
+
+       if (cmd_len != 0 && cmd_buf != NULL) {
+               data = (u8 *) skb_put(skb, cmd_len);
+               memcpy(data, cmd_buf, cmd_len);
+       }
+
+       mutex_lock(&wmi->op_mutex);
+
+       /* check if wmi stopped flag is set */
+       if (unlikely(wmi->stopped)) {
+               ret = -EPROTO;
+               goto out;
+       }
+
+       /* record the rsp buffer and length */
+       wmi->cmd_rsp_buf = rsp_buf;
+       wmi->cmd_rsp_len = rsp_len;
+
+       ret = ath9k_wmi_cmd_issue(wmi, skb, cmd_id, cmd_len);
+       if (ret)
+               goto out;
+
+       time_left = wait_for_completion_timeout(&wmi->cmd_wait, timeout);
+       if (!time_left) {
+               ath_print(common, ATH_DBG_WMI,
+                         "Timeout waiting for WMI command: %s\n",
+                         wmi_cmd_to_name(cmd_id));
+               mutex_unlock(&wmi->op_mutex);
+               return -ETIMEDOUT;
+       }
+
+       mutex_unlock(&wmi->op_mutex);
+
+       return 0;
+
+out:
+       ath_print(common, ATH_DBG_WMI,
+                 "WMI failure for: %s\n", wmi_cmd_to_name(cmd_id));
+       mutex_unlock(&wmi->op_mutex);
+       dev_kfree_skb_any(skb);
+
+       return ret;
+}
diff --git a/drivers/net/wireless/ath/ath9k/wmi.h b/drivers/net/wireless/ath/ath9k/wmi.h
new file mode 100644 (file)
index 0000000..39ef926
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+#ifndef WMI_H
+#define WMI_H
+
+
+struct wmi_event_txrate {
+       u32 txrate;
+       struct {
+               u8 rssi_thresh;
+               u8 per;
+       } rc_stats;
+} __packed;
+
+struct wmi_cmd_hdr {
+       u16 command_id;
+       u16 seq_no;
+} __packed;
+
+struct wmi_swba {
+       u8 beacon_pending;
+} __packed;
+
+enum wmi_cmd_id {
+       WMI_ECHO_CMDID = 0x0001,
+       WMI_ACCESS_MEMORY_CMDID,
+
+       /* Commands to Target */
+       WMI_DISABLE_INTR_CMDID,
+       WMI_ENABLE_INTR_CMDID,
+       WMI_RX_LINK_CMDID,
+       WMI_ATH_INIT_CMDID,
+       WMI_ABORT_TXQ_CMDID,
+       WMI_STOP_TX_DMA_CMDID,
+       WMI_STOP_DMA_RECV_CMDID,
+       WMI_ABORT_TX_DMA_CMDID,
+       WMI_DRAIN_TXQ_CMDID,
+       WMI_DRAIN_TXQ_ALL_CMDID,
+       WMI_START_RECV_CMDID,
+       WMI_STOP_RECV_CMDID,
+       WMI_FLUSH_RECV_CMDID,
+       WMI_SET_MODE_CMDID,
+       WMI_RESET_CMDID,
+       WMI_NODE_CREATE_CMDID,
+       WMI_NODE_REMOVE_CMDID,
+       WMI_VAP_REMOVE_CMDID,
+       WMI_VAP_CREATE_CMDID,
+       WMI_BEACON_UPDATE_CMDID,
+       WMI_REG_READ_CMDID,
+       WMI_REG_WRITE_CMDID,
+       WMI_RC_STATE_CHANGE_CMDID,
+       WMI_RC_RATE_UPDATE_CMDID,
+       WMI_DEBUG_INFO_CMDID,
+       WMI_HOST_ATTACH,
+       WMI_TARGET_IC_UPDATE_CMDID,
+       WMI_TGT_STATS_CMDID,
+       WMI_TX_AGGR_ENABLE_CMDID,
+       WMI_TGT_DETACH_CMDID,
+       WMI_TGT_TXQ_ENABLE_CMDID,
+};
+
+enum wmi_event_id {
+       WMI_TGT_RDY_EVENTID = 0x1001,
+       WMI_SWBA_EVENTID,
+       WMI_FATAL_EVENTID,
+       WMI_TXTO_EVENTID,
+       WMI_BMISS_EVENTID,
+       WMI_WLAN_TXCOMP_EVENTID,
+       WMI_DELBA_EVENTID,
+       WMI_TXRATE_EVENTID,
+};
+
+struct wmi {
+       struct ath9k_htc_priv *drv_priv;
+       struct htc_target *htc;
+       enum htc_endpoint_id ctrl_epid;
+       struct mutex op_mutex;
+       struct completion cmd_wait;
+       u16 tx_seq_id;
+       u8 *cmd_rsp_buf;
+       u32 cmd_rsp_len;
+       bool stopped;
+
+       struct sk_buff *wmi_skb;
+       spinlock_t wmi_lock;
+};
+
+struct wmi *ath9k_init_wmi(struct ath9k_htc_priv *priv);
+void ath9k_deinit_wmi(struct ath9k_htc_priv *priv);
+int ath9k_wmi_connect(struct htc_target *htc, struct wmi *wmi,
+                     enum htc_endpoint_id *wmi_ctrl_epid);
+int ath9k_wmi_cmd(struct wmi *wmi, enum wmi_cmd_id cmd_id,
+                 u8 *cmd_buf, u32 cmd_len,
+                 u8 *rsp_buf, u32 rsp_len,
+                 u32 timeout);
+void ath9k_wmi_tasklet(unsigned long data);
+
+#define WMI_CMD(_wmi_cmd)                                              \
+       do {                                                            \
+               ret = ath9k_wmi_cmd(priv->wmi, _wmi_cmd, NULL, 0,       \
+                                   (u8 *) &cmd_rsp,                    \
+                                   sizeof(cmd_rsp), HZ);               \
+       } while (0)
+
+#define WMI_CMD_BUF(_wmi_cmd, _buf)                                    \
+       do {                                                            \
+               ret = ath9k_wmi_cmd(priv->wmi, _wmi_cmd,                \
+                                   (u8 *) _buf, sizeof(*_buf),         \
+                                   &cmd_rsp, sizeof(cmd_rsp), HZ);     \
+       } while (0)
+
+#endif /* WMI_H */
index 8263633..873bf52 100644 (file)
@@ -59,6 +59,7 @@ enum ATH_DEBUG {
        ATH_DBG_PS              = 0x00000800,
        ATH_DBG_HWTIMER         = 0x00001000,
        ATH_DBG_BTCOEX          = 0x00002000,
+       ATH_DBG_WMI             = 0x00004000,
        ATH_DBG_ANY             = 0xffffffff
 };