1 | /* |
2 | * Copyright (C) 2010 Bruno Randolf <br1@einfach.org> |
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 | |
17 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
18 | |
19 | #include "ath5k.h" |
20 | #include "reg.h" |
21 | #include "debug.h" |
22 | #include "ani.h" |
23 | |
24 | /** |
25 | * DOC: Basic ANI Operation |
26 | * |
27 | * Adaptive Noise Immunity (ANI) controls five noise immunity parameters |
28 | * depending on the amount of interference in the environment, increasing |
29 | * or reducing sensitivity as necessary. |
30 | * |
31 | * The parameters are: |
32 | * |
33 | * - "noise immunity" |
34 | * |
35 | * - "spur immunity" |
36 | * |
37 | * - "firstep level" |
38 | * |
39 | * - "OFDM weak signal detection" |
40 | * |
41 | * - "CCK weak signal detection" |
42 | * |
43 | * Basically we look at the amount of ODFM and CCK timing errors we get and then |
44 | * raise or lower immunity accordingly by setting one or more of these |
45 | * parameters. |
46 | * |
47 | * Newer chipsets have PHY error counters in hardware which will generate a MIB |
48 | * interrupt when they overflow. Older hardware has too enable PHY error frames |
49 | * by setting a RX flag and then count every single PHY error. When a specified |
50 | * threshold of errors has been reached we will raise immunity. |
51 | * Also we regularly check the amount of errors and lower or raise immunity as |
52 | * necessary. |
53 | */ |
54 | |
55 | |
56 | /***********************\ |
57 | * ANI parameter control * |
58 | \***********************/ |
59 | |
60 | /** |
61 | * ath5k_ani_set_noise_immunity_level() - Set noise immunity level |
62 | * @ah: The &struct ath5k_hw |
63 | * @level: level between 0 and @ATH5K_ANI_MAX_NOISE_IMM_LVL |
64 | */ |
65 | void |
66 | ath5k_ani_set_noise_immunity_level(struct ath5k_hw *ah, int level) |
67 | { |
68 | /* TODO: |
69 | * ANI documents suggest the following five levels to use, but the HAL |
70 | * and ath9k use only the last two levels, making this |
71 | * essentially an on/off option. There *may* be a reason for this (???), |
72 | * so i stick with the HAL version for now... |
73 | */ |
74 | #if 0 |
75 | static const s8 lo[] = { -52, -56, -60, -64, -70 }; |
76 | static const s8 hi[] = { -18, -18, -16, -14, -12 }; |
77 | static const s8 sz[] = { -34, -41, -48, -55, -62 }; |
78 | static const s8 fr[] = { -70, -72, -75, -78, -80 }; |
79 | #else |
80 | static const s8 lo[] = { -64, -70 }; |
81 | static const s8 hi[] = { -14, -12 }; |
82 | static const s8 sz[] = { -55, -62 }; |
83 | static const s8 fr[] = { -78, -80 }; |
84 | #endif |
85 | if (level < 0 || level >= ARRAY_SIZE(sz)) { |
86 | ATH5K_ERR(ah, "noise immunity level %d out of range" , |
87 | level); |
88 | return; |
89 | } |
90 | |
91 | AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DESIRED_SIZE, |
92 | AR5K_PHY_DESIRED_SIZE_TOT, sz[level]); |
93 | AR5K_REG_WRITE_BITS(ah, AR5K_PHY_AGCCOARSE, |
94 | AR5K_PHY_AGCCOARSE_LO, lo[level]); |
95 | AR5K_REG_WRITE_BITS(ah, AR5K_PHY_AGCCOARSE, |
96 | AR5K_PHY_AGCCOARSE_HI, hi[level]); |
97 | AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SIG, |
98 | AR5K_PHY_SIG_FIRPWR, fr[level]); |
99 | |
100 | ah->ani_state.noise_imm_level = level; |
101 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "new level %d" , level); |
102 | } |
103 | |
104 | /** |
105 | * ath5k_ani_set_spur_immunity_level() - Set spur immunity level |
106 | * @ah: The &struct ath5k_hw |
107 | * @level: level between 0 and @max_spur_level (the maximum level is dependent |
108 | * on the chip revision). |
109 | */ |
110 | void |
111 | ath5k_ani_set_spur_immunity_level(struct ath5k_hw *ah, int level) |
112 | { |
113 | static const int val[] = { 2, 4, 6, 8, 10, 12, 14, 16 }; |
114 | |
115 | if (level < 0 || level >= ARRAY_SIZE(val) || |
116 | level > ah->ani_state.max_spur_level) { |
117 | ATH5K_ERR(ah, "spur immunity level %d out of range" , |
118 | level); |
119 | return; |
120 | } |
121 | |
122 | AR5K_REG_WRITE_BITS(ah, AR5K_PHY_OFDM_SELFCORR, |
123 | AR5K_PHY_OFDM_SELFCORR_CYPWR_THR1, val[level]); |
124 | |
125 | ah->ani_state.spur_level = level; |
126 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "new level %d" , level); |
127 | } |
128 | |
129 | /** |
130 | * ath5k_ani_set_firstep_level() - Set "firstep" level |
131 | * @ah: The &struct ath5k_hw |
132 | * @level: level between 0 and @ATH5K_ANI_MAX_FIRSTEP_LVL |
133 | */ |
134 | void |
135 | ath5k_ani_set_firstep_level(struct ath5k_hw *ah, int level) |
136 | { |
137 | static const int val[] = { 0, 4, 8 }; |
138 | |
139 | if (level < 0 || level >= ARRAY_SIZE(val)) { |
140 | ATH5K_ERR(ah, "firstep level %d out of range" , level); |
141 | return; |
142 | } |
143 | |
144 | AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SIG, |
145 | AR5K_PHY_SIG_FIRSTEP, val[level]); |
146 | |
147 | ah->ani_state.firstep_level = level; |
148 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "new level %d" , level); |
149 | } |
150 | |
151 | /** |
152 | * ath5k_ani_set_ofdm_weak_signal_detection() - Set OFDM weak signal detection |
153 | * @ah: The &struct ath5k_hw |
154 | * @on: turn on or off |
155 | */ |
156 | void |
157 | ath5k_ani_set_ofdm_weak_signal_detection(struct ath5k_hw *ah, bool on) |
158 | { |
159 | static const int m1l[] = { 127, 50 }; |
160 | static const int m2l[] = { 127, 40 }; |
161 | static const int m1[] = { 127, 0x4d }; |
162 | static const int m2[] = { 127, 0x40 }; |
163 | static const int m2cnt[] = { 31, 16 }; |
164 | static const int m2lcnt[] = { 63, 48 }; |
165 | |
166 | AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, |
167 | AR5K_PHY_WEAK_OFDM_LOW_THR_M1, m1l[on]); |
168 | AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, |
169 | AR5K_PHY_WEAK_OFDM_LOW_THR_M2, m2l[on]); |
170 | AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_HIGH_THR, |
171 | AR5K_PHY_WEAK_OFDM_HIGH_THR_M1, m1[on]); |
172 | AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_HIGH_THR, |
173 | AR5K_PHY_WEAK_OFDM_HIGH_THR_M2, m2[on]); |
174 | AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_HIGH_THR, |
175 | AR5K_PHY_WEAK_OFDM_HIGH_THR_M2_COUNT, m2cnt[on]); |
176 | AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, |
177 | AR5K_PHY_WEAK_OFDM_LOW_THR_M2_COUNT, m2lcnt[on]); |
178 | |
179 | if (on) |
180 | AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, |
181 | AR5K_PHY_WEAK_OFDM_LOW_THR_SELFCOR_EN); |
182 | else |
183 | AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, |
184 | AR5K_PHY_WEAK_OFDM_LOW_THR_SELFCOR_EN); |
185 | |
186 | ah->ani_state.ofdm_weak_sig = on; |
187 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "turned %s" , |
188 | on ? "on" : "off" ); |
189 | } |
190 | |
191 | /** |
192 | * ath5k_ani_set_cck_weak_signal_detection() - Set CCK weak signal detection |
193 | * @ah: The &struct ath5k_hw |
194 | * @on: turn on or off |
195 | */ |
196 | void |
197 | ath5k_ani_set_cck_weak_signal_detection(struct ath5k_hw *ah, bool on) |
198 | { |
199 | static const int val[] = { 8, 6 }; |
200 | AR5K_REG_WRITE_BITS(ah, AR5K_PHY_CCK_CROSSCORR, |
201 | AR5K_PHY_CCK_CROSSCORR_WEAK_SIG_THR, val[on]); |
202 | ah->ani_state.cck_weak_sig = on; |
203 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "turned %s" , |
204 | on ? "on" : "off" ); |
205 | } |
206 | |
207 | |
208 | /***************\ |
209 | * ANI algorithm * |
210 | \***************/ |
211 | |
212 | /** |
213 | * ath5k_ani_raise_immunity() - Increase noise immunity |
214 | * @ah: The &struct ath5k_hw |
215 | * @as: The &struct ath5k_ani_state |
216 | * @ofdm_trigger: If this is true we are called because of too many OFDM errors, |
217 | * the algorithm will tune more parameters then. |
218 | * |
219 | * Try to raise noise immunity (=decrease sensitivity) in several steps |
220 | * depending on the average RSSI of the beacons we received. |
221 | */ |
222 | static void |
223 | ath5k_ani_raise_immunity(struct ath5k_hw *ah, struct ath5k_ani_state *as, |
224 | bool ofdm_trigger) |
225 | { |
226 | int = ewma_beacon_rssi_read(e: &ah->ah_beacon_rssi_avg); |
227 | |
228 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "raise immunity (%s)" , |
229 | ofdm_trigger ? "ODFM" : "CCK" ); |
230 | |
231 | /* first: raise noise immunity */ |
232 | if (as->noise_imm_level < ATH5K_ANI_MAX_NOISE_IMM_LVL) { |
233 | ath5k_ani_set_noise_immunity_level(ah, level: as->noise_imm_level + 1); |
234 | return; |
235 | } |
236 | |
237 | /* only OFDM: raise spur immunity level */ |
238 | if (ofdm_trigger && |
239 | as->spur_level < ah->ani_state.max_spur_level) { |
240 | ath5k_ani_set_spur_immunity_level(ah, level: as->spur_level + 1); |
241 | return; |
242 | } |
243 | |
244 | /* AP mode */ |
245 | if (ah->opmode == NL80211_IFTYPE_AP) { |
246 | if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP_LVL) |
247 | ath5k_ani_set_firstep_level(ah, level: as->firstep_level + 1); |
248 | return; |
249 | } |
250 | |
251 | /* STA and IBSS mode */ |
252 | |
253 | /* TODO: for IBSS mode it would be better to keep a beacon RSSI average |
254 | * per each neighbour node and use the minimum of these, to make sure we |
255 | * don't shut out a remote node by raising immunity too high. */ |
256 | |
257 | if (rssi > ATH5K_ANI_RSSI_THR_HIGH) { |
258 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, |
259 | "beacon RSSI high" ); |
260 | /* only OFDM: beacon RSSI is high, we can disable ODFM weak |
261 | * signal detection */ |
262 | if (ofdm_trigger && as->ofdm_weak_sig) { |
263 | ath5k_ani_set_ofdm_weak_signal_detection(ah, on: false); |
264 | ath5k_ani_set_spur_immunity_level(ah, level: 0); |
265 | return; |
266 | } |
267 | /* as a last resort or CCK: raise firstep level */ |
268 | if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP_LVL) { |
269 | ath5k_ani_set_firstep_level(ah, level: as->firstep_level + 1); |
270 | return; |
271 | } |
272 | } else if (rssi > ATH5K_ANI_RSSI_THR_LOW) { |
273 | /* beacon RSSI in mid range, we need OFDM weak signal detect, |
274 | * but can raise firstep level */ |
275 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, |
276 | "beacon RSSI mid" ); |
277 | if (ofdm_trigger && !as->ofdm_weak_sig) |
278 | ath5k_ani_set_ofdm_weak_signal_detection(ah, on: true); |
279 | if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP_LVL) |
280 | ath5k_ani_set_firstep_level(ah, level: as->firstep_level + 1); |
281 | return; |
282 | } else if (ah->ah_current_channel->band == NL80211_BAND_2GHZ) { |
283 | /* beacon RSSI is low. in B/G mode turn of OFDM weak signal |
284 | * detect and zero firstep level to maximize CCK sensitivity */ |
285 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, |
286 | "beacon RSSI low, 2GHz" ); |
287 | if (ofdm_trigger && as->ofdm_weak_sig) |
288 | ath5k_ani_set_ofdm_weak_signal_detection(ah, on: false); |
289 | if (as->firstep_level > 0) |
290 | ath5k_ani_set_firstep_level(ah, level: 0); |
291 | return; |
292 | } |
293 | |
294 | /* TODO: why not?: |
295 | if (as->cck_weak_sig == true) { |
296 | ath5k_ani_set_cck_weak_signal_detection(ah, false); |
297 | } |
298 | */ |
299 | } |
300 | |
301 | /** |
302 | * ath5k_ani_lower_immunity() - Decrease noise immunity |
303 | * @ah: The &struct ath5k_hw |
304 | * @as: The &struct ath5k_ani_state |
305 | * |
306 | * Try to lower noise immunity (=increase sensitivity) in several steps |
307 | * depending on the average RSSI of the beacons we received. |
308 | */ |
309 | static void |
310 | ath5k_ani_lower_immunity(struct ath5k_hw *ah, struct ath5k_ani_state *as) |
311 | { |
312 | int = ewma_beacon_rssi_read(e: &ah->ah_beacon_rssi_avg); |
313 | |
314 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "lower immunity" ); |
315 | |
316 | if (ah->opmode == NL80211_IFTYPE_AP) { |
317 | /* AP mode */ |
318 | if (as->firstep_level > 0) { |
319 | ath5k_ani_set_firstep_level(ah, level: as->firstep_level - 1); |
320 | return; |
321 | } |
322 | } else { |
323 | /* STA and IBSS mode (see TODO above) */ |
324 | if (rssi > ATH5K_ANI_RSSI_THR_HIGH) { |
325 | /* beacon signal is high, leave OFDM weak signal |
326 | * detection off or it may oscillate |
327 | * TODO: who said it's off??? */ |
328 | } else if (rssi > ATH5K_ANI_RSSI_THR_LOW) { |
329 | /* beacon RSSI is mid-range: turn on ODFM weak signal |
330 | * detection and next, lower firstep level */ |
331 | if (!as->ofdm_weak_sig) { |
332 | ath5k_ani_set_ofdm_weak_signal_detection(ah, |
333 | on: true); |
334 | return; |
335 | } |
336 | if (as->firstep_level > 0) { |
337 | ath5k_ani_set_firstep_level(ah, |
338 | level: as->firstep_level - 1); |
339 | return; |
340 | } |
341 | } else { |
342 | /* beacon signal is low: only reduce firstep level */ |
343 | if (as->firstep_level > 0) { |
344 | ath5k_ani_set_firstep_level(ah, |
345 | level: as->firstep_level - 1); |
346 | return; |
347 | } |
348 | } |
349 | } |
350 | |
351 | /* all modes */ |
352 | if (as->spur_level > 0) { |
353 | ath5k_ani_set_spur_immunity_level(ah, level: as->spur_level - 1); |
354 | return; |
355 | } |
356 | |
357 | /* finally, reduce noise immunity */ |
358 | if (as->noise_imm_level > 0) { |
359 | ath5k_ani_set_noise_immunity_level(ah, level: as->noise_imm_level - 1); |
360 | return; |
361 | } |
362 | } |
363 | |
364 | /** |
365 | * ath5k_hw_ani_get_listen_time() - Update counters and return listening time |
366 | * @ah: The &struct ath5k_hw |
367 | * @as: The &struct ath5k_ani_state |
368 | * |
369 | * Return an approximation of the time spent "listening" in milliseconds (ms) |
370 | * since the last call of this function. |
371 | * Save a snapshot of the counter values for debugging/statistics. |
372 | */ |
373 | static int |
374 | ath5k_hw_ani_get_listen_time(struct ath5k_hw *ah, struct ath5k_ani_state *as) |
375 | { |
376 | struct ath_common *common = ath5k_hw_common(ah); |
377 | int listen; |
378 | |
379 | spin_lock_bh(lock: &common->cc_lock); |
380 | |
381 | ath_hw_cycle_counters_update(common); |
382 | memcpy(&as->last_cc, &common->cc_ani, sizeof(as->last_cc)); |
383 | |
384 | /* clears common->cc_ani */ |
385 | listen = ath_hw_get_listen_time(common); |
386 | |
387 | spin_unlock_bh(lock: &common->cc_lock); |
388 | |
389 | return listen; |
390 | } |
391 | |
392 | /** |
393 | * ath5k_ani_save_and_clear_phy_errors() - Clear and save PHY error counters |
394 | * @ah: The &struct ath5k_hw |
395 | * @as: The &struct ath5k_ani_state |
396 | * |
397 | * Clear the PHY error counters as soon as possible, since this might be called |
398 | * from a MIB interrupt and we want to make sure we don't get interrupted again. |
399 | * Add the count of CCK and OFDM errors to our internal state, so it can be used |
400 | * by the algorithm later. |
401 | * |
402 | * Will be called from interrupt and tasklet context. |
403 | * Returns 0 if both counters are zero. |
404 | */ |
405 | static int |
406 | ath5k_ani_save_and_clear_phy_errors(struct ath5k_hw *ah, |
407 | struct ath5k_ani_state *as) |
408 | { |
409 | unsigned int ofdm_err, cck_err; |
410 | |
411 | if (!ah->ah_capabilities.cap_has_phyerr_counters) |
412 | return 0; |
413 | |
414 | ofdm_err = ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT1); |
415 | cck_err = ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT2); |
416 | |
417 | /* reset counters first, we might be in a hurry (interrupt) */ |
418 | ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_OFDM_TRIG_HIGH, |
419 | AR5K_PHYERR_CNT1); |
420 | ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_CCK_TRIG_HIGH, |
421 | AR5K_PHYERR_CNT2); |
422 | |
423 | ofdm_err = ATH5K_ANI_OFDM_TRIG_HIGH - (ATH5K_PHYERR_CNT_MAX - ofdm_err); |
424 | cck_err = ATH5K_ANI_CCK_TRIG_HIGH - (ATH5K_PHYERR_CNT_MAX - cck_err); |
425 | |
426 | /* sometimes both can be zero, especially when there is a superfluous |
427 | * second interrupt. detect that here and return an error. */ |
428 | if (ofdm_err <= 0 && cck_err <= 0) |
429 | return 0; |
430 | |
431 | /* avoid negative values should one of the registers overflow */ |
432 | if (ofdm_err > 0) { |
433 | as->ofdm_errors += ofdm_err; |
434 | as->sum_ofdm_errors += ofdm_err; |
435 | } |
436 | if (cck_err > 0) { |
437 | as->cck_errors += cck_err; |
438 | as->sum_cck_errors += cck_err; |
439 | } |
440 | return 1; |
441 | } |
442 | |
443 | /** |
444 | * ath5k_ani_period_restart() - Restart ANI period |
445 | * @as: The &struct ath5k_ani_state |
446 | * |
447 | * Just reset counters, so they are clear for the next "ani period". |
448 | */ |
449 | static void |
450 | ath5k_ani_period_restart(struct ath5k_ani_state *as) |
451 | { |
452 | /* keep last values for debugging */ |
453 | as->last_ofdm_errors = as->ofdm_errors; |
454 | as->last_cck_errors = as->cck_errors; |
455 | as->last_listen = as->listen_time; |
456 | |
457 | as->ofdm_errors = 0; |
458 | as->cck_errors = 0; |
459 | as->listen_time = 0; |
460 | } |
461 | |
462 | /** |
463 | * ath5k_ani_calibration() - The main ANI calibration function |
464 | * @ah: The &struct ath5k_hw |
465 | * |
466 | * We count OFDM and CCK errors relative to the time where we did not send or |
467 | * receive ("listen" time) and raise or lower immunity accordingly. |
468 | * This is called regularly (every second) from the calibration timer, but also |
469 | * when an error threshold has been reached. |
470 | * |
471 | * In order to synchronize access from different contexts, this should be |
472 | * called only indirectly by scheduling the ANI tasklet! |
473 | */ |
474 | void |
475 | ath5k_ani_calibration(struct ath5k_hw *ah) |
476 | { |
477 | struct ath5k_ani_state *as = &ah->ani_state; |
478 | int listen, ofdm_high, ofdm_low, cck_high, cck_low; |
479 | |
480 | /* get listen time since last call and add it to the counter because we |
481 | * might not have restarted the "ani period" last time. |
482 | * always do this to calculate the busy time also in manual mode */ |
483 | listen = ath5k_hw_ani_get_listen_time(ah, as); |
484 | as->listen_time += listen; |
485 | |
486 | if (as->ani_mode != ATH5K_ANI_MODE_AUTO) |
487 | return; |
488 | |
489 | ath5k_ani_save_and_clear_phy_errors(ah, as); |
490 | |
491 | ofdm_high = as->listen_time * ATH5K_ANI_OFDM_TRIG_HIGH / 1000; |
492 | cck_high = as->listen_time * ATH5K_ANI_CCK_TRIG_HIGH / 1000; |
493 | ofdm_low = as->listen_time * ATH5K_ANI_OFDM_TRIG_LOW / 1000; |
494 | cck_low = as->listen_time * ATH5K_ANI_CCK_TRIG_LOW / 1000; |
495 | |
496 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, |
497 | "listen %d (now %d)" , as->listen_time, listen); |
498 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, |
499 | "check high ofdm %d/%d cck %d/%d" , |
500 | as->ofdm_errors, ofdm_high, as->cck_errors, cck_high); |
501 | |
502 | if (as->ofdm_errors > ofdm_high || as->cck_errors > cck_high) { |
503 | /* too many PHY errors - we have to raise immunity */ |
504 | bool ofdm_flag = as->ofdm_errors > ofdm_high; |
505 | ath5k_ani_raise_immunity(ah, as, ofdm_trigger: ofdm_flag); |
506 | ath5k_ani_period_restart(as); |
507 | |
508 | } else if (as->listen_time > 5 * ATH5K_ANI_LISTEN_PERIOD) { |
509 | /* If more than 5 (TODO: why 5?) periods have passed and we got |
510 | * relatively little errors we can try to lower immunity */ |
511 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, |
512 | "check low ofdm %d/%d cck %d/%d" , |
513 | as->ofdm_errors, ofdm_low, as->cck_errors, cck_low); |
514 | |
515 | if (as->ofdm_errors <= ofdm_low && as->cck_errors <= cck_low) |
516 | ath5k_ani_lower_immunity(ah, as); |
517 | |
518 | ath5k_ani_period_restart(as); |
519 | } |
520 | } |
521 | |
522 | |
523 | /*******************\ |
524 | * Interrupt handler * |
525 | \*******************/ |
526 | |
527 | /** |
528 | * ath5k_ani_mib_intr() - Interrupt handler for ANI MIB counters |
529 | * @ah: The &struct ath5k_hw |
530 | * |
531 | * Just read & reset the registers quickly, so they don't generate more |
532 | * interrupts, save the counters and schedule the tasklet to decide whether |
533 | * to raise immunity or not. |
534 | * |
535 | * We just need to handle PHY error counters, ath5k_hw_update_mib_counters() |
536 | * should take care of all "normal" MIB interrupts. |
537 | */ |
538 | void |
539 | ath5k_ani_mib_intr(struct ath5k_hw *ah) |
540 | { |
541 | struct ath5k_ani_state *as = &ah->ani_state; |
542 | |
543 | /* nothing to do here if HW does not have PHY error counters - they |
544 | * can't be the reason for the MIB interrupt then */ |
545 | if (!ah->ah_capabilities.cap_has_phyerr_counters) |
546 | return; |
547 | |
548 | /* not in use but clear anyways */ |
549 | ath5k_hw_reg_write(ah, val: 0, AR5K_OFDM_FIL_CNT); |
550 | ath5k_hw_reg_write(ah, val: 0, AR5K_CCK_FIL_CNT); |
551 | |
552 | if (ah->ani_state.ani_mode != ATH5K_ANI_MODE_AUTO) |
553 | return; |
554 | |
555 | /* If one of the errors triggered, we can get a superfluous second |
556 | * interrupt, even though we have already reset the register. The |
557 | * function detects that so we can return early. */ |
558 | if (ath5k_ani_save_and_clear_phy_errors(ah, as) == 0) |
559 | return; |
560 | |
561 | if (as->ofdm_errors > ATH5K_ANI_OFDM_TRIG_HIGH || |
562 | as->cck_errors > ATH5K_ANI_CCK_TRIG_HIGH) |
563 | tasklet_schedule(t: &ah->ani_tasklet); |
564 | } |
565 | |
566 | /** |
567 | * ath5k_ani_phy_error_report - Used by older HW to report PHY errors |
568 | * |
569 | * @ah: The &struct ath5k_hw |
570 | * @phyerr: One of enum ath5k_phy_error_code |
571 | * |
572 | * This is used by hardware without PHY error counters to report PHY errors |
573 | * on a frame-by-frame basis, instead of the interrupt. |
574 | */ |
575 | void |
576 | ath5k_ani_phy_error_report(struct ath5k_hw *ah, |
577 | enum ath5k_phy_error_code phyerr) |
578 | { |
579 | struct ath5k_ani_state *as = &ah->ani_state; |
580 | |
581 | if (phyerr == AR5K_RX_PHY_ERROR_OFDM_TIMING) { |
582 | as->ofdm_errors++; |
583 | if (as->ofdm_errors > ATH5K_ANI_OFDM_TRIG_HIGH) |
584 | tasklet_schedule(t: &ah->ani_tasklet); |
585 | } else if (phyerr == AR5K_RX_PHY_ERROR_CCK_TIMING) { |
586 | as->cck_errors++; |
587 | if (as->cck_errors > ATH5K_ANI_CCK_TRIG_HIGH) |
588 | tasklet_schedule(t: &ah->ani_tasklet); |
589 | } |
590 | } |
591 | |
592 | |
593 | /****************\ |
594 | * Initialization * |
595 | \****************/ |
596 | |
597 | /** |
598 | * ath5k_enable_phy_err_counters() - Enable PHY error counters |
599 | * @ah: The &struct ath5k_hw |
600 | * |
601 | * Enable PHY error counters for OFDM and CCK timing errors. |
602 | */ |
603 | static void |
604 | ath5k_enable_phy_err_counters(struct ath5k_hw *ah) |
605 | { |
606 | ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_OFDM_TRIG_HIGH, |
607 | AR5K_PHYERR_CNT1); |
608 | ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_CCK_TRIG_HIGH, |
609 | AR5K_PHYERR_CNT2); |
610 | ath5k_hw_reg_write(ah, AR5K_PHY_ERR_FIL_OFDM, AR5K_PHYERR_CNT1_MASK); |
611 | ath5k_hw_reg_write(ah, AR5K_PHY_ERR_FIL_CCK, AR5K_PHYERR_CNT2_MASK); |
612 | |
613 | /* not in use */ |
614 | ath5k_hw_reg_write(ah, val: 0, AR5K_OFDM_FIL_CNT); |
615 | ath5k_hw_reg_write(ah, val: 0, AR5K_CCK_FIL_CNT); |
616 | } |
617 | |
618 | /** |
619 | * ath5k_disable_phy_err_counters() - Disable PHY error counters |
620 | * @ah: The &struct ath5k_hw |
621 | * |
622 | * Disable PHY error counters for OFDM and CCK timing errors. |
623 | */ |
624 | static void |
625 | ath5k_disable_phy_err_counters(struct ath5k_hw *ah) |
626 | { |
627 | ath5k_hw_reg_write(ah, val: 0, AR5K_PHYERR_CNT1); |
628 | ath5k_hw_reg_write(ah, val: 0, AR5K_PHYERR_CNT2); |
629 | ath5k_hw_reg_write(ah, val: 0, AR5K_PHYERR_CNT1_MASK); |
630 | ath5k_hw_reg_write(ah, val: 0, AR5K_PHYERR_CNT2_MASK); |
631 | |
632 | /* not in use */ |
633 | ath5k_hw_reg_write(ah, val: 0, AR5K_OFDM_FIL_CNT); |
634 | ath5k_hw_reg_write(ah, val: 0, AR5K_CCK_FIL_CNT); |
635 | } |
636 | |
637 | /** |
638 | * ath5k_ani_init() - Initialize ANI |
639 | * @ah: The &struct ath5k_hw |
640 | * @mode: One of enum ath5k_ani_mode |
641 | * |
642 | * Initialize ANI according to mode. |
643 | */ |
644 | void |
645 | ath5k_ani_init(struct ath5k_hw *ah, enum ath5k_ani_mode mode) |
646 | { |
647 | /* ANI is only possible on 5212 and newer */ |
648 | if (ah->ah_version < AR5K_AR5212) |
649 | return; |
650 | |
651 | if (mode < ATH5K_ANI_MODE_OFF || mode > ATH5K_ANI_MODE_AUTO) { |
652 | ATH5K_ERR(ah, "ANI mode %d out of range" , mode); |
653 | return; |
654 | } |
655 | |
656 | /* clear old state information */ |
657 | memset(&ah->ani_state, 0, sizeof(ah->ani_state)); |
658 | |
659 | /* older hardware has more spur levels than newer */ |
660 | if (ah->ah_mac_srev < AR5K_SREV_AR2414) |
661 | ah->ani_state.max_spur_level = 7; |
662 | else |
663 | ah->ani_state.max_spur_level = 2; |
664 | |
665 | /* initial values for our ani parameters */ |
666 | if (mode == ATH5K_ANI_MODE_OFF) { |
667 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "ANI off\n" ); |
668 | } else if (mode == ATH5K_ANI_MODE_MANUAL_LOW) { |
669 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, |
670 | "ANI manual low -> high sensitivity\n" ); |
671 | ath5k_ani_set_noise_immunity_level(ah, level: 0); |
672 | ath5k_ani_set_spur_immunity_level(ah, level: 0); |
673 | ath5k_ani_set_firstep_level(ah, level: 0); |
674 | ath5k_ani_set_ofdm_weak_signal_detection(ah, on: true); |
675 | ath5k_ani_set_cck_weak_signal_detection(ah, on: true); |
676 | } else if (mode == ATH5K_ANI_MODE_MANUAL_HIGH) { |
677 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, |
678 | "ANI manual high -> low sensitivity\n" ); |
679 | ath5k_ani_set_noise_immunity_level(ah, |
680 | ATH5K_ANI_MAX_NOISE_IMM_LVL); |
681 | ath5k_ani_set_spur_immunity_level(ah, |
682 | level: ah->ani_state.max_spur_level); |
683 | ath5k_ani_set_firstep_level(ah, ATH5K_ANI_MAX_FIRSTEP_LVL); |
684 | ath5k_ani_set_ofdm_weak_signal_detection(ah, on: false); |
685 | ath5k_ani_set_cck_weak_signal_detection(ah, on: false); |
686 | } else if (mode == ATH5K_ANI_MODE_AUTO) { |
687 | ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "ANI auto\n" ); |
688 | ath5k_ani_set_noise_immunity_level(ah, level: 0); |
689 | ath5k_ani_set_spur_immunity_level(ah, level: 0); |
690 | ath5k_ani_set_firstep_level(ah, level: 0); |
691 | ath5k_ani_set_ofdm_weak_signal_detection(ah, on: true); |
692 | ath5k_ani_set_cck_weak_signal_detection(ah, on: false); |
693 | } |
694 | |
695 | /* newer hardware has PHY error counter registers which we can use to |
696 | * get OFDM and CCK error counts. older hardware has to set rxfilter and |
697 | * report every single PHY error by calling ath5k_ani_phy_error_report() |
698 | */ |
699 | if (mode == ATH5K_ANI_MODE_AUTO) { |
700 | if (ah->ah_capabilities.cap_has_phyerr_counters) |
701 | ath5k_enable_phy_err_counters(ah); |
702 | else |
703 | ath5k_hw_set_rx_filter(ah, filter: ath5k_hw_get_rx_filter(ah) | |
704 | AR5K_RX_FILTER_PHYERR); |
705 | } else { |
706 | if (ah->ah_capabilities.cap_has_phyerr_counters) |
707 | ath5k_disable_phy_err_counters(ah); |
708 | else |
709 | ath5k_hw_set_rx_filter(ah, filter: ath5k_hw_get_rx_filter(ah) & |
710 | ~AR5K_RX_FILTER_PHYERR); |
711 | } |
712 | |
713 | ah->ani_state.ani_mode = mode; |
714 | } |
715 | |
716 | |
717 | /**************\ |
718 | * Debug output * |
719 | \**************/ |
720 | |
721 | #ifdef CONFIG_ATH5K_DEBUG |
722 | |
723 | /** |
724 | * ath5k_ani_print_counters() - Print ANI counters |
725 | * @ah: The &struct ath5k_hw |
726 | * |
727 | * Used for debugging ANI |
728 | */ |
729 | void |
730 | ath5k_ani_print_counters(struct ath5k_hw *ah) |
731 | { |
732 | /* clears too */ |
733 | pr_notice("ACK fail\t%d\n" , ath5k_hw_reg_read(ah, AR5K_ACK_FAIL)); |
734 | pr_notice("RTS fail\t%d\n" , ath5k_hw_reg_read(ah, AR5K_RTS_FAIL)); |
735 | pr_notice("RTS success\t%d\n" , ath5k_hw_reg_read(ah, AR5K_RTS_OK)); |
736 | pr_notice("FCS error\t%d\n" , ath5k_hw_reg_read(ah, AR5K_FCS_FAIL)); |
737 | |
738 | /* no clear */ |
739 | pr_notice("tx\t%d\n" , ath5k_hw_reg_read(ah, AR5K_PROFCNT_TX)); |
740 | pr_notice("rx\t%d\n" , ath5k_hw_reg_read(ah, AR5K_PROFCNT_RX)); |
741 | pr_notice("busy\t%d\n" , ath5k_hw_reg_read(ah, AR5K_PROFCNT_RXCLR)); |
742 | pr_notice("cycles\t%d\n" , ath5k_hw_reg_read(ah, AR5K_PROFCNT_CYCLE)); |
743 | |
744 | pr_notice("AR5K_PHYERR_CNT1\t%d\n" , |
745 | ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT1)); |
746 | pr_notice("AR5K_PHYERR_CNT2\t%d\n" , |
747 | ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT2)); |
748 | pr_notice("AR5K_OFDM_FIL_CNT\t%d\n" , |
749 | ath5k_hw_reg_read(ah, AR5K_OFDM_FIL_CNT)); |
750 | pr_notice("AR5K_CCK_FIL_CNT\t%d\n" , |
751 | ath5k_hw_reg_read(ah, AR5K_CCK_FIL_CNT)); |
752 | } |
753 | |
754 | #endif |
755 | |