Commit | Line | Data |
---|---|---|
f1dc5600 S |
1 | /* |
2 | * Copyright (c) 2008 Atheros Communications Inc. | |
3 | * | |
4 | * Permission to use, copy, modify, and/or distribute this software for any | |
5 | * purpose with or without fee is hereby granted, provided that the above | |
6 | * copyright notice and this permission notice appear in all copies. | |
7 | * | |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
15 | */ | |
16 | ||
394cf0a1 | 17 | #include "ath9k.h" |
f1dc5600 | 18 | |
cbe61d8a | 19 | static int ath9k_hw_get_ani_channel_idx(struct ath_hw *ah, |
f1dc5600 S |
20 | struct ath9k_channel *chan) |
21 | { | |
f1dc5600 S |
22 | int i; |
23 | ||
2660b81a S |
24 | for (i = 0; i < ARRAY_SIZE(ah->ani); i++) { |
25 | if (ah->ani[i].c && | |
26 | ah->ani[i].c->channel == chan->channel) | |
f1dc5600 | 27 | return i; |
2660b81a S |
28 | if (ah->ani[i].c == NULL) { |
29 | ah->ani[i].c = chan; | |
f1dc5600 S |
30 | return i; |
31 | } | |
32 | } | |
33 | ||
34 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, | |
35 | "No more channel states left. Using channel 0\n"); | |
36 | ||
37 | return 0; | |
38 | } | |
39 | ||
cbe61d8a | 40 | static bool ath9k_hw_ani_control(struct ath_hw *ah, |
f1dc5600 S |
41 | enum ath9k_ani_cmd cmd, int param) |
42 | { | |
2660b81a | 43 | struct ar5416AniState *aniState = ah->curani; |
f1dc5600 | 44 | |
2660b81a | 45 | switch (cmd & ah->ani_function) { |
f1dc5600 S |
46 | case ATH9K_ANI_NOISE_IMMUNITY_LEVEL:{ |
47 | u32 level = param; | |
48 | ||
2660b81a | 49 | if (level >= ARRAY_SIZE(ah->totalSizeDesired)) { |
f1dc5600 | 50 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, |
04bd4638 S |
51 | "level out of range (%u > %u)\n", |
52 | level, | |
2660b81a | 53 | (unsigned)ARRAY_SIZE(ah->totalSizeDesired)); |
f1dc5600 S |
54 | return false; |
55 | } | |
56 | ||
57 | REG_RMW_FIELD(ah, AR_PHY_DESIRED_SZ, | |
58 | AR_PHY_DESIRED_SZ_TOT_DES, | |
2660b81a | 59 | ah->totalSizeDesired[level]); |
f1dc5600 S |
60 | REG_RMW_FIELD(ah, AR_PHY_AGC_CTL1, |
61 | AR_PHY_AGC_CTL1_COARSE_LOW, | |
2660b81a | 62 | ah->coarse_low[level]); |
f1dc5600 S |
63 | REG_RMW_FIELD(ah, AR_PHY_AGC_CTL1, |
64 | AR_PHY_AGC_CTL1_COARSE_HIGH, | |
2660b81a | 65 | ah->coarse_high[level]); |
f1dc5600 S |
66 | REG_RMW_FIELD(ah, AR_PHY_FIND_SIG, |
67 | AR_PHY_FIND_SIG_FIRPWR, | |
2660b81a | 68 | ah->firpwr[level]); |
f1dc5600 S |
69 | |
70 | if (level > aniState->noiseImmunityLevel) | |
2660b81a | 71 | ah->stats.ast_ani_niup++; |
f1dc5600 | 72 | else if (level < aniState->noiseImmunityLevel) |
2660b81a | 73 | ah->stats.ast_ani_nidown++; |
f1dc5600 S |
74 | aniState->noiseImmunityLevel = level; |
75 | break; | |
76 | } | |
77 | case ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION:{ | |
78 | const int m1ThreshLow[] = { 127, 50 }; | |
79 | const int m2ThreshLow[] = { 127, 40 }; | |
80 | const int m1Thresh[] = { 127, 0x4d }; | |
81 | const int m2Thresh[] = { 127, 0x40 }; | |
82 | const int m2CountThr[] = { 31, 16 }; | |
83 | const int m2CountThrLow[] = { 63, 48 }; | |
84 | u32 on = param ? 1 : 0; | |
85 | ||
86 | REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW, | |
87 | AR_PHY_SFCORR_LOW_M1_THRESH_LOW, | |
88 | m1ThreshLow[on]); | |
89 | REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW, | |
90 | AR_PHY_SFCORR_LOW_M2_THRESH_LOW, | |
91 | m2ThreshLow[on]); | |
92 | REG_RMW_FIELD(ah, AR_PHY_SFCORR, | |
93 | AR_PHY_SFCORR_M1_THRESH, | |
94 | m1Thresh[on]); | |
95 | REG_RMW_FIELD(ah, AR_PHY_SFCORR, | |
96 | AR_PHY_SFCORR_M2_THRESH, | |
97 | m2Thresh[on]); | |
98 | REG_RMW_FIELD(ah, AR_PHY_SFCORR, | |
99 | AR_PHY_SFCORR_M2COUNT_THR, | |
100 | m2CountThr[on]); | |
101 | REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW, | |
102 | AR_PHY_SFCORR_LOW_M2COUNT_THR_LOW, | |
103 | m2CountThrLow[on]); | |
104 | ||
105 | REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT, | |
106 | AR_PHY_SFCORR_EXT_M1_THRESH_LOW, | |
107 | m1ThreshLow[on]); | |
108 | REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT, | |
109 | AR_PHY_SFCORR_EXT_M2_THRESH_LOW, | |
110 | m2ThreshLow[on]); | |
111 | REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT, | |
112 | AR_PHY_SFCORR_EXT_M1_THRESH, | |
113 | m1Thresh[on]); | |
114 | REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT, | |
115 | AR_PHY_SFCORR_EXT_M2_THRESH, | |
116 | m2Thresh[on]); | |
117 | ||
118 | if (on) | |
119 | REG_SET_BIT(ah, AR_PHY_SFCORR_LOW, | |
120 | AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW); | |
121 | else | |
122 | REG_CLR_BIT(ah, AR_PHY_SFCORR_LOW, | |
123 | AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW); | |
124 | ||
125 | if (!on != aniState->ofdmWeakSigDetectOff) { | |
126 | if (on) | |
2660b81a | 127 | ah->stats.ast_ani_ofdmon++; |
f1dc5600 | 128 | else |
2660b81a | 129 | ah->stats.ast_ani_ofdmoff++; |
f1dc5600 S |
130 | aniState->ofdmWeakSigDetectOff = !on; |
131 | } | |
132 | break; | |
133 | } | |
134 | case ATH9K_ANI_CCK_WEAK_SIGNAL_THR:{ | |
135 | const int weakSigThrCck[] = { 8, 6 }; | |
136 | u32 high = param ? 1 : 0; | |
137 | ||
138 | REG_RMW_FIELD(ah, AR_PHY_CCK_DETECT, | |
139 | AR_PHY_CCK_DETECT_WEAK_SIG_THR_CCK, | |
140 | weakSigThrCck[high]); | |
141 | if (high != aniState->cckWeakSigThreshold) { | |
142 | if (high) | |
2660b81a | 143 | ah->stats.ast_ani_cckhigh++; |
f1dc5600 | 144 | else |
2660b81a | 145 | ah->stats.ast_ani_ccklow++; |
f1dc5600 S |
146 | aniState->cckWeakSigThreshold = high; |
147 | } | |
148 | break; | |
149 | } | |
150 | case ATH9K_ANI_FIRSTEP_LEVEL:{ | |
151 | const int firstep[] = { 0, 4, 8 }; | |
152 | u32 level = param; | |
153 | ||
154 | if (level >= ARRAY_SIZE(firstep)) { | |
155 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, | |
04bd4638 S |
156 | "level out of range (%u > %u)\n", |
157 | level, | |
f1dc5600 S |
158 | (unsigned) ARRAY_SIZE(firstep)); |
159 | return false; | |
160 | } | |
161 | REG_RMW_FIELD(ah, AR_PHY_FIND_SIG, | |
162 | AR_PHY_FIND_SIG_FIRSTEP, | |
163 | firstep[level]); | |
164 | if (level > aniState->firstepLevel) | |
2660b81a | 165 | ah->stats.ast_ani_stepup++; |
f1dc5600 | 166 | else if (level < aniState->firstepLevel) |
2660b81a | 167 | ah->stats.ast_ani_stepdown++; |
f1dc5600 S |
168 | aniState->firstepLevel = level; |
169 | break; | |
170 | } | |
171 | case ATH9K_ANI_SPUR_IMMUNITY_LEVEL:{ | |
172 | const int cycpwrThr1[] = | |
173 | { 2, 4, 6, 8, 10, 12, 14, 16 }; | |
174 | u32 level = param; | |
175 | ||
176 | if (level >= ARRAY_SIZE(cycpwrThr1)) { | |
177 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, | |
04bd4638 S |
178 | "level out of range (%u > %u)\n", |
179 | level, | |
f1dc5600 S |
180 | (unsigned) |
181 | ARRAY_SIZE(cycpwrThr1)); | |
182 | return false; | |
183 | } | |
184 | REG_RMW_FIELD(ah, AR_PHY_TIMING5, | |
185 | AR_PHY_TIMING5_CYCPWR_THR1, | |
186 | cycpwrThr1[level]); | |
187 | if (level > aniState->spurImmunityLevel) | |
2660b81a | 188 | ah->stats.ast_ani_spurup++; |
f1dc5600 | 189 | else if (level < aniState->spurImmunityLevel) |
2660b81a | 190 | ah->stats.ast_ani_spurdown++; |
f1dc5600 S |
191 | aniState->spurImmunityLevel = level; |
192 | break; | |
193 | } | |
194 | case ATH9K_ANI_PRESENT: | |
195 | break; | |
196 | default: | |
197 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, | |
04bd4638 | 198 | "invalid cmd %u\n", cmd); |
f1dc5600 S |
199 | return false; |
200 | } | |
201 | ||
04bd4638 | 202 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, "ANI parameters:\n"); |
f1dc5600 S |
203 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, |
204 | "noiseImmunityLevel=%d, spurImmunityLevel=%d, " | |
205 | "ofdmWeakSigDetectOff=%d\n", | |
206 | aniState->noiseImmunityLevel, aniState->spurImmunityLevel, | |
207 | !aniState->ofdmWeakSigDetectOff); | |
208 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, | |
209 | "cckWeakSigThreshold=%d, " | |
210 | "firstepLevel=%d, listenTime=%d\n", | |
211 | aniState->cckWeakSigThreshold, aniState->firstepLevel, | |
212 | aniState->listenTime); | |
213 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, | |
214 | "cycleCount=%d, ofdmPhyErrCount=%d, cckPhyErrCount=%d\n\n", | |
215 | aniState->cycleCount, aniState->ofdmPhyErrCount, | |
216 | aniState->cckPhyErrCount); | |
217 | ||
218 | return true; | |
219 | } | |
220 | ||
cbe61d8a | 221 | static void ath9k_hw_update_mibstats(struct ath_hw *ah, |
f1dc5600 S |
222 | struct ath9k_mib_stats *stats) |
223 | { | |
224 | stats->ackrcv_bad += REG_READ(ah, AR_ACK_FAIL); | |
225 | stats->rts_bad += REG_READ(ah, AR_RTS_FAIL); | |
226 | stats->fcs_bad += REG_READ(ah, AR_FCS_FAIL); | |
227 | stats->rts_good += REG_READ(ah, AR_RTS_OK); | |
228 | stats->beacons += REG_READ(ah, AR_BEACON_CNT); | |
229 | } | |
230 | ||
cbe61d8a | 231 | static void ath9k_ani_restart(struct ath_hw *ah) |
f1dc5600 | 232 | { |
f1dc5600 S |
233 | struct ar5416AniState *aniState; |
234 | ||
235 | if (!DO_ANI(ah)) | |
236 | return; | |
237 | ||
2660b81a | 238 | aniState = ah->curani; |
f1dc5600 S |
239 | |
240 | aniState->listenTime = 0; | |
2660b81a | 241 | if (ah->has_hw_phycounters) { |
f1dc5600 S |
242 | if (aniState->ofdmTrigHigh > AR_PHY_COUNTMAX) { |
243 | aniState->ofdmPhyErrBase = 0; | |
244 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, | |
245 | "OFDM Trigger is too high for hw counters\n"); | |
246 | } else { | |
247 | aniState->ofdmPhyErrBase = | |
248 | AR_PHY_COUNTMAX - aniState->ofdmTrigHigh; | |
249 | } | |
250 | if (aniState->cckTrigHigh > AR_PHY_COUNTMAX) { | |
251 | aniState->cckPhyErrBase = 0; | |
252 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, | |
253 | "CCK Trigger is too high for hw counters\n"); | |
254 | } else { | |
255 | aniState->cckPhyErrBase = | |
256 | AR_PHY_COUNTMAX - aniState->cckTrigHigh; | |
257 | } | |
258 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, | |
04bd4638 S |
259 | "Writing ofdmbase=%u cckbase=%u\n", |
260 | aniState->ofdmPhyErrBase, | |
f1dc5600 S |
261 | aniState->cckPhyErrBase); |
262 | REG_WRITE(ah, AR_PHY_ERR_1, aniState->ofdmPhyErrBase); | |
263 | REG_WRITE(ah, AR_PHY_ERR_2, aniState->cckPhyErrBase); | |
264 | REG_WRITE(ah, AR_PHY_ERR_MASK_1, AR_PHY_ERR_OFDM_TIMING); | |
265 | REG_WRITE(ah, AR_PHY_ERR_MASK_2, AR_PHY_ERR_CCK_TIMING); | |
266 | ||
cbe61d8a | 267 | ath9k_hw_update_mibstats(ah, &ah->ah_mibStats); |
f1dc5600 S |
268 | } |
269 | aniState->ofdmPhyErrCount = 0; | |
270 | aniState->cckPhyErrCount = 0; | |
271 | } | |
272 | ||
cbe61d8a | 273 | static void ath9k_hw_ani_ofdm_err_trigger(struct ath_hw *ah) |
f1dc5600 | 274 | { |
38b33707 | 275 | struct ieee80211_conf *conf = &ah->ah_sc->hw->conf; |
f1dc5600 | 276 | struct ar5416AniState *aniState; |
f1dc5600 S |
277 | int32_t rssi; |
278 | ||
279 | if (!DO_ANI(ah)) | |
280 | return; | |
281 | ||
2660b81a | 282 | aniState = ah->curani; |
f1dc5600 S |
283 | |
284 | if (aniState->noiseImmunityLevel < HAL_NOISE_IMMUNE_MAX) { | |
285 | if (ath9k_hw_ani_control(ah, ATH9K_ANI_NOISE_IMMUNITY_LEVEL, | |
286 | aniState->noiseImmunityLevel + 1)) { | |
287 | return; | |
288 | } | |
289 | } | |
290 | ||
291 | if (aniState->spurImmunityLevel < HAL_SPUR_IMMUNE_MAX) { | |
292 | if (ath9k_hw_ani_control(ah, ATH9K_ANI_SPUR_IMMUNITY_LEVEL, | |
293 | aniState->spurImmunityLevel + 1)) { | |
294 | return; | |
295 | } | |
296 | } | |
297 | ||
2660b81a | 298 | if (ah->opmode == NL80211_IFTYPE_AP) { |
f1dc5600 S |
299 | if (aniState->firstepLevel < HAL_FIRST_STEP_MAX) { |
300 | ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL, | |
301 | aniState->firstepLevel + 1); | |
302 | } | |
303 | return; | |
304 | } | |
cbe61d8a | 305 | rssi = BEACON_RSSI(ah); |
f1dc5600 S |
306 | if (rssi > aniState->rssiThrHigh) { |
307 | if (!aniState->ofdmWeakSigDetectOff) { | |
308 | if (ath9k_hw_ani_control(ah, | |
309 | ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION, | |
310 | false)) { | |
311 | ath9k_hw_ani_control(ah, | |
312 | ATH9K_ANI_SPUR_IMMUNITY_LEVEL, 0); | |
313 | return; | |
314 | } | |
315 | } | |
316 | if (aniState->firstepLevel < HAL_FIRST_STEP_MAX) { | |
317 | ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL, | |
318 | aniState->firstepLevel + 1); | |
319 | return; | |
320 | } | |
321 | } else if (rssi > aniState->rssiThrLow) { | |
322 | if (aniState->ofdmWeakSigDetectOff) | |
323 | ath9k_hw_ani_control(ah, | |
324 | ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION, | |
325 | true); | |
326 | if (aniState->firstepLevel < HAL_FIRST_STEP_MAX) | |
327 | ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL, | |
328 | aniState->firstepLevel + 1); | |
329 | return; | |
330 | } else { | |
38b33707 | 331 | if (conf->channel->band == IEEE80211_BAND_2GHZ) { |
f1dc5600 S |
332 | if (!aniState->ofdmWeakSigDetectOff) |
333 | ath9k_hw_ani_control(ah, | |
334 | ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION, | |
335 | false); | |
336 | if (aniState->firstepLevel > 0) | |
337 | ath9k_hw_ani_control(ah, | |
338 | ATH9K_ANI_FIRSTEP_LEVEL, 0); | |
339 | return; | |
340 | } | |
341 | } | |
342 | } | |
343 | ||
cbe61d8a | 344 | static void ath9k_hw_ani_cck_err_trigger(struct ath_hw *ah) |
f1dc5600 | 345 | { |
38b33707 | 346 | struct ieee80211_conf *conf = &ah->ah_sc->hw->conf; |
f1dc5600 | 347 | struct ar5416AniState *aniState; |
f1dc5600 S |
348 | int32_t rssi; |
349 | ||
350 | if (!DO_ANI(ah)) | |
351 | return; | |
352 | ||
2660b81a | 353 | aniState = ah->curani; |
f1dc5600 S |
354 | if (aniState->noiseImmunityLevel < HAL_NOISE_IMMUNE_MAX) { |
355 | if (ath9k_hw_ani_control(ah, ATH9K_ANI_NOISE_IMMUNITY_LEVEL, | |
356 | aniState->noiseImmunityLevel + 1)) { | |
357 | return; | |
358 | } | |
359 | } | |
2660b81a | 360 | if (ah->opmode == NL80211_IFTYPE_AP) { |
f1dc5600 S |
361 | if (aniState->firstepLevel < HAL_FIRST_STEP_MAX) { |
362 | ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL, | |
363 | aniState->firstepLevel + 1); | |
364 | } | |
365 | return; | |
366 | } | |
cbe61d8a | 367 | rssi = BEACON_RSSI(ah); |
f1dc5600 S |
368 | if (rssi > aniState->rssiThrLow) { |
369 | if (aniState->firstepLevel < HAL_FIRST_STEP_MAX) | |
370 | ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL, | |
371 | aniState->firstepLevel + 1); | |
372 | } else { | |
38b33707 | 373 | if (conf->channel->band == IEEE80211_BAND_2GHZ) { |
f1dc5600 S |
374 | if (aniState->firstepLevel > 0) |
375 | ath9k_hw_ani_control(ah, | |
376 | ATH9K_ANI_FIRSTEP_LEVEL, 0); | |
377 | } | |
378 | } | |
379 | } | |
380 | ||
cbe61d8a | 381 | static void ath9k_hw_ani_lower_immunity(struct ath_hw *ah) |
f1dc5600 | 382 | { |
f1dc5600 S |
383 | struct ar5416AniState *aniState; |
384 | int32_t rssi; | |
385 | ||
2660b81a | 386 | aniState = ah->curani; |
f1dc5600 | 387 | |
2660b81a | 388 | if (ah->opmode == NL80211_IFTYPE_AP) { |
f1dc5600 S |
389 | if (aniState->firstepLevel > 0) { |
390 | if (ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL, | |
391 | aniState->firstepLevel - 1)) | |
392 | return; | |
393 | } | |
394 | } else { | |
cbe61d8a | 395 | rssi = BEACON_RSSI(ah); |
f1dc5600 S |
396 | if (rssi > aniState->rssiThrHigh) { |
397 | /* XXX: Handle me */ | |
398 | } else if (rssi > aniState->rssiThrLow) { | |
399 | if (aniState->ofdmWeakSigDetectOff) { | |
400 | if (ath9k_hw_ani_control(ah, | |
401 | ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION, | |
402 | true) == true) | |
403 | return; | |
404 | } | |
405 | if (aniState->firstepLevel > 0) { | |
406 | if (ath9k_hw_ani_control(ah, | |
407 | ATH9K_ANI_FIRSTEP_LEVEL, | |
408 | aniState->firstepLevel - 1) == true) | |
409 | return; | |
410 | } | |
411 | } else { | |
412 | if (aniState->firstepLevel > 0) { | |
413 | if (ath9k_hw_ani_control(ah, | |
414 | ATH9K_ANI_FIRSTEP_LEVEL, | |
415 | aniState->firstepLevel - 1) == true) | |
416 | return; | |
417 | } | |
418 | } | |
419 | } | |
420 | ||
421 | if (aniState->spurImmunityLevel > 0) { | |
422 | if (ath9k_hw_ani_control(ah, ATH9K_ANI_SPUR_IMMUNITY_LEVEL, | |
423 | aniState->spurImmunityLevel - 1)) | |
424 | return; | |
425 | } | |
426 | ||
427 | if (aniState->noiseImmunityLevel > 0) { | |
428 | ath9k_hw_ani_control(ah, ATH9K_ANI_NOISE_IMMUNITY_LEVEL, | |
429 | aniState->noiseImmunityLevel - 1); | |
430 | return; | |
431 | } | |
432 | } | |
433 | ||
cbe61d8a | 434 | static int32_t ath9k_hw_ani_get_listen_time(struct ath_hw *ah) |
f1dc5600 | 435 | { |
f1dc5600 S |
436 | struct ar5416AniState *aniState; |
437 | u32 txFrameCount, rxFrameCount, cycleCount; | |
438 | int32_t listenTime; | |
439 | ||
440 | txFrameCount = REG_READ(ah, AR_TFCNT); | |
441 | rxFrameCount = REG_READ(ah, AR_RFCNT); | |
442 | cycleCount = REG_READ(ah, AR_CCCNT); | |
443 | ||
2660b81a | 444 | aniState = ah->curani; |
f1dc5600 S |
445 | if (aniState->cycleCount == 0 || aniState->cycleCount > cycleCount) { |
446 | ||
447 | listenTime = 0; | |
2660b81a | 448 | ah->stats.ast_ani_lzero++; |
f1dc5600 S |
449 | } else { |
450 | int32_t ccdelta = cycleCount - aniState->cycleCount; | |
451 | int32_t rfdelta = rxFrameCount - aniState->rxFrameCount; | |
452 | int32_t tfdelta = txFrameCount - aniState->txFrameCount; | |
453 | listenTime = (ccdelta - rfdelta - tfdelta) / 44000; | |
454 | } | |
455 | aniState->cycleCount = cycleCount; | |
456 | aniState->txFrameCount = txFrameCount; | |
457 | aniState->rxFrameCount = rxFrameCount; | |
458 | ||
459 | return listenTime; | |
460 | } | |
461 | ||
cbe61d8a | 462 | void ath9k_ani_reset(struct ath_hw *ah) |
f1dc5600 | 463 | { |
f1dc5600 | 464 | struct ar5416AniState *aniState; |
2660b81a | 465 | struct ath9k_channel *chan = ah->curchan; |
f1dc5600 S |
466 | int index; |
467 | ||
468 | if (!DO_ANI(ah)) | |
469 | return; | |
470 | ||
471 | index = ath9k_hw_get_ani_channel_idx(ah, chan); | |
2660b81a S |
472 | aniState = &ah->ani[index]; |
473 | ah->curani = aniState; | |
f1dc5600 | 474 | |
2660b81a S |
475 | if (DO_ANI(ah) && ah->opmode != NL80211_IFTYPE_STATION |
476 | && ah->opmode != NL80211_IFTYPE_ADHOC) { | |
f1dc5600 | 477 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, |
2660b81a S |
478 | "Reset ANI state opmode %u\n", ah->opmode); |
479 | ah->stats.ast_ani_reset++; | |
f1dc5600 S |
480 | |
481 | ath9k_hw_ani_control(ah, ATH9K_ANI_NOISE_IMMUNITY_LEVEL, 0); | |
482 | ath9k_hw_ani_control(ah, ATH9K_ANI_SPUR_IMMUNITY_LEVEL, 0); | |
483 | ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL, 0); | |
484 | ath9k_hw_ani_control(ah, ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION, | |
485 | !ATH9K_ANI_USE_OFDM_WEAK_SIG); | |
486 | ath9k_hw_ani_control(ah, ATH9K_ANI_CCK_WEAK_SIGNAL_THR, | |
487 | ATH9K_ANI_CCK_WEAK_SIG_THR); | |
488 | ||
489 | ath9k_hw_setrxfilter(ah, ath9k_hw_getrxfilter(ah) | | |
490 | ATH9K_RX_FILTER_PHYERR); | |
491 | ||
2660b81a S |
492 | if (ah->opmode == NL80211_IFTYPE_AP) { |
493 | ah->curani->ofdmTrigHigh = | |
494 | ah->config.ofdm_trig_high; | |
495 | ah->curani->ofdmTrigLow = | |
496 | ah->config.ofdm_trig_low; | |
497 | ah->curani->cckTrigHigh = | |
498 | ah->config.cck_trig_high; | |
499 | ah->curani->cckTrigLow = | |
500 | ah->config.cck_trig_low; | |
f1dc5600 S |
501 | } |
502 | ath9k_ani_restart(ah); | |
503 | return; | |
504 | } | |
505 | ||
506 | if (aniState->noiseImmunityLevel != 0) | |
507 | ath9k_hw_ani_control(ah, ATH9K_ANI_NOISE_IMMUNITY_LEVEL, | |
508 | aniState->noiseImmunityLevel); | |
509 | if (aniState->spurImmunityLevel != 0) | |
510 | ath9k_hw_ani_control(ah, ATH9K_ANI_SPUR_IMMUNITY_LEVEL, | |
511 | aniState->spurImmunityLevel); | |
512 | if (aniState->ofdmWeakSigDetectOff) | |
513 | ath9k_hw_ani_control(ah, ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION, | |
514 | !aniState->ofdmWeakSigDetectOff); | |
515 | if (aniState->cckWeakSigThreshold) | |
516 | ath9k_hw_ani_control(ah, ATH9K_ANI_CCK_WEAK_SIGNAL_THR, | |
517 | aniState->cckWeakSigThreshold); | |
518 | if (aniState->firstepLevel != 0) | |
519 | ath9k_hw_ani_control(ah, ATH9K_ANI_FIRSTEP_LEVEL, | |
520 | aniState->firstepLevel); | |
2660b81a | 521 | if (ah->has_hw_phycounters) { |
f1dc5600 S |
522 | ath9k_hw_setrxfilter(ah, ath9k_hw_getrxfilter(ah) & |
523 | ~ATH9K_RX_FILTER_PHYERR); | |
524 | ath9k_ani_restart(ah); | |
525 | REG_WRITE(ah, AR_PHY_ERR_MASK_1, AR_PHY_ERR_OFDM_TIMING); | |
526 | REG_WRITE(ah, AR_PHY_ERR_MASK_2, AR_PHY_ERR_CCK_TIMING); | |
527 | ||
528 | } else { | |
529 | ath9k_ani_restart(ah); | |
530 | ath9k_hw_setrxfilter(ah, ath9k_hw_getrxfilter(ah) | | |
531 | ATH9K_RX_FILTER_PHYERR); | |
532 | } | |
533 | } | |
534 | ||
cbe61d8a | 535 | void ath9k_hw_ani_monitor(struct ath_hw *ah, |
f1dc5600 S |
536 | const struct ath9k_node_stats *stats, |
537 | struct ath9k_channel *chan) | |
538 | { | |
f1dc5600 S |
539 | struct ar5416AniState *aniState; |
540 | int32_t listenTime; | |
541 | ||
99506882 GJ |
542 | if (!DO_ANI(ah)) |
543 | return; | |
544 | ||
2660b81a S |
545 | aniState = ah->curani; |
546 | ah->stats.ast_nodestats = *stats; | |
f1dc5600 S |
547 | |
548 | listenTime = ath9k_hw_ani_get_listen_time(ah); | |
549 | if (listenTime < 0) { | |
2660b81a | 550 | ah->stats.ast_ani_lneg++; |
f1dc5600 S |
551 | ath9k_ani_restart(ah); |
552 | return; | |
553 | } | |
554 | ||
555 | aniState->listenTime += listenTime; | |
556 | ||
2660b81a | 557 | if (ah->has_hw_phycounters) { |
f1dc5600 S |
558 | u32 phyCnt1, phyCnt2; |
559 | u32 ofdmPhyErrCnt, cckPhyErrCnt; | |
560 | ||
cbe61d8a | 561 | ath9k_hw_update_mibstats(ah, &ah->ah_mibStats); |
f1dc5600 S |
562 | |
563 | phyCnt1 = REG_READ(ah, AR_PHY_ERR_1); | |
564 | phyCnt2 = REG_READ(ah, AR_PHY_ERR_2); | |
565 | ||
566 | if (phyCnt1 < aniState->ofdmPhyErrBase || | |
567 | phyCnt2 < aniState->cckPhyErrBase) { | |
568 | if (phyCnt1 < aniState->ofdmPhyErrBase) { | |
569 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, | |
04bd4638 | 570 | "phyCnt1 0x%x, resetting " |
f1dc5600 | 571 | "counter value to 0x%x\n", |
04bd4638 | 572 | phyCnt1, |
f1dc5600 S |
573 | aniState->ofdmPhyErrBase); |
574 | REG_WRITE(ah, AR_PHY_ERR_1, | |
575 | aniState->ofdmPhyErrBase); | |
576 | REG_WRITE(ah, AR_PHY_ERR_MASK_1, | |
577 | AR_PHY_ERR_OFDM_TIMING); | |
578 | } | |
579 | if (phyCnt2 < aniState->cckPhyErrBase) { | |
580 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, | |
04bd4638 | 581 | "phyCnt2 0x%x, resetting " |
f1dc5600 | 582 | "counter value to 0x%x\n", |
04bd4638 | 583 | phyCnt2, |
f1dc5600 S |
584 | aniState->cckPhyErrBase); |
585 | REG_WRITE(ah, AR_PHY_ERR_2, | |
586 | aniState->cckPhyErrBase); | |
587 | REG_WRITE(ah, AR_PHY_ERR_MASK_2, | |
588 | AR_PHY_ERR_CCK_TIMING); | |
589 | } | |
590 | return; | |
591 | } | |
592 | ||
593 | ofdmPhyErrCnt = phyCnt1 - aniState->ofdmPhyErrBase; | |
2660b81a | 594 | ah->stats.ast_ani_ofdmerrs += |
f1dc5600 S |
595 | ofdmPhyErrCnt - aniState->ofdmPhyErrCount; |
596 | aniState->ofdmPhyErrCount = ofdmPhyErrCnt; | |
597 | ||
598 | cckPhyErrCnt = phyCnt2 - aniState->cckPhyErrBase; | |
2660b81a | 599 | ah->stats.ast_ani_cckerrs += |
f1dc5600 S |
600 | cckPhyErrCnt - aniState->cckPhyErrCount; |
601 | aniState->cckPhyErrCount = cckPhyErrCnt; | |
602 | } | |
603 | ||
2660b81a | 604 | if (aniState->listenTime > 5 * ah->aniperiod) { |
f1dc5600 S |
605 | if (aniState->ofdmPhyErrCount <= aniState->listenTime * |
606 | aniState->ofdmTrigLow / 1000 && | |
607 | aniState->cckPhyErrCount <= aniState->listenTime * | |
608 | aniState->cckTrigLow / 1000) | |
609 | ath9k_hw_ani_lower_immunity(ah); | |
610 | ath9k_ani_restart(ah); | |
2660b81a | 611 | } else if (aniState->listenTime > ah->aniperiod) { |
f1dc5600 S |
612 | if (aniState->ofdmPhyErrCount > aniState->listenTime * |
613 | aniState->ofdmTrigHigh / 1000) { | |
614 | ath9k_hw_ani_ofdm_err_trigger(ah); | |
615 | ath9k_ani_restart(ah); | |
616 | } else if (aniState->cckPhyErrCount > | |
617 | aniState->listenTime * aniState->cckTrigHigh / | |
618 | 1000) { | |
619 | ath9k_hw_ani_cck_err_trigger(ah); | |
620 | ath9k_ani_restart(ah); | |
621 | } | |
622 | } | |
623 | } | |
624 | ||
cbe61d8a | 625 | bool ath9k_hw_phycounters(struct ath_hw *ah) |
f1dc5600 | 626 | { |
2660b81a | 627 | return ah->has_hw_phycounters ? true : false; |
f1dc5600 S |
628 | } |
629 | ||
cbe61d8a | 630 | void ath9k_enable_mib_counters(struct ath_hw *ah) |
f1dc5600 | 631 | { |
f1dc5600 S |
632 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, "Enable MIB counters\n"); |
633 | ||
cbe61d8a | 634 | ath9k_hw_update_mibstats(ah, &ah->ah_mibStats); |
f1dc5600 S |
635 | |
636 | REG_WRITE(ah, AR_FILT_OFDM, 0); | |
637 | REG_WRITE(ah, AR_FILT_CCK, 0); | |
638 | REG_WRITE(ah, AR_MIBC, | |
639 | ~(AR_MIBC_COW | AR_MIBC_FMC | AR_MIBC_CMC | AR_MIBC_MCS) | |
640 | & 0x0f); | |
641 | REG_WRITE(ah, AR_PHY_ERR_MASK_1, AR_PHY_ERR_OFDM_TIMING); | |
642 | REG_WRITE(ah, AR_PHY_ERR_MASK_2, AR_PHY_ERR_CCK_TIMING); | |
643 | } | |
644 | ||
0fd06c90 | 645 | /* Freeze the MIB counters, get the stats and then clear them */ |
cbe61d8a | 646 | void ath9k_hw_disable_mib_counters(struct ath_hw *ah) |
f1dc5600 | 647 | { |
f1dc5600 | 648 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, "Disable MIB counters\n"); |
0fd06c90 | 649 | REG_WRITE(ah, AR_MIBC, AR_MIBC_FMC); |
cbe61d8a | 650 | ath9k_hw_update_mibstats(ah, &ah->ah_mibStats); |
0fd06c90 | 651 | REG_WRITE(ah, AR_MIBC, AR_MIBC_CMC); |
f1dc5600 S |
652 | REG_WRITE(ah, AR_FILT_OFDM, 0); |
653 | REG_WRITE(ah, AR_FILT_CCK, 0); | |
654 | } | |
655 | ||
cbe61d8a | 656 | u32 ath9k_hw_GetMibCycleCountsPct(struct ath_hw *ah, |
f1dc5600 S |
657 | u32 *rxc_pcnt, |
658 | u32 *rxf_pcnt, | |
659 | u32 *txf_pcnt) | |
660 | { | |
661 | static u32 cycles, rx_clear, rx_frame, tx_frame; | |
662 | u32 good = 1; | |
663 | ||
664 | u32 rc = REG_READ(ah, AR_RCCNT); | |
665 | u32 rf = REG_READ(ah, AR_RFCNT); | |
666 | u32 tf = REG_READ(ah, AR_TFCNT); | |
667 | u32 cc = REG_READ(ah, AR_CCCNT); | |
668 | ||
669 | if (cycles == 0 || cycles > cc) { | |
670 | DPRINTF(ah->ah_sc, ATH_DBG_CHANNEL, | |
04bd4638 | 671 | "cycle counter wrap. ExtBusy = 0\n"); |
f1dc5600 S |
672 | good = 0; |
673 | } else { | |
674 | u32 cc_d = cc - cycles; | |
675 | u32 rc_d = rc - rx_clear; | |
676 | u32 rf_d = rf - rx_frame; | |
677 | u32 tf_d = tf - tx_frame; | |
678 | ||
679 | if (cc_d != 0) { | |
680 | *rxc_pcnt = rc_d * 100 / cc_d; | |
681 | *rxf_pcnt = rf_d * 100 / cc_d; | |
682 | *txf_pcnt = tf_d * 100 / cc_d; | |
683 | } else { | |
684 | good = 0; | |
685 | } | |
686 | } | |
687 | ||
688 | cycles = cc; | |
689 | rx_frame = rf; | |
690 | rx_clear = rc; | |
691 | tx_frame = tf; | |
692 | ||
693 | return good; | |
694 | } | |
695 | ||
696 | /* | |
697 | * Process a MIB interrupt. We may potentially be invoked because | |
698 | * any of the MIB counters overflow/trigger so don't assume we're | |
699 | * here because a PHY error counter triggered. | |
700 | */ | |
cbe61d8a | 701 | void ath9k_hw_procmibevent(struct ath_hw *ah, |
f1dc5600 S |
702 | const struct ath9k_node_stats *stats) |
703 | { | |
f1dc5600 S |
704 | u32 phyCnt1, phyCnt2; |
705 | ||
706 | /* Reset these counters regardless */ | |
707 | REG_WRITE(ah, AR_FILT_OFDM, 0); | |
708 | REG_WRITE(ah, AR_FILT_CCK, 0); | |
709 | if (!(REG_READ(ah, AR_SLP_MIB_CTRL) & AR_SLP_MIB_PENDING)) | |
710 | REG_WRITE(ah, AR_SLP_MIB_CTRL, AR_SLP_MIB_CLEAR); | |
711 | ||
712 | /* Clear the mib counters and save them in the stats */ | |
cbe61d8a | 713 | ath9k_hw_update_mibstats(ah, &ah->ah_mibStats); |
2660b81a | 714 | ah->stats.ast_nodestats = *stats; |
f1dc5600 S |
715 | |
716 | if (!DO_ANI(ah)) | |
717 | return; | |
718 | ||
719 | /* NB: these are not reset-on-read */ | |
720 | phyCnt1 = REG_READ(ah, AR_PHY_ERR_1); | |
721 | phyCnt2 = REG_READ(ah, AR_PHY_ERR_2); | |
722 | if (((phyCnt1 & AR_MIBCNT_INTRMASK) == AR_MIBCNT_INTRMASK) || | |
723 | ((phyCnt2 & AR_MIBCNT_INTRMASK) == AR_MIBCNT_INTRMASK)) { | |
2660b81a | 724 | struct ar5416AniState *aniState = ah->curani; |
f1dc5600 S |
725 | u32 ofdmPhyErrCnt, cckPhyErrCnt; |
726 | ||
727 | /* NB: only use ast_ani_*errs with AH_PRIVATE_DIAG */ | |
728 | ofdmPhyErrCnt = phyCnt1 - aniState->ofdmPhyErrBase; | |
2660b81a | 729 | ah->stats.ast_ani_ofdmerrs += |
f1dc5600 S |
730 | ofdmPhyErrCnt - aniState->ofdmPhyErrCount; |
731 | aniState->ofdmPhyErrCount = ofdmPhyErrCnt; | |
732 | ||
733 | cckPhyErrCnt = phyCnt2 - aniState->cckPhyErrBase; | |
2660b81a | 734 | ah->stats.ast_ani_cckerrs += |
f1dc5600 S |
735 | cckPhyErrCnt - aniState->cckPhyErrCount; |
736 | aniState->cckPhyErrCount = cckPhyErrCnt; | |
737 | ||
738 | /* | |
739 | * NB: figure out which counter triggered. If both | |
740 | * trigger we'll only deal with one as the processing | |
741 | * clobbers the error counter so the trigger threshold | |
742 | * check will never be true. | |
743 | */ | |
744 | if (aniState->ofdmPhyErrCount > aniState->ofdmTrigHigh) | |
745 | ath9k_hw_ani_ofdm_err_trigger(ah); | |
746 | if (aniState->cckPhyErrCount > aniState->cckTrigHigh) | |
747 | ath9k_hw_ani_cck_err_trigger(ah); | |
748 | /* NB: always restart to insure the h/w counters are reset */ | |
749 | ath9k_ani_restart(ah); | |
750 | } | |
751 | } | |
752 | ||
cbe61d8a | 753 | void ath9k_hw_ani_setup(struct ath_hw *ah) |
f1dc5600 | 754 | { |
f1dc5600 S |
755 | int i; |
756 | ||
757 | const int totalSizeDesired[] = { -55, -55, -55, -55, -62 }; | |
758 | const int coarseHigh[] = { -14, -14, -14, -14, -12 }; | |
759 | const int coarseLow[] = { -64, -64, -64, -64, -70 }; | |
760 | const int firpwr[] = { -78, -78, -78, -78, -80 }; | |
761 | ||
762 | for (i = 0; i < 5; i++) { | |
2660b81a S |
763 | ah->totalSizeDesired[i] = totalSizeDesired[i]; |
764 | ah->coarse_high[i] = coarseHigh[i]; | |
765 | ah->coarse_low[i] = coarseLow[i]; | |
766 | ah->firpwr[i] = firpwr[i]; | |
f1dc5600 S |
767 | } |
768 | } | |
769 | ||
cbe61d8a | 770 | void ath9k_hw_ani_attach(struct ath_hw *ah) |
f1dc5600 | 771 | { |
f1dc5600 S |
772 | int i; |
773 | ||
774 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, "Attach ANI\n"); | |
775 | ||
2660b81a S |
776 | ah->has_hw_phycounters = 1; |
777 | ||
778 | memset(ah->ani, 0, sizeof(ah->ani)); | |
779 | for (i = 0; i < ARRAY_SIZE(ah->ani); i++) { | |
780 | ah->ani[i].ofdmTrigHigh = ATH9K_ANI_OFDM_TRIG_HIGH; | |
781 | ah->ani[i].ofdmTrigLow = ATH9K_ANI_OFDM_TRIG_LOW; | |
782 | ah->ani[i].cckTrigHigh = ATH9K_ANI_CCK_TRIG_HIGH; | |
783 | ah->ani[i].cckTrigLow = ATH9K_ANI_CCK_TRIG_LOW; | |
784 | ah->ani[i].rssiThrHigh = ATH9K_ANI_RSSI_THR_HIGH; | |
785 | ah->ani[i].rssiThrLow = ATH9K_ANI_RSSI_THR_LOW; | |
786 | ah->ani[i].ofdmWeakSigDetectOff = | |
f1dc5600 | 787 | !ATH9K_ANI_USE_OFDM_WEAK_SIG; |
2660b81a | 788 | ah->ani[i].cckWeakSigThreshold = |
f1dc5600 | 789 | ATH9K_ANI_CCK_WEAK_SIG_THR; |
2660b81a S |
790 | ah->ani[i].spurImmunityLevel = ATH9K_ANI_SPUR_IMMUNE_LVL; |
791 | ah->ani[i].firstepLevel = ATH9K_ANI_FIRSTEP_LVL; | |
792 | if (ah->has_hw_phycounters) { | |
793 | ah->ani[i].ofdmPhyErrBase = | |
f1dc5600 | 794 | AR_PHY_COUNTMAX - ATH9K_ANI_OFDM_TRIG_HIGH; |
2660b81a | 795 | ah->ani[i].cckPhyErrBase = |
f1dc5600 S |
796 | AR_PHY_COUNTMAX - ATH9K_ANI_CCK_TRIG_HIGH; |
797 | } | |
798 | } | |
2660b81a | 799 | if (ah->has_hw_phycounters) { |
f1dc5600 S |
800 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, |
801 | "Setting OfdmErrBase = 0x%08x\n", | |
2660b81a | 802 | ah->ani[0].ofdmPhyErrBase); |
f1dc5600 | 803 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, "Setting cckErrBase = 0x%08x\n", |
2660b81a | 804 | ah->ani[0].cckPhyErrBase); |
f1dc5600 | 805 | |
2660b81a S |
806 | REG_WRITE(ah, AR_PHY_ERR_1, ah->ani[0].ofdmPhyErrBase); |
807 | REG_WRITE(ah, AR_PHY_ERR_2, ah->ani[0].cckPhyErrBase); | |
f1dc5600 S |
808 | ath9k_enable_mib_counters(ah); |
809 | } | |
2660b81a S |
810 | ah->aniperiod = ATH9K_ANI_PERIOD; |
811 | if (ah->config.enable_ani) | |
812 | ah->proc_phyerr |= HAL_PROCESS_ANI; | |
f1dc5600 S |
813 | } |
814 | ||
cbe61d8a | 815 | void ath9k_hw_ani_detach(struct ath_hw *ah) |
f1dc5600 | 816 | { |
f1dc5600 S |
817 | DPRINTF(ah->ah_sc, ATH_DBG_ANI, "Detach ANI\n"); |
818 | ||
2660b81a | 819 | if (ah->has_hw_phycounters) { |
f1dc5600 S |
820 | ath9k_hw_disable_mib_counters(ah); |
821 | REG_WRITE(ah, AR_PHY_ERR_1, 0); | |
822 | REG_WRITE(ah, AR_PHY_ERR_2, 0); | |
823 | } | |
824 | } |