diff options
Diffstat (limited to 'sys/dev/ath/ath_hal/ah.c')
-rw-r--r-- | sys/dev/ath/ath_hal/ah.c | 1585 |
1 files changed, 1585 insertions, 0 deletions
diff --git a/sys/dev/ath/ath_hal/ah.c b/sys/dev/ath/ath_hal/ah.c new file mode 100644 index 000000000000..df2ca240c1a8 --- /dev/null +++ b/sys/dev/ath/ath_hal/ah.c @@ -0,0 +1,1585 @@ +/*- + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2002-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or 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. + */ +#include "opt_ah.h" + +#include "ah.h" +#include "ah_internal.h" +#include "ah_devid.h" +#include "ah_eeprom.h" /* for 5ghz fast clock flag */ + +#include "ar5416/ar5416reg.h" /* NB: includes ar5212reg.h */ +#include "ar9003/ar9300_devid.h" + +/* linker set of registered chips */ +OS_SET_DECLARE(ah_chips, struct ath_hal_chip); +TAILQ_HEAD(, ath_hal_chip) ah_chip_list = TAILQ_HEAD_INITIALIZER(ah_chip_list); + +int +ath_hal_add_chip(struct ath_hal_chip *ahc) +{ + + TAILQ_INSERT_TAIL(&ah_chip_list, ahc, node); + return (0); +} + +int +ath_hal_remove_chip(struct ath_hal_chip *ahc) +{ + + TAILQ_REMOVE(&ah_chip_list, ahc, node); + return (0); +} + +/* + * Check the set of registered chips to see if any recognize + * the device as one they can support. + */ +const char* +ath_hal_probe(uint16_t vendorid, uint16_t devid) +{ + struct ath_hal_chip * const *pchip; + struct ath_hal_chip *pc; + + /* Linker set */ + OS_SET_FOREACH(pchip, ah_chips) { + const char *name = (*pchip)->probe(vendorid, devid); + if (name != AH_NULL) + return name; + } + + /* List */ + TAILQ_FOREACH(pc, &ah_chip_list, node) { + const char *name = pc->probe(vendorid, devid); + if (name != AH_NULL) + return name; + } + + return AH_NULL; +} + +/* + * Attach detects device chip revisions, initializes the hwLayer + * function list, reads EEPROM information, + * selects reset vectors, and performs a short self test. + * Any failures will return an error that should cause a hardware + * disable. + */ +struct ath_hal* +ath_hal_attach(uint16_t devid, HAL_SOFTC sc, + HAL_BUS_TAG st, HAL_BUS_HANDLE sh, uint16_t *eepromdata, + HAL_OPS_CONFIG *ah_config, + HAL_STATUS *error) +{ + struct ath_hal_chip * const *pchip; + struct ath_hal_chip *pc; + + OS_SET_FOREACH(pchip, ah_chips) { + struct ath_hal_chip *chip = *pchip; + struct ath_hal *ah; + + /* XXX don't have vendorid, assume atheros one works */ + if (chip->probe(ATHEROS_VENDOR_ID, devid) == AH_NULL) + continue; + ah = chip->attach(devid, sc, st, sh, eepromdata, ah_config, + error); + if (ah != AH_NULL) { + /* copy back private state to public area */ + ah->ah_devid = AH_PRIVATE(ah)->ah_devid; + ah->ah_subvendorid = AH_PRIVATE(ah)->ah_subvendorid; + ah->ah_macVersion = AH_PRIVATE(ah)->ah_macVersion; + ah->ah_macRev = AH_PRIVATE(ah)->ah_macRev; + ah->ah_phyRev = AH_PRIVATE(ah)->ah_phyRev; + ah->ah_analog5GhzRev = AH_PRIVATE(ah)->ah_analog5GhzRev; + ah->ah_analog2GhzRev = AH_PRIVATE(ah)->ah_analog2GhzRev; + return ah; + } + } + + /* List */ + TAILQ_FOREACH(pc, &ah_chip_list, node) { + struct ath_hal_chip *chip = pc; + struct ath_hal *ah; + + /* XXX don't have vendorid, assume atheros one works */ + if (chip->probe(ATHEROS_VENDOR_ID, devid) == AH_NULL) + continue; + ah = chip->attach(devid, sc, st, sh, eepromdata, ah_config, + error); + if (ah != AH_NULL) { + /* copy back private state to public area */ + ah->ah_devid = AH_PRIVATE(ah)->ah_devid; + ah->ah_subvendorid = AH_PRIVATE(ah)->ah_subvendorid; + ah->ah_macVersion = AH_PRIVATE(ah)->ah_macVersion; + ah->ah_macRev = AH_PRIVATE(ah)->ah_macRev; + ah->ah_phyRev = AH_PRIVATE(ah)->ah_phyRev; + ah->ah_analog5GhzRev = AH_PRIVATE(ah)->ah_analog5GhzRev; + ah->ah_analog2GhzRev = AH_PRIVATE(ah)->ah_analog2GhzRev; + return ah; + } + } + + return AH_NULL; +} + +const char * +ath_hal_mac_name(struct ath_hal *ah) +{ + switch (ah->ah_macVersion) { + case AR_SREV_VERSION_CRETE: + case AR_SREV_VERSION_MAUI_1: + return "AR5210"; + case AR_SREV_VERSION_MAUI_2: + case AR_SREV_VERSION_OAHU: + return "AR5211"; + case AR_SREV_VERSION_VENICE: + return "AR5212"; + case AR_SREV_VERSION_GRIFFIN: + return "AR2413"; + case AR_SREV_VERSION_CONDOR: + return "AR5424"; + case AR_SREV_VERSION_EAGLE: + return "AR5413"; + case AR_SREV_VERSION_COBRA: + return "AR2415"; + case AR_SREV_2425: /* Swan */ + return "AR2425"; + case AR_SREV_2417: /* Nala */ + return "AR2417"; + case AR_XSREV_VERSION_OWL_PCI: + return "AR5416"; + case AR_XSREV_VERSION_OWL_PCIE: + return "AR5418"; + case AR_XSREV_VERSION_HOWL: + return "AR9130"; + case AR_XSREV_VERSION_SOWL: + return "AR9160"; + case AR_XSREV_VERSION_MERLIN: + if (AH_PRIVATE(ah)->ah_ispcie) + return "AR9280"; + return "AR9220"; + case AR_XSREV_VERSION_KITE: + return "AR9285"; + case AR_XSREV_VERSION_KIWI: + if (AH_PRIVATE(ah)->ah_ispcie) + return "AR9287"; + return "AR9227"; + case AR_SREV_VERSION_AR9380: + if (ah->ah_macRev >= AR_SREV_REVISION_AR9580_10) + return "AR9580"; + return "AR9380"; + case AR_SREV_VERSION_AR9460: + return "AR9460"; + case AR_SREV_VERSION_AR9330: + return "AR9330"; + case AR_SREV_VERSION_AR9340: + return "AR9340"; + case AR_SREV_VERSION_QCA9550: + return "QCA9550"; + case AR_SREV_VERSION_AR9485: + return "AR9485"; + case AR_SREV_VERSION_QCA9565: + return "QCA9565"; + case AR_SREV_VERSION_QCA9530: + return "QCA9530"; + } + return "????"; +} + +/* + * Return the mask of available modes based on the hardware capabilities. + */ +u_int +ath_hal_getwirelessmodes(struct ath_hal*ah) +{ + return ath_hal_getWirelessModes(ah); +} + +/* linker set of registered RF backends */ +OS_SET_DECLARE(ah_rfs, struct ath_hal_rf); +TAILQ_HEAD(, ath_hal_rf) ah_rf_list = TAILQ_HEAD_INITIALIZER(ah_rf_list); + +int +ath_hal_add_rf(struct ath_hal_rf *arf) +{ + + TAILQ_INSERT_TAIL(&ah_rf_list, arf, node); + return (0); +} + +int +ath_hal_remove_rf(struct ath_hal_rf *arf) +{ + + TAILQ_REMOVE(&ah_rf_list, arf, node); + return (0); +} + +/* + * Check the set of registered RF backends to see if + * any recognize the device as one they can support. + */ +struct ath_hal_rf * +ath_hal_rfprobe(struct ath_hal *ah, HAL_STATUS *ecode) +{ + struct ath_hal_rf * const *prf; + struct ath_hal_rf * rf; + + OS_SET_FOREACH(prf, ah_rfs) { + struct ath_hal_rf *rf = *prf; + if (rf->probe(ah)) + return rf; + } + + TAILQ_FOREACH(rf, &ah_rf_list, node) { + if (rf->probe(ah)) + return rf; + } + *ecode = HAL_ENOTSUPP; + return AH_NULL; +} + +const char * +ath_hal_rf_name(struct ath_hal *ah) +{ + switch (ah->ah_analog5GhzRev & AR_RADIO_SREV_MAJOR) { + case 0: /* 5210 */ + return "5110"; /* NB: made up */ + case AR_RAD5111_SREV_MAJOR: + case AR_RAD5111_SREV_PROD: + return "5111"; + case AR_RAD2111_SREV_MAJOR: + return "2111"; + case AR_RAD5112_SREV_MAJOR: + case AR_RAD5112_SREV_2_0: + case AR_RAD5112_SREV_2_1: + return "5112"; + case AR_RAD2112_SREV_MAJOR: + case AR_RAD2112_SREV_2_0: + case AR_RAD2112_SREV_2_1: + return "2112"; + case AR_RAD2413_SREV_MAJOR: + return "2413"; + case AR_RAD5413_SREV_MAJOR: + return "5413"; + case AR_RAD2316_SREV_MAJOR: + return "2316"; + case AR_RAD2317_SREV_MAJOR: + return "2317"; + case AR_RAD5424_SREV_MAJOR: + return "5424"; + + case AR_RAD5133_SREV_MAJOR: + return "5133"; + case AR_RAD2133_SREV_MAJOR: + return "2133"; + case AR_RAD5122_SREV_MAJOR: + return "5122"; + case AR_RAD2122_SREV_MAJOR: + return "2122"; + } + return "????"; +} + +/* + * Poll the register looking for a specific value. + */ +HAL_BOOL +ath_hal_wait(struct ath_hal *ah, u_int reg, uint32_t mask, uint32_t val) +{ +#define AH_TIMEOUT 5000 + return ath_hal_waitfor(ah, reg, mask, val, AH_TIMEOUT); +#undef AH_TIMEOUT +} + +HAL_BOOL +ath_hal_waitfor(struct ath_hal *ah, u_int reg, uint32_t mask, uint32_t val, uint32_t timeout) +{ + int i; + + for (i = 0; i < timeout; i++) { + if ((OS_REG_READ(ah, reg) & mask) == val) + return AH_TRUE; + OS_DELAY(10); + } + HALDEBUG(ah, HAL_DEBUG_REGIO | HAL_DEBUG_PHYIO, + "%s: timeout on reg 0x%x: 0x%08x & 0x%08x != 0x%08x\n", + __func__, reg, OS_REG_READ(ah, reg), mask, val); + return AH_FALSE; +} + +/* + * Reverse the bits starting at the low bit for a value of + * bit_count in size + */ +uint32_t +ath_hal_reverseBits(uint32_t val, uint32_t n) +{ + uint32_t retval; + int i; + + for (i = 0, retval = 0; i < n; i++) { + retval = (retval << 1) | (val & 1); + val >>= 1; + } + return retval; +} + +/* 802.11n related timing definitions */ + +#define OFDM_PLCP_BITS 22 +#define HT_L_STF 8 +#define HT_L_LTF 8 +#define HT_L_SIG 4 +#define HT_SIG 8 +#define HT_STF 4 +#define HT_LTF(n) ((n) * 4) + +#define HT_RC_2_MCS(_rc) ((_rc) & 0x1f) +#define HT_RC_2_STREAMS(_rc) ((((_rc) & 0x78) >> 3) + 1) +#define IS_HT_RATE(_rc) ( (_rc) & IEEE80211_RATE_MCS) + +/* + * Calculate the duration of a packet whether it is 11n or legacy. + */ +uint32_t +ath_hal_pkt_txtime(struct ath_hal *ah, const HAL_RATE_TABLE *rates, uint32_t frameLen, + uint16_t rateix, HAL_BOOL isht40, HAL_BOOL shortPreamble, + HAL_BOOL includeSifs) +{ + uint8_t rc; + int numStreams; + + rc = rates->info[rateix].rateCode; + + /* Legacy rate? Return the old way */ + if (! IS_HT_RATE(rc)) + return ath_hal_computetxtime(ah, rates, frameLen, rateix, + shortPreamble, includeSifs); + + /* 11n frame - extract out the number of spatial streams */ + numStreams = HT_RC_2_STREAMS(rc); + KASSERT(numStreams > 0 && numStreams <= 4, + ("number of spatial streams needs to be 1..3: MCS rate 0x%x!", + rateix)); + + /* XXX TODO: Add SIFS */ + return ath_computedur_ht(frameLen, rc, numStreams, isht40, + shortPreamble); +} + +static const uint16_t ht20_bps[32] = { + 26, 52, 78, 104, 156, 208, 234, 260, + 52, 104, 156, 208, 312, 416, 468, 520, + 78, 156, 234, 312, 468, 624, 702, 780, + 104, 208, 312, 416, 624, 832, 936, 1040 +}; +static const uint16_t ht40_bps[32] = { + 54, 108, 162, 216, 324, 432, 486, 540, + 108, 216, 324, 432, 648, 864, 972, 1080, + 162, 324, 486, 648, 972, 1296, 1458, 1620, + 216, 432, 648, 864, 1296, 1728, 1944, 2160 +}; + +/* + * Calculate the transmit duration of an 11n frame. + */ +uint32_t +ath_computedur_ht(uint32_t frameLen, uint16_t rate, int streams, + HAL_BOOL isht40, HAL_BOOL isShortGI) +{ + uint32_t bitsPerSymbol, numBits, numSymbols, txTime; + + KASSERT(rate & IEEE80211_RATE_MCS, ("not mcs %d", rate)); + KASSERT((rate &~ IEEE80211_RATE_MCS) < 31, ("bad mcs 0x%x", rate)); + + if (isht40) + bitsPerSymbol = ht40_bps[HT_RC_2_MCS(rate)]; + else + bitsPerSymbol = ht20_bps[HT_RC_2_MCS(rate)]; + numBits = OFDM_PLCP_BITS + (frameLen << 3); + numSymbols = howmany(numBits, bitsPerSymbol); + if (isShortGI) + txTime = ((numSymbols * 18) + 4) / 5; /* 3.6us */ + else + txTime = numSymbols * 4; /* 4us */ + return txTime + HT_L_STF + HT_L_LTF + + HT_L_SIG + HT_SIG + HT_STF + HT_LTF(streams); +} + +/* + * Compute the time to transmit a frame of length frameLen bytes + * using the specified rate, phy, and short preamble setting. + */ +uint16_t +ath_hal_computetxtime(struct ath_hal *ah, + const HAL_RATE_TABLE *rates, uint32_t frameLen, uint16_t rateix, + HAL_BOOL shortPreamble, HAL_BOOL includeSifs) +{ + uint32_t bitsPerSymbol, numBits, numSymbols, phyTime, txTime; + uint32_t kbps; + + /* Warn if this function is called for 11n rates; it should not be! */ + if (IS_HT_RATE(rates->info[rateix].rateCode)) + ath_hal_printf(ah, "%s: MCS rate? (index %d; hwrate 0x%x)\n", + __func__, rateix, rates->info[rateix].rateCode); + + kbps = rates->info[rateix].rateKbps; + /* + * index can be invalid during dynamic Turbo transitions. + * XXX + */ + if (kbps == 0) + return 0; + switch (rates->info[rateix].phy) { + case IEEE80211_T_CCK: + phyTime = CCK_PREAMBLE_BITS + CCK_PLCP_BITS; + if (shortPreamble && rates->info[rateix].shortPreamble) + phyTime >>= 1; + numBits = frameLen << 3; + txTime = phyTime + + ((numBits * 1000)/kbps); + if (includeSifs) + txTime += CCK_SIFS_TIME; + break; + case IEEE80211_T_OFDM: + bitsPerSymbol = (kbps * OFDM_SYMBOL_TIME) / 1000; + HALASSERT(bitsPerSymbol != 0); + + numBits = OFDM_PLCP_BITS + (frameLen << 3); + numSymbols = howmany(numBits, bitsPerSymbol); + txTime = OFDM_PREAMBLE_TIME + + (numSymbols * OFDM_SYMBOL_TIME); + if (includeSifs) + txTime += OFDM_SIFS_TIME; + break; + case IEEE80211_T_OFDM_HALF: + bitsPerSymbol = (kbps * OFDM_HALF_SYMBOL_TIME) / 1000; + HALASSERT(bitsPerSymbol != 0); + + numBits = OFDM_HALF_PLCP_BITS + (frameLen << 3); + numSymbols = howmany(numBits, bitsPerSymbol); + txTime = OFDM_HALF_PREAMBLE_TIME + + (numSymbols * OFDM_HALF_SYMBOL_TIME); + if (includeSifs) + txTime += OFDM_HALF_SIFS_TIME; + break; + case IEEE80211_T_OFDM_QUARTER: + bitsPerSymbol = (kbps * OFDM_QUARTER_SYMBOL_TIME) / 1000; + HALASSERT(bitsPerSymbol != 0); + + numBits = OFDM_QUARTER_PLCP_BITS + (frameLen << 3); + numSymbols = howmany(numBits, bitsPerSymbol); + txTime = OFDM_QUARTER_PREAMBLE_TIME + + (numSymbols * OFDM_QUARTER_SYMBOL_TIME); + if (includeSifs) + txTime += OFDM_QUARTER_SIFS_TIME; + break; + case IEEE80211_T_TURBO: + bitsPerSymbol = (kbps * TURBO_SYMBOL_TIME) / 1000; + HALASSERT(bitsPerSymbol != 0); + + numBits = TURBO_PLCP_BITS + (frameLen << 3); + numSymbols = howmany(numBits, bitsPerSymbol); + txTime = TURBO_PREAMBLE_TIME + + (numSymbols * TURBO_SYMBOL_TIME); + if (includeSifs) + txTime += TURBO_SIFS_TIME; + break; + default: + HALDEBUG(ah, HAL_DEBUG_PHYIO, + "%s: unknown phy %u (rate ix %u)\n", + __func__, rates->info[rateix].phy, rateix); + txTime = 0; + break; + } + return txTime; +} + +int +ath_hal_get_curmode(struct ath_hal *ah, const struct ieee80211_channel *chan) +{ + /* + * Pick a default mode at bootup. A channel change is inevitable. + */ + if (!chan) + return HAL_MODE_11NG_HT20; + + if (IEEE80211_IS_CHAN_TURBO(chan)) + return HAL_MODE_TURBO; + + /* check for NA_HT before plain A, since IS_CHAN_A includes NA_HT */ + if (IEEE80211_IS_CHAN_5GHZ(chan) && IEEE80211_IS_CHAN_HT20(chan)) + return HAL_MODE_11NA_HT20; + if (IEEE80211_IS_CHAN_5GHZ(chan) && IEEE80211_IS_CHAN_HT40U(chan)) + return HAL_MODE_11NA_HT40PLUS; + if (IEEE80211_IS_CHAN_5GHZ(chan) && IEEE80211_IS_CHAN_HT40D(chan)) + return HAL_MODE_11NA_HT40MINUS; + if (IEEE80211_IS_CHAN_A(chan)) + return HAL_MODE_11A; + + /* check for NG_HT before plain G, since IS_CHAN_G includes NG_HT */ + if (IEEE80211_IS_CHAN_2GHZ(chan) && IEEE80211_IS_CHAN_HT20(chan)) + return HAL_MODE_11NG_HT20; + if (IEEE80211_IS_CHAN_2GHZ(chan) && IEEE80211_IS_CHAN_HT40U(chan)) + return HAL_MODE_11NG_HT40PLUS; + if (IEEE80211_IS_CHAN_2GHZ(chan) && IEEE80211_IS_CHAN_HT40D(chan)) + return HAL_MODE_11NG_HT40MINUS; + + /* + * XXX For FreeBSD, will this work correctly given the DYN + * chan mode (OFDM+CCK dynamic) ? We have pure-G versions DYN-BG.. + */ + if (IEEE80211_IS_CHAN_G(chan)) + return HAL_MODE_11G; + if (IEEE80211_IS_CHAN_B(chan)) + return HAL_MODE_11B; + + HALASSERT(0); + return HAL_MODE_11NG_HT20; +} + +typedef enum { + WIRELESS_MODE_11a = 0, + WIRELESS_MODE_TURBO = 1, + WIRELESS_MODE_11b = 2, + WIRELESS_MODE_11g = 3, + WIRELESS_MODE_108g = 4, + + WIRELESS_MODE_MAX +} WIRELESS_MODE; + +/* + * XXX TODO: for some (?) chips, an 11b mode still runs at 11bg. + * Maybe AR5211 has separate 11b and 11g only modes, so 11b is 22MHz + * and 11g is 44MHz, but AR5416 and later run 11b in 11bg mode, right? + */ +static WIRELESS_MODE +ath_hal_chan2wmode(struct ath_hal *ah, const struct ieee80211_channel *chan) +{ + if (IEEE80211_IS_CHAN_B(chan)) + return WIRELESS_MODE_11b; + if (IEEE80211_IS_CHAN_G(chan)) + return WIRELESS_MODE_11g; + if (IEEE80211_IS_CHAN_108G(chan)) + return WIRELESS_MODE_108g; + if (IEEE80211_IS_CHAN_TURBO(chan)) + return WIRELESS_MODE_TURBO; + return WIRELESS_MODE_11a; +} + +/* + * Convert between microseconds and core system clocks. + */ + /* 11a Turbo 11b 11g 108g */ +static const uint8_t CLOCK_RATE[] = { 40, 80, 22, 44, 88 }; + +#define CLOCK_FAST_RATE_5GHZ_OFDM 44 + +u_int +ath_hal_mac_clks(struct ath_hal *ah, u_int usecs) +{ + const struct ieee80211_channel *c = AH_PRIVATE(ah)->ah_curchan; + u_int clks; + + /* NB: ah_curchan may be null when called attach time */ + /* XXX merlin and later specific workaround - 5ghz fast clock is 44 */ + if (c != AH_NULL && IS_5GHZ_FAST_CLOCK_EN(ah, c)) { + clks = usecs * CLOCK_FAST_RATE_5GHZ_OFDM; + if (IEEE80211_IS_CHAN_HT40(c)) + clks <<= 1; + } else if (c != AH_NULL) { + clks = usecs * CLOCK_RATE[ath_hal_chan2wmode(ah, c)]; + if (IEEE80211_IS_CHAN_HT40(c)) + clks <<= 1; + } else + clks = usecs * CLOCK_RATE[WIRELESS_MODE_11b]; + + /* Compensate for half/quarter rate */ + if (c != AH_NULL && IEEE80211_IS_CHAN_HALF(c)) + clks = clks / 2; + else if (c != AH_NULL && IEEE80211_IS_CHAN_QUARTER(c)) + clks = clks / 4; + + return clks; +} + +u_int +ath_hal_mac_usec(struct ath_hal *ah, u_int clks) +{ + uint64_t psec; + + psec = ath_hal_mac_psec(ah, clks); + return (psec / 1000000); +} + +/* + * XXX TODO: half, quarter rates. + */ +uint64_t +ath_hal_mac_psec(struct ath_hal *ah, u_int clks) +{ + const struct ieee80211_channel *c = AH_PRIVATE(ah)->ah_curchan; + uint64_t psec; + + /* NB: ah_curchan may be null when called attach time */ + /* XXX merlin and later specific workaround - 5ghz fast clock is 44 */ + if (c != AH_NULL && IS_5GHZ_FAST_CLOCK_EN(ah, c)) { + psec = (clks * 1000000ULL) / CLOCK_FAST_RATE_5GHZ_OFDM; + if (IEEE80211_IS_CHAN_HT40(c)) + psec >>= 1; + } else if (c != AH_NULL) { + psec = (clks * 1000000ULL) / CLOCK_RATE[ath_hal_chan2wmode(ah, c)]; + if (IEEE80211_IS_CHAN_HT40(c)) + psec >>= 1; + } else + psec = (clks * 1000000ULL) / CLOCK_RATE[WIRELESS_MODE_11b]; + return psec; +} + +/* + * Setup a h/w rate table's reverse lookup table and + * fill in ack durations. This routine is called for + * each rate table returned through the ah_getRateTable + * method. The reverse lookup tables are assumed to be + * initialized to zero (or at least the first entry). + * We use this as a key that indicates whether or not + * we've previously setup the reverse lookup table. + * + * XXX not reentrant, but shouldn't matter + */ +void +ath_hal_setupratetable(struct ath_hal *ah, HAL_RATE_TABLE *rt) +{ +#define N(a) (sizeof(a)/sizeof(a[0])) + int i; + + if (rt->rateCodeToIndex[0] != 0) /* already setup */ + return; + for (i = 0; i < N(rt->rateCodeToIndex); i++) + rt->rateCodeToIndex[i] = (uint8_t) -1; + for (i = 0; i < rt->rateCount; i++) { + uint8_t code = rt->info[i].rateCode; + uint8_t cix = rt->info[i].controlRate; + + HALASSERT(code < N(rt->rateCodeToIndex)); + rt->rateCodeToIndex[code] = i; + HALASSERT((code | rt->info[i].shortPreamble) < + N(rt->rateCodeToIndex)); + rt->rateCodeToIndex[code | rt->info[i].shortPreamble] = i; + /* + * XXX for 11g the control rate to use for 5.5 and 11 Mb/s + * depends on whether they are marked as basic rates; + * the static tables are setup with an 11b-compatible + * 2Mb/s rate which will work but is suboptimal + */ + rt->info[i].lpAckDuration = ath_hal_computetxtime(ah, rt, + WLAN_CTRL_FRAME_SIZE, cix, AH_FALSE, AH_TRUE); + rt->info[i].spAckDuration = ath_hal_computetxtime(ah, rt, + WLAN_CTRL_FRAME_SIZE, cix, AH_TRUE, AH_TRUE); + } +#undef N +} + +HAL_STATUS +ath_hal_getcapability(struct ath_hal *ah, HAL_CAPABILITY_TYPE type, + uint32_t capability, uint32_t *result) +{ + const HAL_CAPABILITIES *pCap = &AH_PRIVATE(ah)->ah_caps; + + switch (type) { + case HAL_CAP_REG_DMN: /* regulatory domain */ + *result = AH_PRIVATE(ah)->ah_currentRD; + return HAL_OK; + case HAL_CAP_DFS_DMN: /* DFS Domain */ + *result = AH_PRIVATE(ah)->ah_dfsDomain; + return HAL_OK; + case HAL_CAP_CIPHER: /* cipher handled in hardware */ + case HAL_CAP_TKIP_MIC: /* handle TKIP MIC in hardware */ + return HAL_ENOTSUPP; + case HAL_CAP_TKIP_SPLIT: /* hardware TKIP uses split keys */ + return HAL_ENOTSUPP; + case HAL_CAP_PHYCOUNTERS: /* hardware PHY error counters */ + return pCap->halHwPhyCounterSupport ? HAL_OK : HAL_ENXIO; + case HAL_CAP_WME_TKIPMIC: /* hardware can do TKIP MIC when WMM is turned on */ + return HAL_ENOTSUPP; + case HAL_CAP_DIVERSITY: /* hardware supports fast diversity */ + return HAL_ENOTSUPP; + case HAL_CAP_KEYCACHE_SIZE: /* hardware key cache size */ + *result = pCap->halKeyCacheSize; + return HAL_OK; + case HAL_CAP_NUM_TXQUEUES: /* number of hardware tx queues */ + *result = pCap->halTotalQueues; + return HAL_OK; + case HAL_CAP_VEOL: /* hardware supports virtual EOL */ + return pCap->halVEOLSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_PSPOLL: /* hardware PS-Poll support works */ + return pCap->halPSPollBroken ? HAL_ENOTSUPP : HAL_OK; + case HAL_CAP_COMPRESSION: + return pCap->halCompressSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_BURST: + return pCap->halBurstSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_FASTFRAME: + return pCap->halFastFramesSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_DIAG: /* hardware diagnostic support */ + *result = AH_PRIVATE(ah)->ah_diagreg; + return HAL_OK; + case HAL_CAP_TXPOW: /* global tx power limit */ + switch (capability) { + case 0: /* facility is supported */ + return HAL_OK; + case 1: /* current limit */ + *result = AH_PRIVATE(ah)->ah_powerLimit; + return HAL_OK; + case 2: /* current max tx power */ + *result = AH_PRIVATE(ah)->ah_maxPowerLevel; + return HAL_OK; + case 3: /* scale factor */ + *result = AH_PRIVATE(ah)->ah_tpScale; + return HAL_OK; + } + return HAL_ENOTSUPP; + case HAL_CAP_BSSIDMASK: /* hardware supports bssid mask */ + return pCap->halBssIdMaskSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_MCAST_KEYSRCH: /* multicast frame keycache search */ + return pCap->halMcastKeySrchSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_TSF_ADJUST: /* hardware has beacon tsf adjust */ + return HAL_ENOTSUPP; + case HAL_CAP_RFSILENT: /* rfsilent support */ + switch (capability) { + case 0: /* facility is supported */ + return pCap->halRfSilentSupport ? HAL_OK : HAL_ENOTSUPP; + case 1: /* current setting */ + return AH_PRIVATE(ah)->ah_rfkillEnabled ? + HAL_OK : HAL_ENOTSUPP; + case 2: /* rfsilent config */ + *result = AH_PRIVATE(ah)->ah_rfsilent; + return HAL_OK; + } + return HAL_ENOTSUPP; + case HAL_CAP_11D: + return HAL_OK; + + case HAL_CAP_HT: + return pCap->halHTSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_GTXTO: + return pCap->halGTTSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_FAST_CC: + return pCap->halFastCCSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_TX_CHAINMASK: /* mask of TX chains supported */ + *result = pCap->halTxChainMask; + return HAL_OK; + case HAL_CAP_RX_CHAINMASK: /* mask of RX chains supported */ + *result = pCap->halRxChainMask; + return HAL_OK; + case HAL_CAP_NUM_GPIO_PINS: + *result = pCap->halNumGpioPins; + return HAL_OK; + case HAL_CAP_CST: + return pCap->halCSTSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_RTS_AGGR_LIMIT: + *result = pCap->halRtsAggrLimit; + return HAL_OK; + case HAL_CAP_4ADDR_AGGR: + return pCap->hal4AddrAggrSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_EXT_CHAN_DFS: + return pCap->halExtChanDfsSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_RX_STBC: + return pCap->halRxStbcSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_TX_STBC: + return pCap->halTxStbcSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_COMBINED_RADAR_RSSI: + return pCap->halUseCombinedRadarRssi ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_AUTO_SLEEP: + return pCap->halAutoSleepSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_MBSSID_AGGR_SUPPORT: + return pCap->halMbssidAggrSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_SPLIT_4KB_TRANS: /* hardware handles descriptors straddling 4k page boundary */ + return pCap->hal4kbSplitTransSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_REG_FLAG: + *result = AH_PRIVATE(ah)->ah_currentRDext; + return HAL_OK; + case HAL_CAP_ENHANCED_DMA_SUPPORT: + return pCap->halEnhancedDmaSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_NUM_TXMAPS: + *result = pCap->halNumTxMaps; + return HAL_OK; + case HAL_CAP_TXDESCLEN: + *result = pCap->halTxDescLen; + return HAL_OK; + case HAL_CAP_TXSTATUSLEN: + *result = pCap->halTxStatusLen; + return HAL_OK; + case HAL_CAP_RXSTATUSLEN: + *result = pCap->halRxStatusLen; + return HAL_OK; + case HAL_CAP_RXFIFODEPTH: + switch (capability) { + case HAL_RX_QUEUE_HP: + *result = pCap->halRxHpFifoDepth; + return HAL_OK; + case HAL_RX_QUEUE_LP: + *result = pCap->halRxLpFifoDepth; + return HAL_OK; + default: + return HAL_ENOTSUPP; + } + case HAL_CAP_RXBUFSIZE: + case HAL_CAP_NUM_MR_RETRIES: + *result = pCap->halNumMRRetries; + return HAL_OK; + case HAL_CAP_BT_COEX: + return pCap->halBtCoexSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_SPECTRAL_SCAN: + return pCap->halSpectralScanSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_HT20_SGI: + return pCap->halHTSGI20Support ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_RXTSTAMP_PREC: /* rx desc tstamp precision (bits) */ + *result = pCap->halRxTstampPrecision; + return HAL_OK; + case HAL_CAP_ANT_DIV_COMB: /* AR9285/AR9485 LNA diversity */ + return pCap->halAntDivCombSupport ? HAL_OK : HAL_ENOTSUPP; + + case HAL_CAP_ENHANCED_DFS_SUPPORT: + return pCap->halEnhancedDfsSupport ? HAL_OK : HAL_ENOTSUPP; + + /* FreeBSD-specific entries for now */ + case HAL_CAP_RXORN_FATAL: /* HAL_INT_RXORN treated as fatal */ + return AH_PRIVATE(ah)->ah_rxornIsFatal ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_INTRMASK: /* mask of supported interrupts */ + *result = pCap->halIntrMask; + return HAL_OK; + case HAL_CAP_BSSIDMATCH: /* hardware has disable bssid match */ + return pCap->halBssidMatchSupport ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_STREAMS: /* number of 11n spatial streams */ + switch (capability) { + case 0: /* TX */ + *result = pCap->halTxStreams; + return HAL_OK; + case 1: /* RX */ + *result = pCap->halRxStreams; + return HAL_OK; + default: + return HAL_ENOTSUPP; + } + case HAL_CAP_RXDESC_SELFLINK: /* hardware supports self-linked final RX descriptors correctly */ + return pCap->halHasRxSelfLinkedTail ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_BB_READ_WAR: /* Baseband read WAR */ + return pCap->halHasBBReadWar? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_SERIALISE_WAR: /* PCI register serialisation */ + return pCap->halSerialiseRegWar ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_MFP: /* Management frame protection setting */ + *result = pCap->halMfpSupport; + return HAL_OK; + case HAL_CAP_RX_LNA_MIXING: /* Hardware uses an RX LNA mixer to map 2 antennas to a 1 stream receiver */ + return pCap->halRxUsingLnaMixing ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_DO_MYBEACON: /* Hardware supports filtering my-beacons */ + return pCap->halRxDoMyBeacon ? HAL_OK : HAL_ENOTSUPP; + case HAL_CAP_TXTSTAMP_PREC: /* tx desc tstamp precision (bits) */ + *result = pCap->halTxTstampPrecision; + return HAL_OK; + default: + return HAL_EINVAL; + } +} + +HAL_BOOL +ath_hal_setcapability(struct ath_hal *ah, HAL_CAPABILITY_TYPE type, + uint32_t capability, uint32_t setting, HAL_STATUS *status) +{ + + switch (type) { + case HAL_CAP_TXPOW: + switch (capability) { + case 3: + if (setting <= HAL_TP_SCALE_MIN) { + AH_PRIVATE(ah)->ah_tpScale = setting; + return AH_TRUE; + } + break; + } + break; + case HAL_CAP_RFSILENT: /* rfsilent support */ + /* + * NB: allow even if halRfSilentSupport is false + * in case the EEPROM is misprogrammed. + */ + switch (capability) { + case 1: /* current setting */ + AH_PRIVATE(ah)->ah_rfkillEnabled = (setting != 0); + return AH_TRUE; + case 2: /* rfsilent config */ + /* XXX better done per-chip for validation? */ + AH_PRIVATE(ah)->ah_rfsilent = setting; + return AH_TRUE; + } + break; + case HAL_CAP_REG_DMN: /* regulatory domain */ + AH_PRIVATE(ah)->ah_currentRD = setting; + return AH_TRUE; + case HAL_CAP_RXORN_FATAL: /* HAL_INT_RXORN treated as fatal */ + AH_PRIVATE(ah)->ah_rxornIsFatal = setting; + return AH_TRUE; + default: + break; + } + if (status) + *status = HAL_EINVAL; + return AH_FALSE; +} + +/* + * Common support for getDiagState method. + */ + +static u_int +ath_hal_getregdump(struct ath_hal *ah, const HAL_REGRANGE *regs, + void *dstbuf, int space) +{ + uint32_t *dp = dstbuf; + int i; + + for (i = 0; space >= 2*sizeof(uint32_t); i++) { + uint32_t r = regs[i].start; + uint32_t e = regs[i].end; + *dp++ = r; + *dp++ = e; + space -= 2*sizeof(uint32_t); + do { + *dp++ = OS_REG_READ(ah, r); + r += sizeof(uint32_t); + space -= sizeof(uint32_t); + } while (r <= e && space >= sizeof(uint32_t)); + } + return (char *) dp - (char *) dstbuf; +} + +static void +ath_hal_setregs(struct ath_hal *ah, const HAL_REGWRITE *regs, int space) +{ + while (space >= sizeof(HAL_REGWRITE)) { + OS_REG_WRITE(ah, regs->addr, regs->value); + regs++, space -= sizeof(HAL_REGWRITE); + } +} + +HAL_BOOL +ath_hal_getdiagstate(struct ath_hal *ah, int request, + const void *args, uint32_t argsize, + void **result, uint32_t *resultsize) +{ + + switch (request) { + case HAL_DIAG_REVS: + *result = &AH_PRIVATE(ah)->ah_devid; + *resultsize = sizeof(HAL_REVS); + return AH_TRUE; + case HAL_DIAG_REGS: + *resultsize = ath_hal_getregdump(ah, args, *result,*resultsize); + return AH_TRUE; + case HAL_DIAG_SETREGS: + ath_hal_setregs(ah, args, argsize); + *resultsize = 0; + return AH_TRUE; + case HAL_DIAG_FATALERR: + *result = &AH_PRIVATE(ah)->ah_fatalState[0]; + *resultsize = sizeof(AH_PRIVATE(ah)->ah_fatalState); + return AH_TRUE; + case HAL_DIAG_EEREAD: + if (argsize != sizeof(uint16_t)) + return AH_FALSE; + if (!ath_hal_eepromRead(ah, *(const uint16_t *)args, *result)) + return AH_FALSE; + *resultsize = sizeof(uint16_t); + return AH_TRUE; +#ifdef AH_PRIVATE_DIAG + case HAL_DIAG_SETKEY: { + const HAL_DIAG_KEYVAL *dk; + + if (argsize != sizeof(HAL_DIAG_KEYVAL)) + return AH_FALSE; + dk = (const HAL_DIAG_KEYVAL *)args; + return ah->ah_setKeyCacheEntry(ah, dk->dk_keyix, + &dk->dk_keyval, dk->dk_mac, dk->dk_xor); + } + case HAL_DIAG_RESETKEY: + if (argsize != sizeof(uint16_t)) + return AH_FALSE; + return ah->ah_resetKeyCacheEntry(ah, *(const uint16_t *)args); +#ifdef AH_SUPPORT_WRITE_EEPROM + case HAL_DIAG_EEWRITE: { + const HAL_DIAG_EEVAL *ee; + if (argsize != sizeof(HAL_DIAG_EEVAL)) + return AH_FALSE; + ee = (const HAL_DIAG_EEVAL *)args; + return ath_hal_eepromWrite(ah, ee->ee_off, ee->ee_data); + } +#endif /* AH_SUPPORT_WRITE_EEPROM */ +#endif /* AH_PRIVATE_DIAG */ + case HAL_DIAG_11NCOMPAT: + if (argsize == 0) { + *resultsize = sizeof(uint32_t); + *((uint32_t *)(*result)) = + AH_PRIVATE(ah)->ah_11nCompat; + } else if (argsize == sizeof(uint32_t)) { + AH_PRIVATE(ah)->ah_11nCompat = *(const uint32_t *)args; + } else + return AH_FALSE; + return AH_TRUE; + case HAL_DIAG_CHANSURVEY: + *result = &AH_PRIVATE(ah)->ah_chansurvey; + *resultsize = sizeof(HAL_CHANNEL_SURVEY); + return AH_TRUE; + } + return AH_FALSE; +} + +/* + * Set the properties of the tx queue with the parameters + * from qInfo. + */ +HAL_BOOL +ath_hal_setTxQProps(struct ath_hal *ah, + HAL_TX_QUEUE_INFO *qi, const HAL_TXQ_INFO *qInfo) +{ + uint32_t cw; + + if (qi->tqi_type == HAL_TX_QUEUE_INACTIVE) { + HALDEBUG(ah, HAL_DEBUG_TXQUEUE, + "%s: inactive queue\n", __func__); + return AH_FALSE; + } + /* XXX validate parameters */ + qi->tqi_ver = qInfo->tqi_ver; + qi->tqi_subtype = qInfo->tqi_subtype; + qi->tqi_qflags = qInfo->tqi_qflags; + qi->tqi_priority = qInfo->tqi_priority; + if (qInfo->tqi_aifs != HAL_TXQ_USEDEFAULT) + qi->tqi_aifs = AH_MIN(qInfo->tqi_aifs, 255); + else + qi->tqi_aifs = INIT_AIFS; + if (qInfo->tqi_cwmin != HAL_TXQ_USEDEFAULT) { + cw = AH_MIN(qInfo->tqi_cwmin, 1024); + /* make sure that the CWmin is of the form (2^n - 1) */ + qi->tqi_cwmin = 1; + while (qi->tqi_cwmin < cw) + qi->tqi_cwmin = (qi->tqi_cwmin << 1) | 1; + } else + qi->tqi_cwmin = qInfo->tqi_cwmin; + if (qInfo->tqi_cwmax != HAL_TXQ_USEDEFAULT) { + cw = AH_MIN(qInfo->tqi_cwmax, 1024); + /* make sure that the CWmax is of the form (2^n - 1) */ + qi->tqi_cwmax = 1; + while (qi->tqi_cwmax < cw) + qi->tqi_cwmax = (qi->tqi_cwmax << 1) | 1; + } else + qi->tqi_cwmax = INIT_CWMAX; + /* Set retry limit values */ + if (qInfo->tqi_shretry != 0) + qi->tqi_shretry = AH_MIN(qInfo->tqi_shretry, 15); + else + qi->tqi_shretry = INIT_SH_RETRY; + if (qInfo->tqi_lgretry != 0) + qi->tqi_lgretry = AH_MIN(qInfo->tqi_lgretry, 15); + else + qi->tqi_lgretry = INIT_LG_RETRY; + qi->tqi_cbrPeriod = qInfo->tqi_cbrPeriod; + qi->tqi_cbrOverflowLimit = qInfo->tqi_cbrOverflowLimit; + qi->tqi_burstTime = qInfo->tqi_burstTime; + qi->tqi_readyTime = qInfo->tqi_readyTime; + + switch (qInfo->tqi_subtype) { + case HAL_WME_UPSD: + if (qi->tqi_type == HAL_TX_QUEUE_DATA) + qi->tqi_intFlags = HAL_TXQ_USE_LOCKOUT_BKOFF_DIS; + break; + default: + break; /* NB: silence compiler */ + } + return AH_TRUE; +} + +HAL_BOOL +ath_hal_getTxQProps(struct ath_hal *ah, + HAL_TXQ_INFO *qInfo, const HAL_TX_QUEUE_INFO *qi) +{ + if (qi->tqi_type == HAL_TX_QUEUE_INACTIVE) { + HALDEBUG(ah, HAL_DEBUG_TXQUEUE, + "%s: inactive queue\n", __func__); + return AH_FALSE; + } + + qInfo->tqi_ver = qi->tqi_ver; + qInfo->tqi_subtype = qi->tqi_subtype; + qInfo->tqi_qflags = qi->tqi_qflags; + qInfo->tqi_priority = qi->tqi_priority; + qInfo->tqi_aifs = qi->tqi_aifs; + qInfo->tqi_cwmin = qi->tqi_cwmin; + qInfo->tqi_cwmax = qi->tqi_cwmax; + qInfo->tqi_shretry = qi->tqi_shretry; + qInfo->tqi_lgretry = qi->tqi_lgretry; + qInfo->tqi_cbrPeriod = qi->tqi_cbrPeriod; + qInfo->tqi_cbrOverflowLimit = qi->tqi_cbrOverflowLimit; + qInfo->tqi_burstTime = qi->tqi_burstTime; + qInfo->tqi_readyTime = qi->tqi_readyTime; + qInfo->tqi_compBuf = qi->tqi_physCompBuf; + return AH_TRUE; +} + + /* 11a Turbo 11b 11g 108g */ +static const int16_t NOISE_FLOOR[] = { -96, -93, -98, -96, -93 }; + +/* + * Read the current channel noise floor and return. + * If nf cal hasn't finished, channel noise floor should be 0 + * and we return a nominal value based on band and frequency. + * + * NB: This is a private routine used by per-chip code to + * implement the ah_getChanNoise method. + */ +int16_t +ath_hal_getChanNoise(struct ath_hal *ah, const struct ieee80211_channel *chan) +{ + HAL_CHANNEL_INTERNAL *ichan; + + ichan = ath_hal_checkchannel(ah, chan); + if (ichan == AH_NULL) { + HALDEBUG(ah, HAL_DEBUG_NFCAL, + "%s: invalid channel %u/0x%x; no mapping\n", + __func__, chan->ic_freq, chan->ic_flags); + return 0; + } + if (ichan->rawNoiseFloor == 0) { + WIRELESS_MODE mode = ath_hal_chan2wmode(ah, chan); + + HALASSERT(mode < WIRELESS_MODE_MAX); + return NOISE_FLOOR[mode] + ath_hal_getNfAdjust(ah, ichan); + } else + return ichan->rawNoiseFloor + ichan->noiseFloorAdjust; +} + +/* + * Fetch the current setup of ctl/ext noise floor values. + * + * If the CHANNEL_MIMO_NF_VALID flag isn't set, the array is simply + * populated with values from NOISE_FLOOR[] + ath_hal_getNfAdjust(). + * + * The caller must supply ctl/ext NF arrays which are at least + * AH_MAX_CHAINS entries long. + */ +int +ath_hal_get_mimo_chan_noise(struct ath_hal *ah, + const struct ieee80211_channel *chan, int16_t *nf_ctl, + int16_t *nf_ext) +{ + HAL_CHANNEL_INTERNAL *ichan; + int i; + + ichan = ath_hal_checkchannel(ah, chan); + if (ichan == AH_NULL) { + HALDEBUG(ah, HAL_DEBUG_NFCAL, + "%s: invalid channel %u/0x%x; no mapping\n", + __func__, chan->ic_freq, chan->ic_flags); + for (i = 0; i < AH_MAX_CHAINS; i++) { + nf_ctl[i] = nf_ext[i] = 0; + } + return 0; + } + + /* Return 0 if there's no valid MIMO values (yet) */ + if (! (ichan->privFlags & CHANNEL_MIMO_NF_VALID)) { + for (i = 0; i < AH_MAX_CHAINS; i++) { + nf_ctl[i] = nf_ext[i] = 0; + } + return 0; + } + if (ichan->rawNoiseFloor == 0) { + WIRELESS_MODE mode = ath_hal_chan2wmode(ah, chan); + HALASSERT(mode < WIRELESS_MODE_MAX); + /* + * See the comment below - this could cause issues for + * stations which have a very low RSSI, below the + * 'normalised' NF values in NOISE_FLOOR[]. + */ + for (i = 0; i < AH_MAX_CHAINS; i++) { + nf_ctl[i] = nf_ext[i] = NOISE_FLOOR[mode] + + ath_hal_getNfAdjust(ah, ichan); + } + return 1; + } else { + /* + * The value returned here from a MIMO radio is presumed to be + * "good enough" as a NF calculation. As RSSI values are calculated + * against this, an adjusted NF may be higher than the RSSI value + * returned from a vary weak station, resulting in an obscenely + * high signal strength calculation being returned. + * + * This should be re-evaluated at a later date, along with any + * signal strength calculations which are made. Quite likely the + * RSSI values will need to be adjusted to ensure the calculations + * don't "wrap" when RSSI is less than the "adjusted" NF value. + * ("Adjust" here is via ichan->noiseFloorAdjust.) + */ + for (i = 0; i < AH_MAX_CHAINS; i++) { + nf_ctl[i] = ichan->noiseFloorCtl[i] + ath_hal_getNfAdjust(ah, ichan); + nf_ext[i] = ichan->noiseFloorExt[i] + ath_hal_getNfAdjust(ah, ichan); + } + return 1; + } +} + +/* + * Process all valid raw noise floors into the dBm noise floor values. + * Though our device has no reference for a dBm noise floor, we perform + * a relative minimization of NF's based on the lowest NF found across a + * channel scan. + */ +void +ath_hal_process_noisefloor(struct ath_hal *ah) +{ + HAL_CHANNEL_INTERNAL *c; + int16_t correct2, correct5; + int16_t lowest2, lowest5; + int i; + + /* + * Find the lowest 2GHz and 5GHz noise floor values after adjusting + * for statistically recorded NF/channel deviation. + */ + correct2 = lowest2 = 0; + correct5 = lowest5 = 0; + for (i = 0; i < AH_PRIVATE(ah)->ah_nchan; i++) { + WIRELESS_MODE mode; + int16_t nf; + + c = &AH_PRIVATE(ah)->ah_channels[i]; + if (c->rawNoiseFloor >= 0) + continue; + /* XXX can't identify proper mode */ + mode = IS_CHAN_5GHZ(c) ? WIRELESS_MODE_11a : WIRELESS_MODE_11g; + nf = c->rawNoiseFloor + NOISE_FLOOR[mode] + + ath_hal_getNfAdjust(ah, c); + if (IS_CHAN_5GHZ(c)) { + if (nf < lowest5) { + lowest5 = nf; + correct5 = NOISE_FLOOR[mode] - + (c->rawNoiseFloor + ath_hal_getNfAdjust(ah, c)); + } + } else { + if (nf < lowest2) { + lowest2 = nf; + correct2 = NOISE_FLOOR[mode] - + (c->rawNoiseFloor + ath_hal_getNfAdjust(ah, c)); + } + } + } + + /* Correct the channels to reach the expected NF value */ + for (i = 0; i < AH_PRIVATE(ah)->ah_nchan; i++) { + c = &AH_PRIVATE(ah)->ah_channels[i]; + if (c->rawNoiseFloor >= 0) + continue; + /* Apply correction factor */ + c->noiseFloorAdjust = ath_hal_getNfAdjust(ah, c) + + (IS_CHAN_5GHZ(c) ? correct5 : correct2); + HALDEBUG(ah, HAL_DEBUG_NFCAL, "%u raw nf %d adjust %d\n", + c->channel, c->rawNoiseFloor, c->noiseFloorAdjust); + } +} + +/* + * INI support routines. + */ + +int +ath_hal_ini_write(struct ath_hal *ah, const HAL_INI_ARRAY *ia, + int col, int regWr) +{ + int r; + + HALASSERT(col < ia->cols); + for (r = 0; r < ia->rows; r++) { + OS_REG_WRITE(ah, HAL_INI_VAL(ia, r, 0), + HAL_INI_VAL(ia, r, col)); + + /* Analog shift register delay seems needed for Merlin - PR kern/154220 */ + if (HAL_INI_VAL(ia, r, 0) >= 0x7800 && HAL_INI_VAL(ia, r, 0) < 0x7900) + OS_DELAY(100); + + DMA_YIELD(regWr); + } + return regWr; +} + +void +ath_hal_ini_bank_setup(uint32_t data[], const HAL_INI_ARRAY *ia, int col) +{ + int r; + + HALASSERT(col < ia->cols); + for (r = 0; r < ia->rows; r++) + data[r] = HAL_INI_VAL(ia, r, col); +} + +int +ath_hal_ini_bank_write(struct ath_hal *ah, const HAL_INI_ARRAY *ia, + const uint32_t data[], int regWr) +{ + int r; + + for (r = 0; r < ia->rows; r++) { + OS_REG_WRITE(ah, HAL_INI_VAL(ia, r, 0), data[r]); + DMA_YIELD(regWr); + } + return regWr; +} + +/* + * These are EEPROM board related routines which should likely live in + * a helper library of some sort. + */ + +/************************************************************** + * ath_ee_getLowerUppderIndex + * + * Return indices surrounding the value in sorted integer lists. + * Requirement: the input list must be monotonically increasing + * and populated up to the list size + * Returns: match is set if an index in the array matches exactly + * or a the target is before or after the range of the array. + */ +HAL_BOOL +ath_ee_getLowerUpperIndex(uint8_t target, uint8_t *pList, uint16_t listSize, + uint16_t *indexL, uint16_t *indexR) +{ + uint16_t i; + + /* + * Check first and last elements for beyond ordered array cases. + */ + if (target <= pList[0]) { + *indexL = *indexR = 0; + return AH_TRUE; + } + if (target >= pList[listSize-1]) { + *indexL = *indexR = (uint16_t)(listSize - 1); + return AH_TRUE; + } + + /* look for value being near or between 2 values in list */ + for (i = 0; i < listSize - 1; i++) { + /* + * If value is close to the current value of the list + * then target is not between values, it is one of the values + */ + if (pList[i] == target) { + *indexL = *indexR = i; + return AH_TRUE; + } + /* + * Look for value being between current value and next value + * if so return these 2 values + */ + if (target < pList[i + 1]) { + *indexL = i; + *indexR = (uint16_t)(i + 1); + return AH_FALSE; + } + } + HALASSERT(0); + *indexL = *indexR = 0; + return AH_FALSE; +} + +/************************************************************** + * ath_ee_FillVpdTable + * + * Fill the Vpdlist for indices Pmax-Pmin + * Note: pwrMin, pwrMax and Vpdlist are all in dBm * 4 + */ +HAL_BOOL +ath_ee_FillVpdTable(uint8_t pwrMin, uint8_t pwrMax, uint8_t *pPwrList, + uint8_t *pVpdList, uint16_t numIntercepts, uint8_t *pRetVpdList) +{ + uint16_t i, k; + uint8_t currPwr = pwrMin; + uint16_t idxL, idxR; + + HALASSERT(pwrMax > pwrMin); + for (i = 0; i <= (pwrMax - pwrMin) / 2; i++) { + ath_ee_getLowerUpperIndex(currPwr, pPwrList, numIntercepts, + &(idxL), &(idxR)); + if (idxR < 1) + idxR = 1; /* extrapolate below */ + if (idxL == numIntercepts - 1) + idxL = (uint16_t)(numIntercepts - 2); /* extrapolate above */ + if (pPwrList[idxL] == pPwrList[idxR]) + k = pVpdList[idxL]; + else + k = (uint16_t)( ((currPwr - pPwrList[idxL]) * pVpdList[idxR] + (pPwrList[idxR] - currPwr) * pVpdList[idxL]) / + (pPwrList[idxR] - pPwrList[idxL]) ); + HALASSERT(k < 256); + pRetVpdList[i] = (uint8_t)k; + currPwr += 2; /* half dB steps */ + } + + return AH_TRUE; +} + +/************************************************************************** + * ath_ee_interpolate + * + * Returns signed interpolated or the scaled up interpolated value + */ +int16_t +ath_ee_interpolate(uint16_t target, uint16_t srcLeft, uint16_t srcRight, + int16_t targetLeft, int16_t targetRight) +{ + int16_t rv; + + if (srcRight == srcLeft) { + rv = targetLeft; + } else { + rv = (int16_t)( ((target - srcLeft) * targetRight + + (srcRight - target) * targetLeft) / (srcRight - srcLeft) ); + } + return rv; +} + +/* + * Adjust the TSF. + */ +void +ath_hal_adjusttsf(struct ath_hal *ah, int32_t tsfdelta) +{ + /* XXX handle wrap/overflow */ + OS_REG_WRITE(ah, AR_TSF_L32, OS_REG_READ(ah, AR_TSF_L32) + tsfdelta); +} + +/* + * Enable or disable CCA. + */ +void +ath_hal_setcca(struct ath_hal *ah, int ena) +{ + /* + * NB: fill me in; this is not provided by default because disabling + * CCA in most locales violates regulatory. + */ +} + +/* + * Get CCA setting. + * + * XXX TODO: turn this and the above function into methods + * in case there are chipset differences in handling CCA. + */ +int +ath_hal_getcca(struct ath_hal *ah) +{ + u_int32_t diag; + if (ath_hal_getcapability(ah, HAL_CAP_DIAG, 0, &diag) != HAL_OK) + return 1; + return ((diag & 0x500000) == 0); +} + +/* + * Set the current state of self-generated ACK and RTS/CTS frames. + * + * For correct DFS operation, the device should not even /ACK/ frames + * that are sent to it during CAC or CSA. + */ +void +ath_hal_set_dfs_cac_tx_quiet(struct ath_hal *ah, HAL_BOOL ena) +{ + + if (ah->ah_setDfsCacTxQuiet == NULL) + return; + ah->ah_setDfsCacTxQuiet(ah, ena); +} + +/* + * This routine is only needed when supporting EEPROM-in-RAM setups + * (eg embedded SoCs and on-board PCI/PCIe devices.) + */ +/* NB: This is in 16 bit words; not bytes */ +/* XXX This doesn't belong here! */ +#define ATH_DATA_EEPROM_SIZE 2048 + +HAL_BOOL +ath_hal_EepromDataRead(struct ath_hal *ah, u_int off, uint16_t *data) +{ + if (ah->ah_eepromdata == AH_NULL) { + HALDEBUG(ah, HAL_DEBUG_ANY, "%s: no eeprom data!\n", __func__); + return AH_FALSE; + } + if (off > ATH_DATA_EEPROM_SIZE) { + HALDEBUG(ah, HAL_DEBUG_ANY, "%s: offset %x > %x\n", + __func__, off, ATH_DATA_EEPROM_SIZE); + return AH_FALSE; + } + (*data) = ah->ah_eepromdata[off]; + return AH_TRUE; +} + +/* + * Do a 2GHz specific MHz->IEEE based on the hardware + * frequency. + * + * This is the unmapped frequency which is programmed into the hardware. + */ +int +ath_hal_mhz2ieee_2ghz(struct ath_hal *ah, int freq) +{ + + if (freq == 2484) + return 14; + if (freq < 2484) + return ((int) freq - 2407) / 5; + else + return 15 + ((freq - 2512) / 20); +} + +/* + * Clear the current survey data. + * + * This should be done during a channel change. + */ +void +ath_hal_survey_clear(struct ath_hal *ah) +{ + + OS_MEMZERO(&AH_PRIVATE(ah)->ah_chansurvey, + sizeof(AH_PRIVATE(ah)->ah_chansurvey)); +} + +/* + * Add a sample to the channel survey. + */ +void +ath_hal_survey_add_sample(struct ath_hal *ah, HAL_SURVEY_SAMPLE *hs) +{ + HAL_CHANNEL_SURVEY *cs; + + cs = &AH_PRIVATE(ah)->ah_chansurvey; + + OS_MEMCPY(&cs->samples[cs->cur_sample], hs, sizeof(*hs)); + cs->samples[cs->cur_sample].seq_num = cs->cur_seq; + cs->cur_sample = (cs->cur_sample + 1) % CHANNEL_SURVEY_SAMPLE_COUNT; + cs->cur_seq++; +} |