1 | /* |
2 | * Copyright (c) 2012 Qualcomm Atheros, 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 | |
17 | #include "ath9k.h" |
18 | |
19 | /* |
20 | * TX polling - checks if the TX engine is stuck somewhere |
21 | * and issues a chip reset if so. |
22 | */ |
23 | static bool ath_tx_complete_check(struct ath_softc *sc) |
24 | { |
25 | struct ath_txq *txq; |
26 | int i; |
27 | |
28 | if (sc->tx99_state) |
29 | return true; |
30 | |
31 | for (i = 0; i < IEEE80211_NUM_ACS; i++) { |
32 | txq = sc->tx.txq_map[i]; |
33 | |
34 | ath_txq_lock(sc, txq); |
35 | if (txq->axq_depth) { |
36 | if (txq->axq_tx_inprogress) { |
37 | ath_txq_unlock(sc, txq); |
38 | goto reset; |
39 | } |
40 | |
41 | txq->axq_tx_inprogress = true; |
42 | } |
43 | ath_txq_unlock(sc, txq); |
44 | } |
45 | |
46 | return true; |
47 | |
48 | reset: |
49 | ath_dbg(ath9k_hw_common(sc->sc_ah), RESET, |
50 | "tx hung, resetting the chip\n" ); |
51 | ath9k_queue_reset(sc, type: RESET_TYPE_TX_HANG); |
52 | return false; |
53 | } |
54 | |
55 | #define RX_INACTIVE_CHECK_INTERVAL (4 * MSEC_PER_SEC) |
56 | |
57 | static bool ath_hw_rx_inactive_check(struct ath_softc *sc) |
58 | { |
59 | struct ath_common *common = ath9k_hw_common(ah: sc->sc_ah); |
60 | u32 interval, count; |
61 | |
62 | interval = jiffies_to_msecs(j: jiffies - sc->rx_active_check_time); |
63 | count = sc->rx_active_count; |
64 | |
65 | if (interval < RX_INACTIVE_CHECK_INTERVAL) |
66 | return true; /* too soon to check */ |
67 | |
68 | sc->rx_active_count = 0; |
69 | sc->rx_active_check_time = jiffies; |
70 | |
71 | /* Need at least one interrupt per second, and we should only react if |
72 | * we are within a factor two of the expected interval |
73 | */ |
74 | if (interval > RX_INACTIVE_CHECK_INTERVAL * 2 || |
75 | count >= interval / MSEC_PER_SEC) |
76 | return true; |
77 | |
78 | ath_dbg(common, RESET, |
79 | "RX inactivity detected. Schedule chip reset\n" ); |
80 | ath9k_queue_reset(sc, type: RESET_TYPE_RX_INACTIVE); |
81 | |
82 | return false; |
83 | } |
84 | |
85 | void ath_hw_check_work(struct work_struct *work) |
86 | { |
87 | struct ath_softc *sc = container_of(work, struct ath_softc, |
88 | hw_check_work.work); |
89 | |
90 | if (!ath_hw_check(sc) || !ath_tx_complete_check(sc) || |
91 | !ath_hw_rx_inactive_check(sc)) |
92 | return; |
93 | |
94 | ieee80211_queue_delayed_work(hw: sc->hw, dwork: &sc->hw_check_work, |
95 | delay: msecs_to_jiffies(ATH_HW_CHECK_POLL_INT)); |
96 | } |
97 | |
98 | /* |
99 | * Checks if the BB/MAC is hung. |
100 | */ |
101 | bool ath_hw_check(struct ath_softc *sc) |
102 | { |
103 | struct ath_common *common = ath9k_hw_common(ah: sc->sc_ah); |
104 | enum ath_reset_type type; |
105 | bool is_alive; |
106 | |
107 | ath9k_ps_wakeup(sc); |
108 | |
109 | is_alive = ath9k_hw_check_alive(ah: sc->sc_ah); |
110 | |
111 | if (!is_alive) { |
112 | ath_dbg(common, RESET, |
113 | "HW hang detected, schedule chip reset\n" ); |
114 | type = RESET_TYPE_MAC_HANG; |
115 | ath9k_queue_reset(sc, type); |
116 | } |
117 | |
118 | ath9k_ps_restore(sc); |
119 | |
120 | return is_alive; |
121 | } |
122 | |
123 | /* |
124 | * PLL-WAR for AR9485/AR9340 |
125 | */ |
126 | static bool ath_hw_pll_rx_hang_check(struct ath_softc *sc, u32 pll_sqsum) |
127 | { |
128 | static int count; |
129 | struct ath_common *common = ath9k_hw_common(ah: sc->sc_ah); |
130 | |
131 | if (pll_sqsum >= 0x40000) { |
132 | count++; |
133 | if (count == 3) { |
134 | ath_dbg(common, RESET, "PLL WAR, resetting the chip\n" ); |
135 | ath9k_queue_reset(sc, type: RESET_TYPE_PLL_HANG); |
136 | count = 0; |
137 | return true; |
138 | } |
139 | } else { |
140 | count = 0; |
141 | } |
142 | |
143 | return false; |
144 | } |
145 | |
146 | void ath_hw_pll_work(struct work_struct *work) |
147 | { |
148 | u32 pll_sqsum; |
149 | struct ath_softc *sc = container_of(work, struct ath_softc, |
150 | hw_pll_work.work); |
151 | struct ath_common *common = ath9k_hw_common(ah: sc->sc_ah); |
152 | /* |
153 | * ensure that the PLL WAR is executed only |
154 | * after the STA is associated (or) if the |
155 | * beaconing had started in interfaces that |
156 | * uses beacons. |
157 | */ |
158 | if (!test_bit(ATH_OP_BEACONS, &common->op_flags)) |
159 | return; |
160 | |
161 | if (sc->tx99_state) |
162 | return; |
163 | |
164 | ath9k_ps_wakeup(sc); |
165 | pll_sqsum = ar9003_get_pll_sqsum_dvc(ah: sc->sc_ah); |
166 | ath9k_ps_restore(sc); |
167 | if (ath_hw_pll_rx_hang_check(sc, pll_sqsum)) |
168 | return; |
169 | |
170 | ieee80211_queue_delayed_work(hw: sc->hw, dwork: &sc->hw_pll_work, |
171 | delay: msecs_to_jiffies(ATH_PLL_WORK_INTERVAL)); |
172 | } |
173 | |
174 | /* |
175 | * PA Pre-distortion. |
176 | */ |
177 | static void ath_paprd_activate(struct ath_softc *sc) |
178 | { |
179 | struct ath_hw *ah = sc->sc_ah; |
180 | struct ath_common *common = ath9k_hw_common(ah); |
181 | struct ath9k_hw_cal_data *caldata = ah->caldata; |
182 | int chain; |
183 | |
184 | if (!caldata || !test_bit(PAPRD_DONE, &caldata->cal_flags)) { |
185 | ath_dbg(common, CALIBRATE, "Failed to activate PAPRD\n" ); |
186 | return; |
187 | } |
188 | |
189 | ar9003_paprd_enable(ah, val: false); |
190 | for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) { |
191 | if (!(ah->txchainmask & BIT(chain))) |
192 | continue; |
193 | |
194 | ar9003_paprd_populate_single_table(ah, caldata, chain); |
195 | } |
196 | |
197 | ath_dbg(common, CALIBRATE, "Activating PAPRD\n" ); |
198 | ar9003_paprd_enable(ah, val: true); |
199 | } |
200 | |
201 | static bool ath_paprd_send_frame(struct ath_softc *sc, struct sk_buff *skb, int chain) |
202 | { |
203 | struct ieee80211_hw *hw = sc->hw; |
204 | struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); |
205 | struct ath_hw *ah = sc->sc_ah; |
206 | struct ath_common *common = ath9k_hw_common(ah); |
207 | struct ath_tx_control txctl; |
208 | unsigned long time_left; |
209 | |
210 | memset(&txctl, 0, sizeof(txctl)); |
211 | txctl.txq = sc->tx.txq_map[IEEE80211_AC_BE]; |
212 | |
213 | memset(tx_info, 0, sizeof(*tx_info)); |
214 | tx_info->band = sc->cur_chandef.chan->band; |
215 | tx_info->flags |= IEEE80211_TX_CTL_NO_ACK; |
216 | tx_info->control.rates[0].idx = 0; |
217 | tx_info->control.rates[0].count = 1; |
218 | tx_info->control.rates[0].flags = IEEE80211_TX_RC_MCS; |
219 | tx_info->control.rates[1].idx = -1; |
220 | |
221 | init_completion(x: &sc->paprd_complete); |
222 | txctl.paprd = BIT(chain); |
223 | |
224 | if (ath_tx_start(hw, skb, txctl: &txctl) != 0) { |
225 | ath_dbg(common, CALIBRATE, "PAPRD TX failed\n" ); |
226 | dev_kfree_skb_any(skb); |
227 | return false; |
228 | } |
229 | |
230 | time_left = wait_for_completion_timeout(x: &sc->paprd_complete, |
231 | timeout: msecs_to_jiffies(ATH_PAPRD_TIMEOUT)); |
232 | |
233 | if (!time_left) |
234 | ath_dbg(common, CALIBRATE, |
235 | "Timeout waiting for paprd training on TX chain %d\n" , |
236 | chain); |
237 | |
238 | return !!time_left; |
239 | } |
240 | |
241 | void ath_paprd_calibrate(struct work_struct *work) |
242 | { |
243 | struct ath_softc *sc = container_of(work, struct ath_softc, paprd_work); |
244 | struct ieee80211_hw *hw = sc->hw; |
245 | struct ath_hw *ah = sc->sc_ah; |
246 | struct ieee80211_hdr *hdr; |
247 | struct sk_buff *skb = NULL; |
248 | struct ath9k_hw_cal_data *caldata = ah->caldata; |
249 | struct ath_common *common = ath9k_hw_common(ah); |
250 | int ftype; |
251 | int chain_ok = 0; |
252 | int chain; |
253 | int len = 1800; |
254 | int ret; |
255 | |
256 | if (!caldata || |
257 | !test_bit(PAPRD_PACKET_SENT, &caldata->cal_flags) || |
258 | test_bit(PAPRD_DONE, &caldata->cal_flags)) { |
259 | ath_dbg(common, CALIBRATE, "Skipping PAPRD calibration\n" ); |
260 | return; |
261 | } |
262 | |
263 | ath9k_ps_wakeup(sc); |
264 | |
265 | if (ar9003_paprd_init_table(ah) < 0) |
266 | goto fail_paprd; |
267 | |
268 | skb = alloc_skb(size: len, GFP_KERNEL); |
269 | if (!skb) |
270 | goto fail_paprd; |
271 | |
272 | skb_put(skb, len); |
273 | memset(skb->data, 0, len); |
274 | hdr = (struct ieee80211_hdr *)skb->data; |
275 | ftype = IEEE80211_FTYPE_DATA | IEEE80211_STYPE_NULLFUNC; |
276 | hdr->frame_control = cpu_to_le16(ftype); |
277 | hdr->duration_id = cpu_to_le16(10); |
278 | memcpy(hdr->addr1, hw->wiphy->perm_addr, ETH_ALEN); |
279 | memcpy(hdr->addr2, hw->wiphy->perm_addr, ETH_ALEN); |
280 | memcpy(hdr->addr3, hw->wiphy->perm_addr, ETH_ALEN); |
281 | |
282 | for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) { |
283 | if (!(ah->txchainmask & BIT(chain))) |
284 | continue; |
285 | |
286 | chain_ok = 0; |
287 | ar9003_paprd_setup_gain_table(ah, chain); |
288 | |
289 | ath_dbg(common, CALIBRATE, |
290 | "Sending PAPRD training frame on chain %d\n" , chain); |
291 | if (!ath_paprd_send_frame(sc, skb, chain)) |
292 | goto fail_paprd; |
293 | |
294 | if (!ar9003_paprd_is_done(ah)) { |
295 | ath_dbg(common, CALIBRATE, |
296 | "PAPRD not yet done on chain %d\n" , chain); |
297 | break; |
298 | } |
299 | |
300 | ret = ar9003_paprd_create_curve(ah, caldata, chain); |
301 | if (ret == -EINPROGRESS) { |
302 | ath_dbg(common, CALIBRATE, |
303 | "PAPRD curve on chain %d needs to be re-trained\n" , |
304 | chain); |
305 | break; |
306 | } else if (ret) { |
307 | ath_dbg(common, CALIBRATE, |
308 | "PAPRD create curve failed on chain %d\n" , |
309 | chain); |
310 | break; |
311 | } |
312 | |
313 | chain_ok = 1; |
314 | } |
315 | kfree_skb(skb); |
316 | |
317 | if (chain_ok) { |
318 | set_bit(nr: PAPRD_DONE, addr: &caldata->cal_flags); |
319 | ath_paprd_activate(sc); |
320 | } |
321 | |
322 | fail_paprd: |
323 | ath9k_ps_restore(sc); |
324 | } |
325 | |
326 | /* |
327 | * ANI performs periodic noise floor calibration |
328 | * that is used to adjust and optimize the chip performance. This |
329 | * takes environmental changes (location, temperature) into account. |
330 | * When the task is complete, it reschedules itself depending on the |
331 | * appropriate interval that was calculated. |
332 | */ |
333 | void ath_ani_calibrate(struct timer_list *t) |
334 | { |
335 | struct ath_common *common = timer_container_of(common, t, ani.timer); |
336 | struct ath_softc *sc = common->priv; |
337 | struct ath_hw *ah = sc->sc_ah; |
338 | bool longcal = false; |
339 | bool shortcal = false; |
340 | bool aniflag = false; |
341 | unsigned int timestamp = jiffies_to_msecs(j: jiffies); |
342 | u32 cal_interval, short_cal_interval, long_cal_interval; |
343 | unsigned long flags; |
344 | |
345 | if (ah->caldata && test_bit(NFCAL_INTF, &ah->caldata->cal_flags)) |
346 | long_cal_interval = ATH_LONG_CALINTERVAL_INT; |
347 | else |
348 | long_cal_interval = ATH_LONG_CALINTERVAL; |
349 | |
350 | short_cal_interval = (ah->opmode == NL80211_IFTYPE_AP) ? |
351 | ATH_AP_SHORT_CALINTERVAL : ATH_STA_SHORT_CALINTERVAL; |
352 | |
353 | /* Only calibrate if awake */ |
354 | if (sc->sc_ah->power_mode != ATH9K_PM_AWAKE) { |
355 | if (++ah->ani_skip_count >= ATH_ANI_MAX_SKIP_COUNT) { |
356 | spin_lock_irqsave(&sc->sc_pm_lock, flags); |
357 | sc->ps_flags |= PS_WAIT_FOR_ANI; |
358 | spin_unlock_irqrestore(lock: &sc->sc_pm_lock, flags); |
359 | } |
360 | goto set_timer; |
361 | } |
362 | ah->ani_skip_count = 0; |
363 | spin_lock_irqsave(&sc->sc_pm_lock, flags); |
364 | sc->ps_flags &= ~PS_WAIT_FOR_ANI; |
365 | spin_unlock_irqrestore(lock: &sc->sc_pm_lock, flags); |
366 | |
367 | ath9k_ps_wakeup(sc); |
368 | |
369 | /* Long calibration runs independently of short calibration. */ |
370 | if ((timestamp - common->ani.longcal_timer) >= long_cal_interval) { |
371 | longcal = true; |
372 | common->ani.longcal_timer = timestamp; |
373 | } |
374 | |
375 | /* Short calibration applies only while caldone is false */ |
376 | if (!common->ani.caldone) { |
377 | if ((timestamp - common->ani.shortcal_timer) >= short_cal_interval) { |
378 | shortcal = true; |
379 | common->ani.shortcal_timer = timestamp; |
380 | common->ani.resetcal_timer = timestamp; |
381 | } |
382 | } else { |
383 | if ((timestamp - common->ani.resetcal_timer) >= |
384 | ATH_RESTART_CALINTERVAL) { |
385 | common->ani.caldone = ath9k_hw_reset_calvalid(ah); |
386 | if (common->ani.caldone) |
387 | common->ani.resetcal_timer = timestamp; |
388 | } |
389 | } |
390 | |
391 | /* Verify whether we must check ANI */ |
392 | if ((timestamp - common->ani.checkani_timer) >= ah->config.ani_poll_interval) { |
393 | aniflag = true; |
394 | common->ani.checkani_timer = timestamp; |
395 | } |
396 | |
397 | /* Call ANI routine if necessary */ |
398 | if (aniflag) { |
399 | spin_lock_irqsave(&common->cc_lock, flags); |
400 | ath9k_hw_ani_monitor(ah, chan: ah->curchan); |
401 | ath_update_survey_stats(sc); |
402 | spin_unlock_irqrestore(lock: &common->cc_lock, flags); |
403 | } |
404 | |
405 | /* Perform calibration if necessary */ |
406 | if (longcal || shortcal) { |
407 | int ret = ath9k_hw_calibrate(ah, chan: ah->curchan, rxchainmask: ah->rxchainmask, |
408 | longcal); |
409 | if (ret < 0) { |
410 | common->ani.caldone = 0; |
411 | ath9k_queue_reset(sc, type: RESET_TYPE_CALIBRATION); |
412 | return; |
413 | } |
414 | |
415 | common->ani.caldone = ret; |
416 | } |
417 | |
418 | ath_dbg(common, ANI, |
419 | "Calibration @%lu finished: %s %s %s, caldone: %s\n" , |
420 | jiffies, |
421 | longcal ? "long" : "" , shortcal ? "short" : "" , |
422 | aniflag ? "ani" : "" , common->ani.caldone ? "true" : "false" ); |
423 | |
424 | ath9k_ps_restore(sc); |
425 | |
426 | set_timer: |
427 | /* |
428 | * Set timer interval based on previous results. |
429 | * The interval must be the shortest necessary to satisfy ANI, |
430 | * short calibration and long calibration. |
431 | */ |
432 | cal_interval = ATH_LONG_CALINTERVAL; |
433 | cal_interval = min(cal_interval, (u32)ah->config.ani_poll_interval); |
434 | if (!common->ani.caldone) |
435 | cal_interval = min(cal_interval, (u32)short_cal_interval); |
436 | |
437 | mod_timer(timer: &common->ani.timer, expires: jiffies + msecs_to_jiffies(m: cal_interval)); |
438 | |
439 | if (ar9003_is_paprd_enabled(ah) && ah->caldata) { |
440 | if (!test_bit(PAPRD_DONE, &ah->caldata->cal_flags)) { |
441 | ieee80211_queue_work(hw: sc->hw, work: &sc->paprd_work); |
442 | } else if (!ah->paprd_table_write_done) { |
443 | ath9k_ps_wakeup(sc); |
444 | ath_paprd_activate(sc); |
445 | ath9k_ps_restore(sc); |
446 | } |
447 | } |
448 | } |
449 | |
450 | void ath_start_ani(struct ath_softc *sc) |
451 | { |
452 | struct ath_hw *ah = sc->sc_ah; |
453 | struct ath_common *common = ath9k_hw_common(ah); |
454 | unsigned long timestamp = jiffies_to_msecs(j: jiffies); |
455 | |
456 | if (common->disable_ani || |
457 | !test_bit(ATH_OP_ANI_RUN, &common->op_flags) || |
458 | sc->cur_chan->offchannel) |
459 | return; |
460 | |
461 | common->ani.longcal_timer = timestamp; |
462 | common->ani.shortcal_timer = timestamp; |
463 | common->ani.checkani_timer = timestamp; |
464 | |
465 | ath_dbg(common, ANI, "Starting ANI\n" ); |
466 | mod_timer(timer: &common->ani.timer, |
467 | expires: jiffies + msecs_to_jiffies(m: (u32)ah->config.ani_poll_interval)); |
468 | } |
469 | |
470 | void ath_stop_ani(struct ath_softc *sc) |
471 | { |
472 | struct ath_common *common = ath9k_hw_common(ah: sc->sc_ah); |
473 | |
474 | ath_dbg(common, ANI, "Stopping ANI\n" ); |
475 | timer_delete_sync(timer: &common->ani.timer); |
476 | } |
477 | |
478 | void ath_check_ani(struct ath_softc *sc) |
479 | { |
480 | struct ath_hw *ah = sc->sc_ah; |
481 | struct ath_common *common = ath9k_hw_common(ah: sc->sc_ah); |
482 | struct ath_beacon_config *cur_conf = &sc->cur_chan->beacon; |
483 | |
484 | /* |
485 | * Check for the various conditions in which ANI has to |
486 | * be stopped. |
487 | */ |
488 | if (ah->opmode == NL80211_IFTYPE_ADHOC) { |
489 | if (!cur_conf->enable_beacon) |
490 | goto stop_ani; |
491 | } else if (ah->opmode == NL80211_IFTYPE_AP) { |
492 | if (!cur_conf->enable_beacon) { |
493 | /* |
494 | * Disable ANI only when there are no |
495 | * associated stations. |
496 | */ |
497 | if (!test_bit(ATH_OP_PRIM_STA_VIF, &common->op_flags)) |
498 | goto stop_ani; |
499 | } |
500 | } else if (ah->opmode == NL80211_IFTYPE_STATION) { |
501 | if (!test_bit(ATH_OP_PRIM_STA_VIF, &common->op_flags)) |
502 | goto stop_ani; |
503 | } |
504 | |
505 | if (!test_bit(ATH_OP_ANI_RUN, &common->op_flags)) { |
506 | set_bit(nr: ATH_OP_ANI_RUN, addr: &common->op_flags); |
507 | ath_start_ani(sc); |
508 | } |
509 | |
510 | return; |
511 | |
512 | stop_ani: |
513 | clear_bit(nr: ATH_OP_ANI_RUN, addr: &common->op_flags); |
514 | ath_stop_ani(sc); |
515 | } |
516 | |
517 | void ath_update_survey_nf(struct ath_softc *sc, int channel) |
518 | { |
519 | struct ath_hw *ah = sc->sc_ah; |
520 | struct ath9k_channel *chan = &ah->channels[channel]; |
521 | struct survey_info *survey = &sc->survey[channel]; |
522 | |
523 | if (chan->noisefloor) { |
524 | survey->filled |= SURVEY_INFO_NOISE_DBM; |
525 | survey->noise = ath9k_hw_getchan_noise(ah, chan, |
526 | nf: chan->noisefloor); |
527 | } |
528 | } |
529 | |
530 | /* |
531 | * Updates the survey statistics and returns the busy time since last |
532 | * update in %, if the measurement duration was long enough for the |
533 | * result to be useful, -1 otherwise. |
534 | */ |
535 | int ath_update_survey_stats(struct ath_softc *sc) |
536 | { |
537 | struct ath_hw *ah = sc->sc_ah; |
538 | struct ath_common *common = ath9k_hw_common(ah); |
539 | int pos = ah->curchan - &ah->channels[0]; |
540 | struct survey_info *survey = &sc->survey[pos]; |
541 | struct ath_cycle_counters *cc = &common->cc_survey; |
542 | unsigned int div = common->clockrate * 1000; |
543 | int ret = 0; |
544 | |
545 | if (!ah->curchan) |
546 | return -1; |
547 | |
548 | if (ah->power_mode == ATH9K_PM_AWAKE) |
549 | ath_hw_cycle_counters_update(common); |
550 | |
551 | if (cc->cycles > 0) { |
552 | survey->filled |= SURVEY_INFO_TIME | |
553 | SURVEY_INFO_TIME_BUSY | |
554 | SURVEY_INFO_TIME_RX | |
555 | SURVEY_INFO_TIME_TX; |
556 | survey->time += cc->cycles / div; |
557 | survey->time_busy += cc->rx_busy / div; |
558 | survey->time_rx += cc->rx_frame / div; |
559 | survey->time_tx += cc->tx_frame / div; |
560 | } |
561 | |
562 | if (cc->cycles < div) |
563 | return -1; |
564 | |
565 | if (cc->cycles > 0) |
566 | ret = cc->rx_busy * 100 / cc->cycles; |
567 | |
568 | memset(cc, 0, sizeof(*cc)); |
569 | |
570 | ath_update_survey_nf(sc, channel: pos); |
571 | |
572 | return ret; |
573 | } |
574 | |