/*
 * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting
 * Copyright (c) 2002-2008 Atheros Communications, Inc.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * $FreeBSD: release/9.0.0/sys/dev/ath/ath_hal/ar5212/ar5212_reset.c 207472 2010-05-01 16:36:14Z imp $
 */
#include "opt_ah.h"

#include "ah.h"
#include "ah_internal.h"
#include "ah_devid.h"

#include "ar5212/ar5212.h"
#include "ar5212/ar5212reg.h"
#include "ar5212/ar5212phy.h"

#include "ah_eeprom_v3.h"

/* Additional Time delay to wait after activiting the Base band */
#define BASE_ACTIVATE_DELAY	100	/* 100 usec */
#define PLL_SETTLE_DELAY	300	/* 300 usec */

static HAL_BOOL ar5212SetResetReg(struct ath_hal *, uint32_t resetMask);
/* NB: public for 5312 use */
HAL_BOOL	ar5212IsSpurChannel(struct ath_hal *,
		    const struct ieee80211_channel *);
HAL_BOOL	ar5212ChannelChange(struct ath_hal *,
		    const struct ieee80211_channel *);
int16_t		ar5212GetNf(struct ath_hal *, struct ieee80211_channel *);
HAL_BOOL	ar5212SetBoardValues(struct ath_hal *,
		    const struct ieee80211_channel *);
void		ar5212SetDeltaSlope(struct ath_hal *,
		    const struct ieee80211_channel *);
HAL_BOOL	ar5212SetTransmitPower(struct ath_hal *ah,
		   const struct ieee80211_channel *chan, uint16_t *rfXpdGain);
static HAL_BOOL ar5212SetRateTable(struct ath_hal *, 
		   const struct ieee80211_channel *, int16_t tpcScaleReduction,
		   int16_t powerLimit,
		   HAL_BOOL commit, int16_t *minPower, int16_t *maxPower);
static void ar5212CorrectGainDelta(struct ath_hal *, int twiceOfdmCckDelta);
static void ar5212GetTargetPowers(struct ath_hal *,
		   const struct ieee80211_channel *,
		   const TRGT_POWER_INFO *pPowerInfo, uint16_t numChannels,
		   TRGT_POWER_INFO *pNewPower);
static uint16_t ar5212GetMaxEdgePower(uint16_t channel,
		   const RD_EDGES_POWER  *pRdEdgesPower);
void		ar5212SetRateDurationTable(struct ath_hal *,
		    const struct ieee80211_channel *);
void		ar5212SetIFSTiming(struct ath_hal *,
		    const struct ieee80211_channel *);

/* NB: public for RF backend use */
void		ar5212GetLowerUpperValues(uint16_t value,
		   uint16_t *pList, uint16_t listSize,
		   uint16_t *pLowerValue, uint16_t *pUpperValue);
void		ar5212ModifyRfBuffer(uint32_t *rfBuf, uint32_t reg32,
		   uint32_t numBits, uint32_t firstBit, uint32_t column);

static int
write_common(struct ath_hal *ah, const HAL_INI_ARRAY *ia,
	HAL_BOOL bChannelChange, int writes)
{
#define IS_NO_RESET_TIMER_ADDR(x)                      \
    ( (((x) >= AR_BEACON) && ((x) <= AR_CFP_DUR)) || \
      (((x) >= AR_SLEEP1) && ((x) <= AR_SLEEP3)))
#define	V(r, c)	(ia)->data[((r)*(ia)->cols) + (c)]
	int r;

	/* Write Common Array Parameters */
	for (r = 0; r < ia->rows; r++) {
		uint32_t reg = V(r, 0);
		/* XXX timer/beacon setup registers? */
		/* On channel change, don't reset the PCU registers */
		if (!(bChannelChange && IS_NO_RESET_TIMER_ADDR(reg))) {
			OS_REG_WRITE(ah, reg, V(r, 1));
			DMA_YIELD(writes);
		}
	}
	return writes;
#undef IS_NO_RESET_TIMER_ADDR
#undef V
}

#define IS_DISABLE_FAST_ADC_CHAN(x) (((x) == 2462) || ((x) == 2467))

/*
 * Places the device in and out of reset and then places sane
 * values in the registers based on EEPROM config, initialization
 * vectors (as determined by the mode), and station configuration
 *
 * bChannelChange is used to preserve DMA/PCU registers across
 * a HW Reset during channel change.
 */
