/* * Copyright (c) 2004-2008 Reyk Floeter <reyk@openbsd.org> * Copyright (c) 2006-2009 Nick Kossifidis <mickflemm@gmail.com> * Copyright (c) 2008-2009 Felix Fietkau <nbd@openwrt.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *
*/
/*************************************\ * EEPROM access functions and helpers *
\*************************************/
/* * Read values from EEPROM and store them in the capability structure
*/
AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MAGIC, ee_magic);
AR5K_EEPROM_READ_HDR(AR5K_EEPROM_PROTECT, ee_protect);
AR5K_EEPROM_READ_HDR(AR5K_EEPROM_REG_DOMAIN, ee_regdomain);
AR5K_EEPROM_READ_HDR(AR5K_EEPROM_VERSION, ee_version);
AR5K_EEPROM_READ_HDR(AR5K_EEPROM_HDR, ee_header);
/* Return if we have an old EEPROM */ if (ah->ah_ee_version < AR5K_EEPROM_VERSION_3_0) return 0;
/* * Validate the checksum of the EEPROM date. There are some * devices with invalid EEPROMs.
*/
AR5K_EEPROM_READ(AR5K_EEPROM_SIZE_UPPER, val); if (val) {
eep_max = (val & AR5K_EEPROM_SIZE_UPPER_MASK) <<
AR5K_EEPROM_SIZE_ENDLOC_SHIFT;
AR5K_EEPROM_READ(AR5K_EEPROM_SIZE_LOWER, val);
eep_max = (eep_max | val) - AR5K_EEPROM_INFO_BASE;
/* * Fail safe check to prevent stupid loops due * to busted EEPROMs. XXX: This value is likely too * big still, waiting on a better value.
*/ if (eep_max > (3 * AR5K_EEPROM_INFO_MAX)) {
ATH5K_ERR(ah, "Invalid max custom EEPROM size: " "%d (0x%04x) max expected: %d (0x%04x)\n",
eep_max, eep_max,
3 * AR5K_EEPROM_INFO_MAX,
3 * AR5K_EEPROM_INFO_MAX); return -EIO;
}
}
/* Check if PCIE_OFFSET points to PCIE_SERDES_SECTION * and enable serdes programming if needed. * * XXX: Serdes values seem to be fixed so * no need to read them here, we write them
* during ath5k_hw_init */
AR5K_EEPROM_READ(AR5K_EEPROM_PCIE_OFFSET, val);
ee->ee_serdes = (val == AR5K_EEPROM_PCIE_SERDES_SECTION) ? true : false;
return 0;
}
/* * Read antenna infos from eeprom
*/ staticint ath5k_eeprom_read_ants(struct ath5k_hw *ah, u32 *offset, unsignedint mode)
{ struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
u32 o = *offset;
u16 val; int i = 0;
if (ah->ah_ee_version < AR5K_EEPROM_VERSION_4_0) goto done;
/* Note: >= v5 have bg freq piers on another location * so these freq piers are ignored for >= v5 (should be 0xff
* anyway) */ switch (mode) { case AR5K_EEPROM_MODE_11A: if (ah->ah_ee_version < AR5K_EEPROM_VERSION_4_1) break;
AR5K_EEPROM_READ(o++, val);
ee->ee_margin_tx_rx[mode] = val & 0x3f; break; case AR5K_EEPROM_MODE_11B:
AR5K_EEPROM_READ(o++, val);
ee->ee_pwr_cal_b[0].freq =
ath5k_eeprom_bin2freq(ee, val & 0xff, mode); if (ee->ee_pwr_cal_b[0].freq != AR5K_EEPROM_CHANNEL_DIS)
ee->ee_n_piers[mode]++;
ret = ath5k_eeprom_read_ants(ah, &offset, mode); if (ret) return ret;
ret = ath5k_eeprom_read_modes(ah, &offset, mode); if (ret) return ret;
}
/* override for older eeprom versions for better performance */ if (ah->ah_ee_version <= AR5K_EEPROM_VERSION_3_2) {
ee->ee_thr_62[AR5K_EEPROM_MODE_11A] = 15;
ee->ee_thr_62[AR5K_EEPROM_MODE_11B] = 28;
ee->ee_thr_62[AR5K_EEPROM_MODE_11G] = 28;
}
return 0;
}
/* Read the frequency piers for each mode (mostly used on newer eeproms with 0xff
* frequency mask) */ staticinlineint
ath5k_eeprom_read_freq_list(struct ath5k_hw *ah, int *offset, int max, struct ath5k_chan_pcal_info *pc, unsignedint mode)
{ struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; int o = *offset; int i = 0;
u8 freq1, freq2;
u16 val;
ee->ee_n_piers[mode] = 0; while (i < max) {
AR5K_EEPROM_READ(o++, val);
/* * Read power calibration for RF5111 chips * * For RF5111 we have an XPD -eXternal Power Detector- curve * for each calibrated channel. Each curve has 0,5dB Power steps * on x axis and PCDAC steps (offsets) on y axis and looks like an * exponential function. To recreate the curve we read 11 points * here and interpolate later.
*/
/* Used to match PCDAC steps with power values on RF5111 chips * (eeprom versions < 4). For RF5111 we have 11 pre-defined PCDAC * steps that match with the power values we read from eeprom. On * older eeprom versions (< 3.2) these steps are equally spaced at * 10% of the pcdac curve -until the curve reaches its maximum- * (11 steps from 0 to 100%) but on newer eeprom versions (>= 3.2) * these 11 steps are spaced in a different way. This function returns * the pcdac steps based on eeprom version and curve min/max so that we * can have pcdac/pwr points.
*/ staticinlinevoid
ath5k_get_pcdac_intercepts(struct ath5k_hw *ah, u8 min, u8 max, u8 *vp)
{ staticconst u16 intercepts3[] = {
0, 5, 10, 20, 30, 50, 70, 85, 90, 95, 100
}; staticconst u16 intercepts3_2[] = {
0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100
}; const u16 *ip; int i;
if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_3_2)
ip = intercepts3_2; else
ip = intercepts3;
for (i = 0; i < ARRAY_SIZE(intercepts3); i++)
vp[i] = (ip[i] * max + (100 - ip[i]) * min) / 100;
}
/* Convert RF5111 specific data to generic raw data
* used by interpolation code */ staticint
ath5k_eeprom_convert_pcal_info_5111(struct ath5k_hw *ah, int mode, struct ath5k_chan_pcal_info *chinfo)
{ struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; struct ath5k_chan_pcal_info_rf5111 *pcinfo; struct ath5k_pdgain_info *pd;
u8 pier, point, idx;
u8 *pdgain_idx = ee->ee_pdc_to_idx[mode];
/* Fill raw data for each calibration pier */ for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) {
pcinfo = &chinfo[pier].rf5111_info;
/* Allocate pd_curves for this cal pier */
chinfo[pier].pd_curves =
kcalloc(AR5K_EEPROM_N_PD_CURVES, sizeof(struct ath5k_pdgain_info),
GFP_KERNEL);
if (!chinfo[pier].pd_curves) goto err_out;
/* Only one curve for RF5111 * find out which one and place * in pd_curves.
* Note: ee_x_gain is reversed here */ for (idx = 0; idx < AR5K_EEPROM_N_PD_CURVES; idx++) {
/* * Read power calibration for RF5112 chips * * For RF5112 we have 4 XPD -eXternal Power Detector- curves * for each calibrated channel on 0, -6, -12 and -18dBm but we only * use the higher (3) and the lower (0) curves. Each curve has 0.5dB * power steps on x axis and PCDAC steps on y axis and looks like a * linear function. To recreate the curve and pass the power values * on hw, we read 4 points for xpd 0 (lower gain -> max power) * and 3 points for xpd 3 (higher gain -> lower power) here and * interpolate later. * * Note: Many vendors just use xpd 0 so xpd 3 is zeroed.
*/
/* Convert RF5112 specific data to generic raw data
* used by interpolation code */ staticint
ath5k_eeprom_convert_pcal_info_5112(struct ath5k_hw *ah, int mode, struct ath5k_chan_pcal_info *chinfo)
{ struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; struct ath5k_chan_pcal_info_rf5112 *pcinfo;
u8 *pdgain_idx = ee->ee_pdc_to_idx[mode]; unsignedint pier, pdg, point;
/* Fill raw data for each calibration pier */ for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) {
pcinfo = &chinfo[pier].rf5112_info;
/* Allocate pd_curves for this cal pier */
chinfo[pier].pd_curves =
kcalloc(AR5K_EEPROM_N_PD_CURVES, sizeof(struct ath5k_pdgain_info),
GFP_KERNEL);
if (!chinfo[pier].pd_curves) goto err_out;
/* Fill pd_curves */ for (pdg = 0; pdg < ee->ee_pd_gains[mode]; pdg++) {
/* Fill raw dataset
* (all power levels are in 0.25dB units) */ for (point = 0; point < pd->pd_points;
point++) { /* Absolute values */
pd->pd_pwr[point] =
pcinfo->pwr_x3[point];
/* Count how many curves we have and * identify them (which one of the 4 * available curves we have on each count). * Curves are stored from lower (x0) to
* higher (x3) gain */ for (i = 0; i < AR5K_EEPROM_N_PD_CURVES; i++) { /* ee_x_gain[mode] is x gain mask */ if ((ee->ee_x_gain[mode] >> i) & 0x1)
pdgain_idx[pd_gains++] = i;
}
ee->ee_pd_gains[mode] = pd_gains;
if (pd_gains == 0 || pd_gains > 2) return -EINVAL;
/* * Read power calibration for RF2413 chips * * For RF2413 we have a Power to PDDAC table (Power Detector) * instead of a PCDAC and 4 pd gain curves for each calibrated channel. * Each curve has power on x axis in 0.5 db steps and PDDADC steps on y * axis and looks like an exponential function like the RF5111 curve. * * To recreate the curves we read here the points and interpolate * later. Note that in most cases only 2 (higher and lower) curves are * used (like RF5112) but vendors have the opportunity to include all * 4 curves on eeprom. The final curve (higher power) has an extra * point for better accuracy like RF5112.
*/
/* For RF2413 power calibration data doesn't start on a fixed location and * if a mode is not supported, its section is missing -not zeroed-. * So we need to calculate the starting offset for each section by using
* these two functions */
/* Return the size of each section based on the mode and the number of pd
* gains available (maximum 4). */ staticinlineunsignedint
ath5k_pdgains_size_2413(struct ath5k_eeprom_info *ee, unsignedint mode)
{ staticconstunsignedint pdgains_size[] = { 4, 6, 9, 12 }; unsignedint sz;
/* Return the starting offset for a section based on the modes supported
* and each section's size. */ staticunsignedint
ath5k_cal_data_offset_2413(struct ath5k_eeprom_info *ee, int mode)
{
u32 offset = AR5K_EEPROM_CAL_DATA_START(ee->ee_misc4);
switch (mode) { case AR5K_EEPROM_MODE_11G: if (AR5K_EEPROM_HDR_11B(ee->ee_header))
offset += ath5k_pdgains_size_2413(ee,
AR5K_EEPROM_MODE_11B) +
AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2;
fallthrough; case AR5K_EEPROM_MODE_11B: if (AR5K_EEPROM_HDR_11A(ee->ee_header))
offset += ath5k_pdgains_size_2413(ee,
AR5K_EEPROM_MODE_11A) +
AR5K_EEPROM_N_5GHZ_CHAN / 2;
fallthrough; case AR5K_EEPROM_MODE_11A: break; default: break;
}
return offset;
}
/* Convert RF2413 specific data to generic raw data
* used by interpolation code */ staticint
ath5k_eeprom_convert_pcal_info_2413(struct ath5k_hw *ah, int mode, struct ath5k_chan_pcal_info *chinfo)
{ struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; struct ath5k_chan_pcal_info_rf2413 *pcinfo;
u8 *pdgain_idx = ee->ee_pdc_to_idx[mode]; unsignedint pier, pdg, point;
/* Fill raw data for each calibration pier */ for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) {
pcinfo = &chinfo[pier].rf2413_info;
/* Allocate pd_curves for this cal pier */
chinfo[pier].pd_curves =
kcalloc(AR5K_EEPROM_N_PD_CURVES, sizeof(struct ath5k_pdgain_info),
GFP_KERNEL);
if (!chinfo[pier].pd_curves) goto err_out;
/* Fill pd_curves */ for (pdg = 0; pdg < ee->ee_pd_gains[mode]; pdg++) {
/* One more point for the highest power
* curve (lowest gain) */ if (pdg == ee->ee_pd_gains[mode] - 1)
pd->pd_points = AR5K_EEPROM_N_PD_POINTS; else
pd->pd_points = AR5K_EEPROM_N_PD_POINTS - 1;
/* Allocate pd points for this curve */
pd->pd_step = kcalloc(pd->pd_points, sizeof(u8), GFP_KERNEL);
/* Fill raw dataset * convert all pwr levels to
* quarter dB for RF5112 compatibility */
pd->pd_step[0] = pcinfo->pddac_i[pdg];
pd->pd_pwr[0] = 4 * pcinfo->pwr_i[pdg];
/* Count how many curves we have and * identify them (which one of the 4 * available curves we have on each count). * Curves are stored from higher to
* lower gain so we go backwards */ for (idx = AR5K_EEPROM_N_PD_CURVES - 1; idx >= 0; idx--) { /* ee_x_gain[mode] is x gain mask */ if ((ee->ee_x_gain[mode] >> idx) & 0x1)
pdgain_idx[pd_gains++] = idx;
}
ee->ee_pd_gains[mode] = pd_gains;
if (pd_gains == 0) return -EINVAL;
offset = ath5k_cal_data_offset_2413(ee, mode); switch (mode) { case AR5K_EEPROM_MODE_11A: if (!AR5K_EEPROM_HDR_11A(ee->ee_header)) return 0;
ath5k_eeprom_init_11a_pcal_freq(ah, offset);
offset += AR5K_EEPROM_N_5GHZ_CHAN / 2;
chinfo = ee->ee_pwr_cal_a; break; case AR5K_EEPROM_MODE_11B: if (!AR5K_EEPROM_HDR_11B(ee->ee_header)) return 0;
ath5k_eeprom_init_11bg_2413(ah, mode, offset);
offset += AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2;
chinfo = ee->ee_pwr_cal_b; break; case AR5K_EEPROM_MODE_11G: if (!AR5K_EEPROM_HDR_11G(ee->ee_header)) return 0;
if (pd_gains > 1) { /* * Pd gain 0 is not the last pd gain * so it only has 2 pd points. * Continue with pd gain 1.
*/
pcinfo->pwr_i[1] = (val >> 10) & 0x1f;
pcinfo->pwr[1][3] = 0;
pcinfo->pddac[1][3] = 0;
} elseif (pd_gains == 1) { /* * Pd gain 0 is the last one so * read the extra point.
*/
pcinfo->pwr[0][3] = (val >> 10) & 0xf;
/* * Read per rate target power (this is the maximum tx power * supported by the card). This info is used when setting * tx power, no matter the channel. * * This also works for v5 EEPROMs.
*/ staticint
ath5k_eeprom_read_target_rate_pwr_info(struct ath5k_hw *ah, unsignedint mode)
{ struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; struct ath5k_rate_pcal_info *rate_pcal_info;
u8 *rate_target_pwr_num;
u32 offset;
u16 val; int i;
/* * Read per channel calibration info from EEPROM * * This info is used to calibrate the baseband power table. Imagine * that for each channel there is a power curve that's hw specific * (depends on amplifier etc) and we try to "correct" this curve using * offsets we pass on to phy chip (baseband -> before amplifier) so that * it can use accurate power values when setting tx power (takes amplifier's * performance on each channel into account). * * EEPROM provides us with the offsets for some pre-calibrated channels * and we have to interpolate to create the full table for these channels and * also the table for any channel.
*/ staticint
ath5k_eeprom_read_pcal_info(struct ath5k_hw *ah)
{ struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; int (*read_pcal)(struct ath5k_hw *hw, int mode); int mode; int err;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.