ath9k: enable ANI to help with noisy environments
[pandora-kernel.git] / drivers / net / wireless / ath9k / core.c
index 5af7dfb..c5033f6 100644 (file)
@@ -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;