root/usr/src/uts/common/io/arn/arn_ani.c
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c) 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 "arn_core.h"
#include "arn_hw.h"
#include "arn_reg.h"
#include "arn_phy.h"

static int
ath9k_hw_get_ani_channel_idx(struct ath_hal *ah, struct ath9k_channel *chan)
{
        struct ath_hal_5416 *ahp = AH5416(ah);
        int i;

        for (i = 0; i < ARRAY_SIZE(ahp->ah_ani); i++) {
                if (ahp->ah_ani[i].c.channel == chan->channel)
                        return (i);
                if (ahp->ah_ani[i].c.channel == 0) {
                        ahp->ah_ani[i].c.channel = chan->channel;
                        ahp->ah_ani[i].c.channelFlags = chan->channelFlags;
                        return (i);
                }
        }

        ARN_DBG((ARN_DBG_ANI, "arn: ath9k_hw_get_ani_channel_idx(): "
            "No more channel states left. Using channel 0\n"));

        return (0);
}

static boolean_t
ath9k_hw_ani_control(struct ath_hal *ah, enum ath9k_ani_cmd cmd, int param)
{
        struct ath_hal_5416 *ahp = AH5416(ah);
        struct ar5416AniState *aniState = ahp->ah_curani;

        switch (cmd & ahp->ah_ani_function) {
        case ATH9K_ANI_NOISE_IMMUNITY_LEVEL: {
                uint32_t level = param;

                if (level >= ARRAY_SIZE(ahp->ah_totalSizeDesired)) {
                        ARN_DBG((ARN_DBG_ANI, "arn: "
                            "ah->ah_sc, ATH_DBG_ANI",
                            "%s: level out of range (%u > %u)\n",
                            __func__, level,
                            (unsigned)ARRAY_SIZE(ahp->ah_totalSizeDesired)));

                        return (B_FALSE);
                }

                REG_RMW_FIELD(ah, AR_PHY_DESIRED_SZ,
                    AR_PHY_DESIRED_SZ_TOT_DES,
                    ahp->ah_totalSizeDesired[level]);
                REG_RMW_FIELD(ah, AR_PHY_AGC_CTL1,
                    AR_PHY_AGC_CTL1_COARSE_LOW,
                    ahp->ah_coarseLow[level]);
                REG_RMW_FIELD(ah, AR_PHY_AGC_CTL1,
                    AR_PHY_AGC_CTL1_COARSE_HIGH,
                    ahp->ah_coarseHigh[level]);
                REG_RMW_FIELD(ah, AR_PHY_FIND_SIG,
                    AR_PHY_FIND_SIG_FIRPWR,
                    ahp->ah_firpwr[level]);

                if (level > aniState->noiseImmunityLevel)
                        ahp->ah_stats.ast_ani_niup++;
                else if (level < aniState->noiseImmunityLevel)
                        ahp->ah_stats.ast_ani_nidown++;
                aniState->noiseImmunityLevel = (uint8_t)level; /* LINT */
                break;
        }
        case ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION: {
                const int m1ThreshLow[] = { 127, 50 };
                const int m2ThreshLow[] = { 127, 40 };
                const int m1Thresh[] = { 127, 0x4d };
                const int m2Thresh[] = { 127, 0x40 };
                const int m2CountThr[] = { 31, 16 };
                const int m2CountThrLow[] = { 63, 48 };
                uint32_t on = param ? 1 : 0;

                REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW,
                    AR_PHY_SFCORR_LOW_M1_THRESH_LOW,
                    m1ThreshLow[on]);
                REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW,
                    AR_PHY_SFCORR_LOW_M2_THRESH_LOW,
                    m2ThreshLow[on]);
                REG_RMW_FIELD(ah, AR_PHY_SFCORR,
                    AR_PHY_SFCORR_M1_THRESH,
                    m1Thresh[on]);
                REG_RMW_FIELD(ah, AR_PHY_SFCORR,
                    AR_PHY_SFCORR_M2_THRESH,
                    m2Thresh[on]);
                REG_RMW_FIELD(ah, AR_PHY_SFCORR,
                    AR_PHY_SFCORR_M2COUNT_THR,
                    m2CountThr[on]);
                REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW,
                    AR_PHY_SFCORR_LOW_M2COUNT_THR_LOW,
                    m2CountThrLow[on]);

                REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT,
                    AR_PHY_SFCORR_EXT_M1_THRESH_LOW,
                    m1ThreshLow[on]);
                REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT,
                    AR_PHY_SFCORR_EXT_M2_THRESH_LOW,
                    m2ThreshLow[on]);
                REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT,
                    AR_PHY_SFCORR_EXT_M1_THRESH,
                    m1Thresh[on]);
                REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT,
                    AR_PHY_SFCORR_EXT_M2_THRESH,
                    m2Thresh[on]);

                if (on)
                        REG_SET_BIT(ah, AR_PHY_SFCORR_LOW,
                            AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW);
                else
                        REG_CLR_BIT(ah, AR_PHY_SFCORR_LOW,
                            AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW);

                if ((!on) != aniState->ofdmWeakSigDetectOff) {
                        if (on)
                                ahp->ah_stats.ast_ani_ofdmon++;
                        else
                                ahp->ah_stats.ast_ani_ofdmoff++;
                        aniState->ofdmWeakSigDetectOff = !on;
                }
                break;
        }
        case ATH9K_ANI_CCK_WEAK_SIGNAL_THR: {
                const int weakSigThrCck[] = { 8, 6 };
                uint32_t high = param ? 1 : 0;

                REG_RMW_FIELD(ah, AR_PHY_CCK_DETECT,
                    AR_PHY_CCK_DETECT_WEAK_SIG_THR_CCK,
                    weakSigThrCck[high]);
                if (high != aniState->cckWeakSigThreshold) {
                        if (high)
                                ahp->ah_stats.ast_ani_cckhigh++;
                        else
                                ahp->ah_stats.ast_ani_ccklow++;
                        /* LINT */
                        aniState->cckWeakSigThreshold = (uint8_t)high;
                }
                break;
        }
        case ATH9K_ANI_FIRSTEP_LEVEL: {
                const int firstep[] = { 0, 4, 8 };
                uint32_t level = param;

                if (level >= ARRAY_SIZE(firstep)) {
                        ARN_DBG((ARN_DBG_ANI, "arn: "
                            "%s: level out of range (%u > %u)\n",
                            __func__, level,
                            (unsigned)ARRAY_SIZE(firstep)));

                        return (B_FALSE);
                }
                REG_RMW_FIELD(ah, AR_PHY_FIND_SIG,
                    AR_PHY_FIND_SIG_FIRSTEP, firstep[level]);
                if (level > aniState->firstepLevel)
                        ahp->ah_stats.ast_ani_stepup++;
                else if (level < aniState->firstepLevel)
                        ahp->ah_stats.ast_ani_stepdown++;
                aniState->firstepLevel = (uint8_t)level; /* LINT */
                break;
        }
        case ATH9K_ANI_SPUR_IMMUNITY_LEVEL: {
                const int cycpwrThr1[] =
                        { 2, 4, 6, 8, 10, 12, 14, 16 };
                uint32_t level = param;

                if (level >= ARRAY_SIZE(cycpwrThr1)) {
                        ARN_DBG((ARN_DBG_ANI, "arn: "
                            "%s: level out of range (%u > %u)\n",
                            __func__, level,
                            (unsigned)ARRAY_SIZE(cycpwrThr1)));

                        return (B_FALSE);
                }
                REG_RMW_FIELD(ah, AR_PHY_TIMING5,
                    AR_PHY_TIMING5_CYCPWR_THR1, cycpwrThr1[level]);
                if (level > aniState->spurImmunityLevel)
                        ahp->ah_stats.ast_ani_spurup++;
                else if (level < aniState->spurImmunityLevel)
                        ahp->ah_stats.ast_ani_spurdown++;
                aniState->spurImmunityLevel = (uint8_t)level; /* LINT */
                break;
        }
        case ATH9K_ANI_PRESENT:
                break;
        default:
                ARN_DBG((ARN_DBG_ANI, "arn: "
                    "%s: invalid cmd %u\n", __func__, cmd));
                return (B_FALSE);
        }

        ARN_DBG((ARN_DBG_ANI, "arn: "
            "%s: ANI parameters:\n", __func__));
        ARN_DBG((ARN_DBG_ANI, "arn: "
            "noiseImmunityLevel=%d, spurImmunityLevel=%d, "
            "ofdmWeakSigDetectOff=%d\n",
            aniState->noiseImmunityLevel, aniState->spurImmunityLevel,
            !aniState->ofdmWeakSigDetectOff));
        ARN_DBG((ARN_DBG_ANI, "arn: "
            "cckWeakSigThreshold=%d, "
            "firstepLevel=%d, listenTime=%d\n",
            aniState->cckWeakSigThreshold, aniState->firstepLevel,
            aniState->listenTime));
        ARN_DBG((ARN_DBG_ANI, "arn: "
            "cycleCount=%d, ofdmPhyErrCount=%d, cckPhyErrCount=%d\n\n",
            aniState->cycleCount, aniState->ofdmPhyErrCount,
            aniState->cckPhyErrCount));

        return (B_TRUE);
}

