root/src/add-ons/kernel/drivers/network/wlan/atheroswifi/dev/ath/ath_hal/ar5212/ar5212_reset.c
/*-
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#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))

/*
 * XXX NDIS 5.x code had MAX_RESET_WAIT set to 2000 for AP code
 * and 10 for Client code
 */
#define MAX_RESET_WAIT                  10

#define TX_QUEUEPEND_CHECK              1
#define TX_ENABLE_CHECK                 2
#define RX_ENABLE_CHECK                 4

/*
 * 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_RESET_TYPE resetType,
        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 */

        /* Blank the channel survey statistics */
        ath_hal_survey_clear(ah);

#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 and associate id */
        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) |
            (ahp->ah_assocId & 0x3fff) << AR_BSS_ID1_AID_S);

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

/**************************************************************
 * ar5212MacStop
 *
 * Disables all active QCUs and ensure that the mac is in a
 * quiessence state.
 */
static HAL_BOOL
ar5212MacStop(struct ath_hal *ah)
{
        HAL_BOOL     status;
        uint32_t    count;
        uint32_t    pendFrameCount;
        uint32_t    macStateFlag;
        uint32_t    queue;

        status = AH_FALSE;

        /* Disable Rx Operation ***********************************/
        OS_REG_SET_BIT(ah, AR_CR, AR_CR_RXD);

        /* Disable TX Operation ***********************************/
#ifdef NOT_YET
        ar5212SetTxdpInvalid(ah);
#endif
        OS_REG_SET_BIT(ah, AR_Q_TXD, AR_Q_TXD_M);

        /* Polling operation for completion of disable ************/
        macStateFlag = TX_ENABLE_CHECK | RX_ENABLE_CHECK;

        for (count = 0; count < MAX_RESET_WAIT; count++) {
                if (macStateFlag & RX_ENABLE_CHECK) {
                        if (!OS_REG_IS_BIT_SET(ah, AR_CR, AR_CR_RXE)) {
                                macStateFlag &= ~RX_ENABLE_CHECK;
                        }
                }

                if (macStateFlag & TX_ENABLE_CHECK) {
                        if (!OS_REG_IS_BIT_SET(ah, AR_Q_TXE, AR_Q_TXE_M)) {
                                macStateFlag &= ~TX_ENABLE_CHECK;
                                macStateFlag |= TX_QUEUEPEND_CHECK;
                        }
                }
                if (macStateFlag & TX_QUEUEPEND_CHECK) {
                        pendFrameCount = 0;
                        for (queue = 0; queue < AR_NUM_DCU; queue++) {
                                pendFrameCount += OS_REG_READ(ah,
                                    AR_Q0_STS + (queue * 4)) &
                                    AR_Q_STS_PEND_FR_CNT;
                        }
                        if (pendFrameCount == 0) {
                                macStateFlag &= ~TX_QUEUEPEND_CHECK;
                        }
                }
                if (macStateFlag == 0) {
                        status = AH_TRUE;
                        break;
                }
                OS_DELAY(50);
        }

        if (status != AH_TRUE) {
                HALDEBUG(ah, HAL_DEBUG_RESET,
                    "%s:Failed to stop the MAC state 0x%x\n",
                    __func__, macStateFlag);
        }

        return status;
}

/*
 * 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;

        /* Never reset the PCIE core */
        if (AH_PRIVATE(ah)->ah_ispcie) {
                resetMask &= ~AR_RC_PCI;
        }

        if (resetMask & (AR_RC_MAC | AR_RC_PCI)) {
                /*
                 * To ensure that the driver can reset the
                 * MAC, wake up the chip
                 */
                rt = ar5212SetPowerMode(ah, HAL_PM_AWAKE, AH_TRUE);

                if (rt != AH_TRUE) {
                        return rt;
                }

                /*
                 * Disable interrupts
                 */
                OS_REG_WRITE(ah, AR_IER, AR_IER_DISABLE);
                OS_REG_READ(ah, AR_IER);

                if (ar5212MacStop(ah) != AH_TRUE) {
                        /*
                         * Failed to stop the MAC gracefully; let's be more forceful then
                         */

                        /* need some delay before flush any pending MMR writes */
                        OS_DELAY(15);
                        OS_REG_READ(ah, AR_RXDP);

                        resetMask |= AR_RC_MAC | AR_RC_BB;
                        /* _Never_ reset PCI Express core */
                        if (! AH_PRIVATE(ah)->ah_ispcie) {
                                resetMask |= AR_RC_PCI;
                        }
#if 0
                        /*
                         * Flush the park address of the PCI controller
                        */
                        /* Read PCI slot information less than Hainan revision */
                        if (AH_PRIVATE(ah)->ah_bustype == HAL_BUS_TYPE_PCI) {
                                if (!IS_5112_REV5_UP(ah)) {
#define PCI_COMMON_CONFIG_STATUS    0x06
                                        u_int32_t    i;
                                        u_int16_t    reg16;

                                        for (i = 0; i < 32; i++) {
                                                ath_hal_read_pci_config_space(ah,
                                                    PCI_COMMON_CONFIG_STATUS,
                                                    &reg16, sizeof(reg16));
                                        }
                                }
#undef PCI_COMMON_CONFIG_STATUS
                        }
#endif
                } else {
                        /*
                         * MAC stopped gracefully; no need to warm-reset the PCI bus
                         */

                        resetMask &= ~AR_RC_PCI;

                        /* need some delay before flush any pending MMR writes */
                        OS_DELAY(15);
                        OS_REG_READ(ah, AR_RXDP);
                }
        }

        (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 descriptor accesses.
                         */
                        mask = INIT_CONFIG_STATUS | AR_CFG_SWRD;
#ifndef AH_NEED_DESC_SWAP
                        mask |= AR_CFG_SWTD;
#endif
                        OS_REG_WRITE(ah, AR_CFG, 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 threshold
 */
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);
}

static uint32_t
udiff(uint32_t u, uint32_t v)
{
        return (u >= v ? u - v : v - u);
}

/*
 * 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 (udiff(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, AH_TRUE));
        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,
                                        AH_TRUE));
                        /* 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, 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);
}