Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e5e2647f authored by Bob Copeland's avatar Bob Copeland Committed by John W. Linville
Browse files

ath5k: use noise calibration from madwifi hal



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: default avatarBob Copeland <me@bobcopeland.com>
Acked-by: default avatarNick Kossifidis <mickflemm@gmail.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent e307fcf0
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -198,6 +198,7 @@
#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,
+2 −0
Original line number Diff line number Diff line
@@ -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);

+128 −57
Original line number Diff line number Diff line
@@ -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
 *
 * 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)
static s32 ath5k_hw_read_measured_noise_floor(struct ath5k_hw *ah)
{
	int ret;
	unsigned int i;
	s32 noise_floor;
	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];
}

/*
	 * Enable noise floor calibration
 * 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.
 */
	AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL,
				AR5K_PHY_AGCCTL_NF);
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;

	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;
	}
	/* 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");

	/* 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);
		return;
	}

			if (noise_floor <= AR5K_TUNE_NOISE_FLOOR)
	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 @@ static int ath5k_hw_rf511x_calibrate(struct ath5k_hw *ah,
	 * 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);
+4 −7
Original line number Diff line number Diff line
@@ -2033,17 +2033,14 @@
#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_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

/*
+1 −16
Original line number Diff line number Diff line
@@ -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);