Commit | Line | Data |
---|---|---|
5b435de0 AS |
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 <linux/slab.h> | |
18 | #include <net/mac80211.h> | |
19 | ||
20 | #include "types.h" | |
21 | #include "main.h" | |
22 | #include "phy_shim.h" | |
23 | #include "antsel.h" | |
24 | ||
25 | #define ANT_SELCFG_AUTO 0x80 /* bit indicates antenna sel AUTO */ | |
26 | #define ANT_SELCFG_MASK 0x33 /* antenna configuration mask */ | |
27 | #define ANT_SELCFG_TX_UNICAST 0 /* unicast tx antenna configuration */ | |
28 | #define ANT_SELCFG_RX_UNICAST 1 /* unicast rx antenna configuration */ | |
29 | #define ANT_SELCFG_TX_DEF 2 /* default tx antenna configuration */ | |
30 | #define ANT_SELCFG_RX_DEF 3 /* default rx antenna configuration */ | |
31 | ||
32 | /* useful macros */ | |
33 | #define BRCMS_ANTSEL_11N_0(ant) ((((ant) & ANT_SELCFG_MASK) >> 4) & 0xf) | |
34 | #define BRCMS_ANTSEL_11N_1(ant) (((ant) & ANT_SELCFG_MASK) & 0xf) | |
35 | #define BRCMS_ANTIDX_11N(ant) (((BRCMS_ANTSEL_11N_0(ant)) << 2) +\ | |
36 | (BRCMS_ANTSEL_11N_1(ant))) | |
37 | #define BRCMS_ANT_ISAUTO_11N(ant) (((ant) & ANT_SELCFG_AUTO) == ANT_SELCFG_AUTO) | |
38 | #define BRCMS_ANTSEL_11N(ant) ((ant) & ANT_SELCFG_MASK) | |
39 | ||
40 | /* antenna switch */ | |
41 | /* defines for no boardlevel antenna diversity */ | |
42 | #define ANT_SELCFG_DEF_2x2 0x01 /* default antenna configuration */ | |
43 | ||
44 | /* 2x3 antdiv defines and tables for GPIO communication */ | |
45 | #define ANT_SELCFG_NUM_2x3 3 | |
46 | #define ANT_SELCFG_DEF_2x3 0x01 /* default antenna configuration */ | |
47 | ||
48 | /* 2x4 antdiv rev4 defines and tables for GPIO communication */ | |
49 | #define ANT_SELCFG_NUM_2x4 4 | |
50 | #define ANT_SELCFG_DEF_2x4 0x02 /* default antenna configuration */ | |
51 | ||
52 | static const u16 mimo_2x4_div_antselpat_tbl[] = { | |
53 | 0, 0, 0x9, 0xa, /* ant0: 0 ant1: 2,3 */ | |
54 | 0, 0, 0x5, 0x6, /* ant0: 1 ant1: 2,3 */ | |
55 | 0, 0, 0, 0, /* n.a. */ | |
56 | 0, 0, 0, 0 /* n.a. */ | |
57 | }; | |
58 | ||
59 | static const u8 mimo_2x4_div_antselid_tbl[16] = { | |
60 | 0, 0, 0, 0, 0, 2, 3, 0, | |
61 | 0, 0, 1, 0, 0, 0, 0, 0 /* pat to antselid */ | |
62 | }; | |
63 | ||
64 | static const u16 mimo_2x3_div_antselpat_tbl[] = { | |
65 | 16, 0, 1, 16, /* ant0: 0 ant1: 1,2 */ | |
66 | 16, 16, 16, 16, /* n.a. */ | |
67 | 16, 2, 16, 16, /* ant0: 2 ant1: 1 */ | |
68 | 16, 16, 16, 16 /* n.a. */ | |
69 | }; | |
70 | ||
71 | static const u8 mimo_2x3_div_antselid_tbl[16] = { | |
72 | 0, 1, 2, 0, 0, 0, 0, 0, | |
73 | 0, 0, 0, 0, 0, 0, 0, 0 /* pat to antselid */ | |
74 | }; | |
75 | ||
76 | /* boardlevel antenna selection: init antenna selection structure */ | |
77 | static void | |
78 | brcms_c_antsel_init_cfg(struct antsel_info *asi, struct brcms_antselcfg *antsel, | |
79 | bool auto_sel) | |
80 | { | |
81 | if (asi->antsel_type == ANTSEL_2x3) { | |
82 | u8 antcfg_def = ANT_SELCFG_DEF_2x3 | | |
83 | ((asi->antsel_avail && auto_sel) ? ANT_SELCFG_AUTO : 0); | |
84 | antsel->ant_config[ANT_SELCFG_TX_DEF] = antcfg_def; | |
85 | antsel->ant_config[ANT_SELCFG_TX_UNICAST] = antcfg_def; | |
86 | antsel->ant_config[ANT_SELCFG_RX_DEF] = antcfg_def; | |
87 | antsel->ant_config[ANT_SELCFG_RX_UNICAST] = antcfg_def; | |
88 | antsel->num_antcfg = ANT_SELCFG_NUM_2x3; | |
89 | ||
90 | } else if (asi->antsel_type == ANTSEL_2x4) { | |
91 | ||
92 | antsel->ant_config[ANT_SELCFG_TX_DEF] = ANT_SELCFG_DEF_2x4; | |
93 | antsel->ant_config[ANT_SELCFG_TX_UNICAST] = ANT_SELCFG_DEF_2x4; | |
94 | antsel->ant_config[ANT_SELCFG_RX_DEF] = ANT_SELCFG_DEF_2x4; | |
95 | antsel->ant_config[ANT_SELCFG_RX_UNICAST] = ANT_SELCFG_DEF_2x4; | |
96 | antsel->num_antcfg = ANT_SELCFG_NUM_2x4; | |
97 | ||
98 | } else { /* no antenna selection available */ | |
99 | ||
100 | antsel->ant_config[ANT_SELCFG_TX_DEF] = ANT_SELCFG_DEF_2x2; | |
101 | antsel->ant_config[ANT_SELCFG_TX_UNICAST] = ANT_SELCFG_DEF_2x2; | |
102 | antsel->ant_config[ANT_SELCFG_RX_DEF] = ANT_SELCFG_DEF_2x2; | |
103 | antsel->ant_config[ANT_SELCFG_RX_UNICAST] = ANT_SELCFG_DEF_2x2; | |
104 | antsel->num_antcfg = 0; | |
105 | } | |
106 | } | |
107 | ||
108 | struct antsel_info *brcms_c_antsel_attach(struct brcms_c_info *wlc) | |
109 | { | |
110 | struct antsel_info *asi; | |
898d3c3b | 111 | struct ssb_sprom *sprom = &wlc->hw->d11core->bus->sprom; |
5b435de0 AS |
112 | |
113 | asi = kzalloc(sizeof(struct antsel_info), GFP_ATOMIC); | |
114 | if (!asi) | |
115 | return NULL; | |
116 | ||
117 | asi->wlc = wlc; | |
118 | asi->pub = wlc->pub; | |
119 | asi->antsel_type = ANTSEL_NA; | |
120 | asi->antsel_avail = false; | |
898d3c3b | 121 | asi->antsel_antswitch = sprom->antswitch; |
5b435de0 AS |
122 | |
123 | if ((asi->pub->sromrev >= 4) && (asi->antsel_antswitch != 0)) { | |
124 | switch (asi->antsel_antswitch) { | |
125 | case ANTSWITCH_TYPE_1: | |
126 | case ANTSWITCH_TYPE_2: | |
127 | case ANTSWITCH_TYPE_3: | |
128 | /* 4321/2 board with 2x3 switch logic */ | |
129 | asi->antsel_type = ANTSEL_2x3; | |
130 | /* Antenna selection availability */ | |
898d3c3b HM |
131 | if ((sprom->ant_available_bg == 7) || |
132 | (sprom->ant_available_a == 7)) { | |
5b435de0 AS |
133 | asi->antsel_avail = true; |
134 | } else if ( | |
898d3c3b HM |
135 | sprom->ant_available_bg == 3 || |
136 | sprom->ant_available_a == 3) { | |
5b435de0 AS |
137 | asi->antsel_avail = false; |
138 | } else { | |
139 | asi->antsel_avail = false; | |
140 | wiphy_err(wlc->wiphy, "antsel_attach: 2o3 " | |
141 | "board cfg invalid\n"); | |
142 | } | |
143 | ||
144 | break; | |
145 | default: | |
146 | break; | |
147 | } | |
148 | } else if ((asi->pub->sromrev == 4) && | |
898d3c3b HM |
149 | (sprom->ant_available_bg == 7) && |
150 | (sprom->ant_available_a == 0)) { | |
5b435de0 AS |
151 | /* hack to match old 4321CB2 cards with 2of3 antenna switch */ |
152 | asi->antsel_type = ANTSEL_2x3; | |
153 | asi->antsel_avail = true; | |
154 | } else if (asi->pub->boardflags2 & BFL2_2X4_DIV) { | |
155 | asi->antsel_type = ANTSEL_2x4; | |
156 | asi->antsel_avail = true; | |
157 | } | |
158 | ||
159 | /* Set the antenna selection type for the low driver */ | |
160 | brcms_b_antsel_type_set(wlc->hw, asi->antsel_type); | |
161 | ||
162 | /* Init (auto/manual) antenna selection */ | |
163 | brcms_c_antsel_init_cfg(asi, &asi->antcfg_11n, true); | |
164 | brcms_c_antsel_init_cfg(asi, &asi->antcfg_cur, true); | |
165 | ||
166 | return asi; | |
167 | } | |
168 | ||
169 | void brcms_c_antsel_detach(struct antsel_info *asi) | |
170 | { | |
171 | kfree(asi); | |
172 | } | |
173 | ||
174 | /* | |
175 | * boardlevel antenna selection: | |
176 | * convert ant_cfg to mimo_antsel (ucode interface) | |
177 | */ | |
178 | static u16 brcms_c_antsel_antcfg2antsel(struct antsel_info *asi, u8 ant_cfg) | |
179 | { | |
180 | u8 idx = BRCMS_ANTIDX_11N(BRCMS_ANTSEL_11N(ant_cfg)); | |
181 | u16 mimo_antsel = 0; | |
182 | ||
183 | if (asi->antsel_type == ANTSEL_2x4) { | |
184 | /* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ | |
185 | mimo_antsel = (mimo_2x4_div_antselpat_tbl[idx] & 0xf); | |
186 | return mimo_antsel; | |
187 | ||
188 | } else if (asi->antsel_type == ANTSEL_2x3) { | |
189 | /* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ | |
190 | mimo_antsel = (mimo_2x3_div_antselpat_tbl[idx] & 0xf); | |
191 | return mimo_antsel; | |
192 | } | |
193 | ||
194 | return mimo_antsel; | |
195 | } | |
196 | ||
197 | /* boardlevel antenna selection: ucode interface control */ | |
198 | static int brcms_c_antsel_cfgupd(struct antsel_info *asi, | |
199 | struct brcms_antselcfg *antsel) | |
200 | { | |
201 | struct brcms_c_info *wlc = asi->wlc; | |
202 | u8 ant_cfg; | |
203 | u16 mimo_antsel; | |
204 | ||
205 | /* 1) Update TX antconfig for all frames that are not unicast data | |
206 | * (aka default TX) | |
207 | */ | |
208 | ant_cfg = antsel->ant_config[ANT_SELCFG_TX_DEF]; | |
209 | mimo_antsel = brcms_c_antsel_antcfg2antsel(asi, ant_cfg); | |
210 | brcms_b_write_shm(wlc->hw, M_MIMO_ANTSEL_TXDFLT, mimo_antsel); | |
211 | /* | |
212 | * Update driver stats for currently selected | |
213 | * default tx/rx antenna config | |
214 | */ | |
215 | asi->antcfg_cur.ant_config[ANT_SELCFG_TX_DEF] = ant_cfg; | |
216 | ||
217 | /* 2) Update RX antconfig for all frames that are not unicast data | |
218 | * (aka default RX) | |
219 | */ | |
220 | ant_cfg = antsel->ant_config[ANT_SELCFG_RX_DEF]; | |
221 | mimo_antsel = brcms_c_antsel_antcfg2antsel(asi, ant_cfg); | |
222 | brcms_b_write_shm(wlc->hw, M_MIMO_ANTSEL_RXDFLT, mimo_antsel); | |
223 | /* | |
224 | * Update driver stats for currently selected | |
225 | * default tx/rx antenna config | |
226 | */ | |
227 | asi->antcfg_cur.ant_config[ANT_SELCFG_RX_DEF] = ant_cfg; | |
228 | ||
229 | return 0; | |
230 | } | |
231 | ||
232 | void brcms_c_antsel_init(struct antsel_info *asi) | |
233 | { | |
234 | if ((asi->antsel_type == ANTSEL_2x3) || | |
235 | (asi->antsel_type == ANTSEL_2x4)) | |
236 | brcms_c_antsel_cfgupd(asi, &asi->antcfg_11n); | |
237 | } | |
238 | ||
239 | /* boardlevel antenna selection: convert id to ant_cfg */ | |
240 | static u8 brcms_c_antsel_id2antcfg(struct antsel_info *asi, u8 id) | |
241 | { | |
242 | u8 antcfg = ANT_SELCFG_DEF_2x2; | |
243 | ||
244 | if (asi->antsel_type == ANTSEL_2x4) { | |
245 | /* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ | |
246 | antcfg = (((id & 0x2) << 3) | ((id & 0x1) + 2)); | |
247 | return antcfg; | |
248 | ||
249 | } else if (asi->antsel_type == ANTSEL_2x3) { | |
250 | /* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ | |
251 | antcfg = (((id & 0x02) << 4) | ((id & 0x1) + 1)); | |
252 | return antcfg; | |
253 | } | |
254 | ||
255 | return antcfg; | |
256 | } | |
257 | ||
258 | void | |
259 | brcms_c_antsel_antcfg_get(struct antsel_info *asi, bool usedef, bool sel, | |
260 | u8 antselid, u8 fbantselid, u8 *antcfg, | |
261 | u8 *fbantcfg) | |
262 | { | |
263 | u8 ant; | |
264 | ||
265 | /* if use default, assign it and return */ | |
266 | if (usedef) { | |
267 | *antcfg = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_DEF]; | |
268 | *fbantcfg = *antcfg; | |
269 | return; | |
270 | } | |
271 | ||
272 | if (!sel) { | |
273 | *antcfg = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; | |
274 | *fbantcfg = *antcfg; | |
275 | ||
276 | } else { | |
277 | ant = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; | |
278 | if ((ant & ANT_SELCFG_AUTO) == ANT_SELCFG_AUTO) { | |
279 | *antcfg = brcms_c_antsel_id2antcfg(asi, antselid); | |
280 | *fbantcfg = brcms_c_antsel_id2antcfg(asi, fbantselid); | |
281 | } else { | |
282 | *antcfg = | |
283 | asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; | |
284 | *fbantcfg = *antcfg; | |
285 | } | |
286 | } | |
287 | return; | |
288 | } | |
289 | ||
290 | /* boardlevel antenna selection: convert mimo_antsel (ucode interface) to id */ | |
291 | u8 brcms_c_antsel_antsel2id(struct antsel_info *asi, u16 antsel) | |
292 | { | |
293 | u8 antselid = 0; | |
294 | ||
295 | if (asi->antsel_type == ANTSEL_2x4) { | |
296 | /* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ | |
297 | antselid = mimo_2x4_div_antselid_tbl[(antsel & 0xf)]; | |
298 | return antselid; | |
299 | ||
300 | } else if (asi->antsel_type == ANTSEL_2x3) { | |
301 | /* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ | |
302 | antselid = mimo_2x3_div_antselid_tbl[(antsel & 0xf)]; | |
303 | return antselid; | |
304 | } | |
305 | ||
306 | return antselid; | |
307 | } |