Path: blob/main/sys/contrib/dev/broadcom/brcm80211/brcmfmac/btcoex.c
178665 views
// SPDX-License-Identifier: ISC1/*2* Copyright (c) 2013 Broadcom Corporation3*/4#include <linux/slab.h>5#include <linux/netdevice.h>6#include <net/cfg80211.h>78#include <brcmu_wifi.h>9#include <brcmu_utils.h>10#include <defs.h>11#include "core.h"12#include "debug.h"13#include "fwil.h"14#include "fwil_types.h"15#include "btcoex.h"16#include "p2p.h"17#include "cfg80211.h"1819/* T1 start SCO/eSCO priority suppression */20#define BRCMF_BTCOEX_OPPR_WIN_TIME msecs_to_jiffies(2000)2122/* BT registers values during DHCP */23#define BRCMF_BT_DHCP_REG50 0x802224#define BRCMF_BT_DHCP_REG51 025#define BRCMF_BT_DHCP_REG64 026#define BRCMF_BT_DHCP_REG65 027#define BRCMF_BT_DHCP_REG71 028#define BRCMF_BT_DHCP_REG66 0x271029#define BRCMF_BT_DHCP_REG41 0x3330#define BRCMF_BT_DHCP_REG68 0x1903132/* number of samples for SCO detection */33#define BRCMF_BT_SCO_SAMPLES 123435/**36* enum brcmf_btcoex_state - BT coex DHCP state machine states37* @BRCMF_BT_DHCP_IDLE: DCHP is idle38* @BRCMF_BT_DHCP_START: DHCP started, wait before39* boosting wifi priority40* @BRCMF_BT_DHCP_OPPR_WIN: graceful DHCP opportunity ended,41* boost wifi priority42* @BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT: wifi priority boost end,43* restore defaults44*/45enum brcmf_btcoex_state {46BRCMF_BT_DHCP_IDLE,47BRCMF_BT_DHCP_START,48BRCMF_BT_DHCP_OPPR_WIN,49BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT50};5152/**53* struct brcmf_btcoex_info - BT coex related information54* @vif: interface for which request was done.55* @timer: timer for DHCP state machine56* @timeout: configured timeout.57* @timer_on: DHCP timer active58* @dhcp_done: DHCP finished before T1/T2 timer expiration59* @bt_state: DHCP state machine state60* @work: DHCP state machine work61* @cfg: driver private data for cfg80211 interface62* @reg66: saved value of btc_params 6663* @reg41: saved value of btc_params 4164* @reg68: saved value of btc_params 6865* @saved_regs_part1: flag indicating regs 66,41,6866* have been saved67* @reg50: saved value of btc_params 5068* @reg51: saved value of btc_params 5169* @reg64: saved value of btc_params 6470* @reg65: saved value of btc_params 6571* @reg71: saved value of btc_params 7172* @saved_regs_part2: flag indicating regs 50,51,64,65,7173* have been saved74*/75struct brcmf_btcoex_info {76struct brcmf_cfg80211_vif *vif;77struct timer_list timer;78u16 timeout;79bool timer_on;80bool dhcp_done;81enum brcmf_btcoex_state bt_state;82struct work_struct work;83struct brcmf_cfg80211_info *cfg;84u32 reg66;85u32 reg41;86u32 reg68;87bool saved_regs_part1;88u32 reg50;89u32 reg51;90u32 reg64;91u32 reg65;92u32 reg71;93bool saved_regs_part2;94};9596/**97* brcmf_btcoex_params_write() - write btc_params firmware variable98* @ifp: interface99* @addr: btc_params register number100* @data: data to write101*/102static s32 brcmf_btcoex_params_write(struct brcmf_if *ifp, u32 addr, u32 data)103{104struct {105__le32 addr;106__le32 data;107} reg_write;108109reg_write.addr = cpu_to_le32(addr);110reg_write.data = cpu_to_le32(data);111return brcmf_fil_iovar_data_set(ifp, "btc_params",112®_write, sizeof(reg_write));113}114115/**116* brcmf_btcoex_params_read() - read btc_params firmware variable117* @ifp: interface118* @addr: btc_params register number119* @data: read data120*/121static s32 brcmf_btcoex_params_read(struct brcmf_if *ifp, u32 addr, u32 *data)122{123*data = addr;124125return brcmf_fil_iovar_int_query(ifp, "btc_params", data);126}127128/**129* brcmf_btcoex_boost_wifi() - control BT SCO/eSCO parameters130* @btci: BT coex info131* @trump_sco:132* true - set SCO/eSCO parameters for compatibility133* during DHCP window134* false - restore saved parameter values135*136* Enhanced BT COEX settings for eSCO compatibility during DHCP window137*/138static void brcmf_btcoex_boost_wifi(struct brcmf_btcoex_info *btci,139bool trump_sco)140{141struct brcmf_if *ifp = brcmf_get_ifp(btci->cfg->pub, 0);142143if (trump_sco && !btci->saved_regs_part2) {144/* this should reduce eSCO agressive145* retransmit w/o breaking it146*/147148/* save current */149brcmf_dbg(INFO, "new SCO/eSCO coex algo {save & override}\n");150brcmf_btcoex_params_read(ifp, 50, &btci->reg50);151brcmf_btcoex_params_read(ifp, 51, &btci->reg51);152brcmf_btcoex_params_read(ifp, 64, &btci->reg64);153brcmf_btcoex_params_read(ifp, 65, &btci->reg65);154brcmf_btcoex_params_read(ifp, 71, &btci->reg71);155156btci->saved_regs_part2 = true;157brcmf_dbg(INFO,158"saved bt_params[50,51,64,65,71]: 0x%x 0x%x 0x%x 0x%x 0x%x\n",159btci->reg50, btci->reg51, btci->reg64,160btci->reg65, btci->reg71);161162/* pacify the eSco */163brcmf_btcoex_params_write(ifp, 50, BRCMF_BT_DHCP_REG50);164brcmf_btcoex_params_write(ifp, 51, BRCMF_BT_DHCP_REG51);165brcmf_btcoex_params_write(ifp, 64, BRCMF_BT_DHCP_REG64);166brcmf_btcoex_params_write(ifp, 65, BRCMF_BT_DHCP_REG65);167brcmf_btcoex_params_write(ifp, 71, BRCMF_BT_DHCP_REG71);168169} else if (btci->saved_regs_part2) {170/* restore previously saved bt params */171brcmf_dbg(INFO, "Do new SCO/eSCO coex algo {restore}\n");172brcmf_btcoex_params_write(ifp, 50, btci->reg50);173brcmf_btcoex_params_write(ifp, 51, btci->reg51);174brcmf_btcoex_params_write(ifp, 64, btci->reg64);175brcmf_btcoex_params_write(ifp, 65, btci->reg65);176brcmf_btcoex_params_write(ifp, 71, btci->reg71);177178brcmf_dbg(INFO,179"restored bt_params[50,51,64,65,71]: 0x%x 0x%x 0x%x 0x%x 0x%x\n",180btci->reg50, btci->reg51, btci->reg64,181btci->reg65, btci->reg71);182183btci->saved_regs_part2 = false;184} else {185brcmf_dbg(INFO, "attempted to restore not saved BTCOEX params\n");186}187}188189/**190* brcmf_btcoex_is_sco_active() - check if SCO/eSCO is active191* @ifp: interface192*193* return: true if SCO/eSCO session is active194*/195static bool brcmf_btcoex_is_sco_active(struct brcmf_if *ifp)196{197int ioc_res = 0;198bool res = false;199int sco_id_cnt = 0;200u32 param27;201int i;202203for (i = 0; i < BRCMF_BT_SCO_SAMPLES; i++) {204ioc_res = brcmf_btcoex_params_read(ifp, 27, ¶m27);205206if (ioc_res < 0) {207brcmf_err("ioc read btc params error\n");208break;209}210211brcmf_dbg(INFO, "sample[%d], btc_params 27:%x\n", i, param27);212213if ((param27 & 0x6) == 2) { /* count both sco & esco */214sco_id_cnt++;215}216217if (sco_id_cnt > 2) {218brcmf_dbg(INFO,219"sco/esco detected, pkt id_cnt:%d samples:%d\n",220sco_id_cnt, i);221res = true;222break;223}224}225brcmf_dbg(TRACE, "exit: result=%d\n", res);226return res;227}228229/*230* btcmf_btcoex_save_part1() - save first step parameters.231*/232static void btcmf_btcoex_save_part1(struct brcmf_btcoex_info *btci)233{234struct brcmf_if *ifp = btci->vif->ifp;235236if (!btci->saved_regs_part1) {237/* Retrieve and save original reg value */238brcmf_btcoex_params_read(ifp, 66, &btci->reg66);239brcmf_btcoex_params_read(ifp, 41, &btci->reg41);240brcmf_btcoex_params_read(ifp, 68, &btci->reg68);241btci->saved_regs_part1 = true;242brcmf_dbg(INFO,243"saved btc_params regs (66,41,68) 0x%x 0x%x 0x%x\n",244btci->reg66, btci->reg41,245btci->reg68);246}247}248249/*250* brcmf_btcoex_restore_part1() - restore first step parameters.251*/252static void brcmf_btcoex_restore_part1(struct brcmf_btcoex_info *btci)253{254struct brcmf_if *ifp;255256if (btci->saved_regs_part1) {257btci->saved_regs_part1 = false;258ifp = btci->vif->ifp;259brcmf_btcoex_params_write(ifp, 66, btci->reg66);260brcmf_btcoex_params_write(ifp, 41, btci->reg41);261brcmf_btcoex_params_write(ifp, 68, btci->reg68);262brcmf_dbg(INFO,263"restored btc_params regs {66,41,68} 0x%x 0x%x 0x%x\n",264btci->reg66, btci->reg41,265btci->reg68);266}267}268269/*270* brcmf_btcoex_timerfunc() - BT coex timer callback271*/272static void brcmf_btcoex_timerfunc(struct timer_list *t)273{274struct brcmf_btcoex_info *bt_local = timer_container_of(bt_local, t,275timer);276brcmf_dbg(TRACE, "enter\n");277278bt_local->timer_on = false;279schedule_work(&bt_local->work);280}281282/**283* brcmf_btcoex_handler() - BT coex state machine work handler284* @work: work285*/286static void brcmf_btcoex_handler(struct work_struct *work)287{288struct brcmf_btcoex_info *btci;289btci = container_of(work, struct brcmf_btcoex_info, work);290if (btci->timer_on) {291btci->timer_on = false;292timer_delete_sync(&btci->timer);293}294295switch (btci->bt_state) {296case BRCMF_BT_DHCP_START:297/* DHCP started provide OPPORTUNITY window298to get DHCP address299*/300brcmf_dbg(INFO, "DHCP started\n");301btci->bt_state = BRCMF_BT_DHCP_OPPR_WIN;302if (btci->timeout < BRCMF_BTCOEX_OPPR_WIN_TIME) {303mod_timer(&btci->timer, btci->timer.expires);304} else {305btci->timeout -= BRCMF_BTCOEX_OPPR_WIN_TIME;306mod_timer(&btci->timer,307jiffies + BRCMF_BTCOEX_OPPR_WIN_TIME);308}309btci->timer_on = true;310break;311312case BRCMF_BT_DHCP_OPPR_WIN:313if (btci->dhcp_done) {314brcmf_dbg(INFO, "DHCP done before T1 expiration\n");315goto idle;316}317318/* DHCP is not over yet, start lowering BT priority */319brcmf_dbg(INFO, "DHCP T1:%d expired\n",320jiffies_to_msecs(BRCMF_BTCOEX_OPPR_WIN_TIME));321brcmf_btcoex_boost_wifi(btci, true);322323btci->bt_state = BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT;324mod_timer(&btci->timer, jiffies + btci->timeout);325btci->timer_on = true;326break;327328case BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT:329if (btci->dhcp_done)330brcmf_dbg(INFO, "DHCP done before T2 expiration\n");331else332brcmf_dbg(INFO, "DHCP T2:%d expired\n",333BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT);334335goto idle;336337default:338brcmf_err("invalid state=%d !!!\n", btci->bt_state);339goto idle;340}341342return;343344idle:345btci->bt_state = BRCMF_BT_DHCP_IDLE;346btci->timer_on = false;347brcmf_btcoex_boost_wifi(btci, false);348cfg80211_crit_proto_stopped(&btci->vif->wdev, GFP_KERNEL);349brcmf_btcoex_restore_part1(btci);350btci->vif = NULL;351}352353/**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{361struct brcmf_btcoex_info *btci;362brcmf_dbg(TRACE, "enter\n");363364btci = kmalloc(sizeof(*btci), GFP_KERNEL);365if (!btci)366return -ENOMEM;367368btci->bt_state = BRCMF_BT_DHCP_IDLE;369370/* Set up timer for BT */371btci->timer_on = false;372btci->timeout = BRCMF_BTCOEX_OPPR_WIN_TIME;373timer_setup(&btci->timer, brcmf_btcoex_timerfunc, 0);374btci->cfg = cfg;375btci->saved_regs_part1 = false;376btci->saved_regs_part2 = false;377378INIT_WORK(&btci->work, brcmf_btcoex_handler);379380cfg->btcoex = btci;381return 0;382}383384/**385* brcmf_btcoex_detach - clean BT coex data386* @cfg: driver private cfg80211 data387*/388void brcmf_btcoex_detach(struct brcmf_cfg80211_info *cfg)389{390brcmf_dbg(TRACE, "enter\n");391392if (!cfg->btcoex)393return;394395timer_shutdown_sync(&cfg->btcoex->timer);396cfg->btcoex->timer_on = false;397398cancel_work_sync(&cfg->btcoex->work);399400brcmf_btcoex_boost_wifi(cfg->btcoex, false);401brcmf_btcoex_restore_part1(cfg->btcoex);402403kfree(cfg->btcoex);404cfg->btcoex = NULL;405}406407static void brcmf_btcoex_dhcp_start(struct brcmf_btcoex_info *btci)408{409struct brcmf_if *ifp = btci->vif->ifp;410411btcmf_btcoex_save_part1(btci);412/* set new regs values */413brcmf_btcoex_params_write(ifp, 66, BRCMF_BT_DHCP_REG66);414brcmf_btcoex_params_write(ifp, 41, BRCMF_BT_DHCP_REG41);415brcmf_btcoex_params_write(ifp, 68, BRCMF_BT_DHCP_REG68);416btci->dhcp_done = false;417btci->bt_state = BRCMF_BT_DHCP_START;418schedule_work(&btci->work);419brcmf_dbg(TRACE, "enable BT DHCP Timer\n");420}421422static void brcmf_btcoex_dhcp_end(struct brcmf_btcoex_info *btci)423{424/* Stop any bt timer because DHCP session is done */425btci->dhcp_done = true;426if (btci->timer_on) {427brcmf_dbg(INFO, "disable BT DHCP Timer\n");428btci->timer_on = false;429timer_delete_sync(&btci->timer);430431/* schedule worker if transition to IDLE is needed */432if (btci->bt_state != BRCMF_BT_DHCP_IDLE) {433brcmf_dbg(INFO, "bt_state:%d\n",434btci->bt_state);435schedule_work(&btci->work);436}437} else {438/* Restore original values */439brcmf_btcoex_restore_part1(btci);440}441}442443/*444* brcmf_btcoex_set_mode - set BT coex mode445* @mode: Wifi-Bluetooth coexistence mode446*447* return: 0 on success448*/449int brcmf_btcoex_set_mode(struct brcmf_cfg80211_vif *vif,450enum brcmf_btcoex_mode mode, u16 duration)451{452struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(vif->wdev.wiphy);453struct brcmf_btcoex_info *btci = cfg->btcoex;454struct brcmf_if *ifp = brcmf_get_ifp(cfg->pub, 0);455456switch (mode) {457case BRCMF_BTCOEX_DISABLED:458brcmf_dbg(INFO, "DHCP session starts\n");459if (btci->bt_state != BRCMF_BT_DHCP_IDLE)460return -EBUSY;461/* Start BT timer only for SCO connection */462if (brcmf_btcoex_is_sco_active(ifp)) {463btci->timeout = msecs_to_jiffies(duration);464btci->vif = vif;465brcmf_btcoex_dhcp_start(btci);466}467break;468469case BRCMF_BTCOEX_ENABLED:470brcmf_dbg(INFO, "DHCP session ends\n");471if (btci->bt_state != BRCMF_BT_DHCP_IDLE &&472vif == btci->vif) {473brcmf_btcoex_dhcp_end(btci);474}475break;476default:477brcmf_dbg(INFO, "Unknown mode, ignored\n");478}479return 0;480}481482483