ath5k: use noise calibration from madwifi hal
authorBob Copeland <me@bobcopeland.com>
Wed, 14 Oct 2009 18:16:30 +0000 (14:16 -0400)
committerJohn W. Linville <linville@tuxdriver.com>
Tue, 27 Oct 2009 20:48:18 +0000 (16:48 -0400)
This updates ath5k to calibrate the noise floor similar to the
way it is done in the madwifi hal and ath9k.  Of note:

- we start NF measurement at the same time as AGC calibration,
  but do not actually read the value until the periodic (long)
  calibration
- we keep a history of the last few values read and write the
  median back to the hardware for CCA
- we do not complain if NF calibration isn't complete, instead
  we keep the last read value.

Signed-off-by: Bob Copeland <me@bobcopeland.com>
Acked-by: Nick Kossifidis <mickflemm@gmail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/ath/ath5k/ath5k.h
drivers/net/wireless/ath/ath5k/attach.c
drivers/net/wireless/ath/ath5k/phy.c
drivers/net/wireless/ath/ath5k/reg.h
drivers/net/wireless/ath/ath5k/reset.c

index 647d826bf5fbb6171e2ef6e454c861255e854232..6a2a9676111121cc1523aadbad93a22e9edc4382 100644 (file)
 #define AR5K_TUNE_CWMAX_11B                    1023
 #define AR5K_TUNE_CWMAX_XR                     7
 #define AR5K_TUNE_NOISE_FLOOR                  -72
+#define AR5K_TUNE_CCA_MAX_GOOD_VALUE           -95
 #define AR5K_TUNE_MAX_TXPOWER                  63
 #define AR5K_TUNE_DEFAULT_TXPOWER              25
 #define AR5K_TUNE_TPC_TXPOWER                  false
@@ -1006,6 +1007,14 @@ struct ath5k_capabilities {
        } cap_queues;
 };
 
+/* size of noise floor history (keep it a power of two) */
+#define ATH5K_NF_CAL_HIST_MAX  8
+struct ath5k_nfcal_hist
+{
+       s16 index;                              /* current index into nfval */
+       s16 nfval[ATH5K_NF_CAL_HIST_MAX];       /* last few noise floors */
+};
+
 
 /***************************************\
   HARDWARE ABSTRACTION LAYER STRUCTURE
@@ -1112,6 +1121,8 @@ struct ath5k_hw {
                struct ieee80211_channel r_last_channel;
        } ah_radar;
 
+       struct ath5k_nfcal_hist ah_nfcal_hist;
+
        /* noise floor from last periodic calibration */
        s32                     ah_noise_floor;
 
@@ -1274,8 +1285,10 @@ extern int ath5k_hw_rfgain_opt_init(struct ath5k_hw *ah);
 extern bool ath5k_channel_ok(struct ath5k_hw *ah, u16 freq, unsigned int flags);
 extern int ath5k_hw_channel(struct ath5k_hw *ah, struct ieee80211_channel *channel);
 /* PHY calibration */
+void ath5k_hw_init_nfcal_hist(struct ath5k_hw *ah);
 extern int ath5k_hw_phy_calibrate(struct ath5k_hw *ah, struct ieee80211_channel *channel);
 extern int ath5k_hw_noise_floor_calibration(struct ath5k_hw *ah, short freq);
+extern s16 ath5k_hw_get_noise_floor(struct ath5k_hw *ah);
 extern void ath5k_hw_calibration_poll(struct ath5k_hw *ah);
 /* Spur mitigation */
 bool ath5k_hw_chan_has_spur_noise(struct ath5k_hw *ah,
index 92995adeb5cd20dfb254e39030983f00d8c4d514..42284445b75e72beeb6cc68d0356c74cc4ba21fc 100644 (file)
@@ -331,6 +331,8 @@ int ath5k_hw_attach(struct ath5k_softc *sc)
 
        ath5k_hw_rfgain_opt_init(ah);
 
+       ath5k_hw_init_nfcal_hist(ah);
+
        /* turn on HW LEDs */
        ath5k_hw_set_ledstate(ah, AR5K_LED_INIT);
 
index 1a039f2bd7327d86c50cb04847db94f07a380c17..895990751d36fedbfe36e9cc272ff5fcd63832da 100644 (file)
@@ -1124,77 +1124,148 @@ ath5k_hw_calibration_poll(struct ath5k_hw *ah)
                ah->ah_swi_mask = AR5K_SWI_FULL_CALIBRATION;
                AR5K_REG_ENABLE_BITS(ah, AR5K_CR, AR5K_CR_SWI);
        }
