ath5k: allow setting txpower to 0
[safe/jmp/linux-2.6] / drivers / net / wireless / ath / ath5k / phy.c
index b48b29d..721ec5e 100644 (file)
@@ -168,9 +168,6 @@ int ath5k_hw_rfgain_opt_init(struct ath5k_hw *ah)
  * tx power and a Peak to Average Power Detector (PAPD) will try
  * to measure the gain.
  *
- * TODO: Use propper tx power setting for the probe packet so
- * that we don't observe a serious power drop on the receiver
- *
  * XXX:  How about forcing a tx packet (bypassing PCU arbitrator etc)
  * just after we enable the probe so that we don't mess with
  * standard traffic ? Maybe it's time to use sw interrupts and
@@ -186,7 +183,7 @@ static void ath5k_hw_request_rfgain_probe(struct ath5k_hw *ah)
 
        /* Send the packet with 2dB below max power as
         * patent doc suggest */
-       ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txpower.txp_max_pwr - 4,
+       ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txpower.txp_ofdm - 4,
                        AR5K_PHY_PAPD_PROBE_TXPOWER) |
                        AR5K_PHY_PAPD_PROBE_TX_NEXT, AR5K_PHY_PAPD_PROBE);
 
@@ -743,13 +740,22 @@ int ath5k_hw_rfregs_init(struct ath5k_hw *ah, struct ieee80211_channel *channel,
                                                AR5K_RF_XPD_GAIN, true);
 
                } else {
-                       /* TODO: Set high and low gain bits */
-                       ath5k_hw_rfb_op(ah, rf_regs,
-                                               ee->ee_x_gain[ee_mode],
+                       u8 *pdg_curve_to_idx = ee->ee_pdc_to_idx[ee_mode];
+                       if (ee->ee_pd_gains[ee_mode] > 1) {
+                               ath5k_hw_rfb_op(ah, rf_regs,
+                                               pdg_curve_to_idx[0],
                                                AR5K_RF_PD_GAIN_LO, true);
-                       ath5k_hw_rfb_op(ah, rf_regs,
-                                               ee->ee_x_gain[ee_mode],
+                               ath5k_hw_rfb_op(ah, rf_regs,
+                                               pdg_curve_to_idx[1],
+                                               AR5K_RF_PD_GAIN_HI, true);
+                       } else {
+                               ath5k_hw_rfb_op(ah, rf_regs,
+                                               pdg_curve_to_idx[0],
+                                               AR5K_RF_PD_GAIN_LO, true);
+                               ath5k_hw_rfb_op(ah, rf_regs,
+                                               pdg_curve_to_idx[0],
                                                AR5K_RF_PD_GAIN_HI, true);
+                       }
 
                        /* Lower synth voltage on Rev 2 */
                        ath5k_hw_rfb_op(ah, rf_regs, 2,
@@ -1088,8 +1094,7 @@ int ath5k_hw_channel(struct ath5k_hw *ah, struct ieee80211_channel *channel)
                                AR5K_PHY_CCKTXCTL_WORLD);
        }
 
-       ah->ah_current_channel.center_freq = channel->center_freq;
-       ah->ah_current_channel.hw_value = channel->hw_value;
+       ah->ah_current_channel = channel;
        ah->ah_turbo = channel->hw_value == CHANNEL_T ? true : false;
 
        return 0;
@@ -1099,74 +1104,168 @@ int ath5k_hw_channel(struct ath5k_hw *ah, struct ieee80211_channel *channel)
   PHY calibration
 \*****************/
 
-/**
- * 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
- *
- * XXX: Since during noise floor calibration antennas are detached according to
- * the patent, we should stop tx queues here.
- */
-int
-ath5k_hw_noise_floor_calibration(struct ath5k_hw *ah, short freq)
+void
+ath5k_hw_calibration_poll(struct ath5k_hw *ah)
 {
-       int ret;
-       unsigned int i;
-       s32 noise_floor;
+       /* Calibration interval in jiffies */
+       unsigned long cal_intval;
 
-       /*
-        * Enable noise floor calibration
-        */
-       AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL,
-                               AR5K_PHY_AGCCTL_NF);
+       cal_intval = msecs_to_jiffies(ah->ah_cal_intval * 1000);
 
-       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;
+       /* Initialize timestamp if needed */
+       if (!ah->ah_cal_tstamp)
+               ah->ah_cal_tstamp = jiffies;
+
+       /* For now we always do full calibration
+        * Mark software interrupt mask and fire software
+        * interrupt (bit gets auto-cleared) */
+       if (time_is_before_eq_jiffies(ah->ah_cal_tstamp + cal_intval)) {
+               ah->ah_cal_tstamp = jiffies;
+               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;
+}
+
+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;
 
-       /* 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);
+       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;
+}
 
-                       if (noise_floor <= AR5K_TUNE_NOISE_FLOOR)
-                               break;
+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];
+}
 
-       ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_CALIBRATE,
-               "noise floor %d\n", noise_floor);
+/*
+ * 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.
+ *
+ * The median of the values in the history is then loaded into the
+ * hardware for its own use for RSSI and CCA measurements.
+ */
+void ath5k_hw_update_noise_floor(struct ath5k_hw *ah)
+{
+       struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
+       u32 val;
+       s16 nf, threshold;
+       u8 ee_mode;
 
-       if (noise_floor > AR5K_TUNE_NOISE_FLOOR) {
-               ATH5K_ERR(ah->ah_sc,
-                       "noise floor calibration failed (%uMHz)\n", freq);
-               return -EAGAIN;
+       /* 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");
+
+               return;
+       }
+
+       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;
        }
 
-       ah->ah_noise_floor = noise_floor;
 
-       return 0;
+       /* 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;
+       }
+
+       ath5k_hw_update_nfcal_hist(ah, nf);
+       nf = ath5k_hw_get_median_noise_floor(ah);
+
+       /* 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);
 }
 
 /*
@@ -1259,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
@@ -1332,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);
@@ -1356,6 +1455,257 @@ int ath5k_hw_phy_calibrate(struct ath5k_hw *ah,
        return ret;
 }
 
+/***************************\
+* Spur mitigation functions *
+\***************************/
+
+bool ath5k_hw_chan_has_spur_noise(struct ath5k_hw *ah,
+                               struct ieee80211_channel *channel)
+{
+       u8 refclk_freq;
+
+       if ((ah->ah_radio == AR5K_RF5112) ||
+       (ah->ah_radio == AR5K_RF5413) ||
+       (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4)))
+               refclk_freq = 40;
+       else
+               refclk_freq = 32;
+
+       if ((channel->center_freq % refclk_freq != 0) &&
+       ((channel->center_freq % refclk_freq < 10) ||
+       (channel->center_freq % refclk_freq > 22)))
+               return true;
+       else
+               return false;
+}
+
+void
+ath5k_hw_set_spur_mitigation_filter(struct ath5k_hw *ah,
+                               struct ieee80211_channel *channel)
+{
+       struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
+       u32 mag_mask[4] = {0, 0, 0, 0};
+       u32 pilot_mask[2] = {0, 0};
+       /* Note: fbin values are scaled up by 2 */
+       u16 spur_chan_fbin, chan_fbin, symbol_width, spur_detection_window;
+       s32 spur_delta_phase, spur_freq_sigma_delta;
+       s32 spur_offset, num_symbols_x16;
+       u8 num_symbol_offsets, i, freq_band;
+
+       /* Convert current frequency to fbin value (the same way channels
+        * are stored on EEPROM, check out ath5k_eeprom_bin2freq) and scale
+        * up by 2 so we can compare it later */
+       if (channel->hw_value & CHANNEL_2GHZ) {
+               chan_fbin = (channel->center_freq - 2300) * 10;
+               freq_band = AR5K_EEPROM_BAND_2GHZ;
+       } else {
+               chan_fbin = (channel->center_freq - 4900) * 10;
+               freq_band = AR5K_EEPROM_BAND_5GHZ;
+       }
+
+       /* Check if any spur_chan_fbin from EEPROM is
+        * within our current channel's spur detection range */
+       spur_chan_fbin = AR5K_EEPROM_NO_SPUR;
+       spur_detection_window = AR5K_SPUR_CHAN_WIDTH;
+       /* XXX: Half/Quarter channels ?*/
+       if (channel->hw_value & CHANNEL_TURBO)
+               spur_detection_window *= 2;
+
+       for (i = 0; i < AR5K_EEPROM_N_SPUR_CHANS; i++) {
+               spur_chan_fbin = ee->ee_spur_chans[i][freq_band];
+
+               /* Note: mask cleans AR5K_EEPROM_NO_SPUR flag
+                * so it's zero if we got nothing from EEPROM */
+               if (spur_chan_fbin == AR5K_EEPROM_NO_SPUR) {
+                       spur_chan_fbin &= AR5K_EEPROM_SPUR_CHAN_MASK;
+                       break;
+               }
+
+               if ((chan_fbin - spur_detection_window <=
+               (spur_chan_fbin & AR5K_EEPROM_SPUR_CHAN_MASK)) &&
+               (chan_fbin + spur_detection_window >=
+               (spur_chan_fbin & AR5K_EEPROM_SPUR_CHAN_MASK))) {
+                       spur_chan_fbin &= AR5K_EEPROM_SPUR_CHAN_MASK;
+                       break;
+               }
+       }
+
+       /* We need to enable spur filter for this channel */
+       if (spur_chan_fbin) {
+               spur_offset = spur_chan_fbin - chan_fbin;
+               /*
+                * Calculate deltas:
+                * spur_freq_sigma_delta -> spur_offset / sample_freq << 21
+                * spur_delta_phase -> spur_offset / chip_freq << 11
+                * Note: Both values have 100KHz resolution
+                */
+               /* XXX: Half/Quarter rate channels ? */
+               switch (channel->hw_value) {
+               case CHANNEL_A:
+                       /* Both sample_freq and chip_freq are 40MHz */
+                       spur_delta_phase = (spur_offset << 17) / 25;
+                       spur_freq_sigma_delta = (spur_delta_phase >> 10);
+                       symbol_width = AR5K_SPUR_SYMBOL_WIDTH_BASE_100Hz;
+                       break;
+               case CHANNEL_G:
+                       /* sample_freq -> 40MHz chip_freq -> 44MHz
+                        * (for b compatibility) */
+                       spur_freq_sigma_delta = (spur_offset << 8) / 55;
+                       spur_delta_phase = (spur_offset << 17) / 25;
+                       symbol_width = AR5K_SPUR_SYMBOL_WIDTH_BASE_100Hz;
+                       break;
+               case CHANNEL_T:
+               case CHANNEL_TG:
+                       /* Both sample_freq and chip_freq are 80MHz */
+                       spur_delta_phase = (spur_offset << 16) / 25;
+                       spur_freq_sigma_delta = (spur_delta_phase >> 10);
+                       symbol_width = AR5K_SPUR_SYMBOL_WIDTH_TURBO_100Hz;
+                       break;
+               default:
+                       return;
+               }
+
+               /* Calculate pilot and magnitude masks */
+
+               /* Scale up spur_offset by 1000 to switch to 100HZ resolution
+                * and divide by symbol_width to find how many symbols we have
+                * Note: number of symbols is scaled up by 16 */
+               num_symbols_x16 = ((spur_offset * 1000) << 4) / symbol_width;
+
+               /* Spur is on a symbol if num_symbols_x16 % 16 is zero */
+               if (!(num_symbols_x16 & 0xF))
+                       /* _X_ */
+                       num_symbol_offsets = 3;
+               else
+                       /* _xx_ */
+                       num_symbol_offsets = 4;
+
+               for (i = 0; i < num_symbol_offsets; i++) {
+
+                       /* Calculate pilot mask */
+                       s32 curr_sym_off =
+                               (num_symbols_x16 / 16) + i + 25;
+
+                       /* Pilot magnitude mask seems to be a way to
+                        * declare the boundaries for our detection
+                        * window or something, it's 2 for the middle
+                        * value(s) where the symbol is expected to be
+                        * and 1 on the boundary values */
+                       u8 plt_mag_map =
+                               (i == 0 || i == (num_symbol_offsets - 1))
+                                                               ? 1 : 2;
+
+                       if (curr_sym_off >= 0 && curr_sym_off <= 32) {
+                               if (curr_sym_off <= 25)
+                                       pilot_mask[0] |= 1 << curr_sym_off;
+                               else if (curr_sym_off >= 27)
+                                       pilot_mask[0] |= 1 << (curr_sym_off - 1);
+                       } else if (curr_sym_off >= 33 && curr_sym_off <= 52)
+                               pilot_mask[1] |= 1 << (curr_sym_off - 33);
+
+                       /* Calculate magnitude mask (for viterbi decoder) */
+                       if (curr_sym_off >= -1 && curr_sym_off <= 14)
+                               mag_mask[0] |=
+                                       plt_mag_map << (curr_sym_off + 1) * 2;
+                       else if (curr_sym_off >= 15 && curr_sym_off <= 30)
+                               mag_mask[1] |=
+                                       plt_mag_map << (curr_sym_off - 15) * 2;
+                       else if (curr_sym_off >= 31 && curr_sym_off <= 46)
+                               mag_mask[2] |=
+                                       plt_mag_map << (curr_sym_off - 31) * 2;
+                       else if (curr_sym_off >= 46 && curr_sym_off <= 53)
+                               mag_mask[3] |=
+                                       plt_mag_map << (curr_sym_off - 47) * 2;
+
+               }
+
+               /* Write settings on hw to enable spur filter */
+               AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK_CTL,
+                                       AR5K_PHY_BIN_MASK_CTL_RATE, 0xff);
+               /* XXX: Self correlator also ? */
+               AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_IQ,
+                                       AR5K_PHY_IQ_PILOT_MASK_EN |
+                                       AR5K_PHY_IQ_CHAN_MASK_EN |
+                                       AR5K_PHY_IQ_SPUR_FILT_EN);
+
+               /* Set delta phase and freq sigma delta */
+               ath5k_hw_reg_write(ah,
+                               AR5K_REG_SM(spur_delta_phase,
+                                       AR5K_PHY_TIMING_11_SPUR_DELTA_PHASE) |
+                               AR5K_REG_SM(spur_freq_sigma_delta,
+                               AR5K_PHY_TIMING_11_SPUR_FREQ_SD) |
+                               AR5K_PHY_TIMING_11_USE_SPUR_IN_AGC,
+                               AR5K_PHY_TIMING_11);
+
+               /* Write pilot masks */
+               ath5k_hw_reg_write(ah, pilot_mask[0], AR5K_PHY_TIMING_7);
+               AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_8,
+                                       AR5K_PHY_TIMING_8_PILOT_MASK_2,
+                                       pilot_mask[1]);
+
+               ath5k_hw_reg_write(ah, pilot_mask[0], AR5K_PHY_TIMING_9);
+               AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_10,
+                                       AR5K_PHY_TIMING_10_PILOT_MASK_2,
+                                       pilot_mask[1]);
+
+               /* Write magnitude masks */
+               ath5k_hw_reg_write(ah, mag_mask[0], AR5K_PHY_BIN_MASK_1);
+               ath5k_hw_reg_write(ah, mag_mask[1], AR5K_PHY_BIN_MASK_2);
+               ath5k_hw_reg_write(ah, mag_mask[2], AR5K_PHY_BIN_MASK_3);
+               AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK_CTL,
+                                       AR5K_PHY_BIN_MASK_CTL_MASK_4,
+                                       mag_mask[3]);
+
+               ath5k_hw_reg_write(ah, mag_mask[0], AR5K_PHY_BIN_MASK2_1);
+               ath5k_hw_reg_write(ah, mag_mask[1], AR5K_PHY_BIN_MASK2_2);
+               ath5k_hw_reg_write(ah, mag_mask[2], AR5K_PHY_BIN_MASK2_3);
+               AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK2_4,
+                                       AR5K_PHY_BIN_MASK2_4_MASK_4,
+                                       mag_mask[3]);
+
+       } else if (ath5k_hw_reg_read(ah, AR5K_PHY_IQ) &
+       AR5K_PHY_IQ_SPUR_FILT_EN) {
+               /* Clean up spur mitigation settings and disable fliter */
+               AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK_CTL,
+                                       AR5K_PHY_BIN_MASK_CTL_RATE, 0);
+               AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_IQ,
+                                       AR5K_PHY_IQ_PILOT_MASK_EN |
+                                       AR5K_PHY_IQ_CHAN_MASK_EN |
+                                       AR5K_PHY_IQ_SPUR_FILT_EN);
+               ath5k_hw_reg_write(ah, 0, AR5K_PHY_TIMING_11);
+
+               /* Clear pilot masks */
+               ath5k_hw_reg_write(ah, 0, AR5K_PHY_TIMING_7);
+               AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_8,
+                                       AR5K_PHY_TIMING_8_PILOT_MASK_2,
+                                       0);
+
+               ath5k_hw_reg_write(ah, 0, AR5K_PHY_TIMING_9);
+               AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_10,
+                                       AR5K_PHY_TIMING_10_PILOT_MASK_2,
+                                       0);
+
+               /* Clear magnitude masks */
+               ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK_1);
+               ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK_2);
+               ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK_3);
+               AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK_CTL,
+                                       AR5K_PHY_BIN_MASK_CTL_MASK_4,
+                                       0);
+
+               ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK2_1);
+               ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK2_2);
+               ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK2_3);
+               AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK2_4,
+                                       AR5K_PHY_BIN_MASK2_4_MASK_4,
+                                       0);
+       }
+}
+
+/********************\
+  Misc PHY functions
+\********************/
+
 int ath5k_hw_phy_disable(struct ath5k_hw *ah)
 {
        ATH5K_TRACE(ah->ah_sc);
@@ -1365,10 +1715,6 @@ int ath5k_hw_phy_disable(struct ath5k_hw *ah)
        return 0;
 }
 
