162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * Copyright (C) 2010 Bruno Randolf <br1@einfach.org>
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Permission to use, copy, modify, and/or distribute this software for any
562306a36Sopenharmony_ci * purpose with or without fee is hereby granted, provided that the above
662306a36Sopenharmony_ci * copyright notice and this permission notice appear in all copies.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
962306a36Sopenharmony_ci * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1062306a36Sopenharmony_ci * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1162306a36Sopenharmony_ci * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1262306a36Sopenharmony_ci * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1362306a36Sopenharmony_ci * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1462306a36Sopenharmony_ci * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1562306a36Sopenharmony_ci */
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include "ath5k.h"
2062306a36Sopenharmony_ci#include "reg.h"
2162306a36Sopenharmony_ci#include "debug.h"
2262306a36Sopenharmony_ci#include "ani.h"
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/**
2562306a36Sopenharmony_ci * DOC: Basic ANI Operation
2662306a36Sopenharmony_ci *
2762306a36Sopenharmony_ci * Adaptive Noise Immunity (ANI) controls five noise immunity parameters
2862306a36Sopenharmony_ci * depending on the amount of interference in the environment, increasing
2962306a36Sopenharmony_ci * or reducing sensitivity as necessary.
3062306a36Sopenharmony_ci *
3162306a36Sopenharmony_ci * The parameters are:
3262306a36Sopenharmony_ci *
3362306a36Sopenharmony_ci *   - "noise immunity"
3462306a36Sopenharmony_ci *
3562306a36Sopenharmony_ci *   - "spur immunity"
3662306a36Sopenharmony_ci *
3762306a36Sopenharmony_ci *   - "firstep level"
3862306a36Sopenharmony_ci *
3962306a36Sopenharmony_ci *   - "OFDM weak signal detection"
4062306a36Sopenharmony_ci *
4162306a36Sopenharmony_ci *   - "CCK weak signal detection"
4262306a36Sopenharmony_ci *
4362306a36Sopenharmony_ci * Basically we look at the amount of ODFM and CCK timing errors we get and then
4462306a36Sopenharmony_ci * raise or lower immunity accordingly by setting one or more of these
4562306a36Sopenharmony_ci * parameters.
4662306a36Sopenharmony_ci *
4762306a36Sopenharmony_ci * Newer chipsets have PHY error counters in hardware which will generate a MIB
4862306a36Sopenharmony_ci * interrupt when they overflow. Older hardware has too enable PHY error frames
4962306a36Sopenharmony_ci * by setting a RX flag and then count every single PHY error. When a specified
5062306a36Sopenharmony_ci * threshold of errors has been reached we will raise immunity.
5162306a36Sopenharmony_ci * Also we regularly check the amount of errors and lower or raise immunity as
5262306a36Sopenharmony_ci * necessary.
5362306a36Sopenharmony_ci */
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci/***********************\
5762306a36Sopenharmony_ci* ANI parameter control *
5862306a36Sopenharmony_ci\***********************/
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci/**
6162306a36Sopenharmony_ci * ath5k_ani_set_noise_immunity_level() - Set noise immunity level
6262306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
6362306a36Sopenharmony_ci * @level: level between 0 and @ATH5K_ANI_MAX_NOISE_IMM_LVL
6462306a36Sopenharmony_ci */
6562306a36Sopenharmony_civoid
6662306a36Sopenharmony_ciath5k_ani_set_noise_immunity_level(struct ath5k_hw *ah, int level)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	/* TODO:
6962306a36Sopenharmony_ci	 * ANI documents suggest the following five levels to use, but the HAL
7062306a36Sopenharmony_ci	 * and ath9k use only the last two levels, making this
7162306a36Sopenharmony_ci	 * essentially an on/off option. There *may* be a reason for this (???),
7262306a36Sopenharmony_ci	 * so i stick with the HAL version for now...
7362306a36Sopenharmony_ci	 */
7462306a36Sopenharmony_ci#if 0
7562306a36Sopenharmony_ci	static const s8 lo[] = { -52, -56, -60, -64, -70 };
7662306a36Sopenharmony_ci	static const s8 hi[] = { -18, -18, -16, -14, -12 };
7762306a36Sopenharmony_ci	static const s8 sz[] = { -34, -41, -48, -55, -62 };
7862306a36Sopenharmony_ci	static const s8 fr[] = { -70, -72, -75, -78, -80 };
7962306a36Sopenharmony_ci#else
8062306a36Sopenharmony_ci	static const s8 lo[] = { -64, -70 };
8162306a36Sopenharmony_ci	static const s8 hi[] = { -14, -12 };
8262306a36Sopenharmony_ci	static const s8 sz[] = { -55, -62 };
8362306a36Sopenharmony_ci	static const s8 fr[] = { -78, -80 };
8462306a36Sopenharmony_ci#endif
8562306a36Sopenharmony_ci	if (level < 0 || level >= ARRAY_SIZE(sz)) {
8662306a36Sopenharmony_ci		ATH5K_ERR(ah, "noise immunity level %d out of range",
8762306a36Sopenharmony_ci			  level);
8862306a36Sopenharmony_ci		return;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DESIRED_SIZE,
9262306a36Sopenharmony_ci				AR5K_PHY_DESIRED_SIZE_TOT, sz[level]);
9362306a36Sopenharmony_ci	AR5K_REG_WRITE_BITS(ah, AR5K_PHY_AGCCOARSE,
9462306a36Sopenharmony_ci				AR5K_PHY_AGCCOARSE_LO, lo[level]);
9562306a36Sopenharmony_ci	AR5K_REG_WRITE_BITS(ah, AR5K_PHY_AGCCOARSE,
9662306a36Sopenharmony_ci				AR5K_PHY_AGCCOARSE_HI, hi[level]);
9762306a36Sopenharmony_ci	AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SIG,
9862306a36Sopenharmony_ci				AR5K_PHY_SIG_FIRPWR, fr[level]);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	ah->ani_state.noise_imm_level = level;
10162306a36Sopenharmony_ci	ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "new level %d", level);
10262306a36Sopenharmony_ci}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci/**
10562306a36Sopenharmony_ci * ath5k_ani_set_spur_immunity_level() - Set spur immunity level
10662306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
10762306a36Sopenharmony_ci * @level: level between 0 and @max_spur_level (the maximum level is dependent
10862306a36Sopenharmony_ci * on the chip revision).
10962306a36Sopenharmony_ci */
11062306a36Sopenharmony_civoid
11162306a36Sopenharmony_ciath5k_ani_set_spur_immunity_level(struct ath5k_hw *ah, int level)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	static const int val[] = { 2, 4, 6, 8, 10, 12, 14, 16 };
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	if (level < 0 || level >= ARRAY_SIZE(val) ||
11662306a36Sopenharmony_ci	    level > ah->ani_state.max_spur_level) {
11762306a36Sopenharmony_ci		ATH5K_ERR(ah, "spur immunity level %d out of range",
11862306a36Sopenharmony_ci			  level);
11962306a36Sopenharmony_ci		return;
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	AR5K_REG_WRITE_BITS(ah, AR5K_PHY_OFDM_SELFCORR,
12362306a36Sopenharmony_ci		AR5K_PHY_OFDM_SELFCORR_CYPWR_THR1, val[level]);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	ah->ani_state.spur_level = level;
12662306a36Sopenharmony_ci	ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "new level %d", level);
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci/**
13062306a36Sopenharmony_ci * ath5k_ani_set_firstep_level() - Set "firstep" level
13162306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
13262306a36Sopenharmony_ci * @level: level between 0 and @ATH5K_ANI_MAX_FIRSTEP_LVL
13362306a36Sopenharmony_ci */
13462306a36Sopenharmony_civoid
13562306a36Sopenharmony_ciath5k_ani_set_firstep_level(struct ath5k_hw *ah, int level)
13662306a36Sopenharmony_ci{
13762306a36Sopenharmony_ci	static const int val[] = { 0, 4, 8 };
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	if (level < 0 || level >= ARRAY_SIZE(val)) {
14062306a36Sopenharmony_ci		ATH5K_ERR(ah, "firstep level %d out of range", level);
14162306a36Sopenharmony_ci		return;
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SIG,
14562306a36Sopenharmony_ci				AR5K_PHY_SIG_FIRSTEP, val[level]);
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	ah->ani_state.firstep_level = level;
14862306a36Sopenharmony_ci	ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "new level %d", level);
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci/**
15262306a36Sopenharmony_ci * ath5k_ani_set_ofdm_weak_signal_detection() - Set OFDM weak signal detection
15362306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
15462306a36Sopenharmony_ci * @on: turn on or off
15562306a36Sopenharmony_ci */
15662306a36Sopenharmony_civoid
15762306a36Sopenharmony_ciath5k_ani_set_ofdm_weak_signal_detection(struct ath5k_hw *ah, bool on)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	static const int m1l[] = { 127, 50 };
16062306a36Sopenharmony_ci	static const int m2l[] = { 127, 40 };
16162306a36Sopenharmony_ci	static const int m1[] = { 127, 0x4d };
16262306a36Sopenharmony_ci	static const int m2[] = { 127, 0x40 };
16362306a36Sopenharmony_ci	static const int m2cnt[] = { 31, 16 };
16462306a36Sopenharmony_ci	static const int m2lcnt[] = { 63, 48 };
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR,
16762306a36Sopenharmony_ci				AR5K_PHY_WEAK_OFDM_LOW_THR_M1, m1l[on]);
16862306a36Sopenharmony_ci	AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR,
16962306a36Sopenharmony_ci				AR5K_PHY_WEAK_OFDM_LOW_THR_M2, m2l[on]);
17062306a36Sopenharmony_ci	AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_HIGH_THR,
17162306a36Sopenharmony_ci				AR5K_PHY_WEAK_OFDM_HIGH_THR_M1, m1[on]);
17262306a36Sopenharmony_ci	AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_HIGH_THR,
17362306a36Sopenharmony_ci				AR5K_PHY_WEAK_OFDM_HIGH_THR_M2, m2[on]);
17462306a36Sopenharmony_ci	AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_HIGH_THR,
17562306a36Sopenharmony_ci			AR5K_PHY_WEAK_OFDM_HIGH_THR_M2_COUNT, m2cnt[on]);
17662306a36Sopenharmony_ci	AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR,
17762306a36Sopenharmony_ci			AR5K_PHY_WEAK_OFDM_LOW_THR_M2_COUNT, m2lcnt[on]);
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	if (on)
18062306a36Sopenharmony_ci		AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR,
18162306a36Sopenharmony_ci				AR5K_PHY_WEAK_OFDM_LOW_THR_SELFCOR_EN);
18262306a36Sopenharmony_ci	else
18362306a36Sopenharmony_ci		AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR,
18462306a36Sopenharmony_ci				AR5K_PHY_WEAK_OFDM_LOW_THR_SELFCOR_EN);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	ah->ani_state.ofdm_weak_sig = on;
18762306a36Sopenharmony_ci	ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "turned %s",
18862306a36Sopenharmony_ci			  on ? "on" : "off");
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci/**
19262306a36Sopenharmony_ci * ath5k_ani_set_cck_weak_signal_detection() - Set CCK weak signal detection
19362306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
19462306a36Sopenharmony_ci * @on: turn on or off
19562306a36Sopenharmony_ci */
19662306a36Sopenharmony_civoid
19762306a36Sopenharmony_ciath5k_ani_set_cck_weak_signal_detection(struct ath5k_hw *ah, bool on)
19862306a36Sopenharmony_ci{
19962306a36Sopenharmony_ci	static const int val[] = { 8, 6 };
20062306a36Sopenharmony_ci	AR5K_REG_WRITE_BITS(ah, AR5K_PHY_CCK_CROSSCORR,
20162306a36Sopenharmony_ci				AR5K_PHY_CCK_CROSSCORR_WEAK_SIG_THR, val[on]);
20262306a36Sopenharmony_ci	ah->ani_state.cck_weak_sig = on;
20362306a36Sopenharmony_ci	ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "turned %s",
20462306a36Sopenharmony_ci			  on ? "on" : "off");
20562306a36Sopenharmony_ci}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci/***************\
20962306a36Sopenharmony_ci* ANI algorithm *
21062306a36Sopenharmony_ci\***************/
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci/**
21362306a36Sopenharmony_ci * ath5k_ani_raise_immunity() - Increase noise immunity
21462306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
21562306a36Sopenharmony_ci * @as: The &struct ath5k_ani_state
21662306a36Sopenharmony_ci * @ofdm_trigger: If this is true we are called because of too many OFDM errors,
21762306a36Sopenharmony_ci * the algorithm will tune more parameters then.
21862306a36Sopenharmony_ci *
21962306a36Sopenharmony_ci * Try to raise noise immunity (=decrease sensitivity) in several steps
22062306a36Sopenharmony_ci * depending on the average RSSI of the beacons we received.
22162306a36Sopenharmony_ci */
22262306a36Sopenharmony_cistatic void
22362306a36Sopenharmony_ciath5k_ani_raise_immunity(struct ath5k_hw *ah, struct ath5k_ani_state *as,
22462306a36Sopenharmony_ci			 bool ofdm_trigger)
22562306a36Sopenharmony_ci{
22662306a36Sopenharmony_ci	int rssi = ewma_beacon_rssi_read(&ah->ah_beacon_rssi_avg);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "raise immunity (%s)",
22962306a36Sopenharmony_ci		ofdm_trigger ? "ODFM" : "CCK");
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	/* first: raise noise immunity */
23262306a36Sopenharmony_ci	if (as->noise_imm_level < ATH5K_ANI_MAX_NOISE_IMM_LVL) {
23362306a36Sopenharmony_ci		ath5k_ani_set_noise_immunity_level(ah, as->noise_imm_level + 1);
23462306a36Sopenharmony_ci		return;
23562306a36Sopenharmony_ci	}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	/* only OFDM: raise spur immunity level */
23862306a36Sopenharmony_ci	if (ofdm_trigger &&
23962306a36Sopenharmony_ci	    as->spur_level < ah->ani_state.max_spur_level) {
24062306a36Sopenharmony_ci		ath5k_ani_set_spur_immunity_level(ah, as->spur_level + 1);
24162306a36Sopenharmony_ci		return;
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	/* AP mode */
24562306a36Sopenharmony_ci	if (ah->opmode == NL80211_IFTYPE_AP) {
24662306a36Sopenharmony_ci		if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP_LVL)
24762306a36Sopenharmony_ci			ath5k_ani_set_firstep_level(ah, as->firstep_level + 1);
24862306a36Sopenharmony_ci		return;
24962306a36Sopenharmony_ci	}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	/* STA and IBSS mode */
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	/* TODO: for IBSS mode it would be better to keep a beacon RSSI average
25462306a36Sopenharmony_ci	 * per each neighbour node and use the minimum of these, to make sure we
25562306a36Sopenharmony_ci	 * don't shut out a remote node by raising immunity too high. */
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	if (rssi > ATH5K_ANI_RSSI_THR_HIGH) {
25862306a36Sopenharmony_ci		ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI,
25962306a36Sopenharmony_ci				  "beacon RSSI high");
26062306a36Sopenharmony_ci		/* only OFDM: beacon RSSI is high, we can disable ODFM weak
26162306a36Sopenharmony_ci		 * signal detection */
26262306a36Sopenharmony_ci		if (ofdm_trigger && as->ofdm_weak_sig) {
26362306a36Sopenharmony_ci			ath5k_ani_set_ofdm_weak_signal_detection(ah, false);
26462306a36Sopenharmony_ci			ath5k_ani_set_spur_immunity_level(ah, 0);
26562306a36Sopenharmony_ci			return;
26662306a36Sopenharmony_ci		}
26762306a36Sopenharmony_ci		/* as a last resort or CCK: raise firstep level */
26862306a36Sopenharmony_ci		if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP_LVL) {
26962306a36Sopenharmony_ci			ath5k_ani_set_firstep_level(ah, as->firstep_level + 1);
27062306a36Sopenharmony_ci			return;
27162306a36Sopenharmony_ci		}
27262306a36Sopenharmony_ci	} else if (rssi > ATH5K_ANI_RSSI_THR_LOW) {
27362306a36Sopenharmony_ci		/* beacon RSSI in mid range, we need OFDM weak signal detect,
27462306a36Sopenharmony_ci		 * but can raise firstep level */
27562306a36Sopenharmony_ci		ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI,
27662306a36Sopenharmony_ci				  "beacon RSSI mid");
27762306a36Sopenharmony_ci		if (ofdm_trigger && !as->ofdm_weak_sig)
27862306a36Sopenharmony_ci			ath5k_ani_set_ofdm_weak_signal_detection(ah, true);
27962306a36Sopenharmony_ci		if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP_LVL)
28062306a36Sopenharmony_ci			ath5k_ani_set_firstep_level(ah, as->firstep_level + 1);
28162306a36Sopenharmony_ci		return;
28262306a36Sopenharmony_ci	} else if (ah->ah_current_channel->band == NL80211_BAND_2GHZ) {
28362306a36Sopenharmony_ci		/* beacon RSSI is low. in B/G mode turn of OFDM weak signal
28462306a36Sopenharmony_ci		 * detect and zero firstep level to maximize CCK sensitivity */
28562306a36Sopenharmony_ci		ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI,
28662306a36Sopenharmony_ci				  "beacon RSSI low, 2GHz");
28762306a36Sopenharmony_ci		if (ofdm_trigger && as->ofdm_weak_sig)
28862306a36Sopenharmony_ci			ath5k_ani_set_ofdm_weak_signal_detection(ah, false);
28962306a36Sopenharmony_ci		if (as->firstep_level > 0)
29062306a36Sopenharmony_ci			ath5k_ani_set_firstep_level(ah, 0);
29162306a36Sopenharmony_ci		return;
29262306a36Sopenharmony_ci	}
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	/* TODO: why not?:
29562306a36Sopenharmony_ci	if (as->cck_weak_sig == true) {
29662306a36Sopenharmony_ci		ath5k_ani_set_cck_weak_signal_detection(ah, false);
29762306a36Sopenharmony_ci	}
29862306a36Sopenharmony_ci	*/
29962306a36Sopenharmony_ci}
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci/**
30262306a36Sopenharmony_ci * ath5k_ani_lower_immunity() - Decrease noise immunity
30362306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
30462306a36Sopenharmony_ci * @as: The &struct ath5k_ani_state
30562306a36Sopenharmony_ci *
30662306a36Sopenharmony_ci * Try to lower noise immunity (=increase sensitivity) in several steps
30762306a36Sopenharmony_ci * depending on the average RSSI of the beacons we received.
30862306a36Sopenharmony_ci */
30962306a36Sopenharmony_cistatic void
31062306a36Sopenharmony_ciath5k_ani_lower_immunity(struct ath5k_hw *ah, struct ath5k_ani_state *as)
31162306a36Sopenharmony_ci{
31262306a36Sopenharmony_ci	int rssi = ewma_beacon_rssi_read(&ah->ah_beacon_rssi_avg);
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "lower immunity");
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	if (ah->opmode == NL80211_IFTYPE_AP) {
31762306a36Sopenharmony_ci		/* AP mode */
31862306a36Sopenharmony_ci		if (as->firstep_level > 0) {
31962306a36Sopenharmony_ci			ath5k_ani_set_firstep_level(ah, as->firstep_level - 1);
32062306a36Sopenharmony_ci			return;
32162306a36Sopenharmony_ci		}
32262306a36Sopenharmony_ci	} else {
32362306a36Sopenharmony_ci		/* STA and IBSS mode (see TODO above) */
32462306a36Sopenharmony_ci		if (rssi > ATH5K_ANI_RSSI_THR_HIGH) {
32562306a36Sopenharmony_ci			/* beacon signal is high, leave OFDM weak signal
32662306a36Sopenharmony_ci			 * detection off or it may oscillate
32762306a36Sopenharmony_ci			 * TODO: who said it's off??? */
32862306a36Sopenharmony_ci		} else if (rssi > ATH5K_ANI_RSSI_THR_LOW) {
32962306a36Sopenharmony_ci			/* beacon RSSI is mid-range: turn on ODFM weak signal
33062306a36Sopenharmony_ci			 * detection and next, lower firstep level */
33162306a36Sopenharmony_ci			if (!as->ofdm_weak_sig) {
33262306a36Sopenharmony_ci				ath5k_ani_set_ofdm_weak_signal_detection(ah,
33362306a36Sopenharmony_ci									 true);
33462306a36Sopenharmony_ci				return;
33562306a36Sopenharmony_ci			}
33662306a36Sopenharmony_ci			if (as->firstep_level > 0) {
33762306a36Sopenharmony_ci				ath5k_ani_set_firstep_level(ah,
33862306a36Sopenharmony_ci							as->firstep_level - 1);
33962306a36Sopenharmony_ci				return;
34062306a36Sopenharmony_ci			}
34162306a36Sopenharmony_ci		} else {
34262306a36Sopenharmony_ci			/* beacon signal is low: only reduce firstep level */
34362306a36Sopenharmony_ci			if (as->firstep_level > 0) {
34462306a36Sopenharmony_ci				ath5k_ani_set_firstep_level(ah,
34562306a36Sopenharmony_ci							as->firstep_level - 1);
34662306a36Sopenharmony_ci				return;
34762306a36Sopenharmony_ci			}
34862306a36Sopenharmony_ci		}
34962306a36Sopenharmony_ci	}
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	/* all modes */
35262306a36Sopenharmony_ci	if (as->spur_level > 0) {
35362306a36Sopenharmony_ci		ath5k_ani_set_spur_immunity_level(ah, as->spur_level - 1);
35462306a36Sopenharmony_ci		return;
35562306a36Sopenharmony_ci	}
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	/* finally, reduce noise immunity */
35862306a36Sopenharmony_ci	if (as->noise_imm_level > 0) {
35962306a36Sopenharmony_ci		ath5k_ani_set_noise_immunity_level(ah, as->noise_imm_level - 1);
36062306a36Sopenharmony_ci		return;
36162306a36Sopenharmony_ci	}
36262306a36Sopenharmony_ci}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci/**
36562306a36Sopenharmony_ci * ath5k_hw_ani_get_listen_time() - Update counters and return listening time
36662306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
36762306a36Sopenharmony_ci * @as: The &struct ath5k_ani_state
36862306a36Sopenharmony_ci *
36962306a36Sopenharmony_ci * Return an approximation of the time spent "listening" in milliseconds (ms)
37062306a36Sopenharmony_ci * since the last call of this function.
37162306a36Sopenharmony_ci * Save a snapshot of the counter values for debugging/statistics.
37262306a36Sopenharmony_ci */
37362306a36Sopenharmony_cistatic int
37462306a36Sopenharmony_ciath5k_hw_ani_get_listen_time(struct ath5k_hw *ah, struct ath5k_ani_state *as)
37562306a36Sopenharmony_ci{
37662306a36Sopenharmony_ci	struct ath_common *common = ath5k_hw_common(ah);
37762306a36Sopenharmony_ci	int listen;
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	spin_lock_bh(&common->cc_lock);
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	ath_hw_cycle_counters_update(common);
38262306a36Sopenharmony_ci	memcpy(&as->last_cc, &common->cc_ani, sizeof(as->last_cc));
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	/* clears common->cc_ani */
38562306a36Sopenharmony_ci	listen = ath_hw_get_listen_time(common);
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	spin_unlock_bh(&common->cc_lock);
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	return listen;
39062306a36Sopenharmony_ci}
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci/**
39362306a36Sopenharmony_ci * ath5k_ani_save_and_clear_phy_errors() - Clear and save PHY error counters
39462306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
39562306a36Sopenharmony_ci * @as: The &struct ath5k_ani_state
39662306a36Sopenharmony_ci *
39762306a36Sopenharmony_ci * Clear the PHY error counters as soon as possible, since this might be called
39862306a36Sopenharmony_ci * from a MIB interrupt and we want to make sure we don't get interrupted again.
39962306a36Sopenharmony_ci * Add the count of CCK and OFDM errors to our internal state, so it can be used
40062306a36Sopenharmony_ci * by the algorithm later.
40162306a36Sopenharmony_ci *
40262306a36Sopenharmony_ci * Will be called from interrupt and tasklet context.
40362306a36Sopenharmony_ci * Returns 0 if both counters are zero.
40462306a36Sopenharmony_ci */
40562306a36Sopenharmony_cistatic int
40662306a36Sopenharmony_ciath5k_ani_save_and_clear_phy_errors(struct ath5k_hw *ah,
40762306a36Sopenharmony_ci				    struct ath5k_ani_state *as)
40862306a36Sopenharmony_ci{
40962306a36Sopenharmony_ci	unsigned int ofdm_err, cck_err;
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	if (!ah->ah_capabilities.cap_has_phyerr_counters)
41262306a36Sopenharmony_ci		return 0;
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	ofdm_err = ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT1);
41562306a36Sopenharmony_ci	cck_err = ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT2);
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	/* reset counters first, we might be in a hurry (interrupt) */
41862306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_OFDM_TRIG_HIGH,
41962306a36Sopenharmony_ci			   AR5K_PHYERR_CNT1);
42062306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_CCK_TRIG_HIGH,
42162306a36Sopenharmony_ci			   AR5K_PHYERR_CNT2);
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	ofdm_err = ATH5K_ANI_OFDM_TRIG_HIGH - (ATH5K_PHYERR_CNT_MAX - ofdm_err);
42462306a36Sopenharmony_ci	cck_err = ATH5K_ANI_CCK_TRIG_HIGH - (ATH5K_PHYERR_CNT_MAX - cck_err);
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	/* sometimes both can be zero, especially when there is a superfluous
42762306a36Sopenharmony_ci	 * second interrupt. detect that here and return an error. */
42862306a36Sopenharmony_ci	if (ofdm_err <= 0 && cck_err <= 0)
42962306a36Sopenharmony_ci		return 0;
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci	/* avoid negative values should one of the registers overflow */
43262306a36Sopenharmony_ci	if (ofdm_err > 0) {
43362306a36Sopenharmony_ci		as->ofdm_errors += ofdm_err;
43462306a36Sopenharmony_ci		as->sum_ofdm_errors += ofdm_err;
43562306a36Sopenharmony_ci	}
43662306a36Sopenharmony_ci	if (cck_err > 0) {
43762306a36Sopenharmony_ci		as->cck_errors += cck_err;
43862306a36Sopenharmony_ci		as->sum_cck_errors += cck_err;
43962306a36Sopenharmony_ci	}
44062306a36Sopenharmony_ci	return 1;
44162306a36Sopenharmony_ci}
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci/**
44462306a36Sopenharmony_ci * ath5k_ani_period_restart() - Restart ANI period
44562306a36Sopenharmony_ci * @as: The &struct ath5k_ani_state
44662306a36Sopenharmony_ci *
44762306a36Sopenharmony_ci * Just reset counters, so they are clear for the next "ani period".
44862306a36Sopenharmony_ci */
44962306a36Sopenharmony_cistatic void
45062306a36Sopenharmony_ciath5k_ani_period_restart(struct ath5k_ani_state *as)
45162306a36Sopenharmony_ci{
45262306a36Sopenharmony_ci	/* keep last values for debugging */
45362306a36Sopenharmony_ci	as->last_ofdm_errors = as->ofdm_errors;
45462306a36Sopenharmony_ci	as->last_cck_errors = as->cck_errors;
45562306a36Sopenharmony_ci	as->last_listen = as->listen_time;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	as->ofdm_errors = 0;
45862306a36Sopenharmony_ci	as->cck_errors = 0;
45962306a36Sopenharmony_ci	as->listen_time = 0;
46062306a36Sopenharmony_ci}
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci/**
46362306a36Sopenharmony_ci * ath5k_ani_calibration() - The main ANI calibration function
46462306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
46562306a36Sopenharmony_ci *
46662306a36Sopenharmony_ci * We count OFDM and CCK errors relative to the time where we did not send or
46762306a36Sopenharmony_ci * receive ("listen" time) and raise or lower immunity accordingly.
46862306a36Sopenharmony_ci * This is called regularly (every second) from the calibration timer, but also
46962306a36Sopenharmony_ci * when an error threshold has been reached.
47062306a36Sopenharmony_ci *
47162306a36Sopenharmony_ci * In order to synchronize access from different contexts, this should be
47262306a36Sopenharmony_ci * called only indirectly by scheduling the ANI tasklet!
47362306a36Sopenharmony_ci */
47462306a36Sopenharmony_civoid
47562306a36Sopenharmony_ciath5k_ani_calibration(struct ath5k_hw *ah)
47662306a36Sopenharmony_ci{
47762306a36Sopenharmony_ci	struct ath5k_ani_state *as = &ah->ani_state;
47862306a36Sopenharmony_ci	int listen, ofdm_high, ofdm_low, cck_high, cck_low;
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	/* get listen time since last call and add it to the counter because we
48162306a36Sopenharmony_ci	 * might not have restarted the "ani period" last time.
48262306a36Sopenharmony_ci	 * always do this to calculate the busy time also in manual mode */
48362306a36Sopenharmony_ci	listen = ath5k_hw_ani_get_listen_time(ah, as);
48462306a36Sopenharmony_ci	as->listen_time += listen;
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	if (as->ani_mode != ATH5K_ANI_MODE_AUTO)
48762306a36Sopenharmony_ci		return;
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	ath5k_ani_save_and_clear_phy_errors(ah, as);
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	ofdm_high = as->listen_time * ATH5K_ANI_OFDM_TRIG_HIGH / 1000;
49262306a36Sopenharmony_ci	cck_high = as->listen_time * ATH5K_ANI_CCK_TRIG_HIGH / 1000;
49362306a36Sopenharmony_ci	ofdm_low = as->listen_time * ATH5K_ANI_OFDM_TRIG_LOW / 1000;
49462306a36Sopenharmony_ci	cck_low = as->listen_time * ATH5K_ANI_CCK_TRIG_LOW / 1000;
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI,
49762306a36Sopenharmony_ci		"listen %d (now %d)", as->listen_time, listen);
49862306a36Sopenharmony_ci	ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI,
49962306a36Sopenharmony_ci		"check high ofdm %d/%d cck %d/%d",
50062306a36Sopenharmony_ci		as->ofdm_errors, ofdm_high, as->cck_errors, cck_high);
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	if (as->ofdm_errors > ofdm_high || as->cck_errors > cck_high) {
50362306a36Sopenharmony_ci		/* too many PHY errors - we have to raise immunity */
50462306a36Sopenharmony_ci		bool ofdm_flag = as->ofdm_errors > ofdm_high;
50562306a36Sopenharmony_ci		ath5k_ani_raise_immunity(ah, as, ofdm_flag);
50662306a36Sopenharmony_ci		ath5k_ani_period_restart(as);
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci	} else if (as->listen_time > 5 * ATH5K_ANI_LISTEN_PERIOD) {
50962306a36Sopenharmony_ci		/* If more than 5 (TODO: why 5?) periods have passed and we got
51062306a36Sopenharmony_ci		 * relatively little errors we can try to lower immunity */
51162306a36Sopenharmony_ci		ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI,
51262306a36Sopenharmony_ci			"check low ofdm %d/%d cck %d/%d",
51362306a36Sopenharmony_ci			as->ofdm_errors, ofdm_low, as->cck_errors, cck_low);
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci		if (as->ofdm_errors <= ofdm_low && as->cck_errors <= cck_low)
51662306a36Sopenharmony_ci			ath5k_ani_lower_immunity(ah, as);
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci		ath5k_ani_period_restart(as);
51962306a36Sopenharmony_ci	}
52062306a36Sopenharmony_ci}
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci/*******************\
52462306a36Sopenharmony_ci* Interrupt handler *
52562306a36Sopenharmony_ci\*******************/
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci/**
52862306a36Sopenharmony_ci * ath5k_ani_mib_intr() - Interrupt handler for ANI MIB counters
52962306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
53062306a36Sopenharmony_ci *
53162306a36Sopenharmony_ci * Just read & reset the registers quickly, so they don't generate more
53262306a36Sopenharmony_ci * interrupts, save the counters and schedule the tasklet to decide whether
53362306a36Sopenharmony_ci * to raise immunity or not.
53462306a36Sopenharmony_ci *
53562306a36Sopenharmony_ci * We just need to handle PHY error counters, ath5k_hw_update_mib_counters()
53662306a36Sopenharmony_ci * should take care of all "normal" MIB interrupts.
53762306a36Sopenharmony_ci */
53862306a36Sopenharmony_civoid
53962306a36Sopenharmony_ciath5k_ani_mib_intr(struct ath5k_hw *ah)
54062306a36Sopenharmony_ci{
54162306a36Sopenharmony_ci	struct ath5k_ani_state *as = &ah->ani_state;
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	/* nothing to do here if HW does not have PHY error counters - they
54462306a36Sopenharmony_ci	 * can't be the reason for the MIB interrupt then */
54562306a36Sopenharmony_ci	if (!ah->ah_capabilities.cap_has_phyerr_counters)
54662306a36Sopenharmony_ci		return;
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	/* not in use but clear anyways */
54962306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, 0, AR5K_OFDM_FIL_CNT);
55062306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, 0, AR5K_CCK_FIL_CNT);
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	if (ah->ani_state.ani_mode != ATH5K_ANI_MODE_AUTO)
55362306a36Sopenharmony_ci		return;
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci	/* If one of the errors triggered, we can get a superfluous second
55662306a36Sopenharmony_ci	 * interrupt, even though we have already reset the register. The
55762306a36Sopenharmony_ci	 * function detects that so we can return early. */
55862306a36Sopenharmony_ci	if (ath5k_ani_save_and_clear_phy_errors(ah, as) == 0)
55962306a36Sopenharmony_ci		return;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	if (as->ofdm_errors > ATH5K_ANI_OFDM_TRIG_HIGH ||
56262306a36Sopenharmony_ci	    as->cck_errors > ATH5K_ANI_CCK_TRIG_HIGH)
56362306a36Sopenharmony_ci		tasklet_schedule(&ah->ani_tasklet);
56462306a36Sopenharmony_ci}
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci/**
56762306a36Sopenharmony_ci * ath5k_ani_phy_error_report - Used by older HW to report PHY errors
56862306a36Sopenharmony_ci *
56962306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
57062306a36Sopenharmony_ci * @phyerr: One of enum ath5k_phy_error_code
57162306a36Sopenharmony_ci *
57262306a36Sopenharmony_ci * This is used by hardware without PHY error counters to report PHY errors
57362306a36Sopenharmony_ci * on a frame-by-frame basis, instead of the interrupt.
57462306a36Sopenharmony_ci */
57562306a36Sopenharmony_civoid
57662306a36Sopenharmony_ciath5k_ani_phy_error_report(struct ath5k_hw *ah,
57762306a36Sopenharmony_ci			   enum ath5k_phy_error_code phyerr)
57862306a36Sopenharmony_ci{
57962306a36Sopenharmony_ci	struct ath5k_ani_state *as = &ah->ani_state;
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_ci	if (phyerr == AR5K_RX_PHY_ERROR_OFDM_TIMING) {
58262306a36Sopenharmony_ci		as->ofdm_errors++;
58362306a36Sopenharmony_ci		if (as->ofdm_errors > ATH5K_ANI_OFDM_TRIG_HIGH)
58462306a36Sopenharmony_ci			tasklet_schedule(&ah->ani_tasklet);
58562306a36Sopenharmony_ci	} else if (phyerr == AR5K_RX_PHY_ERROR_CCK_TIMING) {
58662306a36Sopenharmony_ci		as->cck_errors++;
58762306a36Sopenharmony_ci		if (as->cck_errors > ATH5K_ANI_CCK_TRIG_HIGH)
58862306a36Sopenharmony_ci			tasklet_schedule(&ah->ani_tasklet);
58962306a36Sopenharmony_ci	}
59062306a36Sopenharmony_ci}
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci/****************\
59462306a36Sopenharmony_ci* Initialization *
59562306a36Sopenharmony_ci\****************/
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci/**
59862306a36Sopenharmony_ci * ath5k_enable_phy_err_counters() - Enable PHY error counters
59962306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
60062306a36Sopenharmony_ci *
60162306a36Sopenharmony_ci * Enable PHY error counters for OFDM and CCK timing errors.
60262306a36Sopenharmony_ci */
60362306a36Sopenharmony_cistatic void
60462306a36Sopenharmony_ciath5k_enable_phy_err_counters(struct ath5k_hw *ah)
60562306a36Sopenharmony_ci{
60662306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_OFDM_TRIG_HIGH,
60762306a36Sopenharmony_ci			   AR5K_PHYERR_CNT1);
60862306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_CCK_TRIG_HIGH,
60962306a36Sopenharmony_ci			   AR5K_PHYERR_CNT2);
61062306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, AR5K_PHY_ERR_FIL_OFDM, AR5K_PHYERR_CNT1_MASK);
61162306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, AR5K_PHY_ERR_FIL_CCK, AR5K_PHYERR_CNT2_MASK);
61262306a36Sopenharmony_ci
61362306a36Sopenharmony_ci	/* not in use */
61462306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, 0, AR5K_OFDM_FIL_CNT);
61562306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, 0, AR5K_CCK_FIL_CNT);
61662306a36Sopenharmony_ci}
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ci/**
61962306a36Sopenharmony_ci * ath5k_disable_phy_err_counters() - Disable PHY error counters
62062306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
62162306a36Sopenharmony_ci *
62262306a36Sopenharmony_ci * Disable PHY error counters for OFDM and CCK timing errors.
62362306a36Sopenharmony_ci */
62462306a36Sopenharmony_cistatic void
62562306a36Sopenharmony_ciath5k_disable_phy_err_counters(struct ath5k_hw *ah)
62662306a36Sopenharmony_ci{
62762306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, 0, AR5K_PHYERR_CNT1);
62862306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, 0, AR5K_PHYERR_CNT2);
62962306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, 0, AR5K_PHYERR_CNT1_MASK);
63062306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, 0, AR5K_PHYERR_CNT2_MASK);
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_ci	/* not in use */
63362306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, 0, AR5K_OFDM_FIL_CNT);
63462306a36Sopenharmony_ci	ath5k_hw_reg_write(ah, 0, AR5K_CCK_FIL_CNT);
63562306a36Sopenharmony_ci}
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci/**
63862306a36Sopenharmony_ci * ath5k_ani_init() - Initialize ANI
63962306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
64062306a36Sopenharmony_ci * @mode: One of enum ath5k_ani_mode
64162306a36Sopenharmony_ci *
64262306a36Sopenharmony_ci * Initialize ANI according to mode.
64362306a36Sopenharmony_ci */
64462306a36Sopenharmony_civoid
64562306a36Sopenharmony_ciath5k_ani_init(struct ath5k_hw *ah, enum ath5k_ani_mode mode)
64662306a36Sopenharmony_ci{
64762306a36Sopenharmony_ci	/* ANI is only possible on 5212 and newer */
64862306a36Sopenharmony_ci	if (ah->ah_version < AR5K_AR5212)
64962306a36Sopenharmony_ci		return;
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_ci	if (mode < ATH5K_ANI_MODE_OFF || mode > ATH5K_ANI_MODE_AUTO) {
65262306a36Sopenharmony_ci		ATH5K_ERR(ah, "ANI mode %d out of range", mode);
65362306a36Sopenharmony_ci		return;
65462306a36Sopenharmony_ci	}
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	/* clear old state information */
65762306a36Sopenharmony_ci	memset(&ah->ani_state, 0, sizeof(ah->ani_state));
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci	/* older hardware has more spur levels than newer */
66062306a36Sopenharmony_ci	if (ah->ah_mac_srev < AR5K_SREV_AR2414)
66162306a36Sopenharmony_ci		ah->ani_state.max_spur_level = 7;
66262306a36Sopenharmony_ci	else
66362306a36Sopenharmony_ci		ah->ani_state.max_spur_level = 2;
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_ci	/* initial values for our ani parameters */
66662306a36Sopenharmony_ci	if (mode == ATH5K_ANI_MODE_OFF) {
66762306a36Sopenharmony_ci		ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "ANI off\n");
66862306a36Sopenharmony_ci	} else if (mode == ATH5K_ANI_MODE_MANUAL_LOW) {
66962306a36Sopenharmony_ci		ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI,
67062306a36Sopenharmony_ci			"ANI manual low -> high sensitivity\n");
67162306a36Sopenharmony_ci		ath5k_ani_set_noise_immunity_level(ah, 0);
67262306a36Sopenharmony_ci		ath5k_ani_set_spur_immunity_level(ah, 0);
67362306a36Sopenharmony_ci		ath5k_ani_set_firstep_level(ah, 0);
67462306a36Sopenharmony_ci		ath5k_ani_set_ofdm_weak_signal_detection(ah, true);
67562306a36Sopenharmony_ci		ath5k_ani_set_cck_weak_signal_detection(ah, true);
67662306a36Sopenharmony_ci	} else if (mode == ATH5K_ANI_MODE_MANUAL_HIGH) {
67762306a36Sopenharmony_ci		ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI,
67862306a36Sopenharmony_ci			"ANI manual high -> low sensitivity\n");
67962306a36Sopenharmony_ci		ath5k_ani_set_noise_immunity_level(ah,
68062306a36Sopenharmony_ci					ATH5K_ANI_MAX_NOISE_IMM_LVL);
68162306a36Sopenharmony_ci		ath5k_ani_set_spur_immunity_level(ah,
68262306a36Sopenharmony_ci					ah->ani_state.max_spur_level);
68362306a36Sopenharmony_ci		ath5k_ani_set_firstep_level(ah, ATH5K_ANI_MAX_FIRSTEP_LVL);
68462306a36Sopenharmony_ci		ath5k_ani_set_ofdm_weak_signal_detection(ah, false);
68562306a36Sopenharmony_ci		ath5k_ani_set_cck_weak_signal_detection(ah, false);
68662306a36Sopenharmony_ci	} else if (mode == ATH5K_ANI_MODE_AUTO) {
68762306a36Sopenharmony_ci		ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "ANI auto\n");
68862306a36Sopenharmony_ci		ath5k_ani_set_noise_immunity_level(ah, 0);
68962306a36Sopenharmony_ci		ath5k_ani_set_spur_immunity_level(ah, 0);
69062306a36Sopenharmony_ci		ath5k_ani_set_firstep_level(ah, 0);
69162306a36Sopenharmony_ci		ath5k_ani_set_ofdm_weak_signal_detection(ah, true);
69262306a36Sopenharmony_ci		ath5k_ani_set_cck_weak_signal_detection(ah, false);
69362306a36Sopenharmony_ci	}
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_ci	/* newer hardware has PHY error counter registers which we can use to
69662306a36Sopenharmony_ci	 * get OFDM and CCK error counts. older hardware has to set rxfilter and
69762306a36Sopenharmony_ci	 * report every single PHY error by calling ath5k_ani_phy_error_report()
69862306a36Sopenharmony_ci	 */
69962306a36Sopenharmony_ci	if (mode == ATH5K_ANI_MODE_AUTO) {
70062306a36Sopenharmony_ci		if (ah->ah_capabilities.cap_has_phyerr_counters)
70162306a36Sopenharmony_ci			ath5k_enable_phy_err_counters(ah);
70262306a36Sopenharmony_ci		else
70362306a36Sopenharmony_ci			ath5k_hw_set_rx_filter(ah, ath5k_hw_get_rx_filter(ah) |
70462306a36Sopenharmony_ci						   AR5K_RX_FILTER_PHYERR);
70562306a36Sopenharmony_ci	} else {
70662306a36Sopenharmony_ci		if (ah->ah_capabilities.cap_has_phyerr_counters)
70762306a36Sopenharmony_ci			ath5k_disable_phy_err_counters(ah);
70862306a36Sopenharmony_ci		else
70962306a36Sopenharmony_ci			ath5k_hw_set_rx_filter(ah, ath5k_hw_get_rx_filter(ah) &
71062306a36Sopenharmony_ci						   ~AR5K_RX_FILTER_PHYERR);
71162306a36Sopenharmony_ci	}
71262306a36Sopenharmony_ci
71362306a36Sopenharmony_ci	ah->ani_state.ani_mode = mode;
71462306a36Sopenharmony_ci}
71562306a36Sopenharmony_ci
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_ci/**************\
71862306a36Sopenharmony_ci* Debug output *
71962306a36Sopenharmony_ci\**************/
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_ci#ifdef CONFIG_ATH5K_DEBUG
72262306a36Sopenharmony_ci
72362306a36Sopenharmony_ci/**
72462306a36Sopenharmony_ci * ath5k_ani_print_counters() - Print ANI counters
72562306a36Sopenharmony_ci * @ah: The &struct ath5k_hw
72662306a36Sopenharmony_ci *
72762306a36Sopenharmony_ci * Used for debugging ANI
72862306a36Sopenharmony_ci */
72962306a36Sopenharmony_civoid
73062306a36Sopenharmony_ciath5k_ani_print_counters(struct ath5k_hw *ah)
73162306a36Sopenharmony_ci{
73262306a36Sopenharmony_ci	/* clears too */
73362306a36Sopenharmony_ci	pr_notice("ACK fail\t%d\n", ath5k_hw_reg_read(ah, AR5K_ACK_FAIL));
73462306a36Sopenharmony_ci	pr_notice("RTS fail\t%d\n", ath5k_hw_reg_read(ah, AR5K_RTS_FAIL));
73562306a36Sopenharmony_ci	pr_notice("RTS success\t%d\n", ath5k_hw_reg_read(ah, AR5K_RTS_OK));
73662306a36Sopenharmony_ci	pr_notice("FCS error\t%d\n", ath5k_hw_reg_read(ah, AR5K_FCS_FAIL));
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_ci	/* no clear */
73962306a36Sopenharmony_ci	pr_notice("tx\t%d\n", ath5k_hw_reg_read(ah, AR5K_PROFCNT_TX));
74062306a36Sopenharmony_ci	pr_notice("rx\t%d\n", ath5k_hw_reg_read(ah, AR5K_PROFCNT_RX));
74162306a36Sopenharmony_ci	pr_notice("busy\t%d\n", ath5k_hw_reg_read(ah, AR5K_PROFCNT_RXCLR));
74262306a36Sopenharmony_ci	pr_notice("cycles\t%d\n", ath5k_hw_reg_read(ah, AR5K_PROFCNT_CYCLE));
74362306a36Sopenharmony_ci
74462306a36Sopenharmony_ci	pr_notice("AR5K_PHYERR_CNT1\t%d\n",
74562306a36Sopenharmony_ci		  ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT1));
74662306a36Sopenharmony_ci	pr_notice("AR5K_PHYERR_CNT2\t%d\n",
74762306a36Sopenharmony_ci		  ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT2));
74862306a36Sopenharmony_ci	pr_notice("AR5K_OFDM_FIL_CNT\t%d\n",
74962306a36Sopenharmony_ci		  ath5k_hw_reg_read(ah, AR5K_OFDM_FIL_CNT));
75062306a36Sopenharmony_ci	pr_notice("AR5K_CCK_FIL_CNT\t%d\n",
75162306a36Sopenharmony_ci		  ath5k_hw_reg_read(ah, AR5K_CCK_FIL_CNT));
75262306a36Sopenharmony_ci}
75362306a36Sopenharmony_ci
75462306a36Sopenharmony_ci#endif
755