HAL_BOOL
ar5212Reset(struct ath_hal *ah, HAL_OPMODE opmode,
	struct ieee80211_channel *chan,
	HAL_BOOL bChannelChange, HAL_STATUS *status)
{
#define	N(a)	(sizeof (a) / sizeof (a[0]))
#define	FAIL(_code)	do { ecode = _code; goto bad; } while (0)
	struct ath_hal_5212 *ahp = AH5212(ah);
	HAL_CHANNEL_INTERNAL *ichan = AH_NULL;
	const HAL_EEPROM *ee;
	uint32_t softLedCfg, softLedState;
	uint32_t saveFrameSeqCount, saveDefAntenna, saveLedState;
	uint32_t macStaId1, synthDelay, txFrm2TxDStart;
	uint16_t rfXpdGain[MAX_NUM_PDGAINS_PER_CHANNEL];
	int16_t cckOfdmPwrDelta = 0;
	u_int modesIndex, freqIndex;
	HAL_STATUS ecode;
	int i, regWrites;
	uint32_t testReg, powerVal;
	int8_t twiceAntennaGain, twiceAntennaReduction;
	uint32_t ackTpcPow, ctsTpcPow, chirpTpcPow;
	HAL_BOOL isBmode = AH_FALSE;

	HALASSERT(ah->ah_magic == AR5212_MAGIC);
	ee = AH_PRIVATE(ah)->ah_eeprom;

	OS_MARK(ah, AH_MARK_RESET, bChannelChange);

	/* Bring out of sleep mode */
	if (!ar5212SetPowerMode(ah, HAL_PM_AWAKE, AH_TRUE)) {
		HALDEBUG(ah, HAL_DEBUG_ANY, "%s: chip did not wakeup\n",
		    __func__);
		FAIL(HAL_EIO);
	}

	/*
	 * Map public channel to private.
	 */
	ichan = ath_hal_checkchannel(ah, chan);
	if (ichan == AH_NULL)
		FAIL(HAL_EINVAL);
	switch (opmode) {
	case HAL_M_STA:
	case HAL_M_IBSS:
	case HAL_M_HOSTAP:
	case HAL_M_MONITOR:
		break;
	default:
		HALDEBUG(ah, HAL_DEBUG_ANY, "%s: invalid operating mode %u\n",
		    __func__, opmode);
		FAIL(HAL_EINVAL);
		break;
	}
	HALASSERT(AH_PRIVATE(ah)->ah_eeversion >= AR_EEPROM_VER3);

	SAVE_CCK(ah, chan, isBmode);

	/* Preserve certain DMA hardware registers on a channel change */
	if (bChannelChange) {
		/*
		 * On Venice, the TSF is almost preserved across a reset;
		 * it requires doubling writes to the RESET_TSF
		 * bit in the AR_BEACON register; it also has the quirk
		 * of the TSF going back in time on the station (station
		 * latches onto the last beacon's tsf during a reset 50%
		 * of the times); the latter is not a problem for adhoc
		 * stations since as long as the TSF is behind, it will
		 * get resynchronized on receiving the next beacon; the
		 * TSF going backwards in time could be a problem for the
		 * sleep operation (supported on infrastructure stations
		 * only) - the best and most general fix for this situation
		 * is to resynchronize the various sleep/beacon timers on
		 * the receipt of the next beacon i.e. when the TSF itself
		 * gets resynchronized to the AP's TSF - power save is
		 * needed to be temporarily disabled until that time
		 *
		 * Need to save the sequence number to restore it after
		 * the reset!
		 */
		saveFrameSeqCount = OS_REG_READ(ah, AR_D_SEQNUM);
	} else
		saveFrameSeqCount = 0;		/* NB: silence compiler */
#if 0
	/*
	 * XXX disable for now; this appears to sometimes cause OFDM
	 * XXX timing error floods when ani is enabled and bg scanning
	 * XXX kicks in
	 */
	/* If the channel change is across the same mode - perform a fast channel change */
	if (IS_2413(ah) || IS_5413(ah)) {
		/*
		 * Fast channel change can only be used when:
		 *  -channel change requested - so it's not the initial reset.
		 *  -it's not a change to the current channel -
		 *	often called when switching modes on a channel
		 *  -the modes of the previous and requested channel are the
		 *	same
		 * XXX opmode shouldn't change either?
		 */
		if (bChannelChange &&
		    (AH_PRIVATE(ah)->ah_curchan != AH_NULL) &&
		    (chan->ic_freq != AH_PRIVATE(ah)->ah_curchan->ic_freq) &&
		    ((chan->ic_flags & IEEE80211_CHAN_ALLTURBO) ==
		     (AH_PRIVATE(ah)->ah_curchan->ic_flags & IEEE80211_CHAN_ALLTURBO))) {
			if (ar5212ChannelChange(ah, chan)) {
				/* If ChannelChange completed - skip the rest of reset */
				/* XXX ani? */
				goto done;
			}
		}
	}
#endif
	/*
	 * Preserve the antenna on a channel change
	 */
	saveDefAntenna = OS_REG_READ(ah, AR_DEF_ANTENNA);
	if (saveDefAntenna == 0)		/* XXX magic constants */
		saveDefAntenna = 1;

	/* Save hardware flag before chip reset clears the register */
	macStaId1 = OS_REG_READ(ah, AR_STA_ID1) & 
		(AR_STA_ID1_BASE_RATE_11B | AR_STA_ID1_USE_DEFANT);

	/* Save led state from pci config register */
	saveLedState = OS_REG_READ(ah, AR_PCICFG) &
		(AR_PCICFG_LEDCTL | AR_PCICFG_LEDMODE | AR_PCICFG_LEDBLINK |
		 AR_PCICFG_LEDSLOW);
	softLedCfg = OS_REG_READ(ah, AR_GPIOCR);
	softLedState = OS_REG_READ(ah, AR_GPIODO);

	ar5212RestoreClock(ah, opmode);		/* move to refclk operation */

	/*
	 * Adjust gain parameters before reset if
	 * there's an outstanding gain updated.
	 */
	(void) ar5212GetRfgain(ah);

	if (!ar5212ChipReset(ah, chan)) {
		HALDEBUG(ah, HAL_DEBUG_ANY, "%s: chip reset failed\n", __func__);
		FAIL(HAL_EIO);
	}

	/* Setup the indices for the next set of register array writes */
	if (IEEE80211_IS_CHAN_2GHZ(chan)) {
		freqIndex  = 2;
		if (IEEE80211_IS_CHAN_108G(chan))
			modesIndex = 5;
		else if (IEEE80211_IS_CHAN_G(chan))
			modesIndex = 4;
		else if (IEEE80211_IS_CHAN_B(chan))
			modesIndex = 3;
		else {
			HALDEBUG(ah, HAL_DEBUG_ANY,
			    "%s: invalid channel %u/0x%x\n",
			    __func__, chan->ic_freq, chan->ic_flags);
			FAIL(HAL_EINVAL);
		}
	} else {
		freqIndex  = 1;
		if (IEEE80211_IS_CHAN_TURBO(chan))
			modesIndex = 2;
		else if (IEEE80211_IS_CHAN_A(chan))
			modesIndex = 1;
		else {
			HALDEBUG(ah, HAL_DEBUG_ANY,
			    "%s: invalid channel %u/0x%x\n",
			    __func__, chan->ic_freq, chan->ic_flags);
			FAIL(HAL_EINVAL);
		}
	}

	OS_MARK(ah, AH_MARK_RESET_LINE, __LINE__);

	/* Set correct Baseband to analog shift setting to access analog chips. */
	OS_REG_WRITE(ah, AR_PHY(0), 0x00000007);

	regWrites = ath_hal_ini_write(ah, &ahp->ah_ini_modes, modesIndex, 0);
	regWrites = write_common(ah, &ahp->ah_ini_common, bChannelChange,
		regWrites);
#ifdef AH_RXCFG_SDMAMW_4BYTES
	/*
	 * Nala doesn't work with 128 byte bursts on pb42(hydra) (ar71xx),
	 * use 4 instead.  Enabling it on all platforms would hurt performance,
	 * so we only enable it on the ones that are affected by it.
	 */
	OS_REG_WRITE(ah, AR_RXCFG, 0);
#endif
	ahp->ah_rfHal->writeRegs(ah, modesIndex, freqIndex, regWrites);

	OS_MARK(ah, AH_MARK_RESET_LINE, __LINE__);

	if (IEEE80211_IS_CHAN_HALF(chan) || IEEE80211_IS_CHAN_QUARTER(chan)) {
		ar5212SetIFSTiming(ah, chan);
		if (IS_5413(ah)) {
			/*
			 * Force window_length for 1/2 and 1/4 rate channels,
			 * the ini file sets this to zero otherwise.
			 */
			OS_REG_RMW_FIELD(ah, AR_PHY_FRAME_CTL,
				AR_PHY_FRAME_CTL_WINLEN, 3);
		}
	}

	/* Overwrite INI values for revised chipsets */
	if (AH_PRIVATE(ah)->ah_phyRev >= AR_PHY_CHIP_ID_REV_2) {
		/* ADC_CTL */
		OS_REG_WRITE(ah, AR_PHY_ADC_CTL,
			SM(2, AR_PHY_ADC_CTL_OFF_INBUFGAIN) |
			SM(2, AR_PHY_ADC_CTL_ON_INBUFGAIN) |
			AR_PHY_ADC_CTL_OFF_PWDDAC |
			AR_PHY_ADC_CTL_OFF_PWDADC);

		/* TX_PWR_ADJ */
		if (ichan->channel == 2484) {
			cckOfdmPwrDelta = SCALE_OC_DELTA(
			    ee->ee_cckOfdmPwrDelta -
			    ee->ee_scaledCh14FilterCckDelta);
		} else {
			cckOfdmPwrDelta = SCALE_OC_DELTA(
			    ee->ee_cckOfdmPwrDelta);
		}

		if (IEEE80211_IS_CHAN_G(chan)) {
		    OS_REG_WRITE(ah, AR_PHY_TXPWRADJ,
			SM((ee->ee_cckOfdmPwrDelta*-1),
			    AR_PHY_TXPWRADJ_CCK_GAIN_DELTA) |
			SM((cckOfdmPwrDelta*-1),
			    AR_PHY_TXPWRADJ_CCK_PCDAC_INDEX));
		} else {
			OS_REG_WRITE(ah, AR_PHY_TXPWRADJ, 0);
		}

		/* Add barker RSSI thresh enable as disabled */
		OS_REG_CLR_BIT(ah, AR_PHY_DAG_CTRLCCK,
			AR_PHY_DAG_CTRLCCK_EN_RSSI_THR);
		OS_REG_RMW_FIELD(ah, AR_PHY_DAG_CTRLCCK,
			AR_PHY_DAG_CTRLCCK_RSSI_THR, 2);

		/* Set the mute mask to the correct default */
		OS_REG_WRITE(ah, AR_SEQ_MASK, 0x0000000F);
	}

	if (AH_PRIVATE(ah)->ah_phyRev >= AR_PHY_CHIP_ID_REV_3) {
		/* Clear reg to alllow RX_CLEAR line debug */
		OS_REG_WRITE(ah, AR_PHY_BLUETOOTH,  0);
	}
	if (AH_PRIVATE(ah)->ah_phyRev >= AR_PHY_CHIP_ID_REV_4) {
#ifdef notyet
		/* Enable burst prefetch for the data queues */
		OS_REG_RMW_FIELD(ah, AR_D_FPCTL, ... );
		/* Enable double-buffering */
		OS_REG_CLR_BIT(ah, AR_TXCFG, AR_TXCFG_DBL_BUF_DIS);
#endif
	}

	/* Set ADC/DAC select values */
	OS_REG_WRITE(ah, AR_PHY_SLEEP_SCAL, 0x0e);

	if (IS_5413(ah) || IS_2417(ah)) {
		uint32_t newReg = 1;
		if (IS_DISABLE_FAST_ADC_CHAN(ichan->channel))
			newReg = 0;
		/* As it's a clock changing register, only write when the value needs to be changed */
		if (OS_REG_READ(ah, AR_PHY_FAST_ADC) != newReg)
			OS_REG_WRITE(ah, AR_PHY_FAST_ADC, newReg);
	}

	/* Setup the transmit power values. */
	if (!ar5212SetTransmitPower(ah, chan, rfXpdGain)) {
		HALDEBUG(ah, HAL_DEBUG_ANY,
		    "%s: error init'ing transmit power\n", __func__);
		FAIL(HAL_EIO);
	}

	/* Write the analog registers */
	if (!ahp->ah_rfHal->setRfRegs(ah, chan, modesIndex, rfXpdGain)) {
		HALDEBUG(ah, HAL_DEBUG_ANY, "%s: ar5212SetRfRegs failed\n",
		    __func__);
		FAIL(HAL_EIO);
	}

	/* Write delta slope for OFDM enabled modes (A, G, Turbo) */
	if (IEEE80211_IS_CHAN_OFDM(chan)) {
		if (IS_5413(ah) ||
		    AH_PRIVATE(ah)->ah_eeversion >= AR_EEPROM_VER5_3)
			ar5212SetSpurMitigation(ah, chan);
		ar5212SetDeltaSlope(ah, chan);
	}

	/* Setup board specific options for EEPROM version 3 */
	if (!ar5212SetBoardValues(ah, chan)) {
		HALDEBUG(ah, HAL_DEBUG_ANY,
		    "%s: error setting board options\n", __func__);
		FAIL(HAL_EIO);
	}

	/* Restore certain DMA hardware registers on a channel change */
	if (bChannelChange)
		OS_REG_WRITE(ah, AR_D_SEQNUM, saveFrameSeqCount);

	OS_MARK(ah, AH_MARK_RESET_LINE, __LINE__);

	OS_REG_WRITE(ah, AR_STA_ID0, LE_READ_4(ahp->ah_macaddr));
	OS_REG_WRITE(ah, AR_STA_ID1, LE_READ_2(ahp->ah_macaddr + 4)
		| macStaId1
		| AR_STA_ID1_RTS_USE_DEF
		| ahp->ah_staId1Defaults
	);
	ar5212SetOperatingMode(ah, opmode);

	/* Set Venice BSSID mask according to current state */
	OS_REG_WRITE(ah, AR_BSSMSKL, LE_READ_4(ahp->ah_bssidmask));
	OS_REG_WRITE(ah, AR_BSSMSKU, LE_READ_2(ahp->ah_bssidmask + 4));

	/* Restore previous led state */
	OS_REG_WRITE(ah, AR_PCICFG, OS_REG_READ(ah, AR_PCICFG) | saveLedState);

	/* Restore soft Led state to GPIO */
	OS_REG_WRITE(ah, AR_GPIOCR, softLedCfg);
	OS_REG_WRITE(ah, AR_GPIODO, softLedState);

	/* Restore previous antenna */
	OS_REG_WRITE(ah, AR_DEF_ANTENNA, saveDefAntenna);

	/* then our BSSID */
	OS_REG_WRITE(ah, AR_BSS_ID0, LE_READ_4(ahp->ah_bssid));
	OS_REG_WRITE(ah, AR_BSS_ID1, LE_READ_2(ahp->ah_bssid + 4));

	/* Restore bmiss rssi & count thresholds */
	OS_REG_WRITE(ah, AR_RSSI_THR, ahp->ah_rssiThr);

	OS_REG_WRITE(ah, AR_ISR, ~0);		/* cleared on write */

	if (!ar5212SetChannel(ah, chan))
		FAIL(HAL_EIO);

	OS_MARK(ah, AH_MARK_RESET_LINE, __LINE__);

	ar5212SetCoverageClass(ah, AH_PRIVATE(ah)->ah_coverageClass, 1);

	ar5212SetRateDurationTable(ah, chan);

	/* Set Tx frame start to tx data start delay */
	if (IS_RAD5112_ANY(ah) &&
	    (IEEE80211_IS_CHAN_HALF(chan) || IEEE80211_IS_CHAN_QUARTER(chan))) {
		txFrm2TxDStart = 
			IEEE80211_IS_CHAN_HALF(chan) ?
					TX_FRAME_D_START_HALF_RATE:
					TX_FRAME_D_START_QUARTER_RATE;
		OS_REG_RMW_FIELD(ah, AR_PHY_TX_CTL, 
			AR_PHY_TX_FRAME_TO_TX_DATA_START, txFrm2TxDStart);
	}

	/*
	 * Setup fast diversity.
	 * Fast diversity can be enabled or disabled via regadd.txt.
	 * Default is enabled.
	 * For reference,
	 *    Disable: reg        val
	 *             0x00009860 0x00009d18 (if 11a / 11g, else no change)
	 *             0x00009970 0x192bb514
	 *             0x0000a208 0xd03e4648
	 *
	 *    Enable:  0x00009860 0x00009d10 (if 11a / 11g, else no change)
	 *             0x00009970 0x192fb514
	 *             0x0000a208 0xd03e6788
	 */

	/* XXX Setup pre PHY ENABLE EAR additions */
	/*
	 * Wait for the frequency synth to settle (synth goes on
	 * via AR_PHY_ACTIVE_EN).  Read the phy active delay register.
	 * Value is in 100ns increments.
	 */
	synthDelay = OS_REG_READ(ah, AR_PHY_RX_DELAY) & AR_PHY_RX_DELAY_DELAY;
	if (IEEE80211_IS_CHAN_B(chan)) {
		synthDelay = (4 * synthDelay) / 22;
	} else {
		synthDelay /= 10;
	}

	/* Activate the PHY (includes baseband activate and synthesizer on) */
	OS_REG_WRITE(ah, AR_PHY_ACTIVE, AR_PHY_ACTIVE_EN);

	/* 
	 * There is an issue if the AP starts the calibration before
	 * the base band timeout completes.  This could result in the
	 * rx_clear false triggering.  As a workaround we add delay an
	 * extra BASE_ACTIVATE_DELAY usecs to ensure this condition
	 * does not happen.
	 */
	if (IEEE80211_IS_CHAN_HALF(chan)) {
		OS_DELAY((synthDelay << 1) + BASE_ACTIVATE_DELAY);
	} else if (IEEE80211_IS_CHAN_QUARTER(chan)) {
		OS_DELAY((synthDelay << 2) + BASE_ACTIVATE_DELAY);
	} else {
		OS_DELAY(synthDelay + BASE_ACTIVATE_DELAY);
	}

	/*
	 * The udelay method is not reliable with notebooks.
	 * Need to check to see if the baseband is ready
	 */
	testReg = OS_REG_READ(ah, AR_PHY_TESTCTRL);
	/* Selects the Tx hold */
	OS_REG_WRITE(ah, AR_PHY_TESTCTRL, AR_PHY_TESTCTRL_TXHOLD);
	i = 0;
	while ((i++ < 20) &&
	       (OS_REG_READ(ah, 0x9c24) & 0x10)) /* test if baseband not ready */		OS_DELAY(200);
	OS_REG_WRITE(ah, AR_PHY_TESTCTRL, testReg);

	/* Calibrate the AGC and start a NF calculation */
	OS_REG_WRITE(ah, AR_PHY_AGC_CONTROL,
		  OS_REG_READ(ah, AR_PHY_AGC_CONTROL)
		| AR_PHY_AGC_CONTROL_CAL
		| AR_PHY_AGC_CONTROL_NF);

	if (!IEEE80211_IS_CHAN_B(chan) && ahp->ah_bIQCalibration != IQ_CAL_DONE) {
		/* Start IQ calibration w/ 2^(INIT_IQCAL_LOG_COUNT_MAX+1) samples */
		OS_REG_RMW_FIELD(ah, AR_PHY_TIMING_CTRL4, 
			AR_PHY_TIMING_CTRL4_IQCAL_LOG_COUNT_MAX,
			INIT_IQCAL_LOG_COUNT_MAX);
		OS_REG_SET_BIT(ah, AR_PHY_TIMING_CTRL4,
			AR_PHY_TIMING_CTRL4_DO_IQCAL);
		ahp->ah_bIQCalibration = IQ_CAL_RUNNING;
	} else
		ahp->ah_bIQCalibration = IQ_CAL_INACTIVE;

	/* Setup compression registers */
	ar5212SetCompRegs(ah);

	/* Set 1:1 QCU to DCU mapping for all queues */
	for (i = 0; i < AR_NUM_DCU; i++)
		OS_REG_WRITE(ah, AR_DQCUMASK(i), 1 << i);

	ahp->ah_intrTxqs = 0;
	for (i = 0; i < AH_PRIVATE(ah)->ah_caps.halTotalQueues; i++)
		ar5212ResetTxQueue(ah, i);

	/*
	 * Setup interrupt handling.  Note that ar5212ResetTxQueue
	 * manipulates the secondary IMR's as queues are enabled
	 * and disabled.  This is done with RMW ops to insure the
	 * settings we make here are preserved.
	 */
	ahp->ah_maskReg = AR_IMR_TXOK | AR_IMR_TXERR | AR_IMR_TXURN
			| AR_IMR_RXOK | AR_IMR_RXERR | AR_IMR_RXORN
			| AR_IMR_HIUERR
			;
	if (opmode == HAL_M_HOSTAP)
		ahp->ah_maskReg |= AR_IMR_MIB;
	OS_REG_WRITE(ah, AR_IMR, ahp->ah_maskReg);
	/* Enable bus errors that are OR'd to set the HIUERR bit */
	OS_REG_WRITE(ah, AR_IMR_S2,
		OS_REG_READ(ah, AR_IMR_S2)
		| AR_IMR_S2_MCABT | AR_IMR_S2_SSERR | AR_IMR_S2_DPERR);

	if (AH_PRIVATE(ah)->ah_rfkillEnabled)
		ar5212EnableRfKill(ah);

	if (!ath_hal_wait(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_CAL, 0)) {
		HALDEBUG(ah, HAL_DEBUG_ANY,
		    "%s: offset calibration failed to complete in 1ms;"
		    " noisy environment?\n", __func__);
	}

	/*
	 * Set clocks back to 32kHz if they had been using refClk, then
	 * use an external 32kHz crystal when sleeping, if one exists.
	 */
	ar5212SetupClock(ah, opmode);

	/*
	 * Writing to AR_BEACON will start timers. Hence it should
	 * be the last register to be written. Do not reset tsf, do
	 * not enable beacons at this point, but preserve other values
	 * like beaconInterval.
	 */
	OS_REG_WRITE(ah, AR_BEACON,
		(OS_REG_READ(ah, AR_BEACON) &~ (AR_BEACON_EN | AR_BEACON_RESET_TSF)));

	/* XXX Setup post reset EAR additions */

	/* QoS support */
	if (AH_PRIVATE(ah)->ah_macVersion > AR_SREV_VERSION_VENICE ||
	    (AH_PRIVATE(ah)->ah_macVersion == AR_SREV_VERSION_VENICE &&
	     AH_PRIVATE(ah)->ah_macRev >= AR_SREV_GRIFFIN_LITE)) {
		OS_REG_WRITE(ah, AR_QOS_CONTROL, 0x100aa);	/* XXX magic */
		OS_REG_WRITE(ah, AR_QOS_SELECT, 0x3210);	/* XXX magic */
	}

	/* Turn on NOACK Support for QoS packets */
	OS_REG_WRITE(ah, AR_NOACK,
		SM(2, AR_NOACK_2BIT_VALUE) |
		SM(5, AR_NOACK_BIT_OFFSET) |
		SM(0, AR_NOACK_BYTE_OFFSET));

	/* Get Antenna Gain reduction */
	if (IEEE80211_IS_CHAN_5GHZ(chan)) {
		ath_hal_eepromGet(ah, AR_EEP_ANTGAINMAX_5, &twiceAntennaGain);
	} else {
		ath_hal_eepromGet(ah, AR_EEP_ANTGAINMAX_2, &twiceAntennaGain);
	}
	twiceAntennaReduction =
		ath_hal_getantennareduction(ah, chan, twiceAntennaGain);

	/* TPC for self-generated frames */

	ackTpcPow = MS(ahp->ah_macTPC, AR_TPC_ACK);
	if ((ackTpcPow-ahp->ah_txPowerIndexOffset) > chan->ic_maxpower)
		ackTpcPow = chan->ic_maxpower+ahp->ah_txPowerIndexOffset;

	if (ackTpcPow > (2*chan->ic_maxregpower - twiceAntennaReduction))
		ackTpcPow = (2*chan->ic_maxregpower - twiceAntennaReduction)
			+ ahp->ah_txPowerIndexOffset;

	ctsTpcPow = MS(ahp->ah_macTPC, AR_TPC_CTS);
	if ((ctsTpcPow-ahp->ah_txPowerIndexOffset) > chan->ic_maxpower)
		ctsTpcPow = chan->ic_maxpower+ahp->ah_txPowerIndexOffset;

	if (ctsTpcPow > (2*chan->ic_maxregpower - twiceAntennaReduction))
		ctsTpcPow = (2*chan->ic_maxregpower - twiceAntennaReduction)
			+ ahp->ah_txPowerIndexOffset;

	chirpTpcPow = MS(ahp->ah_macTPC, AR_TPC_CHIRP);
	if ((chirpTpcPow-ahp->ah_txPowerIndexOffset) > chan->ic_maxpower)
		chirpTpcPow = chan->ic_maxpower+ahp->ah_txPowerIndexOffset;

	if (chirpTpcPow > (2*chan->ic_maxregpower - twiceAntennaReduction))
		chirpTpcPow = (2*chan->ic_maxregpower - twiceAntennaReduction)
			+ ahp->ah_txPowerIndexOffset;

	if (ackTpcPow > 63)
		ackTpcPow = 63;
	if (ctsTpcPow > 63)
		ctsTpcPow = 63;
	if (chirpTpcPow > 63)
		chirpTpcPow = 63;

	powerVal = SM(ackTpcPow, AR_TPC_ACK) |
		SM(ctsTpcPow, AR_TPC_CTS) |
		SM(chirpTpcPow, AR_TPC_CHIRP);

	OS_REG_WRITE(ah, AR_TPC, powerVal);

	/* Restore user-specified settings */
	if (ahp->ah_miscMode != 0)
		OS_REG_WRITE(ah, AR_MISC_MODE, ahp->ah_miscMode);
	if (ahp->ah_sifstime != (u_int) -1)
		ar5212SetSifsTime(ah, ahp->ah_sifstime);
	if (ahp->ah_slottime != (u_int) -1)
		ar5212SetSlotTime(ah, ahp->ah_slottime);
	if (ahp->ah_acktimeout != (u_int) -1)
		ar5212SetAckTimeout(ah, ahp->ah_acktimeout);
	if (ahp->ah_ctstimeout != (u_int) -1)
		ar5212SetCTSTimeout(ah, ahp->ah_ctstimeout);
	if (AH_PRIVATE(ah)->ah_diagreg != 0)
		OS_REG_WRITE(ah, AR_DIAG_SW, AH_PRIVATE(ah)->ah_diagreg);

	AH_PRIVATE(ah)->ah_opmode = opmode;	/* record operating mode */
#if 0
done:
#endif
	if (bChannelChange && !IEEE80211_IS_CHAN_DFS(chan)) 
		chan->ic_state &= ~IEEE80211_CHANSTATE_CWINT;

	HALDEBUG(ah, HAL_DEBUG_RESET, "%s: done\n", __func__);

	RESTORE_CCK(ah, chan, isBmode);
	
	OS_MARK(ah, AH_MARK_RESET_DONE, 0);

	return AH_TRUE;
bad:
	RESTORE_CCK(ah, chan, isBmode);

	OS_MARK(ah, AH_MARK_RESET_DONE, ecode);
	if (status != AH_NULL)
		*status = ecode;
	return AH_FALSE;
#undef FAIL
#undef N
}

