From: Felix Fietkau Date: Mon, 29 Aug 2011 18:41:18 +0000 (+0000) Subject: ath9k: fix a few crash issues on hardware reset X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=eae76139f22e945ba82ae13cbbd5ba1411aa84c3;p=openwrt%2Fstaging%2F981213.git ath9k: fix a few crash issues on hardware reset SVN-Revision: 28122 --- diff --git a/package/mac80211/patches/581-ath9k_merge_reset_functions.patch b/package/mac80211/patches/581-ath9k_merge_reset_functions.patch deleted file mode 100644 index 6159284bf9..0000000000 --- a/package/mac80211/patches/581-ath9k_merge_reset_functions.patch +++ /dev/null @@ -1,368 +0,0 @@ ---- a/drivers/net/wireless/ath/ath9k/main.c -+++ b/drivers/net/wireless/ath/ath9k/main.c -@@ -212,83 +212,47 @@ static int ath_update_survey_stats(struc - return ret; - } - --/* -- * Set/change channels. If the channel is really being changed, it's done -- * by reseting the chip. To accomplish this we must first cleanup any pending -- * DMA, then restart stuff. --*/ --static int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw, -- struct ath9k_channel *hchan) -+static bool ath_prepare_reset(struct ath_softc *sc, bool retry_tx, bool flush) - { - struct ath_hw *ah = sc->sc_ah; - struct ath_common *common = ath9k_hw_common(ah); -- struct ieee80211_conf *conf = &common->hw->conf; -- bool fastcc = true, stopped; -- struct ieee80211_channel *channel = hw->conf.channel; -- struct ath9k_hw_cal_data *caldata = NULL; -- int r; -+ bool ret; - -- if (sc->sc_flags & SC_OP_INVALID) -- return -EIO; -+ ieee80211_stop_queues(sc->hw); - - sc->hw_busy_count = 0; -- - del_timer_sync(&common->ani.timer); - cancel_work_sync(&sc->paprd_work); - cancel_work_sync(&sc->hw_check_work); - cancel_delayed_work_sync(&sc->tx_complete_work); - cancel_delayed_work_sync(&sc->hw_pll_work); - -- ath9k_ps_wakeup(sc); -- -- spin_lock_bh(&sc->sc_pcu_lock); -- -- /* -- * This is only performed if the channel settings have -- * actually changed. -- * -- * To switch channels clear any pending DMA operations; -- * wait long enough for the RX fifo to drain, reset the -- * hardware at the new frequency, and then re-enable -- * the relevant bits of the h/w. -- */ - ath9k_hw_disable_interrupts(ah); -- stopped = ath_drain_all_txq(sc, false); -- -- if (!ath_stoprecv(sc)) -- stopped = false; - -- if (!ath9k_hw_check_alive(ah)) -- stopped = false; -+ ret = ath_drain_all_txq(sc, retry_tx); - -- /* XXX: do not flush receive queue here. We don't want -- * to flush data frames already in queue because of -- * changing channel. */ -- -- if (!stopped || !(sc->sc_flags & SC_OP_OFFCHANNEL)) -- fastcc = false; -+ if (!ath_stoprecv(sc)) -+ ret = false; - -- if (!(sc->sc_flags & SC_OP_OFFCHANNEL)) -- caldata = &sc->caldata; -+ if (!flush) { -+ if (ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) -+ ath_rx_tasklet(sc, 0, true); -+ ath_rx_tasklet(sc, 0, false); -+ } else { -+ ath_flushrecv(sc); -+ } - -- ath_dbg(common, ATH_DBG_CONFIG, -- "(%u MHz) -> (%u MHz), conf_is_ht40: %d fastcc: %d\n", -- sc->sc_ah->curchan->channel, -- channel->center_freq, conf_is_ht40(conf), -- fastcc); -+ return ret; -+} - -- r = ath9k_hw_reset(ah, hchan, caldata, fastcc); -- if (r) { -- ath_err(common, -- "Unable to reset channel (%u MHz), reset status %d\n", -- channel->center_freq, r); -- goto ps_restore; -- } -+static bool ath_complete_reset(struct ath_softc *sc, bool start) -+{ -+ struct ath_hw *ah = sc->sc_ah; -+ struct ath_common *common = ath9k_hw_common(ah); - - if (ath_startrecv(sc) != 0) { - ath_err(common, "Unable to restart recv logic\n"); -- r = -EIO; -- goto ps_restore; -+ return false; - } - - ath9k_cmn_update_txpow(ah, sc->curtxpow, -@@ -296,21 +260,89 @@ static int ath_set_channel(struct ath_so - ath9k_hw_set_interrupts(ah, ah->imask); - ath9k_hw_enable_interrupts(ah); - -- if (!(sc->sc_flags & (SC_OP_OFFCHANNEL))) { -+ if (!(sc->sc_flags & (SC_OP_OFFCHANNEL)) && start) { - if (sc->sc_flags & SC_OP_BEACONS) - 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); - if (!common->disable_ani) - ath_start_ani(common); - } - -- ps_restore: -- ieee80211_wake_queues(hw); -+ ieee80211_wake_queues(sc->hw); -+ -+ return true; -+} -+ -+static int ath_reset_internal(struct ath_softc *sc, struct ath9k_channel *hchan, -+ bool retry_tx) -+{ -+ struct ath_hw *ah = sc->sc_ah; -+ struct ath_common *common = ath9k_hw_common(ah); -+ struct ath9k_hw_cal_data *caldata = NULL; -+ bool fastcc = true; -+ bool flush = false; -+ int r; -+ -+ if (!(sc->sc_flags & SC_OP_OFFCHANNEL)) { -+ fastcc = false; -+ caldata = &sc->caldata; -+ } -+ -+ if (!hchan) { -+ fastcc = false; -+ flush = true; -+ hchan = ah->curchan; -+ } -+ -+ if (fastcc && !ath9k_hw_check_alive(ah)) -+ fastcc = false; -+ -+ if (!ath_prepare_reset(sc, retry_tx, flush)) -+ fastcc = false; -+ -+ ath_dbg(common, ATH_DBG_CONFIG, -+ "Reset to %u MHz, HT40: %d fastcc: %d\n", -+ hchan->channel, !!(hchan->channelFlags & (CHANNEL_HT40MINUS | -+ CHANNEL_HT40PLUS)), -+ fastcc); -+ -+ r = ath9k_hw_reset(ah, hchan, caldata, fastcc); -+ if (r) { -+ ath_err(common, -+ "Unable to reset channel, reset status %d\n", r); -+ return r; -+ } -+ -+ if (!ath_complete_reset(sc, true)) -+ return -EIO; -+ -+ return 0; -+} -+ -+ -+/* -+ * Set/change channels. If the channel is really being changed, it's done -+ * by reseting the chip. To accomplish this we must first cleanup any pending -+ * DMA, then restart stuff. -+*/ -+static int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw, -+ struct ath9k_channel *hchan) -+{ -+ int r; - -+ if (sc->sc_flags & SC_OP_INVALID) -+ return -EIO; -+ -+ ath9k_ps_wakeup(sc); -+ -+ spin_lock_bh(&sc->sc_pcu_lock); -+ r = ath_reset_internal(sc, hchan, false); - spin_unlock_bh(&sc->sc_pcu_lock); - - ath9k_ps_restore(sc); -+ - return r; - } - -@@ -893,28 +925,13 @@ static void ath_radio_enable(struct ath_ - channel->center_freq, r); - } - -- ath9k_cmn_update_txpow(ah, sc->curtxpow, -- sc->config.txpowlimit, &sc->curtxpow); -- if (ath_startrecv(sc) != 0) { -- ath_err(common, "Unable to restart recv logic\n"); -- goto out; -- } -- if (sc->sc_flags & SC_OP_BEACONS) -- ath_set_beacon(sc); /* restart beacons */ -- -- /* Re-Enable interrupts */ -- ath9k_hw_set_interrupts(ah, ah->imask); -- ath9k_hw_enable_interrupts(ah); -+ ath_complete_reset(sc, true); - - /* Enable LED */ - ath9k_hw_cfg_output(ah, ah->led_pin, - AR_GPIO_OUTPUT_MUX_AS_OUTPUT); - ath9k_hw_set_gpio(ah, ah->led_pin, 0); - -- ieee80211_wake_queues(hw); -- ieee80211_queue_delayed_work(hw, &sc->hw_pll_work, HZ/2); -- --out: - spin_unlock_bh(&sc->sc_pcu_lock); - - ath9k_ps_restore(sc); -@@ -927,12 +944,8 @@ void ath_radio_disable(struct ath_softc - int r; - - ath9k_ps_wakeup(sc); -- cancel_delayed_work_sync(&sc->hw_pll_work); -- - spin_lock_bh(&sc->sc_pcu_lock); - -- ieee80211_stop_queues(hw); -- - /* - * Keep the LED on when the radio is disabled - * during idle unassociated state. -@@ -942,13 +955,7 @@ void ath_radio_disable(struct ath_softc - ath9k_hw_cfg_gpio_input(ah, ah->led_pin); - } - -- /* Disable interrupts */ -- ath9k_hw_disable_interrupts(ah); -- -- ath_drain_all_txq(sc, false); /* clear pending tx frames */ -- -- ath_stoprecv(sc); /* turn off frame recv */ -- ath_flushrecv(sc); /* flush recv queue */ -+ ath_prepare_reset(sc, false, true); - - if (!ah->curchan) - ah->curchan = ath9k_cmn_get_curchannel(hw, ah); -@@ -970,48 +977,11 @@ void ath_radio_disable(struct ath_softc - - int ath_reset(struct ath_softc *sc, bool retry_tx) - { -- struct ath_hw *ah = sc->sc_ah; -- struct ath_common *common = ath9k_hw_common(ah); -- struct ieee80211_hw *hw = sc->hw; - int r; - -- sc->hw_busy_count = 0; -- -- /* Stop ANI */ -- -- del_timer_sync(&common->ani.timer); -- - ath9k_ps_wakeup(sc); - -- ieee80211_stop_queues(hw); -- -- ath9k_hw_disable_interrupts(ah); -- ath_drain_all_txq(sc, retry_tx); -- -- ath_stoprecv(sc); -- ath_flushrecv(sc); -- -- r = ath9k_hw_reset(ah, sc->sc_ah->curchan, ah->caldata, false); -- if (r) -- ath_err(common, -- "Unable to reset hardware; reset status %d\n", r); -- -- if (ath_startrecv(sc) != 0) -- ath_err(common, "Unable to start recv logic\n"); -- -- /* -- * We may be doing a reset in response to a request -- * that changes the channel so update any state that -- * might change as a result. -- */ -- ath9k_cmn_update_txpow(ah, sc->curtxpow, -- sc->config.txpowlimit, &sc->curtxpow); -- -- if ((sc->sc_flags & SC_OP_BEACONS) || !(sc->sc_flags & (SC_OP_OFFCHANNEL))) -- ath_set_beacon(sc); /* restart beacons */ -- -- ath9k_hw_set_interrupts(ah, ah->imask); -- ath9k_hw_enable_interrupts(ah); -+ r = ath_reset_internal(sc, NULL, retry_tx); - - if (retry_tx) { - int i; -@@ -1024,12 +994,6 @@ int ath_reset(struct ath_softc *sc, bool - } - } - -- ieee80211_wake_queues(hw); -- -- /* Start ANI */ -- if (!common->disable_ani) -- ath_start_ani(common); -- - ath9k_ps_restore(sc); - - return r; -@@ -1081,28 +1045,6 @@ static int ath9k_start(struct ieee80211_ - goto mutex_unlock; - } - -- /* -- * This is needed only to setup initial state -- * but it's best done after a reset. -- */ -- ath9k_cmn_update_txpow(ah, sc->curtxpow, -- sc->config.txpowlimit, &sc->curtxpow); -- -- /* -- * Setup the hardware after reset: -- * The receive engine is set going. -- * Frame transmit is handled entirely -- * in the frame output path; there's nothing to do -- * here except setup the interrupt mask. -- */ -- if (ath_startrecv(sc) != 0) { -- ath_err(common, "Unable to start recv logic\n"); -- r = -EIO; -- spin_unlock_bh(&sc->sc_pcu_lock); -- goto mutex_unlock; -- } -- spin_unlock_bh(&sc->sc_pcu_lock); -- - /* Setup our intr mask. */ - ah->imask = ATH9K_INT_TX | ATH9K_INT_RXEOL | - ATH9K_INT_RXORN | ATH9K_INT_FATAL | -@@ -1125,12 +1067,14 @@ static int ath9k_start(struct ieee80211_ - - /* Disable BMISS interrupt when we're not associated */ - ah->imask &= ~(ATH9K_INT_SWBA | ATH9K_INT_BMISS); -- ath9k_hw_set_interrupts(ah, ah->imask); -- ath9k_hw_enable_interrupts(ah); - -- ieee80211_wake_queues(hw); -+ if (!ath_complete_reset(sc, false)) { -+ r = -EIO; -+ spin_unlock_bh(&sc->sc_pcu_lock); -+ goto mutex_unlock; -+ } - -- ieee80211_queue_delayed_work(sc->hw, &sc->tx_complete_work, 0); -+ spin_unlock_bh(&sc->sc_pcu_lock); - - if ((ah->btcoex_hw.scheme != ATH_BTCOEX_CFG_NONE) && - !ah->btcoex_hw.enabled) { diff --git a/package/mac80211/patches/581-ath9k_use_reset_work.patch b/package/mac80211/patches/581-ath9k_use_reset_work.patch new file mode 100644 index 0000000000..db8d85aeeb --- /dev/null +++ b/package/mac80211/patches/581-ath9k_use_reset_work.patch @@ -0,0 +1,253 @@ +--- a/drivers/net/wireless/ath/ath9k/ath9k.h ++++ b/drivers/net/wireless/ath/ath9k/ath9k.h +@@ -429,6 +429,7 @@ void ath9k_set_beaconing_status(struct a + + #define ATH_PAPRD_TIMEOUT 100 /* msecs */ + ++void ath_reset_work(struct work_struct *work); + void ath_hw_check(struct work_struct *work); + void ath_hw_pll_work(struct work_struct *work); + void ath_paprd_calibrate(struct work_struct *work); +@@ -609,6 +610,7 @@ struct ath_softc { + struct mutex mutex; + struct work_struct paprd_work; + struct work_struct hw_check_work; ++ struct work_struct hw_reset_work; + struct completion paprd_complete; + + unsigned int hw_busy_count; +@@ -655,7 +657,6 @@ struct ath_softc { + }; + + void ath9k_tasklet(unsigned long data); +-int ath_reset(struct ath_softc *sc, bool retry_tx); + int ath_cabq_update(struct ath_softc *); + + static inline void ath_read_cachesize(struct ath_common *common, int *csz) +--- a/drivers/net/wireless/ath/ath9k/init.c ++++ b/drivers/net/wireless/ath/ath9k/init.c +@@ -776,6 +776,7 @@ int ath9k_init_device(u16 devid, struct + goto error_world; + } + ++ INIT_WORK(&sc->hw_reset_work, ath_reset_work); + INIT_WORK(&sc->hw_check_work, ath_hw_check); + INIT_WORK(&sc->paprd_work, ath_paprd_calibrate); + INIT_DELAYED_WORK(&sc->hw_pll_work, ath_hw_pll_work); +--- a/drivers/net/wireless/ath/ath9k/main.c ++++ b/drivers/net/wireless/ath/ath9k/main.c +@@ -595,74 +595,6 @@ static void ath_node_detach(struct ath_s + ath_tx_node_cleanup(sc, an); + } + +-void ath_hw_check(struct work_struct *work) +-{ +- struct ath_softc *sc = container_of(work, struct ath_softc, hw_check_work); +- struct ath_common *common = ath9k_hw_common(sc->sc_ah); +- unsigned long flags; +- int busy; +- +- ath9k_ps_wakeup(sc); +- if (ath9k_hw_check_alive(sc->sc_ah)) +- goto out; +- +- spin_lock_irqsave(&common->cc_lock, flags); +- busy = ath_update_survey_stats(sc); +- spin_unlock_irqrestore(&common->cc_lock, flags); +- +- ath_dbg(common, ATH_DBG_RESET, "Possible baseband hang, " +- "busy=%d (try %d)\n", busy, sc->hw_busy_count + 1); +- if (busy >= 99) { +- if (++sc->hw_busy_count >= 3) { +- spin_lock_bh(&sc->sc_pcu_lock); +- ath_reset(sc, true); +- spin_unlock_bh(&sc->sc_pcu_lock); +- } +- } else if (busy >= 0) +- sc->hw_busy_count = 0; +- +-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"); +- spin_lock_bh(&sc->sc_pcu_lock); +- ath_reset(sc, true); +- spin_unlock_bh(&sc->sc_pcu_lock); +- 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) + { +@@ -675,9 +607,7 @@ void ath9k_tasklet(unsigned long data) + + if ((status & ATH9K_INT_FATAL) || + (status & ATH9K_INT_BB_WATCHDOG)) { +- spin_lock(&sc->sc_pcu_lock); +- ath_reset(sc, true); +- spin_unlock(&sc->sc_pcu_lock); ++ ieee80211_queue_work(sc->hw, &sc->hw_reset_work); + return; + } + +@@ -968,7 +898,7 @@ void ath_radio_disable(struct ath_softc + ath9k_ps_restore(sc); + } + +-int ath_reset(struct ath_softc *sc, bool retry_tx) ++static int ath_reset(struct ath_softc *sc, bool retry_tx) + { + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); +@@ -1035,6 +965,84 @@ int ath_reset(struct ath_softc *sc, bool + return r; + } + ++void ath_reset_work(struct work_struct *work) ++{ ++ struct ath_softc *sc = container_of(work, struct ath_softc, hw_check_work); ++ ++ spin_lock_bh(&sc->sc_pcu_lock); ++ ath_reset(sc, true); ++ spin_unlock_bh(&sc->sc_pcu_lock); ++} ++ ++void ath_hw_check(struct work_struct *work) ++{ ++ struct ath_softc *sc = container_of(work, struct ath_softc, hw_check_work); ++ struct ath_common *common = ath9k_hw_common(sc->sc_ah); ++ unsigned long flags; ++ int busy; ++ ++ ath9k_ps_wakeup(sc); ++ if (ath9k_hw_check_alive(sc->sc_ah)) ++ goto out; ++ ++ spin_lock_irqsave(&common->cc_lock, flags); ++ busy = ath_update_survey_stats(sc); ++ spin_unlock_irqrestore(&common->cc_lock, flags); ++ ++ ath_dbg(common, ATH_DBG_RESET, "Possible baseband hang, " ++ "busy=%d (try %d)\n", busy, sc->hw_busy_count + 1); ++ if (busy >= 99) { ++ if (++sc->hw_busy_count >= 3) { ++ spin_lock_bh(&sc->sc_pcu_lock); ++ ath_reset(sc, true); ++ spin_unlock_bh(&sc->sc_pcu_lock); ++ } ++ ++ } else if (busy >= 0) ++ sc->hw_busy_count = 0; ++ ++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"); ++ spin_lock_bh(&sc->sc_pcu_lock); ++ ath_reset(sc, true); ++ spin_unlock_bh(&sc->sc_pcu_lock); ++ 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); ++ } ++} ++ + /**********************/ + /* mac80211 callbacks */ + /**********************/ +--- a/drivers/net/wireless/ath/ath9k/xmit.c ++++ b/drivers/net/wireless/ath/ath9k/xmit.c +@@ -601,7 +601,7 @@ static void ath_tx_complete_aggr(struct + rcu_read_unlock(); + + if (needreset) +- ath_reset(sc, false); ++ ieee80211_queue_work(sc->hw, &sc->hw_reset_work); + } + + static bool ath_lookup_legacy(struct ath_buf *bf) +@@ -2268,9 +2268,7 @@ static void ath_tx_complete_poll_work(st + if (needreset) { + ath_dbg(ath9k_hw_common(sc->sc_ah), ATH_DBG_RESET, + "tx hung, resetting the chip\n"); +- spin_lock_bh(&sc->sc_pcu_lock); +- ath_reset(sc, true); +- spin_unlock_bh(&sc->sc_pcu_lock); ++ ieee80211_queue_work(sc->hw, &sc->hw_reset_work); + } + + ieee80211_queue_delayed_work(sc->hw, &sc->tx_complete_work, +--- a/drivers/net/wireless/ath/ath9k/beacon.c ++++ b/drivers/net/wireless/ath/ath9k/beacon.c +@@ -386,9 +386,7 @@ void ath_beacon_tasklet(unsigned long da + ath_dbg(common, ATH_DBG_BSTUCK, + "beacon is officially stuck\n"); + sc->sc_flags |= SC_OP_TSF_RESET; +- spin_lock(&sc->sc_pcu_lock); +- ath_reset(sc, true); +- spin_unlock(&sc->sc_pcu_lock); ++ ieee80211_queue_work(sc->hw, &sc->hw_reset_work); + } + + return; diff --git a/package/mac80211/patches/582-ath9k_antenna_control.patch b/package/mac80211/patches/582-ath9k_antenna_control.patch deleted file mode 100644 index 2e2bbea17c..0000000000 --- a/package/mac80211/patches/582-ath9k_antenna_control.patch +++ /dev/null @@ -1,178 +0,0 @@ ---- a/drivers/net/wireless/ath/ath9k/init.c -+++ b/drivers/net/wireless/ath/ath9k/init.c -@@ -652,9 +652,22 @@ static void ath9k_init_txpower_limits(st - ah->curchan = curchan; - } - -+void ath9k_reload_chainmask_settings(struct ath_softc *sc) -+{ -+ if (!(sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_HT)) -+ return; -+ -+ if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) -+ setup_ht_cap(sc, &sc->sbands[IEEE80211_BAND_2GHZ].ht_cap); -+ if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) -+ setup_ht_cap(sc, &sc->sbands[IEEE80211_BAND_5GHZ].ht_cap); -+} -+ -+ - void ath9k_set_hw_capab(struct ath_softc *sc, struct ieee80211_hw *hw) - { -- struct ath_common *common = ath9k_hw_common(sc->sc_ah); -+ struct ath_hw *ah = sc->sc_ah; -+ struct ath_common *common = ath9k_hw_common(ah); - - hw->flags = IEEE80211_HW_RX_INCLUDES_FCS | - IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING | -@@ -692,6 +705,16 @@ void ath9k_set_hw_capab(struct ath_softc - hw->sta_data_size = sizeof(struct ath_node); - hw->vif_data_size = sizeof(struct ath_vif); - -+ hw->wiphy->available_antennas_rx = BIT(ah->caps.max_rxchains) - 1; -+ hw->wiphy->available_antennas_tx = BIT(ah->caps.max_txchains) - 1; -+ -+ /* single chain devices with rx diversity */ -+ if (ah->caps.hw_caps & ATH9K_HW_CAP_ANT_DIV_COMB) -+ hw->wiphy->available_antennas_rx = BIT(0) | BIT(1); -+ -+ sc->ant_rx = hw->wiphy->available_antennas_rx; -+ sc->ant_tx = hw->wiphy->available_antennas_tx; -+ - #ifdef CONFIG_ATH9K_RATE_CONTROL - hw->rate_control_algorithm = "ath9k_rate_control"; - #endif -@@ -703,12 +726,7 @@ void ath9k_set_hw_capab(struct ath_softc - hw->wiphy->bands[IEEE80211_BAND_5GHZ] = - &sc->sbands[IEEE80211_BAND_5GHZ]; - -- if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_HT) { -- if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) -- setup_ht_cap(sc, &sc->sbands[IEEE80211_BAND_2GHZ].ht_cap); -- if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) -- setup_ht_cap(sc, &sc->sbands[IEEE80211_BAND_5GHZ].ht_cap); -- } -+ ath9k_reload_chainmask_settings(sc); - - SET_IEEE80211_PERM_ADDR(hw, common->macaddr); - } ---- a/drivers/net/wireless/ath/ath9k/ath9k.h -+++ b/drivers/net/wireless/ath/ath9k/ath9k.h -@@ -652,6 +652,7 @@ struct ath_softc { - struct ath_descdma txsdma; - - struct ath_ant_comb ant_comb; -+ u8 ant_tx, ant_rx; - }; - - void ath9k_tasklet(unsigned long data); -@@ -673,6 +674,7 @@ int ath9k_init_device(u16 devid, struct - const struct ath_bus_ops *bus_ops); - void ath9k_deinit_device(struct ath_softc *sc); - void ath9k_set_hw_capab(struct ath_softc *sc, struct ieee80211_hw *hw); -+void ath9k_reload_chainmask_settings(struct ath_softc *sc); - - void ath_radio_disable(struct ath_softc *sc, struct ieee80211_hw *hw); - bool ath9k_uses_beacons(int type); ---- a/drivers/net/wireless/ath/ath9k/main.c -+++ b/drivers/net/wireless/ath/ath9k/main.c -@@ -270,6 +270,22 @@ static bool ath_complete_reset(struct at - ath_start_ani(common); - } - -+ if (ath9k_hw_ops(ah)->antdiv_comb_conf_get && sc->ant_rx != 3) { -+ struct ath_hw_antcomb_conf div_ant_conf; -+ u8 lna_conf; -+ -+ ath9k_hw_antdiv_comb_conf_get(ah, &div_ant_conf); -+ -+ if (sc->ant_rx == 1) -+ lna_conf = ATH_ANT_DIV_COMB_LNA1; -+ else -+ lna_conf = ATH_ANT_DIV_COMB_LNA2; -+ div_ant_conf.main_lna_conf = lna_conf; -+ div_ant_conf.alt_lna_conf = lna_conf; -+ -+ ath9k_hw_antdiv_comb_conf_set(ah, &div_ant_conf); -+ } -+ - ieee80211_wake_queues(sc->hw); - - return true; -@@ -2366,6 +2382,59 @@ static int ath9k_get_stats(struct ieee80 - return 0; - } - -+static u32 fill_chainmask(u32 cap, u32 new) -+{ -+ u32 filled = 0; -+ int i; -+ -+ for (i = 0; cap && new; i++, cap >>= 1) { -+ if (!(cap & BIT(0))) -+ continue; -+ -+ if (new & BIT(0)) -+ filled |= BIT(i); -+ -+ new >>= 1; -+ } -+ -+ return filled; -+} -+ -+static int ath9k_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) -+{ -+ struct ath_softc *sc = hw->priv; -+ struct ath_hw *ah = sc->sc_ah; -+ -+ if (!rx_ant || !tx_ant) -+ return -EINVAL; -+ -+ sc->ant_rx = rx_ant; -+ sc->ant_tx = tx_ant; -+ -+ if (ah->caps.rx_chainmask == 1) -+ return 0; -+ -+ /* AR9100 runs into calibration issues if not all rx chains are enabled */ -+ if (AR_SREV_9100(ah)) -+ ah->rxchainmask = 0x7; -+ else -+ ah->rxchainmask = fill_chainmask(ah->caps.rx_chainmask, rx_ant); -+ -+ ah->txchainmask = fill_chainmask(ah->caps.tx_chainmask, tx_ant); -+ ath9k_reload_chainmask_settings(sc); -+ -+ return 0; -+} -+ -+static int ath9k_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) -+{ -+ struct ath_softc *sc = hw->priv; -+ -+ *tx_ant = sc->ant_tx; -+ *rx_ant = sc->ant_rx; -+ return 0; -+} -+ - struct ieee80211_ops ath9k_ops = { - .tx = ath9k_tx, - .start = ath9k_start, -@@ -2392,4 +2461,6 @@ struct ieee80211_ops ath9k_ops = { - .tx_frames_pending = ath9k_tx_frames_pending, - .tx_last_beacon = ath9k_tx_last_beacon, - .get_stats = ath9k_get_stats, -+ .set_antenna = ath9k_set_antenna, -+ .get_antenna = ath9k_get_antenna, - }; ---- a/drivers/net/wireless/ath/ath9k/recv.c -+++ b/drivers/net/wireless/ath/ath9k/recv.c -@@ -1956,7 +1956,7 @@ int ath_rx_tasklet(struct ath_softc *sc, - ath_rx_ps(sc, skb); - spin_unlock_irqrestore(&sc->sc_pm_lock, flags); - -- if (ah->caps.hw_caps & ATH9K_HW_CAP_ANT_DIV_COMB) -+ if ((ah->caps.hw_caps & ATH9K_HW_CAP_ANT_DIV_COMB) && sc->ant_rx == 3) - ath_ant_comb_scan(sc, &rs); - - ieee80211_rx(hw, skb); diff --git a/package/mac80211/patches/582-ath9k_merge_reset_functions.patch b/package/mac80211/patches/582-ath9k_merge_reset_functions.patch new file mode 100644 index 0000000000..5484829b16 --- /dev/null +++ b/package/mac80211/patches/582-ath9k_merge_reset_functions.patch @@ -0,0 +1,446 @@ +--- a/drivers/net/wireless/ath/ath9k/main.c ++++ b/drivers/net/wireless/ath/ath9k/main.c +@@ -212,83 +212,57 @@ static int ath_update_survey_stats(struc + return ret; + } + +-/* +- * Set/change channels. If the channel is really being changed, it's done +- * by reseting the chip. To accomplish this we must first cleanup any pending +- * DMA, then restart stuff. +-*/ +-static int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw, +- struct ath9k_channel *hchan) ++static void __ath_cancel_work(struct ath_softc *sc) + { +- struct ath_hw *ah = sc->sc_ah; +- struct ath_common *common = ath9k_hw_common(ah); +- struct ieee80211_conf *conf = &common->hw->conf; +- bool fastcc = true, stopped; +- struct ieee80211_channel *channel = hw->conf.channel; +- struct ath9k_hw_cal_data *caldata = NULL; +- int r; +- +- if (sc->sc_flags & SC_OP_INVALID) +- return -EIO; +- +- sc->hw_busy_count = 0; +- +- del_timer_sync(&common->ani.timer); + cancel_work_sync(&sc->paprd_work); + cancel_work_sync(&sc->hw_check_work); + cancel_delayed_work_sync(&sc->tx_complete_work); + cancel_delayed_work_sync(&sc->hw_pll_work); ++} + +- ath9k_ps_wakeup(sc); ++static void ath_cancel_work(struct ath_softc *sc) ++{ ++ __ath_cancel_work(sc); ++ cancel_work_sync(&sc->hw_reset_work); ++} + +- spin_lock_bh(&sc->sc_pcu_lock); ++static bool ath_prepare_reset(struct ath_softc *sc, bool retry_tx, bool flush) ++{ ++ struct ath_hw *ah = sc->sc_ah; ++ struct ath_common *common = ath9k_hw_common(ah); ++ bool ret; + +- /* +- * This is only performed if the channel settings have +- * actually changed. +- * +- * To switch channels clear any pending DMA operations; +- * wait long enough for the RX fifo to drain, reset the +- * hardware at the new frequency, and then re-enable +- * the relevant bits of the h/w. +- */ +- ath9k_hw_disable_interrupts(ah); +- stopped = ath_drain_all_txq(sc, false); ++ ieee80211_stop_queues(sc->hw); + +- if (!ath_stoprecv(sc)) +- stopped = false; ++ sc->hw_busy_count = 0; ++ del_timer_sync(&common->ani.timer); + +- if (!ath9k_hw_check_alive(ah)) +- stopped = false; ++ ath9k_hw_disable_interrupts(ah); + +- /* XXX: do not flush receive queue here. We don't want +- * to flush data frames already in queue because of +- * changing channel. */ ++ ret = ath_drain_all_txq(sc, retry_tx); + +- if (!stopped || !(sc->sc_flags & SC_OP_OFFCHANNEL)) +- fastcc = false; ++ if (!ath_stoprecv(sc)) ++ ret = false; + +- if (!(sc->sc_flags & SC_OP_OFFCHANNEL)) +- caldata = &sc->caldata; ++ if (!flush) { ++ if (ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) ++ ath_rx_tasklet(sc, 0, true); ++ ath_rx_tasklet(sc, 0, false); ++ } else { ++ ath_flushrecv(sc); ++ } + +- ath_dbg(common, ATH_DBG_CONFIG, +- "(%u MHz) -> (%u MHz), conf_is_ht40: %d fastcc: %d\n", +- sc->sc_ah->curchan->channel, +- channel->center_freq, conf_is_ht40(conf), +- fastcc); ++ return ret; ++} + +- r = ath9k_hw_reset(ah, hchan, caldata, fastcc); +- if (r) { +- ath_err(common, +- "Unable to reset channel (%u MHz), reset status %d\n", +- channel->center_freq, r); +- goto ps_restore; +- } ++static bool ath_complete_reset(struct ath_softc *sc, bool start) ++{ ++ struct ath_hw *ah = sc->sc_ah; ++ struct ath_common *common = ath9k_hw_common(ah); + + if (ath_startrecv(sc) != 0) { + ath_err(common, "Unable to restart recv logic\n"); +- r = -EIO; +- goto ps_restore; ++ return false; + } + + ath9k_cmn_update_txpow(ah, sc->curtxpow, +@@ -296,21 +270,93 @@ static int ath_set_channel(struct ath_so + ath9k_hw_set_interrupts(ah, ah->imask); + ath9k_hw_enable_interrupts(ah); + +- if (!(sc->sc_flags & (SC_OP_OFFCHANNEL))) { ++ if (!(sc->sc_flags & (SC_OP_OFFCHANNEL)) && start) { + if (sc->sc_flags & SC_OP_BEACONS) + 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); + if (!common->disable_ani) + ath_start_ani(common); + } + +- ps_restore: +- ieee80211_wake_queues(hw); ++ ieee80211_wake_queues(sc->hw); ++ ++ return true; ++} ++ ++static int ath_reset_internal(struct ath_softc *sc, struct ath9k_channel *hchan, ++ bool retry_tx) ++{ ++ struct ath_hw *ah = sc->sc_ah; ++ struct ath_common *common = ath9k_hw_common(ah); ++ struct ath9k_hw_cal_data *caldata = NULL; ++ bool fastcc = true; ++ bool flush = false; ++ int r; ++ ++ __ath_cancel_work(sc); ++ ++ spin_lock_bh(&sc->sc_pcu_lock); + ++ if (!(sc->sc_flags & SC_OP_OFFCHANNEL)) { ++ fastcc = false; ++ caldata = &sc->caldata; ++ } ++ ++ if (!hchan) { ++ fastcc = false; ++ flush = true; ++ hchan = ah->curchan; ++ } ++ ++ if (fastcc && !ath9k_hw_check_alive(ah)) ++ fastcc = false; ++ ++ if (!ath_prepare_reset(sc, retry_tx, flush)) ++ fastcc = false; ++ ++ ath_dbg(common, ATH_DBG_CONFIG, ++ "Reset to %u MHz, HT40: %d fastcc: %d\n", ++ hchan->channel, !!(hchan->channelFlags & (CHANNEL_HT40MINUS | ++ CHANNEL_HT40PLUS)), ++ fastcc); ++ ++ r = ath9k_hw_reset(ah, hchan, caldata, fastcc); ++ if (r) { ++ ath_err(common, ++ "Unable to reset channel, reset status %d\n", r); ++ goto out; ++ } ++ ++ if (!ath_complete_reset(sc, true)) ++ r = -EIO; ++ ++out: + spin_unlock_bh(&sc->sc_pcu_lock); ++ return 0; ++} ++ ++ ++/* ++ * Set/change channels. If the channel is really being changed, it's done ++ * by reseting the chip. To accomplish this we must first cleanup any pending ++ * DMA, then restart stuff. ++*/ ++static int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw, ++ struct ath9k_channel *hchan) ++{ ++ int r; ++ ++ if (sc->sc_flags & SC_OP_INVALID) ++ return -EIO; ++ ++ ath9k_ps_wakeup(sc); ++ ++ r = ath_reset_internal(sc, hchan, false); + + ath9k_ps_restore(sc); ++ + return r; + } + +@@ -823,28 +869,13 @@ static void ath_radio_enable(struct ath_ + channel->center_freq, r); + } + +- ath9k_cmn_update_txpow(ah, sc->curtxpow, +- sc->config.txpowlimit, &sc->curtxpow); +- if (ath_startrecv(sc) != 0) { +- ath_err(common, "Unable to restart recv logic\n"); +- goto out; +- } +- if (sc->sc_flags & SC_OP_BEACONS) +- ath_set_beacon(sc); /* restart beacons */ +- +- /* Re-Enable interrupts */ +- ath9k_hw_set_interrupts(ah, ah->imask); +- ath9k_hw_enable_interrupts(ah); ++ ath_complete_reset(sc, true); + + /* Enable LED */ + ath9k_hw_cfg_output(ah, ah->led_pin, + AR_GPIO_OUTPUT_MUX_AS_OUTPUT); + ath9k_hw_set_gpio(ah, ah->led_pin, 0); + +- ieee80211_wake_queues(hw); +- ieee80211_queue_delayed_work(hw, &sc->hw_pll_work, HZ/2); +- +-out: + spin_unlock_bh(&sc->sc_pcu_lock); + + ath9k_ps_restore(sc); +@@ -857,11 +888,10 @@ void ath_radio_disable(struct ath_softc + int r; + + ath9k_ps_wakeup(sc); +- cancel_delayed_work_sync(&sc->hw_pll_work); + +- spin_lock_bh(&sc->sc_pcu_lock); ++ ath_cancel_work(sc); + +- ieee80211_stop_queues(hw); ++ spin_lock_bh(&sc->sc_pcu_lock); + + /* + * Keep the LED on when the radio is disabled +@@ -872,13 +902,7 @@ void ath_radio_disable(struct ath_softc + ath9k_hw_cfg_gpio_input(ah, ah->led_pin); + } + +- /* Disable interrupts */ +- ath9k_hw_disable_interrupts(ah); +- +- ath_drain_all_txq(sc, false); /* clear pending tx frames */ +- +- ath_stoprecv(sc); /* turn off frame recv */ +- ath_flushrecv(sc); /* flush recv queue */ ++ ath_prepare_reset(sc, false, true); + + if (!ah->curchan) + ah->curchan = ath9k_cmn_get_curchannel(hw, ah); +@@ -900,48 +924,11 @@ void ath_radio_disable(struct ath_softc + + static int ath_reset(struct ath_softc *sc, bool retry_tx) + { +- struct ath_hw *ah = sc->sc_ah; +- struct ath_common *common = ath9k_hw_common(ah); +- struct ieee80211_hw *hw = sc->hw; + int r; + +- sc->hw_busy_count = 0; +- +- /* Stop ANI */ +- +- del_timer_sync(&common->ani.timer); +- + ath9k_ps_wakeup(sc); + +- ieee80211_stop_queues(hw); +- +- ath9k_hw_disable_interrupts(ah); +- ath_drain_all_txq(sc, retry_tx); +- +- ath_stoprecv(sc); +- ath_flushrecv(sc); +- +- r = ath9k_hw_reset(ah, sc->sc_ah->curchan, ah->caldata, false); +- if (r) +- ath_err(common, +- "Unable to reset hardware; reset status %d\n", r); +- +- if (ath_startrecv(sc) != 0) +- ath_err(common, "Unable to start recv logic\n"); +- +- /* +- * We may be doing a reset in response to a request +- * that changes the channel so update any state that +- * might change as a result. +- */ +- ath9k_cmn_update_txpow(ah, sc->curtxpow, +- sc->config.txpowlimit, &sc->curtxpow); +- +- if ((sc->sc_flags & SC_OP_BEACONS) || !(sc->sc_flags & (SC_OP_OFFCHANNEL))) +- ath_set_beacon(sc); /* restart beacons */ +- +- ath9k_hw_set_interrupts(ah, ah->imask); +- ath9k_hw_enable_interrupts(ah); ++ r = ath_reset_internal(sc, NULL, retry_tx); + + if (retry_tx) { + int i; +@@ -954,12 +941,6 @@ static int ath_reset(struct ath_softc *s + } + } + +- ieee80211_wake_queues(hw); +- +- /* Start ANI */ +- if (!common->disable_ani) +- ath_start_ani(common); +- + ath9k_ps_restore(sc); + + return r; +@@ -969,9 +950,7 @@ void ath_reset_work(struct work_struct * + { + struct ath_softc *sc = container_of(work, struct ath_softc, hw_check_work); + +- spin_lock_bh(&sc->sc_pcu_lock); + ath_reset(sc, true); +- spin_unlock_bh(&sc->sc_pcu_lock); + } + + void ath_hw_check(struct work_struct *work) +@@ -992,11 +971,8 @@ void ath_hw_check(struct work_struct *wo + ath_dbg(common, ATH_DBG_RESET, "Possible baseband hang, " + "busy=%d (try %d)\n", busy, sc->hw_busy_count + 1); + if (busy >= 99) { +- if (++sc->hw_busy_count >= 3) { +- spin_lock_bh(&sc->sc_pcu_lock); +- ath_reset(sc, true); +- spin_unlock_bh(&sc->sc_pcu_lock); +- } ++ if (++sc->hw_busy_count >= 3) ++ ieee80211_queue_work(sc->hw, &sc->hw_reset_work); + + } else if (busy >= 0) + sc->hw_busy_count = 0; +@@ -1016,9 +992,7 @@ static void ath_hw_pll_rx_hang_check(str + /* Rx is hung for more than 500ms. Reset it */ + ath_dbg(common, ATH_DBG_RESET, + "Possible RX hang, resetting"); +- spin_lock_bh(&sc->sc_pcu_lock); +- ath_reset(sc, true); +- spin_unlock_bh(&sc->sc_pcu_lock); ++ ieee80211_queue_work(sc->hw, &sc->hw_reset_work); + count = 0; + } + } else +@@ -1089,28 +1063,6 @@ static int ath9k_start(struct ieee80211_ + goto mutex_unlock; + } + +- /* +- * This is needed only to setup initial state +- * but it's best done after a reset. +- */ +- ath9k_cmn_update_txpow(ah, sc->curtxpow, +- sc->config.txpowlimit, &sc->curtxpow); +- +- /* +- * Setup the hardware after reset: +- * The receive engine is set going. +- * Frame transmit is handled entirely +- * in the frame output path; there's nothing to do +- * here except setup the interrupt mask. +- */ +- if (ath_startrecv(sc) != 0) { +- ath_err(common, "Unable to start recv logic\n"); +- r = -EIO; +- spin_unlock_bh(&sc->sc_pcu_lock); +- goto mutex_unlock; +- } +- spin_unlock_bh(&sc->sc_pcu_lock); +- + /* Setup our intr mask. */ + ah->imask = ATH9K_INT_TX | ATH9K_INT_RXEOL | + ATH9K_INT_RXORN | ATH9K_INT_FATAL | +@@ -1133,12 +1085,14 @@ static int ath9k_start(struct ieee80211_ + + /* Disable BMISS interrupt when we're not associated */ + ah->imask &= ~(ATH9K_INT_SWBA | ATH9K_INT_BMISS); +- ath9k_hw_set_interrupts(ah, ah->imask); +- ath9k_hw_enable_interrupts(ah); + +- ieee80211_wake_queues(hw); ++ if (!ath_complete_reset(sc, false)) { ++ r = -EIO; ++ spin_unlock_bh(&sc->sc_pcu_lock); ++ goto mutex_unlock; ++ } + +- ieee80211_queue_delayed_work(sc->hw, &sc->tx_complete_work, 0); ++ spin_unlock_bh(&sc->sc_pcu_lock); + + if ((ah->btcoex_hw.scheme != ATH_BTCOEX_CFG_NONE) && + !ah->btcoex_hw.enabled) { +@@ -1231,10 +1185,7 @@ static void ath9k_stop(struct ieee80211_ + + mutex_lock(&sc->mutex); + +- cancel_delayed_work_sync(&sc->tx_complete_work); +- cancel_delayed_work_sync(&sc->hw_pll_work); +- cancel_work_sync(&sc->paprd_work); +- cancel_work_sync(&sc->hw_check_work); ++ ath_cancel_work(sc); + + if (sc->sc_flags & SC_OP_INVALID) { + ath_dbg(common, ATH_DBG_ANY, "Device not present\n"); +@@ -2351,9 +2302,11 @@ static void ath9k_flush(struct ieee80211 + 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); +- spin_unlock_bh(&sc->sc_pcu_lock); ++ + ath9k_ps_restore(sc); + ieee80211_wake_queues(hw); + diff --git a/package/mac80211/patches/583-ath9k_antenna_control.patch b/package/mac80211/patches/583-ath9k_antenna_control.patch new file mode 100644 index 0000000000..d69684d12b --- /dev/null +++ b/package/mac80211/patches/583-ath9k_antenna_control.patch @@ -0,0 +1,178 @@ +--- a/drivers/net/wireless/ath/ath9k/init.c ++++ b/drivers/net/wireless/ath/ath9k/init.c +@@ -652,9 +652,22 @@ static void ath9k_init_txpower_limits(st + ah->curchan = curchan; + } + ++void ath9k_reload_chainmask_settings(struct ath_softc *sc) ++{ ++ if (!(sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_HT)) ++ return; ++ ++ if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) ++ setup_ht_cap(sc, &sc->sbands[IEEE80211_BAND_2GHZ].ht_cap); ++ if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) ++ setup_ht_cap(sc, &sc->sbands[IEEE80211_BAND_5GHZ].ht_cap); ++} ++ ++ + void ath9k_set_hw_capab(struct ath_softc *sc, struct ieee80211_hw *hw) + { +- struct ath_common *common = ath9k_hw_common(sc->sc_ah); ++ struct ath_hw *ah = sc->sc_ah; ++ struct ath_common *common = ath9k_hw_common(ah); + + hw->flags = IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING | +@@ -692,6 +705,16 @@ void ath9k_set_hw_capab(struct ath_softc + hw->sta_data_size = sizeof(struct ath_node); + hw->vif_data_size = sizeof(struct ath_vif); + ++ hw->wiphy->available_antennas_rx = BIT(ah->caps.max_rxchains) - 1; ++ hw->wiphy->available_antennas_tx = BIT(ah->caps.max_txchains) - 1; ++ ++ /* single chain devices with rx diversity */ ++ if (ah->caps.hw_caps & ATH9K_HW_CAP_ANT_DIV_COMB) ++ hw->wiphy->available_antennas_rx = BIT(0) | BIT(1); ++ ++ sc->ant_rx = hw->wiphy->available_antennas_rx; ++ sc->ant_tx = hw->wiphy->available_antennas_tx; ++ + #ifdef CONFIG_ATH9K_RATE_CONTROL + hw->rate_control_algorithm = "ath9k_rate_control"; + #endif +@@ -703,12 +726,7 @@ void ath9k_set_hw_capab(struct ath_softc + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = + &sc->sbands[IEEE80211_BAND_5GHZ]; + +- if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_HT) { +- if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) +- setup_ht_cap(sc, &sc->sbands[IEEE80211_BAND_2GHZ].ht_cap); +- if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) +- setup_ht_cap(sc, &sc->sbands[IEEE80211_BAND_5GHZ].ht_cap); +- } ++ ath9k_reload_chainmask_settings(sc); + + SET_IEEE80211_PERM_ADDR(hw, common->macaddr); + } +--- a/drivers/net/wireless/ath/ath9k/ath9k.h ++++ b/drivers/net/wireless/ath/ath9k/ath9k.h +@@ -654,6 +654,7 @@ struct ath_softc { + struct ath_descdma txsdma; + + struct ath_ant_comb ant_comb; ++ u8 ant_tx, ant_rx; + }; + + void ath9k_tasklet(unsigned long data); +@@ -674,6 +675,7 @@ int ath9k_init_device(u16 devid, struct + const struct ath_bus_ops *bus_ops); + void ath9k_deinit_device(struct ath_softc *sc); + void ath9k_set_hw_capab(struct ath_softc *sc, struct ieee80211_hw *hw); ++void ath9k_reload_chainmask_settings(struct ath_softc *sc); + + void ath_radio_disable(struct ath_softc *sc, struct ieee80211_hw *hw); + bool ath9k_uses_beacons(int type); +--- a/drivers/net/wireless/ath/ath9k/main.c ++++ b/drivers/net/wireless/ath/ath9k/main.c +@@ -280,6 +280,22 @@ static bool ath_complete_reset(struct at + ath_start_ani(common); + } + ++ if (ath9k_hw_ops(ah)->antdiv_comb_conf_get && sc->ant_rx != 3) { ++ struct ath_hw_antcomb_conf div_ant_conf; ++ u8 lna_conf; ++ ++ ath9k_hw_antdiv_comb_conf_get(ah, &div_ant_conf); ++ ++ if (sc->ant_rx == 1) ++ lna_conf = ATH_ANT_DIV_COMB_LNA1; ++ else ++ lna_conf = ATH_ANT_DIV_COMB_LNA2; ++ div_ant_conf.main_lna_conf = lna_conf; ++ div_ant_conf.alt_lna_conf = lna_conf; ++ ++ ath9k_hw_antdiv_comb_conf_set(ah, &div_ant_conf); ++ } ++ + ieee80211_wake_queues(sc->hw); + + return true; +@@ -2383,6 +2399,59 @@ static int ath9k_get_stats(struct ieee80 + return 0; + } + ++static u32 fill_chainmask(u32 cap, u32 new) ++{ ++ u32 filled = 0; ++ int i; ++ ++ for (i = 0; cap && new; i++, cap >>= 1) { ++ if (!(cap & BIT(0))) ++ continue; ++ ++ if (new & BIT(0)) ++ filled |= BIT(i); ++ ++ new >>= 1; ++ } ++ ++ return filled; ++} ++ ++static int ath9k_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) ++{ ++ struct ath_softc *sc = hw->priv; ++ struct ath_hw *ah = sc->sc_ah; ++ ++ if (!rx_ant || !tx_ant) ++ return -EINVAL; ++ ++ sc->ant_rx = rx_ant; ++ sc->ant_tx = tx_ant; ++ ++ if (ah->caps.rx_chainmask == 1) ++ return 0; ++ ++ /* AR9100 runs into calibration issues if not all rx chains are enabled */ ++ if (AR_SREV_9100(ah)) ++ ah->rxchainmask = 0x7; ++ else ++ ah->rxchainmask = fill_chainmask(ah->caps.rx_chainmask, rx_ant); ++ ++ ah->txchainmask = fill_chainmask(ah->caps.tx_chainmask, tx_ant); ++ ath9k_reload_chainmask_settings(sc); ++ ++ return 0; ++} ++ ++static int ath9k_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) ++{ ++ struct ath_softc *sc = hw->priv; ++ ++ *tx_ant = sc->ant_tx; ++ *rx_ant = sc->ant_rx; ++ return 0; ++} ++ + struct ieee80211_ops ath9k_ops = { + .tx = ath9k_tx, + .start = ath9k_start, +@@ -2409,4 +2478,6 @@ struct ieee80211_ops ath9k_ops = { + .tx_frames_pending = ath9k_tx_frames_pending, + .tx_last_beacon = ath9k_tx_last_beacon, + .get_stats = ath9k_get_stats, ++ .set_antenna = ath9k_set_antenna, ++ .get_antenna = ath9k_get_antenna, + }; +--- a/drivers/net/wireless/ath/ath9k/recv.c ++++ b/drivers/net/wireless/ath/ath9k/recv.c +@@ -1956,7 +1956,7 @@ int ath_rx_tasklet(struct ath_softc *sc, + ath_rx_ps(sc, skb); + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + +- if (ah->caps.hw_caps & ATH9K_HW_CAP_ANT_DIV_COMB) ++ if ((ah->caps.hw_caps & ATH9K_HW_CAP_ANT_DIV_COMB) && sc->ant_rx == 3) + ath_ant_comb_scan(sc, &rs); + + ieee80211_rx(hw, skb);