|
@@ -490,6 +490,122 @@ void ath_update_chainmask(struct ath_softc *sc, int is_ht)
|
|
|
__func__, sc->sc_tx_chainmask, sc->sc_rx_chainmask);
|
|
|
}
|
|
|
|
|
|
+/*******/
|
|
|
+/* ANI */
|
|
|
+/*******/
|
|
|
+
|
|
|
+/*
|
|
|
+ * This routine performs the periodic noise floor calibration function
|
|
|
+ * that is used to adjust and optimize the chip performance. This
|
|
|
+ * takes environmental changes (location, temperature) into account.
|
|
|
+ * When the task is complete, it reschedules itself depending on the
|
|
|
+ * appropriate interval that was calculated.
|
|
|
+ */
|
|
|
+
|
|
|
+static void ath_ani_calibrate(unsigned long data)
|
|
|
+{
|
|
|
+ struct ath_softc *sc;
|
|
|
+ struct ath_hal *ah;
|
|
|
+ bool longcal = false;
|
|
|
+ bool shortcal = false;
|
|
|
+ bool aniflag = false;
|
|
|
+ unsigned int timestamp = jiffies_to_msecs(jiffies);
|
|
|
+ u32 cal_interval;
|
|
|
+
|
|
|
+ sc = (struct ath_softc *)data;
|
|
|
+ ah = sc->sc_ah;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * don't calibrate when we're scanning.
|
|
|
+ * we are most likely not on our home channel.
|
|
|
+ */
|
|
|
+ if (sc->rx_filter & FIF_BCN_PRBRESP_PROMISC)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /* Long calibration runs independently of short calibration. */
|
|
|
+ if ((timestamp - sc->sc_ani.sc_longcal_timer) >= ATH_LONG_CALINTERVAL) {
|
|
|
+ longcal = true;
|
|
|
+ DPRINTF(sc, ATH_DBG_ANI, "%s: longcal @%lu\n",
|
|
|
+ __func__, jiffies);
|
|
|
+ sc->sc_ani.sc_longcal_timer = timestamp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Short calibration applies only while sc_caldone is false */
|
|
|
+ if (!sc->sc_ani.sc_caldone) {
|
|
|
+ if ((timestamp - sc->sc_ani.sc_shortcal_timer) >=
|
|
|
+ ATH_SHORT_CALINTERVAL) {
|
|
|
+ shortcal = true;
|
|
|
+ DPRINTF(sc, ATH_DBG_ANI, "%s: shortcal @%lu\n",
|
|
|
+ __func__, jiffies);
|
|
|
+ sc->sc_ani.sc_shortcal_timer = timestamp;
|
|
|
+ sc->sc_ani.sc_resetcal_timer = timestamp;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if ((timestamp - sc->sc_ani.sc_resetcal_timer) >=
|
|
|
+ ATH_RESTART_CALINTERVAL) {
|
|
|
+ ath9k_hw_reset_calvalid(ah, ah->ah_curchan,
|
|
|
+ &sc->sc_ani.sc_caldone);
|
|
|
+ if (sc->sc_ani.sc_caldone)
|
|
|
+ sc->sc_ani.sc_resetcal_timer = timestamp;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Verify whether we must check ANI */
|
|
|
+ if ((timestamp - sc->sc_ani.sc_checkani_timer) >=
|
|
|
+ ATH_ANI_POLLINTERVAL) {
|
|
|
+ aniflag = true;
|
|
|
+ sc->sc_ani.sc_checkani_timer = timestamp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Skip all processing if there's nothing to do. */
|
|
|
+ if (longcal || shortcal || aniflag) {
|
|
|
+ /* Call ANI routine if necessary */
|
|
|
+ if (aniflag)
|
|
|
+ ath9k_hw_ani_monitor(ah, &sc->sc_halstats,
|
|
|
+ ah->ah_curchan);
|
|
|
+
|
|
|
+ /* Perform calibration if necessary */
|
|
|
+ if (longcal || shortcal) {
|
|
|
+ bool iscaldone = false;
|
|
|
+
|
|
|
+ if (ath9k_hw_calibrate(ah, ah->ah_curchan,
|
|
|
+ sc->sc_rx_chainmask, longcal,
|
|
|
+ &iscaldone)) {
|
|
|
+ if (longcal)
|
|
|
+ sc->sc_ani.sc_noise_floor =
|
|
|
+ ath9k_hw_getchan_noise(ah,
|
|
|
+ ah->ah_curchan);
|
|
|
+
|
|
|
+ DPRINTF(sc, ATH_DBG_ANI,
|
|
|
+ "%s: calibrate chan %u/%x nf: %d\n",
|
|
|
+ __func__,
|
|
|
+ ah->ah_curchan->channel,
|
|
|
+ ah->ah_curchan->channelFlags,
|
|
|
+ sc->sc_ani.sc_noise_floor);
|
|
|
+ } else {
|
|
|
+ DPRINTF(sc, ATH_DBG_ANY,
|
|
|
+ "%s: calibrate chan %u/%x failed\n",
|
|
|
+ __func__,
|
|
|
+ ah->ah_curchan->channel,
|
|
|
+ ah->ah_curchan->channelFlags);
|
|
|
+ }
|
|
|
+ sc->sc_ani.sc_caldone = iscaldone;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Set timer interval based on previous results.
|
|
|
+ * The interval must be the shortest necessary to satisfy ANI,
|
|
|
+ * short calibration and long calibration.
|
|
|
+ */
|
|
|
+
|
|
|
+ cal_interval = ATH_ANI_POLLINTERVAL;
|
|
|
+ if (!sc->sc_ani.sc_caldone)
|
|
|
+ cal_interval = min(cal_interval, (u32)ATH_SHORT_CALINTERVAL);
|
|
|
+
|
|
|
+ mod_timer(&sc->sc_ani.timer, jiffies + msecs_to_jiffies(cal_interval));
|
|
|
+}
|
|
|
+
|
|
|
/******************/
|
|
|
/* VAP management */
|
|
|
/******************/
|
|
@@ -676,12 +792,6 @@ int ath_open(struct ath_softc *sc, struct ath9k_channel *initial_chan)
|
|
|
if (ah->ah_caps.hw_caps & ATH9K_HW_CAP_HT)
|
|
|
sc->sc_imask |= ATH9K_INT_CST;
|
|
|
|
|
|
- /* Note: We disable MIB interrupts for now as we don't yet
|
|
|
- * handle processing ANI, otherwise you will get an interrupt
|
|
|
- * storm after about 7 hours of usage making the system unusable
|
|
|
- * with huge latency. Once we do have ANI processing included
|
|
|
- * we can re-enable this interrupt. */
|
|
|
-#if 0
|
|
|
/*
|
|
|
* Enable MIB interrupts when there are hardware phy counters.
|
|
|
* Note we only do this (at the moment) for station mode.
|
|
@@ -690,7 +800,6 @@ int ath_open(struct ath_softc *sc, struct ath9k_channel *initial_chan)
|
|
|
((sc->sc_ah->ah_opmode == ATH9K_M_STA) ||
|
|
|
(sc->sc_ah->ah_opmode == ATH9K_M_IBSS)))
|
|
|
sc->sc_imask |= ATH9K_INT_MIB;
|
|
|
-#endif
|
|
|
/*
|
|
|
* Some hardware processes the TIM IE and fires an
|
|
|
* interrupt when the TIM bit is set. For hardware
|
|
@@ -991,6 +1100,10 @@ int ath_init(u16 devid, struct ath_softc *sc)
|
|
|
}
|
|
|
sc->sc_ah = ah;
|
|
|
|
|
|
+ /* Initializes the noise floor to a reasonable default value.
|
|
|
+ * Later on this will be updated during ANI processing. */
|
|
|
+ sc->sc_ani.sc_noise_floor = ATH_DEFAULT_NOISE_FLOOR;
|
|
|
+
|
|
|
/* Get the hardware key cache size. */
|
|
|
sc->sc_keymax = ah->ah_caps.keycache_size;
|
|
|
if (sc->sc_keymax > ATH_KEYMAX) {
|
|
@@ -1098,6 +1211,8 @@ int ath_init(u16 devid, struct ath_softc *sc)
|
|
|
goto bad2;
|
|
|
}
|
|
|
|
|
|
+ setup_timer(&sc->sc_ani.timer, ath_ani_calibrate, (unsigned long)sc);
|
|
|
+
|
|
|
sc->sc_rc = ath_rate_attach(ah);
|
|
|
if (sc->sc_rc == NULL) {
|
|
|
error = -EIO;
|