-/********************\
-  Misc PHY functions
-\********************/
-
 /*
  * Get the PHY Chip revision
  */
@@ -1417,25 +1763,189 @@ u16 ath5k_hw_radio_revision(struct ath5k_hw *ah, unsigned int chan)
        return ret;
 }
 
+/*****************\
+* Antenna control *
+\*****************/
+
 void /*TODO:Boundary check*/
-ath5k_hw_set_def_antenna(struct ath5k_hw *ah, unsigned int ant)
+ath5k_hw_set_def_antenna(struct ath5k_hw *ah, u8 ant)
 {
        ATH5K_TRACE(ah->ah_sc);
-       /*Just a try M.F.*/
+
        if (ah->ah_version != AR5K_AR5210)
-               ath5k_hw_reg_write(ah, ant, AR5K_DEFAULT_ANTENNA);
+               ath5k_hw_reg_write(ah, ant & 0x7, AR5K_DEFAULT_ANTENNA);
 }
 
 unsigned int ath5k_hw_get_def_antenna(struct ath5k_hw *ah)
 {
        ATH5K_TRACE(ah->ah_sc);
-       /*Just a try M.F.*/
+
        if (ah->ah_version != AR5K_AR5210)
-               return ath5k_hw_reg_read(ah, AR5K_DEFAULT_ANTENNA);
+               return ath5k_hw_reg_read(ah, AR5K_DEFAULT_ANTENNA) & 0x7;
 
        return false; /*XXX: What do we return for 5210 ?*/
 }
 