static void
ath9k_hw_update_mibstats(struct ath_hal *ah, struct ath9k_mib_stats *stats)
{
        stats->ackrcv_bad += REG_READ(ah, AR_ACK_FAIL);
        stats->rts_bad += REG_READ(ah, AR_RTS_FAIL);
        stats->fcs_bad += REG_READ(ah, AR_FCS_FAIL);
        stats->rts_good += REG_READ(ah, AR_RTS_OK);
        stats->beacons += REG_READ(ah, AR_BEACON_CNT);
}

static void
ath9k_ani_restart(struct ath_hal *ah)
{
        struct ath_hal_5416 *ahp = AH5416(ah);
        struct ar5416AniState *aniState;

        if (!DO_ANI(ah))
                return;

        aniState = ahp->ah_curani;

        aniState->listenTime = 0;
        if (ahp->ah_hasHwPhyCounters) {
                if (aniState->ofdmTrigHigh > AR_PHY_COUNTMAX) {
                        aniState->ofdmPhyErrBase = 0;
                        ARN_DBG((ARN_DBG_ANI, "arn: "
                            "OFDM Trigger is too high for hw counters\n"));
                } else {
                        aniState->ofdmPhyErrBase =
                            AR_PHY_COUNTMAX - aniState->ofdmTrigHigh;
                }
                if (aniState->cckTrigHigh > AR_PHY_COUNTMAX) {
                        aniState->cckPhyErrBase = 0;
                        ARN_DBG((ARN_DBG_ANI, "arn: "
                            "CCK Trigger is too high for hw counters\n"));
                } else {
                        aniState->cckPhyErrBase =
                            AR_PHY_COUNTMAX - aniState->cckTrigHigh;
                }

                ARN_DBG((ARN_DBG_ANI, "arn: "
                    "%s: Writing ofdmbase=%u   cckbase=%u\n",
                    __func__, aniState->ofdmPhyErrBase,
                    aniState->cckPhyErrBase));

                REG_WRITE(ah, AR_PHY_ERR_1, aniState->ofdmPhyErrBase);
                REG_WRITE(ah, AR_PHY_ERR_2, aniState->cckPhyErrBase);
                REG_WRITE(ah, AR_PHY_ERR_MASK_1, AR_PHY_ERR_OFDM_TIMING);
                REG_WRITE(ah, AR_PHY_ERR_MASK_2, AR_PHY_ERR_CCK_TIMING);

                ath9k_hw_update_mibstats(ah, &ahp->ah_mibStats);
        }
        aniState->ofdmPhyErrCount = 0;
        aniState->cckPhyErrCount = 0;
}

