Commit | Line | Data |
---|---|---|
a9533e7e HP |
1 | /* |
2 | * Copyright (c) 2010 Broadcom Corporation | |
3 | * | |
4 | * Permission to use, copy, modify, and/or distribute this software for any | |
5 | * purpose with or without fee is hereby granted, provided that the above | |
6 | * copyright notice and this permission notice appear in all copies. | |
7 | * | |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY | |
11 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION | |
13 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |
14 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
15 | */ | |
16 | ||
17 | #include <wlc_cfg.h> | |
18 | ||
3327989a | 19 | #include <linux/kernel.h> |
c6ac24e9 BR |
20 | #include <linux/module.h> |
21 | #include <linux/pci.h> | |
45575664 | 22 | |
45575664 | 23 | #include <bcmdefs.h> |
a9533e7e HP |
24 | #include <bcmutils.h> |
25 | #include <siutils.h> | |
a52ba66c | 26 | #include <bcmdevs.h> |
a52ba66c | 27 | #include <sbhnddma.h> |
45575664 AS |
28 | #include <wlioctl.h> |
29 | ||
45575664 AS |
30 | #include "d11.h" |
31 | #include "wlc_rate.h" | |
32 | #include "wlc_key.h" | |
33 | #include "wlc_scb.h" | |
34 | #include "wlc_pub.h" | |
35 | #include "wl_dbg.h" | |
36 | #include "phy/wlc_phy_hal.h" | |
37 | #include "wlc_bmac.h" | |
38 | #include "wlc_channel.h" | |
39 | #include "wlc_bsscfg.h" | |
7ae92624 | 40 | #include "wlc_main.h" |
45575664 AS |
41 | #include "wl_export.h" |
42 | #include "wlc_phy_shim.h" | |
43 | #include "wlc_antsel.h" | |
a9533e7e HP |
44 | |
45 | /* useful macros */ | |
46 | #define WLC_ANTSEL_11N_0(ant) ((((ant) & ANT_SELCFG_MASK) >> 4) & 0xf) | |
47 | #define WLC_ANTSEL_11N_1(ant) (((ant) & ANT_SELCFG_MASK) & 0xf) | |
48 | #define WLC_ANTIDX_11N(ant) (((WLC_ANTSEL_11N_0(ant)) << 2) + (WLC_ANTSEL_11N_1(ant))) | |
49 | #define WLC_ANT_ISAUTO_11N(ant) (((ant) & ANT_SELCFG_AUTO) == ANT_SELCFG_AUTO) | |
50 | #define WLC_ANTSEL_11N(ant) ((ant) & ANT_SELCFG_MASK) | |
51 | ||
52 | /* antenna switch */ | |
53 | /* defines for no boardlevel antenna diversity */ | |
54 | #define ANT_SELCFG_DEF_2x2 0x01 /* default antenna configuration */ | |
55 | ||
56 | /* 2x3 antdiv defines and tables for GPIO communication */ | |
57 | #define ANT_SELCFG_NUM_2x3 3 | |
58 | #define ANT_SELCFG_DEF_2x3 0x01 /* default antenna configuration */ | |
59 | ||
60 | /* 2x4 antdiv rev4 defines and tables for GPIO communication */ | |
61 | #define ANT_SELCFG_NUM_2x4 4 | |
62 | #define ANT_SELCFG_DEF_2x4 0x02 /* default antenna configuration */ | |
63 | ||
64 | /* static functions */ | |
299f8a46 RV |
65 | static int wlc_antsel_cfgupd(struct antsel_info *asi, wlc_antselcfg_t *antsel); |
66 | static u8 wlc_antsel_id2antcfg(struct antsel_info *asi, u8 id); | |
67 | static u16 wlc_antsel_antcfg2antsel(struct antsel_info *asi, u8 ant_cfg); | |
68 | static void wlc_antsel_init_cfg(struct antsel_info *asi, | |
69 | wlc_antselcfg_t *antsel, | |
a9533e7e HP |
70 | bool auto_sel); |
71 | ||
7d4df48e | 72 | const u16 mimo_2x4_div_antselpat_tbl[] = { |
a9533e7e HP |
73 | 0, 0, 0x9, 0xa, /* ant0: 0 ant1: 2,3 */ |
74 | 0, 0, 0x5, 0x6, /* ant0: 1 ant1: 2,3 */ | |
75 | 0, 0, 0, 0, /* n.a. */ | |
76 | 0, 0, 0, 0 /* n.a. */ | |
77 | }; | |
78 | ||
41feb5ed | 79 | const u8 mimo_2x4_div_antselid_tbl[16] = { |
a9533e7e HP |
80 | 0, 0, 0, 0, 0, 2, 3, 0, |
81 | 0, 0, 1, 0, 0, 0, 0, 0 /* pat to antselid */ | |
82 | }; | |
83 | ||
7d4df48e | 84 | const u16 mimo_2x3_div_antselpat_tbl[] = { |
a9533e7e HP |
85 | 16, 0, 1, 16, /* ant0: 0 ant1: 1,2 */ |
86 | 16, 16, 16, 16, /* n.a. */ | |
87 | 16, 2, 16, 16, /* ant0: 2 ant1: 1 */ | |
88 | 16, 16, 16, 16 /* n.a. */ | |
89 | }; | |
90 | ||
41feb5ed | 91 | const u8 mimo_2x3_div_antselid_tbl[16] = { |
a9533e7e HP |
92 | 0, 1, 2, 0, 0, 0, 0, 0, |
93 | 0, 0, 0, 0, 0, 0, 0, 0 /* pat to antselid */ | |
94 | }; | |
95 | ||
d769f4ce AS |
96 | struct antsel_info *wlc_antsel_attach(struct wlc_info *wlc) |
97 | { | |
299f8a46 | 98 | struct antsel_info *asi; |
a9533e7e | 99 | |
299f8a46 | 100 | asi = kzalloc(sizeof(struct antsel_info), GFP_ATOMIC); |
ca8c1e59 | 101 | if (!asi) { |
d769f4ce AS |
102 | WL_ERROR("wl%d: wlc_antsel_attach: out of mem\n", |
103 | wlc->pub->unit); | |
a9533e7e HP |
104 | return NULL; |
105 | } | |
106 | ||
a9533e7e | 107 | asi->wlc = wlc; |
d769f4ce | 108 | asi->pub = wlc->pub; |
a9533e7e | 109 | asi->antsel_type = ANTSEL_NA; |
0965ae88 | 110 | asi->antsel_avail = false; |
41feb5ed | 111 | asi->antsel_antswitch = (u8) getintvar(asi->pub->vars, "antswitch"); |
a9533e7e HP |
112 | |
113 | if ((asi->pub->sromrev >= 4) && (asi->antsel_antswitch != 0)) { | |
114 | switch (asi->antsel_antswitch) { | |
115 | case ANTSWITCH_TYPE_1: | |
116 | case ANTSWITCH_TYPE_2: | |
117 | case ANTSWITCH_TYPE_3: | |
118 | /* 4321/2 board with 2x3 switch logic */ | |
119 | asi->antsel_type = ANTSEL_2x3; | |
120 | /* Antenna selection availability */ | |
7d4df48e GKH |
121 | if (((u16) getintvar(asi->pub->vars, "aa2g") == 7) || |
122 | ((u16) getintvar(asi->pub->vars, "aa5g") == 7)) { | |
0f0881b0 | 123 | asi->antsel_avail = true; |
a9533e7e | 124 | } else |
7d4df48e | 125 | if (((u16) getintvar(asi->pub->vars, "aa2g") == |
a9533e7e | 126 | 3) |
7d4df48e | 127 | || ((u16) getintvar(asi->pub->vars, "aa5g") |
a9533e7e | 128 | == 3)) { |
0965ae88 | 129 | asi->antsel_avail = false; |
a9533e7e | 130 | } else { |
0965ae88 | 131 | asi->antsel_avail = false; |
f4528696 | 132 | WL_ERROR("wlc_antsel_attach: 2o3 board cfg invalid\n"); |
a9533e7e HP |
133 | ASSERT(0); |
134 | } | |
135 | break; | |
136 | default: | |
137 | break; | |
138 | } | |
139 | } else if ((asi->pub->sromrev == 4) && | |
7d4df48e GKH |
140 | ((u16) getintvar(asi->pub->vars, "aa2g") == 7) && |
141 | ((u16) getintvar(asi->pub->vars, "aa5g") == 0)) { | |
a9533e7e HP |
142 | /* hack to match old 4321CB2 cards with 2of3 antenna switch */ |
143 | asi->antsel_type = ANTSEL_2x3; | |
0f0881b0 | 144 | asi->antsel_avail = true; |
a9533e7e HP |
145 | } else if (asi->pub->boardflags2 & BFL2_2X4_DIV) { |
146 | asi->antsel_type = ANTSEL_2x4; | |
0f0881b0 | 147 | asi->antsel_avail = true; |
a9533e7e HP |
148 | } |
149 | ||
150 | /* Set the antenna selection type for the low driver */ | |
d769f4ce | 151 | wlc_bmac_antsel_type_set(wlc->hw, asi->antsel_type); |
a9533e7e HP |
152 | |
153 | /* Init (auto/manual) antenna selection */ | |
0f0881b0 GKH |
154 | wlc_antsel_init_cfg(asi, &asi->antcfg_11n, true); |
155 | wlc_antsel_init_cfg(asi, &asi->antcfg_cur, true); | |
a9533e7e HP |
156 | |
157 | return asi; | |
158 | } | |
159 | ||
299f8a46 | 160 | void wlc_antsel_detach(struct antsel_info *asi) |
a2627bc0 | 161 | { |
182acb3c | 162 | kfree(asi); |
a9533e7e HP |
163 | } |
164 | ||
299f8a46 | 165 | void wlc_antsel_init(struct antsel_info *asi) |
a9533e7e HP |
166 | { |
167 | if ((asi->antsel_type == ANTSEL_2x3) || | |
168 | (asi->antsel_type == ANTSEL_2x4)) | |
169 | wlc_antsel_cfgupd(asi, &asi->antcfg_11n); | |
170 | } | |
171 | ||
172 | /* boardlevel antenna selection: init antenna selection structure */ | |
173 | static void | |
299f8a46 | 174 | wlc_antsel_init_cfg(struct antsel_info *asi, wlc_antselcfg_t *antsel, |
a9533e7e HP |
175 | bool auto_sel) |
176 | { | |
177 | if (asi->antsel_type == ANTSEL_2x3) { | |
41feb5ed | 178 | u8 antcfg_def = ANT_SELCFG_DEF_2x3 | |
a9533e7e HP |
179 | ((asi->antsel_avail && auto_sel) ? ANT_SELCFG_AUTO : 0); |
180 | antsel->ant_config[ANT_SELCFG_TX_DEF] = antcfg_def; | |
181 | antsel->ant_config[ANT_SELCFG_TX_UNICAST] = antcfg_def; | |
182 | antsel->ant_config[ANT_SELCFG_RX_DEF] = antcfg_def; | |
183 | antsel->ant_config[ANT_SELCFG_RX_UNICAST] = antcfg_def; | |
184 | antsel->num_antcfg = ANT_SELCFG_NUM_2x3; | |
185 | ||
186 | } else if (asi->antsel_type == ANTSEL_2x4) { | |
187 | ||
188 | antsel->ant_config[ANT_SELCFG_TX_DEF] = ANT_SELCFG_DEF_2x4; | |
189 | antsel->ant_config[ANT_SELCFG_TX_UNICAST] = ANT_SELCFG_DEF_2x4; | |
190 | antsel->ant_config[ANT_SELCFG_RX_DEF] = ANT_SELCFG_DEF_2x4; | |
191 | antsel->ant_config[ANT_SELCFG_RX_UNICAST] = ANT_SELCFG_DEF_2x4; | |
192 | antsel->num_antcfg = ANT_SELCFG_NUM_2x4; | |
193 | ||
194 | } else { /* no antenna selection available */ | |
195 | ||
196 | antsel->ant_config[ANT_SELCFG_TX_DEF] = ANT_SELCFG_DEF_2x2; | |
197 | antsel->ant_config[ANT_SELCFG_TX_UNICAST] = ANT_SELCFG_DEF_2x2; | |
198 | antsel->ant_config[ANT_SELCFG_RX_DEF] = ANT_SELCFG_DEF_2x2; | |
199 | antsel->ant_config[ANT_SELCFG_RX_UNICAST] = ANT_SELCFG_DEF_2x2; | |
200 | antsel->num_antcfg = 0; | |
201 | } | |
202 | } | |
203 | ||
204 | void BCMFASTPATH | |
299f8a46 | 205 | wlc_antsel_antcfg_get(struct antsel_info *asi, bool usedef, bool sel, |
41feb5ed GKH |
206 | u8 antselid, u8 fbantselid, u8 *antcfg, |
207 | u8 *fbantcfg) | |
a9533e7e | 208 | { |
41feb5ed | 209 | u8 ant; |
a9533e7e HP |
210 | |
211 | /* if use default, assign it and return */ | |
212 | if (usedef) { | |
213 | *antcfg = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_DEF]; | |
214 | *fbantcfg = *antcfg; | |
215 | return; | |
216 | } | |
217 | ||
218 | if (!sel) { | |
219 | *antcfg = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; | |
220 | *fbantcfg = *antcfg; | |
221 | ||
222 | } else { | |
223 | ant = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; | |
224 | if ((ant & ANT_SELCFG_AUTO) == ANT_SELCFG_AUTO) { | |
225 | *antcfg = wlc_antsel_id2antcfg(asi, antselid); | |
226 | *fbantcfg = wlc_antsel_id2antcfg(asi, fbantselid); | |
227 | } else { | |
228 | *antcfg = | |
229 | asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; | |
230 | *fbantcfg = *antcfg; | |
231 | } | |
232 | } | |
233 | return; | |
234 | } | |
235 | ||
236 | /* boardlevel antenna selection: convert mimo_antsel (ucode interface) to id */ | |
299f8a46 | 237 | u8 wlc_antsel_antsel2id(struct antsel_info *asi, u16 antsel) |
a9533e7e | 238 | { |
41feb5ed | 239 | u8 antselid = 0; |
a9533e7e HP |
240 | |
241 | if (asi->antsel_type == ANTSEL_2x4) { | |
242 | /* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ | |
243 | antselid = mimo_2x4_div_antselid_tbl[(antsel & 0xf)]; | |
244 | return antselid; | |
245 | ||
246 | } else if (asi->antsel_type == ANTSEL_2x3) { | |
247 | /* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ | |
248 | antselid = mimo_2x3_div_antselid_tbl[(antsel & 0xf)]; | |
249 | return antselid; | |
250 | } | |
251 | ||
252 | return antselid; | |
253 | } | |
254 | ||
255 | /* boardlevel antenna selection: convert id to ant_cfg */ | |
299f8a46 | 256 | static u8 wlc_antsel_id2antcfg(struct antsel_info *asi, u8 id) |
a9533e7e | 257 | { |
41feb5ed | 258 | u8 antcfg = ANT_SELCFG_DEF_2x2; |
a9533e7e HP |
259 | |
260 | if (asi->antsel_type == ANTSEL_2x4) { | |
261 | /* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ | |
262 | antcfg = (((id & 0x2) << 3) | ((id & 0x1) + 2)); | |
263 | return antcfg; | |
264 | ||
265 | } else if (asi->antsel_type == ANTSEL_2x3) { | |
266 | /* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ | |
267 | antcfg = (((id & 0x02) << 4) | ((id & 0x1) + 1)); | |
268 | return antcfg; | |
269 | } | |
270 | ||
271 | return antcfg; | |
272 | } | |
273 | ||
274 | /* boardlevel antenna selection: convert ant_cfg to mimo_antsel (ucode interface) */ | |
299f8a46 | 275 | static u16 wlc_antsel_antcfg2antsel(struct antsel_info *asi, u8 ant_cfg) |
a9533e7e | 276 | { |
41feb5ed | 277 | u8 idx = WLC_ANTIDX_11N(WLC_ANTSEL_11N(ant_cfg)); |
7d4df48e | 278 | u16 mimo_antsel = 0; |
a9533e7e HP |
279 | |
280 | if (asi->antsel_type == ANTSEL_2x4) { | |
281 | /* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ | |
282 | mimo_antsel = (mimo_2x4_div_antselpat_tbl[idx] & 0xf); | |
283 | return mimo_antsel; | |
284 | ||
285 | } else if (asi->antsel_type == ANTSEL_2x3) { | |
286 | /* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ | |
287 | mimo_antsel = (mimo_2x3_div_antselpat_tbl[idx] & 0xf); | |
288 | return mimo_antsel; | |
289 | } | |
290 | ||
291 | return mimo_antsel; | |
292 | } | |
293 | ||
294 | /* boardlevel antenna selection: ucode interface control */ | |
299f8a46 | 295 | static int wlc_antsel_cfgupd(struct antsel_info *asi, wlc_antselcfg_t *antsel) |
a9533e7e | 296 | { |
c6a9e1fc | 297 | struct wlc_info *wlc = asi->wlc; |
41feb5ed | 298 | u8 ant_cfg; |
7d4df48e | 299 | u16 mimo_antsel; |
a9533e7e HP |
300 | |
301 | ASSERT(asi->antsel_type != ANTSEL_NA); | |
302 | ||
303 | /* 1) Update TX antconfig for all frames that are not unicast data | |
304 | * (aka default TX) | |
305 | */ | |
306 | ant_cfg = antsel->ant_config[ANT_SELCFG_TX_DEF]; | |
307 | mimo_antsel = wlc_antsel_antcfg2antsel(asi, ant_cfg); | |
308 | wlc_write_shm(wlc, M_MIMO_ANTSEL_TXDFLT, mimo_antsel); | |
309 | /* Update driver stats for currently selected default tx/rx antenna config */ | |
310 | asi->antcfg_cur.ant_config[ANT_SELCFG_TX_DEF] = ant_cfg; | |
311 | ||
312 | /* 2) Update RX antconfig for all frames that are not unicast data | |
313 | * (aka default RX) | |
314 | */ | |
315 | ant_cfg = antsel->ant_config[ANT_SELCFG_RX_DEF]; | |
316 | mimo_antsel = wlc_antsel_antcfg2antsel(asi, ant_cfg); | |
317 | wlc_write_shm(wlc, M_MIMO_ANTSEL_RXDFLT, mimo_antsel); | |
318 | /* Update driver stats for currently selected default tx/rx antenna config */ | |
319 | asi->antcfg_cur.ant_config[ANT_SELCFG_RX_DEF] = ant_cfg; | |
320 | ||
321 | return 0; | |
322 | } |