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