Merge branch 'stable/bug.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git...
[pandora-kernel.git] / drivers / net / wireless / ath / ath9k / main.c
index 1482fa6..2ca351f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008-2009 Atheros Communications Inc.
+ * Copyright (c) 2008-2011 Atheros Communications Inc.
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -299,7 +299,7 @@ int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw,
 
        if (!(sc->sc_flags & (SC_OP_OFFCHANNEL))) {
                if (sc->sc_flags & SC_OP_BEACONS)
-                       ath_beacon_config(sc, NULL);
+                       ath_set_beacon(sc);
                ieee80211_queue_delayed_work(sc->hw, &sc->tx_complete_work, 0);
                ieee80211_queue_delayed_work(sc->hw, &sc->hw_pll_work, HZ/2);
                ath_start_ani(common);
@@ -624,6 +624,43 @@ out:
        ath9k_ps_restore(sc);
 }
 
+static void ath_hw_pll_rx_hang_check(struct ath_softc *sc, u32 pll_sqsum)
+{
+       static int count;
+       struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+
+       if (pll_sqsum >= 0x40000) {
+               count++;
+               if (count == 3) {
+                       /* Rx is hung for more than 500ms. Reset it */
+                       ath_dbg(common, ATH_DBG_RESET,
+                               "Possible RX hang, resetting");
+                       ath_reset(sc, true);
+                       count = 0;
+               }
+       } else
+               count = 0;
+}
+
+void ath_hw_pll_work(struct work_struct *work)
+{
+       struct ath_softc *sc = container_of(work, struct ath_softc,
+                                           hw_pll_work.work);
+       u32 pll_sqsum;
+
+       if (AR_SREV_9485(sc->sc_ah)) {
+
+               ath9k_ps_wakeup(sc);
+               pll_sqsum = ar9003_get_pll_sqsum_dvc(sc->sc_ah);
+               ath9k_ps_restore(sc);
+
+               ath_hw_pll_rx_hang_check(sc, pll_sqsum);
+
+               ieee80211_queue_delayed_work(sc->hw, &sc->hw_pll_work, HZ/5);
+       }
+}
+
+
 void ath9k_tasklet(unsigned long data)
 {
        struct ath_softc *sc = (struct ath_softc *)data;
@@ -633,7 +670,8 @@ void ath9k_tasklet(unsigned long data)
        u32 status = sc->intrstatus;
        u32 rxmask;
 
-       if (status & ATH9K_INT_FATAL) {
+       if ((status & ATH9K_INT_FATAL) ||
+           (status & ATH9K_INT_BB_WATCHDOG)) {
                ath_reset(sc, true);
                return;
        }
@@ -652,6 +690,17 @@ void ath9k_tasklet(unsigned long data)
            !ath9k_hw_check_alive(ah))
                ieee80211_queue_work(sc->hw, &sc->hw_check_work);
 
+       if ((status & ATH9K_INT_TSFOOR) && sc->ps_enabled) {
+               /*
+                * TSF sync does not look correct; remain awake to sync with
+                * the next Beacon.
+                */
+               ath_dbg(common, ATH_DBG_PS,
+                       "TSFOOR - Sync with next Beacon\n");
+               sc->ps_flags |= PS_WAIT_FOR_BEACON | PS_BEACON_SYNC |
+                               PS_TSFOOR_SYNC;
+       }
+
        if (ah->caps.hw_caps & ATH9K_HW_CAP_EDMA)
                rxmask = (ATH9K_INT_RXHP | ATH9K_INT_RXLP | ATH9K_INT_RXEOL |
                          ATH9K_INT_RXORN);
@@ -674,16 +723,6 @@ void ath9k_tasklet(unsigned long data)
                        ath_tx_tasklet(sc);
        }
 
-       if ((status & ATH9K_INT_TSFOOR) && sc->ps_enabled) {
-               /*
-                * TSF sync does not look correct; remain awake to sync with
-                * the next Beacon.
-                */
-               ath_dbg(common, ATH_DBG_PS,
-                       "TSFOOR - Sync with next Beacon\n");
-               sc->ps_flags |= PS_WAIT_FOR_BEACON | PS_BEACON_SYNC;
-       }
-
        if (ah->btcoex_hw.scheme == ATH_BTCOEX_CFG_3WIRE)
                if (status & ATH9K_INT_GENTIMER)
                        ath_gen_timer_isr(sc->sc_ah);
