Commit | Line | Data |
---|---|---|
1e1f4ad2 | 1 | /* |
5b68138e | 2 | * Copyright (c) 2010-2011 Atheros Communications Inc. |
1e1f4ad2 SM |
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 | ||
d244f21e SM |
157 | #ifdef CONFIG_MAC80211_LEDS |
158 | void ath9k_led_work(struct work_struct *work) | |
1e1f4ad2 | 159 | { |
d244f21e SM |
160 | struct ath9k_htc_priv *priv = container_of(work, |
161 | struct ath9k_htc_priv, | |
162 | led_work); | |
1e1f4ad2 | 163 | |
d244f21e SM |
164 | ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin, |
165 | (priv->brightness == LED_OFF)); | |
1e1f4ad2 SM |
166 | } |
167 | ||
168 | static void ath9k_led_brightness(struct led_classdev *led_cdev, | |
169 | enum led_brightness brightness) | |
170 | { | |
d244f21e SM |
171 | struct ath9k_htc_priv *priv = container_of(led_cdev, |
172 | struct ath9k_htc_priv, | |
173 | led_cdev); | |
1e1f4ad2 | 174 | |
d244f21e SM |
175 | /* Not locked, but it's just a tiny green light..*/ |
176 | priv->brightness = brightness; | |
177 | ieee80211_queue_work(priv->hw, &priv->led_work); | |
1e1f4ad2 SM |
178 | } |
179 | ||
180 | void ath9k_deinit_leds(struct ath9k_htc_priv *priv) | |
181 | { | |
d244f21e SM |
182 | if (!priv->led_registered) |
183 | return; | |
184 | ||
185 | ath9k_led_brightness(&priv->led_cdev, LED_OFF); | |
186 | led_classdev_unregister(&priv->led_cdev); | |
187 | cancel_work_sync(&priv->led_work); | |
1e1f4ad2 SM |
188 | } |
189 | ||
190 | void ath9k_init_leds(struct ath9k_htc_priv *priv) | |
191 | { | |
1e1f4ad2 SM |
192 | int ret; |
193 | ||
194 | if (AR_SREV_9287(priv->ah)) | |
195 | priv->ah->led_pin = ATH_LED_PIN_9287; | |
196 | else if (AR_SREV_9271(priv->ah)) | |
197 | priv->ah->led_pin = ATH_LED_PIN_9271; | |
198 | else if (AR_DEVID_7010(priv->ah)) | |
199 | priv->ah->led_pin = ATH_LED_PIN_7010; | |
200 | else | |
201 | priv->ah->led_pin = ATH_LED_PIN_DEF; | |
202 | ||
203 | /* Configure gpio 1 for output */ | |
204 | ath9k_hw_cfg_output(priv->ah, priv->ah->led_pin, | |
205 | AR_GPIO_OUTPUT_MUX_AS_OUTPUT); | |
206 | /* LED off, active low */ | |
207 | ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin, 1); | |
208 | ||
d244f21e SM |
209 | snprintf(priv->led_name, sizeof(priv->led_name), |
210 | "ath9k_htc-%s", wiphy_name(priv->hw->wiphy)); | |
211 | priv->led_cdev.name = priv->led_name; | |
212 | priv->led_cdev.brightness_set = ath9k_led_brightness; | |
1e1f4ad2 | 213 | |
d244f21e SM |
214 | ret = led_classdev_register(wiphy_dev(priv->hw->wiphy), &priv->led_cdev); |
215 | if (ret < 0) | |
216 | return; | |
1e1f4ad2 | 217 | |
d244f21e SM |
218 | INIT_WORK(&priv->led_work, ath9k_led_work); |
219 | priv->led_registered = true; | |
220 | ||
221 | return; | |
1e1f4ad2 | 222 | } |
d244f21e | 223 | #endif |
1e1f4ad2 SM |
224 | |
225 | /*******************/ | |
226 | /* Rfkill */ | |
227 | /*******************/ | |
228 | ||
229 | static bool ath_is_rfkill_set(struct ath9k_htc_priv *priv) | |
230 | { | |
90826313 MSS |
231 | bool is_blocked; |
232 | ||
233 | ath9k_htc_ps_wakeup(priv); | |
234 | is_blocked = ath9k_hw_gpio_get(priv->ah, priv->ah->rfkill_gpio) == | |
235 | priv->ah->rfkill_polarity; | |
236 | ath9k_htc_ps_restore(priv); | |
237 | ||
238 | return is_blocked; | |
1e1f4ad2 SM |
239 | } |
240 | ||
241 | void ath9k_htc_rfkill_poll_state(struct ieee80211_hw *hw) | |
242 | { | |
243 | struct ath9k_htc_priv *priv = hw->priv; | |
244 | bool blocked = !!ath_is_rfkill_set(priv); | |
245 | ||
246 | wiphy_rfkill_set_hw_state(hw->wiphy, blocked); | |
247 | } | |
248 | ||
249 | void ath9k_start_rfkill_poll(struct ath9k_htc_priv *priv) | |
250 | { | |
251 | if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT) | |
252 | wiphy_rfkill_start_polling(priv->hw->wiphy); | |
253 | } | |
254 | ||
255 | void ath9k_htc_radio_enable(struct ieee80211_hw *hw) | |
256 | { | |
257 | struct ath9k_htc_priv *priv = hw->priv; | |
258 | struct ath_hw *ah = priv->ah; | |
259 | struct ath_common *common = ath9k_hw_common(ah); | |
260 | int ret; | |
261 | u8 cmd_rsp; | |
262 | ||
263 | if (!ah->curchan) | |
264 | ah->curchan = ath9k_cmn_get_curchannel(hw, ah); | |
265 | ||
266 | /* Reset the HW */ | |
267 | ret = ath9k_hw_reset(ah, ah->curchan, ah->caldata, false); | |
268 | if (ret) { | |
269 | ath_err(common, | |
270 | "Unable to reset hardware; reset status %d (freq %u MHz)\n", | |
271 | ret, ah->curchan->channel); | |
272 | } | |
273 | ||
b2a5c3df RM |
274 | ath9k_cmn_update_txpow(ah, priv->curtxpow, priv->txpowlimit, |
275 | &priv->curtxpow); | |
1e1f4ad2 SM |
276 | |
277 | /* Start RX */ | |
278 | WMI_CMD(WMI_START_RECV_CMDID); | |
279 | ath9k_host_rx_init(priv); | |
280 | ||
281 | /* Start TX */ | |
282 | htc_start(priv->htc); | |
658ef04f | 283 | spin_lock_bh(&priv->tx.tx_lock); |
8e86a547 | 284 | priv->tx.flags &= ~ATH9K_HTC_OP_TX_QUEUES_STOP; |
658ef04f | 285 | spin_unlock_bh(&priv->tx.tx_lock); |
1e1f4ad2 SM |
286 | ieee80211_wake_queues(hw); |
287 | ||
288 | WMI_CMD(WMI_ENABLE_INTR_CMDID); | |
289 | ||
290 | /* Enable LED */ | |
291 | ath9k_hw_cfg_output(ah, ah->led_pin, | |
292 | AR_GPIO_OUTPUT_MUX_AS_OUTPUT); | |
293 | ath9k_hw_set_gpio(ah, ah->led_pin, 0); | |
294 | } | |
295 | ||
296 | void ath9k_htc_radio_disable(struct ieee80211_hw *hw) | |
297 | { | |
298 | struct ath9k_htc_priv *priv = hw->priv; | |
299 | struct ath_hw *ah = priv->ah; | |
300 | struct ath_common *common = ath9k_hw_common(ah); | |
301 | int ret; | |
302 | u8 cmd_rsp; | |
303 | ||
304 | ath9k_htc_ps_wakeup(priv); | |
305 | ||
306 | /* Disable LED */ | |
307 | ath9k_hw_set_gpio(ah, ah->led_pin, 1); | |
308 | ath9k_hw_cfg_gpio_input(ah, ah->led_pin); | |
309 | ||
310 | WMI_CMD(WMI_DISABLE_INTR_CMDID); | |
311 | ||
312 | /* Stop TX */ | |
313 | ieee80211_stop_queues(hw); | |
b587fc81 | 314 | ath9k_htc_tx_drain(priv); |
1e1f4ad2 | 315 | WMI_CMD(WMI_DRAIN_TXQ_ALL_CMDID); |
1e1f4ad2 SM |
316 | |
317 | /* Stop RX */ | |
318 | WMI_CMD(WMI_STOP_RECV_CMDID); | |
319 | ||
f4c88991 SM |
320 | /* Clear the WMI event queue */ |
321 | ath9k_wmi_event_drain(priv); | |
322 | ||
1e1f4ad2 SM |
323 | /* |
324 | * The MIB counters have to be disabled here, | |
325 | * since the target doesn't do it. | |
326 | */ | |
327 | ath9k_hw_disable_mib_counters(ah); | |
328 | ||
329 | if (!ah->curchan) | |
330 | ah->curchan = ath9k_cmn_get_curchannel(hw, ah); | |
331 | ||
332 | /* Reset the HW */ | |
333 | ret = ath9k_hw_reset(ah, ah->curchan, ah->caldata, false); | |
334 | if (ret) { | |
335 | ath_err(common, | |
336 | "Unable to reset hardware; reset status %d (freq %u MHz)\n", | |
337 | ret, ah->curchan->channel); | |
338 | } | |
339 | ||
340 | /* Disable the PHY */ | |
341 | ath9k_hw_phy_disable(ah); | |
342 | ||
343 | ath9k_htc_ps_restore(priv); | |
344 | ath9k_htc_setpower(priv, ATH9K_PM_FULL_SLEEP); | |
345 | } |