static void
ath9k_hw_ani_ofdm_err_trigger(struct ath_hal *ah)
{
        struct ath_hal_5416 *ahp = AH5416(ah);
        struct ath9k_channel *chan = ah->ah_curchan;
        struct ar5416AniState *aniState;
        enum wireless_mode mode;
        int32_t rssi;

        if (!DO_ANI(ah))
                return;

        aniState = ahp->ah_curani;

        if (aniState->noiseImmunityLevel < HAL_NOISE_IMMUNE_MAX) {
                if (ath9k_hw_ani_control(ah, ATH9K_ANI_NOISE_IMMUNITY_LEVEL,
                    aniState->noiseImmunityLevel + 1)) {
                        return;
                }
        }

        if (aniState->spurImmunityLevel < HAL_SPUR_IMMUNE_MAX) {
                if (ath9k_hw_ani_control(ah, ATH9K_ANI_SPUR_IMMUNITY_LEVEL,
                    aniState->spurImmunityLevel + 1)) {
                        return;
                }
        }

        if (ah->ah_opmode == ATH9K_M_HOSTAP) {
                if (aniState->firstepLevel < HAL_FIRST_STEP_MAX) {
                        (void) ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL,
                            aniState->firstepLevel + 1);
                }
                return;
        }
        rssi = BEACON_RSSI(ahp);
        if (rssi > aniState->rssiThrHigh) {
                if (!aniState->ofdmWeakSigDetectOff) {
                        if (ath9k_hw_ani_control(ah,
                            ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION,
                            B_FALSE)) {
                                (void) ath9k_hw_ani_control(ah,
                                    ATH9K_ANI_SPUR_IMMUNITY_LEVEL, 0);
                                return;
                        }
                }
                if (aniState->firstepLevel < HAL_FIRST_STEP_MAX) {
                        (void) ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL,
                            aniState->firstepLevel + 1);
                        return;
                }
        } else if (rssi > aniState->rssiThrLow) {
                if (aniState->ofdmWeakSigDetectOff)
                        (void) ath9k_hw_ani_control(ah,
                            ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION,
                            B_TRUE);
                if (aniState->firstepLevel < HAL_FIRST_STEP_MAX)
                        (void) ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL,
                            aniState->firstepLevel + 1);
                return;
        } else {
                mode = ath9k_hw_chan2wmode(ah, chan);
                if (mode == ATH9K_MODE_11G || mode == ATH9K_MODE_11B) {
                        if (!aniState->ofdmWeakSigDetectOff)
                                (void) ath9k_hw_ani_control(ah,
                                    ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION,
                                    B_FALSE);
                        if (aniState->firstepLevel > 0)
                                (void) ath9k_hw_ani_control(ah,
                                    ATH9K_ANI_FIRSTEP_LEVEL, 0);
                        return;
                }
        }
}