@@ -699,6 +738,7 @@ irqreturn_t ath_isr(int irq, void *dev)
 {
 #define SCHED_INTR (                           \
                ATH9K_INT_FATAL |               \
+               ATH9K_INT_BB_WATCHDOG |         \
                ATH9K_INT_RXORN |               \
                ATH9K_INT_RXEOL |               \
                ATH9K_INT_RX |                  \
@@ -828,48 +868,6 @@ chip_reset:
 #undef SCHED_INTR
 }
 
-static void ath9k_bss_assoc_info(struct ath_softc *sc,
-                                struct ieee80211_hw *hw,
-                                struct ieee80211_vif *vif,
-                                struct ieee80211_bss_conf *bss_conf)
-{
-       struct ath_hw *ah = sc->sc_ah;
-       struct ath_common *common = ath9k_hw_common(ah);
-
-       if (bss_conf->assoc) {
-               ath_dbg(common, ATH_DBG_CONFIG,
-                       "Bss Info ASSOC %d, bssid: %pM\n",
-                       bss_conf->aid, common->curbssid);
-
-               /* New association, store aid */
-               common->curaid = bss_conf->aid;
-               ath9k_hw_write_associd(ah);
-
-               /*
-                * Request a re-configuration of Beacon related timers
-                * on the receipt of the first Beacon frame (i.e.,
-                * after time sync with the AP).
-                */
-               sc->ps_flags |= PS_BEACON_SYNC;
-
-               /* Configure the beacon */
-               ath_beacon_config(sc, vif);
-
-               /* Reset rssi stats */
-               sc->last_rssi = ATH_RSSI_DUMMY_MARKER;
-               sc->sc_ah->stats.avgbrssi = ATH_RSSI_DUMMY_MARKER;
-
-               sc->sc_flags |= SC_OP_ANI_RUN;
-               ath_start_ani(common);
-       } else {
-               ath_dbg(common, ATH_DBG_CONFIG, "Bss Info DISASSOC\n");
-               common->curaid = 0;
-               /* Stop ANI */
-               sc->sc_flags &= ~SC_OP_ANI_RUN;
-               del_timer_sync(&common->ani.timer);
-       }
-}
-
 void ath_radio_enable(struct ath_softc *sc, struct ieee80211_hw *hw)
 {
        struct ath_hw *ah = sc->sc_ah;
@@ -899,7 +897,7 @@ void ath_radio_enable(struct ath_softc *sc, struct ieee80211_hw *hw)
                goto out;
        }
        if (sc->sc_flags & SC_OP_BEACONS)
-               ath_beacon_config(sc, NULL);    /* restart beacons */
+               ath_set_beacon(sc);     /* restart beacons */
 
        /* Re-Enable  interrupts */
        ath9k_hw_set_interrupts(ah, ah->imask);
@@ -1006,7 +1004,7 @@ int ath_reset(struct ath_softc *sc, bool retry_tx)
                               sc->config.txpowlimit, &sc->curtxpow);
 
        if ((sc->sc_flags & SC_OP_BEACONS) || !(sc->sc_flags & (SC_OP_OFFCHANNEL)))
-               ath_beacon_config(sc, NULL);    /* restart beacons */
+               ath_set_beacon(sc);     /* restart beacons */
 
        ath9k_hw_set_interrupts(ah, ah->imask);
 
