Commit | Line | Data |
---|---|---|
61730d4d PH |
1 | /* |
2 | * Copyright (c) 2013 Broadcom Corporation | |
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 ANY | |
11 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION | |
13 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |
14 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
15 | */ | |
16 | #include <linux/slab.h> | |
17 | #include <linux/netdevice.h> | |
18 | #include <net/cfg80211.h> | |
19 | ||
20 | #include <brcmu_wifi.h> | |
21 | #include <brcmu_utils.h> | |
22 | #include <defs.h> | |
23 | #include <dhd.h> | |
24 | #include <dhd_dbg.h> | |
25 | #include "fwil.h" | |
26 | #include "fwil_types.h" | |
27 | #include "btcoex.h" | |
28 | #include "p2p.h" | |
29 | #include "wl_cfg80211.h" | |
30 | ||
31 | /* T1 start SCO/eSCO priority suppression */ | |
32 | #define BRCMF_BTCOEX_OPPR_WIN_TIME 2000 | |
33 | ||
34 | /* BT registers values during DHCP */ | |
35 | #define BRCMF_BT_DHCP_REG50 0x8022 | |
36 | #define BRCMF_BT_DHCP_REG51 0 | |
37 | #define BRCMF_BT_DHCP_REG64 0 | |
38 | #define BRCMF_BT_DHCP_REG65 0 | |
39 | #define BRCMF_BT_DHCP_REG71 0 | |
40 | #define BRCMF_BT_DHCP_REG66 0x2710 | |
41 | #define BRCMF_BT_DHCP_REG41 0x33 | |
42 | #define BRCMF_BT_DHCP_REG68 0x190 | |
43 | ||
44 | /* number of samples for SCO detection */ | |
45 | #define BRCMF_BT_SCO_SAMPLES 12 | |
46 | ||
47 | /** | |
48 | * enum brcmf_btcoex_state - BT coex DHCP state machine states | |
49 | * @BRCMF_BT_DHCP_IDLE: DCHP is idle | |
50 | * @BRCMF_BT_DHCP_START: DHCP started, wait before | |
51 | * boosting wifi priority | |
52 | * @BRCMF_BT_DHCP_OPPR_WIN: graceful DHCP opportunity ended, | |
53 | * boost wifi priority | |
54 | * @BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT: wifi priority boost end, | |
55 | * restore defaults | |
56 | */ | |
57 | enum brcmf_btcoex_state { | |
58 | BRCMF_BT_DHCP_IDLE, | |
59 | BRCMF_BT_DHCP_START, | |
60 | BRCMF_BT_DHCP_OPPR_WIN, | |
61 | BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT | |
62 | }; | |
63 | ||
64 | /** | |
65 | * struct brcmf_btcoex_info - BT coex related information | |
66 | * @vif: interface for which request was done. | |
67 | * @timer: timer for DHCP state machine | |
68 | * @timeout: configured timeout. | |
69 | * @timer_on: DHCP timer active | |
70 | * @dhcp_done: DHCP finished before T1/T2 timer expiration | |
71 | * @bt_state: DHCP state machine state | |
72 | * @work: DHCP state machine work | |
73 | * @cfg: driver private data for cfg80211 interface | |
74 | * @reg66: saved value of btc_params 66 | |
75 | * @reg41: saved value of btc_params 41 | |
76 | * @reg68: saved value of btc_params 68 | |
77 | * @saved_regs_part1: flag indicating regs 66,41,68 | |
78 | * have been saved | |
79 | * @reg51: saved value of btc_params 51 | |
80 | * @reg64: saved value of btc_params 64 | |
81 | * @reg65: saved value of btc_params 65 | |
82 | * @reg71: saved value of btc_params 71 | |
83 | * @saved_regs_part1: flag indicating regs 50,51,64,65,71 | |
84 | * have been saved | |
85 | */ | |
86 | struct brcmf_btcoex_info { | |
87 | struct brcmf_cfg80211_vif *vif; | |
88 | struct timer_list timer; | |
89 | u16 timeout; | |
90 | bool timer_on; | |
91 | bool dhcp_done; | |
92 | enum brcmf_btcoex_state bt_state; | |
93 | struct work_struct work; | |
94 | struct brcmf_cfg80211_info *cfg; | |
95 | u32 reg66; | |
96 | u32 reg41; | |
97 | u32 reg68; | |
98 | bool saved_regs_part1; | |
99 | u32 reg50; | |
100 | u32 reg51; | |
101 | u32 reg64; | |
102 | u32 reg65; | |
103 | u32 reg71; | |
104 | bool saved_regs_part2; | |
105 | }; | |
106 | ||
107 | /** | |
108 | * brcmf_btcoex_params_write() - write btc_params firmware variable | |
109 | * @ifp: interface | |
110 | * @addr: btc_params register number | |
111 | * @data: data to write | |
112 | */ | |
113 | static s32 brcmf_btcoex_params_write(struct brcmf_if *ifp, u32 addr, u32 data) | |
114 | { | |
115 | struct { | |
116 | __le32 addr; | |
117 | __le32 data; | |
118 | } reg_write; | |
119 | ||
120 | reg_write.addr = cpu_to_le32(addr); | |
121 | reg_write.data = cpu_to_le32(data); | |
122 | return brcmf_fil_iovar_data_set(ifp, "btc_params", | |
123 | ®_write, sizeof(reg_write)); | |
124 | } | |
125 | ||
126 | /** | |
127 | * brcmf_btcoex_params_read() - read btc_params firmware variable | |
128 | * @ifp: interface | |
129 | * @addr: btc_params register number | |
130 | * @data: read data | |
131 | */ | |
132 | static s32 brcmf_btcoex_params_read(struct brcmf_if *ifp, u32 addr, u32 *data) | |
133 | { | |
134 | *data = addr; | |
135 | ||
136 | return brcmf_fil_iovar_int_get(ifp, "btc_params", data); | |
137 | } | |
138 | ||
139 | /** | |
140 | * brcmf_btcoex_boost_wifi() - control BT SCO/eSCO parameters | |
141 | * @btci: BT coex info | |
142 | * @trump_sco: | |
143 | * true - set SCO/eSCO parameters for compatibility | |
144 | * during DHCP window | |
145 | * false - restore saved parameter values | |
146 | * | |
147 | * Enhanced BT COEX settings for eSCO compatibility during DHCP window | |
148 | */ | |
149 | static void brcmf_btcoex_boost_wifi(struct brcmf_btcoex_info *btci, | |
150 | bool trump_sco) | |
151 | { | |
152 | struct brcmf_if *ifp = btci->cfg->pub->iflist[0]; | |
153 | ||
154 | if (trump_sco && !btci->saved_regs_part2) { | |
155 | /* this should reduce eSCO agressive | |
156 | * retransmit w/o breaking it | |
157 | */ | |
158 | ||
159 | /* save current */ | |
160 | brcmf_dbg(TRACE, "new SCO/eSCO coex algo {save & override}\n"); | |
161 | brcmf_btcoex_params_read(ifp, 50, &btci->reg50); | |
162 | brcmf_btcoex_params_read(ifp, 51, &btci->reg51); | |
163 | brcmf_btcoex_params_read(ifp, 64, &btci->reg64); | |
164 | brcmf_btcoex_params_read(ifp, 65, &btci->reg65); | |
165 | brcmf_btcoex_params_read(ifp, 71, &btci->reg71); | |
166 | ||
167 | btci->saved_regs_part2 = true; | |
168 | brcmf_dbg(TRACE, | |
169 | "saved bt_params[50,51,64,65,71]: 0x%x 0x%x 0x%x 0x%x 0x%x\n", | |
170 | btci->reg50, btci->reg51, btci->reg64, | |
171 | btci->reg65, btci->reg71); | |
172 | ||
173 | /* pacify the eSco */ | |
174 | brcmf_btcoex_params_write(ifp, 50, BRCMF_BT_DHCP_REG50); | |
175 | brcmf_btcoex_params_write(ifp, 51, BRCMF_BT_DHCP_REG51); | |
176 | brcmf_btcoex_params_write(ifp, 64, BRCMF_BT_DHCP_REG64); | |
177 | brcmf_btcoex_params_write(ifp, 65, BRCMF_BT_DHCP_REG65); | |
178 | brcmf_btcoex_params_write(ifp, 71, BRCMF_BT_DHCP_REG71); | |
179 | ||
180 | } else if (btci->saved_regs_part2) { | |
181 | /* restore previously saved bt params */ | |
182 | brcmf_dbg(TRACE, "Do new SCO/eSCO coex algo {restore}\n"); | |
183 | brcmf_btcoex_params_write(ifp, 50, btci->reg50); | |
184 | brcmf_btcoex_params_write(ifp, 51, btci->reg51); | |
185 | brcmf_btcoex_params_write(ifp, 64, btci->reg64); | |
186 | brcmf_btcoex_params_write(ifp, 65, btci->reg65); | |
187 | brcmf_btcoex_params_write(ifp, 71, btci->reg71); | |
188 | ||
189 | brcmf_dbg(TRACE, | |
190 | "restored bt_params[50,51,64,65,71]: 0x%x 0x%x 0x%x 0x%x 0x%x\n", | |
191 | btci->reg50, btci->reg51, btci->reg64, | |
192 | btci->reg65, btci->reg71); | |
193 | ||
194 | btci->saved_regs_part2 = false; | |
195 | } else { | |
196 | brcmf_err("attempted to restore not saved BTCOEX params\n"); | |
197 | } | |
198 | } | |
199 | ||
200 | /** | |
201 | * brcmf_btcoex_is_sco_active() - check if SCO/eSCO is active | |
202 | * @ifp: interface | |
203 | * | |
204 | * return: true if SCO/eSCO session is active | |
205 | */ | |
206 | static bool brcmf_btcoex_is_sco_active(struct brcmf_if *ifp) | |
207 | { | |
208 | int ioc_res = 0; | |
209 | bool res = false; | |
210 | int sco_id_cnt = 0; | |
211 | u32 param27; | |
212 | int i; | |
213 | ||
214 | for (i = 0; i < BRCMF_BT_SCO_SAMPLES; i++) { | |
215 | ioc_res = brcmf_btcoex_params_read(ifp, 27, ¶m27); | |
216 | ||
217 | if (ioc_res < 0) { | |
218 | brcmf_err("ioc read btc params error\n"); | |
219 | break; | |
220 | } | |
221 | ||
222 | brcmf_dbg(TRACE, "sample[%d], btc_params 27:%x\n", i, param27); | |
223 | ||
224 | if ((param27 & 0x6) == 2) { /* count both sco & esco */ | |
225 | sco_id_cnt++; | |
226 | } | |
227 | ||
228 | if (sco_id_cnt > 2) { | |
229 | brcmf_dbg(TRACE, | |
230 | "sco/esco detected, pkt id_cnt:%d samples:%d\n", | |
231 | sco_id_cnt, i); | |
232 | res = true; | |
233 | break; | |
234 | } | |
235 | } | |
236 | brcmf_dbg(TRACE, "exit: result=%d\n", res); | |
237 | return res; | |
238 | } | |
239 | ||
240 | /** | |
241 | * btcmf_btcoex_save_part1() - save first step parameters. | |
242 | */ | |
243 | static void btcmf_btcoex_save_part1(struct brcmf_btcoex_info *btci) | |
244 | { | |
245 | struct brcmf_if *ifp = btci->vif->ifp; | |
246 | ||
247 | if (!btci->saved_regs_part1) { | |
248 | /* Retrieve and save original reg value */ | |
249 | brcmf_btcoex_params_read(ifp, 66, &btci->reg66); | |
250 | brcmf_btcoex_params_read(ifp, 41, &btci->reg41); | |
251 | brcmf_btcoex_params_read(ifp, 68, &btci->reg68); | |
252 | btci->saved_regs_part1 = true; | |
253 | brcmf_dbg(TRACE, | |
254 | "saved btc_params regs (66,41,68) 0x%x 0x%x 0x%x\n", | |
255 | btci->reg66, btci->reg41, | |
256 | btci->reg68); | |
257 | } | |
258 | } | |
259 | ||
260 | /** | |
261 | * brcmf_btcoex_restore_part1() - restore first step parameters. | |
262 | */ | |
263 | static void brcmf_btcoex_restore_part1(struct brcmf_btcoex_info *btci) | |
264 | { | |
265 | struct brcmf_if *ifp; | |
266 | ||
267 | if (btci->saved_regs_part1) { | |
268 | btci->saved_regs_part1 = false; | |
269 | ifp = btci->vif->ifp; | |
270 | brcmf_btcoex_params_write(ifp, 66, btci->reg66); | |
271 | brcmf_btcoex_params_write(ifp, 41, btci->reg41); | |
272 | brcmf_btcoex_params_write(ifp, 68, btci->reg68); | |
273 | brcmf_dbg(TRACE, | |
274 | "restored btc_params regs {66,41,68} 0x%x 0x%x 0x%x\n", | |
275 | btci->reg66, btci->reg41, | |
276 | btci->reg68); | |
277 | } | |
278 | } | |
279 | ||
280 | /** | |
281 | * brcmf_btcoex_timerfunc() - BT coex timer callback | |
282 | */ | |
283 | static void brcmf_btcoex_timerfunc(ulong data) | |
284 | { | |
285 | struct brcmf_btcoex_info *bt_local = (struct brcmf_btcoex_info *)data; | |
286 | brcmf_dbg(TRACE, "enter\n"); | |
287 | ||
288 | bt_local->timer_on = false; | |
289 | schedule_work(&bt_local->work); | |
290 | } | |
291 | ||
292 | /** | |
293 | * brcmf_btcoex_handler() - BT coex state machine work handler | |
294 | * @work: work | |
295 | */ | |
296 | static void brcmf_btcoex_handler(struct work_struct *work) | |
297 | { | |
298 | struct brcmf_btcoex_info *btci; | |
299 | btci = container_of(work, struct brcmf_btcoex_info, work); | |
300 | if (btci->timer_on) { | |
301 | btci->timer_on = false; | |
302 | del_timer_sync(&btci->timer); | |
303 | } | |
304 | ||
305 | switch (btci->bt_state) { | |
306 | case BRCMF_BT_DHCP_START: | |
307 | /* DHCP started provide OPPORTUNITY window | |
308 | to get DHCP address | |
309 | */ | |
310 | brcmf_dbg(TRACE, "DHCP started\n"); | |
311 | btci->bt_state = BRCMF_BT_DHCP_OPPR_WIN; | |
312 | if (btci->timeout < BRCMF_BTCOEX_OPPR_WIN_TIME) { | |
313 | mod_timer(&btci->timer, btci->timer.expires); | |
314 | } else { | |
315 | btci->timeout -= BRCMF_BTCOEX_OPPR_WIN_TIME; | |
316 | mod_timer(&btci->timer, | |
317 | jiffies + | |
318 | msecs_to_jiffies(BRCMF_BTCOEX_OPPR_WIN_TIME)); | |
319 | } | |
320 | btci->timer_on = true; | |
321 | break; | |
322 | ||
323 | case BRCMF_BT_DHCP_OPPR_WIN: | |
324 | if (btci->dhcp_done) { | |
325 | brcmf_dbg(TRACE, "DHCP done before T1 expiration\n"); | |
326 | goto idle; | |
327 | } | |
328 | ||
329 | /* DHCP is not over yet, start lowering BT priority */ | |
330 | brcmf_dbg(TRACE, "DHCP T1:%d expired\n", | |
331 | BRCMF_BTCOEX_OPPR_WIN_TIME); | |
332 | brcmf_btcoex_boost_wifi(btci, true); | |
333 | ||
334 | btci->bt_state = BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT; | |
335 | mod_timer(&btci->timer, | |
336 | jiffies + msecs_to_jiffies(btci->timeout)); | |
337 | btci->timer_on = true; | |
338 | break; | |
339 | ||
340 | case BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT: | |
341 | if (btci->dhcp_done) | |
342 | brcmf_dbg(TRACE, "DHCP done before T2 expiration\n"); | |
343 | else | |
344 | brcmf_dbg(TRACE, "DHCP T2:%d expired\n", | |
345 | BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT); | |
346 | ||
347 | goto idle; | |
348 | ||
349 | default: | |
350 | brcmf_err("invalid state=%d !!!\n", btci->bt_state); | |
351 | goto idle; | |
352 | } | |
353 | ||
354 | return; | |
355 | ||
356 | idle: | |
357 | btci->bt_state = BRCMF_BT_DHCP_IDLE; | |
358 | btci->timer_on = false; | |
359 | brcmf_btcoex_boost_wifi(btci, false); | |
360 | cfg80211_crit_proto_stopped(&btci->vif->wdev, GFP_KERNEL); | |
361 | brcmf_btcoex_restore_part1(btci); | |
362 | btci->vif = NULL; | |
363 | } | |
364 | ||
365 | /** | |
366 | * brcmf_btcoex_attach() - initialize BT coex data | |
367 | * @cfg: driver private cfg80211 data | |
368 | * | |
369 | * return: 0 on success | |
370 | */ | |
371 | int brcmf_btcoex_attach(struct brcmf_cfg80211_info *cfg) | |
372 | { | |
373 | struct brcmf_btcoex_info *btci = NULL; | |
374 | brcmf_dbg(TRACE, "enter\n"); | |
375 | ||
376 | btci = kmalloc(sizeof(struct brcmf_btcoex_info), GFP_KERNEL); | |
377 | if (!btci) | |
378 | return -ENOMEM; | |
379 | ||
380 | btci->bt_state = BRCMF_BT_DHCP_IDLE; | |
381 | ||
382 | /* Set up timer for BT */ | |
383 | btci->timer_on = false; | |
384 | btci->timeout = BRCMF_BTCOEX_OPPR_WIN_TIME; | |
385 | init_timer(&btci->timer); | |
386 | btci->timer.data = (ulong)btci; | |
387 | btci->timer.function = brcmf_btcoex_timerfunc; | |
388 | btci->cfg = cfg; | |
389 | btci->saved_regs_part1 = false; | |
390 | btci->saved_regs_part2 = false; | |
391 | ||
392 | INIT_WORK(&btci->work, brcmf_btcoex_handler); | |
393 | ||
394 | cfg->btcoex = btci; | |
395 | return 0; | |
396 | } | |
397 | ||
398 | /** | |
399 | * brcmf_btcoex_detach - clean BT coex data | |
400 | * @cfg: driver private cfg80211 data | |
401 | */ | |
402 | void brcmf_btcoex_detach(struct brcmf_cfg80211_info *cfg) | |
403 | { | |
404 | brcmf_dbg(TRACE, "enter\n"); | |
405 | ||
406 | if (!cfg->btcoex) | |
407 | return; | |
408 | ||
409 | if (cfg->btcoex->timer_on) { | |
410 | cfg->btcoex->timer_on = false; | |
411 | del_timer_sync(&cfg->btcoex->timer); | |
412 | } | |
413 | ||
414 | cancel_work_sync(&cfg->btcoex->work); | |
415 | ||
416 | brcmf_btcoex_boost_wifi(cfg->btcoex, false); | |
417 | brcmf_btcoex_restore_part1(cfg->btcoex); | |
418 | ||
419 | kfree(cfg->btcoex); | |
420 | cfg->btcoex = NULL; | |
421 | } | |
422 | ||
423 | static void brcmf_btcoex_dhcp_start(struct brcmf_btcoex_info *btci) | |
424 | { | |
425 | struct brcmf_if *ifp = btci->vif->ifp; | |
426 | ||
427 | btcmf_btcoex_save_part1(btci); | |
428 | /* set new regs values */ | |
429 | brcmf_btcoex_params_write(ifp, 66, BRCMF_BT_DHCP_REG66); | |
430 | brcmf_btcoex_params_write(ifp, 41, BRCMF_BT_DHCP_REG41); | |
431 | brcmf_btcoex_params_write(ifp, 68, BRCMF_BT_DHCP_REG68); | |
432 | btci->dhcp_done = false; | |
433 | btci->bt_state = BRCMF_BT_DHCP_START; | |
434 | schedule_work(&btci->work); | |
435 | brcmf_dbg(TRACE, "enable BT DHCP Timer\n"); | |
436 | } | |
437 | ||
438 | static void brcmf_btcoex_dhcp_end(struct brcmf_btcoex_info *btci) | |
439 | { | |
440 | /* Stop any bt timer because DHCP session is done */ | |
441 | btci->dhcp_done = true; | |
442 | if (btci->timer_on) { | |
443 | brcmf_dbg(TRACE, "disable BT DHCP Timer\n"); | |
444 | btci->timer_on = false; | |
445 | del_timer_sync(&btci->timer); | |
446 | ||
447 | /* schedule worker if transition to IDLE is needed */ | |
448 | if (btci->bt_state != BRCMF_BT_DHCP_IDLE) { | |
449 | brcmf_dbg(TRACE, "bt_state:%d\n", | |
450 | btci->bt_state); | |
451 | schedule_work(&btci->work); | |
452 | } | |
453 | } else { | |
454 | /* Restore original values */ | |
455 | brcmf_btcoex_restore_part1(btci); | |
456 | } | |
457 | } | |
458 | ||
459 | /** | |
460 | * brcmf_btcoex_set_mode - set BT coex mode | |
461 | * @cfg: driver private cfg80211 data | |
462 | * @mode: Wifi-Bluetooth coexistence mode | |
463 | * | |
464 | * return: 0 on success | |
465 | */ | |
466 | int brcmf_btcoex_set_mode(struct brcmf_cfg80211_vif *vif, | |
467 | enum brcmf_btcoex_mode mode, u16 duration) | |
468 | { | |
469 | struct brcmf_cfg80211_info *cfg = wiphy_priv(vif->wdev.wiphy); | |
470 | struct brcmf_btcoex_info *btci = cfg->btcoex; | |
471 | struct brcmf_if *ifp = cfg->pub->iflist[0]; | |
472 | ||
473 | switch (mode) { | |
474 | case BRCMF_BTCOEX_DISABLED: | |
475 | brcmf_dbg(TRACE, "DHCP session starts\n"); | |
476 | if (btci->bt_state != BRCMF_BT_DHCP_IDLE) | |
477 | return -EBUSY; | |
478 | /* Start BT timer only for SCO connection */ | |
479 | if (brcmf_btcoex_is_sco_active(ifp)) { | |
480 | btci->timeout = duration; | |
481 | btci->vif = vif; | |
482 | brcmf_btcoex_dhcp_start(btci); | |
483 | } | |
484 | break; | |
485 | ||
486 | case BRCMF_BTCOEX_ENABLED: | |
487 | brcmf_dbg(TRACE, "DHCP session ends\n"); | |
488 | if (btci->bt_state != BRCMF_BT_DHCP_IDLE && | |
489 | vif == btci->vif) { | |
490 | brcmf_btcoex_dhcp_end(btci); | |
491 | } | |
492 | break; | |
493 | default: | |
494 | brcmf_dbg(TRACE, "Unknown mode, ignored\n"); | |
495 | } | |
496 | return 0; | |
497 | } |