]>
Commit | Line | Data |
---|---|---|
1e1f4ad2 SM |
1 | /* |
2 | * Copyright (c) 2010 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 | ||
21cb9879 VN |
17 | #include "htc.h" |
18 | ||
19 | /******************/ | |
20 | /* BTCOEX */ | |
21 | /******************/ | |
22 | ||
23 | /* | |
24 | * Detects if there is any priority bt traffic | |
25 | */ | |
26 | static void ath_detect_bt_priority(struct ath9k_htc_priv *priv) | |
27 | { | |
28 | struct ath_btcoex *btcoex = &priv->btcoex; | |
29 | struct ath_hw *ah = priv->ah; | |
30 | ||
31 | if (ath9k_hw_gpio_get(ah, ah->btcoex_hw.btpriority_gpio)) | |
32 | btcoex->bt_priority_cnt++; | |
33 | ||
34 | if (time_after(jiffies, btcoex->bt_priority_time + | |
35 | msecs_to_jiffies(ATH_BT_PRIORITY_TIME_THRESHOLD))) { | |
36 | priv->op_flags &= ~(OP_BT_PRIORITY_DETECTED | OP_BT_SCAN); | |
37 | /* Detect if colocated bt started scanning */ | |
38 | if (btcoex->bt_priority_cnt >= ATH_BT_CNT_SCAN_THRESHOLD) { | |
226afe68 JP |
39 | ath_dbg(ath9k_hw_common(ah), ATH_DBG_BTCOEX, |
40 | "BT scan detected\n"); | |
21cb9879 VN |
41 | priv->op_flags |= (OP_BT_SCAN | |
42 | OP_BT_PRIORITY_DETECTED); | |
43 | } else if (btcoex->bt_priority_cnt >= ATH_BT_CNT_THRESHOLD) { | |
226afe68 JP |
44 | ath_dbg(ath9k_hw_common(ah), ATH_DBG_BTCOEX, |
45 | "BT priority traffic detected\n"); | |
21cb9879 VN |
46 | priv->op_flags |= OP_BT_PRIORITY_DETECTED; |
47 | } | |
48 | ||
49 | btcoex->bt_priority_cnt = 0; | |
50 | btcoex->bt_priority_time = jiffies; | |
51 | } | |
52 | } | |
53 | ||
54 | /* | |
55 | * This is the master bt coex work which runs for every | |
56 | * 45ms, bt traffic will be given priority during 55% of this | |
57 | * period while wlan gets remaining 45% | |
58 | */ | |
59 | static void ath_btcoex_period_work(struct work_struct *work) | |
60 | { | |
61 | struct ath9k_htc_priv *priv = container_of(work, struct ath9k_htc_priv, | |
62 | coex_period_work.work); | |
63 | struct ath_btcoex *btcoex = &priv->btcoex; | |
64 | struct ath_common *common = ath9k_hw_common(priv->ah); | |
65 | u32 timer_period; | |
66 | bool is_btscan; | |
67 | int ret; | |
21cb9879 VN |
68 | |
69 | ath_detect_bt_priority(priv); | |
70 | ||
71 | is_btscan = !!(priv->op_flags & OP_BT_SCAN); | |
72 | ||
3a0593ef SM |
73 | ret = ath9k_htc_update_cap_target(priv, |
74 | !!(priv->op_flags & OP_BT_PRIORITY_DETECTED)); | |
0ff2b5c0 SM |
75 | if (ret) { |
76 | ath_err(common, "Unable to set BTCOEX parameters\n"); | |
77 | return; | |
78 | } | |
21cb9879 | 79 | |
978f78bf | 80 | ath9k_hw_btcoex_bt_stomp(priv->ah, is_btscan ? ATH_BTCOEX_STOMP_ALL : |
21cb9879 VN |
81 | btcoex->bt_stomp_type); |
82 | ||
83 | timer_period = is_btscan ? btcoex->btscan_no_stomp : | |
84 | btcoex->btcoex_no_stomp; | |
85 | ieee80211_queue_delayed_work(priv->hw, &priv->duty_cycle_work, | |
86 | msecs_to_jiffies(timer_period)); | |
87 | ieee80211_queue_delayed_work(priv->hw, &priv->coex_period_work, | |
88 | msecs_to_jiffies(btcoex->btcoex_period)); | |
89 | } | |
90 | ||
91 | /* | |
92 | * Work to time slice between wlan and bt traffic and | |
93 | * configure weight registers | |
94 | */ | |
95 | static void ath_btcoex_duty_cycle_work(struct work_struct *work) | |
96 | { | |
97 | struct ath9k_htc_priv *priv = container_of(work, struct ath9k_htc_priv, | |
98 | duty_cycle_work.work); | |
99 | struct ath_hw *ah = priv->ah; | |
100 | struct ath_btcoex *btcoex = &priv->btcoex; | |
101 | struct ath_common *common = ath9k_hw_common(ah); | |
102 | bool is_btscan = priv->op_flags & OP_BT_SCAN; | |
103 | ||
226afe68 JP |
104 | ath_dbg(common, ATH_DBG_BTCOEX, |
105 | "time slice work for bt and wlan\n"); | |
21cb9879 VN |
106 | |
107 | if (btcoex->bt_stomp_type == ATH_BTCOEX_STOMP_LOW || is_btscan) | |
978f78bf | 108 | ath9k_hw_btcoex_bt_stomp(ah, ATH_BTCOEX_STOMP_NONE); |
21cb9879 | 109 | else if (btcoex->bt_stomp_type == ATH_BTCOEX_STOMP_ALL) |
978f78bf | 110 | ath9k_hw_btcoex_bt_stomp(ah, ATH_BTCOEX_STOMP_LOW); |
21cb9879 VN |
111 | } |
112 | ||
113 | void ath_htc_init_btcoex_work(struct ath9k_htc_priv *priv) | |
114 | { | |
115 | struct ath_btcoex *btcoex = &priv->btcoex; | |
116 | ||
117 | btcoex->btcoex_period = ATH_BTCOEX_DEF_BT_PERIOD; | |
118 | btcoex->btcoex_no_stomp = (100 - ATH_BTCOEX_DEF_DUTY_CYCLE) * | |
119 | btcoex->btcoex_period / 100; | |
120 | btcoex->btscan_no_stomp = (100 - ATH_BTCOEX_BTSCAN_DUTY_CYCLE) * | |
121 | btcoex->btcoex_period / 100; | |
122 | INIT_DELAYED_WORK(&priv->coex_period_work, ath_btcoex_period_work); | |
123 | INIT_DELAYED_WORK(&priv->duty_cycle_work, ath_btcoex_duty_cycle_work); | |
124 | } | |
125 | ||
126 | /* | |
127 | * (Re)start btcoex work | |
128 | */ | |
129 | ||
130 | void ath_htc_resume_btcoex_work(struct ath9k_htc_priv *priv) | |
131 | { | |
132 | struct ath_btcoex *btcoex = &priv->btcoex; | |
133 | struct ath_hw *ah = priv->ah; | |
134 | ||
226afe68 | 135 | ath_dbg(ath9k_hw_common(ah), ATH_DBG_BTCOEX, "Starting btcoex work\n"); |
21cb9879 VN |
136 | |
137 | btcoex->bt_priority_cnt = 0; | |
138 | btcoex->bt_priority_time = jiffies; | |
139 | priv->op_flags &= ~(OP_BT_PRIORITY_DETECTED | OP_BT_SCAN); | |
140 | ieee80211_queue_delayed_work(priv->hw, &priv->coex_period_work, 0); | |
141 | } | |
142 | ||
143 | ||
144 | /* | |
145 | * Cancel btcoex and bt duty cycle work. | |
146 | */ | |
147 | void ath_htc_cancel_btcoex_work(struct ath9k_htc_priv *priv) | |
148 | { | |
149 | cancel_delayed_work_sync(&priv->coex_period_work); | |
150 | cancel_delayed_work_sync(&priv->duty_cycle_work); | |
151 | } | |
1e1f4ad2 SM |
152 | |
153 | /*******/ | |
154 | /* LED */ | |
155 | /*******/ | |
156 | ||
157 | static void ath9k_led_blink_work(struct work_struct *work) | |
158 | { | |
159 | struct ath9k_htc_priv *priv = container_of(work, struct ath9k_htc_priv, | |
160 | ath9k_led_blink_work.work); | |
161 | ||
162 | if (!(priv->op_flags & OP_LED_ASSOCIATED)) | |
163 | return; | |
164 | ||
165 | if ((priv->led_on_duration == ATH_LED_ON_DURATION_IDLE) || | |
166 | (priv->led_off_duration == ATH_LED_OFF_DURATION_IDLE)) | |
167 | ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin, 0); | |
168 | else | |
169 | ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin, | |
170 | (priv->op_flags & OP_LED_ON) ? 1 : 0); | |
171 | ||
172 | ieee80211_queue_delayed_work(priv->hw, | |
173 | &priv->ath9k_led_blink_work, | |
174 | (priv->op_flags & OP_LED_ON) ? | |
175 | msecs_to_jiffies(priv->led_off_duration) : | |
176 | msecs_to_jiffies(priv->led_on_duration)); | |
177 | ||
178 | priv->led_on_duration = priv->led_on_cnt ? | |
179 | max((ATH_LED_ON_DURATION_IDLE - priv->led_on_cnt), 25) : | |
180 | ATH_LED_ON_DURATION_IDLE; | |
181 | priv->led_off_duration = priv->led_off_cnt ? | |
182 | max((ATH_LED_OFF_DURATION_IDLE - priv->led_off_cnt), 10) : | |
183 | ATH_LED_OFF_DURATION_IDLE; | |
184 | priv->led_on_cnt = priv->led_off_cnt = 0; | |
185 | ||
186 | if (priv->op_flags & OP_LED_ON) | |
187 | priv->op_flags &= ~OP_LED_ON; | |
188 | else | |
189 | priv->op_flags |= OP_LED_ON; | |
190 | } | |
191 | ||
192 | static void ath9k_led_brightness_work(struct work_struct *work) | |
193 | { | |
194 | struct ath_led *led = container_of(work, struct ath_led, | |
195 | brightness_work.work); | |
196 | struct ath9k_htc_priv *priv = led->priv; | |
197 | ||
198 | switch (led->brightness) { | |
199 | case LED_OFF: | |
200 | if (led->led_type == ATH_LED_ASSOC || | |
201 | led->led_type == ATH_LED_RADIO) { | |
202 | ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin, | |
203 | (led->led_type == ATH_LED_RADIO)); | |
204 | priv->op_flags &= ~OP_LED_ASSOCIATED; | |
205 | if (led->led_type == ATH_LED_RADIO) | |
206 | priv->op_flags &= ~OP_LED_ON; | |
207 | } else { | |
208 | priv->led_off_cnt++; | |
209 | } | |
210 | break; | |
211 | case LED_FULL: | |
212 | if (led->led_type == ATH_LED_ASSOC) { | |
213 | priv->op_flags |= OP_LED_ASSOCIATED; | |
214 | ieee80211_queue_delayed_work(priv->hw, | |
215 | &priv->ath9k_led_blink_work, 0); | |
216 | } else if (led->led_type == ATH_LED_RADIO) { | |
217 | ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin, 0); | |
218 | priv->op_flags |= OP_LED_ON; | |
219 | } else { | |
220 | priv->led_on_cnt++; | |
221 | } | |
222 | break; | |
223 | default: | |
224 | break; | |
225 | } | |
226 | } | |
227 | ||
228 | static void ath9k_led_brightness(struct led_classdev *led_cdev, | |
229 | enum led_brightness brightness) | |
230 | { | |
231 | struct ath_led *led = container_of(led_cdev, struct ath_led, led_cdev); | |
232 | struct ath9k_htc_priv *priv = led->priv; | |
233 | ||
234 | led->brightness = brightness; | |
235 | if (!(priv->op_flags & OP_LED_DEINIT)) | |
236 | ieee80211_queue_delayed_work(priv->hw, | |
237 | &led->brightness_work, 0); | |
238 | } | |
239 | ||
240 | void ath9k_led_stop_brightness(struct ath9k_htc_priv *priv) | |
241 | { | |
242 | cancel_delayed_work_sync(&priv->radio_led.brightness_work); | |
243 | cancel_delayed_work_sync(&priv->assoc_led.brightness_work); | |
244 | cancel_delayed_work_sync(&priv->tx_led.brightness_work); | |
245 | cancel_delayed_work_sync(&priv->rx_led.brightness_work); | |
246 | } | |
247 | ||
248 | static int ath9k_register_led(struct ath9k_htc_priv *priv, struct ath_led *led, | |
249 | char *trigger) | |
250 | { | |
251 | int ret; | |
252 | ||
253 | led->priv = priv; | |
254 | led->led_cdev.name = led->name; | |
255 | led->led_cdev.default_trigger = trigger; | |
256 | led->led_cdev.brightness_set = ath9k_led_brightness; | |
257 | ||
258 | ret = led_classdev_register(wiphy_dev(priv->hw->wiphy), &led->led_cdev); | |
259 | if (ret) | |
260 | ath_err(ath9k_hw_common(priv->ah), | |
261 | "Failed to register led:%s", led->name); | |
262 | else | |
263 | led->registered = 1; | |
264 | ||
265 | INIT_DELAYED_WORK(&led->brightness_work, ath9k_led_brightness_work); | |
266 | ||
267 | return ret; | |
268 | } | |
269 | ||
270 | static void ath9k_unregister_led(struct ath_led *led) | |
271 | { | |
272 | if (led->registered) { | |
273 | led_classdev_unregister(&led->led_cdev); | |
274 | led->registered = 0; | |
275 | } | |
276 | } | |
277 | ||
278 | void ath9k_deinit_leds(struct ath9k_htc_priv *priv) | |
279 | { | |
280 | priv->op_flags |= OP_LED_DEINIT; | |
281 | ath9k_unregister_led(&priv->assoc_led); | |
282 | priv->op_flags &= ~OP_LED_ASSOCIATED; | |
283 | ath9k_unregister_led(&priv->tx_led); | |
284 | ath9k_unregister_led(&priv->rx_led); | |
285 | ath9k_unregister_led(&priv->radio_led); | |
286 | } | |
287 | ||
288 | void ath9k_init_leds(struct ath9k_htc_priv *priv) | |
289 | { | |
290 | char *trigger; | |
291 | int ret; | |
292 | ||
293 | if (AR_SREV_9287(priv->ah)) | |
294 | priv->ah->led_pin = ATH_LED_PIN_9287; | |
295 | else if (AR_SREV_9271(priv->ah)) | |
296 | priv->ah->led_pin = ATH_LED_PIN_9271; | |
297 | else if (AR_DEVID_7010(priv->ah)) | |
298 | priv->ah->led_pin = ATH_LED_PIN_7010; | |
299 | else | |
300 | priv->ah->led_pin = ATH_LED_PIN_DEF; | |
301 | ||
302 | /* Configure gpio 1 for output */ | |
303 | ath9k_hw_cfg_output(priv->ah, priv->ah->led_pin, | |
304 | AR_GPIO_OUTPUT_MUX_AS_OUTPUT); | |
305 | /* LED off, active low */ | |
306 | ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin, 1); | |
307 | ||
308 | INIT_DELAYED_WORK(&priv->ath9k_led_blink_work, ath9k_led_blink_work); | |
309 | ||
310 | trigger = ieee80211_get_radio_led_name(priv->hw); | |
311 | snprintf(priv->radio_led.name, sizeof(priv->radio_led.name), | |
312 | "ath9k-%s::radio", wiphy_name(priv->hw->wiphy)); | |
313 | ret = ath9k_register_led(priv, &priv->radio_led, trigger); | |
314 | priv->radio_led.led_type = ATH_LED_RADIO; | |
315 | if (ret) | |
316 | goto fail; | |
317 | ||
318 | trigger = ieee80211_get_assoc_led_name(priv->hw); | |
319 | snprintf(priv->assoc_led.name, sizeof(priv->assoc_led.name), | |
320 | "ath9k-%s::assoc", wiphy_name(priv->hw->wiphy)); | |
321 | ret = ath9k_register_led(priv, &priv->assoc_led, trigger); | |
322 | priv->assoc_led.led_type = ATH_LED_ASSOC; | |
323 | if (ret) | |
324 | goto fail; | |
325 | ||
326 | trigger = ieee80211_get_tx_led_name(priv->hw); | |
327 | snprintf(priv->tx_led.name, sizeof(priv->tx_led.name), | |
328 | "ath9k-%s::tx", wiphy_name(priv->hw->wiphy)); | |
329 | ret = ath9k_register_led(priv, &priv->tx_led, trigger); | |
330 | priv->tx_led.led_type = ATH_LED_TX; | |
331 | if (ret) | |
332 | goto fail; | |
333 | ||
334 | trigger = ieee80211_get_rx_led_name(priv->hw); | |
335 | snprintf(priv->rx_led.name, sizeof(priv->rx_led.name), | |
336 | "ath9k-%s::rx", wiphy_name(priv->hw->wiphy)); | |
337 | ret = ath9k_register_led(priv, &priv->rx_led, trigger); | |
338 | priv->rx_led.led_type = ATH_LED_RX; | |
339 | if (ret) | |
340 | goto fail; | |
341 | ||
342 | priv->op_flags &= ~OP_LED_DEINIT; | |
343 | ||
344 | return; | |
345 | ||
346 | fail: | |
347 | cancel_delayed_work_sync(&priv->ath9k_led_blink_work); | |
348 | ath9k_deinit_leds(priv); | |
349 | } | |
350 | ||
351 | /*******************/ | |
352 | /* Rfkill */ | |
353 | /*******************/ | |
354 | ||
355 | static bool ath_is_rfkill_set(struct ath9k_htc_priv *priv) | |
356 | { | |
357 | return ath9k_hw_gpio_get(priv->ah, priv->ah->rfkill_gpio) == | |
358 | priv->ah->rfkill_polarity; | |
359 | } | |
360 | ||
361 | void ath9k_htc_rfkill_poll_state(struct ieee80211_hw *hw) | |
362 | { | |
363 | struct ath9k_htc_priv *priv = hw->priv; | |
364 | bool blocked = !!ath_is_rfkill_set(priv); | |
365 | ||
366 | wiphy_rfkill_set_hw_state(hw->wiphy, blocked); | |
367 | } | |
368 | ||
369 | void ath9k_start_rfkill_poll(struct ath9k_htc_priv *priv) | |
370 | { | |
371 | if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT) | |
372 | wiphy_rfkill_start_polling(priv->hw->wiphy); | |
373 | } | |
374 | ||
375 | void ath9k_htc_radio_enable(struct ieee80211_hw *hw) | |
376 | { | |
377 | struct ath9k_htc_priv *priv = hw->priv; | |
378 | struct ath_hw *ah = priv->ah; | |
379 | struct ath_common *common = ath9k_hw_common(ah); | |
380 | int ret; | |
381 | u8 cmd_rsp; | |
382 | ||
383 | if (!ah->curchan) | |
384 | ah->curchan = ath9k_cmn_get_curchannel(hw, ah); | |
385 | ||
386 | /* Reset the HW */ | |
387 | ret = ath9k_hw_reset(ah, ah->curchan, ah->caldata, false); | |
388 | if (ret) { | |
389 | ath_err(common, | |
390 | "Unable to reset hardware; reset status %d (freq %u MHz)\n", | |
391 | ret, ah->curchan->channel); | |
392 | } | |
393 | ||
b2a5c3df RM |
394 | ath9k_cmn_update_txpow(ah, priv->curtxpow, priv->txpowlimit, |
395 | &priv->curtxpow); | |
1e1f4ad2 SM |
396 | |
397 | /* Start RX */ | |
398 | WMI_CMD(WMI_START_RECV_CMDID); | |
399 | ath9k_host_rx_init(priv); | |
400 | ||
401 | /* Start TX */ | |
402 | htc_start(priv->htc); | |
658ef04f | 403 | spin_lock_bh(&priv->tx.tx_lock); |
8e86a547 | 404 | priv->tx.flags &= ~ATH9K_HTC_OP_TX_QUEUES_STOP; |
658ef04f | 405 | spin_unlock_bh(&priv->tx.tx_lock); |
1e1f4ad2 SM |
406 | ieee80211_wake_queues(hw); |
407 | ||
408 | WMI_CMD(WMI_ENABLE_INTR_CMDID); | |
409 | ||
410 | /* Enable LED */ | |
411 | ath9k_hw_cfg_output(ah, ah->led_pin, | |
412 | AR_GPIO_OUTPUT_MUX_AS_OUTPUT); | |
413 | ath9k_hw_set_gpio(ah, ah->led_pin, 0); | |
414 | } | |
415 | ||
416 | void ath9k_htc_radio_disable(struct ieee80211_hw *hw) | |
417 | { | |
418 | struct ath9k_htc_priv *priv = hw->priv; | |
419 | struct ath_hw *ah = priv->ah; | |
420 | struct ath_common *common = ath9k_hw_common(ah); | |
421 | int ret; | |
422 | u8 cmd_rsp; | |
423 | ||
424 | ath9k_htc_ps_wakeup(priv); | |
425 | ||
426 | /* Disable LED */ | |
427 | ath9k_hw_set_gpio(ah, ah->led_pin, 1); | |
428 | ath9k_hw_cfg_gpio_input(ah, ah->led_pin); | |
429 | ||
430 | WMI_CMD(WMI_DISABLE_INTR_CMDID); | |
431 | ||
432 | /* Stop TX */ | |
433 | ieee80211_stop_queues(hw); | |
b587fc81 | 434 | ath9k_htc_tx_drain(priv); |
1e1f4ad2 | 435 | WMI_CMD(WMI_DRAIN_TXQ_ALL_CMDID); |
1e1f4ad2 SM |
436 | |
437 | /* Stop RX */ | |
438 | WMI_CMD(WMI_STOP_RECV_CMDID); | |
439 | ||
f4c88991 SM |
440 | /* Clear the WMI event queue */ |
441 | ath9k_wmi_event_drain(priv); | |
442 | ||
1e1f4ad2 SM |
443 | /* |
444 | * The MIB counters have to be disabled here, | |
445 | * since the target doesn't do it. | |
446 | */ | |
447 | ath9k_hw_disable_mib_counters(ah); | |
448 | ||
449 | if (!ah->curchan) | |
450 | ah->curchan = ath9k_cmn_get_curchannel(hw, ah); | |
451 | ||
452 | /* Reset the HW */ | |
453 | ret = ath9k_hw_reset(ah, ah->curchan, ah->caldata, false); | |
454 | if (ret) { | |
455 | ath_err(common, | |
456 | "Unable to reset hardware; reset status %d (freq %u MHz)\n", | |
457 | ret, ah->curchan->channel); | |
458 | } | |
459 | ||
460 | /* Disable the PHY */ | |
461 | ath9k_hw_phy_disable(ah); | |
462 | ||
463 | ath9k_htc_ps_restore(priv); | |
464 | ath9k_htc_setpower(priv, ATH9K_PM_FULL_SLEEP); | |
465 | } |