static void
ath9k_hw_ani_cck_err_trigger(struct ath_hal *ah)
{
        struct ath_hal_5416 *ahp = AH5416(ah);
        struct ath9k_channel *chan = ah->ah_curchan;
        struct ar5416AniState *aniState;
        enum wireless_mode mode;
        int32_t rssi;

        if (!DO_ANI(ah))
                return;

        aniState = ahp->ah_curani;
        if (aniState->noiseImmunityLevel < HAL_NOISE_IMMUNE_MAX) {
                if (ath9k_hw_ani_control(ah, ATH9K_ANI_NOISE_IMMUNITY_LEVEL,
                    aniState->noiseImmunityLevel + 1)) {
                        return;
                }
        }
        if (ah->ah_opmode == ATH9K_M_HOSTAP) {
                if (aniState->firstepLevel < HAL_FIRST_STEP_MAX) {
                        (void) ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL,
                            aniState->firstepLevel + 1);
                }
                return;
        }
        rssi = BEACON_RSSI(ahp);
        if (rssi > aniState->rssiThrLow) {
                if (aniState->firstepLevel < HAL_FIRST_STEP_MAX)
                        (void) ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL,
                            aniState->firstepLevel + 1);
        } else {
                mode = ath9k_hw_chan2wmode(ah, chan);
                if (mode == ATH9K_MODE_11G || mode == ATH9K_MODE_11B) {
                        if (aniState->firstepLevel > 0)
                                (void) ath9k_hw_ani_control(ah,
                                    ATH9K_ANI_FIRSTEP_LEVEL, 0);
                }
        }
}

static void
ath9k_hw_ani_lower_immunity(struct ath_hal *ah)
{
        struct ath_hal_5416 *ahp = AH5416(ah);
        struct ar5416AniState *aniState;
        int32_t rssi;

        aniState = ahp->ah_curani;

        if (ah->ah_opmode == ATH9K_M_HOSTAP) {
                if (aniState->firstepLevel > 0) {
                        if (ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL,
                            aniState->firstepLevel - 1))
                                return;
                }
        } else {
                rssi = BEACON_RSSI(ahp);
                if (rssi > aniState->rssiThrHigh) {
                        /* XXX: Handle me */
                        ARN_DBG((ARN_DBG_ANI, "arn: ath9k_ani_reset():\n"));
                } else if (rssi > aniState->rssiThrLow) {
                        if (aniState->ofdmWeakSigDetectOff) {
                                if (ath9k_hw_ani_control(ah,
                                    ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION,
                                    B_TRUE) == B_TRUE)
                                        return;
                        }
                        if (aniState->firstepLevel > 0) {
                                if (ath9k_hw_ani_control(ah,
                                    ATH9K_ANI_FIRSTEP_LEVEL,
                                    aniState->firstepLevel - 1) == B_TRUE)
                                        return;
                        }
                } else {
                        if (aniState->firstepLevel > 0) {
                                if (ath9k_hw_ani_control(ah,
                                    ATH9K_ANI_FIRSTEP_LEVEL,
                                    aniState->firstepLevel - 1) == B_TRUE)
                                        return;
                        }
                }
        }

        if (aniState->spurImmunityLevel > 0) {
                if (ath9k_hw_ani_control(ah, ATH9K_ANI_SPUR_IMMUNITY_LEVEL,
                    aniState->spurImmunityLevel - 1))
                        return;
        }

        if (aniState->noiseImmunityLevel > 0) {
                (void) ath9k_hw_ani_control(ah, ATH9K_ANI_NOISE_IMMUNITY_LEVEL,
                    aniState->noiseImmunityLevel - 1);
                return;
        }
}

static int32_t
ath9k_hw_ani_get_listen_time(struct ath_hal *ah)
{
        struct ath_hal_5416 *ahp = AH5416(ah);
        struct ar5416AniState *aniState;
        uint32_t txFrameCount, rxFrameCount, cycleCount;
        int32_t listenTime;

        txFrameCount = REG_READ(ah, AR_TFCNT);
        rxFrameCount = REG_READ(ah, AR_RFCNT);
        cycleCount = REG_READ(ah, AR_CCCNT);

        aniState = ahp->ah_curani;
        if (aniState->cycleCount == 0 || aniState->cycleCount > cycleCount) {

                listenTime = 0;
                ahp->ah_stats.ast_ani_lzero++;
        } else {
                int32_t ccdelta = cycleCount - aniState->cycleCount;
                int32_t rfdelta = rxFrameCount - aniState->rxFrameCount;
                int32_t tfdelta = txFrameCount - aniState->txFrameCount;
                listenTime = (ccdelta - rfdelta - tfdelta) / 44000;
        }
        aniState->cycleCount = cycleCount;
        aniState->txFrameCount = txFrameCount;
        aniState->rxFrameCount = rxFrameCount;

        return (listenTime);
}