/*
 * Call the rf backend to change the channel.
 */
HAL_BOOL
ar5212SetChannel(struct ath_hal *ah, const struct ieee80211_channel *chan)
{
	struct ath_hal_5212 *ahp = AH5212(ah);

	/* Change the synth */
	if (!ahp->ah_rfHal->setChannel(ah, chan))
		return AH_FALSE;
	return AH_TRUE;
}

/*
 * This channel change evaluates whether the selected hardware can
 * perform a synthesizer-only channel change (no reset).  If the
 * TX is not stopped, or the RFBus cannot be granted in the given
 * time, the function returns false as a reset is necessary
 */
HAL_BOOL
ar5212ChannelChange(struct ath_hal *ah, const struct ieee80211_channel *chan)
{
	uint32_t       ulCount;
	uint32_t   data, synthDelay, qnum;
	uint16_t   rfXpdGain[MAX_NUM_PDGAINS_PER_CHANNEL];
	HAL_BOOL    txStopped = AH_TRUE;
	HAL_CHANNEL_INTERNAL *ichan;

	/*
	 * Map public channel to private.
	 */
	ichan = ath_hal_checkchannel(ah, chan);

	/* TX must be stopped or RF Bus grant will not work */
	for (qnum = 0; qnum < AH_PRIVATE(ah)->ah_caps.halTotalQueues; qnum++) {
		if (ar5212NumTxPending(ah, qnum)) {
			txStopped = AH_FALSE;
			break;
		}
	}
	if (!txStopped)
		return AH_FALSE;

	/* Kill last Baseband Rx Frame */
	OS_REG_WRITE(ah, AR_PHY_RFBUS_REQ, AR_PHY_RFBUS_REQ_REQUEST); /* Request analog bus grant */
	for (ulCount = 0; ulCount < 100; ulCount++) {
		if (OS_REG_READ(ah, AR_PHY_RFBUS_GNT))
			break;
		OS_DELAY(5);
	}
	if (ulCount >= 100)
		return AH_FALSE;

	/* Change the synth */
	if (!ar5212SetChannel(ah, chan))
		return AH_FALSE;

	/*
	 * Wait for the frequency synth to settle (synth goes on via PHY_ACTIVE_EN).
	 * Read the phy active delay register. Value is in 100ns increments.
	 */
	data = OS_REG_READ(ah, AR_PHY_RX_DELAY) & AR_PHY_RX_DELAY_DELAY;
	if (IEEE80211_IS_CHAN_B(chan)) {
		synthDelay = (4 * data) / 22;
	} else {
		synthDelay = data / 10;
	}
	OS_DELAY(synthDelay + BASE_ACTIVATE_DELAY);

	/* Setup the transmit power values. */
	if (!ar5212SetTransmitPower(ah, chan, rfXpdGain)) {
		HALDEBUG(ah, HAL_DEBUG_ANY,
		    "%s: error init'ing transmit power\n", __func__);
		return AH_FALSE;
	}

	/* Write delta slope for OFDM enabled modes (A, G, Turbo) */
	if (IEEE80211_IS_CHAN_OFDM(chan)) {
		if (IS_5413(ah) ||
		    AH_PRIVATE(ah)->ah_eeversion >= AR_EEPROM_VER5_3)
			ar5212SetSpurMitigation(ah, chan);
		ar5212SetDeltaSlope(ah, chan);
	}

	/* Release the RFBus Grant */
	OS_REG_WRITE(ah, AR_PHY_RFBUS_REQ, 0);

	/* Start Noise Floor Cal */
	OS_REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NF);
	return AH_TRUE;
}

void
ar5212SetOperatingMode(struct ath_hal *ah, int opmode)
{
	uint32_t val;

	val = OS_REG_READ(ah, AR_STA_ID1);
	val &= ~(AR_STA_ID1_STA_AP | AR_STA_ID1_ADHOC);
	switch (opmode) {
	case HAL_M_HOSTAP:
		OS_REG_WRITE(ah, AR_STA_ID1, val | AR_STA_ID1_STA_AP
					| AR_STA_ID1_KSRCH_MODE);
		OS_REG_CLR_BIT(ah, AR_CFG, AR_CFG_AP_ADHOC_INDICATION);
		break;
	case HAL_M_IBSS:
		OS_REG_WRITE(ah, AR_STA_ID1, val | AR_STA_ID1_ADHOC
					| AR_STA_ID1_KSRCH_MODE);
		OS_REG_SET_BIT(ah, AR_CFG, AR_CFG_AP_ADHOC_INDICATION);
		break;
	case HAL_M_STA:
	case HAL_M_MONITOR:
		OS_REG_WRITE(ah, AR_STA_ID1, val | AR_STA_ID1_KSRCH_MODE);
		break;
	}
}

/*
 * Places the PHY and Radio chips into reset.  A full reset
 * must be called to leave this state.  The PCI/MAC/PCU are
 * not placed into reset as we must receive interrupt to
 * re-enable the hardware.
 */
HAL_BOOL
ar5212PhyDisable(struct ath_hal *ah)
{
	return ar5212SetResetReg(ah, AR_RC_BB);
}

/*
 * Places all of hardware into reset
 */
HAL_BOOL
ar5212Disable(struct ath_hal *ah)
{
	if (!ar5212SetPowerMode(ah, HAL_PM_AWAKE, AH_TRUE))
		return AH_FALSE;
	/*
	 * Reset the HW - PCI must be reset after the rest of the
	 * device has been reset.
	 */
	return ar5212SetResetReg(ah, AR_RC_MAC | AR_RC_BB | AR_RC_PCI);
}

/*
 * Places the hardware into reset and then pulls it out of reset
 *
 * TODO: Only write the PLL if we're changing to or from CCK mode
 * 
 * WARNING: The order of the PLL and mode registers must be correct.
 */
HAL_BOOL
ar5212ChipReset(struct ath_hal *ah, const struct ieee80211_channel *chan)
{

	OS_MARK(ah, AH_MARK_CHIPRESET, chan ? chan->ic_freq : 0);

	/*
	 * Reset the HW - PCI must be reset after the rest of the
	 * device has been reset
	 */
	if (!ar5212SetResetReg(ah, AR_RC_MAC | AR_RC_BB | AR_RC_PCI))
		return AH_FALSE;

	/* Bring out of sleep mode (AGAIN) */
	if (!ar5212SetPowerMode(ah, HAL_PM_AWAKE, AH_TRUE))
		return AH_FALSE;

	/* Clear warm reset register */
	if (!ar5212SetResetReg(ah, 0))
		return AH_FALSE;

	/*
	 * Perform warm reset before the mode/PLL/turbo registers
	 * are changed in order to deactivate the radio.  Mode changes
	 * with an active radio can result in corrupted shifts to the
	 * radio device.
	 */

	/*
	 * Set CCK and Turbo modes correctly.
	 */
	if (chan != AH_NULL) {		/* NB: can be null during attach */
		uint32_t rfMode, phyPLL = 0, curPhyPLL, turbo;

		if (IS_5413(ah)) {	/* NB: =>'s 5424 also */
			rfMode = AR_PHY_MODE_AR5112;
			if (IEEE80211_IS_CHAN_HALF(chan))
				rfMode |= AR_PHY_MODE_HALF;
			else if (IEEE80211_IS_CHAN_QUARTER(chan))
				rfMode |= AR_PHY_MODE_QUARTER;

			if (IEEE80211_IS_CHAN_CCK(chan))
				phyPLL = AR_PHY_PLL_CTL_44_5112;
			else
				phyPLL = AR_PHY_PLL_CTL_40_5413;
		} else if (IS_RAD5111(ah)) {
			rfMode = AR_PHY_MODE_AR5111;
			if (IEEE80211_IS_CHAN_CCK(chan))
				phyPLL = AR_PHY_PLL_CTL_44;
			else
				phyPLL = AR_PHY_PLL_CTL_40;
			if (IEEE80211_IS_CHAN_HALF(chan))
				phyPLL = AR_PHY_PLL_CTL_HALF;
			else if (IEEE80211_IS_CHAN_QUARTER(chan))
				phyPLL = AR_PHY_PLL_CTL_QUARTER;
		} else {		/* 5112, 2413, 2316, 2317 */
			rfMode = AR_PHY_MODE_AR5112;
			if (IEEE80211_IS_CHAN_CCK(chan))
				phyPLL = AR_PHY_PLL_CTL_44_5112;
			else
				phyPLL = AR_PHY_PLL_CTL_40_5112;
			if (IEEE80211_IS_CHAN_HALF(chan))
				phyPLL |= AR_PHY_PLL_CTL_HALF;
			else if (IEEE80211_IS_CHAN_QUARTER(chan))
				phyPLL |= AR_PHY_PLL_CTL_QUARTER;
		}
		if (IEEE80211_IS_CHAN_G(chan))
			rfMode |= AR_PHY_MODE_DYNAMIC;
		else if (IEEE80211_IS_CHAN_OFDM(chan))
			rfMode |= AR_PHY_MODE_OFDM;
		else
			rfMode |= AR_PHY_MODE_CCK;
		if (IEEE80211_IS_CHAN_5GHZ(chan))
			rfMode |= AR_PHY_MODE_RF5GHZ;
		else
			rfMode |= AR_PHY_MODE_RF2GHZ;
		turbo = IEEE80211_IS_CHAN_TURBO(chan) ?
			(AR_PHY_FC_TURBO_MODE | AR_PHY_FC_TURBO_SHORT) : 0;
		curPhyPLL = OS_REG_READ(ah, AR_PHY_PLL_CTL);
		/*
		 * PLL, Mode, and Turbo values must be written in the correct
		 * order to ensure:
		 * - The PLL cannot be set to 44 unless the CCK or DYNAMIC
		 *   mode bit is set
		 * - Turbo cannot be set at the same time as CCK or DYNAMIC
		 */
		if (IEEE80211_IS_CHAN_CCK(chan)) {
			OS_REG_WRITE(ah, AR_PHY_TURBO, turbo);
			OS_REG_WRITE(ah, AR_PHY_MODE, rfMode);
			if (curPhyPLL != phyPLL) {
				OS_REG_WRITE(ah,  AR_PHY_PLL_CTL,  phyPLL);
				/* Wait for the PLL to settle */
				OS_DELAY(PLL_SETTLE_DELAY);
			}
		} else {
			if (curPhyPLL != phyPLL) {
				OS_REG_WRITE(ah,  AR_PHY_PLL_CTL,  phyPLL);
				/* Wait for the PLL to settle */
				OS_DELAY(PLL_SETTLE_DELAY);
			}
			OS_REG_WRITE(ah, AR_PHY_TURBO, turbo);
			OS_REG_WRITE(ah, AR_PHY_MODE, rfMode);
		}
	}
	return AH_TRUE;
}

/*
 * Recalibrate the lower PHY chips to account for temperature/environment
 * changes.
 */
