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" |
| 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; |
| 111 | struct si_pub *sih = wlc->hw->sih; |
| 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; |
| 121 | asi->antsel_antswitch = (u8) getintvar(sih, BRCMS_SROM_ANTSWITCH); |
| 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 */ |
| 131 | if (((u16) getintvar(sih, BRCMS_SROM_AA2G) == 7) || |
| 132 | ((u16) getintvar(sih, BRCMS_SROM_AA5G) == 7)) { |
| 133 | asi->antsel_avail = true; |
| 134 | } else if ( |
| 135 | (u16) getintvar(sih, BRCMS_SROM_AA2G) == 3 || |
| 136 | (u16) getintvar(sih, BRCMS_SROM_AA5G) == 3) { |
| 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) && |
| 149 | ((u16) getintvar(sih, BRCMS_SROM_AA2G) == 7) && |
| 150 | ((u16) getintvar(sih, BRCMS_SROM_AA5G) == 0)) { |
| 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 | } |