void
ath9k_ani_reset(struct ath_hal *ah)
{
        struct ath_hal_5416 *ahp = AH5416(ah);
        struct ar5416AniState *aniState;
        struct ath9k_channel *chan = ah->ah_curchan;
        int index;

        /* For Lint Reasons */
        boolean_t ANI_USE_OFDM_WEAK_SIG = ATH9K_ANI_USE_OFDM_WEAK_SIG;

        if (!DO_ANI(ah))
                return;

        index = ath9k_hw_get_ani_channel_idx(ah, chan);
        aniState = &ahp->ah_ani[index];
        ahp->ah_curani = aniState;

        if (DO_ANI(ah) && ah->ah_opmode != ATH9K_M_STA &&
            ah->ah_opmode != ATH9K_M_IBSS) {
                ARN_DBG((ARN_DBG_ANI, "arn: ath9k_ani_reset(): "
                    "Reset ANI state opmode %u\n", ah->ah_opmode));
                ahp->ah_stats.ast_ani_reset++;

                (void) ath9k_hw_ani_control(ah,
                    ATH9K_ANI_NOISE_IMMUNITY_LEVEL, 0);
                (void) ath9k_hw_ani_control(ah,
                    ATH9K_ANI_SPUR_IMMUNITY_LEVEL, 0);
                (void) ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL, 0);
                (void) ath9k_hw_ani_control
                    (ah, ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION,
                    !ANI_USE_OFDM_WEAK_SIG /* !ATH9K_ANI_USE_OFDM_WEAK_SIG */);
                (void) ath9k_hw_ani_control(ah, ATH9K_ANI_CCK_WEAK_SIGNAL_THR,
                    ATH9K_ANI_CCK_WEAK_SIG_THR);

                ath9k_hw_setrxfilter(ah, ath9k_hw_getrxfilter(ah) |
                    ATH9K_RX_FILTER_PHYERR);

                if (ah->ah_opmode == ATH9K_M_HOSTAP) {
                        ahp->ah_curani->ofdmTrigHigh =
                            ah->ah_config.ofdm_trig_high;
                        ahp->ah_curani->ofdmTrigLow =
                            ah->ah_config.ofdm_trig_low;
                        ahp->ah_curani->cckTrigHigh =
                            ah->ah_config.cck_trig_high;
                        ahp->ah_curani->cckTrigLow =
                            ah->ah_config.cck_trig_low;
                }
                ath9k_ani_restart(ah);
                return;
        }

        if (aniState->noiseImmunityLevel != 0)
                (void) ath9k_hw_ani_control(ah, ATH9K_ANI_NOISE_IMMUNITY_LEVEL,
                    aniState->noiseImmunityLevel);
        if (aniState->spurImmunityLevel != 0)
                (void) ath9k_hw_ani_control(ah, ATH9K_ANI_SPUR_IMMUNITY_LEVEL,
                    aniState->spurImmunityLevel);
        if (aniState->ofdmWeakSigDetectOff)
                (void) ath9k_hw_ani_control
                    (ah, ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION,
                    !aniState->ofdmWeakSigDetectOff);
        if (aniState->cckWeakSigThreshold)
                (void) ath9k_hw_ani_control(ah, ATH9K_ANI_CCK_WEAK_SIGNAL_THR,
                    aniState->cckWeakSigThreshold);
        if (aniState->firstepLevel != 0)
                (void) ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL,
                    aniState->firstepLevel);
        if (ahp->ah_hasHwPhyCounters) {
                ath9k_hw_setrxfilter(ah, ath9k_hw_getrxfilter(ah) &
                    ~ATH9K_RX_FILTER_PHYERR);
                ath9k_ani_restart(ah);
                REG_WRITE(ah, AR_PHY_ERR_MASK_1, AR_PHY_ERR_OFDM_TIMING);
                REG_WRITE(ah, AR_PHY_ERR_MASK_2, AR_PHY_ERR_CCK_TIMING);

        } else {
                ath9k_ani_restart(ah);
                ath9k_hw_setrxfilter(ah, ath9k_hw_getrxfilter(ah) |
                    ATH9K_RX_FILTER_PHYERR);
        }
}

/* ARGSUSED */
void
ath9k_hw_ani_monitor(struct ath_hal *ah, const struct ath9k_node_stats *stats,
    struct ath9k_channel *chan)
{
        struct ath_hal_5416 *ahp = AH5416(ah);
        struct ar5416AniState *aniState;
        int32_t listenTime;

        aniState = ahp->ah_curani;
        ahp->ah_stats.ast_nodestats = *stats;

        listenTime = ath9k_hw_ani_get_listen_time(ah);
        if (listenTime < 0) {
                ahp->ah_stats.ast_ani_lneg++;
                ath9k_ani_restart(ah);
                return;
        }