HAL_BOOL
ar5212PerCalibrationN(struct ath_hal *ah,
	struct ieee80211_channel *chan,
	u_int chainMask, HAL_BOOL longCal, HAL_BOOL *isCalDone)
{
#define IQ_CAL_TRIES    10
	struct ath_hal_5212 *ahp = AH5212(ah);
	HAL_CHANNEL_INTERNAL *ichan;
	int32_t qCoff, qCoffDenom;
	int32_t iqCorrMeas, iCoff, iCoffDenom;
	uint32_t powerMeasQ, powerMeasI;
	HAL_BOOL isBmode = AH_FALSE;

	OS_MARK(ah, AH_MARK_PERCAL, chan->ic_freq);
	*isCalDone = AH_FALSE;
	ichan = ath_hal_checkchannel(ah, chan);
	if (ichan == AH_NULL) {
		HALDEBUG(ah, HAL_DEBUG_ANY,
		    "%s: invalid channel %u/0x%x; no mapping\n",
		    __func__, chan->ic_freq, chan->ic_flags);
		return AH_FALSE;
	}
	SAVE_CCK(ah, chan, isBmode);

	if (ahp->ah_bIQCalibration == IQ_CAL_DONE ||
	    ahp->ah_bIQCalibration == IQ_CAL_INACTIVE)
		*isCalDone = AH_TRUE;

	/* IQ calibration in progress. Check to see if it has finished. */
	if (ahp->ah_bIQCalibration == IQ_CAL_RUNNING &&
	    !(OS_REG_READ(ah, AR_PHY_TIMING_CTRL4) & AR_PHY_TIMING_CTRL4_DO_IQCAL)) {
		int i;

		/* IQ Calibration has finished. */
		ahp->ah_bIQCalibration = IQ_CAL_INACTIVE;
		*isCalDone = AH_TRUE;

		/* workaround for misgated IQ Cal results */
		i = 0;
		do {
			/* Read calibration results. */
			powerMeasI = OS_REG_READ(ah, AR_PHY_IQCAL_RES_PWR_MEAS_I);
			powerMeasQ = OS_REG_READ(ah, AR_PHY_IQCAL_RES_PWR_MEAS_Q);
			iqCorrMeas = OS_REG_READ(ah, AR_PHY_IQCAL_RES_IQ_CORR_MEAS);
			if (powerMeasI && powerMeasQ)
				break;
			/* Do we really need this??? */
			OS_REG_SET_BIT(ah, AR_PHY_TIMING_CTRL4,
			    AR_PHY_TIMING_CTRL4_DO_IQCAL);
		} while (++i < IQ_CAL_TRIES);

		HALDEBUG(ah, HAL_DEBUG_PERCAL,
		    "%s: IQ cal finished: %d tries\n", __func__, i);
		HALDEBUG(ah, HAL_DEBUG_PERCAL,
		    "%s: powerMeasI %u powerMeasQ %u iqCorrMeas %d\n",
		    __func__, powerMeasI, powerMeasQ, iqCorrMeas);

		/*
		 * Prescale these values to remove 64-bit operation
		 * requirement at the loss of a little precision.
		 */
		iCoffDenom = (powerMeasI / 2 + powerMeasQ / 2) / 128;
		qCoffDenom = powerMeasQ / 128;

		/* Protect against divide-by-0 and loss of sign bits. */
		if (iCoffDenom != 0 && qCoffDenom >= 2) {
			iCoff = (int8_t)(-iqCorrMeas) / iCoffDenom;
			/* IQCORR_Q_I_COFF is a signed 6 bit number */
			if (iCoff < -32) {
				iCoff = -32;
			} else if (iCoff > 31) {
				iCoff = 31;
			}

			/* IQCORR_Q_Q_COFF is a signed 5 bit number */
			qCoff = (powerMeasI / qCoffDenom) - 128;
			if (qCoff < -16) {
				qCoff = -16;
			} else if (qCoff > 15) {
				qCoff = 15;
			}

			HALDEBUG(ah, HAL_DEBUG_PERCAL,
			    "%s: iCoff %d qCoff %d\n", __func__, iCoff, qCoff);

			/* Write values and enable correction */
			OS_REG_RMW_FIELD(ah, AR_PHY_TIMING_CTRL4,
				AR_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF, iCoff);
			OS_REG_RMW_FIELD(ah, AR_PHY_TIMING_CTRL4,
				AR_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF, qCoff);
			OS_REG_SET_BIT(ah, AR_PHY_TIMING_CTRL4, 
				AR_PHY_TIMING_CTRL4_IQCORR_ENABLE);

			ahp->ah_bIQCalibration = IQ_CAL_DONE;
			ichan->privFlags |= CHANNEL_IQVALID;
			ichan->iCoff = iCoff;
			ichan->qCoff = qCoff;
		}
	} else if (!IEEE80211_IS_CHAN_B(chan) &&
	    ahp->ah_bIQCalibration == IQ_CAL_DONE &&
	    (ichan->privFlags & CHANNEL_IQVALID) == 0) {
		/*
		 * Start IQ calibration if configured channel has changed.
		 * Use a magic number of 15 based on default value.
		 */
		OS_REG_RMW_FIELD(ah, AR_PHY_TIMING_CTRL4,
			AR_PHY_TIMING_CTRL4_IQCAL_LOG_COUNT_MAX,
			INIT_IQCAL_LOG_COUNT_MAX);
		OS_REG_SET_BIT(ah, AR_PHY_TIMING_CTRL4,
			AR_PHY_TIMING_CTRL4_DO_IQCAL);
		ahp->ah_bIQCalibration = IQ_CAL_RUNNING;
	}
	/* XXX EAR */

	if (longCal) {
		/* Check noise floor results */
		ar5212GetNf(ah, chan);
		if (!IEEE80211_IS_CHAN_CWINT(chan)) {
			/* Perform cal for 5Ghz channels and any OFDM on 5112 */
			if (IEEE80211_IS_CHAN_5GHZ(chan) ||
			    (IS_RAD5112(ah) && IEEE80211_IS_CHAN_OFDM(chan)))
				ar5212RequestRfgain(ah);
		}
	}
	RESTORE_CCK(ah, chan, isBmode);

	return AH_TRUE;
#undef IQ_CAL_TRIES
}

HAL_BOOL
ar5212PerCalibration(struct ath_hal *ah,  struct ieee80211_channel *chan,
	HAL_BOOL *isIQdone)
{
	return ar5212PerCalibrationN(ah, chan, 0x1, AH_TRUE, isIQdone);
}

HAL_BOOL
ar5212ResetCalValid(struct ath_hal *ah, const struct ieee80211_channel *chan)
{
	HAL_CHANNEL_INTERNAL *ichan;

	ichan = ath_hal_checkchannel(ah, chan);
	if (ichan == AH_NULL) {
		HALDEBUG(ah, HAL_DEBUG_ANY,
		    "%s: invalid channel %u/0x%x; no mapping\n",
		    __func__, chan->ic_freq, chan->ic_flags);
		return AH_FALSE;
	}
	ichan->privFlags &= ~CHANNEL_IQVALID;
	return AH_TRUE;
}

/*
 * Write the given reset bit mask into the reset register
 */
static HAL_BOOL
ar5212SetResetReg(struct ath_hal *ah, uint32_t resetMask)
{
	uint32_t mask = resetMask ? resetMask : ~0;
	HAL_BOOL rt;

	/* XXX ar5212MacStop & co. */

	if (AH_PRIVATE(ah)->ah_ispcie) {
		resetMask &= ~AR_RC_PCI;
	}

	(void) OS_REG_READ(ah, AR_RXDP);/* flush any pending MMR writes */
	OS_REG_WRITE(ah, AR_RC, resetMask);
	OS_DELAY(15);			/* need to wait at least 128 clocks
					   when reseting PCI before read */
	mask &= (AR_RC_MAC | AR_RC_BB);
	resetMask &= (AR_RC_MAC | AR_RC_BB);
	rt = ath_hal_wait(ah, AR_RC, mask, resetMask);
        if ((resetMask & AR_RC_MAC) == 0) {
		if (isBigEndian()) {
			/*
			 * Set CFG, little-endian for register
			 * and descriptor accesses.
			 */
			mask = INIT_CONFIG_STATUS | AR_CFG_SWRD | AR_CFG_SWRG;
#ifndef AH_NEED_DESC_SWAP
			mask |= AR_CFG_SWTD;
#endif
			OS_REG_WRITE(ah, AR_CFG, LE_READ_4(&mask));
		} else
			OS_REG_WRITE(ah, AR_CFG, INIT_CONFIG_STATUS);
		if (ar5212SetPowerMode(ah, HAL_PM_AWAKE, AH_TRUE))
			(void) OS_REG_READ(ah, AR_ISR_RAC);
	}

	/* track PHY power state so we don't try to r/w BB registers */
	AH5212(ah)->ah_phyPowerOn = ((resetMask & AR_RC_BB) == 0);
	return rt;
}

int16_t
ar5212GetNoiseFloor(struct ath_hal *ah)
{
	int16_t nf = (OS_REG_READ(ah, AR_PHY(25)) >> 19) & 0x1ff;
	if (nf & 0x100)
		nf = 0 - ((nf ^ 0x1ff) + 1);
	return nf;
}

static HAL_BOOL
getNoiseFloorThresh(struct ath_hal *ah, const struct ieee80211_channel *chan,
	int16_t *nft)
{
	const HAL_EEPROM *ee = AH_PRIVATE(ah)->ah_eeprom;

	HALASSERT(ah->ah_magic == AR5212_MAGIC);

	switch (chan->ic_flags & IEEE80211_CHAN_ALLFULL) {
	case IEEE80211_CHAN_A:
		*nft = ee->ee_noiseFloorThresh[headerInfo11A];
		break;
	case IEEE80211_CHAN_B:
		*nft = ee->ee_noiseFloorThresh[headerInfo11B];
		break;
	case IEEE80211_CHAN_G:
	case IEEE80211_CHAN_PUREG:	/* NB: really 108G */
		*nft = ee->ee_noiseFloorThresh[headerInfo11G];
		break;
	default:
		HALDEBUG(ah, HAL_DEBUG_ANY,
		    "%s: invalid channel flags %u/0x%x\n",
		    __func__, chan->ic_freq, chan->ic_flags);
		return AH_FALSE;
	}
	return AH_TRUE;
}

/*
 * Setup the noise floor cal history buffer.
 */
void 
ar5212InitNfCalHistBuffer(struct ath_hal *ah)
{
	struct ath_hal_5212 *ahp = AH5212(ah);
	int i;

	ahp->ah_nfCalHist.first_run = 1;	
	ahp->ah_nfCalHist.currIndex = 0;
	ahp->ah_nfCalHist.privNF = AR5212_CCA_MAX_GOOD_VALUE;
	ahp->ah_nfCalHist.invalidNFcount = AR512_NF_CAL_HIST_MAX;
	for (i = 0; i < AR512_NF_CAL_HIST_MAX; i ++)
		ahp->ah_nfCalHist.nfCalBuffer[i] = AR5212_CCA_MAX_GOOD_VALUE;
}

/*
 * Add a noise floor value to the ring buffer.
 */
static __inline void
updateNFHistBuff(struct ar5212NfCalHist *h, int16_t nf)
{
 	h->nfCalBuffer[h->currIndex] = nf;
     	if (++h->currIndex >= AR512_NF_CAL_HIST_MAX)
		h->currIndex = 0;
}	

/*
 * Return the median noise floor value in the ring buffer.
 */
int16_t 
ar5212GetNfHistMid(const int16_t calData[AR512_NF_CAL_HIST_MAX])
{
	int16_t sort[AR512_NF_CAL_HIST_MAX];
	int i, j;

	OS_MEMCPY(sort, calData, AR512_NF_CAL_HIST_MAX*sizeof(int16_t));
	for (i = 0; i < AR512_NF_CAL_HIST_MAX-1; i ++) {
		for (j = 1; j < AR512_NF_CAL_HIST_MAX-i; j ++) {
			if (sort[j] > sort[j-1]) {
				int16_t nf = sort[j];
				sort[j] = sort[j-1];
				sort[j-1] = nf;
			}
		}
	}
	return sort[(AR512_NF_CAL_HIST_MAX-1)>>1];
}

/*
 * Read the NF and check it against the noise floor threshhold
 */
int16_t
ar5212GetNf(struct ath_hal *ah, struct ieee80211_channel *chan)
{
	struct ath_hal_5212 *ahp = AH5212(ah);
	struct ar5212NfCalHist *h = &ahp->ah_nfCalHist;
	HAL_CHANNEL_INTERNAL *ichan = ath_hal_checkchannel(ah, chan);
	int16_t nf, nfThresh;
 	int32_t val;

	if (OS_REG_READ(ah, AR_PHY_AGC_CONTROL) & AR_PHY_AGC_CONTROL_NF) {
		HALDEBUG(ah, HAL_DEBUG_ANY,
		    "%s: NF did not complete in calibration window\n", __func__);
		ichan->rawNoiseFloor = h->privNF;	/* most recent value */
		return ichan->rawNoiseFloor;
	}

	/*
	 * Finished NF cal, check against threshold.
	 */
	nf = ar5212GetNoiseFloor(ah);
	if (getNoiseFloorThresh(ah, chan, &nfThresh)) {
		if (nf > nfThresh) {
			HALDEBUG(ah, HAL_DEBUG_ANY,
			    "%s: noise floor failed detected; detected %u, "
			    "threshold %u\n", __func__, nf, nfThresh);
			/*
			 * NB: Don't discriminate 2.4 vs 5Ghz, if this
			 *     happens it indicates a problem regardless
			 *     of the band.
			 */
			chan->ic_state |= IEEE80211_CHANSTATE_CWINT;
			nf = 0;
		}
	} else
		nf = 0;

	/*
	 * Pass through histogram and write median value as
	 * calculated from the accrued window.  We require a
	 * full window of in-range values to be seen before we
	 * start using the history.
	 */
	updateNFHistBuff(h, nf);
	if (h->first_run) {
		if (nf < AR5212_CCA_MIN_BAD_VALUE ||
		    nf > AR5212_CCA_MAX_HIGH_VALUE) {
			nf = AR5212_CCA_MAX_GOOD_VALUE;
			h->invalidNFcount = AR512_NF_CAL_HIST_MAX;
		} else if (--(h->invalidNFcount) == 0) {
			h->first_run = 0;
			h->privNF = nf = ar5212GetNfHistMid(h->nfCalBuffer);
		} else {
			nf = AR5212_CCA_MAX_GOOD_VALUE;
		}
	} else {
		h->privNF = nf = ar5212GetNfHistMid(h->nfCalBuffer);
	}

	val = OS_REG_READ(ah, AR_PHY(25));
	val &= 0xFFFFFE00;
	val |= (((uint32_t)nf << 1) & 0x1FF);
	OS_REG_WRITE(ah, AR_PHY(25), val);
	OS_REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_ENABLE_NF);
	OS_REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NO_UPDATE_NF);
	OS_REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NF);

	if (!ath_hal_wait(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NF, 0)) {
#ifdef AH_DEBUG
		ath_hal_printf(ah, "%s: AGC not ready AGC_CONTROL 0x%x\n",
		    __func__, OS_REG_READ(ah, AR_PHY_AGC_CONTROL));
#endif
	}

	/*
	 * Now load a high maxCCAPower value again so that we're
	 * not capped by the median we just loaded
	 */
	val &= 0xFFFFFE00;
	val |= (((uint32_t)(-50) << 1) & 0x1FF);
	OS_REG_WRITE(ah, AR_PHY(25), val);
	OS_REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_ENABLE_NF);
	OS_REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NO_UPDATE_NF);
	OS_REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NF);

	return (ichan->rawNoiseFloor = nf);
}

/*
 * Set up compression configuration registers
 */
void
ar5212SetCompRegs(struct ath_hal *ah)
{
	struct ath_hal_5212 *ahp = AH5212(ah);
	int i;

        /* Check if h/w supports compression */
	if (!AH_PRIVATE(ah)->ah_caps.halCompressSupport)
		return;

	OS_REG_WRITE(ah, AR_DCCFG, 1);

	OS_REG_WRITE(ah, AR_CCFG,
		(AR_COMPRESSION_WINDOW_SIZE >> 8) & AR_CCFG_WIN_M);

	OS_REG_WRITE(ah, AR_CCFG,
		OS_REG_READ(ah, AR_CCFG) | AR_CCFG_MIB_INT_EN);
	OS_REG_WRITE(ah, AR_CCUCFG,
		AR_CCUCFG_RESET_VAL | AR_CCUCFG_CATCHUP_EN);

	OS_REG_WRITE(ah, AR_CPCOVF, 0);

	/* reset decompression mask */
	for (i = 0; i < HAL_DECOMP_MASK_SIZE; i++) {
		OS_REG_WRITE(ah, AR_DCM_A, i);
		OS_REG_WRITE(ah, AR_DCM_D, ahp->ah_decompMask[i]);
	}
}

