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 */ 46 enum 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 */ 76 struct 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 */ 103 static 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 */ 122 static s32 brcmf_btcoex_params_read(struct brcmf_if *ifp, u32 addr, u32 *data) 123 { 124 *data = addr; 125 126 return brcmf_fil_iovar_int_query(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 */ 139 static 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 */ 196 static 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 */ 233 static 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 */ 253 static 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 */ 273 static void brcmf_btcoex_timerfunc(struct timer_list *t) 274 { 275 struct brcmf_btcoex_info *bt_local = timer_container_of(bt_local, t, 276 timer); 277 brcmf_dbg(TRACE, "enter\n"); 278 279 bt_local->timer_on = false; 280 schedule_work(&bt_local->work); 281 } 282 283 /** 284 * brcmf_btcoex_handler() - BT coex state machine work handler 285 * @work: work 286 */ 287 static void brcmf_btcoex_handler(struct work_struct *work) 288 { 289 struct brcmf_btcoex_info *btci; 290 btci = container_of(work, struct brcmf_btcoex_info, work); 291 if (btci->timer_on) { 292 btci->timer_on = false; 293 timer_delete_sync(&btci->timer); 294 } 295 296 switch (btci->bt_state) { 297 case BRCMF_BT_DHCP_START: 298 /* DHCP started provide OPPORTUNITY window 299 to get DHCP address 300 */ 301 brcmf_dbg(INFO, "DHCP started\n"); 302 btci->bt_state = BRCMF_BT_DHCP_OPPR_WIN; 303 if (btci->timeout < BRCMF_BTCOEX_OPPR_WIN_TIME) { 304 mod_timer(&btci->timer, btci->timer.expires); 305 } else { 306 btci->timeout -= BRCMF_BTCOEX_OPPR_WIN_TIME; 307 mod_timer(&btci->timer, 308 jiffies + BRCMF_BTCOEX_OPPR_WIN_TIME); 309 } 310 btci->timer_on = true; 311 break; 312 313 case BRCMF_BT_DHCP_OPPR_WIN: 314 if (btci->dhcp_done) { 315 brcmf_dbg(INFO, "DHCP done before T1 expiration\n"); 316 goto idle; 317 } 318 319 /* DHCP is not over yet, start lowering BT priority */ 320 brcmf_dbg(INFO, "DHCP T1:%d expired\n", 321 jiffies_to_msecs(BRCMF_BTCOEX_OPPR_WIN_TIME)); 322 brcmf_btcoex_boost_wifi(btci, true); 323 324 btci->bt_state = BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT; 325 mod_timer(&btci->timer, jiffies + btci->timeout); 326 btci->timer_on = true; 327 break; 328 329 case BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT: 330 if (btci->dhcp_done) 331 brcmf_dbg(INFO, "DHCP done before T2 expiration\n"); 332 else 333 brcmf_dbg(INFO, "DHCP T2:%d expired\n", 334 BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT); 335 336 goto idle; 337 338 default: 339 brcmf_err("invalid state=%d !!!\n", btci->bt_state); 340 goto idle; 341 } 342 343 return; 344 345 idle: 346 btci->bt_state = BRCMF_BT_DHCP_IDLE; 347 btci->timer_on = false; 348 brcmf_btcoex_boost_wifi(btci, false); 349 cfg80211_crit_proto_stopped(&btci->vif->wdev, GFP_KERNEL); 350 brcmf_btcoex_restore_part1(btci); 351 btci->vif = NULL; 352 } 353 354 /** 355 * brcmf_btcoex_attach() - initialize BT coex data 356 * @cfg: driver private cfg80211 data 357 * 358 * return: 0 on success 359 */ 360 int brcmf_btcoex_attach(struct brcmf_cfg80211_info *cfg) 361 { 362 struct brcmf_btcoex_info *btci; 363 brcmf_dbg(TRACE, "enter\n"); 364 365 btci = kmalloc(sizeof(*btci), GFP_KERNEL); 366 if (!btci) 367 return -ENOMEM; 368 369 btci->bt_state = BRCMF_BT_DHCP_IDLE; 370 371 /* Set up timer for BT */ 372 btci->timer_on = false; 373 btci->timeout = BRCMF_BTCOEX_OPPR_WIN_TIME; 374 timer_setup(&btci->timer, brcmf_btcoex_timerfunc, 0); 375 btci->cfg = cfg; 376 btci->saved_regs_part1 = false; 377 btci->saved_regs_part2 = false; 378 379 INIT_WORK(&btci->work, brcmf_btcoex_handler); 380 381 cfg->btcoex = btci; 382 return 0; 383 } 384 385 /** 386 * brcmf_btcoex_detach - clean BT coex data 387 * @cfg: driver private cfg80211 data 388 */ 389 void brcmf_btcoex_detach(struct brcmf_cfg80211_info *cfg) 390 { 391 brcmf_dbg(TRACE, "enter\n"); 392 393 if (!cfg->btcoex) 394 return; 395 396 if (cfg->btcoex->timer_on) { 397 cfg->btcoex->timer_on = false; 398 timer_shutdown_sync(&cfg->btcoex->timer); 399 } 400 401 cancel_work_sync(&cfg->btcoex->work); 402 403 brcmf_btcoex_boost_wifi(cfg->btcoex, false); 404 brcmf_btcoex_restore_part1(cfg->btcoex); 405 406 kfree(cfg->btcoex); 407 cfg->btcoex = NULL; 408 } 409 410 static void brcmf_btcoex_dhcp_start(struct brcmf_btcoex_info *btci) 411 { 412 struct brcmf_if *ifp = btci->vif->ifp; 413 414 btcmf_btcoex_save_part1(btci); 415 /* set new regs values */ 416 brcmf_btcoex_params_write(ifp, 66, BRCMF_BT_DHCP_REG66); 417 brcmf_btcoex_params_write(ifp, 41, BRCMF_BT_DHCP_REG41); 418 brcmf_btcoex_params_write(ifp, 68, BRCMF_BT_DHCP_REG68); 419 btci->dhcp_done = false; 420 btci->bt_state = BRCMF_BT_DHCP_START; 421 schedule_work(&btci->work); 422 brcmf_dbg(TRACE, "enable BT DHCP Timer\n"); 423 } 424 425 static void brcmf_btcoex_dhcp_end(struct brcmf_btcoex_info *btci) 426 { 427 /* Stop any bt timer because DHCP session is done */ 428 btci->dhcp_done = true; 429 if (btci->timer_on) { 430 brcmf_dbg(INFO, "disable BT DHCP Timer\n"); 431 btci->timer_on = false; 432 timer_delete_sync(&btci->timer); 433 434 /* schedule worker if transition to IDLE is needed */ 435 if (btci->bt_state != BRCMF_BT_DHCP_IDLE) { 436 brcmf_dbg(INFO, "bt_state:%d\n", 437 btci->bt_state); 438 schedule_work(&btci->work); 439 } 440 } else { 441 /* Restore original values */ 442 brcmf_btcoex_restore_part1(btci); 443 } 444 } 445 446 /* 447 * brcmf_btcoex_set_mode - set BT coex mode 448 * @mode: Wifi-Bluetooth coexistence mode 449 * 450 * return: 0 on success 451 */ 452 int brcmf_btcoex_set_mode(struct brcmf_cfg80211_vif *vif, 453 enum brcmf_btcoex_mode mode, u16 duration) 454 { 455 struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(vif->wdev.wiphy); 456 struct brcmf_btcoex_info *btci = cfg->btcoex; 457 struct brcmf_if *ifp = brcmf_get_ifp(cfg->pub, 0); 458 459 switch (mode) { 460 case BRCMF_BTCOEX_DISABLED: 461 brcmf_dbg(INFO, "DHCP session starts\n"); 462 if (btci->bt_state != BRCMF_BT_DHCP_IDLE) 463 return -EBUSY; 464 /* Start BT timer only for SCO connection */ 465 if (brcmf_btcoex_is_sco_active(ifp)) { 466 btci->timeout = msecs_to_jiffies(duration); 467 btci->vif = vif; 468 brcmf_btcoex_dhcp_start(btci); 469 } 470 break; 471 472 case BRCMF_BTCOEX_ENABLED: 473 brcmf_dbg(INFO, "DHCP session ends\n"); 474 if (btci->bt_state != BRCMF_BT_DHCP_IDLE && 475 vif == btci->vif) { 476 brcmf_btcoex_dhcp_end(btci); 477 } 478 break; 479 default: 480 brcmf_dbg(INFO, "Unknown mode, ignored\n"); 481 } 482 return 0; 483 } 484