        aniState->listenTime += listenTime;

        if (ahp->ah_hasHwPhyCounters) {
                uint32_t phyCnt1, phyCnt2;
                uint32_t ofdmPhyErrCnt, cckPhyErrCnt;

                ath9k_hw_update_mibstats(ah, &ahp->ah_mibStats);

                phyCnt1 = REG_READ(ah, AR_PHY_ERR_1);
                phyCnt2 = REG_READ(ah, AR_PHY_ERR_2);

                if (phyCnt1 < aniState->ofdmPhyErrBase ||
                    phyCnt2 < aniState->cckPhyErrBase) {
                        if (phyCnt1 < aniState->ofdmPhyErrBase) {
                                ARN_DBG((ARN_DBG_ANI, "arn: "
                                    "%s: phyCnt1 0x%x, resetting "
                                    "counter value to 0x%x\n",
                                    __func__, phyCnt1,
                                    aniState->ofdmPhyErrBase));
                                REG_WRITE(ah, AR_PHY_ERR_1,
                                    aniState->ofdmPhyErrBase);
                                REG_WRITE(ah, AR_PHY_ERR_MASK_1,
                                    AR_PHY_ERR_OFDM_TIMING);
                        }
                        if (phyCnt2 < aniState->cckPhyErrBase) {
                                ARN_DBG((ARN_DBG_ANI, "arn: "
                                    "%s: phyCnt2 0x%x, resetting "
                                    "counter value to 0x%x\n",
                                    __func__, phyCnt2,
                                    aniState->cckPhyErrBase));
                                REG_WRITE(ah, AR_PHY_ERR_2,
                                    aniState->cckPhyErrBase);
                                REG_WRITE(ah, AR_PHY_ERR_MASK_2,
                                    AR_PHY_ERR_CCK_TIMING);
                        }
                        return;
                }

                ofdmPhyErrCnt = phyCnt1 - aniState->ofdmPhyErrBase;
                ahp->ah_stats.ast_ani_ofdmerrs +=
                    ofdmPhyErrCnt - aniState->ofdmPhyErrCount;
                aniState->ofdmPhyErrCount = ofdmPhyErrCnt;

                cckPhyErrCnt = phyCnt2 - aniState->cckPhyErrBase;
                ahp->ah_stats.ast_ani_cckerrs +=
                    cckPhyErrCnt - aniState->cckPhyErrCount;
                aniState->cckPhyErrCount = cckPhyErrCnt;
        }

        if (!DO_ANI(ah))
                return;

        if (aniState->listenTime > 5 * ahp->ah_aniPeriod) {
                if (aniState->ofdmPhyErrCount <= aniState->listenTime *
                    aniState->ofdmTrigLow / 1000 &&
                    aniState->cckPhyErrCount <= aniState->listenTime *
                    aniState->cckTrigLow / 1000)
                        ath9k_hw_ani_lower_immunity(ah);
                ath9k_ani_restart(ah);
        } else if (aniState->listenTime > ahp->ah_aniPeriod) {
                if (aniState->ofdmPhyErrCount > aniState->listenTime *
                    aniState->ofdmTrigHigh / 1000) {
                        ath9k_hw_ani_ofdm_err_trigger(ah);
                        ath9k_ani_restart(ah);
                } else if (aniState->cckPhyErrCount >
                    aniState->listenTime * aniState->cckTrigHigh / 1000) {
                        ath9k_hw_ani_cck_err_trigger(ah);
                        ath9k_ani_restart(ah);
                }
        }
}

boolean_t
ath9k_hw_phycounters(struct ath_hal *ah)
{
        struct ath_hal_5416 *ahp = AH5416(ah);

        return (ahp->ah_hasHwPhyCounters ? B_TRUE : B_FALSE);
}

void
ath9k_enable_mib_counters(struct ath_hal *ah)
{
        struct ath_hal_5416 *ahp = AH5416(ah);

        ARN_DBG((ARN_DBG_ANI, "arn: ath9k_enable_mib_counters(): "
            "Enable MIB counters\n"));

        ath9k_hw_update_mibstats(ah, &ahp->ah_mibStats);

        REG_WRITE(ah, AR_FILT_OFDM, 0);
        REG_WRITE(ah, AR_FILT_CCK, 0);
        REG_WRITE(ah, AR_MIBC,
            ~(AR_MIBC_COW | AR_MIBC_FMC | AR_MIBC_CMC | AR_MIBC_MCS) & 0x0f);
        REG_WRITE(ah, AR_PHY_ERR_MASK_1, AR_PHY_ERR_OFDM_TIMING);
        REG_WRITE(ah, AR_PHY_ERR_MASK_2, AR_PHY_ERR_CCK_TIMING);
}