HAL_BOOL
ar5212SetAntennaSwitchInternal(struct ath_hal *ah, HAL_ANT_SETTING settings,
	const struct ieee80211_channel *chan)
{
#define	ANT_SWITCH_TABLE1	AR_PHY(88)
#define	ANT_SWITCH_TABLE2	AR_PHY(89)
	struct ath_hal_5212 *ahp = AH5212(ah);
	const HAL_EEPROM *ee = AH_PRIVATE(ah)->ah_eeprom;
	uint32_t antSwitchA, antSwitchB;
	int ix;

	HALASSERT(ah->ah_magic == AR5212_MAGIC);
	HALASSERT(ahp->ah_phyPowerOn);

	switch (chan->ic_flags & IEEE80211_CHAN_ALLFULL) {
	case IEEE80211_CHAN_A:
		ix = 0;
		break;
	case IEEE80211_CHAN_G:
	case IEEE80211_CHAN_PUREG:		/* NB: 108G */
		ix = 2;
		break;
	case IEEE80211_CHAN_B:
		if (IS_2425(ah) || IS_2417(ah)) {
			/* NB: Nala/Swan: 11b is handled using 11g */
			ix = 2;
		} else
			ix = 1;
		break;
	default:
		HALDEBUG(ah, HAL_DEBUG_ANY, "%s: invalid channel flags 0x%x\n",
		    __func__, chan->ic_flags);
		return AH_FALSE;
	}

	antSwitchA =  ee->ee_antennaControl[1][ix]
		   | (ee->ee_antennaControl[2][ix] << 6)
		   | (ee->ee_antennaControl[3][ix] << 12) 
		   | (ee->ee_antennaControl[4][ix] << 18)
		   | (ee->ee_antennaControl[5][ix] << 24)
		   ;
	antSwitchB =  ee->ee_antennaControl[6][ix]
		   | (ee->ee_antennaControl[7][ix] << 6)
		   | (ee->ee_antennaControl[8][ix] << 12)
		   | (ee->ee_antennaControl[9][ix] << 18)
		   | (ee->ee_antennaControl[10][ix] << 24)
		   ;
	/*
	 * For fixed antenna, give the same setting for both switch banks
	 */
	switch (settings) {
	case HAL_ANT_FIXED_A:
		antSwitchB = antSwitchA;
		break;
	case HAL_ANT_FIXED_B:
		antSwitchA = antSwitchB;
		break;
	case HAL_ANT_VARIABLE:
		break;
	default:
		HALDEBUG(ah, HAL_DEBUG_ANY, "%s: bad antenna setting %u\n",
		    __func__, settings);
		return AH_FALSE;
	}
	if (antSwitchB == antSwitchA) {
		HALDEBUG(ah, HAL_DEBUG_RFPARAM,
		    "%s: Setting fast diversity off.\n", __func__);
		OS_REG_CLR_BIT(ah,AR_PHY_CCK_DETECT, 
			       AR_PHY_CCK_DETECT_BB_ENABLE_ANT_FAST_DIV);
		ahp->ah_diversity = AH_FALSE;
	} else {
		HALDEBUG(ah, HAL_DEBUG_RFPARAM,
		    "%s: Setting fast diversity on.\n", __func__);
		OS_REG_SET_BIT(ah,AR_PHY_CCK_DETECT, 
			       AR_PHY_CCK_DETECT_BB_ENABLE_ANT_FAST_DIV);
		ahp->ah_diversity = AH_TRUE;
	}
	ahp->ah_antControl = settings;

	OS_REG_WRITE(ah, ANT_SWITCH_TABLE1, antSwitchA);
	OS_REG_WRITE(ah, ANT_SWITCH_TABLE2, antSwitchB);

	return AH_TRUE;
#undef ANT_SWITCH_TABLE2
#undef ANT_SWITCH_TABLE1
}

HAL_BOOL
ar5212IsSpurChannel(struct ath_hal *ah, const struct ieee80211_channel *chan)
{
	uint16_t freq = ath_hal_gethwchannel(ah, chan);
	uint32_t clockFreq =
	    ((IS_5413(ah) || IS_RAD5112_ANY(ah) || IS_2417(ah)) ? 40 : 32);
	return ( ((freq % clockFreq) != 0)
              && (((freq % clockFreq) < 10)
             || (((freq) % clockFreq) > 22)) );
}

/*
 * Read EEPROM header info and program the device for correct operation
 * given the channel value.
 */
HAL_BOOL
ar5212SetBoardValues(struct ath_hal *ah, const struct ieee80211_channel *chan)
{
#define NO_FALSE_DETECT_BACKOFF   2
#define CB22_FALSE_DETECT_BACKOFF 6
#define	AR_PHY_BIS(_ah, _reg, _mask, _val) \
	OS_REG_WRITE(_ah, AR_PHY(_reg), \
		(OS_REG_READ(_ah, AR_PHY(_reg)) & _mask) | (_val));
	struct ath_hal_5212 *ahp = AH5212(ah);
	const HAL_EEPROM *ee = AH_PRIVATE(ah)->ah_eeprom;
	int arrayMode, falseDectectBackoff;
	int is2GHz = IEEE80211_IS_CHAN_2GHZ(chan);
	HAL_CHANNEL_INTERNAL *ichan = ath_hal_checkchannel(ah, chan);
	int8_t adcDesiredSize, pgaDesiredSize;
	uint16_t switchSettling, txrxAtten, rxtxMargin;
	int iCoff, qCoff;

	HALASSERT(ah->ah_magic == AR5212_MAGIC);

	switch (chan->ic_flags & IEEE80211_CHAN_ALLTURBOFULL) {
	case IEEE80211_CHAN_A:
	case IEEE80211_CHAN_ST:
		arrayMode = headerInfo11A;
		if (!IS_RAD5112_ANY(ah) && !IS_2413(ah) && !IS_5413(ah))
			OS_REG_RMW_FIELD(ah, AR_PHY_FRAME_CTL,
				AR_PHY_FRAME_CTL_TX_CLIP,
				ahp->ah_gainValues.currStep->paramVal[GP_TXCLIP]);
		break;
	case IEEE80211_CHAN_B:
		arrayMode = headerInfo11B;
		break;
	case IEEE80211_CHAN_G:
	case IEEE80211_CHAN_108G:
		arrayMode = headerInfo11G;
		break;
	default:
		HALDEBUG(ah, HAL_DEBUG_ANY, "%s: invalid channel flags 0x%x\n",
		    __func__, chan->ic_flags);
		return AH_FALSE;
	}

	/* Set the antenna register(s) correctly for the chip revision */
	AR_PHY_BIS(ah, 68, 0xFFFFFC06,
		(ee->ee_antennaControl[0][arrayMode] << 4) | 0x1);

	ar5212SetAntennaSwitchInternal(ah, ahp->ah_antControl, chan);

	/* Set the Noise Floor Thresh on ar5211 devices */
	OS_REG_WRITE(ah, AR_PHY(90),
		(ee->ee_noiseFloorThresh[arrayMode] & 0x1FF)
		| (1 << 9));

	if (ee->ee_version >= AR_EEPROM_VER5_0 && IEEE80211_IS_CHAN_TURBO(chan)) {
		switchSettling = ee->ee_switchSettlingTurbo[is2GHz];
		adcDesiredSize = ee->ee_adcDesiredSizeTurbo[is2GHz];
		pgaDesiredSize = ee->ee_pgaDesiredSizeTurbo[is2GHz];
		txrxAtten = ee->ee_txrxAttenTurbo[is2GHz];
		rxtxMargin = ee->ee_rxtxMarginTurbo[is2GHz];
	} else {
		switchSettling = ee->ee_switchSettling[arrayMode];
		adcDesiredSize = ee->ee_adcDesiredSize[arrayMode];
		pgaDesiredSize = ee->ee_pgaDesiredSize[is2GHz];
		txrxAtten = ee->ee_txrxAtten[is2GHz];
		rxtxMargin = ee->ee_rxtxMargin[is2GHz];
	}

	OS_REG_RMW_FIELD(ah, AR_PHY_SETTLING, 
			 AR_PHY_SETTLING_SWITCH, switchSettling);
	OS_REG_RMW_FIELD(ah, AR_PHY_DESIRED_SZ,
			 AR_PHY_DESIRED_SZ_ADC, adcDesiredSize);
	OS_REG_RMW_FIELD(ah, AR_PHY_DESIRED_SZ,
			 AR_PHY_DESIRED_SZ_PGA, pgaDesiredSize);
	OS_REG_RMW_FIELD(ah, AR_PHY_RXGAIN,
			 AR_PHY_RXGAIN_TXRX_ATTEN, txrxAtten);
	OS_REG_WRITE(ah, AR_PHY(13),
		(ee->ee_txEndToXPAOff[arrayMode] << 24)
		| (ee->ee_txEndToXPAOff[arrayMode] << 16)
		| (ee->ee_txFrameToXPAOn[arrayMode] << 8)
		| ee->ee_txFrameToXPAOn[arrayMode]);
	AR_PHY_BIS(ah, 10, 0xFFFF00FF,
		ee->ee_txEndToXLNAOn[arrayMode] << 8);
	AR_PHY_BIS(ah, 25, 0xFFF80FFF,
		(ee->ee_thresh62[arrayMode] << 12) & 0x7F000);

	/*
	 * False detect backoff - suspected 32 MHz spur causes false
	 * detects in OFDM, causing Tx Hangs.  Decrease weak signal
	 * sensitivity for this card.
	 */
	falseDectectBackoff = NO_FALSE_DETECT_BACKOFF;
	if (ee->ee_version < AR_EEPROM_VER3_3) {
		/* XXX magic number */
		if (AH_PRIVATE(ah)->ah_subvendorid == 0x1022 &&
		    IEEE80211_IS_CHAN_OFDM(chan))
			falseDectectBackoff += CB22_FALSE_DETECT_BACKOFF;
	} else {
		if (ar5212IsSpurChannel(ah, chan))
			falseDectectBackoff += ee->ee_falseDetectBackoff[arrayMode];
	}
	AR_PHY_BIS(ah, 73, 0xFFFFFF01, (falseDectectBackoff << 1) & 0xFE);

	if (ichan->privFlags & CHANNEL_IQVALID) {
		iCoff = ichan->iCoff;
		qCoff = ichan->qCoff;
	} else {
		iCoff = ee->ee_iqCalI[is2GHz];
		qCoff = ee->ee_iqCalQ[is2GHz];
	}

	/* write previous IQ results */
	OS_REG_RMW_FIELD(ah, AR_PHY_TIMING_CTRL4,
		AR_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF, iCoff);
	OS_REG_RMW_FIELD(ah, AR_PHY_TIMING_CTRL4,
		AR_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF, qCoff);
	OS_REG_SET_BIT(ah, AR_PHY_TIMING_CTRL4,
		AR_PHY_TIMING_CTRL4_IQCORR_ENABLE);

	if (ee->ee_version >= AR_EEPROM_VER4_1) {
		if (!IEEE80211_IS_CHAN_108G(chan) || ee->ee_version >= AR_EEPROM_VER5_0)
			OS_REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ,
				AR_PHY_GAIN_2GHZ_RXTX_MARGIN, rxtxMargin);
	}
	if (ee->ee_version >= AR_EEPROM_VER5_1) {
		/* for now always disabled */
		OS_REG_WRITE(ah,  AR_PHY_HEAVY_CLIP_ENABLE,  0);
	}

	return AH_TRUE;
#undef AR_PHY_BIS
#undef NO_FALSE_DETECT_BACKOFF
#undef CB22_FALSE_DETECT_BACKOFF
}

/*
 * Apply Spur Immunity to Boards that require it.
 * Applies only to OFDM RX operation.
 */

