1 | /* |
2 | * Copyright (c) 2010-2011 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 | |
17 | #include "htc.h" |
18 | |
19 | /******************/ |
20 | /* BTCOEX */ |
21 | /******************/ |
22 | |
23 | #define ATH_HTC_BTCOEX_PRODUCT_ID "wb193" |
24 | |
25 | #ifdef CONFIG_ATH9K_BTCOEX_SUPPORT |
26 | |
27 | /* |
28 | * Detects if there is any priority bt traffic |
29 | */ |
30 | static void ath_detect_bt_priority(struct ath9k_htc_priv *priv) |
31 | { |
32 | struct ath_btcoex *btcoex = &priv->btcoex; |
33 | struct ath_hw *ah = priv->ah; |
34 | |
35 | if (ath9k_hw_gpio_get(ah, gpio: ah->btcoex_hw.btpriority_gpio)) |
36 | btcoex->bt_priority_cnt++; |
37 | |
38 | if (time_after(jiffies, btcoex->bt_priority_time + |
39 | msecs_to_jiffies(ATH_BT_PRIORITY_TIME_THRESHOLD))) { |
40 | clear_bit(OP_BT_PRIORITY_DETECTED, addr: &priv->op_flags); |
41 | clear_bit(OP_BT_SCAN, addr: &priv->op_flags); |
42 | /* Detect if colocated bt started scanning */ |
43 | if (btcoex->bt_priority_cnt >= ATH_BT_CNT_SCAN_THRESHOLD) { |
44 | ath_dbg(ath9k_hw_common(ah), BTCOEX, |
45 | "BT scan detected\n" ); |
46 | set_bit(OP_BT_PRIORITY_DETECTED, addr: &priv->op_flags); |
47 | set_bit(OP_BT_SCAN, addr: &priv->op_flags); |
48 | } else if (btcoex->bt_priority_cnt >= ATH_BT_CNT_THRESHOLD) { |
49 | ath_dbg(ath9k_hw_common(ah), BTCOEX, |
50 | "BT priority traffic detected\n" ); |
51 | set_bit(OP_BT_PRIORITY_DETECTED, addr: &priv->op_flags); |
52 | } |
53 | |
54 | btcoex->bt_priority_cnt = 0; |
55 | btcoex->bt_priority_time = jiffies; |
56 | } |
57 | } |
58 | |
59 | /* |
60 | * This is the master bt coex work which runs for every |
61 | * 45ms, bt traffic will be given priority during 55% of this |
62 | * period while wlan gets remaining 45% |
63 | */ |
64 | static void ath_btcoex_period_work(struct work_struct *work) |
65 | { |
66 | struct ath9k_htc_priv *priv = container_of(work, struct ath9k_htc_priv, |
67 | coex_period_work.work); |
68 | struct ath_btcoex *btcoex = &priv->btcoex; |
69 | struct ath_common *common = ath9k_hw_common(ah: priv->ah); |
70 | u32 timer_period; |
71 | int ret; |
72 | |
73 | ath_detect_bt_priority(priv); |
74 | |
75 | ret = ath9k_htc_update_cap_target(priv, |
76 | test_bit(OP_BT_PRIORITY_DETECTED, &priv->op_flags)); |
77 | if (ret) { |
78 | ath_err(common, "Unable to set BTCOEX parameters\n" ); |
79 | return; |
80 | } |
81 | |
82 | ath9k_hw_btcoex_bt_stomp(ah: priv->ah, test_bit(OP_BT_SCAN, &priv->op_flags) ? |
83 | ATH_BTCOEX_STOMP_ALL : btcoex->bt_stomp_type); |
84 | |
85 | ath9k_hw_btcoex_enable(ah: priv->ah); |
86 | timer_period = test_bit(OP_BT_SCAN, &priv->op_flags) ? |
87 | btcoex->btscan_no_stomp : btcoex->btcoex_no_stomp; |
88 | ieee80211_queue_delayed_work(hw: priv->hw, dwork: &priv->duty_cycle_work, |
89 | delay: msecs_to_jiffies(m: timer_period)); |
90 | ieee80211_queue_delayed_work(hw: priv->hw, dwork: &priv->coex_period_work, |
91 | delay: msecs_to_jiffies(m: btcoex->btcoex_period)); |
92 | } |
93 | |
94 | /* |
95 | * Work to time slice between wlan and bt traffic and |
96 | * configure weight registers |
97 | */ |
98 | static void ath_btcoex_duty_cycle_work(struct work_struct *work) |
99 | { |
100 | struct ath9k_htc_priv *priv = container_of(work, struct ath9k_htc_priv, |
101 | duty_cycle_work.work); |
102 | struct ath_hw *ah = priv->ah; |
103 | struct ath_btcoex *btcoex = &priv->btcoex; |
104 | struct ath_common *common = ath9k_hw_common(ah); |
105 | |
106 | ath_dbg(common, BTCOEX, "time slice work for bt and wlan\n" ); |
107 | |
108 | if (btcoex->bt_stomp_type == ATH_BTCOEX_STOMP_LOW || |
109 | test_bit(OP_BT_SCAN, &priv->op_flags)) |
110 | ath9k_hw_btcoex_bt_stomp(ah, stomp_type: ATH_BTCOEX_STOMP_NONE); |
111 | else if (btcoex->bt_stomp_type == ATH_BTCOEX_STOMP_ALL) |
112 | ath9k_hw_btcoex_bt_stomp(ah, stomp_type: ATH_BTCOEX_STOMP_LOW); |
113 | |
114 | ath9k_hw_btcoex_enable(ah: priv->ah); |
115 | } |
116 | |
117 | static void ath_htc_init_btcoex_work(struct ath9k_htc_priv *priv) |
118 | { |
119 | struct ath_btcoex *btcoex = &priv->btcoex; |
120 | |
121 | btcoex->btcoex_period = ATH_BTCOEX_DEF_BT_PERIOD; |
122 | btcoex->btcoex_no_stomp = (100 - ATH_BTCOEX_DEF_DUTY_CYCLE) * |
123 | btcoex->btcoex_period / 100; |
124 | btcoex->btscan_no_stomp = (100 - ATH_BTCOEX_BTSCAN_DUTY_CYCLE) * |
125 | btcoex->btcoex_period / 100; |
126 | INIT_DELAYED_WORK(&priv->coex_period_work, ath_btcoex_period_work); |
127 | INIT_DELAYED_WORK(&priv->duty_cycle_work, ath_btcoex_duty_cycle_work); |
128 | } |
129 | |
130 | /* |
131 | * (Re)start btcoex work |
132 | */ |
133 | |
134 | static void ath_htc_resume_btcoex_work(struct ath9k_htc_priv *priv) |
135 | { |
136 | struct ath_btcoex *btcoex = &priv->btcoex; |
137 | struct ath_hw *ah = priv->ah; |
138 | |
139 | ath_dbg(ath9k_hw_common(ah), BTCOEX, "Starting btcoex work\n" ); |
140 | |
141 | btcoex->bt_priority_cnt = 0; |
142 | btcoex->bt_priority_time = jiffies; |
143 | clear_bit(OP_BT_PRIORITY_DETECTED, addr: &priv->op_flags); |
144 | clear_bit(OP_BT_SCAN, addr: &priv->op_flags); |
145 | ieee80211_queue_delayed_work(hw: priv->hw, dwork: &priv->coex_period_work, delay: 0); |
146 | } |
147 | |
148 | |
149 | /* |
150 | * Cancel btcoex and bt duty cycle work. |
151 | */ |
152 | static void ath_htc_cancel_btcoex_work(struct ath9k_htc_priv *priv) |
153 | { |
154 | cancel_delayed_work_sync(dwork: &priv->coex_period_work); |
155 | cancel_delayed_work_sync(dwork: &priv->duty_cycle_work); |
156 | } |
157 | |
158 | void ath9k_htc_start_btcoex(struct ath9k_htc_priv *priv) |
159 | { |
160 | struct ath_hw *ah = priv->ah; |
161 | |
162 | if (ath9k_hw_get_btcoex_scheme(ah) == ATH_BTCOEX_CFG_3WIRE) { |
163 | ath9k_hw_btcoex_set_weight(ah, AR_BT_COEX_WGHT, |
164 | AR_STOMP_LOW_WLAN_WGHT, stomp_type: 0); |
165 | ath9k_hw_btcoex_enable(ah); |
166 | ath_htc_resume_btcoex_work(priv); |
167 | } |
168 | } |
169 | |
170 | void ath9k_htc_stop_btcoex(struct ath9k_htc_priv *priv) |
171 | { |
172 | struct ath_hw *ah = priv->ah; |
173 | |
174 | if (ah->btcoex_hw.enabled && |
175 | ath9k_hw_get_btcoex_scheme(ah) != ATH_BTCOEX_CFG_NONE) { |
176 | if (ah->btcoex_hw.scheme == ATH_BTCOEX_CFG_3WIRE) |
177 | ath_htc_cancel_btcoex_work(priv); |
178 | ath9k_hw_btcoex_disable(ah); |
179 | } |
180 | } |
181 | |
182 | void ath9k_htc_init_btcoex(struct ath9k_htc_priv *priv, char *product) |
183 | { |
184 | struct ath_hw *ah = priv->ah; |
185 | struct ath_common *common = ath9k_hw_common(ah); |
186 | int qnum; |
187 | |
188 | /* |
189 | * Check if BTCOEX is globally disabled. |
190 | */ |
191 | if (!common->btcoex_enabled) { |
192 | ah->btcoex_hw.scheme = ATH_BTCOEX_CFG_NONE; |
193 | return; |
194 | } |
195 | |
196 | if (product && strncmp(product, ATH_HTC_BTCOEX_PRODUCT_ID, 5) == 0) { |
197 | ah->btcoex_hw.scheme = ATH_BTCOEX_CFG_3WIRE; |
198 | } |
199 | |
200 | switch (ath9k_hw_get_btcoex_scheme(ah: priv->ah)) { |
201 | case ATH_BTCOEX_CFG_NONE: |
202 | break; |
203 | case ATH_BTCOEX_CFG_3WIRE: |
204 | priv->ah->btcoex_hw.btactive_gpio = 7; |
205 | priv->ah->btcoex_hw.btpriority_gpio = 6; |
206 | priv->ah->btcoex_hw.wlanactive_gpio = 8; |
207 | priv->btcoex.bt_stomp_type = ATH_BTCOEX_STOMP_LOW; |
208 | ath9k_hw_btcoex_init_3wire(ah: priv->ah); |
209 | ath_htc_init_btcoex_work(priv); |
210 | qnum = priv->hwq_map[IEEE80211_AC_BE]; |
211 | ath9k_hw_init_btcoex_hw(ah: priv->ah, qnum); |
212 | break; |
213 | default: |
214 | WARN_ON(1); |
215 | break; |
216 | } |
217 | } |
218 | |
219 | #endif /* CONFIG_ATH9K_BTCOEX_SUPPORT */ |
220 | |
221 | /*******/ |
222 | /* LED */ |
223 | /*******/ |
224 | |
225 | #ifdef CONFIG_MAC80211_LEDS |
226 | void ath9k_led_work(struct work_struct *work) |
227 | { |
228 | struct ath9k_htc_priv *priv = container_of(work, |
229 | struct ath9k_htc_priv, |
230 | led_work); |
231 | |
232 | ath9k_hw_set_gpio(ah: priv->ah, gpio: priv->ah->led_pin, |
233 | val: (priv->brightness == LED_OFF)); |
234 | } |
235 | |
236 | static void ath9k_led_brightness(struct led_classdev *led_cdev, |
237 | enum led_brightness brightness) |
238 | { |
239 | struct ath9k_htc_priv *priv = container_of(led_cdev, |
240 | struct ath9k_htc_priv, |
241 | led_cdev); |
242 | |
243 | /* Not locked, but it's just a tiny green light..*/ |
244 | priv->brightness = brightness; |
245 | ieee80211_queue_work(hw: priv->hw, work: &priv->led_work); |
246 | } |
247 | |
248 | void ath9k_deinit_leds(struct ath9k_htc_priv *priv) |
249 | { |
250 | if (!priv->led_registered) |
251 | return; |
252 | |
253 | ath9k_led_brightness(led_cdev: &priv->led_cdev, brightness: LED_OFF); |
254 | led_classdev_unregister(led_cdev: &priv->led_cdev); |
255 | cancel_work_sync(work: &priv->led_work); |
256 | |
257 | ath9k_hw_gpio_free(ah: priv->ah, gpio: priv->ah->led_pin); |
258 | } |
259 | |
260 | |
261 | void ath9k_configure_leds(struct ath9k_htc_priv *priv) |
262 | { |
263 | /* Configure gpio 1 for output */ |
264 | ath9k_hw_gpio_request_out(ah: priv->ah, gpio: priv->ah->led_pin, |
265 | label: "ath9k-led" , |
266 | AR_GPIO_OUTPUT_MUX_AS_OUTPUT); |
267 | /* LED off, active low */ |
268 | ath9k_hw_set_gpio(ah: priv->ah, gpio: priv->ah->led_pin, val: 1); |
269 | } |
270 | |
271 | void ath9k_init_leds(struct ath9k_htc_priv *priv) |
272 | { |
273 | int ret; |
274 | |
275 | if (AR_SREV_9287(priv->ah)) |
276 | priv->ah->led_pin = ATH_LED_PIN_9287; |
277 | else if (AR_SREV_9271(priv->ah)) |
278 | priv->ah->led_pin = ATH_LED_PIN_9271; |
279 | else if (AR_DEVID_7010(priv->ah)) |
280 | priv->ah->led_pin = ATH_LED_PIN_7010; |
281 | else |
282 | priv->ah->led_pin = ATH_LED_PIN_DEF; |
283 | |
284 | if (!ath9k_htc_led_blink) |
285 | priv->led_cdev.default_trigger = |
286 | ieee80211_get_radio_led_name(hw: priv->hw); |
287 | |
288 | ath9k_configure_leds(priv); |
289 | |
290 | snprintf(buf: priv->led_name, size: sizeof(priv->led_name), |
291 | fmt: "ath9k_htc-%s" , wiphy_name(wiphy: priv->hw->wiphy)); |
292 | priv->led_cdev.name = priv->led_name; |
293 | priv->led_cdev.brightness_set = ath9k_led_brightness; |
294 | |
295 | ret = led_classdev_register(parent: wiphy_dev(wiphy: priv->hw->wiphy), led_cdev: &priv->led_cdev); |
296 | if (ret < 0) |
297 | return; |
298 | |
299 | INIT_WORK(&priv->led_work, ath9k_led_work); |
300 | priv->led_registered = true; |
301 | |
302 | return; |
303 | } |
304 | #endif |
305 | |
306 | /*******************/ |
307 | /* Rfkill */ |
308 | /*******************/ |
309 | |
310 | static bool ath_is_rfkill_set(struct ath9k_htc_priv *priv) |
311 | { |
312 | bool is_blocked; |
313 | |
314 | ath9k_htc_ps_wakeup(priv); |
315 | is_blocked = ath9k_hw_gpio_get(ah: priv->ah, gpio: priv->ah->rfkill_gpio) == |
316 | priv->ah->rfkill_polarity; |
317 | ath9k_htc_ps_restore(priv); |
318 | |
319 | return is_blocked; |
320 | } |
321 | |
322 | void ath9k_htc_rfkill_poll_state(struct ieee80211_hw *hw) |
323 | { |
324 | struct ath9k_htc_priv *priv = hw->priv; |
325 | bool blocked = !!ath_is_rfkill_set(priv); |
326 | |
327 | wiphy_rfkill_set_hw_state(wiphy: hw->wiphy, blocked); |
328 | } |
329 | |
330 | void ath9k_start_rfkill_poll(struct ath9k_htc_priv *priv) |
331 | { |
332 | if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT) |
333 | wiphy_rfkill_start_polling(wiphy: priv->hw->wiphy); |
334 | } |
335 | |