void
ath9k_hw_disable_mib_counters(struct ath_hal *ah)
{
        struct ath_hal_5416 *ahp = AH5416(ah);

        ARN_DBG((ARN_DBG_ANI,
            "arn: ath9k_hw_disable_mib_counters(): "
            "Disable MIB counters\n"));

        REG_WRITE(ah, AR_MIBC, AR_MIBC_FMC | AR_MIBC_CMC);

        ath9k_hw_update_mibstats(ah, &ahp->ah_mibStats);

        REG_WRITE(ah, AR_FILT_OFDM, 0);
        REG_WRITE(ah, AR_FILT_CCK, 0);
}

uint32_t
ath9k_hw_GetMibCycleCountsPct(struct ath_hal *ah, uint32_t *rxc_pcnt,
    uint32_t *rxf_pcnt, uint32_t *txf_pcnt)
{
        static uint32_t cycles, rx_clear, rx_frame, tx_frame;
        uint32_t good = 1;

        uint32_t rc = REG_READ(ah, AR_RCCNT);
        uint32_t rf = REG_READ(ah, AR_RFCNT);
        uint32_t tf = REG_READ(ah, AR_TFCNT);
        uint32_t cc = REG_READ(ah, AR_CCCNT);

        if (cycles == 0 || cycles > cc) {
                ARN_DBG((ARN_DBG_CHANNEL,
                    "arn: ath9k_hw_GetMibCycleCountsPct(): "
                    "cycle counter wrap. ExtBusy = 0\n"));
                good = 0;
        } else {
                uint32_t cc_d = cc - cycles;
                uint32_t rc_d = rc - rx_clear;
                uint32_t rf_d = rf - rx_frame;
                uint32_t tf_d = tf - tx_frame;

                if (cc_d != 0) {
                        *rxc_pcnt = rc_d * 100 / cc_d;
                        *rxf_pcnt = rf_d * 100 / cc_d;
                        *txf_pcnt = tf_d * 100 / cc_d;
                } else {
                        good = 0;
                }
        }

        cycles = cc;
        rx_frame = rf;
        rx_clear = rc;
        tx_frame = tf;

        return (good);
}

/*
 * Process a MIB interrupt.  We may potentially be invoked because
 * any of the MIB counters overflow/trigger so don't assume we're
 * here because a PHY error counter triggered.
 */
void
ath9k_hw_procmibevent(struct ath_hal *ah,
    const struct ath9k_node_stats *stats)
{
        struct ath_hal_5416 *ahp = AH5416(ah);
        uint32_t phyCnt1, phyCnt2;

        /* Reset these counters regardless */
        REG_WRITE(ah, AR_FILT_OFDM, 0);
        REG_WRITE(ah, AR_FILT_CCK, 0);
        if (!(REG_READ(ah, AR_SLP_MIB_CTRL) & AR_SLP_MIB_PENDING))
                REG_WRITE(ah, AR_SLP_MIB_CTRL, AR_SLP_MIB_CLEAR);

        /* Clear the mib counters and save them in the stats */
        ath9k_hw_update_mibstats(ah, &ahp->ah_mibStats);
        ahp->ah_stats.ast_nodestats = *stats;

        if (!DO_ANI(ah))
                return;

        /* NB: these are not reset-on-read */
        phyCnt1 = REG_READ(ah, AR_PHY_ERR_1);
        phyCnt2 = REG_READ(ah, AR_PHY_ERR_2);
        if (((phyCnt1 & AR_MIBCNT_INTRMASK) == AR_MIBCNT_INTRMASK) ||
            ((phyCnt2 & AR_MIBCNT_INTRMASK) == AR_MIBCNT_INTRMASK)) {
                struct ar5416AniState *aniState = ahp->ah_curani;
                uint32_t ofdmPhyErrCnt, cckPhyErrCnt;

                /* NB: only use ast_ani_*errs with AH_PRIVATE_DIAG */
                ofdmPhyErrCnt = phyCnt1 - aniState->ofdmPhyErrBase;
                ahp->ah_stats.ast_ani_ofdmerrs +=
                    ofdmPhyErrCnt - aniState->ofdmPhyErrCount;
                aniState->ofdmPhyErrCount = ofdmPhyErrCnt;

                cckPhyErrCnt = phyCnt2 - aniState->cckPhyErrBase;
                ahp->ah_stats.ast_ani_cckerrs +=
                    cckPhyErrCnt - aniState->cckPhyErrCount;
                aniState->cckPhyErrCount = cckPhyErrCnt;

                /*
                 * NB: figure out which counter triggered.  If both
                 * trigger we'll only deal with one as the processing
                 * clobbers the error counter so the trigger threshold
                 * check will never be true.
                 */
                if (aniState->ofdmPhyErrCount > aniState->ofdmTrigHigh)
                        ath9k_hw_ani_ofdm_err_trigger(ah);
                if (aniState->cckPhyErrCount > aniState->cckTrigHigh)
                        ath9k_hw_ani_cck_err_trigger(ah);
                /* NB: always restart to insure the h/w counters are reset */
                ath9k_ani_restart(ah);
        }
}