+}
 
+static int sign_extend(int val, const int nbits)
+{
+       int order = BIT(nbits-1);
+       return (val ^ order) - order;
 }
 
-/**
- * ath5k_hw_noise_floor_calibration - perform PHY noise floor calibration
- *
- * @ah: struct ath5k_hw pointer we are operating on
- * @freq: the channel frequency, just used for error logging
- *
- * This function performs a noise floor calibration of the PHY and waits for
- * it to complete. Then the noise floor value is compared to some maximum
- * noise floor we consider valid.
- *
- * Note that this is different from what the madwifi HAL does: it reads the
- * noise floor and afterwards initiates the calibration. Since the noise floor
- * calibration can take some time to finish, depending on the current channel
- * use, that avoids the occasional timeout warnings we are seeing now.
- *
- * See the following link for an Atheros patent on noise floor calibration:
- * http://patft.uspto.gov/netacgi/nph-Parser?Sect1=PTO1&Sect2=HITOFF&d=PALL \
- * &p=1&u=%2Fnetahtml%2FPTO%2Fsrchnum.htm&r=1&f=G&l=50&s1=7245893.PN.&OS=PN/7
+static s32 ath5k_hw_read_measured_noise_floor(struct ath5k_hw *ah)
+{
+       s32 val;
+
+       val = ath5k_hw_reg_read(ah, AR5K_PHY_NF);
+       return sign_extend(AR5K_REG_MS(val, AR5K_PHY_NF_MINCCA_PWR), 9);
+}
+
+void ath5k_hw_init_nfcal_hist(struct ath5k_hw *ah)
+{
+       int i;
+
+       ah->ah_nfcal_hist.index = 0;
+       for (i = 0; i < ATH5K_NF_CAL_HIST_MAX; i++)
+               ah->ah_nfcal_hist.nfval[i] = AR5K_TUNE_CCA_MAX_GOOD_VALUE;
+}
+
+static void ath5k_hw_update_nfcal_hist(struct ath5k_hw *ah, s16 noise_floor)
+{
+       struct ath5k_nfcal_hist *hist = &ah->ah_nfcal_hist;
+       hist->index = (hist->index + 1) & (ATH5K_NF_CAL_HIST_MAX-1);
+       hist->nfval[hist->index] = noise_floor;
+}
+
+static s16 ath5k_hw_get_median_noise_floor(struct ath5k_hw *ah)
+{
+       s16 sort[ATH5K_NF_CAL_HIST_MAX];
+       s16 tmp;
+       int i, j;
+
+       memcpy(sort, ah->ah_nfcal_hist.nfval, sizeof(sort));
+       for (i = 0; i < ATH5K_NF_CAL_HIST_MAX - 1; i++) {
+               for (j = 1; j < ATH5K_NF_CAL_HIST_MAX - i; j++) {
+                       if (sort[j] > sort[j-1]) {
+                               tmp = sort[j];
+                               sort[j] = sort[j-1];
+                               sort[j-1] = tmp;
+                       }
+               }
+       }
+       for (i = 0; i < ATH5K_NF_CAL_HIST_MAX; i++) {
+               ATH5K_DBG(ah->ah_sc, ATH5K_DEBUG_CALIBRATE,
+                       "cal %d:%d\n", i, sort[i]);
+       }
+       return sort[(ATH5K_NF_CAL_HIST_MAX-1) / 2];
+}
+
+/*
+ * When we tell the hardware to perform a noise floor calibration
+ * by setting the AR5K_PHY_AGCCTL_NF bit, it will periodically
+ * sample-and-hold the minimum noise level seen at the antennas.
+ * This value is then stored in a ring buffer of recently measured
+ * noise floor values so we have a moving window of the last few
+ * samples.
  *
- * XXX: Since during noise floor calibration antennas are detached according to
- * the patent, we should stop tx queues here.
+ * The median of the values in the history is then loaded into the
+ * hardware for its own use for RSSI and CCA measurements.
  */