@@ -1389,7 +1387,9 @@ static void ath9k_calculate_summary_state(struct ieee80211_hw *hw,
                ath9k_hw_set_tsfadjust(ah, 0);
                sc->sc_flags &= ~SC_OP_TSF_RESET;
 
-               if (iter_data.nwds + iter_data.nmeshes)
+               if (iter_data.nmeshes)
+                       ah->opmode = NL80211_IFTYPE_MESH_POINT;
+               else if (iter_data.nwds)
                        ah->opmode = NL80211_IFTYPE_AP;
                else if (iter_data.nadhocs)
                        ah->opmode = NL80211_IFTYPE_ADHOC;
@@ -1413,6 +1413,7 @@ static void ath9k_calculate_summary_state(struct ieee80211_hw *hw,
 
        /* Set up ANI */
        if ((iter_data.naps + iter_data.nadhocs) > 0) {
+               sc->sc_ah->stats.avgbrssi = ATH_RSSI_DUMMY_MARKER;
                sc->sc_flags |= SC_OP_ANI_RUN;
                ath_start_ani(common);
        } else {
@@ -1452,7 +1453,6 @@ static int ath9k_add_interface(struct ieee80211_hw *hw,
        struct ath_softc *sc = hw->priv;
        struct ath_hw *ah = sc->sc_ah;
        struct ath_common *common = ath9k_hw_common(ah);
-       struct ath_vif *avp = (void *)vif->drv_priv;
        int ret = 0;
 
        ath9k_ps_wakeup(sc);
@@ -1482,8 +1482,9 @@ static int ath9k_add_interface(struct ieee80211_hw *hw,
                }
        }
 
-       if ((vif->type == NL80211_IFTYPE_ADHOC) &&
-           sc->nvifs > 0) {
+       if ((ah->opmode == NL80211_IFTYPE_ADHOC) ||
+           ((vif->type == NL80211_IFTYPE_ADHOC) &&
+            sc->nvifs > 0)) {
                ath_err(common, "Cannot create ADHOC interface when other"
                        " interfaces already exist.\n");
                ret = -EINVAL;
@@ -1493,10 +1494,6 @@ static int ath9k_add_interface(struct ieee80211_hw *hw,
        ath_dbg(common, ATH_DBG_CONFIG,
                "Attach a VIF of type: %d\n", vif->type);
 
-       /* Set the VIF opmode */
-       avp->av_opmode = vif->type;
-       avp->av_bslot = -1;
-
        sc->nvifs++;
 
        ath9k_do_vif_add_setup(hw, vif);
@@ -1782,23 +1779,68 @@ static int ath9k_sta_add(struct ieee80211_hw *hw,
                         struct ieee80211_sta *sta)
 {
        struct ath_softc *sc = hw->priv;
+       struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+       struct ath_node *an = (struct ath_node *) sta->drv_priv;
+       struct ieee80211_key_conf ps_key = { };
 
        ath_node_attach(sc, sta);
 
+       if (vif->type != NL80211_IFTYPE_AP &&
+           vif->type != NL80211_IFTYPE_AP_VLAN)
+               return 0;
+
+       an->ps_key = ath_key_config(common, vif, sta, &ps_key);
+
        return 0;
 }
 
+static void ath9k_del_ps_key(struct ath_softc *sc,
+                            struct ieee80211_vif *vif,
+                            struct ieee80211_sta *sta)
+{
+       struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+       struct ath_node *an = (struct ath_node *) sta->drv_priv;
+       struct ieee80211_key_conf ps_key = { .hw_key_idx = an->ps_key };
+
+       if (!an->ps_key)
+           return;
+
+       ath_key_delete(common, &ps_key);
+}
+
 static int ath9k_sta_remove(struct ieee80211_hw *hw,
                            struct ieee80211_vif *vif,
                            struct ieee80211_sta *sta)
 {
        struct ath_softc *sc = hw->priv;
 
+       ath9k_del_ps_key(sc, vif, sta);
        ath_node_detach(sc, sta);
 
        return 0;
 }
 
+static void ath9k_sta_notify(struct ieee80211_hw *hw,
+                        struct ieee80211_vif *vif,
+                        enum sta_notify_cmd cmd,
+                        struct ieee80211_sta *sta)
+{
+       struct ath_softc *sc = hw->priv;
+       struct ath_node *an = (struct ath_node *) sta->drv_priv;
+
+       switch (cmd) {
+       case STA_NOTIFY_SLEEP:
+               an->sleeping = true;
+               if (ath_tx_aggr_sleep(sc, an))
+                       ieee80211_sta_set_tim(sta);
+               break;
+       case STA_NOTIFY_AWAKE:
+               an->sleeping = false;
+               ath_tx_aggr_wakeup(sc, an);
+               break;
+       }
+}
+
 static int ath9k_conf_tx(struct ieee80211_hw *hw, u16 queue,
                         const struct ieee80211_tx_queue_params *params)
 {
@@ -1855,12 +1897,29 @@ static int ath9k_set_key(struct ieee80211_hw *hw,
        if (ath9k_modparam_nohwcrypt)
                return -ENOSPC;
 
+       if (vif->type == NL80211_IFTYPE_ADHOC &&
+           (key->cipher == WLAN_CIPHER_SUITE_TKIP ||
+            key->cipher == WLAN_CIPHER_SUITE_CCMP) &&
+           !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) {
+               /*
+                * For now, disable hw crypto for the RSN IBSS group keys. This
+                * could be optimized in the future to use a modified key cache
+                * design to support per-STA RX GTK, but until that gets
+                * implemented, use of software crypto for group addressed
+                * frames is a acceptable to allow RSN IBSS to be used.
+                */
+               return -EOPNOTSUPP;
+       }
+
        mutex_lock(&sc->mutex);
        ath9k_ps_wakeup(sc);
        ath_dbg(common, ATH_DBG_CONFIG, "Set HW Key\n");
 
        switch (cmd) {
        case SET_KEY:
+               if (sta)
+                       ath9k_del_ps_key(sc, vif, sta);
+
                ret = ath_key_config(common, vif, sta, key);
                if (ret >= 0) {
                        key->hw_key_idx = ret;
@@ -1886,6 +1945,92 @@ static int ath9k_set_key(struct ieee80211_hw *hw,
 
        return ret;
 }
+static void ath9k_bss_iter(void *data, u8 *mac, struct ieee80211_vif *vif)
+{
+       struct ath_softc *sc = data;
+       struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+       struct ieee80211_bss_conf *bss_conf = &vif->bss_conf;
+       struct ath_vif *avp = (void *)vif->drv_priv;
+
+       switch (sc->sc_ah->opmode) {
+       case NL80211_IFTYPE_ADHOC:
+               /* There can be only one vif available */
+               memcpy(common->curbssid, bss_conf->bssid, ETH_ALEN);
+               common->curaid = bss_conf->aid;
+               ath9k_hw_write_associd(sc->sc_ah);
+               /* configure beacon */
+               if (bss_conf->enable_beacon)
+                       ath_beacon_config(sc, vif);
+               break;
+       case NL80211_IFTYPE_STATION:
+               /*
+                * Skip iteration if primary station vif's bss info
+                * was not changed
+                */
+               if (sc->sc_flags & SC_OP_PRIM_STA_VIF)
+                       break;
+
+               if (bss_conf->assoc) {
+                       sc->sc_flags |= SC_OP_PRIM_STA_VIF;
+                       avp->primary_sta_vif = true;
+                       memcpy(common->curbssid, bss_conf->bssid, ETH_ALEN);
+                       common->curaid = bss_conf->aid;
+                       ath9k_hw_write_associd(sc->sc_ah);
+                       ath_dbg(common, ATH_DBG_CONFIG,
+                               "Bss Info ASSOC %d, bssid: %pM\n",
+                               bss_conf->aid, common->curbssid);
+                       ath_beacon_config(sc, vif);
+                       /*
+                        * Request a re-configuration of Beacon related timers
+                        * on the receipt of the first Beacon frame (i.e.,
+                        * after time sync with the AP).
+                        */
+                       sc->ps_flags |= PS_BEACON_SYNC | PS_WAIT_FOR_BEACON;
+                       /* Reset rssi stats */
+                       sc->last_rssi = ATH_RSSI_DUMMY_MARKER;
+                       sc->sc_ah->stats.avgbrssi = ATH_RSSI_DUMMY_MARKER;
+
+                       sc->sc_flags |= SC_OP_ANI_RUN;
+                       ath_start_ani(common);
+               }
+               break;
+       default:
+               break;
+       }
+}
+
+static void ath9k_config_bss(struct ath_softc *sc, struct ieee80211_vif *vif)
+{
+       struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+       struct ieee80211_bss_conf *bss_conf = &vif->bss_conf;
+       struct ath_vif *avp = (void *)vif->drv_priv;
+
+       /* Reconfigure bss info */
+       if (avp->primary_sta_vif && !bss_conf->assoc) {
+               ath_dbg(common, ATH_DBG_CONFIG,
+                       "Bss Info DISASSOC %d, bssid %pM\n",
+                       common->curaid, common->curbssid);
+               sc->sc_flags &= ~(SC_OP_PRIM_STA_VIF | SC_OP_BEACONS);
+               avp->primary_sta_vif = false;
+               memset(common->curbssid, 0, ETH_ALEN);
+               common->curaid = 0;
+       }
+
+       ieee80211_iterate_active_interfaces_atomic(
+                       sc->hw, ath9k_bss_iter, sc);
+
+       /*
+        * None of station vifs are associated.
+        * Clear bssid & aid
+        */
+       if ((sc->sc_ah->opmode == NL80211_IFTYPE_STATION) &&
+           !(sc->sc_flags & SC_OP_PRIM_STA_VIF)) {
+               ath9k_hw_write_associd(sc->sc_ah);
+               /* Stop ANI */
+               sc->sc_flags &= ~SC_OP_ANI_RUN;
+               del_timer_sync(&common->ani.timer);
+       }
+}
 
 static void ath9k_bss_info_changed(struct ieee80211_hw *hw,
                                   struct ieee80211_vif *vif,
@@ -1893,7 +2038,6 @@ static void ath9k_bss_info_changed(struct ieee80211_hw *hw,
                                   u32 changed)
 {
        struct ath_softc *sc = hw->priv;
-       struct ath_beacon_config *cur_conf = &sc->cur_beacon_conf;
        struct ath_hw *ah = sc->sc_ah;
        struct ath_common *common = ath9k_hw_common(ah);
        struct ath_vif *avp = (void *)vif->drv_priv;
@@ -1904,20 +2048,10 @@ static void ath9k_bss_info_changed(struct ieee80211_hw *hw,
        mutex_lock(&sc->mutex);
 
        if (changed & BSS_CHANGED_BSSID) {
-               /* Set BSSID */
-               memcpy(common->curbssid, bss_conf->bssid, ETH_ALEN);
-               memcpy(avp->bssid, bss_conf->bssid, ETH_ALEN);
-               common->curaid = 0;
-               ath9k_hw_write_associd(ah);
-
-               /* Set aggregation protection mode parameters */
-               sc->config.ath_aggr_prot = 0;
+               ath9k_config_bss(sc, vif);
 
                ath_dbg(common, ATH_DBG_CONFIG, "BSSID: %pM aid: 0x%x\n",
                        common->curbssid, common->curaid);
-
-               /* need to reconfigure the beacon */
-               sc->sc_flags &= ~SC_OP_BEACONS ;
        }
 
        /* Enable transmission of beacons (AP, IBSS, MESH) */
@@ -1958,7 +2092,6 @@ static void ath9k_bss_info_changed(struct ieee80211_hw *hw,
        }
 
        if (changed & BSS_CHANGED_BEACON_INT) {
-               cur_conf->beacon_interval = bss_conf->beacon_int;
                /*
                 * In case of AP mode, the HW TSF has to be reset
                 * when the beacon interval changes.
@@ -1970,9 +2103,8 @@ static void ath9k_bss_info_changed(struct ieee80211_hw *hw,
                        if (!error)
                                ath_beacon_config(sc, vif);
                        ath9k_set_beaconing_status(sc, true);
-               } else {
+               } else
                        ath_beacon_config(sc, vif);
-               }
        }
 
        if (changed & BSS_CHANGED_ERP_PREAMBLE) {
@@ -1994,12 +2126,6 @@ static void ath9k_bss_info_changed(struct ieee80211_hw *hw,
                        sc->sc_flags &= ~SC_OP_PROTECT_ENABLE;
        }
 
-       if (changed & BSS_CHANGED_ASSOC) {
-               ath_dbg(common, ATH_DBG_CONFIG, "BSS Changed ASSOC %d\n",
-                       bss_conf->assoc);
-               ath9k_bss_assoc_info(sc, hw, vif, bss_conf);
-       }
-
        mutex_unlock(&sc->mutex);
        ath9k_ps_restore(sc);
 }
@@ -2145,10 +2271,9 @@ static void ath9k_flush(struct ieee80211_hw *hw, bool drop)
        struct ath_common *common = ath9k_hw_common(ah);
        int timeout = 200; /* ms */
        int i, j;
+       bool drain_txq;
 
-       ath9k_ps_wakeup(sc);
        mutex_lock(&sc->mutex);
-
        cancel_delayed_work_sync(&sc->tx_complete_work);
 
        if (sc->sc_flags & SC_OP_INVALID) {
@@ -2161,7 +2286,7 @@ static void ath9k_flush(struct ieee80211_hw *hw, bool drop)
                timeout = 1;
 
        for (j = 0; j < timeout; j++) {
-               int npend = 0;
+               bool npend = false;
 
                if (j)
                        usleep_range(1000, 2000);
@@ -2170,22 +2295,82 @@ static void ath9k_flush(struct ieee80211_hw *hw, bool drop)
                        if (!ATH_TXQ_SETUP(sc, i))
                                continue;
 
-                       npend += ath9k_has_pending_frames(sc, &sc->tx.txq[i]);
+                       npend = ath9k_has_pending_frames(sc, &sc->tx.txq[i]);
+
+                       if (npend)
+                               break;
                }
 
                if (!npend)
                    goto out;
        }
 
-       if (!ath_drain_all_txq(sc, false))
+       ath9k_ps_wakeup(sc);
+       spin_lock_bh(&sc->sc_pcu_lock);
+       drain_txq = ath_drain_all_txq(sc, false);
+       spin_unlock_bh(&sc->sc_pcu_lock);
+       if (!drain_txq)
                ath_reset(sc, false);
-
+       ath9k_ps_restore(sc);
        ieee80211_wake_queues(hw);
 
 out:
        ieee80211_queue_delayed_work(hw, &sc->tx_complete_work, 0);
        mutex_unlock(&sc->mutex);
-       ath9k_ps_restore(sc);
+}
+
+static bool ath9k_tx_frames_pending(struct ieee80211_hw *hw)
+{
+       struct ath_softc *sc = hw->priv;
+       int i;
+
+       for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) {
+               if (!ATH_TXQ_SETUP(sc, i))
+                       continue;
+
+               if (ath9k_has_pending_frames(sc, &sc->tx.txq[i]))
+                       return true;
+       }
+       return false;
+}
+
+int ath9k_tx_last_beacon(struct ieee80211_hw *hw)
+{
+       struct ath_softc *sc = hw->priv;
+       struct ath_hw *ah = sc->sc_ah;
+       struct ieee80211_vif *vif;
+       struct ath_vif *avp;
+       struct ath_buf *bf;
+       struct ath_tx_status ts;
+       int status;
+
+       vif = sc->beacon.bslot[0];
+       if (!vif)
+               return 0;
+
+       avp = (void *)vif->drv_priv;
+       if (!avp->is_bslot_active)
+               return 0;
+
+       if (!sc->beacon.tx_processed) {
+               tasklet_disable(&sc->bcon_tasklet);
+
+               bf = avp->av_bcbuf;
+               if (!bf || !bf->bf_mpdu)
+                       goto skip;
+
+               status = ath9k_hw_txprocdesc(ah, bf->bf_desc, &ts);
+               if (status == -EINPROGRESS)
+                       goto skip;
+
+               sc->beacon.tx_processed = true;
+               sc->beacon.tx_last = !(ts.ts_status & ATH9K_TXERR_MASK);
+
+skip:
+               tasklet_enable(&sc->bcon_tasklet);
+       }
+
+       return sc->beacon.tx_last;
 }
 
 struct ieee80211_ops ath9k_ops = {
@@ -2199,6 +2384,7 @@ struct ieee80211_ops ath9k_ops = {
        .configure_filter   = ath9k_configure_filter,
        .sta_add            = ath9k_sta_add,
        .sta_remove         = ath9k_sta_remove,
+       .sta_notify         = ath9k_sta_notify,
        .conf_tx            = ath9k_conf_tx,
        .bss_info_changed   = ath9k_bss_info_changed,
        .set_key            = ath9k_set_key,
@@ -2210,4 +2396,6 @@ struct ieee80211_ops ath9k_ops = {
        .rfkill_poll        = ath9k_rfkill_poll_state,
        .set_coverage_class = ath9k_set_coverage_class,
        .flush              = ath9k_flush,
+       .tx_frames_pending  = ath9k_tx_frames_pending,
+       .tx_last_beacon = ath9k_tx_last_beacon,
 };