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