483 lines · c
1// SPDX-License-Identifier: ISC2/*3 * Copyright (c) 2013 Broadcom Corporation4 */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 0x802225#define BRCMF_BT_DHCP_REG51 026#define BRCMF_BT_DHCP_REG64 027#define BRCMF_BT_DHCP_REG65 028#define BRCMF_BT_DHCP_REG71 029#define BRCMF_BT_DHCP_REG66 0x271030#define BRCMF_BT_DHCP_REG41 0x3331#define BRCMF_BT_DHCP_REG68 0x19032 33/* number of samples for SCO detection */34#define BRCMF_BT_SCO_SAMPLES 1235 36/**37* enum brcmf_btcoex_state - BT coex DHCP state machine states38* @BRCMF_BT_DHCP_IDLE: DCHP is idle39* @BRCMF_BT_DHCP_START: DHCP started, wait before40* boosting wifi priority41* @BRCMF_BT_DHCP_OPPR_WIN: graceful DHCP opportunity ended,42* boost wifi priority43* @BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT: wifi priority boost end,44* restore defaults45*/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_TIMEOUT51};52 53/**54 * struct brcmf_btcoex_info - BT coex related information55 * @vif: interface for which request was done.56 * @timer: timer for DHCP state machine57 * @timeout: configured timeout.58 * @timer_on: DHCP timer active59 * @dhcp_done: DHCP finished before T1/T2 timer expiration60 * @bt_state: DHCP state machine state61 * @work: DHCP state machine work62 * @cfg: driver private data for cfg80211 interface63 * @reg66: saved value of btc_params 6664 * @reg41: saved value of btc_params 4165 * @reg68: saved value of btc_params 6866 * @saved_regs_part1: flag indicating regs 66,41,6867 * have been saved68 * @reg50: saved value of btc_params 5069 * @reg51: saved value of btc_params 5170 * @reg64: saved value of btc_params 6471 * @reg65: saved value of btc_params 6572 * @reg71: saved value of btc_params 7173 * @saved_regs_part2: flag indicating regs 50,51,64,65,7174 * have been saved75 */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 variable99 * @ifp: interface100 * @addr: btc_params register number101 * @data: data to write102 */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 variable118 * @ifp: interface119 * @addr: btc_params register number120 * @data: read data121 */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_query(ifp, "btc_params", data);127}128 129/**130 * brcmf_btcoex_boost_wifi() - control BT SCO/eSCO parameters131 * @btci: BT coex info132 * @trump_sco:133 * true - set SCO/eSCO parameters for compatibility134 * during DHCP window135 * false - restore saved parameter values136 *137 * Enhanced BT COEX settings for eSCO compatibility during DHCP window138 */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 agressive146 * retransmit w/o breaking it147 */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 active192 * @ifp: interface193 *194 * return: true if SCO/eSCO session is active195 */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 callback272 */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 handler284 * @work: work285 */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 window298 to get DHCP address299 */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 else332 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 data355 * @cfg: driver private cfg80211 data356 *357 * return: 0 on success358 */359int brcmf_btcoex_attach(struct brcmf_cfg80211_info *cfg)360{361 struct brcmf_btcoex_info *btci;362 brcmf_dbg(TRACE, "enter\n");363 364 btci = kmalloc(sizeof(*btci), 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 data386 * @cfg: driver private cfg80211 data387 */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 timer_shutdown_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 mode447 * @mode: Wifi-Bluetooth coexistence mode448 *449 * return: 0 on success450 */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}483