-int
-ath5k_hw_noise_floor_calibration(struct ath5k_hw *ah, short freq)
+void ath5k_hw_update_noise_floor(struct ath5k_hw *ah)
 {
-       int ret;
-       unsigned int i;
-       s32 noise_floor;
+       struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
+       u32 val;
+       s16 nf, threshold;
+       u8 ee_mode;
 
-       /*
-        * Enable noise floor calibration
-        */
-       AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL,
-                               AR5K_PHY_AGCCTL_NF);
+       /* keep last value if calibration hasn't completed */
+       if (ath5k_hw_reg_read(ah, AR5K_PHY_AGCCTL) & AR5K_PHY_AGCCTL_NF) {
+               ATH5K_DBG(ah->ah_sc, ATH5K_DEBUG_CALIBRATE,
+                       "NF did not complete in calibration window\n");
 
-       ret = ath5k_hw_register_timeout(ah, AR5K_PHY_AGCCTL,
-                       AR5K_PHY_AGCCTL_NF, 0, false);
-       if (ret) {
-               ATH5K_ERR(ah->ah_sc,
-                       "noise floor calibration timeout (%uMHz)\n", freq);
-               return -EAGAIN;
+               return;
        }
 
-       /* Wait until the noise floor is calibrated and read the value */
-       for (i = 20; i > 0; i--) {
-               mdelay(1);
-               noise_floor = ath5k_hw_reg_read(ah, AR5K_PHY_NF);
-               noise_floor = AR5K_PHY_NF_RVAL(noise_floor);
-               if (noise_floor & AR5K_PHY_NF_ACTIVE) {
-                       noise_floor = AR5K_PHY_NF_AVAL(noise_floor);
-
-                       if (noise_floor <= AR5K_TUNE_NOISE_FLOOR)
-                               break;
-               }
+       switch (ah->ah_current_channel->hw_value & CHANNEL_MODES) {
+       case CHANNEL_A:
+       case CHANNEL_T:
+       case CHANNEL_XR:
+               ee_mode = AR5K_EEPROM_MODE_11A;
+               break;
+       case CHANNEL_G:
+       case CHANNEL_TG:
+               ee_mode = AR5K_EEPROM_MODE_11G;
+               break;
+       default:
+       case CHANNEL_B:
+               ee_mode = AR5K_EEPROM_MODE_11B;
+               break;
        }
 
-       ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_CALIBRATE,
-               "noise floor %d\n", noise_floor);
 
-       if (noise_floor > AR5K_TUNE_NOISE_FLOOR) {
-               ATH5K_ERR(ah->ah_sc,
-                       "noise floor calibration failed (%uMHz)\n", freq);
-               return -EAGAIN;
+       /* completed NF calibration, test threshold */
+       nf = ath5k_hw_read_measured_noise_floor(ah);
+       threshold = ee->ee_noise_floor_thr[ee_mode];
+
+       if (nf > threshold) {
+               ATH5K_DBG(ah->ah_sc, ATH5K_DEBUG_CALIBRATE,
+                       "noise floor failure detected; "
+                       "read %d, threshold %d\n",
+                       nf, threshold);
+
+               nf = AR5K_TUNE_CCA_MAX_GOOD_VALUE;
        }
 
-       ah->ah_noise_floor = noise_floor;
+       ath5k_hw_update_nfcal_hist(ah, nf);
+       nf = ath5k_hw_get_median_noise_floor(ah);
 
-       return 0;
+       /* load noise floor (in .5 dBm) so the hardware will use it */
+       val = ath5k_hw_reg_read(ah, AR5K_PHY_NF) & ~AR5K_PHY_NF_M;
+       val |= (nf * 2) & AR5K_PHY_NF_M;
+       ath5k_hw_reg_write(ah, val, AR5K_PHY_NF);
+
+       AR5K_REG_MASKED_BITS(ah, AR5K_PHY_AGCCTL, AR5K_PHY_AGCCTL_NF,
+               ~(AR5K_PHY_AGCCTL_NF_EN | AR5K_PHY_AGCCTL_NF_NOUPDATE));
+
+       ath5k_hw_register_timeout(ah, AR5K_PHY_AGCCTL, AR5K_PHY_AGCCTL_NF,
+               0, false);
+
+       /*
+        * Load a high max CCA Power value (-50 dBm in .5 dBm units)
+        * so that we're not capped by the median we just loaded.
+        * This will be used as the initial value for the next noise
+        * floor calibration.
+        */
+       val = (val & ~AR5K_PHY_NF_M) | ((-50 * 2) & AR5K_PHY_NF_M);
+       ath5k_hw_reg_write(ah, val, AR5K_PHY_NF);
+       AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL,
+               AR5K_PHY_AGCCTL_NF_EN |
+               AR5K_PHY_AGCCTL_NF_NOUPDATE |
+               AR5K_PHY_AGCCTL_NF);
+
+       ah->ah_noise_floor = nf;
+
+       ATH5K_DBG(ah->ah_sc, ATH5K_DEBUG_CALIBRATE,
+               "noise floor calibrated: %d\n", nf);
 }
 
 /*
@@ -1287,7 +1358,7 @@ static int ath5k_hw_rf5110_calibrate(struct ath5k_hw *ah,
                return ret;
        }
 
-       ath5k_hw_noise_floor_calibration(ah, channel->center_freq);
+       ath5k_hw_update_noise_floor(ah);
 
        /*
         * Re-enable RX/TX and beacons
@@ -1360,7 +1431,7 @@ done:
         * since noise floor calibration interrupts rx path while I/Q
         * calibration doesn't. We don't need to run noise floor calibration
         * as often as I/Q calibration.*/