void
ath9k_hw_ani_setup(struct ath_hal *ah)
{
        struct ath_hal_5416 *ahp = AH5416(ah);
        int i;

        const int totalSizeDesired[] = { -55, -55, -55, -55, -62 };
        const int coarseHigh[] = { -14, -14, -14, -14, -12 };
        const int coarseLow[] = { -64, -64, -64, -64, -70 };
        const int firpwr[] = { -78, -78, -78, -78, -80 };

        for (i = 0; i < 5; i++) {
                ahp->ah_totalSizeDesired[i] = totalSizeDesired[i];
                ahp->ah_coarseHigh[i] = coarseHigh[i];
                ahp->ah_coarseLow[i] = coarseLow[i];
                ahp->ah_firpwr[i] = firpwr[i];
        }
}

void
ath9k_hw_ani_attach(struct ath_hal *ah)
{
        struct ath_hal_5416 *ahp = AH5416(ah);
        int i;

        /* For Lint Reasons */
        boolean_t ANI_USE_OFDM_WEAK_SIG = ATH9K_ANI_USE_OFDM_WEAK_SIG;

        ARN_DBG((ARN_DBG_ANI, "arn: ath9k_hw_ani_attach(): "
            "Attach ANI\n"));

        ahp->ah_hasHwPhyCounters = 1;

        (void) memset(ahp->ah_ani, 0, sizeof (ahp->ah_ani));
        for (i = 0; i < ARRAY_SIZE(ahp->ah_ani); i++) {
                ahp->ah_ani[i].ofdmTrigHigh = ATH9K_ANI_OFDM_TRIG_HIGH;
                ahp->ah_ani[i].ofdmTrigLow = ATH9K_ANI_OFDM_TRIG_LOW;
                ahp->ah_ani[i].cckTrigHigh = ATH9K_ANI_CCK_TRIG_HIGH;
                ahp->ah_ani[i].cckTrigLow = ATH9K_ANI_CCK_TRIG_LOW;
                ahp->ah_ani[i].rssiThrHigh = ATH9K_ANI_RSSI_THR_HIGH;
                ahp->ah_ani[i].rssiThrLow = ATH9K_ANI_RSSI_THR_LOW;
                ahp->ah_ani[i].ofdmWeakSigDetectOff =
                    !ANI_USE_OFDM_WEAK_SIG /* !ATH9K_ANI_USE_OFDM_WEAK_SIG */;
                ahp->ah_ani[i].cckWeakSigThreshold =
                    ATH9K_ANI_CCK_WEAK_SIG_THR;
                ahp->ah_ani[i].spurImmunityLevel = ATH9K_ANI_SPUR_IMMUNE_LVL;
                ahp->ah_ani[i].firstepLevel = ATH9K_ANI_FIRSTEP_LVL;
                if (ahp->ah_hasHwPhyCounters) {
                        ahp->ah_ani[i].ofdmPhyErrBase =
                            AR_PHY_COUNTMAX - ATH9K_ANI_OFDM_TRIG_HIGH;
                        ahp->ah_ani[i].cckPhyErrBase =
                            AR_PHY_COUNTMAX - ATH9K_ANI_CCK_TRIG_HIGH;
                }
        }
        if (ahp->ah_hasHwPhyCounters) {
                ARN_DBG((ARN_DBG_ANI, "arn: ath9k_hw_ani_attach(): "
                    "Setting OfdmErrBase = 0x%08x\n",
                    ahp->ah_ani[0].ofdmPhyErrBase));
                ARN_DBG((ARN_DBG_ANI, "arn: ath9k_hw_ani_attach(): "
                    "Setting cckErrBase = 0x%08x\n",
                    ahp->ah_ani[0].cckPhyErrBase));

                REG_WRITE(ah, AR_PHY_ERR_1, ahp->ah_ani[0].ofdmPhyErrBase);
                REG_WRITE(ah, AR_PHY_ERR_2, ahp->ah_ani[0].cckPhyErrBase);
                ath9k_enable_mib_counters(ah);
        }
        ahp->ah_aniPeriod = ATH9K_ANI_PERIOD;
        if (ah->ah_config.enable_ani)
                ahp->ah_procPhyErr |= HAL_PROCESS_ANI;
}

void
ath9k_hw_ani_detach(struct ath_hal *ah)
{
        struct ath_hal_5416 *ahp = AH5416(ah);

        ARN_DBG((ARN_DBG_ANI, "arn: ath9k_hw_ani_detach(): "
            "Detach ANI\n"));

        if (ahp->ah_hasHwPhyCounters) {
                ath9k_hw_disable_mib_counters(ah);
                REG_WRITE(ah, AR_PHY_ERR_1, 0);
                REG_WRITE(ah, AR_PHY_ERR_2, 0);
        }
}