| Wey-Yi Guy | 3be63ff | 2010-10-08 16:05:19 -0700 | [diff] [blame] | 1 | /****************************************************************************** | 
|  | 2 | * | 
|  | 3 | * This file is provided under a dual BSD/GPLv2 license.  When using or | 
|  | 4 | * redistributing this file, you may do so under either license. | 
|  | 5 | * | 
|  | 6 | * GPL LICENSE SUMMARY | 
|  | 7 | * | 
| Wey-Yi Guy | 901069c | 2011-04-05 09:42:00 -0700 | [diff] [blame] | 8 | * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. | 
| Wey-Yi Guy | 3be63ff | 2010-10-08 16:05:19 -0700 | [diff] [blame] | 9 | * | 
|  | 10 | * This program is free software; you can redistribute it and/or modify | 
|  | 11 | * it under the terms of version 2 of the GNU General Public License as | 
|  | 12 | * published by the Free Software Foundation. | 
|  | 13 | * | 
|  | 14 | * This program is distributed in the hope that it will be useful, but | 
|  | 15 | * WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | 17 | * General Public License for more details. | 
|  | 18 | * | 
|  | 19 | * You should have received a copy of the GNU General Public License | 
|  | 20 | * along with this program; if not, write to the Free Software | 
|  | 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, | 
|  | 22 | * USA | 
|  | 23 | * | 
|  | 24 | * The full GNU General Public License is included in this distribution | 
|  | 25 | * in the file called LICENSE.GPL. | 
|  | 26 | * | 
|  | 27 | * Contact Information: | 
|  | 28 | *  Intel Linux Wireless <ilw@linux.intel.com> | 
|  | 29 | * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 | 
|  | 30 | * | 
|  | 31 | * BSD LICENSE | 
|  | 32 | * | 
| Wey-Yi Guy | 901069c | 2011-04-05 09:42:00 -0700 | [diff] [blame] | 33 | * Copyright(c) 2005 - 2011 Intel Corporation. All rights reserved. | 
| Wey-Yi Guy | 3be63ff | 2010-10-08 16:05:19 -0700 | [diff] [blame] | 34 | * All rights reserved. | 
|  | 35 | * | 
|  | 36 | * Redistribution and use in source and binary forms, with or without | 
|  | 37 | * modification, are permitted provided that the following conditions | 
|  | 38 | * are met: | 
|  | 39 | * | 
|  | 40 | *  * Redistributions of source code must retain the above copyright | 
|  | 41 | *    notice, this list of conditions and the following disclaimer. | 
|  | 42 | *  * Redistributions in binary form must reproduce the above copyright | 
|  | 43 | *    notice, this list of conditions and the following disclaimer in | 
|  | 44 | *    the documentation and/or other materials provided with the | 
|  | 45 | *    distribution. | 
|  | 46 | *  * Neither the name Intel Corporation nor the names of its | 
|  | 47 | *    contributors may be used to endorse or promote products derived | 
|  | 48 | *    from this software without specific prior written permission. | 
|  | 49 | * | 
|  | 50 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
|  | 51 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
|  | 52 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
|  | 53 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
|  | 54 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
|  | 55 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
|  | 56 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
|  | 57 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
|  | 58 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|  | 59 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | 60 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  | 61 | *****************************************************************************/ | 
|  | 62 |  | 
|  | 63 |  | 
|  | 64 | #include <linux/kernel.h> | 
|  | 65 | #include <linux/module.h> | 
|  | 66 | #include <linux/slab.h> | 
|  | 67 | #include <linux/init.h> | 
|  | 68 |  | 
|  | 69 | #include <net/mac80211.h> | 
|  | 70 |  | 
|  | 71 | #include "iwl-commands.h" | 
|  | 72 | #include "iwl-dev.h" | 
|  | 73 | #include "iwl-core.h" | 
|  | 74 | #include "iwl-debug.h" | 
|  | 75 | #include "iwl-agn.h" | 
|  | 76 | #include "iwl-io.h" | 
|  | 77 |  | 
| Wey-Yi Guy | 3be63ff | 2010-10-08 16:05:19 -0700 | [diff] [blame] | 78 | /****************************************************************************** | 
|  | 79 | * | 
|  | 80 | * EEPROM related functions | 
|  | 81 | * | 
|  | 82 | ******************************************************************************/ | 
|  | 83 |  | 
| Wey-Yi Guy | 3be63ff | 2010-10-08 16:05:19 -0700 | [diff] [blame] | 84 | int iwl_eeprom_check_version(struct iwl_priv *priv) | 
|  | 85 | { | 
|  | 86 | u16 eeprom_ver; | 
|  | 87 | u16 calib_ver; | 
|  | 88 |  | 
|  | 89 | eeprom_ver = iwl_eeprom_query16(priv, EEPROM_VERSION); | 
| Don Fry | 16b80b7 | 2011-04-20 15:25:14 -0700 | [diff] [blame] | 90 | calib_ver = iwlagn_eeprom_calib_version(priv); | 
| Wey-Yi Guy | 3be63ff | 2010-10-08 16:05:19 -0700 | [diff] [blame] | 91 |  | 
|  | 92 | if (eeprom_ver < priv->cfg->eeprom_ver || | 
|  | 93 | calib_ver < priv->cfg->eeprom_calib_ver) | 
|  | 94 | goto err; | 
|  | 95 |  | 
|  | 96 | IWL_INFO(priv, "device EEPROM VER=0x%x, CALIB=0x%x\n", | 
|  | 97 | eeprom_ver, calib_ver); | 
|  | 98 |  | 
|  | 99 | return 0; | 
|  | 100 | err: | 
|  | 101 | IWL_ERR(priv, "Unsupported (too old) EEPROM VER=0x%x < 0x%x " | 
|  | 102 | "CALIB=0x%x < 0x%x\n", | 
|  | 103 | eeprom_ver, priv->cfg->eeprom_ver, | 
|  | 104 | calib_ver,  priv->cfg->eeprom_calib_ver); | 
|  | 105 | return -EINVAL; | 
|  | 106 |  | 
|  | 107 | } | 
|  | 108 |  | 
| Wey-Yi Guy | 21a5b3c | 2010-11-10 13:32:59 -0800 | [diff] [blame] | 109 | int iwl_eeprom_check_sku(struct iwl_priv *priv) | 
|  | 110 | { | 
|  | 111 | u16 eeprom_sku; | 
| Wey-Yi Guy | dbbf175 | 2010-11-15 13:43:07 -0800 | [diff] [blame] | 112 | u16 radio_cfg; | 
| Wey-Yi Guy | 21a5b3c | 2010-11-10 13:32:59 -0800 | [diff] [blame] | 113 |  | 
|  | 114 | eeprom_sku = iwl_eeprom_query16(priv, EEPROM_SKU_CAP); | 
|  | 115 |  | 
| Wey-Yi Guy | 239712e | 2011-01-20 08:08:04 -0800 | [diff] [blame] | 116 | if (!priv->cfg->sku) { | 
|  | 117 | /* not using sku overwrite */ | 
|  | 118 | priv->cfg->sku = | 
|  | 119 | ((eeprom_sku & EEPROM_SKU_CAP_BAND_SELECTION) >> | 
| Wey-Yi Guy | 21a5b3c | 2010-11-10 13:32:59 -0800 | [diff] [blame] | 120 | EEPROM_SKU_CAP_BAND_POS); | 
| Wey-Yi Guy | 239712e | 2011-01-20 08:08:04 -0800 | [diff] [blame] | 121 | if (eeprom_sku & EEPROM_SKU_CAP_11N_ENABLE) | 
|  | 122 | priv->cfg->sku |= IWL_SKU_N; | 
|  | 123 | } | 
| Wey-Yi Guy | 21a5b3c | 2010-11-10 13:32:59 -0800 | [diff] [blame] | 124 | if (!priv->cfg->sku) { | 
|  | 125 | IWL_ERR(priv, "Invalid device sku\n"); | 
|  | 126 | return -EINVAL; | 
|  | 127 | } | 
|  | 128 |  | 
|  | 129 | IWL_INFO(priv, "Device SKU: 0X%x\n", priv->cfg->sku); | 
|  | 130 |  | 
| Wey-Yi Guy | dbbf175 | 2010-11-15 13:43:07 -0800 | [diff] [blame] | 131 | if (!priv->cfg->valid_tx_ant && !priv->cfg->valid_rx_ant) { | 
|  | 132 | /* not using .cfg overwrite */ | 
|  | 133 | radio_cfg = iwl_eeprom_query16(priv, EEPROM_RADIO_CONFIG); | 
|  | 134 | priv->cfg->valid_tx_ant = EEPROM_RF_CFG_TX_ANT_MSK(radio_cfg); | 
| Wey-Yi Guy | 38d5939 | 2011-01-18 07:59:13 -0800 | [diff] [blame] | 135 | priv->cfg->valid_rx_ant = EEPROM_RF_CFG_RX_ANT_MSK(radio_cfg); | 
| Wey-Yi Guy | dbbf175 | 2010-11-15 13:43:07 -0800 | [diff] [blame] | 136 | if (!priv->cfg->valid_tx_ant || !priv->cfg->valid_rx_ant) { | 
|  | 137 | IWL_ERR(priv, "Invalid chain (0X%x, 0X%x)\n", | 
|  | 138 | priv->cfg->valid_tx_ant, | 
|  | 139 | priv->cfg->valid_rx_ant); | 
|  | 140 | return -EINVAL; | 
|  | 141 | } | 
|  | 142 | IWL_INFO(priv, "Valid Tx ant: 0X%x, Valid Rx ant: 0X%x\n", | 
|  | 143 | priv->cfg->valid_tx_ant, priv->cfg->valid_rx_ant); | 
|  | 144 | } | 
|  | 145 | /* | 
|  | 146 | * for some special cases, | 
|  | 147 | * EEPROM did not reflect the correct antenna setting | 
|  | 148 | * so overwrite the valid tx/rx antenna from .cfg | 
|  | 149 | */ | 
| Wey-Yi Guy | 21a5b3c | 2010-11-10 13:32:59 -0800 | [diff] [blame] | 150 | return 0; | 
|  | 151 | } | 
|  | 152 |  | 
| Wey-Yi Guy | 3be63ff | 2010-10-08 16:05:19 -0700 | [diff] [blame] | 153 | void iwl_eeprom_get_mac(const struct iwl_priv *priv, u8 *mac) | 
|  | 154 | { | 
|  | 155 | const u8 *addr = priv->cfg->ops->lib->eeprom_ops.query_addr(priv, | 
|  | 156 | EEPROM_MAC_ADDRESS); | 
|  | 157 | memcpy(mac, addr, ETH_ALEN); | 
|  | 158 | } | 
|  | 159 |  | 
|  | 160 | /** | 
|  | 161 | * iwl_get_max_txpower_avg - get the highest tx power from all chains. | 
|  | 162 | *     find the highest tx power from all chains for the channel | 
|  | 163 | */ | 
|  | 164 | static s8 iwl_get_max_txpower_avg(struct iwl_priv *priv, | 
|  | 165 | struct iwl_eeprom_enhanced_txpwr *enhanced_txpower, | 
|  | 166 | int element, s8 *max_txpower_in_half_dbm) | 
|  | 167 | { | 
|  | 168 | s8 max_txpower_avg = 0; /* (dBm) */ | 
|  | 169 |  | 
| Wey-Yi Guy | 3be63ff | 2010-10-08 16:05:19 -0700 | [diff] [blame] | 170 | /* Take the highest tx power from any valid chains */ | 
|  | 171 | if ((priv->cfg->valid_tx_ant & ANT_A) && | 
|  | 172 | (enhanced_txpower[element].chain_a_max > max_txpower_avg)) | 
|  | 173 | max_txpower_avg = enhanced_txpower[element].chain_a_max; | 
|  | 174 | if ((priv->cfg->valid_tx_ant & ANT_B) && | 
|  | 175 | (enhanced_txpower[element].chain_b_max > max_txpower_avg)) | 
|  | 176 | max_txpower_avg = enhanced_txpower[element].chain_b_max; | 
|  | 177 | if ((priv->cfg->valid_tx_ant & ANT_C) && | 
|  | 178 | (enhanced_txpower[element].chain_c_max > max_txpower_avg)) | 
|  | 179 | max_txpower_avg = enhanced_txpower[element].chain_c_max; | 
|  | 180 | if (((priv->cfg->valid_tx_ant == ANT_AB) | | 
|  | 181 | (priv->cfg->valid_tx_ant == ANT_BC) | | 
|  | 182 | (priv->cfg->valid_tx_ant == ANT_AC)) && | 
|  | 183 | (enhanced_txpower[element].mimo2_max > max_txpower_avg)) | 
|  | 184 | max_txpower_avg =  enhanced_txpower[element].mimo2_max; | 
|  | 185 | if ((priv->cfg->valid_tx_ant == ANT_ABC) && | 
|  | 186 | (enhanced_txpower[element].mimo3_max > max_txpower_avg)) | 
|  | 187 | max_txpower_avg = enhanced_txpower[element].mimo3_max; | 
|  | 188 |  | 
|  | 189 | /* | 
|  | 190 | * max. tx power in EEPROM is in 1/2 dBm format | 
|  | 191 | * convert from 1/2 dBm to dBm (round-up convert) | 
|  | 192 | * but we also do not want to loss 1/2 dBm resolution which | 
|  | 193 | * will impact performance | 
|  | 194 | */ | 
|  | 195 | *max_txpower_in_half_dbm = max_txpower_avg; | 
|  | 196 | return (max_txpower_avg & 0x01) + (max_txpower_avg >> 1); | 
|  | 197 | } | 
|  | 198 |  | 
| Johannes Berg | 8d6748c | 2010-12-09 09:30:14 -0800 | [diff] [blame] | 199 | static void | 
|  | 200 | iwlcore_eeprom_enh_txp_read_element(struct iwl_priv *priv, | 
|  | 201 | struct iwl_eeprom_enhanced_txpwr *txp, | 
|  | 202 | s8 max_txpower_avg) | 
|  | 203 | { | 
|  | 204 | int ch_idx; | 
|  | 205 | bool is_ht40 = txp->flags & IWL_EEPROM_ENH_TXP_FL_40MHZ; | 
|  | 206 | enum ieee80211_band band; | 
|  | 207 |  | 
|  | 208 | band = txp->flags & IWL_EEPROM_ENH_TXP_FL_BAND_52G ? | 
|  | 209 | IEEE80211_BAND_5GHZ : IEEE80211_BAND_2GHZ; | 
|  | 210 |  | 
|  | 211 | for (ch_idx = 0; ch_idx < priv->channel_count; ch_idx++) { | 
|  | 212 | struct iwl_channel_info *ch_info = &priv->channel_info[ch_idx]; | 
|  | 213 |  | 
|  | 214 | /* update matching channel or from common data only */ | 
|  | 215 | if (txp->channel != 0 && ch_info->channel != txp->channel) | 
|  | 216 | continue; | 
|  | 217 |  | 
|  | 218 | /* update matching band only */ | 
|  | 219 | if (band != ch_info->band) | 
|  | 220 | continue; | 
|  | 221 |  | 
|  | 222 | if (ch_info->max_power_avg < max_txpower_avg && !is_ht40) { | 
|  | 223 | ch_info->max_power_avg = max_txpower_avg; | 
|  | 224 | ch_info->curr_txpow = max_txpower_avg; | 
|  | 225 | ch_info->scan_power = max_txpower_avg; | 
|  | 226 | } | 
|  | 227 |  | 
|  | 228 | if (is_ht40 && ch_info->ht40_max_power_avg < max_txpower_avg) | 
|  | 229 | ch_info->ht40_max_power_avg = max_txpower_avg; | 
|  | 230 | } | 
|  | 231 | } | 
|  | 232 |  | 
|  | 233 | #define EEPROM_TXP_OFFS	(0x00 | INDIRECT_ADDRESS | INDIRECT_TXP_LIMIT) | 
|  | 234 | #define EEPROM_TXP_ENTRY_LEN sizeof(struct iwl_eeprom_enhanced_txpwr) | 
|  | 235 | #define EEPROM_TXP_SZ_OFFS (0x00 | INDIRECT_ADDRESS | INDIRECT_TXP_LIMIT_SIZE) | 
|  | 236 |  | 
| Wey-Yi Guy | 33c6877 | 2010-12-03 10:33:36 -0800 | [diff] [blame] | 237 | #define TXP_CHECK_AND_PRINT(x) ((txp->flags & IWL_EEPROM_ENH_TXP_FL_##x) \ | 
|  | 238 | ? # x " " : "") | 
|  | 239 |  | 
| Johannes Berg | c6fc108 | 2010-12-09 12:56:42 -0800 | [diff] [blame] | 240 | void iwlcore_eeprom_enhanced_txpower(struct iwl_priv *priv) | 
| Johannes Berg | 8d6748c | 2010-12-09 09:30:14 -0800 | [diff] [blame] | 241 | { | 
|  | 242 | struct iwl_eeprom_enhanced_txpwr *txp_array, *txp; | 
|  | 243 | int idx, entries; | 
|  | 244 | __le16 *txp_len; | 
|  | 245 | s8 max_txp_avg, max_txp_avg_halfdbm; | 
|  | 246 |  | 
|  | 247 | BUILD_BUG_ON(sizeof(struct iwl_eeprom_enhanced_txpwr) != 8); | 
|  | 248 |  | 
|  | 249 | /* the length is in 16-bit words, but we want entries */ | 
|  | 250 | txp_len = (__le16 *) iwlagn_eeprom_query_addr(priv, EEPROM_TXP_SZ_OFFS); | 
|  | 251 | entries = le16_to_cpup(txp_len) * 2 / EEPROM_TXP_ENTRY_LEN; | 
|  | 252 |  | 
|  | 253 | txp_array = (void *) iwlagn_eeprom_query_addr(priv, EEPROM_TXP_OFFS); | 
| Wey-Yi Guy | 33c6877 | 2010-12-03 10:33:36 -0800 | [diff] [blame] | 254 |  | 
| Johannes Berg | 8d6748c | 2010-12-09 09:30:14 -0800 | [diff] [blame] | 255 | for (idx = 0; idx < entries; idx++) { | 
|  | 256 | txp = &txp_array[idx]; | 
| Johannes Berg | 8d6748c | 2010-12-09 09:30:14 -0800 | [diff] [blame] | 257 | /* skip invalid entries */ | 
|  | 258 | if (!(txp->flags & IWL_EEPROM_ENH_TXP_FL_VALID)) | 
|  | 259 | continue; | 
|  | 260 |  | 
| Wey-Yi Guy | 33c6877 | 2010-12-03 10:33:36 -0800 | [diff] [blame] | 261 | IWL_DEBUG_EEPROM(priv, "%s %d:\t %s%s%s%s%s%s%s%s (0x%02x)\n", | 
|  | 262 | (txp->channel && (txp->flags & | 
|  | 263 | IWL_EEPROM_ENH_TXP_FL_COMMON_TYPE)) ? | 
|  | 264 | "Common " : (txp->channel) ? | 
|  | 265 | "Channel" : "Common", | 
|  | 266 | (txp->channel), | 
|  | 267 | TXP_CHECK_AND_PRINT(VALID), | 
|  | 268 | TXP_CHECK_AND_PRINT(BAND_52G), | 
|  | 269 | TXP_CHECK_AND_PRINT(OFDM), | 
|  | 270 | TXP_CHECK_AND_PRINT(40MHZ), | 
|  | 271 | TXP_CHECK_AND_PRINT(HT_AP), | 
|  | 272 | TXP_CHECK_AND_PRINT(RES1), | 
|  | 273 | TXP_CHECK_AND_PRINT(RES2), | 
|  | 274 | TXP_CHECK_AND_PRINT(COMMON_TYPE), | 
|  | 275 | txp->flags); | 
|  | 276 | IWL_DEBUG_EEPROM(priv, "\t\t chain_A: 0x%02x " | 
|  | 277 | "chain_B: 0X%02x chain_C: 0X%02x\n", | 
|  | 278 | txp->chain_a_max, txp->chain_b_max, | 
|  | 279 | txp->chain_c_max); | 
|  | 280 | IWL_DEBUG_EEPROM(priv, "\t\t MIMO2: 0x%02x " | 
|  | 281 | "MIMO3: 0x%02x High 20_on_40: 0x%02x " | 
|  | 282 | "Low 20_on_40: 0x%02x\n", | 
|  | 283 | txp->mimo2_max, txp->mimo3_max, | 
|  | 284 | ((txp->delta_20_in_40 & 0xf0) >> 4), | 
|  | 285 | (txp->delta_20_in_40 & 0x0f)); | 
|  | 286 |  | 
| Johannes Berg | 8d6748c | 2010-12-09 09:30:14 -0800 | [diff] [blame] | 287 | max_txp_avg = iwl_get_max_txpower_avg(priv, txp_array, idx, | 
|  | 288 | &max_txp_avg_halfdbm); | 
|  | 289 |  | 
|  | 290 | /* | 
|  | 291 | * Update the user limit values values to the highest | 
|  | 292 | * power supported by any channel | 
|  | 293 | */ | 
|  | 294 | if (max_txp_avg > priv->tx_power_user_lmt) | 
|  | 295 | priv->tx_power_user_lmt = max_txp_avg; | 
|  | 296 | if (max_txp_avg_halfdbm > priv->tx_power_lmt_in_half_dbm) | 
|  | 297 | priv->tx_power_lmt_in_half_dbm = max_txp_avg_halfdbm; | 
|  | 298 |  | 
|  | 299 | iwlcore_eeprom_enh_txp_read_element(priv, txp, max_txp_avg); | 
|  | 300 | } | 
|  | 301 | } |