-       ath5k_hw_noise_floor_calibration(ah, channel->center_freq);
+       ath5k_hw_update_noise_floor(ah);
 
        /* Initiate a gain_F calibration */
        ath5k_hw_request_rfgain_probe(ah);
index 64227abe3c20e240059e887f396751a98ca2a060..4cb9c5df9f46e578aea8437c1937482fbfccfe4e 100644 (file)
 #define        AR5K_PHY_AGCCTL_NF_NOUPDATE     0x00020000      /* Don't update nf automaticaly */
 
 /*
- * PHY noise floor status register
+ * PHY noise floor status register (CCA = Clear Channel Assessment)
  */
 #define AR5K_PHY_NF                    0x9864                  /* Register address */
-#define AR5K_PHY_NF_M                  0x000001ff      /* Noise floor mask */
-#define AR5K_PHY_NF_ACTIVE             0x00000100      /* Noise floor calibration still active */
-#define AR5K_PHY_NF_RVAL(_n)           (((_n) >> 19) & AR5K_PHY_NF_M)
-#define AR5K_PHY_NF_AVAL(_n)           (-((_n) ^ AR5K_PHY_NF_M) + 1)
-#define AR5K_PHY_NF_SVAL(_n)           (((_n) & AR5K_PHY_NF_M) | (1 << 9))
+#define AR5K_PHY_NF_M                  0x000001ff      /* Noise floor, written to hardware in 1/2 dBm units */
+#define AR5K_PHY_NF_SVAL(_n)           (((_n) & AR5K_PHY_NF_M) | (1 << 9))
 #define        AR5K_PHY_NF_THRESH62            0x0007f000      /* Thresh62 -check ANI patent- (field) */
 #define        AR5K_PHY_NF_THRESH62_S          12
-#define        AR5K_PHY_NF_MINCCA_PWR          0x0ff80000      /* ??? */
+#define        AR5K_PHY_NF_MINCCA_PWR          0x0ff80000      /* Minimum measured noise level, read from hardware in 1 dBm units */
 #define        AR5K_PHY_NF_MINCCA_PWR_S        19
 
 /*
index 3dab3d856d7b3409af819832d397320a8bf7b6b7..62954fc778692dae949ee32c91a4528b3d4a098e 100644 (file)
@@ -1293,7 +1293,7 @@ int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode,
         * out and/or noise floor calibration might timeout.
         */
        AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL,
-                               AR5K_PHY_AGCCTL_CAL);
+                               AR5K_PHY_AGCCTL_CAL | AR5K_PHY_AGCCTL_NF);
 
        /* At the same time start I/Q calibration for QAM constellation
         * -no need for CCK- */
@@ -1314,21 +1314,6 @@ int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode,
                        channel->center_freq);
        }
 
-       /*
-        * If we run NF calibration before AGC, it always times out.
-        * Binary HAL starts NF and AGC calibration at the same time
-        * and only waits for AGC to finish. Also if AGC or NF cal.
-        * times out, reset doesn't fail on binary HAL. I believe
-        * that's wrong because since rx path is routed to a detector,
-        * if cal. doesn't finish we won't have RX. Sam's HAL for AR5210/5211
-        * enables noise floor calibration after offset calibration and if noise
-        * floor calibration fails, reset fails. I believe that's
-        * a better approach, we just need to find a polling interval
-        * that suits best, even if reset continues we need to make
-        * sure that rx path is ready.
-        */
-       ath5k_hw_noise_floor_calibration(ah, channel->center_freq);
-
        /* Restore antenna mode */
        ath5k_hw_set_antenna_mode(ah, ah->ah_ant_mode);