diff options
Diffstat (limited to 'drivers/net/wireless/brcm80211/brcmsmac/antsel.c')
| -rw-r--r-- | drivers/net/wireless/brcm80211/brcmsmac/antsel.c | 309 | 
1 files changed, 309 insertions, 0 deletions
diff --git a/drivers/net/wireless/brcm80211/brcmsmac/antsel.c b/drivers/net/wireless/brcm80211/brcmsmac/antsel.c new file mode 100644 index 00000000000..54c61691959 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/antsel.c @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/slab.h> +#include <net/mac80211.h> + +#include "types.h" +#include "main.h" +#include "phy_shim.h" +#include "antsel.h" +#include "debug.h" + +#define ANT_SELCFG_AUTO		0x80	/* bit indicates antenna sel AUTO */ +#define ANT_SELCFG_MASK		0x33	/* antenna configuration mask */ +#define ANT_SELCFG_TX_UNICAST	0	/* unicast tx antenna configuration */ +#define ANT_SELCFG_RX_UNICAST	1	/* unicast rx antenna configuration */ +#define ANT_SELCFG_TX_DEF	2	/* default tx antenna configuration */ +#define ANT_SELCFG_RX_DEF	3	/* default rx antenna configuration */ + +/* useful macros */ +#define BRCMS_ANTSEL_11N_0(ant)	((((ant) & ANT_SELCFG_MASK) >> 4) & 0xf) +#define BRCMS_ANTSEL_11N_1(ant)	(((ant) & ANT_SELCFG_MASK) & 0xf) +#define BRCMS_ANTIDX_11N(ant)	(((BRCMS_ANTSEL_11N_0(ant)) << 2) +\ +				(BRCMS_ANTSEL_11N_1(ant))) +#define BRCMS_ANT_ISAUTO_11N(ant) (((ant) & ANT_SELCFG_AUTO) == ANT_SELCFG_AUTO) +#define BRCMS_ANTSEL_11N(ant)	((ant) & ANT_SELCFG_MASK) + +/* antenna switch */ +/* defines for no boardlevel antenna diversity */ +#define ANT_SELCFG_DEF_2x2	0x01	/* default antenna configuration */ + +/* 2x3 antdiv defines and tables for GPIO communication */ +#define ANT_SELCFG_NUM_2x3	3 +#define ANT_SELCFG_DEF_2x3	0x01	/* default antenna configuration */ + +/* 2x4 antdiv rev4 defines and tables for GPIO communication */ +#define ANT_SELCFG_NUM_2x4	4 +#define ANT_SELCFG_DEF_2x4	0x02	/* default antenna configuration */ + +static const u16 mimo_2x4_div_antselpat_tbl[] = { +	0, 0, 0x9, 0xa,		/* ant0: 0 ant1: 2,3 */ +	0, 0, 0x5, 0x6,		/* ant0: 1 ant1: 2,3 */ +	0, 0, 0, 0,		/* n.a.              */ +	0, 0, 0, 0		/* n.a.              */ +}; + +static const u8 mimo_2x4_div_antselid_tbl[16] = { +	0, 0, 0, 0, 0, 2, 3, 0, +	0, 0, 1, 0, 0, 0, 0, 0	/* pat to antselid */ +}; + +static const u16 mimo_2x3_div_antselpat_tbl[] = { +	16, 0, 1, 16,		/* ant0: 0 ant1: 1,2 */ +	16, 16, 16, 16,		/* n.a.              */ +	16, 2, 16, 16,		/* ant0: 2 ant1: 1   */ +	16, 16, 16, 16		/* n.a.              */ +}; + +static const u8 mimo_2x3_div_antselid_tbl[16] = { +	0, 1, 2, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0	/* pat to antselid */ +}; + +/* boardlevel antenna selection: init antenna selection structure */ +static void +brcms_c_antsel_init_cfg(struct antsel_info *asi, struct brcms_antselcfg *antsel, +		    bool auto_sel) +{ +	if (asi->antsel_type == ANTSEL_2x3) { +		u8 antcfg_def = ANT_SELCFG_DEF_2x3 | +		    ((asi->antsel_avail && auto_sel) ? ANT_SELCFG_AUTO : 0); +		antsel->ant_config[ANT_SELCFG_TX_DEF] = antcfg_def; +		antsel->ant_config[ANT_SELCFG_TX_UNICAST] = antcfg_def; +		antsel->ant_config[ANT_SELCFG_RX_DEF] = antcfg_def; +		antsel->ant_config[ANT_SELCFG_RX_UNICAST] = antcfg_def; +		antsel->num_antcfg = ANT_SELCFG_NUM_2x3; + +	} else if (asi->antsel_type == ANTSEL_2x4) { + +		antsel->ant_config[ANT_SELCFG_TX_DEF] = ANT_SELCFG_DEF_2x4; +		antsel->ant_config[ANT_SELCFG_TX_UNICAST] = ANT_SELCFG_DEF_2x4; +		antsel->ant_config[ANT_SELCFG_RX_DEF] = ANT_SELCFG_DEF_2x4; +		antsel->ant_config[ANT_SELCFG_RX_UNICAST] = ANT_SELCFG_DEF_2x4; +		antsel->num_antcfg = ANT_SELCFG_NUM_2x4; + +	} else {		/* no antenna selection available */ + +		antsel->ant_config[ANT_SELCFG_TX_DEF] = ANT_SELCFG_DEF_2x2; +		antsel->ant_config[ANT_SELCFG_TX_UNICAST] = ANT_SELCFG_DEF_2x2; +		antsel->ant_config[ANT_SELCFG_RX_DEF] = ANT_SELCFG_DEF_2x2; +		antsel->ant_config[ANT_SELCFG_RX_UNICAST] = ANT_SELCFG_DEF_2x2; +		antsel->num_antcfg = 0; +	} +} + +struct antsel_info *brcms_c_antsel_attach(struct brcms_c_info *wlc) +{ +	struct antsel_info *asi; +	struct ssb_sprom *sprom = &wlc->hw->d11core->bus->sprom; + +	asi = kzalloc(sizeof(struct antsel_info), GFP_ATOMIC); +	if (!asi) +		return NULL; + +	asi->wlc = wlc; +	asi->pub = wlc->pub; +	asi->antsel_type = ANTSEL_NA; +	asi->antsel_avail = false; +	asi->antsel_antswitch = sprom->antswitch; + +	if ((asi->pub->sromrev >= 4) && (asi->antsel_antswitch != 0)) { +		switch (asi->antsel_antswitch) { +		case ANTSWITCH_TYPE_1: +		case ANTSWITCH_TYPE_2: +		case ANTSWITCH_TYPE_3: +			/* 4321/2 board with 2x3 switch logic */ +			asi->antsel_type = ANTSEL_2x3; +			/* Antenna selection availability */ +			if ((sprom->ant_available_bg == 7) || +			    (sprom->ant_available_a == 7)) { +				asi->antsel_avail = true; +			} else if ( +				sprom->ant_available_bg == 3 || +				sprom->ant_available_a == 3) { +				asi->antsel_avail = false; +			} else { +				asi->antsel_avail = false; +				brcms_err(wlc->hw->d11core, +					  "antsel_attach: 2o3 " +					  "board cfg invalid\n"); +			} + +			break; +		default: +			break; +		} +	} else if ((asi->pub->sromrev == 4) && +		   (sprom->ant_available_bg == 7) && +		   (sprom->ant_available_a == 0)) { +		/* hack to match old 4321CB2 cards with 2of3 antenna switch */ +		asi->antsel_type = ANTSEL_2x3; +		asi->antsel_avail = true; +	} else if (asi->pub->boardflags2 & BFL2_2X4_DIV) { +		asi->antsel_type = ANTSEL_2x4; +		asi->antsel_avail = true; +	} + +	/* Set the antenna selection type for the low driver */ +	brcms_b_antsel_type_set(wlc->hw, asi->antsel_type); + +	/* Init (auto/manual) antenna selection */ +	brcms_c_antsel_init_cfg(asi, &asi->antcfg_11n, true); +	brcms_c_antsel_init_cfg(asi, &asi->antcfg_cur, true); + +	return asi; +} + +void brcms_c_antsel_detach(struct antsel_info *asi) +{ +	kfree(asi); +} + +/* + * boardlevel antenna selection: + *   convert ant_cfg to mimo_antsel (ucode interface) + */ +static u16 brcms_c_antsel_antcfg2antsel(struct antsel_info *asi, u8 ant_cfg) +{ +	u8 idx = BRCMS_ANTIDX_11N(BRCMS_ANTSEL_11N(ant_cfg)); +	u16 mimo_antsel = 0; + +	if (asi->antsel_type == ANTSEL_2x4) { +		/* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ +		mimo_antsel = (mimo_2x4_div_antselpat_tbl[idx] & 0xf); +		return mimo_antsel; + +	} else if (asi->antsel_type == ANTSEL_2x3) { +		/* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ +		mimo_antsel = (mimo_2x3_div_antselpat_tbl[idx] & 0xf); +		return mimo_antsel; +	} + +	return mimo_antsel; +} + +/* boardlevel antenna selection: ucode interface control */ +static int brcms_c_antsel_cfgupd(struct antsel_info *asi, +				 struct brcms_antselcfg *antsel) +{ +	struct brcms_c_info *wlc = asi->wlc; +	u8 ant_cfg; +	u16 mimo_antsel; + +	/* 1) Update TX antconfig for all frames that are not unicast data +	 *    (aka default TX) +	 */ +	ant_cfg = antsel->ant_config[ANT_SELCFG_TX_DEF]; +	mimo_antsel = brcms_c_antsel_antcfg2antsel(asi, ant_cfg); +	brcms_b_write_shm(wlc->hw, M_MIMO_ANTSEL_TXDFLT, mimo_antsel); +	/* +	 * Update driver stats for currently selected +	 * default tx/rx antenna config +	 */ +	asi->antcfg_cur.ant_config[ANT_SELCFG_TX_DEF] = ant_cfg; + +	/* 2) Update RX antconfig for all frames that are not unicast data +	 *    (aka default RX) +	 */ +	ant_cfg = antsel->ant_config[ANT_SELCFG_RX_DEF]; +	mimo_antsel = brcms_c_antsel_antcfg2antsel(asi, ant_cfg); +	brcms_b_write_shm(wlc->hw, M_MIMO_ANTSEL_RXDFLT, mimo_antsel); +	/* +	 * Update driver stats for currently selected +	 * default tx/rx antenna config +	 */ +	asi->antcfg_cur.ant_config[ANT_SELCFG_RX_DEF] = ant_cfg; + +	return 0; +} + +void brcms_c_antsel_init(struct antsel_info *asi) +{ +	if ((asi->antsel_type == ANTSEL_2x3) || +	    (asi->antsel_type == ANTSEL_2x4)) +		brcms_c_antsel_cfgupd(asi, &asi->antcfg_11n); +} + +/* boardlevel antenna selection: convert id to ant_cfg */ +static u8 brcms_c_antsel_id2antcfg(struct antsel_info *asi, u8 id) +{ +	u8 antcfg = ANT_SELCFG_DEF_2x2; + +	if (asi->antsel_type == ANTSEL_2x4) { +		/* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ +		antcfg = (((id & 0x2) << 3) | ((id & 0x1) + 2)); +		return antcfg; + +	} else if (asi->antsel_type == ANTSEL_2x3) { +		/* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ +		antcfg = (((id & 0x02) << 4) | ((id & 0x1) + 1)); +		return antcfg; +	} + +	return antcfg; +} + +void +brcms_c_antsel_antcfg_get(struct antsel_info *asi, bool usedef, bool sel, +		      u8 antselid, u8 fbantselid, u8 *antcfg, +		      u8 *fbantcfg) +{ +	u8 ant; + +	/* if use default, assign it and return */ +	if (usedef) { +		*antcfg = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_DEF]; +		*fbantcfg = *antcfg; +		return; +	} + +	if (!sel) { +		*antcfg = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; +		*fbantcfg = *antcfg; + +	} else { +		ant = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; +		if ((ant & ANT_SELCFG_AUTO) == ANT_SELCFG_AUTO) { +			*antcfg = brcms_c_antsel_id2antcfg(asi, antselid); +			*fbantcfg = brcms_c_antsel_id2antcfg(asi, fbantselid); +		} else { +			*antcfg = +			    asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; +			*fbantcfg = *antcfg; +		} +	} +	return; +} + +/* boardlevel antenna selection: convert mimo_antsel (ucode interface) to id */ +u8 brcms_c_antsel_antsel2id(struct antsel_info *asi, u16 antsel) +{ +	u8 antselid = 0; + +	if (asi->antsel_type == ANTSEL_2x4) { +		/* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ +		antselid = mimo_2x4_div_antselid_tbl[(antsel & 0xf)]; +		return antselid; + +	} else if (asi->antsel_type == ANTSEL_2x3) { +		/* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ +		antselid = mimo_2x3_div_antselid_tbl[(antsel & 0xf)]; +		return antselid; +	} + +	return antselid; +}  | 
