From bbf2a906b6bde84e2b76d220c5f30ec3b52232fc Mon Sep 17 00:00:00 2001
From: Felix Fietkau <nbd@openwrt.org>
Date: Sat, 9 Oct 2010 00:36:54 +0000
Subject: ath9k: implement extended channel utilization statistics via survey

SVN-Revision: 23350
---
 .../patches/520-ath9k_common_clockrate.patch       | 116 +++++++
 .../patches/521-ath5k_common_clockrate.patch       |  65 ++++
 .../mac80211/patches/522-ath_common_counters.patch | 347 +++++++++++++++++++++
 .../patches/523-ath5k_use_common_counters.patch    | 104 ++++++
 .../524-mac80211_survey_channel_stats.patch        | 105 +++++++
 .../patches/525-ath9k_channel_count_check.patch    |  33 ++
 .../patches/526-ath9k_survey_channel_stats.patch   | 165 ++++++++++
 7 files changed, 935 insertions(+)
 create mode 100644 package/mac80211/patches/520-ath9k_common_clockrate.patch
 create mode 100644 package/mac80211/patches/521-ath5k_common_clockrate.patch
 create mode 100644 package/mac80211/patches/522-ath_common_counters.patch
 create mode 100644 package/mac80211/patches/523-ath5k_use_common_counters.patch
 create mode 100644 package/mac80211/patches/524-mac80211_survey_channel_stats.patch
 create mode 100644 package/mac80211/patches/525-ath9k_channel_count_check.patch
 create mode 100644 package/mac80211/patches/526-ath9k_survey_channel_stats.patch

(limited to 'package/mac80211/patches')

