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