+/*
+ * Enable/disable fast rx antenna diversity
+ */
+static void
+ath5k_hw_set_fast_div(struct ath5k_hw *ah, u8 ee_mode, bool enable)
+{
+       switch (ee_mode) {
+       case AR5K_EEPROM_MODE_11G:
+               /* XXX: This is set to
+                * disabled on initvals !!! */
+       case AR5K_EEPROM_MODE_11A:
+               if (enable)
+                       AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_AGCCTL,
+                                       AR5K_PHY_AGCCTL_OFDM_DIV_DIS);
+               else
+                       AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL,
+                                       AR5K_PHY_AGCCTL_OFDM_DIV_DIS);
+               break;
+       case AR5K_EEPROM_MODE_11B:
+               AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL,
+                                       AR5K_PHY_AGCCTL_OFDM_DIV_DIS);
+               break;
+       default:
+               return;
+       }
+
+       if (enable) {
+               AR5K_REG_WRITE_BITS(ah, AR5K_PHY_RESTART,
+                               AR5K_PHY_RESTART_DIV_GC, 0xc);
+
+               AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_FAST_ANT_DIV,
+                                       AR5K_PHY_FAST_ANT_DIV_EN);
+       } else {
+               AR5K_REG_WRITE_BITS(ah, AR5K_PHY_RESTART,
+                               AR5K_PHY_RESTART_DIV_GC, 0x8);
+
+               AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_FAST_ANT_DIV,
+                                       AR5K_PHY_FAST_ANT_DIV_EN);
+       }
+}
+
+/*
+ * Set antenna operating mode
+ */
+void
+ath5k_hw_set_antenna_mode(struct ath5k_hw *ah, u8 ant_mode)
+{
+       struct ieee80211_channel *channel = ah->ah_current_channel;
+       bool use_def_for_tx, update_def_on_tx, use_def_for_rts, fast_div;
+       bool use_def_for_sg;
+       u8 def_ant, tx_ant, ee_mode;
+       u32 sta_id1 = 0;
+
+       def_ant = ah->ah_def_ant;
+
+       ATH5K_TRACE(ah->ah_sc);
+
+       switch (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;
+       case CHANNEL_B:
+               ee_mode = AR5K_EEPROM_MODE_11B;
+               break;
+       default:
+               ATH5K_ERR(ah->ah_sc,
+                       "invalid channel: %d\n", channel->center_freq);
+               return;
+       }
+
+       switch (ant_mode) {
+       case AR5K_ANTMODE_DEFAULT:
+               tx_ant = 0;
+               use_def_for_tx = false;
+               update_def_on_tx = false;
+               use_def_for_rts = false;
+               use_def_for_sg = false;
+               fast_div = true;
+               break;
+       case AR5K_ANTMODE_FIXED_A:
+               def_ant = 1;
+               tx_ant = 0;
+               use_def_for_tx = true;
+               update_def_on_tx = false;
+               use_def_for_rts = true;
+               use_def_for_sg = true;
+               fast_div = false;
+               break;
+       case AR5K_ANTMODE_FIXED_B:
+               def_ant = 2;
+               tx_ant = 0;
+               use_def_for_tx = true;
+               update_def_on_tx = false;
+               use_def_for_rts = true;
+               use_def_for_sg = true;
+               fast_div = false;
+               break;
+       case AR5K_ANTMODE_SINGLE_AP:
+               def_ant = 1;    /* updated on tx */
+               tx_ant = 0;
+               use_def_for_tx = true;
+               update_def_on_tx = true;
+               use_def_for_rts = true;
+               use_def_for_sg = true;
+               fast_div = true;
+               break;
+       case AR5K_ANTMODE_SECTOR_AP:
+               tx_ant = 1;     /* variable */
+               use_def_for_tx = false;
+               update_def_on_tx = false;
+               use_def_for_rts = true;
+               use_def_for_sg = false;
+               fast_div = false;
+               break;
+       case AR5K_ANTMODE_SECTOR_STA:
+               tx_ant = 1;     /* variable */
+               use_def_for_tx = true;
+               update_def_on_tx = false;
+               use_def_for_rts = true;
+               use_def_for_sg = false;
+               fast_div = true;
+               break;
+       case AR5K_ANTMODE_DEBUG:
+               def_ant = 1;
+               tx_ant = 2;
+               use_def_for_tx = false;
+               update_def_on_tx = false;
+               use_def_for_rts = false;
+               use_def_for_sg = false;
+               fast_div = false;
+               break;
+       default:
+               return;
+       }
+
+       ah->ah_tx_ant = tx_ant;
+       ah->ah_ant_mode = ant_mode;
+
+       sta_id1 |= use_def_for_tx ? AR5K_STA_ID1_DEFAULT_ANTENNA : 0;
+       sta_id1 |= update_def_on_tx ? AR5K_STA_ID1_DESC_ANTENNA : 0;
+       sta_id1 |= use_def_for_rts ? AR5K_STA_ID1_RTS_DEF_ANTENNA : 0;
+       sta_id1 |= use_def_for_sg ? AR5K_STA_ID1_SELFGEN_DEF_ANT : 0;
+
+       AR5K_REG_DISABLE_BITS(ah, AR5K_STA_ID1, AR5K_STA_ID1_ANTENNA_SETTINGS);
+
+       if (sta_id1)
+               AR5K_REG_ENABLE_BITS(ah, AR5K_STA_ID1, sta_id1);
+
+       /* Note: set diversity before default antenna
+        * because it won't work correctly */
+       ath5k_hw_set_fast_div(ah, ee_mode, fast_div);
+       ath5k_hw_set_def_antenna(ah, def_ant);
+}
+
 
 /****************\
 * TX power setup *
@@ -1489,6 +1999,10 @@ ath5k_get_linear_pcdac_min(const u8 *stepL, const u8 *stepR,
        s16 min_pwrL, min_pwrR;
        s16 pwr_i;
 
+       /* Some vendors write the same pcdac value twice !!! */
+       if (stepL[0] == stepL[1] || stepR[0] == stepR[1])
+               return max(pwrL[0], pwrR[0]);
+
        if (pwrL[0] == pwrL[1])
                min_pwrL = pwrL[0];
        else {
@@ -1750,13 +2264,12 @@ done:
  * Get the max edge power for this channel if
  * we have such data from EEPROM's Conformance Test
  * Limits (CTL), and limit max power if needed.
- *
- * FIXME: Only works for world regulatory domains
  */
 static void
 ath5k_get_max_ctl_power(struct ath5k_hw *ah,
                        struct ieee80211_channel *channel)
 {
+       struct ath_regulatory *regulatory = ath5k_hw_regulatory(ah);
        struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
        struct ath5k_edge_power *rep = ee->ee_ctl_pwr;
        u8 *ctl_val = ee->ee_ctl;
@@ -1767,26 +2280,23 @@ ath5k_get_max_ctl_power(struct ath5k_hw *ah,
        u8 ctl_idx = 0xFF;
        u32 target = channel->center_freq;
 
-       /* Find out a CTL for our mode that's not mapped
-        * on a specific reg domain.
-        *
-        * TODO: Map our current reg domain to one of the 3 available
-        * reg domain ids so that we can support more CTLs. */
+       ctl_mode = ath_regd_get_band_ctl(regulatory, channel->band);
+
        switch (channel->hw_value & CHANNEL_MODES) {
        case CHANNEL_A:
-               ctl_mode = AR5K_CTL_11A | AR5K_CTL_NO_REGDOMAIN;
+               ctl_mode |= AR5K_CTL_11A;
                break;
        case CHANNEL_G:
-               ctl_mode = AR5K_CTL_11G | AR5K_CTL_NO_REGDOMAIN;
+               ctl_mode |= AR5K_CTL_11G;
                break;
        case CHANNEL_B:
-               ctl_mode = AR5K_CTL_11B | AR5K_CTL_NO_REGDOMAIN;
+               ctl_mode |= AR5K_CTL_11B;
                break;
        case CHANNEL_T:
-               ctl_mode = AR5K_CTL_TURBO | AR5K_CTL_NO_REGDOMAIN;
+               ctl_mode |= AR5K_CTL_TURBO;
                break;
        case CHANNEL_TG:
-               ctl_mode = AR5K_CTL_TURBOG | AR5K_CTL_NO_REGDOMAIN;
+               ctl_mode |= AR5K_CTL_TURBOG;
                break;
        case CHANNEL_XR:
                /* Fall through */
@@ -2482,8 +2992,19 @@ ath5k_setup_rate_powertable(struct ath5k_hw *ah, u16 max_pwr,
                for (i = 8; i <= 15; i++)
                        rates[i] -= ah->ah_txpower.txp_cck_ofdm_gainf_delta;
 
-       ah->ah_txpower.txp_min_pwr = rates[7];
-       ah->ah_txpower.txp_max_pwr = rates[0];
+       /* Now that we have all rates setup use table offset to
+        * match the power range set by user with the power indices
+        * on PCDAC/PDADC table */
+       for (i = 0; i < 16; i++) {
+               rates[i] += ah->ah_txpower.txp_offset;
+               /* Don't get out of bounds */
+               if (rates[i] > 63)
+                       rates[i] = 63;
+       }
+
+       /* Min/max in 0.25dB units */
+       ah->ah_txpower.txp_min_pwr = 2 * rates[7];
+       ah->ah_txpower.txp_max_pwr = 2 * rates[0];
        ah->ah_txpower.txp_ofdm = rates[7];
 }
 
@@ -2504,8 +3025,6 @@ ath5k_hw_txpower(struct ath5k_hw *ah, struct ieee80211_channel *channel,
                ATH5K_ERR(ah->ah_sc, "invalid tx power: %u\n", txpower);
                return -EINVAL;
        }
-       if (txpower == 0)
-               txpower = AR5K_TUNE_DEFAULT_TXPOWER;
 
        /* Reset TX power values */
        memset(&ah->ah_txpower, 0, sizeof(ah->ah_txpower));
@@ -2591,16 +3110,37 @@ ath5k_hw_txpower(struct ath5k_hw *ah, struct ieee80211_channel *channel,
        return 0;
 }
 
-int ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, u8 mode, u8 txpower)
+int ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, u8 txpower)
 {
        /*Just a try M.F.*/
-       struct ieee80211_channel *channel = &ah->ah_current_channel;
+       struct ieee80211_channel *channel = ah->ah_current_channel;
+       u8 ee_mode;
 
        ATH5K_TRACE(ah->ah_sc);
+
+       switch (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;
+       case CHANNEL_B:
+               ee_mode = AR5K_EEPROM_MODE_11B;
+               break;
+       default:
+               ATH5K_ERR(ah->ah_sc,
+                       "invalid channel: %d\n", channel->center_freq);
+               return -EINVAL;
+       }
+
        ATH5K_DBG(ah->ah_sc, ATH5K_DEBUG_TXPOWER,
                "changing txpower to %d\n", txpower);
 
-       return ath5k_hw_txpower(ah, channel, mode, txpower);
+       return ath5k_hw_txpower(ah, channel, ee_mode, txpower);
 }
 
 #undef _ATH5K_PHY