void
ar5212SetSpurMitigation(struct ath_hal *ah,
	const struct ieee80211_channel *chan)
{
	uint32_t pilotMask[2] = {0, 0}, binMagMask[4] = {0, 0, 0 , 0};
	uint16_t i, finalSpur, curChanAsSpur, binWidth = 0, spurDetectWidth, spurChan;
	int32_t spurDeltaPhase = 0, spurFreqSd = 0, spurOffset, binOffsetNumT16, curBinOffset;
	int16_t numBinOffsets;
	static const uint16_t magMapFor4[4] = {1, 2, 2, 1};
	static const uint16_t magMapFor3[3] = {1, 2, 1};
	const uint16_t *pMagMap;
	HAL_BOOL is2GHz = IEEE80211_IS_CHAN_2GHZ(chan);
	HAL_CHANNEL_INTERNAL *ichan = ath_hal_checkchannel(ah, chan);
	uint32_t val;

#define CHAN_TO_SPUR(_f, _freq)   ( ((_freq) - ((_f) ? 2300 : 4900)) * 10 )
	if (IS_2417(ah)) {
		HALDEBUG(ah, HAL_DEBUG_RFPARAM, "%s: no spur mitigation\n",
		    __func__);
		return;
	}

	curChanAsSpur = CHAN_TO_SPUR(is2GHz, ichan->channel);

	if (ichan->mainSpur) {
		/* Pull out the saved spur value */
		finalSpur = ichan->mainSpur;
	} else {
		/*
		 * Check if spur immunity should be performed for this channel
		 * Should only be performed once per channel and then saved
		 */
		finalSpur = AR_NO_SPUR;
		spurDetectWidth = HAL_SPUR_CHAN_WIDTH;
		if (IEEE80211_IS_CHAN_TURBO(chan))
			spurDetectWidth *= 2;

		/* Decide if any spur affects the current channel */
		for (i = 0; i < AR_EEPROM_MODAL_SPURS; i++) {
			spurChan = ath_hal_getSpurChan(ah, i, is2GHz);
			if (spurChan == AR_NO_SPUR) {
				break;
			}
			if ((curChanAsSpur - spurDetectWidth <= (spurChan & HAL_SPUR_VAL_MASK)) &&
			    (curChanAsSpur + spurDetectWidth >= (spurChan & HAL_SPUR_VAL_MASK))) {
				finalSpur = spurChan & HAL_SPUR_VAL_MASK;
				break;
			}
		}
		/* Save detected spur (or no spur) for this channel */
		ichan->mainSpur = finalSpur;
	}

	/* Write spur immunity data */
	if (finalSpur == AR_NO_SPUR) {
		/* Disable Spur Immunity Regs if they appear set */
		if (OS_REG_READ(ah, AR_PHY_TIMING_CTRL4) & AR_PHY_TIMING_CTRL4_ENABLE_SPUR_FILTER) {
			/* Clear Spur Delta Phase, Spur Freq, and enable bits */
			OS_REG_RMW_FIELD(ah, AR_PHY_MASK_CTL, AR_PHY_MASK_CTL_RATE, 0);
			val = OS_REG_READ(ah, AR_PHY_TIMING_CTRL4);
			val &= ~(AR_PHY_TIMING_CTRL4_ENABLE_SPUR_FILTER |
				 AR_PHY_TIMING_CTRL4_ENABLE_CHAN_MASK |
				 AR_PHY_TIMING_CTRL4_ENABLE_PILOT_MASK);
			OS_REG_WRITE(ah, AR_PHY_MASK_CTL, val);
			OS_REG_WRITE(ah, AR_PHY_TIMING11, 0);

			/* Clear pilot masks */
			OS_REG_WRITE(ah, AR_PHY_TIMING7, 0);
			OS_REG_RMW_FIELD(ah, AR_PHY_TIMING8, AR_PHY_TIMING8_PILOT_MASK_2, 0);
			OS_REG_WRITE(ah, AR_PHY_TIMING9, 0);
			OS_REG_RMW_FIELD(ah, AR_PHY_TIMING10, AR_PHY_TIMING10_PILOT_MASK_2, 0);

			/* Clear magnitude masks */
			OS_REG_WRITE(ah, AR_PHY_BIN_MASK_1, 0);
			OS_REG_WRITE(ah, AR_PHY_BIN_MASK_2, 0);
			OS_REG_WRITE(ah, AR_PHY_BIN_MASK_3, 0);
			OS_REG_RMW_FIELD(ah, AR_PHY_MASK_CTL, AR_PHY_MASK_CTL_MASK_4, 0);
			OS_REG_WRITE(ah, AR_PHY_BIN_MASK2_1, 0);
			OS_REG_WRITE(ah, AR_PHY_BIN_MASK2_2, 0);
			OS_REG_WRITE(ah, AR_PHY_BIN_MASK2_3, 0);
			OS_REG_RMW_FIELD(ah, AR_PHY_BIN_MASK2_4, AR_PHY_BIN_MASK2_4_MASK_4, 0);
		}
	} else {
		spurOffset = finalSpur - curChanAsSpur;
		/*
		 * Spur calculations:
		 * spurDeltaPhase is (spurOffsetIn100KHz / chipFrequencyIn100KHz) << 21
		 * spurFreqSd is (spurOffsetIn100KHz / sampleFrequencyIn100KHz) << 11
		 */
		if (IEEE80211_IS_CHAN_TURBO(chan)) {
			/* Chip Frequency & sampleFrequency are 80 MHz */
			spurDeltaPhase = (spurOffset << 16) / 25;
			spurFreqSd = spurDeltaPhase >> 10;
			binWidth = HAL_BIN_WIDTH_TURBO_100HZ;
		} else if (IEEE80211_IS_CHAN_G(chan)) {
			/* Chip Frequency is 44MHz, sampleFrequency is 40 MHz */
			spurFreqSd = (spurOffset << 8) / 55;
			spurDeltaPhase = (spurOffset << 17) / 25;
			binWidth = HAL_BIN_WIDTH_BASE_100HZ;
		} else {
			HALASSERT(!IEEE80211_IS_CHAN_B(chan));
			/* Chip Frequency & sampleFrequency are 40 MHz */
			spurDeltaPhase = (spurOffset << 17) / 25;
			spurFreqSd = spurDeltaPhase >> 10;
			binWidth = HAL_BIN_WIDTH_BASE_100HZ;
		}

		/* Compute Pilot Mask */
		binOffsetNumT16 = ((spurOffset * 1000) << 4) / binWidth;
		/* The spur is on a bin if it's remainder at times 16 is 0 */
		if (binOffsetNumT16 & 0xF) {
			numBinOffsets = 4;
			pMagMap = magMapFor4;
		} else {
			numBinOffsets = 3;
			pMagMap = magMapFor3;
		}
		for (i = 0; i < numBinOffsets; i++) {
			if ((binOffsetNumT16 >> 4) > HAL_MAX_BINS_ALLOWED) {
				HALDEBUG(ah, HAL_DEBUG_ANY,
				    "Too man bins in spur mitigation\n");
				return;
			}

			/* Get Pilot Mask values */
			curBinOffset = (binOffsetNumT16 >> 4) + i + 25;
			if ((curBinOffset >= 0) && (curBinOffset <= 32)) {
				if (curBinOffset <= 25)
					pilotMask[0] |= 1 << curBinOffset;
				else if (curBinOffset >= 27)
					pilotMask[0] |= 1 << (curBinOffset - 1);
			} else if ((curBinOffset >= 33) && (curBinOffset <= 52))
				pilotMask[1] |= 1 << (curBinOffset - 33);

			/* Get viterbi values */
			if ((curBinOffset >= -1) && (curBinOffset <= 14))
				binMagMask[0] |= pMagMap[i] << (curBinOffset + 1) * 2;
			else if ((curBinOffset >= 15) && (curBinOffset <= 30))
				binMagMask[1] |= pMagMap[i] << (curBinOffset - 15) * 2;
			else if ((curBinOffset >= 31) && (curBinOffset <= 46))
				binMagMask[2] |= pMagMap[i] << (curBinOffset -31) * 2;
			else if((curBinOffset >= 47) && (curBinOffset <= 53))
				binMagMask[3] |= pMagMap[i] << (curBinOffset -47) * 2;
		}

		/* Write Spur Delta Phase, Spur Freq, and enable bits */
		OS_REG_RMW_FIELD(ah, AR_PHY_MASK_CTL, AR_PHY_MASK_CTL_RATE, 0xFF);
		val = OS_REG_READ(ah, AR_PHY_TIMING_CTRL4);
		val |= (AR_PHY_TIMING_CTRL4_ENABLE_SPUR_FILTER |
			AR_PHY_TIMING_CTRL4_ENABLE_CHAN_MASK | 
			AR_PHY_TIMING_CTRL4_ENABLE_PILOT_MASK);
		OS_REG_WRITE(ah, AR_PHY_TIMING_CTRL4, val);
		OS_REG_WRITE(ah, AR_PHY_TIMING11, AR_PHY_TIMING11_USE_SPUR_IN_AGC |		
			     SM(spurFreqSd, AR_PHY_TIMING11_SPUR_FREQ_SD) |
			     SM(spurDeltaPhase, AR_PHY_TIMING11_SPUR_DELTA_PHASE));

		/* Write pilot masks */
		OS_REG_WRITE(ah, AR_PHY_TIMING7, pilotMask[0]);
		OS_REG_RMW_FIELD(ah, AR_PHY_TIMING8, AR_PHY_TIMING8_PILOT_MASK_2, pilotMask[1]);
		OS_REG_WRITE(ah, AR_PHY_TIMING9, pilotMask[0]);
		OS_REG_RMW_FIELD(ah, AR_PHY_TIMING10, AR_PHY_TIMING10_PILOT_MASK_2, pilotMask[1]);

		/* Write magnitude masks */
		OS_REG_WRITE(ah, AR_PHY_BIN_MASK_1, binMagMask[0]);
		OS_REG_WRITE(ah, AR_PHY_BIN_MASK_2, binMagMask[1]);
		OS_REG_WRITE(ah, AR_PHY_BIN_MASK_3, binMagMask[2]);
		OS_REG_RMW_FIELD(ah, AR_PHY_MASK_CTL, AR_PHY_MASK_CTL_MASK_4, binMagMask[3]);
		OS_REG_WRITE(ah, AR_PHY_BIN_MASK2_1, binMagMask[0]);
		OS_REG_WRITE(ah, AR_PHY_BIN_MASK2_2, binMagMask[1]);
		OS_REG_WRITE(ah, AR_PHY_BIN_MASK2_3, binMagMask[2]);
		OS_REG_RMW_FIELD(ah, AR_PHY_BIN_MASK2_4, AR_PHY_BIN_MASK2_4_MASK_4, binMagMask[3]);
	}
#undef CHAN_TO_SPUR
}


/*
 * Delta slope coefficient computation.
 * Required for OFDM operation.
 */
void
ar5212SetDeltaSlope(struct ath_hal *ah, const struct ieee80211_channel *chan)
{
#define COEF_SCALE_S 24
#define INIT_CLOCKMHZSCALED	0x64000000
	uint16_t freq = ath_hal_gethwchannel(ah, chan);
	unsigned long coef_scaled, coef_exp, coef_man, ds_coef_exp, ds_coef_man;
	unsigned long clockMhzScaled = INIT_CLOCKMHZSCALED;

	if (IEEE80211_IS_CHAN_TURBO(chan))
		clockMhzScaled *= 2;
	/* half and quarter rate can divide the scaled clock by 2 or 4 respectively */
	/* scale for selected channel bandwidth */ 
	if (IEEE80211_IS_CHAN_HALF(chan)) {
		clockMhzScaled = clockMhzScaled >> 1;
	} else if (IEEE80211_IS_CHAN_QUARTER(chan)) {
		clockMhzScaled = clockMhzScaled >> 2;
	} 

	/*
	 * ALGO -> coef = 1e8/fcarrier*fclock/40;
	 * scaled coef to provide precision for this floating calculation 
	 */
	coef_scaled = clockMhzScaled / freq;

	/*
	 * ALGO -> coef_exp = 14-floor(log2(coef)); 
	 * floor(log2(x)) is the highest set bit position
	 */
	for (coef_exp = 31; coef_exp > 0; coef_exp--)
		if ((coef_scaled >> coef_exp) & 0x1)
			break;
	/* A coef_exp of 0 is a legal bit position but an unexpected coef_exp */
	HALASSERT(coef_exp);
	coef_exp = 14 - (coef_exp - COEF_SCALE_S);

	/*
	 * ALGO -> coef_man = floor(coef* 2^coef_exp+0.5);
	 * The coefficient is already shifted up for scaling
	 */
	coef_man = coef_scaled + (1 << (COEF_SCALE_S - coef_exp - 1));
	ds_coef_man = coef_man >> (COEF_SCALE_S - coef_exp);
	ds_coef_exp = coef_exp - 16;

	OS_REG_RMW_FIELD(ah, AR_PHY_TIMING3,
		AR_PHY_TIMING3_DSC_MAN, ds_coef_man);
	OS_REG_RMW_FIELD(ah, AR_PHY_TIMING3,
		AR_PHY_TIMING3_DSC_EXP, ds_coef_exp);
#undef INIT_CLOCKMHZSCALED
#undef COEF_SCALE_S
}

/*
 * Set a limit on the overall output power.  Used for dynamic
 * transmit power control and the like.
 *
 * NB: limit is in units of 0.5 dbM.
 */
HAL_BOOL
ar5212SetTxPowerLimit(struct ath_hal *ah, uint32_t limit)
{
	/* XXX blech, construct local writable copy */
	struct ieee80211_channel dummy = *AH_PRIVATE(ah)->ah_curchan;
	uint16_t dummyXpdGains[2];
	HAL_BOOL isBmode;

	SAVE_CCK(ah, &dummy, isBmode);
	AH_PRIVATE(ah)->ah_powerLimit = AH_MIN(limit, MAX_RATE_POWER);
	return ar5212SetTransmitPower(ah, &dummy, dummyXpdGains);
}

/*
 * Set the transmit power in the baseband for the given
 * operating channel and mode.
 */
HAL_BOOL
ar5212SetTransmitPower(struct ath_hal *ah,
	const struct ieee80211_channel *chan, uint16_t *rfXpdGain)
{
#define	POW_OFDM(_r, _s)	(((0 & 1)<< ((_s)+6)) | (((_r) & 0x3f) << (_s)))
#define	POW_CCK(_r, _s)		(((_r) & 0x3f) << (_s))
#define	N(a)			(sizeof (a) / sizeof (a[0]))
	static const uint16_t tpcScaleReductionTable[5] =
		{ 0, 3, 6, 9, MAX_RATE_POWER };
	struct ath_hal_5212 *ahp = AH5212(ah);
	uint16_t freq = ath_hal_gethwchannel(ah, chan);
	const HAL_EEPROM *ee = AH_PRIVATE(ah)->ah_eeprom;
	int16_t minPower, maxPower, tpcInDb, powerLimit;
	int i;

	HALASSERT(ah->ah_magic == AR5212_MAGIC);

	OS_MEMZERO(ahp->ah_pcdacTable, ahp->ah_pcdacTableSize);
	OS_MEMZERO(ahp->ah_ratesArray, sizeof(ahp->ah_ratesArray));

	powerLimit = AH_MIN(MAX_RATE_POWER, AH_PRIVATE(ah)->ah_powerLimit);
	if (powerLimit >= MAX_RATE_POWER || powerLimit == 0)
		tpcInDb = tpcScaleReductionTable[AH_PRIVATE(ah)->ah_tpScale];
	else
		tpcInDb = 0;
	if (!ar5212SetRateTable(ah, chan, tpcInDb, powerLimit,
				AH_TRUE, &minPower, &maxPower)) {
		HALDEBUG(ah, HAL_DEBUG_ANY, "%s: unable to set rate table\n",
		    __func__);
		return AH_FALSE;
	}
	if (!ahp->ah_rfHal->setPowerTable(ah,
		&minPower, &maxPower, chan, rfXpdGain)) {
		HALDEBUG(ah, HAL_DEBUG_ANY, "%s: unable to set power table\n",
		    __func__);
		return AH_FALSE;
	}

	/* 
	 * Adjust XR power/rate up by 2 dB to account for greater peak
	 * to avg ratio - except in newer avg power designs
	 */
	if (!IS_2413(ah) && !IS_5413(ah))
		ahp->ah_ratesArray[15] += 4;
	/* 
	 * txPowerIndexOffset is set by the SetPowerTable() call -
	 *  adjust the rate table 
	 */
	for (i = 0; i < N(ahp->ah_ratesArray); i++) {
		ahp->ah_ratesArray[i] += ahp->ah_txPowerIndexOffset;
		if (ahp->ah_ratesArray[i] > 63) 
			ahp->ah_ratesArray[i] = 63;
	}

	if (ee->ee_eepMap < 2) {
		/* 
		 * Correct gain deltas for 5212 G operation -
		 * Removed with revised chipset
		 */
		if (AH_PRIVATE(ah)->ah_phyRev < AR_PHY_CHIP_ID_REV_2 &&
		    IEEE80211_IS_CHAN_G(chan)) {
			uint16_t cckOfdmPwrDelta;

			if (freq == 2484) 
				cckOfdmPwrDelta = SCALE_OC_DELTA(
					ee->ee_cckOfdmPwrDelta - 
					ee->ee_scaledCh14FilterCckDelta);
			else 
				cckOfdmPwrDelta = SCALE_OC_DELTA(
					ee->ee_cckOfdmPwrDelta);
			ar5212CorrectGainDelta(ah, cckOfdmPwrDelta);
		}
		/* 
		 * Finally, write the power values into the
		 * baseband power table
		 */
		for (i = 0; i < (PWR_TABLE_SIZE/2); i++) {
			OS_REG_WRITE(ah, AR_PHY_PCDAC_TX_POWER(i),
				 ((((ahp->ah_pcdacTable[2*i + 1] << 8) | 0xff) & 0xffff) << 16)
				| (((ahp->ah_pcdacTable[2*i]     << 8) | 0xff) & 0xffff)
			);
		}
	}

	/* Write the OFDM power per rate set */
	OS_REG_WRITE(ah, AR_PHY_POWER_TX_RATE1, 
		POW_OFDM(ahp->ah_ratesArray[3], 24)
	      | POW_OFDM(ahp->ah_ratesArray[2], 16)
	      | POW_OFDM(ahp->ah_ratesArray[1],  8)
	      | POW_OFDM(ahp->ah_ratesArray[0],  0)
	);
	OS_REG_WRITE(ah, AR_PHY_POWER_TX_RATE2, 
		POW_OFDM(ahp->ah_ratesArray[7], 24)
	      | POW_OFDM(ahp->ah_ratesArray[6], 16)
	      | POW_OFDM(ahp->ah_ratesArray[5],  8)
	      | POW_OFDM(ahp->ah_ratesArray[4],  0)
	);

	/* Write the CCK power per rate set */
	OS_REG_WRITE(ah, AR_PHY_POWER_TX_RATE3,
		POW_CCK(ahp->ah_ratesArray[10], 24)
	      | POW_CCK(ahp->ah_ratesArray[9],  16)
	      | POW_CCK(ahp->ah_ratesArray[15],  8)	/* XR target power */
	      | POW_CCK(ahp->ah_ratesArray[8],   0)
	);
	OS_REG_WRITE(ah, AR_PHY_POWER_TX_RATE4,
		POW_CCK(ahp->ah_ratesArray[14], 24)
	      | POW_CCK(ahp->ah_ratesArray[13], 16)
	      | POW_CCK(ahp->ah_ratesArray[12],  8)
	      | POW_CCK(ahp->ah_ratesArray[11],  0)
	);

	/*
	 * Set max power to 30 dBm and, optionally,
	 * enable TPC in tx descriptors.
	 */
	OS_REG_WRITE(ah, AR_PHY_POWER_TX_RATE_MAX, MAX_RATE_POWER |
		(ahp->ah_tpcEnabled ? AR_PHY_POWER_TX_RATE_MAX_TPC_ENABLE : 0));

	return AH_TRUE;
#undef N
#undef POW_CCK
#undef POW_OFDM
}