diff --git a/package/mac80211/patches/520-ath9k_common_clockrate.patch b/package/mac80211/patches/520-ath9k_common_clockrate.patch
new file mode 100644
index 0000000..3266e0e
--- /dev/null
+++ b/package/mac80211/patches/520-ath9k_common_clockrate.patch
@@ -0,0 +1,116 @@
+--- a/drivers/net/wireless/ath/ath.h
++++ b/drivers/net/wireless/ath/ath.h
+@@ -145,6 +145,8 @@ struct ath_common {
+ 	DECLARE_BITMAP(tkip_keymap, ATH_KEYMAX);
+ 	enum ath_crypt_caps crypt_caps;
+ 
++	unsigned int clockrate;
++
+ 	struct ath_regulatory regulatory;
+ 	const struct ath_ops *ops;
+ 	const struct ath_bus_ops *bus_ops;
+--- a/drivers/net/wireless/ath/ath9k/hw.c
++++ b/drivers/net/wireless/ath/ath9k/hw.c
+@@ -91,29 +91,32 @@ static void ath9k_hw_ani_cache_ini_regs(
+ /* Helper Functions */
+ /********************/
+ 
+-static u32 ath9k_hw_mac_clks(struct ath_hw *ah, u32 usecs)
++static void ath9k_hw_set_clockrate(struct ath_hw *ah)
+ {
+ 	struct ieee80211_conf *conf = &ath9k_hw_common(ah)->hw->conf;
++	struct ath_common *common = ath9k_hw_common(ah);
++	unsigned int clockrate;
+ 
+ 	if (!ah->curchan) /* should really check for CCK instead */
+-		return usecs *ATH9K_CLOCK_RATE_CCK;
+-	if (conf->channel->band == IEEE80211_BAND_2GHZ)
+-		return usecs *ATH9K_CLOCK_RATE_2GHZ_OFDM;
+-
+-	if (ah->caps.hw_caps & ATH9K_HW_CAP_FASTCLOCK)
+-		return usecs * ATH9K_CLOCK_FAST_RATE_5GHZ_OFDM;
++		clockrate = ATH9K_CLOCK_RATE_CCK;
++	else if (conf->channel->band == IEEE80211_BAND_2GHZ)
++		clockrate = ATH9K_CLOCK_RATE_2GHZ_OFDM;
++	else if (ah->caps.hw_caps & ATH9K_HW_CAP_FASTCLOCK)
++		clockrate = ATH9K_CLOCK_FAST_RATE_5GHZ_OFDM;
+ 	else
+-		return usecs * ATH9K_CLOCK_RATE_5GHZ_OFDM;
++		clockrate = ATH9K_CLOCK_RATE_5GHZ_OFDM;
++
++	if (conf_is_ht40(conf))
++		clockrate *= 2;
++
++	common->clockrate = clockrate;
+ }
+ 
+ static u32 ath9k_hw_mac_to_clks(struct ath_hw *ah, u32 usecs)
+ {
+-	struct ieee80211_conf *conf = &ath9k_hw_common(ah)->hw->conf;
++	struct ath_common *common = ath9k_hw_common(ah);
+ 
+-	if (conf_is_ht40(conf))
+-		return ath9k_hw_mac_clks(ah, usecs) * 2;
+-	else
+-		return ath9k_hw_mac_clks(ah, usecs);
++	return usecs * common->clockrate;
+ }
+ 
+ bool ath9k_hw_wait(struct ath_hw *ah, u32 reg, u32 mask, u32 val, u32 timeout)
+@@ -1168,6 +1171,7 @@ static bool ath9k_hw_channel_change(stru
+ 			  "Failed to set channel\n");
+ 		return false;
+ 	}
++	ath9k_hw_set_clockrate(ah);
+ 
+ 	ah->eep_ops->set_txpower(ah, chan,
+ 			     ath9k_regd_get_ctl(regulatory, chan),
+@@ -1380,6 +1384,8 @@ int ath9k_hw_reset(struct ath_hw *ah, st
+ 	if (r)
+ 		return r;
+ 
++	ath9k_hw_set_clockrate(ah);
++
+ 	ENABLE_REGWRITE_BUFFER(ah);
+ 
+ 	for (i = 0; i < AR_NUM_DCU; i++)
+--- a/drivers/net/wireless/ath/ath9k/ani.c
++++ b/drivers/net/wireless/ath/ath9k/ani.c
+@@ -465,35 +465,13 @@ static void ath9k_hw_ani_lower_immunity(
+ 		ath9k_hw_set_cck_nil(ah, aniState->cckNoiseImmunityLevel - 1);
+ }
+ 
+-static u8 ath9k_hw_chan_2_clockrate_mhz(struct ath_hw *ah)
+-{
+-	struct ath9k_channel *chan = ah->curchan;
+-	struct ieee80211_conf *conf = &ath9k_hw_common(ah)->hw->conf;
+-	u8 clockrate; /* in MHz */
+-
+-	if (!ah->curchan) /* should really check for CCK instead */
+-		clockrate = ATH9K_CLOCK_RATE_CCK;
+-	else if (conf->channel->band == IEEE80211_BAND_2GHZ)
+-		clockrate = ATH9K_CLOCK_RATE_2GHZ_OFDM;
+-	else if (IS_CHAN_A_FAST_CLOCK(ah, chan))
+-		clockrate = ATH9K_CLOCK_FAST_RATE_5GHZ_OFDM;
+-	else
+-		clockrate = ATH9K_CLOCK_RATE_5GHZ_OFDM;
+-
+-	if (conf_is_ht40(conf))
+-		return clockrate * 2;
+-
+-	return clockrate;
+-}
+-
+ static int32_t ath9k_hw_ani_get_listen_time(struct ath_hw *ah)
+ {
++	struct ath_common *common = ath9k_hw_common(ah);
+ 	int32_t listen_time;
+-	int32_t clock_rate;
+ 
+ 	ath9k_hw_update_cycle_counters(ah);
+-	clock_rate = ath9k_hw_chan_2_clockrate_mhz(ah) * 1000;
+-	listen_time = ah->listen_time / clock_rate;
++	listen_time = ah->listen_time / (common->clockrate * 1000);
+ 	ah->listen_time = 0;
+ 
+ 	return listen_time;
diff --git a/package/mac80211/patches/521-ath5k_common_clockrate.patch b/package/mac80211/patches/521-ath5k_common_clockrate.patch
new file mode 100644
index 0000000..08c1834
--- /dev/null
+++ b/package/mac80211/patches/521-ath5k_common_clockrate.patch
@@ -0,0 +1,65 @@
+--- a/drivers/net/wireless/ath/ath5k/pcu.c
++++ b/drivers/net/wireless/ath/ath5k/pcu.c
+@@ -207,7 +207,8 @@ static int ath5k_hw_set_cts_timeout(stru
+  */
+ unsigned int ath5k_hw_htoclock(struct ath5k_hw *ah, unsigned int usec)
+ {
+-	return usec * ath5k_hw_get_clockrate(ah);
++	struct ath_common *common = ath5k_hw_common(ah);
++	return usec * common->clockrate;
+ }
+ 
+ /**
+@@ -216,17 +217,19 @@ unsigned int ath5k_hw_htoclock(struct at
+  */
+ unsigned int ath5k_hw_clocktoh(struct ath5k_hw *ah, unsigned int clock)
+ {
+-	return clock / ath5k_hw_get_clockrate(ah);
++	struct ath_common *common = ath5k_hw_common(ah);
++	return clock / common->clockrate;
+ }
+ 
+ /**
+- * ath5k_hw_get_clockrate - Get the clock rate for current mode
++ * ath5k_hw_set_clockrate - Set common->clockrate for the current channel
+  *
+  * @ah: The &struct ath5k_hw
+  */
+-unsigned int ath5k_hw_get_clockrate(struct ath5k_hw *ah)
++void ath5k_hw_set_clockrate(struct ath5k_hw *ah)
+ {
+ 	struct ieee80211_channel *channel = ah->ah_current_channel;
++	struct ath_common *common = ath5k_hw_common(ah);
+ 	int clock;
+ 
+ 	if (channel->hw_value & CHANNEL_5GHZ)
+@@ -240,7 +243,7 @@ unsigned int ath5k_hw_get_clockrate(stru
+ 	if (channel->hw_value & CHANNEL_TURBO)
+ 		clock *= 2;
+ 
+-	return clock;
++	common->clockrate = clock;
+ }
+ 
+ /**
+--- a/drivers/net/wireless/ath/ath5k/ath5k.h
++++ b/drivers/net/wireless/ath/ath5k/ath5k.h
+@@ -1201,7 +1201,7 @@ void ath5k_hw_set_ack_bitrate_high(struc
+ /* Clock rate related functions */
+ unsigned int ath5k_hw_htoclock(struct ath5k_hw *ah, unsigned int usec);
+ unsigned int ath5k_hw_clocktoh(struct ath5k_hw *ah, unsigned int clock);
+-unsigned int ath5k_hw_get_clockrate(struct ath5k_hw *ah);
++void ath5k_hw_set_clockrate(struct ath5k_hw *ah);
+ 
+ /* Queue Control Unit, DFS Control Unit Functions */
+ int ath5k_hw_get_tx_queueprops(struct ath5k_hw *ah, int queue,
+--- a/drivers/net/wireless/ath/ath5k/phy.c
++++ b/drivers/net/wireless/ath/ath5k/phy.c
+@@ -1093,6 +1093,7 @@ int ath5k_hw_channel(struct ath5k_hw *ah
+ 
+ 	ah->ah_current_channel = channel;
+ 	ah->ah_turbo = channel->hw_value == CHANNEL_T ? true : false;
++	ath5k_hw_set_clockrate(ah);
+ 
+ 	return 0;
+ }
diff --git a/package/mac80211/patches/522-ath_common_counters.patch b/package/mac80211/patches/522-ath_common_counters.patch
new file mode 100644
index 0000000..f719575
--- /dev/null
+++ b/package/mac80211/patches/522-ath_common_counters.patch
@@ -0,0 +1,347 @@
+--- a/drivers/net/wireless/ath/ath.h
++++ b/drivers/net/wireless/ath/ath.h
+@@ -19,6 +19,7 @@
+ 
+ #include <linux/skbuff.h>
+ #include <linux/if_ether.h>
++#include <linux/spinlock.h>
+ #include <net/mac80211.h>
+ 
+ /*
+@@ -42,6 +43,13 @@ struct ath_ani {
+ 	struct timer_list timer;
+ };
+ 
++struct ath_cycle_counters {
++	u32 cycles;
++	u32 rx_busy;
++	u32 rx_frame;
++	u32 tx_frame;
++};
++
+ enum ath_device_state {
+ 	ATH_HW_UNAVAILABLE,
+ 	ATH_HW_INITIALIZED,
+@@ -147,6 +155,10 @@ struct ath_common {
+ 
+ 	unsigned int clockrate;
+ 
++	spinlock_t cc_lock;
++	struct ath_cycle_counters cc_ani;
++	struct ath_cycle_counters cc_survey;
++
+ 	struct ath_regulatory regulatory;
+ 	const struct ath_ops *ops;
+ 	const struct ath_bus_ops *bus_ops;
+@@ -163,5 +175,7 @@ int ath_key_config(struct ath_common *co
+ 			  struct ieee80211_sta *sta,
+ 			  struct ieee80211_key_conf *key);
+ bool ath_hw_keyreset(struct ath_common *common, u16 entry);
++void ath_hw_cycle_counters_update(struct ath_common *common);
++int32_t ath_hw_get_listen_time(struct ath_common *common);
+ 
+ #endif /* ATH_H */
+--- a/drivers/net/wireless/ath/ath9k/ani.c
++++ b/drivers/net/wireless/ath/ath9k/ani.c
+@@ -465,18 +465,6 @@ static void ath9k_hw_ani_lower_immunity(
+ 		ath9k_hw_set_cck_nil(ah, aniState->cckNoiseImmunityLevel - 1);
+ }
+ 
+-static int32_t ath9k_hw_ani_get_listen_time(struct ath_hw *ah)
+-{
+-	struct ath_common *common = ath9k_hw_common(ah);
+-	int32_t listen_time;
+-
+-	ath9k_hw_update_cycle_counters(ah);
+-	listen_time = ah->listen_time / (common->clockrate * 1000);
+-	ah->listen_time = 0;
+-
+-	return listen_time;
+-}
+-
+ static void ath9k_ani_reset_old(struct ath_hw *ah, bool is_scanning)
+ {
+ 	struct ar5416AniState *aniState;
+@@ -655,7 +643,9 @@ static void ath9k_hw_ani_read_counters(s
+ 	u32 phyCnt1, phyCnt2;
+ 	int32_t listenTime;
+ 
+-	listenTime = ath9k_hw_ani_get_listen_time(ah);
++	ath_hw_cycle_counters_update(common);
++	listenTime = ath_hw_get_listen_time(common);
++
+ 	if (listenTime < 0) {
+ 		ah->stats.ast_ani_lneg++;
+ 		ath9k_ani_restart(ah);
+@@ -796,54 +786,6 @@ void ath9k_hw_disable_mib_counters(struc
+ }
+ EXPORT_SYMBOL(ath9k_hw_disable_mib_counters);
+ 
+-void ath9k_hw_update_cycle_counters(struct ath_hw *ah)
+-{
+-	struct ath_cycle_counters cc;
+-	bool clear;
+-
+-	memcpy(&cc, &ah->cc, sizeof(cc));
+-
+-	/* freeze counters */
+-	REG_WRITE(ah, AR_MIBC, AR_MIBC_FMC);
+-
+-	ah->cc.cycles = REG_READ(ah, AR_CCCNT);
+-	if (ah->cc.cycles < cc.cycles) {
+-		clear = true;
+-		goto skip;
+-	}
+-
+-	ah->cc.rx_clear = REG_READ(ah, AR_RCCNT);
+-	ah->cc.rx_frame = REG_READ(ah, AR_RFCNT);
+-	ah->cc.tx_frame = REG_READ(ah, AR_TFCNT);
+-
+-	/* prevent wraparound */
+-	if (ah->cc.cycles & BIT(31))
+-		clear = true;
+-
+-#define CC_DELTA(_field, _reg) ah->cc_delta._field += ah->cc._field - cc._field
+-	CC_DELTA(cycles, AR_CCCNT);
+-	CC_DELTA(rx_frame, AR_RFCNT);
+-	CC_DELTA(rx_clear, AR_RCCNT);
+-	CC_DELTA(tx_frame, AR_TFCNT);
+-#undef CC_DELTA
+-
+-	ah->listen_time += (ah->cc.cycles - cc.cycles) -
+-		 ((ah->cc.rx_frame - cc.rx_frame) +
+-		  (ah->cc.tx_frame - cc.tx_frame));
+-
+-skip:
+-	if (clear) {
+-		REG_WRITE(ah, AR_CCCNT, 0);
+-		REG_WRITE(ah, AR_RFCNT, 0);
+-		REG_WRITE(ah, AR_RCCNT, 0);
+-		REG_WRITE(ah, AR_TFCNT, 0);
+-		memset(&ah->cc, 0, sizeof(ah->cc));
+-	}
+-
+-	/* unfreeze counters */
+-	REG_WRITE(ah, AR_MIBC, 0);
+-}
+-
+ /*
+  * Process a MIB interrupt.  We may potentially be invoked because
+  * any of the MIB counters overflow/trigger so don't assume we're
+--- a/drivers/net/wireless/ath/ath9k/ani.h
++++ b/drivers/net/wireless/ath/ath9k/ani.h
+@@ -93,13 +93,6 @@ struct ath9k_mib_stats {
+ 	u32 beacons;
+ };
+ 
+-struct ath_cycle_counters {
+-	u32 cycles;
+-	u32 rx_frame;
+-	u32 rx_clear;
+-	u32 tx_frame;
+-};
+-
+ /* INI default values for ANI registers */
+ struct ath9k_ani_default {
+ 	u16 m1ThreshLow;
+@@ -164,7 +157,6 @@ struct ar5416Stats {
+ 
+ void ath9k_enable_mib_counters(struct ath_hw *ah);
+ void ath9k_hw_disable_mib_counters(struct ath_hw *ah);
+-void ath9k_hw_update_cycle_counters(struct ath_hw *ah);
+ void ath9k_hw_ani_setup(struct ath_hw *ah);
+ void ath9k_hw_ani_init(struct ath_hw *ah);
+ int ath9k_hw_get_ani_channel_idx(struct ath_hw *ah,
+--- a/drivers/net/wireless/ath/ath9k/ar9003_phy.c
++++ b/drivers/net/wireless/ath/ath9k/ar9003_phy.c
+@@ -1254,13 +1254,12 @@ void ar9003_hw_bb_watchdog_dbg_info(stru
+ 		  "** BB mode: BB_gen_controls=0x%08x **\n",
+ 		  REG_READ(ah, AR_PHY_GEN_CTRL));
+ 
+-	ath9k_hw_update_cycle_counters(ah);
+-#define PCT(_field) (ah->cc_delta._field * 100 / ah->cc_delta.cycles)
+-	if (ah->cc_delta.cycles)
++#define PCT(_field) (common->cc_survey._field * 100 / common->cc_survey.cycles)
++	if (common->cc_survey.cycles)
+ 		ath_print(common, ATH_DBG_RESET,
+ 			  "** BB busy times: rx_clear=%d%%, "
+ 			  "rx_frame=%d%%, tx_frame=%d%% **\n",
+-			  PCT(rx_clear), PCT(rx_frame), PCT(tx_frame));
++			  PCT(rx_busy), PCT(rx_frame), PCT(tx_frame));
+ 
+ 	ath_print(common, ATH_DBG_RESET,
+ 		  "==== BB update: done ====\n\n");
+--- a/drivers/net/wireless/ath/ath9k/hw.h
++++ b/drivers/net/wireless/ath/ath9k/hw.h
+@@ -740,8 +740,6 @@ struct ath_hw {
+ 	int coarse_low[5];
+ 	int firpwr[5];
+ 	enum ath9k_ani_cmd ani_function;
+-	struct ath_cycle_counters cc, cc_delta;
+-	int32_t listen_time;
+ 
+ 	/* Bluetooth coexistance */
+ 	struct ath_btcoex_hw btcoex_hw;
+--- a/drivers/net/wireless/ath/ath9k/main.c
++++ b/drivers/net/wireless/ath/ath9k/main.c
+@@ -400,6 +400,7 @@ void ath_ani_calibrate(unsigned long dat
+ 	bool aniflag = false;
+ 	unsigned int timestamp = jiffies_to_msecs(jiffies);
+ 	u32 cal_interval, short_cal_interval, long_cal_interval;
++	unsigned long flags;
+ 
+ 	if (ah->caldata && ah->caldata->nfcal_interference)
+ 		long_cal_interval = ATH_LONG_CALINTERVAL_INT;
+@@ -450,8 +451,11 @@ void ath_ani_calibrate(unsigned long dat
+ 	/* Skip all processing if there's nothing to do. */
+ 	if (longcal || shortcal || aniflag) {
+ 		/* Call ANI routine if necessary */
+-		if (aniflag)
++		if (aniflag) {
++			spin_lock_irqsave(&common->cc_lock, flags);
+ 			ath9k_hw_ani_monitor(ah, ah->curchan);
++			spin_unlock_irqrestore(&common->cc_lock, flags);
++		}
+ 
+ 		/* Perform calibration if necessary */
+ 		if (longcal || shortcal) {
+@@ -636,6 +640,7 @@ irqreturn_t ath_isr(int irq, void *dev)
+ 
+ 	struct ath_softc *sc = dev;
+ 	struct ath_hw *ah = sc->sc_ah;
++	struct ath_common *common = ath9k_hw_common(ah);
+ 	enum ath9k_int status;
+ 	bool sched = false;
+ 
+@@ -685,7 +690,12 @@ irqreturn_t ath_isr(int irq, void *dev)
+ 
+ 	if ((ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) &&
+ 	    (status & ATH9K_INT_BB_WATCHDOG)) {
++
++		spin_lock(&common->cc_lock);
++		ath_hw_cycle_counters_update(common);
+ 		ar9003_hw_bb_watchdog_dbg_info(ah);
++		spin_unlock(&common->cc_lock);
++
+ 		goto chip_reset;
+ 	}
+ 
+--- a/drivers/net/wireless/ath/ath9k/reg.h
++++ b/drivers/net/wireless/ath/ath9k/reg.h
+@@ -107,12 +107,6 @@
+ #define AR_RXCFG_DMASZ_256B  6
+ #define AR_RXCFG_DMASZ_512B  7
+ 
+-#define AR_MIBC              0x0040
+-#define AR_MIBC_COW          0x00000001
+-#define AR_MIBC_FMC          0x00000002
+-#define AR_MIBC_CMC          0x00000004
+-#define AR_MIBC_MCS          0x00000008
+-
+ #define AR_TOPS              0x0044
+ #define AR_TOPS_MASK         0x0000FFFF
+ 
+@@ -1524,11 +1518,6 @@ enum {
+ #define AR_TPC_CHIRP           0x003f0000
+ #define AR_TPC_CHIRP_S         0x16
+ 
+-#define AR_TFCNT           0x80ec
+-#define AR_RFCNT           0x80f0
+-#define AR_RCCNT           0x80f4
+-#define AR_CCCNT           0x80f8
+-
+ #define AR_QUIET1          0x80fc
+ #define AR_QUIET1_NEXT_QUIET_S         0
+ #define AR_QUIET1_NEXT_QUIET_M         0x0000ffff
+--- a/drivers/net/wireless/ath/hw.c
++++ b/drivers/net/wireless/ath/hw.c
+@@ -124,3 +124,62 @@ void ath_hw_setbssidmask(struct ath_comm
+ 	REG_WRITE(ah, get_unaligned_le16(common->bssidmask + 4), AR_BSSMSKU);
+ }
+ EXPORT_SYMBOL(ath_hw_setbssidmask);
++
++
++/**
++ * ath_hw_cycle_counters_update - common function to update cycle counters
++ *
++ * @common: the ath_common struct for the device.
++ *
++ * This function is used to update all cycle counters in one place.
++ * It has to be called while holding common->cc_lock!
++ */
++void ath_hw_cycle_counters_update(struct ath_common *common)
++{
++	u32 cycles, busy, rx, tx;
++	void *ah = common->ah;
++
++	/* freeze */
++	REG_WRITE(ah, AR_MIBC_FMC, AR_MIBC);
++
++	/* read */
++	cycles = REG_READ(ah, AR_CCCNT);
++	busy = REG_READ(ah, AR_RCCNT);
++	rx = REG_READ(ah, AR_RFCNT);
++	tx = REG_READ(ah, AR_TFCNT);
++
++	/* clear */
++	REG_WRITE(ah, 0, AR_CCCNT);
++	REG_WRITE(ah, 0, AR_RFCNT);
++	REG_WRITE(ah, 0, AR_RCCNT);
++	REG_WRITE(ah, 0, AR_TFCNT);
++
++	/* unfreeze */
++	REG_WRITE(ah, 0, AR_MIBC);
++
++	/* update all cycle counters here */
++	common->cc_ani.cycles += cycles;
++	common->cc_ani.rx_busy += busy;
++	common->cc_ani.rx_frame += rx;
++	common->cc_ani.tx_frame += tx;
++
++	common->cc_survey.cycles += cycles;
++	common->cc_survey.rx_busy += busy;
++	common->cc_survey.rx_frame += rx;
++	common->cc_survey.tx_frame += tx;
++}
++EXPORT_SYMBOL(ath_hw_cycle_counters_update);
++
++int32_t ath_hw_get_listen_time(struct ath_common *common)
++{
++	struct ath_cycle_counters *cc = &common->cc_ani;
++	int32_t listen_time;
++
++	listen_time = (cc->cycles - cc->rx_frame - cc->tx_frame) /
++		      (common->clockrate * 1000);
++
++	memset(cc, 0, sizeof(*cc));
++
++	return listen_time;
++}
++EXPORT_SYMBOL(ath_hw_get_listen_time);
+--- a/drivers/net/wireless/ath/reg.h
++++ b/drivers/net/wireless/ath/reg.h
+@@ -17,6 +17,12 @@
+ #ifndef ATH_REGISTERS_H
+ #define ATH_REGISTERS_H
+ 
++#define AR_MIBC			0x0040
++#define AR_MIBC_COW		0x00000001
++#define AR_MIBC_FMC		0x00000002
++#define AR_MIBC_CMC		0x00000004
++#define AR_MIBC_MCS		0x00000008
++
+ /*
+  * BSSID mask registers. See ath_hw_set_bssid_mask()
+  * for detailed documentation about these registers.
+@@ -24,6 +30,11 @@
+ #define AR_BSSMSKL		0x80e0
+ #define AR_BSSMSKU		0x80e4
+ 
++#define AR_TFCNT		0x80ec
++#define AR_RFCNT		0x80f0
++#define AR_RCCNT		0x80f4
++#define AR_CCCNT		0x80f8
++
+ #define AR_KEYTABLE_0           0x8800
+ #define AR_KEYTABLE(_n)         (AR_KEYTABLE_0 + ((_n)*32))
+ #define AR_KEY_CACHE_SIZE       128
diff --git a/package/mac80211/patches/523-ath5k_use_common_counters.patch b/package/mac80211/patches/523-ath5k_use_common_counters.patch
new file mode 100644
index 0000000..1899f8d
--- /dev/null
+++ b/package/mac80211/patches/523-ath5k_use_common_counters.patch
@@ -0,0 +1,104 @@
+--- a/drivers/net/wireless/ath/ath5k/ani.c
++++ b/drivers/net/wireless/ath/ath5k/ani.c
+@@ -355,41 +355,28 @@ ath5k_ani_lower_immunity(struct ath5k_hw
+ 
+ 
+ /**
+- * ath5k_hw_ani_get_listen_time() - Calculate time spent listening
++ * ath5k_hw_ani_get_listen_time() - Update counters and return listening time
+  *
+  * Return an approximation of the time spent "listening" in milliseconds (ms)
+- * since the last call of this function by deducting the cycles spent
+- * transmitting and receiving from the total cycle count.
+- * Save profile count values for debugging/statistics and because we might want
+- * to use them later.
+- *
+- * We assume no one else clears these registers!
++ * since the last call of this function.
++ * Save a snapshot of the counter values for debugging/statistics.
+  */
+ static int
+ ath5k_hw_ani_get_listen_time(struct ath5k_hw *ah, struct ath5k_ani_state *as)
+ {
++	struct ath_common *common = ath5k_hw_common(ah);
+ 	int listen;
+ 
+-	/* freeze */
+-	ath5k_hw_reg_write(ah, AR5K_MIBC_FMC, AR5K_MIBC);
+-	/* read */
+-	as->pfc_cycles = ath5k_hw_reg_read(ah, AR5K_PROFCNT_CYCLE);
+-	as->pfc_busy = ath5k_hw_reg_read(ah, AR5K_PROFCNT_RXCLR);
+-	as->pfc_tx = ath5k_hw_reg_read(ah, AR5K_PROFCNT_TX);
+-	as->pfc_rx = ath5k_hw_reg_read(ah, AR5K_PROFCNT_RX);
+-	/* clear */
+-	ath5k_hw_reg_write(ah, 0, AR5K_PROFCNT_TX);
+-	ath5k_hw_reg_write(ah, 0, AR5K_PROFCNT_RX);
+-	ath5k_hw_reg_write(ah, 0, AR5K_PROFCNT_RXCLR);
+-	ath5k_hw_reg_write(ah, 0, AR5K_PROFCNT_CYCLE);
+-	/* un-freeze */
+-	ath5k_hw_reg_write(ah, 0, AR5K_MIBC);
++	spin_lock_bh(&common->cc_lock);
+ 
+-	/* TODO: where does 44000 come from? (11g clock rate?) */
+-	listen = (as->pfc_cycles - as->pfc_rx - as->pfc_tx) / 44000;
++	ath_hw_cycle_counters_update(common);
++	memcpy(&as->last_cc, &common->cc_ani, sizeof(as->last_cc));
++
++	/* clears common->cc_ani */
++	listen = ath_hw_get_listen_time(common);
++
++	spin_unlock_bh(&common->cc_lock);
+ 
+-	if (as->pfc_cycles == 0 || listen < 0)
+-		return 0;
+ 	return listen;
+ }
+ 
+--- a/drivers/net/wireless/ath/ath5k/ani.h
++++ b/drivers/net/wireless/ath/ath5k/ani.h
+@@ -75,10 +75,7 @@ struct ath5k_ani_state {
+ 	unsigned int		cck_errors;
+ 
+ 	/* debug/statistics only: numbers from last ANI calibration */
+-	unsigned int		pfc_tx;
+-	unsigned int		pfc_rx;
+-	unsigned int		pfc_busy;
+-	unsigned int		pfc_cycles;
++	struct ath_cycle_counters last_cc;
+ 	unsigned int		last_listen;
+ 	unsigned int		last_ofdm_errors;
+ 	unsigned int		last_cck_errors;
+--- a/drivers/net/wireless/ath/ath5k/debug.c
++++ b/drivers/net/wireless/ath/ath5k/debug.c
+@@ -715,20 +715,21 @@ static ssize_t read_file_ani(struct file
+ 	len += snprintf(buf+len, sizeof(buf)-len,
+ 			"beacon RSSI average:\t%d\n",
+ 			sc->ah->ah_beacon_rssi_avg.avg);
++
++#define CC_PRINT(_struct, _field) \
++	_struct._field, \
++	_struct.cycles > 0 ? \
++	_struct._field*100/_struct.cycles : 0
++
+ 	len += snprintf(buf+len, sizeof(buf)-len, "profcnt tx\t\t%u\t(%d%%)\n",
+-			as->pfc_tx,
+-			as->pfc_cycles > 0 ?
+-			as->pfc_tx*100/as->pfc_cycles : 0);
++			CC_PRINT(as->last_cc, tx_frame));
+ 	len += snprintf(buf+len, sizeof(buf)-len, "profcnt rx\t\t%u\t(%d%%)\n",
+-			as->pfc_rx,
+-			as->pfc_cycles > 0 ?
+-			as->pfc_rx*100/as->pfc_cycles : 0);
++			CC_PRINT(as->last_cc, rx_frame));
+ 	len += snprintf(buf+len, sizeof(buf)-len, "profcnt busy\t\t%u\t(%d%%)\n",
+-			as->pfc_busy,
+-			as->pfc_cycles > 0 ?
+-			as->pfc_busy*100/as->pfc_cycles : 0);
++			CC_PRINT(as->last_cc, rx_busy));
++#undef CC_PRINT
+ 	len += snprintf(buf+len, sizeof(buf)-len, "profcnt cycles\t\t%u\n",
+-			as->pfc_cycles);
++			as->last_cc.cycles);
+ 	len += snprintf(buf+len, sizeof(buf)-len,
+ 			"listen time\t\t%d\tlast: %d\n",
+ 			as->listen_time, as->last_listen);
diff --git a/package/mac80211/patches/524-mac80211_survey_channel_stats.patch b/package/mac80211/patches/524-mac80211_survey_channel_stats.patch
new file mode 100644
index 0000000..b05484f
--- /dev/null
+++ b/package/mac80211/patches/524-mac80211_survey_channel_stats.patch
@@ -0,0 +1,105 @@
+--- a/include/linux/nl80211.h
++++ b/include/linux/nl80211.h
+@@ -1413,6 +1413,16 @@ enum nl80211_reg_rule_flags {
+  * @NL80211_SURVEY_INFO_FREQUENCY: center frequency of channel
+  * @NL80211_SURVEY_INFO_NOISE: noise level of channel (u8, dBm)
+  * @NL80211_SURVEY_INFO_IN_USE: channel is currently being used
++ * @NL80211_SURVEY_INFO_CHANNEL_TIME: amount of time (in ms) that the radio
++ *	spent on this channel
++ * @NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY: amount of the time the primary
++ *	channel was sensed busy (either due to activity or energy detect)
++ * @NL80211_SURVEY_INFO_CHANNEL_TIME_EXT_BUSY: amount of time the extension
++ *	channel was sensed busy
++ * @NL80211_SURVEY_INFO_CHANNEL_TIME_RX: amount of time the radio spent
++ *	receiving data
++ * @NL80211_SURVEY_INFO_CHANNEL_TIME_TX: amount of time the radio spent
++ *	transmitting data
+  * @NL80211_SURVEY_INFO_MAX: highest survey info attribute number
+  *	currently defined
+  * @__NL80211_SURVEY_INFO_AFTER_LAST: internal use
+@@ -1422,6 +1432,11 @@ enum nl80211_survey_info {
+ 	NL80211_SURVEY_INFO_FREQUENCY,
+ 	NL80211_SURVEY_INFO_NOISE,
+ 	NL80211_SURVEY_INFO_IN_USE,
++	NL80211_SURVEY_INFO_CHANNEL_TIME,
++	NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY,
++	NL80211_SURVEY_INFO_CHANNEL_TIME_EXT_BUSY,
++	NL80211_SURVEY_INFO_CHANNEL_TIME_RX,
++	NL80211_SURVEY_INFO_CHANNEL_TIME_TX,
+ 
+ 	/* keep last */
+ 	__NL80211_SURVEY_INFO_AFTER_LAST,
+--- a/include/net/cfg80211.h
++++ b/include/net/cfg80211.h
+@@ -295,6 +295,11 @@ struct key_params {
+  *
+  * @SURVEY_INFO_NOISE_DBM: noise (in dBm) was filled in
+  * @SURVEY_INFO_IN_USE: channel is currently being used
++ * @SURVEY_INFO_CHANNEL_TIME: channel active time (in ms) was filled in
++ * @SURVEY_INFO_CHANNEL_TIME_BUSY: channel busy time was filled in
++ * @SURVEY_INFO_CHANNEL_TIME_EXT_BUSY: extension channel busy time was filled in
++ * @SURVEY_INFO_CHANNEL_TIME_RX: channel receive time was filled in
++ * @SURVEY_INFO_CHANNEL_TIME_TX: channel transmit time was filled in
+  *
+  * Used by the driver to indicate which info in &struct survey_info
+  * it has filled in during the get_survey().
+@@ -302,6 +307,11 @@ struct key_params {
+ enum survey_info_flags {
+ 	SURVEY_INFO_NOISE_DBM = 1<<0,
+ 	SURVEY_INFO_IN_USE = 1<<1,
++	SURVEY_INFO_CHANNEL_TIME = 1<<2,
++	SURVEY_INFO_CHANNEL_TIME_BUSY = 1<<3,
++	SURVEY_INFO_CHANNEL_TIME_EXT_BUSY = 1<<4,
++	SURVEY_INFO_CHANNEL_TIME_RX = 1<<5,
++	SURVEY_INFO_CHANNEL_TIME_TX = 1<<6,
+ };
+ 
+ /**
+@@ -311,6 +321,11 @@ enum survey_info_flags {
+  * @filled: bitflag of flags from &enum survey_info_flags
+  * @noise: channel noise in dBm. This and all following fields are
+  *     optional
++ * @channel_time: amount of time in ms the radio spent on the channel
++ * @channel_time_busy: amount of time the primary channel was sensed busy
++ * @channel_time_ext_busy: amount of time the extension channel was sensed busy
++ * @channel_time_rx: amount of time the radio spent receiving data
++ * @channel_time_tx: amount of time the radio spent transmitting data
+  *
+  * Used by dump_survey() to report back per-channel survey information.
+  *
+@@ -319,6 +334,11 @@ enum survey_info_flags {
+  */
+ struct survey_info {
+ 	struct ieee80211_channel *channel;
++	u64 channel_time;
++	u64 channel_time_busy;
++	u64 channel_time_ext_busy;
++	u64 channel_time_rx;
++	u64 channel_time_tx;
+ 	u32 filled;
+ 	s8 noise;
+ };
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -3176,6 +3176,21 @@ static int nl80211_send_survey(struct sk
+ 			    survey->noise);
+ 	if (survey->filled & SURVEY_INFO_IN_USE)
+ 		NLA_PUT_FLAG(msg, NL80211_SURVEY_INFO_IN_USE);
++	if (survey->filled & SURVEY_INFO_CHANNEL_TIME)
++		NLA_PUT_U64(msg, NL80211_SURVEY_INFO_CHANNEL_TIME,
++			    survey->channel_time);
++	if (survey->filled & SURVEY_INFO_CHANNEL_TIME_BUSY)
++		NLA_PUT_U64(msg, NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY,
++			    survey->channel_time_busy);
++	if (survey->filled & SURVEY_INFO_CHANNEL_TIME_EXT_BUSY)
++		NLA_PUT_U64(msg, NL80211_SURVEY_INFO_CHANNEL_TIME_EXT_BUSY,
++			    survey->channel_time_ext_busy);
++	if (survey->filled & SURVEY_INFO_CHANNEL_TIME_RX)
++		NLA_PUT_U64(msg, NL80211_SURVEY_INFO_CHANNEL_TIME_RX,
++			    survey->channel_time_rx);
++	if (survey->filled & SURVEY_INFO_CHANNEL_TIME_TX)
++		NLA_PUT_U64(msg, NL80211_SURVEY_INFO_CHANNEL_TIME_TX,
++			    survey->channel_time_tx);
+ 
+ 	nla_nest_end(msg, infoattr);
+ 
diff --git a/package/mac80211/patches/525-ath9k_channel_count_check.patch b/package/mac80211/patches/525-ath9k_channel_count_check.patch
new file mode 100644
index 0000000..ae74393
--- /dev/null
+++ b/package/mac80211/patches/525-ath9k_channel_count_check.patch
@@ -0,0 +1,33 @@
+--- a/drivers/net/wireless/ath/ath9k/hw.h
++++ b/drivers/net/wireless/ath/ath9k/hw.h
+@@ -61,6 +61,8 @@
+ 
+ #define ATH9K_RSSI_BAD			-128
+ 
++#define ATH9K_NUM_CHANNELS	38
++
+ /* Register read/write primitives */
+ #define REG_WRITE(_ah, _reg, _val) \
+ 	ath9k_hw_common(_ah)->ops->write((_ah), (_val), (_reg))
+@@ -618,7 +620,7 @@ struct ath_hw {
+ 	struct ath9k_hw_version hw_version;
+ 	struct ath9k_ops_config config;
+ 	struct ath9k_hw_capabilities caps;
+-	struct ath9k_channel channels[38];
++	struct ath9k_channel channels[ATH9K_NUM_CHANNELS];
+ 	struct ath9k_channel *curchan;
+ 
+ 	union {
+--- a/drivers/net/wireless/ath/ath9k/init.c
++++ b/drivers/net/wireless/ath/ath9k/init.c
+@@ -482,6 +482,10 @@ static int ath9k_init_channels_rates(str
+ {
+ 	void *channels;
+ 
++	BUILD_BUG_ON(ARRAY_SIZE(ath9k_2ghz_chantable) +
++		     ARRAY_SIZE(ath9k_5ghz_chantable) !=
++		     ATH9K_NUM_CHANNELS);
++
+ 	if (test_bit(ATH9K_MODE_11G, sc->sc_ah->caps.wireless_modes)) {
+ 		channels = kmemdup(ath9k_2ghz_chantable,
+ 			sizeof(ath9k_2ghz_chantable), GFP_KERNEL);
diff --git a/package/mac80211/patches/526-ath9k_survey_channel_stats.patch b/package/mac80211/patches/526-ath9k_survey_channel_stats.patch
new file mode 100644
index 0000000..842dc81
--- /dev/null
+++ b/package/mac80211/patches/526-ath9k_survey_channel_stats.patch
@@ -0,0 +1,165 @@
+--- a/drivers/net/wireless/ath/ath9k/ath9k.h
++++ b/drivers/net/wireless/ath/ath9k/ath9k.h
+@@ -594,6 +594,8 @@ struct ath_softc {
+ 	struct delayed_work wiphy_work;
+ 	unsigned long wiphy_scheduler_int;
+ 	int wiphy_scheduler_index;
++	struct survey_info *cur_survey;
++	struct survey_info survey[ATH9K_NUM_CHANNELS];
+ 
+ 	struct tasklet_struct intr_tq;
+ 	struct tasklet_struct bcon_tasklet;
+--- a/drivers/net/wireless/ath/ath9k/main.c
++++ b/drivers/net/wireless/ath/ath9k/main.c
+@@ -176,6 +176,49 @@ static void ath_start_ani(struct ath_com
+ 			msecs_to_jiffies((u32)ah->config.ani_poll_interval));
+ }
+ 
++static void ath_update_survey_nf(struct ath_softc *sc, int channel)
++{
++	struct ath_hw *ah = sc->sc_ah;
++	struct ath9k_channel *chan = &ah->channels[channel];
++	struct survey_info *survey = &sc->survey[channel];
++
++	if (chan->noisefloor) {
++		survey->filled |= SURVEY_INFO_NOISE_DBM;
++		survey->noise = chan->noisefloor;
++	}
++}
++
++static void ath_update_survey_stats(struct ath_softc *sc)
++{
++	struct ath_hw *ah = sc->sc_ah;
++	struct ath_common *common = ath9k_hw_common(ah);
++	int pos = ah->curchan - &ah->channels[0];
++	struct survey_info *survey = &sc->survey[pos];
++	struct ath_cycle_counters *cc = &common->cc_survey;
++	unsigned long flags;
++	unsigned int div = common->clockrate * 1000;
++
++	spin_lock_irqsave(&common->cc_lock, flags);
++
++	ath_hw_cycle_counters_update(common);
++
++	if (cc->cycles > 0) {
++		survey->filled |= SURVEY_INFO_CHANNEL_TIME |
++			SURVEY_INFO_CHANNEL_TIME_BUSY |
++			SURVEY_INFO_CHANNEL_TIME_RX |
++			SURVEY_INFO_CHANNEL_TIME_TX;
++		survey->channel_time += cc->cycles / div;
++		survey->channel_time_busy += cc->rx_busy / div;
++		survey->channel_time_rx += cc->rx_frame / div;
++		survey->channel_time_tx += cc->tx_frame / div;
++	}
++	memset(cc, 0, sizeof(*cc));
++
++	ath_update_survey_nf(sc, pos);
++
++	spin_unlock_irqrestore(&common->cc_lock, flags);
++}
++
+ /*
+  * 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
+@@ -1533,7 +1576,8 @@ static int ath9k_config(struct ieee80211
+ {
+ 	struct ath_wiphy *aphy = hw->priv;
+ 	struct ath_softc *sc = aphy->sc;
+-	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);
+ 	struct ieee80211_conf *conf = &hw->conf;
+ 	bool disable_radio;
+ 
+@@ -1599,6 +1643,10 @@ static int ath9k_config(struct ieee80211
+ 	if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
+ 		struct ieee80211_channel *curchan = hw->conf.channel;
+ 		int pos = curchan->hw_value;
++		int old_pos = -1;
++
++		if (ah->curchan)
++			old_pos = ah->curchan - &ah->channels[0];
+ 
+ 		aphy->chan_idx = pos;
+ 		aphy->chan_is_ht = conf_is_ht(conf);
+@@ -1626,12 +1674,43 @@ static int ath9k_config(struct ieee80211
+ 
+ 		ath_update_chainmask(sc, conf_is_ht(conf));
+ 
++		/* update survey stats for the old channel before switching */
++		ath_update_survey_stats(sc);
++
++		/*
++		 * If the operating channel changes, change the survey in-use flags
++		 * along with it.
++		 * Reset the survey data for the new channel, unless we're switching
++		 * back to the operating channel from an off-channel operation.
++		 */
++		if (!(hw->conf.flags & IEEE80211_CONF_OFFCHANNEL) &&
++		    sc->cur_survey != &sc->survey[pos]) {
++
++			if (sc->cur_survey)
++				sc->cur_survey->filled &= ~SURVEY_INFO_IN_USE;
++
++			sc->cur_survey = &sc->survey[pos];
++
++			memset(sc->cur_survey, 0, sizeof(struct survey_info));
++			sc->cur_survey->filled |= SURVEY_INFO_IN_USE;
++		} else if (!(sc->survey[pos].filled & SURVEY_INFO_IN_USE)) {
++			memset(&sc->survey[pos], 0, sizeof(struct survey_info));
++		}
++
+ 		if (ath_set_channel(sc, hw, &sc->sc_ah->channels[pos]) < 0) {
+ 			ath_print(common, ATH_DBG_FATAL,
+ 				  "Unable to set channel\n");
+ 			mutex_unlock(&sc->mutex);
+ 			return -EINVAL;
+ 		}
++
++		/*
++		 * The most recent snapshot of channel->noisefloor for the old
++		 * channel is only available after the hardware reset. Copy it to
++		 * the survey stats now.
++		 */
++		if (old_pos >= 0)
++			ath_update_survey_nf(sc, old_pos);
+ 	}
+ 
+ skip_chan_change:
+@@ -2001,9 +2080,12 @@ static int ath9k_get_survey(struct ieee8
+ {
+ 	struct ath_wiphy *aphy = hw->priv;
+ 	struct ath_softc *sc = aphy->sc;
+-	struct ath_hw *ah = sc->sc_ah;
+ 	struct ieee80211_supported_band *sband;
+-	struct ath9k_channel *chan;
++	struct ieee80211_channel *chan;
++	int pos;
++
++	if (idx == 0)
++		ath_update_survey_stats(sc);
+ 
+ 	sband = hw->wiphy->bands[IEEE80211_BAND_2GHZ];
+ 	if (sband && idx >= sband->n_channels) {
+@@ -2017,17 +2099,10 @@ static int ath9k_get_survey(struct ieee8
+ 	if (!sband || idx >= sband->n_channels)
+ 	    return -ENOENT;
+ 
+-	survey->channel = &sband->channels[idx];
+-	chan = &ah->channels[survey->channel->hw_value];
+-	survey->filled = 0;
+-
+-	if (chan == ah->curchan)
+-		survey->filled |= SURVEY_INFO_IN_USE;
+-
+-	if (chan->noisefloor) {
+-		survey->filled |= SURVEY_INFO_NOISE_DBM;
+-		survey->noise = chan->noisefloor;
+-	}
++	chan = &sband->channels[idx];
++	pos = chan->hw_value;
++	memcpy(survey, &sc->survey[pos], sizeof(*survey));
++	survey->channel = chan;
+ 
+ 	return 0;
+ }
-- 
cgit v1.1