aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFelix Fietkau <nbd@openwrt.org>2011-08-28 18:38:24 +0000
committerFelix Fietkau <nbd@openwrt.org>2011-08-28 18:38:24 +0000
commit65fbf0f9f04e7773edc8f273ac24afa232bf01d8 (patch)
treee021e922696045c1b0670b2813193c97c89ee969
parent293d665351aec1046ecb731795e1777a9d26cdda (diff)
downloadmaster-187ad058-65fbf0f9f04e7773edc8f273ac24afa232bf01d8.tar.gz
master-187ad058-65fbf0f9f04e7773edc8f273ac24afa232bf01d8.tar.bz2
master-187ad058-65fbf0f9f04e7773edc8f273ac24afa232bf01d8.zip
ath9k: implement rx/tx antenna control
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@28107 3c298f89-4303-0410-b956-a3cf2f4a3e73
-rw-r--r--package/mac80211/patches/513-ath9k_channelbw_debugfs.patch75
-rw-r--r--package/mac80211/patches/531-ath9k_cur_txpower.patch4
-rw-r--r--package/mac80211/patches/580-ath9k_remove_common_chainmask.patch273
-rw-r--r--package/mac80211/patches/581-ath9k_merge_reset_functions.patch343
-rw-r--r--package/mac80211/patches/582-ath9k_antdiv_comb_cb.patch50
-rw-r--r--package/mac80211/patches/583-ath9k_antenna_control.patch179
6 files changed, 893 insertions, 31 deletions
diff --git a/package/mac80211/patches/513-ath9k_channelbw_debugfs.patch b/package/mac80211/patches/513-ath9k_channelbw_debugfs.patch
index 6ae1323d2b..fedd17f51d 100644
--- a/package/mac80211/patches/513-ath9k_channelbw_debugfs.patch
+++ b/package/mac80211/patches/513-ath9k_channelbw_debugfs.patch
@@ -22,36 +22,53 @@
}
--- a/drivers/net/wireless/ath/ath9k/main.c
+++ b/drivers/net/wireless/ath/ath9k/main.c
-@@ -226,6 +226,7 @@ static int ath_set_channel(struct ath_so
- bool fastcc = true, stopped;
- struct ieee80211_channel *channel = hw->conf.channel;
- struct ath9k_hw_cal_data *caldata = NULL;
-+ u32 oldflags;
- int r;
+@@ -1654,9 +1654,10 @@ static int ath9k_config(struct ieee80211
- if (sc->sc_flags & SC_OP_INVALID)
-@@ -268,6 +269,24 @@ static int ath_set_channel(struct ath_so
- if (!stopped || !(sc->sc_flags & SC_OP_OFFCHANNEL))
- fastcc = false;
+ if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
+ struct ieee80211_channel *curchan = hw->conf.channel;
+- struct ath9k_channel old_chan;
++ struct ath9k_channel old_chan, *hchan;
+ int pos = curchan->hw_value;
+ int old_pos = -1;
++ u32 oldflags;
+ unsigned long flags;
-+ oldflags = hchan->channelFlags;
-+ switch (sc->chan_bw) {
-+ case 5:
-+ hchan->channelFlags &= ~CHANNEL_HALF;
-+ hchan->channelFlags |= CHANNEL_QUARTER;
-+ break;
-+ case 10:
-+ hchan->channelFlags &= ~CHANNEL_QUARTER;
-+ hchan->channelFlags |= CHANNEL_HALF;
-+ break;
-+ default:
-+ hchan->channelFlags &= ~(CHANNEL_HALF | CHANNEL_QUARTER);
-+ break;
-+ }
-+
-+ if (oldflags != hchan->channelFlags)
-+ fastcc = false;
+ if (ah->curchan)
+@@ -1709,7 +1710,23 @@ static int ath9k_config(struct ieee80211
+ memset(&sc->survey[pos], 0, sizeof(struct survey_info));
+ }
+
+- if (ath_set_channel(sc, hw, &sc->sc_ah->channels[pos]) < 0) {
++ hchan = &sc->sc_ah->channels[pos];
++ oldflags = hchan->channelFlags;
++ switch (sc->chan_bw) {
++ case 5:
++ hchan->channelFlags &= ~CHANNEL_HALF;
++ hchan->channelFlags |= CHANNEL_QUARTER;
++ break;
++ case 10:
++ hchan->channelFlags &= ~CHANNEL_QUARTER;
++ hchan->channelFlags |= CHANNEL_HALF;
++ break;
++ default:
++ hchan->channelFlags &= ~(CHANNEL_HALF | CHANNEL_QUARTER);
++ break;
++ }
+
- if (!(sc->sc_flags & SC_OP_OFFCHANNEL))
- caldata = &sc->caldata;
++ if (ath_set_channel(sc, hw, hchan) < 0) {
+ ath_err(common, "Unable to set channel\n");
+ mutex_unlock(&sc->mutex);
+ return -EINVAL;
+--- a/drivers/net/wireless/ath/ath9k/hw.c
++++ b/drivers/net/wireless/ath/ath9k/hw.c
+@@ -1504,6 +1504,10 @@ int ath9k_hw_reset(struct ath_hw *ah, st
+ }
+ ah->noise = ath9k_hw_getchan_noise(ah, chan);
++ if (!ah->curchan || ((ah->curchan->channelFlags ^ chan->channelFlags) &
++ (CHANNEL_HALF | CHANNEL_QUARTER)))
++ bChannelChange = false;
++
+ if (bChannelChange &&
+ (ah->chip_fullsleep != true) &&
+ (ah->curchan != NULL) &&
diff --git a/package/mac80211/patches/531-ath9k_cur_txpower.patch b/package/mac80211/patches/531-ath9k_cur_txpower.patch
index f0de43eb47..8e104d9fee 100644
--- a/package/mac80211/patches/531-ath9k_cur_txpower.patch
+++ b/package/mac80211/patches/531-ath9k_cur_txpower.patch
@@ -1,6 +1,6 @@
--- a/drivers/net/wireless/ath/ath9k/main.c
+++ b/drivers/net/wireless/ath/ath9k/main.c
-@@ -1734,6 +1734,8 @@ static int ath9k_config(struct ieee80211
+@@ -1732,6 +1732,8 @@ static int ath9k_config(struct ieee80211
return -EINVAL;
}
@@ -9,7 +9,7 @@
/*
* The most recent snapshot of channel->noisefloor for the old
* channel is only available after the hardware reset. Copy it to
-@@ -1751,6 +1753,7 @@ static int ath9k_config(struct ieee80211
+@@ -1749,6 +1751,7 @@ static int ath9k_config(struct ieee80211
ath9k_cmn_update_txpow(ah, sc->curtxpow,
sc->config.txpowlimit, &sc->curtxpow);
ath9k_ps_restore(sc);
diff --git a/package/mac80211/patches/580-ath9k_remove_common_chainmask.patch b/package/mac80211/patches/580-ath9k_remove_common_chainmask.patch
new file mode 100644
index 0000000000..1164fc2acc
--- /dev/null
+++ b/package/mac80211/patches/580-ath9k_remove_common_chainmask.patch
@@ -0,0 +1,273 @@
+--- a/drivers/net/wireless/ath/ath.h
++++ b/drivers/net/wireless/ath/ath.h
+@@ -140,9 +140,6 @@ struct ath_common {
+ u8 curbssid[ETH_ALEN];
+ u8 bssidmask[ETH_ALEN];
+
+- u8 tx_chainmask;
+- u8 rx_chainmask;
+-
+ u32 rx_bufsize;
+
+ u32 keymax;
+--- a/drivers/net/wireless/ath/ath9k/ar9003_paprd.c
++++ b/drivers/net/wireless/ath/ath9k/ar9003_paprd.c
+@@ -113,7 +113,7 @@ static int ar9003_get_training_power_5g(
+ if (delta > scale)
+ return -1;
+
+- switch (get_streams(common->tx_chainmask)) {
++ switch (get_streams(ah->txchainmask)) {
+ case 1:
+ delta = 6;
+ break;
+@@ -126,7 +126,7 @@ static int ar9003_get_training_power_5g(
+ default:
+ delta = 0;
+ ath_dbg(common, ATH_DBG_CALIBRATE,
+- "Invalid tx-chainmask: %u\n", common->tx_chainmask);
++ "Invalid tx-chainmask: %u\n", ah->txchainmask);
+ }
+
+ power += delta;
+--- a/drivers/net/wireless/ath/ath9k/main.c
++++ b/drivers/net/wireless/ath/ath9k/main.c
+@@ -318,7 +318,6 @@ static void ath_paprd_activate(struct at
+ {
+ struct ath_hw *ah = sc->sc_ah;
+ struct ath9k_hw_cal_data *caldata = ah->caldata;
+- struct ath_common *common = ath9k_hw_common(ah);
+ int chain;
+
+ if (!caldata || !caldata->paprd_done)
+@@ -327,7 +326,7 @@ static void ath_paprd_activate(struct at
+ ath9k_ps_wakeup(sc);
+ ar9003_paprd_enable(ah, false);
+ for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) {
+- if (!(common->tx_chainmask & BIT(chain)))
++ if (!(ah->txchainmask & BIT(chain)))
+ continue;
+
+ ar9003_paprd_populate_single_table(ah, caldata, chain);
+@@ -414,7 +413,7 @@ void ath_paprd_calibrate(struct work_str
+ memcpy(hdr->addr3, hw->wiphy->perm_addr, ETH_ALEN);
+
+ for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) {
+- if (!(common->tx_chainmask & BIT(chain)))
++ if (!(ah->txchainmask & BIT(chain)))
+ continue;
+
+ chain_ok = 0;
+@@ -535,7 +534,7 @@ void ath_ani_calibrate(unsigned long dat
+ if (longcal || shortcal) {
+ common->ani.caldone =
+ ath9k_hw_calibrate(ah, ah->curchan,
+- common->rx_chainmask, longcal);
++ ah->rxchainmask, longcal);
+ }
+
+ ath9k_ps_restore(sc);
+--- a/drivers/net/wireless/ath/ath9k/hw.c
++++ b/drivers/net/wireless/ath/ath9k/hw.c
+@@ -1485,9 +1485,6 @@ int ath9k_hw_reset(struct ath_hw *ah, st
+ u64 tsf = 0;
+ int i, r;
+
+- ah->txchainmask = common->tx_chainmask;
+- ah->rxchainmask = common->rx_chainmask;
+-
+ if (!ath9k_hw_setpower(ah, ATH9K_PM_AWAKE))
+ return -EIO;
+
+@@ -2105,6 +2102,8 @@ int ath9k_hw_fill_cap_info(struct ath_hw
+
+ pCap->tx_chainmask = fixup_chainmask(chip_chainmask, pCap->tx_chainmask);
+ pCap->rx_chainmask = fixup_chainmask(chip_chainmask, pCap->rx_chainmask);
++ ah->txchainmask = pCap->tx_chainmask;
++ ah->rxchainmask = pCap->rx_chainmask;
+
+ ah->misc_mode |= AR_PCU_MIC_NEW_LOC_ENA;
+
+--- a/drivers/net/wireless/ath/ath9k/xmit.c
++++ b/drivers/net/wireless/ath/ath9k/xmit.c
+@@ -1655,7 +1655,7 @@ u8 ath_txchainmask_reduction(struct ath_
+
+ static void ath_buf_set_rate(struct ath_softc *sc, struct ath_buf *bf, int len)
+ {
+- struct ath_common *common = ath9k_hw_common(sc->sc_ah);
++ struct ath_hw *ah = sc->sc_ah;
+ struct ath9k_11n_rate_series series[4];
+ struct sk_buff *skb;
+ struct ieee80211_tx_info *tx_info;
+@@ -1715,7 +1715,7 @@ static void ath_buf_set_rate(struct ath_
+ /* MCS rates */
+ series[i].Rate = rix | 0x80;
+ series[i].ChSel = ath_txchainmask_reduction(sc,
+- common->tx_chainmask, series[i].Rate);
++ ah->txchainmask, series[i].Rate);
+ series[i].PktDuration = ath_pkt_duration(sc, rix, len,
+ is_40, is_sgi, is_sp);
+ if (rix < 8 && (tx_info->flags & IEEE80211_TX_CTL_STBC))
+@@ -1740,10 +1740,10 @@ static void ath_buf_set_rate(struct ath_
+ }
+
+ if (bf->bf_state.bfs_paprd)
+- series[i].ChSel = common->tx_chainmask;
++ series[i].ChSel = ah->txchainmask;
+ else
+ series[i].ChSel = ath_txchainmask_reduction(sc,
+- common->tx_chainmask, series[i].Rate);
++ ah->txchainmask, series[i].Rate);
+
+ series[i].PktDuration = ath9k_hw_computetxtime(sc->sc_ah,
+ phy, rate->bitrate * 100, len, rix, is_sp);
+--- a/drivers/net/wireless/ath/ath9k/beacon.c
++++ b/drivers/net/wireless/ath/ath9k/beacon.c
+@@ -107,7 +107,7 @@ static void ath_beacon_setup(struct ath_
+ series[0].Tries = 1;
+ series[0].Rate = rate;
+ series[0].ChSel = ath_txchainmask_reduction(sc,
+- common->tx_chainmask, series[0].Rate);
++ ah->txchainmask, series[0].Rate);
+ series[0].RateFlags = (ctsrate) ? ATH9K_RATESERIES_RTS_CTS : 0;
+ ath9k_hw_set11n_ratescenario(ah, ds, ds, 0, ctsrate, ctsduration,
+ series, 4, 0);
+--- a/drivers/net/wireless/ath/ath9k/debug.c
++++ b/drivers/net/wireless/ath/ath9k/debug.c
+@@ -95,11 +95,11 @@ static ssize_t read_file_tx_chainmask(st
+ size_t count, loff_t *ppos)
+ {
+ struct ath_softc *sc = file->private_data;
+- struct ath_common *common = ath9k_hw_common(sc->sc_ah);
++ struct ath_hw *ah = sc->sc_ah;
+ char buf[32];
+ unsigned int len;
+
+- len = sprintf(buf, "0x%08x\n", common->tx_chainmask);
++ len = sprintf(buf, "0x%08x\n", ah->txchainmask);
+ return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+ }
+
+@@ -107,7 +107,7 @@ static ssize_t write_file_tx_chainmask(s
+ size_t count, loff_t *ppos)
+ {
+ struct ath_softc *sc = file->private_data;
+- struct ath_common *common = ath9k_hw_common(sc->sc_ah);
++ struct ath_hw *ah = sc->sc_ah;
+ unsigned long mask;
+ char buf[32];
+ ssize_t len;
+@@ -120,8 +120,8 @@ static ssize_t write_file_tx_chainmask(s
+ if (strict_strtoul(buf, 0, &mask))
+ return -EINVAL;
+
+- common->tx_chainmask = mask;
+- sc->sc_ah->caps.tx_chainmask = mask;
++ ah->txchainmask = mask;
++ ah->caps.tx_chainmask = mask;
+ return count;
+ }
+
+@@ -138,11 +138,11 @@ static ssize_t read_file_rx_chainmask(st
+ size_t count, loff_t *ppos)
+ {
+ struct ath_softc *sc = file->private_data;
+- struct ath_common *common = ath9k_hw_common(sc->sc_ah);
++ struct ath_hw *ah = sc->sc_ah;
+ char buf[32];
+ unsigned int len;
+
+- len = sprintf(buf, "0x%08x\n", common->rx_chainmask);
++ len = sprintf(buf, "0x%08x\n", ah->rxchainmask);
+ return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+ }
+
+@@ -150,7 +150,7 @@ static ssize_t write_file_rx_chainmask(s
+ size_t count, loff_t *ppos)
+ {
+ struct ath_softc *sc = file->private_data;
+- struct ath_common *common = ath9k_hw_common(sc->sc_ah);
++ struct ath_hw *ah = sc->sc_ah;
+ unsigned long mask;
+ char buf[32];
+ ssize_t len;
+@@ -163,8 +163,8 @@ static ssize_t write_file_rx_chainmask(s
+ if (strict_strtoul(buf, 0, &mask))
+ return -EINVAL;
+
+- common->rx_chainmask = mask;
+- sc->sc_ah->caps.rx_chainmask = mask;
++ ah->rxchainmask = mask;
++ ah->caps.rx_chainmask = mask;
+ return count;
+ }
+
+--- a/drivers/net/wireless/ath/ath9k/htc_drv_init.c
++++ b/drivers/net/wireless/ath/ath9k/htc_drv_init.c
+@@ -509,8 +509,8 @@ static void setup_ht_cap(struct ath9k_ht
+ memset(&ht_info->mcs, 0, sizeof(ht_info->mcs));
+
+ /* ath9k_htc supports only 1 or 2 stream devices */
+- tx_streams = ath9k_cmn_count_streams(common->tx_chainmask, 2);
+- rx_streams = ath9k_cmn_count_streams(common->rx_chainmask, 2);
++ tx_streams = ath9k_cmn_count_streams(priv->ah->txchainmask, 2);
++ rx_streams = ath9k_cmn_count_streams(priv->ah->rxchainmask, 2);
+
+ ath_dbg(common, ATH_DBG_CONFIG,
+ "TX streams %d, RX streams: %d\n",
+@@ -601,9 +601,6 @@ static void ath9k_init_misc(struct ath9k
+ {
+ 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;
+-
+ memcpy(common->bssidmask, ath_bcast_mac, ETH_ALEN);
+
+ priv->ah->opmode = NL80211_IFTYPE_STATION;
+--- a/drivers/net/wireless/ath/ath9k/htc_drv_main.c
++++ b/drivers/net/wireless/ath/ath9k/htc_drv_main.c
+@@ -826,8 +826,7 @@ void ath9k_htc_ani_work(struct work_stru
+ if (longcal || shortcal)
+ common->ani.caldone =
+ ath9k_hw_calibrate(ah, ah->curchan,
+- common->rx_chainmask,
+- longcal);
++ ah->rxchainmask, longcal);
+
+ ath9k_htc_ps_restore(priv);
+ }
+--- a/drivers/net/wireless/ath/ath9k/init.c
++++ b/drivers/net/wireless/ath/ath9k/init.c
+@@ -270,8 +270,8 @@ static void setup_ht_cap(struct ath_soft
+
+ /* set up supported mcs set */
+ memset(&ht_info->mcs, 0, sizeof(ht_info->mcs));
+- tx_streams = ath9k_cmn_count_streams(common->tx_chainmask, max_streams);
+- rx_streams = ath9k_cmn_count_streams(common->rx_chainmask, max_streams);
++ tx_streams = ath9k_cmn_count_streams(ah->txchainmask, max_streams);
++ rx_streams = ath9k_cmn_count_streams(ah->rxchainmask, max_streams);
+
+ ath_dbg(common, ATH_DBG_CONFIG,
+ "TX streams %d, RX streams: %d\n",
+@@ -506,9 +506,6 @@ static void ath9k_init_misc(struct ath_s
+ sc->sc_flags |= SC_OP_RXAGGR;
+ }
+
+- common->tx_chainmask = sc->sc_ah->caps.tx_chainmask;
+- common->rx_chainmask = sc->sc_ah->caps.rx_chainmask;
+-
+ ath9k_hw_set_diversity(sc->sc_ah, true);
+ sc->rx.defant = ath9k_hw_getdefantenna(sc->sc_ah);
+
+@@ -645,10 +642,8 @@ static void ath9k_init_band_txpower(stru
+ static void ath9k_init_txpower_limits(struct ath_softc *sc)
+ {
+ struct ath_hw *ah = sc->sc_ah;
+- struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+ struct ath9k_channel *curchan = ah->curchan;
+
+- ah->txchainmask = common->tx_chainmask;
+ if (ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ)
+ ath9k_init_band_txpower(sc, IEEE80211_BAND_2GHZ);
+ if (ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ)
diff --git a/package/mac80211/patches/581-ath9k_merge_reset_functions.patch b/package/mac80211/patches/581-ath9k_merge_reset_functions.patch
new file mode 100644
index 0000000000..201994db49
--- /dev/null
+++ b/package/mac80211/patches/581-ath9k_merge_reset_functions.patch
@@ -0,0 +1,343 @@
+--- a/drivers/net/wireless/ath/ath9k/main.c
++++ b/drivers/net/wireless/ath/ath9k/main.c
+@@ -212,83 +212,39 @@ 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)
+ {
+ 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;
+-
+- /* 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;
++ 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)
++{
++ 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,
+@@ -299,18 +255,84 @@ static int ath_set_channel(struct ath_so
+ if (!(sc->sc_flags & (SC_OP_OFFCHANNEL))) {
+ 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;
++ int r;
++
++ if (!(sc->sc_flags & SC_OP_OFFCHANNEL)) {
++ fastcc = false;
++ caldata = &sc->caldata;
++ }
+
++ if (!hchan) {
++ fastcc = false;
++ hchan = ah->curchan;
++ }
++
++ if (fastcc && !ath9k_hw_check_alive(ah))
++ fastcc = false;
++
++ if (!ath_prepare_reset(sc, retry_tx))
++ 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))
++ 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 +915,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);
+
+ /* 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);
+@@ -942,13 +949,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);
+
+ if (!ah->curchan)
+ ah->curchan = ath9k_cmn_get_curchannel(hw, ah);
+@@ -970,48 +971,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 +988,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 +1039,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 +1061,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)) {
++ 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/582-ath9k_antdiv_comb_cb.patch b/package/mac80211/patches/582-ath9k_antdiv_comb_cb.patch
new file mode 100644
index 0000000000..92c8ea4b8b
--- /dev/null
+++ b/package/mac80211/patches/582-ath9k_antdiv_comb_cb.patch
@@ -0,0 +1,50 @@
+--- a/drivers/net/wireless/ath/ath9k/hw-ops.h
++++ b/drivers/net/wireless/ath/ath9k/hw-ops.h
+@@ -121,13 +121,15 @@ static inline void ath9k_hw_set_clrdmask
+ static inline void ath9k_hw_antdiv_comb_conf_get(struct ath_hw *ah,
+ struct ath_hw_antcomb_conf *antconf)
+ {
+- ath9k_hw_ops(ah)->antdiv_comb_conf_get(ah, antconf);
++ if (ath9k_hw_ops(ah)->antdiv_comb_conf_get)
++ ath9k_hw_ops(ah)->antdiv_comb_conf_get(ah, antconf);
+ }
+
+ static inline void ath9k_hw_antdiv_comb_conf_set(struct ath_hw *ah,
+ struct ath_hw_antcomb_conf *antconf)
+ {
+- ath9k_hw_ops(ah)->antdiv_comb_conf_set(ah, antconf);
++ if (ath9k_hw_ops(ah)->antdiv_comb_conf_set)
++ ath9k_hw_ops(ah)->antdiv_comb_conf_set(ah, antconf);
+ }
+
+ /* Private hardware call ops */
+--- a/drivers/net/wireless/ath/ath9k/ar9002_phy.c
++++ b/drivers/net/wireless/ath/ath9k/ar9002_phy.c
+@@ -570,8 +570,10 @@ void ar9002_hw_attach_phy_ops(struct ath
+ priv_ops->compute_pll_control = ar9002_hw_compute_pll_control;
+ priv_ops->do_getnf = ar9002_hw_do_getnf;
+
+- ops->antdiv_comb_conf_get = ar9002_hw_antdiv_comb_conf_get;
+- ops->antdiv_comb_conf_set = ar9002_hw_antdiv_comb_conf_set;
++ if (AR_SREV_9285(ah)) {
++ ops->antdiv_comb_conf_get = ar9002_hw_antdiv_comb_conf_get;
++ ops->antdiv_comb_conf_set = ar9002_hw_antdiv_comb_conf_set;
++ }
+
+ ar9002_hw_set_nf_limits(ah);
+ }
+--- a/drivers/net/wireless/ath/ath9k/ar9003_phy.c
++++ b/drivers/net/wireless/ath/ath9k/ar9003_phy.c
+@@ -1283,8 +1283,10 @@ void ar9003_hw_attach_phy_ops(struct ath
+ priv_ops->ani_cache_ini_regs = ar9003_hw_ani_cache_ini_regs;
+ priv_ops->set_radar_params = ar9003_hw_set_radar_params;
+
+- ops->antdiv_comb_conf_get = ar9003_hw_antdiv_comb_conf_get;
+- ops->antdiv_comb_conf_set = ar9003_hw_antdiv_comb_conf_set;
++ if (AR_SREV_9330(ah) || AR_SREV_9485(ah)) {
++ ops->antdiv_comb_conf_get = ar9003_hw_antdiv_comb_conf_get;
++ ops->antdiv_comb_conf_set = ar9003_hw_antdiv_comb_conf_set;
++ }
+
+ ar9003_hw_set_nf_limits(ah);
+ ar9003_hw_set_radar_conf(ah);
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..a1caa8aaa9
--- /dev/null
+++ b/package/mac80211/patches/583-ath9k_antenna_control.patch
@@ -0,0 +1,179 @@
+--- 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,17 @@ 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.max_rxchains == 1 &&
++ ath9k_hw_ops(ah)->antdiv_comb_conf_get)
++ hw->wiphy->available_antennas_rx = 3;
++
++ 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 +727,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;
++ u32 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
+@@ -262,6 +262,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;
+@@ -2360,6 +2376,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,
+@@ -2386,4 +2455,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);