/*
 * Sets the transmit power in the baseband for the given
 * operating channel and mode.
 */
static HAL_BOOL
ar5212SetRateTable(struct ath_hal *ah, const struct ieee80211_channel *chan,
	int16_t tpcScaleReduction, int16_t powerLimit, HAL_BOOL commit,
	int16_t *pMinPower, int16_t *pMaxPower)
{
	struct ath_hal_5212 *ahp = AH5212(ah);
	uint16_t freq = ath_hal_gethwchannel(ah, chan);
	const HAL_EEPROM *ee = AH_PRIVATE(ah)->ah_eeprom;
	uint16_t *rpow = ahp->ah_ratesArray;
	uint16_t twiceMaxEdgePower = MAX_RATE_POWER;
	uint16_t twiceMaxEdgePowerCck = MAX_RATE_POWER;
	uint16_t twiceMaxRDPower = MAX_RATE_POWER;
	int i;
	uint8_t cfgCtl;
	int8_t twiceAntennaGain, twiceAntennaReduction;
	const RD_EDGES_POWER *rep;
	TRGT_POWER_INFO targetPowerOfdm, targetPowerCck;
	int16_t scaledPower, maxAvailPower = 0;
	int16_t r13, r9, r7, r0;

	HALASSERT(ah->ah_magic == AR5212_MAGIC);

	twiceMaxRDPower = chan->ic_maxregpower * 2;
	*pMaxPower = -MAX_RATE_POWER;
	*pMinPower = MAX_RATE_POWER;

	/* Get conformance test limit maximum for this channel */
	cfgCtl = ath_hal_getctl(ah, chan);
	for (i = 0; i < ee->ee_numCtls; i++) {
		uint16_t twiceMinEdgePower;

		if (ee->ee_ctl[i] == 0)
			continue;
		if (ee->ee_ctl[i] == cfgCtl ||
		    cfgCtl == ((ee->ee_ctl[i] & CTL_MODE_M) | SD_NO_CTL)) {
			rep = &ee->ee_rdEdgesPower[i * NUM_EDGES];
			twiceMinEdgePower = ar5212GetMaxEdgePower(freq, rep);
			if ((cfgCtl & ~CTL_MODE_M) == SD_NO_CTL) {
				/* Find the minimum of all CTL edge powers that apply to this channel */
				twiceMaxEdgePower = AH_MIN(twiceMaxEdgePower, twiceMinEdgePower);
			} else {
				twiceMaxEdgePower = twiceMinEdgePower;
				break;
			}
		}
	}

	if (IEEE80211_IS_CHAN_G(chan)) {
		/* Check for a CCK CTL for 11G CCK powers */
		cfgCtl = (cfgCtl & ~CTL_MODE_M) | CTL_11B;
		for (i = 0; i < ee->ee_numCtls; i++) {
			uint16_t twiceMinEdgePowerCck;

			if (ee->ee_ctl[i] == 0)
				continue;
			if (ee->ee_ctl[i] == cfgCtl ||
			    cfgCtl == ((ee->ee_ctl[i] & CTL_MODE_M) | SD_NO_CTL)) {
				rep = &ee->ee_rdEdgesPower[i * NUM_EDGES];
				twiceMinEdgePowerCck = ar5212GetMaxEdgePower(freq, rep);
				if ((cfgCtl & ~CTL_MODE_M) == SD_NO_CTL) {
					/* Find the minimum of all CTL edge powers that apply to this channel */
					twiceMaxEdgePowerCck = AH_MIN(twiceMaxEdgePowerCck, twiceMinEdgePowerCck);
				} else {
					twiceMaxEdgePowerCck = twiceMinEdgePowerCck;
					break;
				}
			}
		}
	} else {
		/* Set the 11B cck edge power to the one found before */
		twiceMaxEdgePowerCck = twiceMaxEdgePower;
	}

	/* Get Antenna Gain reduction */
	if (IEEE80211_IS_CHAN_5GHZ(chan)) {
		ath_hal_eepromGet(ah, AR_EEP_ANTGAINMAX_5, &twiceAntennaGain);
	} else {
		ath_hal_eepromGet(ah, AR_EEP_ANTGAINMAX_2, &twiceAntennaGain);
	}
	twiceAntennaReduction =
		ath_hal_getantennareduction(ah, chan, twiceAntennaGain);

	if (IEEE80211_IS_CHAN_OFDM(chan)) {
		/* Get final OFDM target powers */
		if (IEEE80211_IS_CHAN_2GHZ(chan)) { 
			ar5212GetTargetPowers(ah, chan, ee->ee_trgtPwr_11g,
				ee->ee_numTargetPwr_11g, &targetPowerOfdm);
		} else {
			ar5212GetTargetPowers(ah, chan, ee->ee_trgtPwr_11a,
				ee->ee_numTargetPwr_11a, &targetPowerOfdm);
		}

		/* Get Maximum OFDM power */
		/* Minimum of target and edge powers */
		scaledPower = AH_MIN(twiceMaxEdgePower,
				twiceMaxRDPower - twiceAntennaReduction);

		/*
		 * If turbo is set, reduce power to keep power
		 * consumption under 2 Watts.  Note that we always do
		 * this unless specially configured.  Then we limit
		 * power only for non-AP operation.
		 */
		if (IEEE80211_IS_CHAN_TURBO(chan)
#ifdef AH_ENABLE_AP_SUPPORT
		    && AH_PRIVATE(ah)->ah_opmode != HAL_M_HOSTAP
#endif
		) {
			/*
			 * If turbo is set, reduce power to keep power
			 * consumption under 2 Watts
			 */
			if (ee->ee_version >= AR_EEPROM_VER3_1)
				scaledPower = AH_MIN(scaledPower,
					ee->ee_turbo2WMaxPower5);
			/*
			 * EEPROM version 4.0 added an additional
			 * constraint on 2.4GHz channels.
			 */
			if (ee->ee_version >= AR_EEPROM_VER4_0 &&
			    IEEE80211_IS_CHAN_2GHZ(chan))
				scaledPower = AH_MIN(scaledPower,
					ee->ee_turbo2WMaxPower2);
		}

		maxAvailPower = AH_MIN(scaledPower,
					targetPowerOfdm.twicePwr6_24);

		/* Reduce power by max regulatory domain allowed restrictions */
		scaledPower = maxAvailPower - (tpcScaleReduction * 2);
		scaledPower = (scaledPower < 0) ? 0 : scaledPower;
		scaledPower = AH_MIN(scaledPower, powerLimit);

		if (commit) {
			/* Set OFDM rates 9, 12, 18, 24 */
			r0 = rpow[0] = rpow[1] = rpow[2] = rpow[3] = rpow[4] = scaledPower;

			/* Set OFDM rates 36, 48, 54, XR */
			rpow[5] = AH_MIN(rpow[0], targetPowerOfdm.twicePwr36);
			rpow[6] = AH_MIN(rpow[0], targetPowerOfdm.twicePwr48);
			r7 = rpow[7] = AH_MIN(rpow[0], targetPowerOfdm.twicePwr54);

			if (ee->ee_version >= AR_EEPROM_VER4_0) {
				/* Setup XR target power from EEPROM */
				rpow[15] = AH_MIN(scaledPower, IEEE80211_IS_CHAN_2GHZ(chan) ?
						  ee->ee_xrTargetPower2 : ee->ee_xrTargetPower5);
			} else {
				/* XR uses 6mb power */
				rpow[15] = rpow[0];
			}
			ahp->ah_ofdmTxPower = *pMaxPower;

		} else {
			r0 = scaledPower;
			r7 = AH_MIN(r0, targetPowerOfdm.twicePwr54);
		}
		*pMinPower = r7;
		*pMaxPower = r0;

		HALDEBUG(ah, HAL_DEBUG_RFPARAM,
		    "%s: MaxRD: %d TurboMax: %d MaxCTL: %d "
		    "TPC_Reduction %d chan=%d (0x%x) maxAvailPower=%d pwr6_24=%d, maxPower=%d\n",
		    __func__, twiceMaxRDPower, ee->ee_turbo2WMaxPower5,
		    twiceMaxEdgePower, tpcScaleReduction * 2,
		    chan->ic_freq, chan->ic_flags,
		    maxAvailPower, targetPowerOfdm.twicePwr6_24, *pMaxPower);
	}

	if (IEEE80211_IS_CHAN_CCK(chan)) {
		/* Get final CCK target powers */
		ar5212GetTargetPowers(ah, chan, ee->ee_trgtPwr_11b,
			ee->ee_numTargetPwr_11b, &targetPowerCck);

		/* Reduce power by max regulatory domain allowed restrictions */
		scaledPower = AH_MIN(twiceMaxEdgePowerCck,
			twiceMaxRDPower - twiceAntennaReduction);
		if (maxAvailPower < AH_MIN(scaledPower, targetPowerCck.twicePwr6_24))
			maxAvailPower = AH_MIN(scaledPower, targetPowerCck.twicePwr6_24);

		/* Reduce power by user selection */
		scaledPower = AH_MIN(scaledPower, targetPowerCck.twicePwr6_24) - (tpcScaleReduction * 2);
		scaledPower = (scaledPower < 0) ? 0 : scaledPower;
		scaledPower = AH_MIN(scaledPower, powerLimit);

		if (commit) {
			/* Set CCK rates 2L, 2S, 5.5L, 5.5S, 11L, 11S */
			rpow[8]  = AH_MIN(scaledPower, targetPowerCck.twicePwr6_24);
			r9 = rpow[9]  = AH_MIN(scaledPower, targetPowerCck.twicePwr36);
			rpow[10] = rpow[9];
			rpow[11] = AH_MIN(scaledPower, targetPowerCck.twicePwr48);
			rpow[12] = rpow[11];
			r13 = rpow[13] = AH_MIN(scaledPower, targetPowerCck.twicePwr54);
			rpow[14] = rpow[13];
		} else {
			r9 = AH_MIN(scaledPower, targetPowerCck.twicePwr36);
			r13 = AH_MIN(scaledPower, targetPowerCck.twicePwr54);
		}

		/* Set min/max power based off OFDM values or initialization */
		if (r13 < *pMinPower)
			*pMinPower = r13;
		if (r9 > *pMaxPower)
			*pMaxPower = r9;

		HALDEBUG(ah, HAL_DEBUG_RFPARAM,
		    "%s: cck: MaxRD: %d MaxCTL: %d "
		    "TPC_Reduction %d chan=%d (0x%x) maxAvailPower=%d pwr6_24=%d, maxPower=%d\n",
		    __func__, twiceMaxRDPower, twiceMaxEdgePowerCck,
		    tpcScaleReduction * 2, chan->ic_freq, chan->ic_flags,
		    maxAvailPower, targetPowerCck.twicePwr6_24, *pMaxPower);
	}
	if (commit) {
		ahp->ah_tx6PowerInHalfDbm = *pMaxPower;
		AH_PRIVATE(ah)->ah_maxPowerLevel = ahp->ah_tx6PowerInHalfDbm;
	}
	return AH_TRUE;
}

HAL_BOOL
ar5212GetChipPowerLimits(struct ath_hal *ah, struct ieee80211_channel *chan)
{
	struct ath_hal_5212 *ahp = AH5212(ah);
#if 0
	static const uint16_t tpcScaleReductionTable[5] =
		{ 0, 3, 6, 9, MAX_RATE_POWER };
	int16_t tpcInDb, powerLimit;
#endif
	int16_t minPower, maxPower;

	/*
	 * Get Pier table max and min powers.
	 */
	if (ahp->ah_rfHal->getChannelMaxMinPower(ah, chan, &maxPower, &minPower)) {
		/* NB: rf code returns 1/4 dBm units, convert */
		chan->ic_maxpower = maxPower / 2;
		chan->ic_minpower = minPower / 2;
	} else {
		HALDEBUG(ah, HAL_DEBUG_ANY,
		    "%s: no min/max power for %u/0x%x\n",
		    __func__, chan->ic_freq, chan->ic_flags);
		chan->ic_maxpower = MAX_RATE_POWER;
		chan->ic_minpower = 0;
	}
#if 0
	/*
	 * Now adjust to reflect any global scale and/or CTL's.
	 * (XXX is that correct?)
	 */
	powerLimit = AH_MIN(MAX_RATE_POWER, AH_PRIVATE(ah)->ah_powerLimit);
	if (powerLimit >= MAX_RATE_POWER || powerLimit == 0)
		tpcInDb = tpcScaleReductionTable[AH_PRIVATE(ah)->ah_tpScale];
	else
		tpcInDb = 0;
	if (!ar5212SetRateTable(ah, chan, tpcInDb, powerLimit,
				AH_FALSE, &minPower, &maxPower)) {
		HALDEBUG(ah, HAL_DEBUG_ANY,
		    "%s: unable to find max/min power\n",__func__);
		return AH_FALSE;
	}
	if (maxPower < chan->ic_maxpower)
		chan->ic_maxpower = maxPower;
	if (minPower < chan->ic_minpower)
		chan->ic_minpower = minPower;
	HALDEBUG(ah, HAL_DEBUG_RESET,
	    "Chan %d: MaxPow = %d MinPow = %d\n",
	    chan->ic_freq, chan->ic_maxpower, chans->ic_minpower);
#endif
	return AH_TRUE;
}

/*
 * Correct for the gain-delta between ofdm and cck mode target
 * powers. Write the results to the rate table and the power table.
 *
 *   Conventions :
 *   1. rpow[ii] is the integer value of 2*(desired power
 *    for the rate ii in dBm) to provide 0.5dB resolution. rate
 *    mapping is as following :
 *     [0..7]  --> ofdm 6, 9, .. 48, 54
 *     [8..14] --> cck 1L, 2L, 2S, .. 11L, 11S
 *     [15]    --> XR (all rates get the same power)
 *   2. powv[ii]  is the pcdac corresponding to ii/2 dBm.
 */
