Commit | Line | Data |
---|---|---|
f1dc5600 | 1 | /* |
5b68138e | 2 | * Copyright (c) 2008-2011 Atheros Communications Inc. |
f1dc5600 S |
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 | ||
c46917bb | 17 | #include "hw.h" |
641d9921 | 18 | #include "hw-ops.h" |
f1dc5600 | 19 | |
795f5e2c LR |
20 | /* Common calibration code */ |
21 | ||
2292ca6d | 22 | #define ATH9K_NF_TOO_HIGH -60 |
f1dc5600 | 23 | |
f1dc5600 S |
24 | static int16_t ath9k_hw_get_nf_hist_mid(int16_t *nfCalBuffer) |
25 | { | |
26 | int16_t nfval; | |
27 | int16_t sort[ATH9K_NF_CAL_HIST_MAX]; | |
28 | int i, j; | |
29 | ||
30 | for (i = 0; i < ATH9K_NF_CAL_HIST_MAX; i++) | |
31 | sort[i] = nfCalBuffer[i]; | |
32 | ||
33 | for (i = 0; i < ATH9K_NF_CAL_HIST_MAX - 1; i++) { | |
34 | for (j = 1; j < ATH9K_NF_CAL_HIST_MAX - i; j++) { | |
35 | if (sort[j] > sort[j - 1]) { | |
36 | nfval = sort[j]; | |
37 | sort[j] = sort[j - 1]; | |
38 | sort[j - 1] = nfval; | |
39 | } | |
40 | } | |
41 | } | |
42 | nfval = sort[(ATH9K_NF_CAL_HIST_MAX - 1) >> 1]; | |
43 | ||
44 | return nfval; | |
45 | } | |
46 | ||
2292ca6d FF |
47 | static struct ath_nf_limits *ath9k_hw_get_nf_limits(struct ath_hw *ah, |
48 | struct ath9k_channel *chan) | |
49 | { | |
50 | struct ath_nf_limits *limit; | |
51 | ||
52 | if (!chan || IS_CHAN_2GHZ(chan)) | |
53 | limit = &ah->nf_2g; | |
54 | else | |
55 | limit = &ah->nf_5g; | |
56 | ||
57 | return limit; | |
58 | } | |
59 | ||
60 | static s16 ath9k_hw_get_default_nf(struct ath_hw *ah, | |
61 | struct ath9k_channel *chan) | |
62 | { | |
63 | return ath9k_hw_get_nf_limits(ah, chan)->nominal; | |
64 | } | |
65 | ||
66 | ||
67 | static void ath9k_hw_update_nfcal_hist_buffer(struct ath_hw *ah, | |
70cf1533 | 68 | struct ath9k_hw_cal_data *cal, |
f1dc5600 S |
69 | int16_t *nfarray) |
70 | { | |
70cf1533 | 71 | struct ath_common *common = ath9k_hw_common(ah); |
28ef6450 | 72 | struct ieee80211_conf *conf = &common->hw->conf; |
2292ca6d | 73 | struct ath_nf_limits *limit; |
70cf1533 FF |
74 | struct ath9k_nfcal_hist *h; |
75 | bool high_nf_mid = false; | |
28ef6450 | 76 | u8 chainmask = (ah->rxchainmask << 3) | ah->rxchainmask; |
f1dc5600 S |
77 | int i; |
78 | ||
70cf1533 | 79 | h = cal->nfCalHist; |
2292ca6d FF |
80 | limit = ath9k_hw_get_nf_limits(ah, ah->curchan); |
81 | ||
f1dc5600 | 82 | for (i = 0; i < NUM_NF_READINGS; i++) { |
28ef6450 RM |
83 | if (!(chainmask & (1 << i)) || |
84 | ((i >= AR5416_MAX_CHAINS) && !conf_is_ht40(conf))) | |
85 | continue; | |
86 | ||
f1dc5600 S |
87 | h[i].nfCalBuffer[h[i].currIndex] = nfarray[i]; |
88 | ||
89 | if (++h[i].currIndex >= ATH9K_NF_CAL_HIST_MAX) | |
90 | h[i].currIndex = 0; | |
91 | ||
92 | if (h[i].invalidNFcount > 0) { | |
f2552e28 FF |
93 | h[i].invalidNFcount--; |
94 | h[i].privNF = nfarray[i]; | |
f1dc5600 S |
95 | } else { |
96 | h[i].privNF = | |
97 | ath9k_hw_get_nf_hist_mid(h[i].nfCalBuffer); | |
98 | } | |
2292ca6d | 99 | |
70cf1533 FF |
100 | if (!h[i].privNF) |
101 | continue; | |
102 | ||
103 | if (h[i].privNF > limit->max) { | |
104 | high_nf_mid = true; | |
105 | ||
226afe68 JP |
106 | ath_dbg(common, ATH_DBG_CALIBRATE, |
107 | "NFmid[%d] (%d) > MAX (%d), %s\n", | |
108 | i, h[i].privNF, limit->max, | |
109 | (cal->nfcal_interference ? | |
110 | "not corrected (due to interference)" : | |
111 | "correcting to MAX")); | |
70cf1533 FF |
112 | |
113 | /* | |
114 | * Normally we limit the average noise floor by the | |
115 | * hardware specific maximum here. However if we have | |
116 | * encountered stuck beacons because of interference, | |
117 | * we bypass this limit here in order to better deal | |
118 | * with our environment. | |
119 | */ | |
120 | if (!cal->nfcal_interference) | |
121 | h[i].privNF = limit->max; | |
122 | } | |
f1dc5600 | 123 | } |
70cf1533 FF |
124 | |
125 | /* | |
126 | * If the noise floor seems normal for all chains, assume that | |
127 | * there is no significant interference in the environment anymore. | |
128 | * Re-enable the enforcement of the NF maximum again. | |
129 | */ | |
130 | if (!high_nf_mid) | |
131 | cal->nfcal_interference = false; | |
f1dc5600 S |
132 | } |
133 | ||
b43d59fb LR |
134 | static bool ath9k_hw_get_nf_thresh(struct ath_hw *ah, |
135 | enum ieee80211_band band, | |
136 | int16_t *nft) | |
f1dc5600 | 137 | { |
76061abb LR |
138 | switch (band) { |
139 | case IEEE80211_BAND_5GHZ: | |
f74df6fb | 140 | *nft = (int8_t)ah->eep_ops->get_eeprom(ah, EEP_NFTHRESH_5); |
f1dc5600 | 141 | break; |
76061abb | 142 | case IEEE80211_BAND_2GHZ: |
f74df6fb | 143 | *nft = (int8_t)ah->eep_ops->get_eeprom(ah, EEP_NFTHRESH_2); |
f1dc5600 S |
144 | break; |
145 | default: | |
76061abb | 146 | BUG_ON(1); |
f1dc5600 S |
147 | return false; |
148 | } | |
149 | ||
150 | return true; | |
151 | } | |
152 | ||
795f5e2c LR |
153 | void ath9k_hw_reset_calibration(struct ath_hw *ah, |
154 | struct ath9k_cal_list *currCal) | |
f1dc5600 | 155 | { |
f1dc5600 S |
156 | int i; |
157 | ||
158 | ath9k_hw_setup_calibration(ah, currCal); | |
159 | ||
160 | currCal->calState = CAL_RUNNING; | |
161 | ||
162 | for (i = 0; i < AR5416_MAX_CHAINS; i++) { | |
2660b81a S |
163 | ah->meas0.sign[i] = 0; |
164 | ah->meas1.sign[i] = 0; | |
165 | ah->meas2.sign[i] = 0; | |
166 | ah->meas3.sign[i] = 0; | |
f1dc5600 S |
167 | } |
168 | ||
2660b81a | 169 | ah->cal_samples = 0; |
f1dc5600 S |
170 | } |
171 | ||
c9e27d94 | 172 | /* This is done for the currently configured channel */ |
cbe61d8a | 173 | bool ath9k_hw_reset_calvalid(struct ath_hw *ah) |
f1dc5600 | 174 | { |
c46917bb LR |
175 | struct ath_common *common = ath9k_hw_common(ah); |
176 | struct ieee80211_conf *conf = &common->hw->conf; | |
cbfe9468 | 177 | struct ath9k_cal_list *currCal = ah->cal_list_curr; |
f1dc5600 | 178 | |
20bd2a09 | 179 | if (!ah->caldata) |
c9e27d94 | 180 | return true; |
f1dc5600 S |
181 | |
182 | if (!AR_SREV_9100(ah) && !AR_SREV_9160_10_OR_LATER(ah)) | |
c9e27d94 | 183 | return true; |
f1dc5600 S |
184 | |
185 | if (currCal == NULL) | |
c9e27d94 | 186 | return true; |
f1dc5600 S |
187 | |
188 | if (currCal->calState != CAL_DONE) { | |
226afe68 JP |
189 | ath_dbg(common, ATH_DBG_CALIBRATE, |
190 | "Calibration state incorrect, %d\n", | |
191 | currCal->calState); | |
c9e27d94 | 192 | return true; |
f1dc5600 S |
193 | } |
194 | ||
6497827f | 195 | if (!(ah->supp_cals & currCal->calData->calType)) |
c9e27d94 | 196 | return true; |
f1dc5600 | 197 | |
226afe68 JP |
198 | ath_dbg(common, ATH_DBG_CALIBRATE, |
199 | "Resetting Cal %d state for channel %u\n", | |
200 | currCal->calData->calType, conf->channel->center_freq); | |
f1dc5600 | 201 | |
20bd2a09 | 202 | ah->caldata->CalValid &= ~currCal->calData->calType; |
f1dc5600 S |
203 | currCal->calState = CAL_WAITING; |
204 | ||
c9e27d94 | 205 | return false; |
f1dc5600 | 206 | } |
7322fd19 | 207 | EXPORT_SYMBOL(ath9k_hw_reset_calvalid); |
f1dc5600 | 208 | |
00c86590 | 209 | void ath9k_hw_start_nfcal(struct ath_hw *ah, bool update) |
f1dc5600 | 210 | { |
4254bc1c FF |
211 | if (ah->caldata) |
212 | ah->caldata->nfcal_pending = true; | |
213 | ||
f1dc5600 S |
214 | REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, |
215 | AR_PHY_AGC_CONTROL_ENABLE_NF); | |
00c86590 FF |
216 | |
217 | if (update) | |
218 | REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, | |
f1dc5600 | 219 | AR_PHY_AGC_CONTROL_NO_UPDATE_NF); |
00c86590 FF |
220 | else |
221 | REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, | |
222 | AR_PHY_AGC_CONTROL_NO_UPDATE_NF); | |
223 | ||
f1dc5600 S |
224 | REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NF); |
225 | } | |
226 | ||
bbacee13 FF |
227 | void ath9k_hw_loadnf(struct ath_hw *ah, struct ath9k_channel *chan) |
228 | { | |
20bd2a09 | 229 | struct ath9k_nfcal_hist *h = NULL; |
bbacee13 FF |
230 | unsigned i, j; |
231 | int32_t val; | |
487f0e01 | 232 | u8 chainmask = (ah->rxchainmask << 3) | ah->rxchainmask; |
bbacee13 | 233 | struct ath_common *common = ath9k_hw_common(ah); |
28ef6450 | 234 | struct ieee80211_conf *conf = &common->hw->conf; |
20bd2a09 | 235 | s16 default_nf = ath9k_hw_get_default_nf(ah, chan); |
bbacee13 | 236 | |
20bd2a09 FF |
237 | if (ah->caldata) |
238 | h = ah->caldata->nfCalHist; | |
bbacee13 FF |
239 | |
240 | for (i = 0; i < NUM_NF_READINGS; i++) { | |
241 | if (chainmask & (1 << i)) { | |
20bd2a09 FF |
242 | s16 nfval; |
243 | ||
28ef6450 RM |
244 | if ((i >= AR5416_MAX_CHAINS) && !conf_is_ht40(conf)) |
245 | continue; | |
246 | ||
20bd2a09 FF |
247 | if (h) |
248 | nfval = h[i].privNF; | |
249 | else | |
250 | nfval = default_nf; | |
251 | ||
bbacee13 FF |
252 | val = REG_READ(ah, ah->nf_regs[i]); |
253 | val &= 0xFFFFFE00; | |
20bd2a09 | 254 | val |= (((u32) nfval << 1) & 0x1ff); |
bbacee13 FF |
255 | REG_WRITE(ah, ah->nf_regs[i], val); |
256 | } | |
257 | } | |
258 | ||
259 | /* | |
260 | * Load software filtered NF value into baseband internal minCCApwr | |
261 | * variable. | |
262 | */ | |
263 | REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, | |
264 | AR_PHY_AGC_CONTROL_ENABLE_NF); | |
265 | REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, | |
266 | AR_PHY_AGC_CONTROL_NO_UPDATE_NF); | |
267 | REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NF); | |
268 | ||
269 | /* | |
270 | * Wait for load to complete, should be fast, a few 10s of us. | |
271 | * The max delay was changed from an original 250us to 10000us | |
272 | * since 250us often results in NF load timeout and causes deaf | |
273 | * condition during stress testing 12/12/2009 | |
274 | */ | |
23952ec9 | 275 | for (j = 0; j < 10000; j++) { |
bbacee13 FF |
276 | if ((REG_READ(ah, AR_PHY_AGC_CONTROL) & |
277 | AR_PHY_AGC_CONTROL_NF) == 0) | |
278 | break; | |
279 | udelay(10); | |
280 | } | |
281 | ||
282 | /* | |
283 | * We timed out waiting for the noisefloor to load, probably due to an | |
284 | * in-progress rx. Simply return here and allow the load plenty of time | |
285 | * to complete before the next calibration interval. We need to avoid | |
286 | * trying to load -50 (which happens below) while the previous load is | |
287 | * still in progress as this can cause rx deafness. Instead by returning | |
288 | * here, the baseband nf cal will just be capped by our present | |
289 | * noisefloor until the next calibration timer. | |
290 | */ | |
23952ec9 | 291 | if (j == 10000) { |
226afe68 JP |
292 | ath_dbg(common, ATH_DBG_ANY, |
293 | "Timeout while waiting for nf to load: AR_PHY_AGC_CONTROL=0x%x\n", | |
294 | REG_READ(ah, AR_PHY_AGC_CONTROL)); | |
bbacee13 FF |
295 | return; |
296 | } | |
297 | ||
298 | /* | |
299 | * Restore maxCCAPower register parameter again so that we're not capped | |
300 | * by the median we just loaded. This will be initial (and max) value | |
301 | * of next noise floor calibration the baseband does. | |
302 | */ | |
303 | ENABLE_REGWRITE_BUFFER(ah); | |
304 | for (i = 0; i < NUM_NF_READINGS; i++) { | |
305 | if (chainmask & (1 << i)) { | |
28ef6450 RM |
306 | if ((i >= AR5416_MAX_CHAINS) && !conf_is_ht40(conf)) |
307 | continue; | |
308 | ||
bbacee13 FF |
309 | val = REG_READ(ah, ah->nf_regs[i]); |
310 | val &= 0xFFFFFE00; | |
311 | val |= (((u32) (-50) << 1) & 0x1ff); | |
312 | REG_WRITE(ah, ah->nf_regs[i], val); | |
313 | } | |
314 | } | |
315 | REGWRITE_BUFFER_FLUSH(ah); | |
bbacee13 FF |
316 | } |
317 | ||
318 | ||
f2552e28 FF |
319 | static void ath9k_hw_nf_sanitize(struct ath_hw *ah, s16 *nf) |
320 | { | |
321 | struct ath_common *common = ath9k_hw_common(ah); | |
322 | struct ath_nf_limits *limit; | |
323 | int i; | |
324 | ||
325 | if (IS_CHAN_2GHZ(ah->curchan)) | |
326 | limit = &ah->nf_2g; | |
327 | else | |
328 | limit = &ah->nf_5g; | |
329 | ||
330 | for (i = 0; i < NUM_NF_READINGS; i++) { | |
331 | if (!nf[i]) | |
332 | continue; | |
333 | ||
226afe68 JP |
334 | ath_dbg(common, ATH_DBG_CALIBRATE, |
335 | "NF calibrated [%s] [chain %d] is %d\n", | |
336 | (i >= 3 ? "ext" : "ctl"), i % 3, nf[i]); | |
54bd5006 | 337 | |
2292ca6d | 338 | if (nf[i] > ATH9K_NF_TOO_HIGH) { |
226afe68 JP |
339 | ath_dbg(common, ATH_DBG_CALIBRATE, |
340 | "NF[%d] (%d) > MAX (%d), correcting to MAX\n", | |
341 | i, nf[i], ATH9K_NF_TOO_HIGH); | |
f2552e28 FF |
342 | nf[i] = limit->max; |
343 | } else if (nf[i] < limit->min) { | |
226afe68 JP |
344 | ath_dbg(common, ATH_DBG_CALIBRATE, |
345 | "NF[%d] (%d) < MIN (%d), correcting to NOM\n", | |
346 | i, nf[i], limit->min); | |
f2552e28 FF |
347 | nf[i] = limit->nominal; |
348 | } | |
349 | } | |
350 | } | |
351 | ||
4254bc1c | 352 | bool ath9k_hw_getnf(struct ath_hw *ah, struct ath9k_channel *chan) |
f1dc5600 | 353 | { |
c46917bb | 354 | struct ath_common *common = ath9k_hw_common(ah); |
f1dc5600 S |
355 | int16_t nf, nfThresh; |
356 | int16_t nfarray[NUM_NF_READINGS] = { 0 }; | |
357 | struct ath9k_nfcal_hist *h; | |
76061abb | 358 | struct ieee80211_channel *c = chan->chan; |
20bd2a09 FF |
359 | struct ath9k_hw_cal_data *caldata = ah->caldata; |
360 | ||
f1dc5600 S |
361 | chan->channelFlags &= (~CHANNEL_CW_INT); |
362 | if (REG_READ(ah, AR_PHY_AGC_CONTROL) & AR_PHY_AGC_CONTROL_NF) { | |
226afe68 JP |
363 | ath_dbg(common, ATH_DBG_CALIBRATE, |
364 | "NF did not complete in calibration window\n"); | |
4254bc1c | 365 | return false; |
d9891c78 FF |
366 | } |
367 | ||
368 | ath9k_hw_do_getnf(ah, nfarray); | |
369 | ath9k_hw_nf_sanitize(ah, nfarray); | |
370 | nf = nfarray[0]; | |
371 | if (ath9k_hw_get_nf_thresh(ah, c->band, &nfThresh) | |
372 | && nf > nfThresh) { | |
226afe68 JP |
373 | ath_dbg(common, ATH_DBG_CALIBRATE, |
374 | "noise floor failed detected; detected %d, threshold %d\n", | |
375 | nf, nfThresh); | |
d9891c78 FF |
376 | chan->channelFlags |= CHANNEL_CW_INT; |
377 | } | |
378 | ||
379 | if (!caldata) { | |
380 | chan->noisefloor = nf; | |
381 | return false; | |
f1dc5600 S |
382 | } |
383 | ||
20bd2a09 | 384 | h = caldata->nfCalHist; |
4254bc1c | 385 | caldata->nfcal_pending = false; |
70cf1533 | 386 | ath9k_hw_update_nfcal_hist_buffer(ah, caldata, nfarray); |
d9891c78 | 387 | chan->noisefloor = h[0].privNF; |
4254bc1c | 388 | return true; |
f1dc5600 S |
389 | } |
390 | ||
20bd2a09 FF |
391 | void ath9k_init_nfcal_hist_buffer(struct ath_hw *ah, |
392 | struct ath9k_channel *chan) | |
f1dc5600 | 393 | { |
20bd2a09 FF |
394 | struct ath9k_nfcal_hist *h; |
395 | s16 default_nf; | |
f1dc5600 | 396 | int i, j; |
f2552e28 | 397 | |
bdd196a3 RM |
398 | ah->caldata->channel = chan->channel; |
399 | ah->caldata->channelFlags = chan->channelFlags & ~CHANNEL_CW_INT; | |
20bd2a09 FF |
400 | h = ah->caldata->nfCalHist; |
401 | default_nf = ath9k_hw_get_default_nf(ah, chan); | |
f1dc5600 | 402 | for (i = 0; i < NUM_NF_READINGS; i++) { |
20bd2a09 FF |
403 | h[i].currIndex = 0; |
404 | h[i].privNF = default_nf; | |
405 | h[i].invalidNFcount = AR_PHY_CCA_FILTERWINDOW_LENGTH; | |
f1dc5600 | 406 | for (j = 0; j < ATH9K_NF_CAL_HIST_MAX; j++) { |
20bd2a09 | 407 | h[i].nfCalBuffer[j] = default_nf; |
f1dc5600 S |
408 | } |
409 | } | |
f1dc5600 S |
410 | } |
411 | ||
70cf1533 FF |
412 | |
413 | void ath9k_hw_bstuck_nfcal(struct ath_hw *ah) | |
414 | { | |
415 | struct ath9k_hw_cal_data *caldata = ah->caldata; | |
416 | ||
417 | if (unlikely(!caldata)) | |
418 | return; | |
419 | ||
420 | /* | |
421 | * If beacons are stuck, the most likely cause is interference. | |
422 | * Triggering a noise floor calibration at this point helps the | |
423 | * hardware adapt to a noisy environment much faster. | |
424 | * To ensure that we recover from stuck beacons quickly, let | |
425 | * the baseband update the internal NF value itself, similar to | |
426 | * what is being done after a full reset. | |
427 | */ | |
428 | if (!caldata->nfcal_pending) | |
429 | ath9k_hw_start_nfcal(ah, true); | |
430 | else if (!(REG_READ(ah, AR_PHY_AGC_CONTROL) & AR_PHY_AGC_CONTROL_NF)) | |
431 | ath9k_hw_getnf(ah, ah->curchan); | |
432 | ||
433 | caldata->nfcal_interference = true; | |
434 | } | |
435 | EXPORT_SYMBOL(ath9k_hw_bstuck_nfcal); | |
436 |