xref: /openbmc/linux/drivers/net/wireless/broadcom/brcm80211/brcmfmac/btcoex.c (revision 34d6f206a88c2651d216bd3487ac956a40b2ba8e)
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   */
brcmf_btcoex_params_write(struct brcmf_if * ifp,u32 addr,u32 data)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  					&reg_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   */
brcmf_btcoex_params_read(struct brcmf_if * ifp,u32 addr,u32 * data)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   */
brcmf_btcoex_boost_wifi(struct brcmf_btcoex_info * btci,bool trump_sco)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   */
brcmf_btcoex_is_sco_active(struct brcmf_if * ifp)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, &param27);
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   */
btcmf_btcoex_save_part1(struct brcmf_btcoex_info * btci)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   */
brcmf_btcoex_restore_part1(struct brcmf_btcoex_info * btci)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   */
brcmf_btcoex_timerfunc(struct timer_list * t)273  static 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   */
brcmf_btcoex_handler(struct work_struct * work)286  static 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  
344  idle:
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   */
brcmf_btcoex_attach(struct brcmf_cfg80211_info * cfg)359  int 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   */
brcmf_btcoex_detach(struct brcmf_cfg80211_info * cfg)388  void 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  
brcmf_btcoex_dhcp_start(struct brcmf_btcoex_info * btci)409  static 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  
brcmf_btcoex_dhcp_end(struct brcmf_btcoex_info * btci)424  static 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   */
brcmf_btcoex_set_mode(struct brcmf_cfg80211_vif * vif,enum brcmf_btcoex_mode mode,u16 duration)451  int 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