static void
ar5212CorrectGainDelta(struct ath_hal *ah, int twiceOfdmCckDelta)
{
#define	N(_a)	(sizeof(_a) / sizeof(_a[0]))
	struct ath_hal_5212 *ahp = AH5212(ah);
	const HAL_EEPROM *ee = AH_PRIVATE(ah)->ah_eeprom;
	int16_t ratesIndex[N(ahp->ah_ratesArray)];
	uint16_t ii, jj, iter;
	int32_t cckIndex;
	int16_t gainDeltaAdjust;

	HALASSERT(ah->ah_magic == AR5212_MAGIC);

	gainDeltaAdjust = ee->ee_cckOfdmGainDelta;

	/* make a local copy of desired powers as initial indices */
	OS_MEMCPY(ratesIndex, ahp->ah_ratesArray, sizeof(ratesIndex));

	/* fix only the CCK indices */
	for (ii = 8; ii < 15; ii++) {
		/* apply a gain_delta correction of -15 for CCK */
		ratesIndex[ii] -= gainDeltaAdjust;

		/* Now check for contention with all ofdm target powers */
		jj = 0;
		iter = 0;
		/* indicates not all ofdm rates checked forcontention yet */
		while (jj < 16) {
			if (ratesIndex[ii] < 0)
				ratesIndex[ii] = 0;
			if (jj == 8) {		/* skip CCK rates */
				jj = 15;
				continue;
			}
			if (ratesIndex[ii] == ahp->ah_ratesArray[jj]) {
				if (ahp->ah_ratesArray[jj] == 0)
					ratesIndex[ii]++;
				else if (iter > 50) {
					/*
					 * To avoid pathological case of of
					 * dm target powers 0 and 0.5dBm
					 */
					ratesIndex[ii]++;
				} else
					ratesIndex[ii]--;
				/* check with all rates again */
				jj = 0;
				iter++;
			} else
				jj++;
		}
		if (ratesIndex[ii] >= PWR_TABLE_SIZE)
			ratesIndex[ii] = PWR_TABLE_SIZE -1;
		cckIndex = ahp->ah_ratesArray[ii] - twiceOfdmCckDelta;
		if (cckIndex < 0)
			cckIndex = 0;

		/* 
		 * Validate that the indexes for the powv are not
		 * out of bounds.
		 */
		HALASSERT(cckIndex < PWR_TABLE_SIZE);
		HALASSERT(ratesIndex[ii] < PWR_TABLE_SIZE);
		ahp->ah_pcdacTable[ratesIndex[ii]] =
			ahp->ah_pcdacTable[cckIndex];
	}
	/* Override rate per power table with new values */
	for (ii = 8; ii < 15; ii++)
		ahp->ah_ratesArray[ii] = ratesIndex[ii];
#undef N
}

/*
 * Find the maximum conformance test limit for the given channel and CTL info
 */
static uint16_t
ar5212GetMaxEdgePower(uint16_t channel, const RD_EDGES_POWER *pRdEdgesPower)
{
	/* temp array for holding edge channels */
	uint16_t tempChannelList[NUM_EDGES];
	uint16_t clo, chi, twiceMaxEdgePower;
	int i, numEdges;

	/* Get the edge power */
	for (i = 0; i < NUM_EDGES; i++) {
		if (pRdEdgesPower[i].rdEdge == 0)
			break;
		tempChannelList[i] = pRdEdgesPower[i].rdEdge;
	}
	numEdges = i;

	ar5212GetLowerUpperValues(channel, tempChannelList,
		numEdges, &clo, &chi);
	/* Get the index for the lower channel */
	for (i = 0; i < numEdges && clo != tempChannelList[i]; i++)
		;
	/* Is lower channel ever outside the rdEdge? */
	HALASSERT(i != numEdges);

	if ((clo == chi && clo == channel) || (pRdEdgesPower[i].flag)) {
		/* 
		 * If there's an exact channel match or an inband flag set
		 * on the lower channel use the given rdEdgePower 
		 */
		twiceMaxEdgePower = pRdEdgesPower[i].twice_rdEdgePower;
		HALASSERT(twiceMaxEdgePower > 0);
	} else
		twiceMaxEdgePower = MAX_RATE_POWER;
	return twiceMaxEdgePower;
}

/*
 * Returns interpolated or the scaled up interpolated value
 */
static uint16_t
interpolate(uint16_t target, uint16_t srcLeft, uint16_t srcRight,
	uint16_t targetLeft, uint16_t targetRight)
{
	uint16_t rv;
	int16_t lRatio;

	/* to get an accurate ratio, always scale, if want to scale, then don't scale back down */
	if ((targetLeft * targetRight) == 0)
		return 0;

	if (srcRight != srcLeft) {
		/*
		 * Note the ratio always need to be scaled,
		 * since it will be a fraction.
		 */
		lRatio = (target - srcLeft) * EEP_SCALE / (srcRight - srcLeft);
		if (lRatio < 0) {
		    /* Return as Left target if value would be negative */
		    rv = targetLeft;
		} else if (lRatio > EEP_SCALE) {
		    /* Return as Right target if Ratio is greater than 100% (SCALE) */
		    rv = targetRight;
		} else {
			rv = (lRatio * targetRight + (EEP_SCALE - lRatio) *
					targetLeft) / EEP_SCALE;
		}
	} else {
		rv = targetLeft;
	}
	return rv;
}

/*
 * Return the four rates of target power for the given target power table 
 * channel, and number of channels
 */
static void
ar5212GetTargetPowers(struct ath_hal *ah, const struct ieee80211_channel *chan,
	const TRGT_POWER_INFO *powInfo,
	uint16_t numChannels, TRGT_POWER_INFO *pNewPower)
{
	uint16_t freq = ath_hal_gethwchannel(ah, chan);
	/* temp array for holding target power channels */
	uint16_t tempChannelList[NUM_TEST_FREQUENCIES];
	uint16_t clo, chi, ixlo, ixhi;
	int i;

	/* Copy the target powers into the temp channel list */
	for (i = 0; i < numChannels; i++)
		tempChannelList[i] = powInfo[i].testChannel;

	ar5212GetLowerUpperValues(freq, tempChannelList,
		numChannels, &clo, &chi);

	/* Get the indices for the channel */
	ixlo = ixhi = 0;
	for (i = 0; i < numChannels; i++) {
		if (clo == tempChannelList[i]) {
			ixlo = i;
		}
		if (chi == tempChannelList[i]) {
			ixhi = i;
			break;
		}
	}

	/*
	 * Get the lower and upper channels, target powers,
	 * and interpolate between them.
	 */
	pNewPower->twicePwr6_24 = interpolate(freq, clo, chi,
		powInfo[ixlo].twicePwr6_24, powInfo[ixhi].twicePwr6_24);
	pNewPower->twicePwr36 = interpolate(freq, clo, chi,
		powInfo[ixlo].twicePwr36, powInfo[ixhi].twicePwr36);
	pNewPower->twicePwr48 = interpolate(freq, clo, chi,
		powInfo[ixlo].twicePwr48, powInfo[ixhi].twicePwr48);
	pNewPower->twicePwr54 = interpolate(freq, clo, chi,
		powInfo[ixlo].twicePwr54, powInfo[ixhi].twicePwr54);
}

/*
 * Search a list for a specified value v that is within
 * EEP_DELTA of the search values.  Return the closest
 * values in the list above and below the desired value.
 * EEP_DELTA is a factional value; everything is scaled
 * so only integer arithmetic is used.
 *
 * NB: the input list is assumed to be sorted in ascending order
 */
void
ar5212GetLowerUpperValues(uint16_t v, uint16_t *lp, uint16_t listSize,
                          uint16_t *vlo, uint16_t *vhi)
{
	uint32_t target = v * EEP_SCALE;
	uint16_t *ep = lp+listSize;

	/*
	 * Check first and last elements for out-of-bounds conditions.
	 */
	if (target < (uint32_t)(lp[0] * EEP_SCALE - EEP_DELTA)) {
		*vlo = *vhi = lp[0];
		return;
	}
	if (target > (uint32_t)(ep[-1] * EEP_SCALE + EEP_DELTA)) {
		*vlo = *vhi = ep[-1];
		return;
	}

	/* look for value being near or between 2 values in list */
	for (; lp < ep; lp++) {
		/*
		 * If value is close to the current value of the list
		 * then target is not between values, it is one of the values
		 */
		if (abs(lp[0] * EEP_SCALE - target) < EEP_DELTA) {
			*vlo = *vhi = lp[0];
			return;
		}
		/*
		 * Look for value being between current value and next value
		 * if so return these 2 values
		 */
		if (target < (uint32_t)(lp[1] * EEP_SCALE - EEP_DELTA)) {
			*vlo = lp[0];
			*vhi = lp[1];
			return;
		}
	}
	HALASSERT(AH_FALSE);		/* should not reach here */
}

/*
 * Perform analog "swizzling" of parameters into their location
 *
 * NB: used by RF backends
 */
void
ar5212ModifyRfBuffer(uint32_t *rfBuf, uint32_t reg32, uint32_t numBits,
                     uint32_t firstBit, uint32_t column)
{
#define	MAX_ANALOG_START	319		/* XXX */
	uint32_t tmp32, mask, arrayEntry, lastBit;
	int32_t bitPosition, bitsLeft;

	HALASSERT(column <= 3);
	HALASSERT(numBits <= 32);
	HALASSERT(firstBit + numBits <= MAX_ANALOG_START);

	tmp32 = ath_hal_reverseBits(reg32, numBits);
	arrayEntry = (firstBit - 1) / 8;
	bitPosition = (firstBit - 1) % 8;
	bitsLeft = numBits;
	while (bitsLeft > 0) {
		lastBit = (bitPosition + bitsLeft > 8) ?
			8 : bitPosition + bitsLeft;
		mask = (((1 << lastBit) - 1) ^ ((1 << bitPosition) - 1)) <<
			(column * 8);
		rfBuf[arrayEntry] &= ~mask;
		rfBuf[arrayEntry] |= ((tmp32 << bitPosition) <<
			(column * 8)) & mask;
		bitsLeft -= 8 - bitPosition;
		tmp32 = tmp32 >> (8 - bitPosition);
		bitPosition = 0;
		arrayEntry++;
	}
#undef MAX_ANALOG_START
}

/*
 * Sets the rate to duration values in MAC - used for multi-
 * rate retry.
 * The rate duration table needs to cover all valid rate codes;
 * the 11g table covers all ofdm rates, while the 11b table
 * covers all cck rates => all valid rates get covered between
 * these two mode's ratetables!
 * But if we're turbo, the ofdm phy is replaced by the turbo phy
 * and cck is not valid with turbo => all rates get covered
 * by the turbo ratetable only
 */
void
ar5212SetRateDurationTable(struct ath_hal *ah,
	const struct ieee80211_channel *chan)
{
	const HAL_RATE_TABLE *rt;
	int i;

	/* NB: band doesn't matter for 1/2 and 1/4 rate */
	if (IEEE80211_IS_CHAN_HALF(chan)) {
		rt = ar5212GetRateTable(ah, HAL_MODE_11A_HALF_RATE);
	} else if (IEEE80211_IS_CHAN_QUARTER(chan)) {
		rt = ar5212GetRateTable(ah, HAL_MODE_11A_QUARTER_RATE);
	} else {
		rt = ar5212GetRateTable(ah,
			IEEE80211_IS_CHAN_TURBO(chan) ? HAL_MODE_TURBO : HAL_MODE_11G);
	}

	for (i = 0; i < rt->rateCount; ++i)
		OS_REG_WRITE(ah,
			AR_RATE_DURATION(rt->info[i].rateCode),
			ath_hal_computetxtime(ah, rt,
				WLAN_CTRL_FRAME_SIZE,
				rt->info[i].controlRate, AH_FALSE));
	if (!IEEE80211_IS_CHAN_TURBO(chan)) {
		/* 11g Table is used to cover the CCK rates. */
		rt = ar5212GetRateTable(ah, HAL_MODE_11G);
		for (i = 0; i < rt->rateCount; ++i) {
			uint32_t reg = AR_RATE_DURATION(rt->info[i].rateCode);

			if (rt->info[i].phy != IEEE80211_T_CCK)
				continue;

			OS_REG_WRITE(ah, reg,
				ath_hal_computetxtime(ah, rt,
					WLAN_CTRL_FRAME_SIZE,
					rt->info[i].controlRate, AH_FALSE));
			/* cck rates have short preamble option also */
			if (rt->info[i].shortPreamble) {
				reg += rt->info[i].shortPreamble << 2;
				OS_REG_WRITE(ah, reg,
					ath_hal_computetxtime(ah, rt,
						WLAN_CTRL_FRAME_SIZE,
						rt->info[i].controlRate,
						AH_TRUE));
			}
		}
	}
}

/* Adjust various register settings based on half/quarter rate clock setting.
 * This includes: +USEC, TX/RX latency, 
 *                + IFS params: slot, eifs, misc etc.
 */
void 
ar5212SetIFSTiming(struct ath_hal *ah, const struct ieee80211_channel *chan)
{
	uint32_t txLat, rxLat, usec, slot, refClock, eifs, init_usec;

	HALASSERT(IEEE80211_IS_CHAN_HALF(chan) ||
		  IEEE80211_IS_CHAN_QUARTER(chan));

	refClock = OS_REG_READ(ah, AR_USEC) & AR_USEC_USEC32;
	if (IEEE80211_IS_CHAN_HALF(chan)) {
		slot = IFS_SLOT_HALF_RATE;
		rxLat = RX_NON_FULL_RATE_LATENCY << AR5212_USEC_RX_LAT_S;
		txLat = TX_HALF_RATE_LATENCY << AR5212_USEC_TX_LAT_S;
		usec = HALF_RATE_USEC;
		eifs = IFS_EIFS_HALF_RATE;
		init_usec = INIT_USEC >> 1;
	} else { /* quarter rate */
		slot = IFS_SLOT_QUARTER_RATE;
		rxLat = RX_NON_FULL_RATE_LATENCY << AR5212_USEC_RX_LAT_S;
		txLat = TX_QUARTER_RATE_LATENCY << AR5212_USEC_TX_LAT_S;
		usec = QUARTER_RATE_USEC;
		eifs = IFS_EIFS_QUARTER_RATE;
		init_usec = INIT_USEC >> 2;
	}

	OS_REG_WRITE(ah, AR_USEC, (usec | refClock | txLat | rxLat));
	OS_REG_WRITE(ah, AR_D_GBL_IFS_SLOT, slot);
	OS_REG_WRITE(ah, AR_D_GBL_IFS_EIFS, eifs);
	OS_REG_RMW_FIELD(ah, AR_D_GBL_IFS_MISC,
				AR_D_GBL_IFS_MISC_USEC_DURATION, init_usec);
}
