From 203c4805e91786f9a010bc7945a0fde70c9da28e Mon Sep 17 00:00:00 2001 From: "Luis R. Rodriguez" Date: Mon, 30 Mar 2009 22:30:33 -0400 Subject: atheros: put atheros wireless drivers into ath/ Signed-off-by: Luis R. Rodriguez Signed-off-by: John W. Linville --- drivers/net/wireless/ath/ath5k/reset.c | 1346 ++++++++++++++++++++++++++++++++ 1 file changed, 1346 insertions(+) create mode 100644 drivers/net/wireless/ath/ath5k/reset.c (limited to 'drivers/net/wireless/ath/ath5k/reset.c') diff --git a/drivers/net/wireless/ath/ath5k/reset.c b/drivers/net/wireless/ath/ath5k/reset.c new file mode 100644 index 00000000000..775fdf78554 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/reset.c @@ -0,0 +1,1346 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter + * Copyright (c) 2006-2008 Nick Kossifidis + * Copyright (c) 2007-2008 Luis Rodriguez + * Copyright (c) 2007-2008 Pavel Roskin + * Copyright (c) 2007-2008 Jiri Slaby + * + * 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. + * + */ + +#define _ATH5K_RESET + +/*****************************\ + Reset functions and helpers +\*****************************/ + +#include /* To determine if a card is pci-e */ +#include /* For get_bitmask_order */ +#include "ath5k.h" +#include "reg.h" +#include "base.h" +#include "debug.h" + +/** + * ath5k_hw_write_ofdm_timings - set OFDM timings on AR5212 + * + * @ah: the &struct ath5k_hw + * @channel: the currently set channel upon reset + * + * Write the delta slope coefficient (used on pilot tracking ?) for OFDM + * operation on the AR5212 upon reset. This is a helper for ath5k_hw_reset(). + * + * Since delta slope is floating point we split it on its exponent and + * mantissa and provide these values on hw. + * + * For more infos i think this patent is related + * http://www.freepatentsonline.com/7184495.html + */ +static inline int ath5k_hw_write_ofdm_timings(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + /* Get exponent and mantissa and set it */ + u32 coef_scaled, coef_exp, coef_man, + ds_coef_exp, ds_coef_man, clock; + + BUG_ON(!(ah->ah_version == AR5K_AR5212) || + !(channel->hw_value & CHANNEL_OFDM)); + + /* Get coefficient + * ALGO: coef = (5 * clock * carrier_freq) / 2) + * we scale coef by shifting clock value by 24 for + * better precision since we use integers */ + /* TODO: Half/quarter rate */ + clock = ath5k_hw_htoclock(1, channel->hw_value & CHANNEL_TURBO); + + coef_scaled = ((5 * (clock << 24)) / 2) / channel->center_freq; + + /* Get exponent + * ALGO: coef_exp = 14 - highest set bit position */ + coef_exp = get_bitmask_order(coef_scaled); + + /* Doesn't make sense if it's zero*/ + if (!coef_exp) + return -EINVAL; + + /* Note: we've shifted coef_scaled by 24 */ + coef_exp = 14 - (coef_exp - 24); + + + /* Get mantissa (significant digits) + * ALGO: coef_mant = floor(coef_scaled* 2^coef_exp+0.5) */ + coef_man = coef_scaled + + (1 << (24 - coef_exp - 1)); + + /* Calculate delta slope coefficient exponent + * and mantissa (remove scaling) and set them on hw */ + ds_coef_man = coef_man >> (24 - coef_exp); + ds_coef_exp = coef_exp - 16; + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_3, + AR5K_PHY_TIMING_3_DSC_MAN, ds_coef_man); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_3, + AR5K_PHY_TIMING_3_DSC_EXP, ds_coef_exp); + + return 0; +} + + +/* + * index into rates for control rates, we can set it up like this because + * this is only used for AR5212 and we know it supports G mode + */ +static const unsigned int control_rates[] = + { 0, 1, 1, 1, 4, 4, 6, 6, 8, 8, 8, 8 }; + +/** + * ath5k_hw_write_rate_duration - fill rate code to duration table + * + * @ah: the &struct ath5k_hw + * @mode: one of enum ath5k_driver_mode + * + * Write the rate code to duration table upon hw reset. This is a helper for + * ath5k_hw_reset(). It seems all this is doing is setting an ACK timeout on + * the hardware, based on current mode, for each rate. The rates which are + * capable of short preamble (802.11b rates 2Mbps, 5.5Mbps, and 11Mbps) have + * different rate code so we write their value twice (one for long preample + * and one for short). + * + * Note: Band doesn't matter here, if we set the values for OFDM it works + * on both a and g modes. So all we have to do is set values for all g rates + * that include all OFDM and CCK rates. If we operate in turbo or xr/half/ + * quarter rate mode, we need to use another set of bitrates (that's why we + * need the mode parameter) but we don't handle these proprietary modes yet. + */ +static inline void ath5k_hw_write_rate_duration(struct ath5k_hw *ah, + unsigned int mode) +{ + struct ath5k_softc *sc = ah->ah_sc; + struct ieee80211_rate *rate; + unsigned int i; + + /* Write rate duration table */ + for (i = 0; i < sc->sbands[IEEE80211_BAND_2GHZ].n_bitrates; i++) { + u32 reg; + u16 tx_time; + + rate = &sc->sbands[IEEE80211_BAND_2GHZ].bitrates[control_rates[i]]; + + /* Set ACK timeout */ + reg = AR5K_RATE_DUR(rate->hw_value); + + /* An ACK frame consists of 10 bytes. If you add the FCS, + * which ieee80211_generic_frame_duration() adds, + * its 14 bytes. Note we use the control rate and not the + * actual rate for this rate. See mac80211 tx.c + * ieee80211_duration() for a brief description of + * what rate we should choose to TX ACKs. */ + tx_time = le16_to_cpu(ieee80211_generic_frame_duration(sc->hw, + sc->vif, 10, rate)); + + ath5k_hw_reg_write(ah, tx_time, reg); + + if (!(rate->flags & IEEE80211_RATE_SHORT_PREAMBLE)) + continue; + + /* + * We're not distinguishing short preamble here, + * This is true, all we'll get is a longer value here + * which is not necessarilly bad. We could use + * export ieee80211_frame_duration() but that needs to be + * fixed first to be properly used by mac802111 drivers: + * + * - remove erp stuff and let the routine figure ofdm + * erp rates + * - remove passing argument ieee80211_local as + * drivers don't have access to it + * - move drivers using ieee80211_generic_frame_duration() + * to this + */ + ath5k_hw_reg_write(ah, tx_time, + reg + (AR5K_SET_SHORT_PREAMBLE << 2)); + } +} + +/* + * Reset chipset + */ +static int ath5k_hw_nic_reset(struct ath5k_hw *ah, u32 val) +{ + int ret; + u32 mask = val ? val : ~0U; + + ATH5K_TRACE(ah->ah_sc); + + /* Read-and-clear RX Descriptor Pointer*/ + ath5k_hw_reg_read(ah, AR5K_RXDP); + + /* + * Reset the device and wait until success + */ + ath5k_hw_reg_write(ah, val, AR5K_RESET_CTL); + + /* Wait at least 128 PCI clocks */ + udelay(15); + + if (ah->ah_version == AR5K_AR5210) { + val &= AR5K_RESET_CTL_PCU | AR5K_RESET_CTL_DMA + | AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_PHY; + mask &= AR5K_RESET_CTL_PCU | AR5K_RESET_CTL_DMA + | AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_PHY; + } else { + val &= AR5K_RESET_CTL_PCU | AR5K_RESET_CTL_BASEBAND; + mask &= AR5K_RESET_CTL_PCU | AR5K_RESET_CTL_BASEBAND; + } + + ret = ath5k_hw_register_timeout(ah, AR5K_RESET_CTL, mask, val, false); + + /* + * Reset configuration register (for hw byte-swap). Note that this + * is only set for big endian. We do the necessary magic in + * AR5K_INIT_CFG. + */ + if ((val & AR5K_RESET_CTL_PCU) == 0) + ath5k_hw_reg_write(ah, AR5K_INIT_CFG, AR5K_CFG); + + return ret; +} + +/* + * Sleep control + */ +int ath5k_hw_set_power(struct ath5k_hw *ah, enum ath5k_power_mode mode, + bool set_chip, u16 sleep_duration) +{ + unsigned int i; + u32 staid, data; + + ATH5K_TRACE(ah->ah_sc); + staid = ath5k_hw_reg_read(ah, AR5K_STA_ID1); + + switch (mode) { + case AR5K_PM_AUTO: + staid &= ~AR5K_STA_ID1_DEFAULT_ANTENNA; + /* fallthrough */ + case AR5K_PM_NETWORK_SLEEP: + if (set_chip) + ath5k_hw_reg_write(ah, + AR5K_SLEEP_CTL_SLE_ALLOW | + sleep_duration, + AR5K_SLEEP_CTL); + + staid |= AR5K_STA_ID1_PWR_SV; + break; + + case AR5K_PM_FULL_SLEEP: + if (set_chip) + ath5k_hw_reg_write(ah, AR5K_SLEEP_CTL_SLE_SLP, + AR5K_SLEEP_CTL); + + staid |= AR5K_STA_ID1_PWR_SV; + break; + + case AR5K_PM_AWAKE: + + staid &= ~AR5K_STA_ID1_PWR_SV; + + if (!set_chip) + goto commit; + + /* Preserve sleep duration */ + data = ath5k_hw_reg_read(ah, AR5K_SLEEP_CTL); + if (data & 0xffc00000) + data = 0; + else + data = data & 0xfffcffff; + + ath5k_hw_reg_write(ah, data, AR5K_SLEEP_CTL); + udelay(15); + + for (i = 50; i > 0; i--) { + /* Check if the chip did wake up */ + if ((ath5k_hw_reg_read(ah, AR5K_PCICFG) & + AR5K_PCICFG_SPWR_DN) == 0) + break; + + /* Wait a bit and retry */ + udelay(200); + ath5k_hw_reg_write(ah, data, AR5K_SLEEP_CTL); + } + + /* Fail if the chip didn't wake up */ + if (i <= 0) + return -EIO; + + break; + + default: + return -EINVAL; + } + +commit: + ah->ah_power_mode = mode; + ath5k_hw_reg_write(ah, staid, AR5K_STA_ID1); + + return 0; +} + +/* + * Bring up MAC + PHY Chips and program PLL + * TODO: Half/Quarter rate support + */ +int ath5k_hw_nic_wakeup(struct ath5k_hw *ah, int flags, bool initial) +{ + struct pci_dev *pdev = ah->ah_sc->pdev; + u32 turbo, mode, clock, bus_flags; + int ret; + + turbo = 0; + mode = 0; + clock = 0; + + ATH5K_TRACE(ah->ah_sc); + + /* Wakeup the device */ + ret = ath5k_hw_set_power(ah, AR5K_PM_AWAKE, true, 0); + if (ret) { + ATH5K_ERR(ah->ah_sc, "failed to wakeup the MAC Chip\n"); + return ret; + } + + if (ah->ah_version != AR5K_AR5210) { + /* + * Get channel mode flags + */ + + if (ah->ah_radio >= AR5K_RF5112) { + mode = AR5K_PHY_MODE_RAD_RF5112; + clock = AR5K_PHY_PLL_RF5112; + } else { + mode = AR5K_PHY_MODE_RAD_RF5111; /*Zero*/ + clock = AR5K_PHY_PLL_RF5111; /*Zero*/ + } + + if (flags & CHANNEL_2GHZ) { + mode |= AR5K_PHY_MODE_FREQ_2GHZ; + clock |= AR5K_PHY_PLL_44MHZ; + + if (flags & CHANNEL_CCK) { + mode |= AR5K_PHY_MODE_MOD_CCK; + } else if (flags & CHANNEL_OFDM) { + /* XXX Dynamic OFDM/CCK is not supported by the + * AR5211 so we set MOD_OFDM for plain g (no + * CCK headers) operation. We need to test + * this, 5211 might support ofdm-only g after + * all, there are also initial register values + * in the code for g mode (see initvals.c). */ + if (ah->ah_version == AR5K_AR5211) + mode |= AR5K_PHY_MODE_MOD_OFDM; + else + mode |= AR5K_PHY_MODE_MOD_DYN; + } else { + ATH5K_ERR(ah->ah_sc, + "invalid radio modulation mode\n"); + return -EINVAL; + } + } else if (flags & CHANNEL_5GHZ) { + mode |= AR5K_PHY_MODE_FREQ_5GHZ; + + if (ah->ah_radio == AR5K_RF5413) + clock = AR5K_PHY_PLL_40MHZ_5413; + else + clock |= AR5K_PHY_PLL_40MHZ; + + if (flags & CHANNEL_OFDM) + mode |= AR5K_PHY_MODE_MOD_OFDM; + else { + ATH5K_ERR(ah->ah_sc, + "invalid radio modulation mode\n"); + return -EINVAL; + } + } else { + ATH5K_ERR(ah->ah_sc, "invalid radio frequency mode\n"); + return -EINVAL; + } + + if (flags & CHANNEL_TURBO) + turbo = AR5K_PHY_TURBO_MODE | AR5K_PHY_TURBO_SHORT; + } else { /* Reset the device */ + + /* ...enable Atheros turbo mode if requested */ + if (flags & CHANNEL_TURBO) + ath5k_hw_reg_write(ah, AR5K_PHY_TURBO_MODE, + AR5K_PHY_TURBO); + } + + /* reseting PCI on PCI-E cards results card to hang + * and always return 0xffff... so we ingore that flag + * for PCI-E cards */ + bus_flags = (pdev->is_pcie) ? 0 : AR5K_RESET_CTL_PCI; + + /* Reset chipset */ + if (ah->ah_version == AR5K_AR5210) { + ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU | + AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_DMA | + AR5K_RESET_CTL_PHY | AR5K_RESET_CTL_PCI); + mdelay(2); + } else { + ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU | + AR5K_RESET_CTL_BASEBAND | bus_flags); + } + if (ret) { + ATH5K_ERR(ah->ah_sc, "failed to reset the MAC Chip\n"); + return -EIO; + } + + /* ...wakeup again!*/ + ret = ath5k_hw_set_power(ah, AR5K_PM_AWAKE, true, 0); + if (ret) { + ATH5K_ERR(ah->ah_sc, "failed to resume the MAC Chip\n"); + return ret; + } + + /* ...final warm reset */ + if (ath5k_hw_nic_reset(ah, 0)) { + ATH5K_ERR(ah->ah_sc, "failed to warm reset the MAC Chip\n"); + return -EIO; + } + + if (ah->ah_version != AR5K_AR5210) { + + /* ...update PLL if needed */ + if (ath5k_hw_reg_read(ah, AR5K_PHY_PLL) != clock) { + ath5k_hw_reg_write(ah, clock, AR5K_PHY_PLL); + udelay(300); + } + + /* ...set the PHY operating mode */ + ath5k_hw_reg_write(ah, mode, AR5K_PHY_MODE); + ath5k_hw_reg_write(ah, turbo, AR5K_PHY_TURBO); + } + + return 0; +} + +/* + * If there is an external 32KHz crystal available, use it + * as ref. clock instead of 32/40MHz clock and baseband clocks + * to save power during sleep or restore normal 32/40MHz + * operation. + * + * XXX: When operating on 32KHz certain PHY registers (27 - 31, + * 123 - 127) require delay on access. + */ +static void ath5k_hw_set_sleep_clock(struct ath5k_hw *ah, bool enable) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u32 scal, spending, usec32; + + /* Only set 32KHz settings if we have an external + * 32KHz crystal present */ + if ((AR5K_EEPROM_HAS32KHZCRYSTAL(ee->ee_misc1) || + AR5K_EEPROM_HAS32KHZCRYSTAL_OLD(ee->ee_misc1)) && + enable) { + + /* 1 usec/cycle */ + AR5K_REG_WRITE_BITS(ah, AR5K_USEC_5211, AR5K_USEC_32, 1); + /* Set up tsf increment on each cycle */ + AR5K_REG_WRITE_BITS(ah, AR5K_TSF_PARM, AR5K_TSF_PARM_INC, 61); + + /* Set baseband sleep control registers + * and sleep control rate */ + ath5k_hw_reg_write(ah, 0x1f, AR5K_PHY_SCR); + + if ((ah->ah_radio == AR5K_RF5112) || + (ah->ah_radio == AR5K_RF5413) || + (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4))) + spending = 0x14; + else + spending = 0x18; + ath5k_hw_reg_write(ah, spending, AR5K_PHY_SPENDING); + + if ((ah->ah_radio == AR5K_RF5112) || + (ah->ah_radio == AR5K_RF5413) || + (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4))) { + ath5k_hw_reg_write(ah, 0x26, AR5K_PHY_SLMT); + ath5k_hw_reg_write(ah, 0x0d, AR5K_PHY_SCAL); + ath5k_hw_reg_write(ah, 0x07, AR5K_PHY_SCLOCK); + ath5k_hw_reg_write(ah, 0x3f, AR5K_PHY_SDELAY); + AR5K_REG_WRITE_BITS(ah, AR5K_PCICFG, + AR5K_PCICFG_SLEEP_CLOCK_RATE, 0x02); + } else { + ath5k_hw_reg_write(ah, 0x0a, AR5K_PHY_SLMT); + ath5k_hw_reg_write(ah, 0x0c, AR5K_PHY_SCAL); + ath5k_hw_reg_write(ah, 0x03, AR5K_PHY_SCLOCK); + ath5k_hw_reg_write(ah, 0x20, AR5K_PHY_SDELAY); + AR5K_REG_WRITE_BITS(ah, AR5K_PCICFG, + AR5K_PCICFG_SLEEP_CLOCK_RATE, 0x03); + } + + /* Enable sleep clock operation */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PCICFG, + AR5K_PCICFG_SLEEP_CLOCK_EN); + + } else { + + /* Disable sleep clock operation and + * restore default parameters */ + AR5K_REG_DISABLE_BITS(ah, AR5K_PCICFG, + AR5K_PCICFG_SLEEP_CLOCK_EN); + + AR5K_REG_WRITE_BITS(ah, AR5K_PCICFG, + AR5K_PCICFG_SLEEP_CLOCK_RATE, 0); + + ath5k_hw_reg_write(ah, 0x1f, AR5K_PHY_SCR); + ath5k_hw_reg_write(ah, AR5K_PHY_SLMT_32MHZ, AR5K_PHY_SLMT); + + if (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4)) + scal = AR5K_PHY_SCAL_32MHZ_2417; + else if (ath5k_eeprom_is_hb63(ah)) + scal = AR5K_PHY_SCAL_32MHZ_HB63; + else + scal = AR5K_PHY_SCAL_32MHZ; + ath5k_hw_reg_write(ah, scal, AR5K_PHY_SCAL); + + ath5k_hw_reg_write(ah, AR5K_PHY_SCLOCK_32MHZ, AR5K_PHY_SCLOCK); + ath5k_hw_reg_write(ah, AR5K_PHY_SDELAY_32MHZ, AR5K_PHY_SDELAY); + + if ((ah->ah_radio == AR5K_RF5112) || + (ah->ah_radio == AR5K_RF5413) || + (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4))) + spending = 0x14; + else + spending = 0x18; + ath5k_hw_reg_write(ah, spending, AR5K_PHY_SPENDING); + + if ((ah->ah_radio == AR5K_RF5112) || + (ah->ah_radio == AR5K_RF5413)) + usec32 = 39; + else + usec32 = 31; + AR5K_REG_WRITE_BITS(ah, AR5K_USEC_5211, AR5K_USEC_32, usec32); + + AR5K_REG_WRITE_BITS(ah, AR5K_TSF_PARM, AR5K_TSF_PARM_INC, 1); + } + return; +} + +static bool ath5k_hw_chan_has_spur_noise(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + u8 refclk_freq; + + if ((ah->ah_radio == AR5K_RF5112) || + (ah->ah_radio == AR5K_RF5413) || + (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4))) + refclk_freq = 40; + else + refclk_freq = 32; + + if ((channel->center_freq % refclk_freq != 0) && + ((channel->center_freq % refclk_freq < 10) || + (channel->center_freq % refclk_freq > 22))) + return true; + else + return false; +} + +/* TODO: Half/Quarter rate */ +static void ath5k_hw_tweak_initval_settings(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + if (ah->ah_version == AR5K_AR5212 && + ah->ah_phy_revision >= AR5K_SREV_PHY_5212A) { + + /* Setup ADC control */ + ath5k_hw_reg_write(ah, + (AR5K_REG_SM(2, + AR5K_PHY_ADC_CTL_INBUFGAIN_OFF) | + AR5K_REG_SM(2, + AR5K_PHY_ADC_CTL_INBUFGAIN_ON) | + AR5K_PHY_ADC_CTL_PWD_DAC_OFF | + AR5K_PHY_ADC_CTL_PWD_ADC_OFF), + AR5K_PHY_ADC_CTL); + + + + /* Disable barker RSSI threshold */ + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_DAG_CCK_CTL, + AR5K_PHY_DAG_CCK_CTL_EN_RSSI_THR); + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DAG_CCK_CTL, + AR5K_PHY_DAG_CCK_CTL_RSSI_THR, 2); + + /* Set the mute mask */ + ath5k_hw_reg_write(ah, 0x0000000f, AR5K_SEQ_MASK); + } + + /* Clear PHY_BLUETOOTH to allow RX_CLEAR line debug */ + if (ah->ah_phy_revision >= AR5K_SREV_PHY_5212B) + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BLUETOOTH); + + /* Enable DCU double buffering */ + if (ah->ah_phy_revision > AR5K_SREV_PHY_5212B) + AR5K_REG_DISABLE_BITS(ah, AR5K_TXCFG, + AR5K_TXCFG_DCU_DBL_BUF_DIS); + + /* Set DAC/ADC delays */ + if (ah->ah_version == AR5K_AR5212) { + u32 scal; + if (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4)) + scal = AR5K_PHY_SCAL_32MHZ_2417; + else if (ath5k_eeprom_is_hb63(ah)) + scal = AR5K_PHY_SCAL_32MHZ_HB63; + else + scal = AR5K_PHY_SCAL_32MHZ; + ath5k_hw_reg_write(ah, scal, AR5K_PHY_SCAL); + } + + /* Set fast ADC */ + if ((ah->ah_radio == AR5K_RF5413) || + (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4))) { + u32 fast_adc = true; + + if (channel->center_freq == 2462 || + channel->center_freq == 2467) + fast_adc = 0; + + /* Only update if needed */ + if (ath5k_hw_reg_read(ah, AR5K_PHY_FAST_ADC) != fast_adc) + ath5k_hw_reg_write(ah, fast_adc, + AR5K_PHY_FAST_ADC); + } + + /* Fix for first revision of the RF5112 RF chipset */ + if (ah->ah_radio == AR5K_RF5112 && + ah->ah_radio_5ghz_revision < + AR5K_SREV_RAD_5112A) { + u32 data; + ath5k_hw_reg_write(ah, AR5K_PHY_CCKTXCTL_WORLD, + AR5K_PHY_CCKTXCTL); + if (channel->hw_value & CHANNEL_5GHZ) + data = 0xffb81020; + else + data = 0xffb80d20; + ath5k_hw_reg_write(ah, data, AR5K_PHY_FRAME_CTL); + } + + if (ah->ah_mac_srev < AR5K_SREV_AR5211) { + u32 usec_reg; + /* 5311 has different tx/rx latency masks + * from 5211, since we deal 5311 the same + * as 5211 when setting initvals, shift + * values here to their proper locations */ + usec_reg = ath5k_hw_reg_read(ah, AR5K_USEC_5211); + ath5k_hw_reg_write(ah, usec_reg & (AR5K_USEC_1 | + AR5K_USEC_32 | + AR5K_USEC_TX_LATENCY_5211 | + AR5K_REG_SM(29, + AR5K_USEC_RX_LATENCY_5210)), + AR5K_USEC_5211); + /* Clear QCU/DCU clock gating register */ + ath5k_hw_reg_write(ah, 0, AR5K_QCUDCU_CLKGT); + /* Set DAC/ADC delays */ + ath5k_hw_reg_write(ah, 0x08, AR5K_PHY_SCAL); + /* Enable PCU FIFO corruption ECO */ + AR5K_REG_ENABLE_BITS(ah, AR5K_DIAG_SW_5211, + AR5K_DIAG_SW_ECO_ENABLE); + } +} + +static void ath5k_hw_commit_eeprom_settings(struct ath5k_hw *ah, + struct ieee80211_channel *channel, u8 *ant, u8 ee_mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + s16 cck_ofdm_pwr_delta; + + /* Adjust power delta for channel 14 */ + if (channel->center_freq == 2484) + cck_ofdm_pwr_delta = + ((ee->ee_cck_ofdm_power_delta - + ee->ee_scaled_cck_delta) * 2) / 10; + else + cck_ofdm_pwr_delta = + (ee->ee_cck_ofdm_power_delta * 2) / 10; + + /* Set CCK to OFDM power delta on tx power + * adjustment register */ + if (ah->ah_phy_revision >= AR5K_SREV_PHY_5212A) { + if (channel->hw_value == CHANNEL_G) + ath5k_hw_reg_write(ah, + AR5K_REG_SM((ee->ee_cck_ofdm_gain_delta * -1), + AR5K_PHY_TX_PWR_ADJ_CCK_GAIN_DELTA) | + AR5K_REG_SM((cck_ofdm_pwr_delta * -1), + AR5K_PHY_TX_PWR_ADJ_CCK_PCDAC_INDEX), + AR5K_PHY_TX_PWR_ADJ); + else + ath5k_hw_reg_write(ah, 0, AR5K_PHY_TX_PWR_ADJ); + } else { + /* For older revs we scale power on sw during tx power + * setup */ + ah->ah_txpower.txp_cck_ofdm_pwr_delta = cck_ofdm_pwr_delta; + ah->ah_txpower.txp_cck_ofdm_gainf_delta = + ee->ee_cck_ofdm_gain_delta; + } + + /* Set antenna idle switch table */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_ANT_CTL, + AR5K_PHY_ANT_CTL_SWTABLE_IDLE, + (ah->ah_antenna[ee_mode][0] | + AR5K_PHY_ANT_CTL_TXRX_EN)); + + /* Set antenna switch table */ + ath5k_hw_reg_write(ah, ah->ah_antenna[ee_mode][ant[0]], + AR5K_PHY_ANT_SWITCH_TABLE_0); + ath5k_hw_reg_write(ah, ah->ah_antenna[ee_mode][ant[1]], + AR5K_PHY_ANT_SWITCH_TABLE_1); + + /* Noise floor threshold */ + ath5k_hw_reg_write(ah, + AR5K_PHY_NF_SVAL(ee->ee_noise_floor_thr[ee_mode]), + AR5K_PHY_NFTHRES); + + if ((channel->hw_value & CHANNEL_TURBO) && + (ah->ah_ee_version >= AR5K_EEPROM_VERSION_5_0)) { + /* Switch settling time (Turbo) */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SETTLING, + AR5K_PHY_SETTLING_SWITCH, + ee->ee_switch_settling_turbo[ee_mode]); + + /* Tx/Rx attenuation (Turbo) */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_GAIN, + AR5K_PHY_GAIN_TXRX_ATTEN, + ee->ee_atn_tx_rx_turbo[ee_mode]); + + /* ADC/PGA desired size (Turbo) */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DESIRED_SIZE, + AR5K_PHY_DESIRED_SIZE_ADC, + ee->ee_adc_desired_size_turbo[ee_mode]); + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DESIRED_SIZE, + AR5K_PHY_DESIRED_SIZE_PGA, + ee->ee_pga_desired_size_turbo[ee_mode]); + + /* Tx/Rx margin (Turbo) */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_GAIN_2GHZ, + AR5K_PHY_GAIN_2GHZ_MARGIN_TXRX, + ee->ee_margin_tx_rx_turbo[ee_mode]); + + } else { + /* Switch settling time */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SETTLING, + AR5K_PHY_SETTLING_SWITCH, + ee->ee_switch_settling[ee_mode]); + + /* Tx/Rx attenuation */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_GAIN, + AR5K_PHY_GAIN_TXRX_ATTEN, + ee->ee_atn_tx_rx[ee_mode]); + + /* ADC/PGA desired size */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DESIRED_SIZE, + AR5K_PHY_DESIRED_SIZE_ADC, + ee->ee_adc_desired_size[ee_mode]); + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DESIRED_SIZE, + AR5K_PHY_DESIRED_SIZE_PGA, + ee->ee_pga_desired_size[ee_mode]); + + /* Tx/Rx margin */ + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_1) + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_GAIN_2GHZ, + AR5K_PHY_GAIN_2GHZ_MARGIN_TXRX, + ee->ee_margin_tx_rx[ee_mode]); + } + + /* XPA delays */ + ath5k_hw_reg_write(ah, + (ee->ee_tx_end2xpa_disable[ee_mode] << 24) | + (ee->ee_tx_end2xpa_disable[ee_mode] << 16) | + (ee->ee_tx_frm2xpa_enable[ee_mode] << 8) | + (ee->ee_tx_frm2xpa_enable[ee_mode]), AR5K_PHY_RF_CTL4); + + /* XLNA delay */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_RF_CTL3, + AR5K_PHY_RF_CTL3_TXE2XLNA_ON, + ee->ee_tx_end2xlna_enable[ee_mode]); + + /* Thresh64 (ANI) */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_NF, + AR5K_PHY_NF_THRESH62, + ee->ee_thr_62[ee_mode]); + + + /* False detect backoff for channels + * that have spur noise. Write the new + * cyclic power RSSI threshold. */ + if (ath5k_hw_chan_has_spur_noise(ah, channel)) + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_OFDM_SELFCORR, + AR5K_PHY_OFDM_SELFCORR_CYPWR_THR1, + AR5K_INIT_CYCRSSI_THR1 + + ee->ee_false_detect[ee_mode]); + else + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_OFDM_SELFCORR, + AR5K_PHY_OFDM_SELFCORR_CYPWR_THR1, + AR5K_INIT_CYCRSSI_THR1); + + /* I/Q correction + * TODO: Per channel i/q infos ? */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_IQ, + AR5K_PHY_IQ_CORR_ENABLE | + (ee->ee_i_cal[ee_mode] << AR5K_PHY_IQ_CORR_Q_I_COFF_S) | + ee->ee_q_cal[ee_mode]); + + /* Heavy clipping -disable for now */ + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_5_1) + ath5k_hw_reg_write(ah, 0, AR5K_PHY_HEAVY_CLIP_ENABLE); + + return; +} + +/* + * Main reset function + */ +int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, + struct ieee80211_channel *channel, bool change_channel) +{ + u32 s_seq[10], s_ant, s_led[3], staid1_flags, tsf_up, tsf_lo; + u32 phy_tst1; + u8 mode, freq, ee_mode, ant[2]; + int i, ret; + + ATH5K_TRACE(ah->ah_sc); + + s_ant = 0; + ee_mode = 0; + staid1_flags = 0; + tsf_up = 0; + tsf_lo = 0; + freq = 0; + mode = 0; + + /* + * Save some registers before a reset + */ + /*DCU/Antenna selection not available on 5210*/ + if (ah->ah_version != AR5K_AR5210) { + + switch (channel->hw_value & CHANNEL_MODES) { + case CHANNEL_A: + mode = AR5K_MODE_11A; + freq = AR5K_INI_RFGAIN_5GHZ; + ee_mode = AR5K_EEPROM_MODE_11A; + break; + case CHANNEL_G: + mode = AR5K_MODE_11G; + freq = AR5K_INI_RFGAIN_2GHZ; + ee_mode = AR5K_EEPROM_MODE_11G; + break; + case CHANNEL_B: + mode = AR5K_MODE_11B; + freq = AR5K_INI_RFGAIN_2GHZ; + ee_mode = AR5K_EEPROM_MODE_11B; + break; + case CHANNEL_T: + mode = AR5K_MODE_11A_TURBO; + freq = AR5K_INI_RFGAIN_5GHZ; + ee_mode = AR5K_EEPROM_MODE_11A; + break; + case CHANNEL_TG: + if (ah->ah_version == AR5K_AR5211) { + ATH5K_ERR(ah->ah_sc, + "TurboG mode not available on 5211"); + return -EINVAL; + } + mode = AR5K_MODE_11G_TURBO; + freq = AR5K_INI_RFGAIN_2GHZ; + ee_mode = AR5K_EEPROM_MODE_11G; + break; + case CHANNEL_XR: + if (ah->ah_version == AR5K_AR5211) { + ATH5K_ERR(ah->ah_sc, + "XR mode not available on 5211"); + return -EINVAL; + } + mode = AR5K_MODE_XR; + freq = AR5K_INI_RFGAIN_5GHZ; + ee_mode = AR5K_EEPROM_MODE_11A; + break; + default: + ATH5K_ERR(ah->ah_sc, + "invalid channel: %d\n", channel->center_freq); + return -EINVAL; + } + + if (change_channel) { + /* + * Save frame sequence count + * For revs. after Oahu, only save + * seq num for DCU 0 (Global seq num) + */ + if (ah->ah_mac_srev < AR5K_SREV_AR5211) { + + for (i = 0; i < 10; i++) + s_seq[i] = ath5k_hw_reg_read(ah, + AR5K_QUEUE_DCU_SEQNUM(i)); + + } else { + s_seq[0] = ath5k_hw_reg_read(ah, + AR5K_QUEUE_DCU_SEQNUM(0)); + } + + /* TSF accelerates on AR5211 durring reset + * As a workaround save it here and restore + * it later so that it's back in time after + * reset. This way it'll get re-synced on the + * next beacon without breaking ad-hoc. + * + * On AR5212 TSF is almost preserved across a + * reset so it stays back in time anyway and + * we don't have to save/restore it. + * + * XXX: Since this breaks power saving we have + * to disable power saving until we receive the + * next beacon, so we can resync beacon timers */ + if (ah->ah_version == AR5K_AR5211) { + tsf_up = ath5k_hw_reg_read(ah, AR5K_TSF_U32); + tsf_lo = ath5k_hw_reg_read(ah, AR5K_TSF_L32); + } + } + + /* Save default antenna */ + s_ant = ath5k_hw_reg_read(ah, AR5K_DEFAULT_ANTENNA); + + if (ah->ah_version == AR5K_AR5212) { + /* Restore normal 32/40MHz clock operation + * to avoid register access delay on certain + * PHY registers */ + ath5k_hw_set_sleep_clock(ah, false); + + /* Since we are going to write rf buffer + * check if we have any pending gain_F + * optimization settings */ + if (change_channel && ah->ah_rf_banks != NULL) + ath5k_hw_gainf_calibrate(ah); + } + } + + /*GPIOs*/ + s_led[0] = ath5k_hw_reg_read(ah, AR5K_PCICFG) & + AR5K_PCICFG_LEDSTATE; + s_led[1] = ath5k_hw_reg_read(ah, AR5K_GPIOCR); + s_led[2] = ath5k_hw_reg_read(ah, AR5K_GPIODO); + + /* AR5K_STA_ID1 flags, only preserve antenna + * settings and ack/cts rate mode */ + staid1_flags = ath5k_hw_reg_read(ah, AR5K_STA_ID1) & + (AR5K_STA_ID1_DEFAULT_ANTENNA | + AR5K_STA_ID1_DESC_ANTENNA | + AR5K_STA_ID1_RTS_DEF_ANTENNA | + AR5K_STA_ID1_ACKCTS_6MB | + AR5K_STA_ID1_BASE_RATE_11B | + AR5K_STA_ID1_SELFGEN_DEF_ANT); + + /* Wakeup the device */ + ret = ath5k_hw_nic_wakeup(ah, channel->hw_value, false); + if (ret) + return ret; + + /* + * Initialize operating mode + */ + ah->ah_op_mode = op_mode; + + /* PHY access enable */ + if (ah->ah_mac_srev >= AR5K_SREV_AR5211) + ath5k_hw_reg_write(ah, AR5K_PHY_SHIFT_5GHZ, AR5K_PHY(0)); + else + ath5k_hw_reg_write(ah, AR5K_PHY_SHIFT_5GHZ | 0x40, + AR5K_PHY(0)); + + /* Write initial settings */ + ret = ath5k_hw_write_initvals(ah, mode, change_channel); + if (ret) + return ret; + + /* + * 5211/5212 Specific + */ + if (ah->ah_version != AR5K_AR5210) { + + /* + * Write initial RF gain settings + * This should work for both 5111/5112 + */ + ret = ath5k_hw_rfgain_init(ah, freq); + if (ret) + return ret; + + mdelay(1); + + /* + * Tweak initval settings for revised + * chipsets and add some more config + * bits + */ + ath5k_hw_tweak_initval_settings(ah, channel); + + /* + * Set TX power (FIXME) + */ + ret = ath5k_hw_txpower(ah, channel, ee_mode, + AR5K_TUNE_DEFAULT_TXPOWER); + if (ret) + return ret; + + /* Write rate duration table only on AR5212 and if + * virtual interface has already been brought up + * XXX: rethink this after new mode changes to + * mac80211 are integrated */ + if (ah->ah_version == AR5K_AR5212 && + ah->ah_sc->vif != NULL) + ath5k_hw_write_rate_duration(ah, mode); + + /* + * Write RF buffer + */ + ret = ath5k_hw_rfregs_init(ah, channel, mode); + if (ret) + return ret; + + + /* Write OFDM timings on 5212*/ + if (ah->ah_version == AR5K_AR5212 && + channel->hw_value & CHANNEL_OFDM) { + ret = ath5k_hw_write_ofdm_timings(ah, channel); + if (ret) + return ret; + } + + /*Enable/disable 802.11b mode on 5111 + (enable 2111 frequency converter + CCK)*/ + if (ah->ah_radio == AR5K_RF5111) { + if (mode == AR5K_MODE_11B) + AR5K_REG_ENABLE_BITS(ah, AR5K_TXCFG, + AR5K_TXCFG_B_MODE); + else + AR5K_REG_DISABLE_BITS(ah, AR5K_TXCFG, + AR5K_TXCFG_B_MODE); + } + + /* + * In case a fixed antenna was set as default + * write the same settings on both AR5K_PHY_ANT_SWITCH_TABLE + * registers. + */ + if (s_ant != 0) { + if (s_ant == AR5K_ANT_FIXED_A) /* 1 - Main */ + ant[0] = ant[1] = AR5K_ANT_FIXED_A; + else /* 2 - Aux */ + ant[0] = ant[1] = AR5K_ANT_FIXED_B; + } else { + ant[0] = AR5K_ANT_FIXED_A; + ant[1] = AR5K_ANT_FIXED_B; + } + + /* Commit values from EEPROM */ + ath5k_hw_commit_eeprom_settings(ah, channel, ant, ee_mode); + + } else { + /* + * For 5210 we do all initialization using + * initvals, so we don't have to modify + * any settings (5210 also only supports + * a/aturbo modes) + */ + mdelay(1); + /* Disable phy and wait */ + ath5k_hw_reg_write(ah, AR5K_PHY_ACT_DISABLE, AR5K_PHY_ACT); + mdelay(1); + } + + /* + * Restore saved values + */ + + /*DCU/Antenna selection not available on 5210*/ + if (ah->ah_version != AR5K_AR5210) { + + if (change_channel) { + if (ah->ah_mac_srev < AR5K_SREV_AR5211) { + for (i = 0; i < 10; i++) + ath5k_hw_reg_write(ah, s_seq[i], + AR5K_QUEUE_DCU_SEQNUM(i)); + } else { + ath5k_hw_reg_write(ah, s_seq[0], + AR5K_QUEUE_DCU_SEQNUM(0)); + } + + + if (ah->ah_version == AR5K_AR5211) { + ath5k_hw_reg_write(ah, tsf_up, AR5K_TSF_U32); + ath5k_hw_reg_write(ah, tsf_lo, AR5K_TSF_L32); + } + } + + ath5k_hw_reg_write(ah, s_ant, AR5K_DEFAULT_ANTENNA); + } + + /* Ledstate */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PCICFG, s_led[0]); + + /* Gpio settings */ + ath5k_hw_reg_write(ah, s_led[1], AR5K_GPIOCR); + ath5k_hw_reg_write(ah, s_led[2], AR5K_GPIODO); + + /* Restore sta_id flags and preserve our mac address*/ + ath5k_hw_reg_write(ah, AR5K_LOW_ID(ah->ah_sta_id), + AR5K_STA_ID0); + ath5k_hw_reg_write(ah, staid1_flags | AR5K_HIGH_ID(ah->ah_sta_id), + AR5K_STA_ID1); + + + /* + * Configure PCU + */ + + /* Restore bssid and bssid mask */ + /* XXX: add ah->aid once mac80211 gives this to us */ + ath5k_hw_set_associd(ah, ah->ah_bssid, 0); + + /* Set PCU config */ + ath5k_hw_set_opmode(ah); + + /* Clear any pending interrupts + * PISR/SISR Not available on 5210 */ + if (ah->ah_version != AR5K_AR5210) + ath5k_hw_reg_write(ah, 0xffffffff, AR5K_PISR); + + /* Set RSSI/BRSSI thresholds + * + * Note: If we decide to set this value + * dynamicaly, have in mind that when AR5K_RSSI_THR + * register is read it might return 0x40 if we haven't + * wrote anything to it plus BMISS RSSI threshold is zeroed. + * So doing a save/restore procedure here isn't the right + * choice. Instead store it on ath5k_hw */ + ath5k_hw_reg_write(ah, (AR5K_TUNE_RSSI_THRES | + AR5K_TUNE_BMISS_THRES << + AR5K_RSSI_THR_BMISS_S), + AR5K_RSSI_THR); + + /* MIC QoS support */ + if (ah->ah_mac_srev >= AR5K_SREV_AR2413) { + ath5k_hw_reg_write(ah, 0x000100aa, AR5K_MIC_QOS_CTL); + ath5k_hw_reg_write(ah, 0x00003210, AR5K_MIC_QOS_SEL); + } + + /* QoS NOACK Policy */ + if (ah->ah_version == AR5K_AR5212) { + ath5k_hw_reg_write(ah, + AR5K_REG_SM(2, AR5K_QOS_NOACK_2BIT_VALUES) | + AR5K_REG_SM(5, AR5K_QOS_NOACK_BIT_OFFSET) | + AR5K_REG_SM(0, AR5K_QOS_NOACK_BYTE_OFFSET), + AR5K_QOS_NOACK); + } + + + /* + * Configure PHY + */ + + /* Set channel on PHY */ + ret = ath5k_hw_channel(ah, channel); + if (ret) + return ret; + + /* + * Enable the PHY and wait until completion + * This includes BaseBand and Synthesizer + * activation. + */ + ath5k_hw_reg_write(ah, AR5K_PHY_ACT_ENABLE, AR5K_PHY_ACT); + + /* + * On 5211+ read activation -> rx delay + * and use it. + * + * TODO: Half/quarter rate support + */ + if (ah->ah_version != AR5K_AR5210) { + u32 delay; + delay = ath5k_hw_reg_read(ah, AR5K_PHY_RX_DELAY) & + AR5K_PHY_RX_DELAY_M; + delay = (channel->hw_value & CHANNEL_CCK) ? + ((delay << 2) / 22) : (delay / 10); + + udelay(100 + (2 * delay)); + } else { + mdelay(1); + } + + /* + * Perform ADC test to see if baseband is ready + * Set tx hold and check adc test register + */ + phy_tst1 = ath5k_hw_reg_read(ah, AR5K_PHY_TST1); + ath5k_hw_reg_write(ah, AR5K_PHY_TST1_TXHOLD, AR5K_PHY_TST1); + for (i = 0; i <= 20; i++) { + if (!(ath5k_hw_reg_read(ah, AR5K_PHY_ADC_TEST) & 0x10)) + break; + udelay(200); + } + ath5k_hw_reg_write(ah, phy_tst1, AR5K_PHY_TST1); + + /* + * Start automatic gain control calibration + * + * During AGC calibration RX path is re-routed to + * a power detector so we don't receive anything. + * + * This method is used to calibrate some static offsets + * used together with on-the fly I/Q calibration (the + * one performed via ath5k_hw_phy_calibrate), that doesn't + * interrupt rx path. + * + * While rx path is re-routed to the power detector we also + * start a noise floor calibration, to measure the + * card's noise floor (the noise we measure when we are not + * transmiting or receiving anything). + * + * If we are in a noisy environment AGC calibration may time + * out and/or noise floor calibration might timeout. + */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_CAL); + + /* At the same time start I/Q calibration for QAM constellation + * -no need for CCK- */ + ah->ah_calibration = false; + if (!(mode == AR5K_MODE_11B)) { + ah->ah_calibration = true; + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_IQ, + AR5K_PHY_IQ_CAL_NUM_LOG_MAX, 15); + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_IQ, + AR5K_PHY_IQ_RUN); + } + + /* Wait for gain calibration to finish (we check for I/Q calibration + * during ath5k_phy_calibrate) */ + if (ath5k_hw_register_timeout(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_CAL, 0, false)) { + ATH5K_ERR(ah->ah_sc, "gain calibration timeout (%uMHz)\n", + channel->center_freq); + } + + /* + * If we run NF calibration before AGC, it always times out. + * Binary HAL starts NF and AGC calibration at the same time + * and only waits for AGC to finish. Also if AGC or NF cal. + * times out, reset doesn't fail on binary HAL. I believe + * that's wrong because since rx path is routed to a detector, + * if cal. doesn't finish we won't have RX. Sam's HAL for AR5210/5211 + * enables noise floor calibration after offset calibration and if noise + * floor calibration fails, reset fails. I believe that's + * a better approach, we just need to find a polling interval + * that suits best, even if reset continues we need to make + * sure that rx path is ready. + */ + ath5k_hw_noise_floor_calibration(ah, channel->center_freq); + + + /* + * Configure QCUs/DCUs + */ + + /* TODO: HW Compression support for data queues */ + /* TODO: Burst prefetch for data queues */ + + /* + * Reset queues and start beacon timers at the end of the reset routine + * This also sets QCU mask on each DCU for 1:1 qcu to dcu mapping + * Note: If we want we can assign multiple qcus on one dcu. + */ + for (i = 0; i < ah->ah_capabilities.cap_queues.q_tx_num; i++) { + ret = ath5k_hw_reset_tx_queue(ah, i); + if (ret) { + ATH5K_ERR(ah->ah_sc, + "failed to reset TX queue #%d\n", i); + return ret; + } + } + + + /* + * Configure DMA/Interrupts + */ + + /* + * Set Rx/Tx DMA Configuration + * + * Set standard DMA size (128). Note that + * a DMA size of 512 causes rx overruns and tx errors + * on pci-e cards (tested on 5424 but since rx overruns + * also occur on 5416/5418 with madwifi we set 128 + * for all PCI-E cards to be safe). + * + * XXX: need to check 5210 for this + * TODO: Check out tx triger level, it's always 64 on dumps but I + * guess we can tweak it and see how it goes ;-) + */ + if (ah->ah_version != AR5K_AR5210) { + AR5K_REG_WRITE_BITS(ah, AR5K_TXCFG, + AR5K_TXCFG_SDMAMR, AR5K_DMASIZE_128B); + AR5K_REG_WRITE_BITS(ah, AR5K_RXCFG, + AR5K_RXCFG_SDMAMW, AR5K_DMASIZE_128B); + } + + /* Pre-enable interrupts on 5211/5212*/ + if (ah->ah_version != AR5K_AR5210) + ath5k_hw_set_imr(ah, ah->ah_imr); + + /* + * Setup RFKill interrupt if rfkill flag is set on eeprom. + * TODO: Use gpio pin and polarity infos from eeprom + * TODO: Handle this in ath5k_intr because it'll result + * a nasty interrupt storm. + */ +#if 0 + if (AR5K_EEPROM_HDR_RFKILL(ah->ah_capabilities.cap_eeprom.ee_header)) { + ath5k_hw_set_gpio_input(ah, 0); + ah->ah_gpio[0] = ath5k_hw_get_gpio(ah, 0); + if (ah->ah_gpio[0] == 0) + ath5k_hw_set_gpio_intr(ah, 0, 1); + else + ath5k_hw_set_gpio_intr(ah, 0, 0); + } +#endif + + /* Enable 32KHz clock function for AR5212+ chips + * Set clocks to 32KHz operation and use an + * external 32KHz crystal when sleeping if one + * exists */ + if (ah->ah_version == AR5K_AR5212) + ath5k_hw_set_sleep_clock(ah, true); + + /* + * Disable beacons and reset the register + */ + AR5K_REG_DISABLE_BITS(ah, AR5K_BEACON, AR5K_BEACON_ENABLE | + AR5K_BEACON_RESET_TSF); + + return 0; +} + +#undef _ATH5K_RESET -- cgit v1.2.3-70-g09d2 From a082381044ce026e83dbd17f8837722b028fc07d Mon Sep 17 00:00:00 2001 From: Nick Kossifidis Date: Thu, 30 Apr 2009 15:55:44 -0400 Subject: ath5k: Allow user/driver to set txpower * Now that we have regulatory control enable the driver to set txpower on hw * Also use txpower table offset so that we can match power range set by user/driver with indices on power table. Tested 2 different cards (a CM9 and an RF5112-based ubnt) and got the same output using a remote machine to measure per-packet rssi (conected the cards using attenuators). I also switched between various tx power levels and i saw an equal power change on the remote machine (so txpower changes as expected) and verified that we have the same output on each rate. Signed-off-by: Nick Kossifidis Signed-off-by: Bob Copeland Signed-off-by: John W. Linville --- drivers/net/wireless/ath/ath5k/ath5k.h | 7 +++--- drivers/net/wireless/ath/ath5k/base.c | 11 ++++++++- drivers/net/wireless/ath/ath5k/phy.c | 45 ++++++++++++++++++++++++++++------ drivers/net/wireless/ath/ath5k/reset.c | 2 +- 4 files changed, 52 insertions(+), 13 deletions(-) (limited to 'drivers/net/wireless/ath/ath5k/reset.c') diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h index 60c6d2edc4b..04b73453f6a 100644 --- a/drivers/net/wireless/ath/ath5k/ath5k.h +++ b/drivers/net/wireless/ath/ath5k/ath5k.h @@ -1100,11 +1100,12 @@ struct ath5k_hw { /* Values in 0.25dB units */ s16 txp_min_pwr; s16 txp_max_pwr; + /* Values in 0.5dB units */ s16 txp_offset; s16 txp_ofdm; - /* Values in dB units */ - s16 txp_cck_ofdm_pwr_delta; s16 txp_cck_ofdm_gainf_delta; + /* Value in dB units */ + s16 txp_cck_ofdm_pwr_delta; } ah_txpower; struct { @@ -1271,7 +1272,7 @@ extern unsigned int ath5k_hw_get_def_antenna(struct ath5k_hw *ah); extern int ath5k_hw_phy_disable(struct ath5k_hw *ah); /* TX power setup */ extern int ath5k_hw_txpower(struct ath5k_hw *ah, struct ieee80211_channel *channel, u8 ee_mode, u8 txpower); -extern int ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, u8 ee_mode, u8 txpower); +extern int ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, u8 txpower); /* * Functions used internaly diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c index 7b80cebffd4..d70856a9520 100644 --- a/drivers/net/wireless/ath/ath5k/base.c +++ b/drivers/net/wireless/ath/ath5k/base.c @@ -2747,12 +2747,21 @@ static int ath5k_config(struct ieee80211_hw *hw, u32 changed) { struct ath5k_softc *sc = hw->priv; + struct ath5k_hw *ah = sc->ah; struct ieee80211_conf *conf = &hw->conf; int ret; mutex_lock(&sc->lock); - sc->power_level = conf->power_level; + sc->bintval = conf->beacon_int; + + if ((changed & IEEE80211_CONF_CHANGE_POWER) && + (sc->power_level != conf->power_level)) { + sc->power_level = conf->power_level; + + /* Half dB steps */ + ath5k_hw_set_txpower_limit(ah, (conf->power_level * 2)); + } ret = ath5k_chan_set(sc, conf->channel); diff --git a/drivers/net/wireless/ath/ath5k/phy.c b/drivers/net/wireless/ath/ath5k/phy.c index b48b29dca3d..bb61b8e2dce 100644 --- a/drivers/net/wireless/ath/ath5k/phy.c +++ b/drivers/net/wireless/ath/ath5k/phy.c @@ -168,9 +168,6 @@ int ath5k_hw_rfgain_opt_init(struct ath5k_hw *ah) * tx power and a Peak to Average Power Detector (PAPD) will try * to measure the gain. * - * TODO: Use propper tx power setting for the probe packet so - * that we don't observe a serious power drop on the receiver - * * XXX: How about forcing a tx packet (bypassing PCU arbitrator etc) * just after we enable the probe so that we don't mess with * standard traffic ? Maybe it's time to use sw interrupts and @@ -186,7 +183,7 @@ static void ath5k_hw_request_rfgain_probe(struct ath5k_hw *ah) /* Send the packet with 2dB below max power as * patent doc suggest */ - ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txpower.txp_max_pwr - 4, + ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txpower.txp_ofdm - 4, AR5K_PHY_PAPD_PROBE_TXPOWER) | AR5K_PHY_PAPD_PROBE_TX_NEXT, AR5K_PHY_PAPD_PROBE); @@ -2482,8 +2479,19 @@ ath5k_setup_rate_powertable(struct ath5k_hw *ah, u16 max_pwr, for (i = 8; i <= 15; i++) rates[i] -= ah->ah_txpower.txp_cck_ofdm_gainf_delta; - ah->ah_txpower.txp_min_pwr = rates[7]; - ah->ah_txpower.txp_max_pwr = rates[0]; + /* Now that we have all rates setup use table offset to + * match the power range set by user with the power indices + * on PCDAC/PDADC table */ + for (i = 0; i < 16; i++) { + rates[i] += ah->ah_txpower.txp_offset; + /* Don't get out of bounds */ + if (rates[i] > 63) + rates[i] = 63; + } + + /* Min/max in 0.25dB units */ + ah->ah_txpower.txp_min_pwr = 2 * rates[7]; + ah->ah_txpower.txp_max_pwr = 2 * rates[0]; ah->ah_txpower.txp_ofdm = rates[7]; } @@ -2591,16 +2599,37 @@ ath5k_hw_txpower(struct ath5k_hw *ah, struct ieee80211_channel *channel, return 0; } -int ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, u8 mode, u8 txpower) +int ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, u8 txpower) { /*Just a try M.F.*/ struct ieee80211_channel *channel = &ah->ah_current_channel; + u8 ee_mode; ATH5K_TRACE(ah->ah_sc); + + switch (channel->hw_value & CHANNEL_MODES) { + case CHANNEL_A: + case CHANNEL_T: + case CHANNEL_XR: + ee_mode = AR5K_EEPROM_MODE_11A; + break; + case CHANNEL_G: + case CHANNEL_TG: + ee_mode = AR5K_EEPROM_MODE_11G; + break; + case CHANNEL_B: + ee_mode = AR5K_EEPROM_MODE_11B; + break; + default: + ATH5K_ERR(ah->ah_sc, + "invalid channel: %d\n", channel->center_freq); + return -EINVAL; + } + ATH5K_DBG(ah->ah_sc, ATH5K_DEBUG_TXPOWER, "changing txpower to %d\n", txpower); - return ath5k_hw_txpower(ah, channel, mode, txpower); + return ath5k_hw_txpower(ah, channel, ee_mode, txpower); } #undef _ATH5K_PHY diff --git a/drivers/net/wireless/ath/ath5k/reset.c b/drivers/net/wireless/ath/ath5k/reset.c index 775fdf78554..cb8a9a1398c 100644 --- a/drivers/net/wireless/ath/ath5k/reset.c +++ b/drivers/net/wireless/ath/ath5k/reset.c @@ -1000,7 +1000,7 @@ int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, * Set TX power (FIXME) */ ret = ath5k_hw_txpower(ah, channel, ee_mode, - AR5K_TUNE_DEFAULT_TXPOWER); + ah->ah_txpower.txp_max_pwr / 2); if (ret) return ret; -- cgit v1.2.3-70-g09d2 From 1889ba0a48688b639c2b2e9e1b0fd8f84e2c37d1 Mon Sep 17 00:00:00 2001 From: Nick Kossifidis Date: Thu, 30 Apr 2009 15:55:46 -0400 Subject: ath5k: Put remaining EEPROM data on ee struct * Put remaining EEPROM information on ee struct and remove is_hb63 function. Now we also have rfkill stuff available. Signed-off-by: Nick Kossifidis Signed-off-by: Bob Copeland Signed-off-by: John W. Linville --- drivers/net/wireless/ath/ath5k/eeprom.c | 24 +++++++++++------------- drivers/net/wireless/ath/ath5k/eeprom.h | 15 ++++++++++----- drivers/net/wireless/ath/ath5k/reset.c | 5 +++-- 3 files changed, 24 insertions(+), 20 deletions(-) (limited to 'drivers/net/wireless/ath/ath5k/reset.c') diff --git a/drivers/net/wireless/ath/ath5k/eeprom.c b/drivers/net/wireless/ath/ath5k/eeprom.c index 587c5b8ddc2..8c9dd019d76 100644 --- a/drivers/net/wireless/ath/ath5k/eeprom.c +++ b/drivers/net/wireless/ath/ath5k/eeprom.c @@ -156,6 +156,17 @@ ath5k_eeprom_init_header(struct ath5k_hw *ah) ee->ee_db[AR5K_EEPROM_MODE_11G][0] = (val >> 3) & 0x7; } + AR5K_EEPROM_READ(AR5K_EEPROM_IS_HB63, val); + + if ((ah->ah_mac_version == (AR5K_SREV_AR2425 >> 4)) && val) + ee->ee_is_hb63 = true; + else + ee->ee_is_hb63 = false; + + AR5K_EEPROM_READ(AR5K_EEPROM_RFKILL, val); + ee->ee_rfkill_pin = (u8) AR5K_REG_MS(val, AR5K_EEPROM_RFKILL_GPIO_SEL); + ee->ee_rfkill_pol = val & AR5K_EEPROM_RFKILL_POLARITY ? true : false; + return 0; } @@ -1789,16 +1800,3 @@ int ath5k_eeprom_read_mac(struct ath5k_hw *ah, u8 *mac) return 0; } - -bool ath5k_eeprom_is_hb63(struct ath5k_hw *ah) -{ - u16 data; - - ath5k_hw_eeprom_read(ah, AR5K_EEPROM_IS_HB63, &data); - - if ((ah->ah_mac_version == (AR5K_SREV_AR2425 >> 4)) && data) - return true; - else - return false; -} - diff --git a/drivers/net/wireless/ath/ath5k/eeprom.h b/drivers/net/wireless/ath/ath5k/eeprom.h index df9ffa044ea..46e4d22591f 100644 --- a/drivers/net/wireless/ath/ath5k/eeprom.h +++ b/drivers/net/wireless/ath/ath5k/eeprom.h @@ -26,6 +26,13 @@ #define AR5K_EEPROM_MAGIC_5210 0x0000145a /* 5210 */ #define AR5K_EEPROM_IS_HB63 0x000b /* Talon detect */ + +#define AR5K_EEPROM_RFKILL 0x0f +#define AR5K_EEPROM_RFKILL_GPIO_SEL 0x0000001c +#define AR5K_EEPROM_RFKILL_GPIO_SEL_S 2 +#define AR5K_EEPROM_RFKILL_POLARITY 0x00000002 +#define AR5K_EEPROM_RFKILL_POLARITY_S 1 + #define AR5K_EEPROM_REG_DOMAIN 0x00bf /* EEPROM regdom */ #define AR5K_EEPROM_CHECKSUM 0x00c0 /* EEPROM checksum */ #define AR5K_EEPROM_INFO_BASE 0x00c0 /* EEPROM header */ @@ -66,11 +73,6 @@ #define AR5K_EEPROM_HDR_RFKILL(_v) (((_v) >> 14) & 0x1) /* Device has RFKill support */ #define AR5K_EEPROM_HDR_T_5GHZ_DIS(_v) (((_v) >> 15) & 0x1) /* Disable turbo for 5Ghz */ -#define AR5K_EEPROM_RFKILL_GPIO_SEL 0x0000001c -#define AR5K_EEPROM_RFKILL_GPIO_SEL_S 2 -#define AR5K_EEPROM_RFKILL_POLARITY 0x00000002 -#define AR5K_EEPROM_RFKILL_POLARITY_S 1 - /* Newer EEPROMs are using a different offset */ #define AR5K_EEPROM_OFF(_v, _v3_0, _v3_3) \ (((_v) >= AR5K_EEPROM_VERSION_3_3) ? _v3_3 : _v3_0) @@ -386,6 +388,9 @@ struct ath5k_eeprom_info { u16 ee_version; u16 ee_header; u16 ee_ant_gain; + u8 ee_rfkill_pin; + bool ee_rfkill_pol; + bool ee_is_hb63; u16 ee_misc0; u16 ee_misc1; u16 ee_misc2; diff --git a/drivers/net/wireless/ath/ath5k/reset.c b/drivers/net/wireless/ath/ath5k/reset.c index cb8a9a1398c..d419c6a3ded 100644 --- a/drivers/net/wireless/ath/ath5k/reset.c +++ b/drivers/net/wireless/ath/ath5k/reset.c @@ -507,7 +507,7 @@ static void ath5k_hw_set_sleep_clock(struct ath5k_hw *ah, bool enable) if (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4)) scal = AR5K_PHY_SCAL_32MHZ_2417; - else if (ath5k_eeprom_is_hb63(ah)) + else if (ee->ee_is_hb63) scal = AR5K_PHY_SCAL_32MHZ_HB63; else scal = AR5K_PHY_SCAL_32MHZ; @@ -598,9 +598,10 @@ static void ath5k_hw_tweak_initval_settings(struct ath5k_hw *ah, /* Set DAC/ADC delays */ if (ah->ah_version == AR5K_AR5212) { u32 scal; + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; if (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4)) scal = AR5K_PHY_SCAL_32MHZ_2417; - else if (ath5k_eeprom_is_hb63(ah)) + else if (ee->ee_is_hb63) scal = AR5K_PHY_SCAL_32MHZ_HB63; else scal = AR5K_PHY_SCAL_32MHZ; -- cgit v1.2.3-70-g09d2 From 2bed03ebf62f9d013a455209bf30d7e086120443 Mon Sep 17 00:00:00 2001 From: Nick Kossifidis Date: Thu, 30 Apr 2009 15:55:49 -0400 Subject: ath5k: Implement antenna control * Add code to support the various antenna scenarios supported by hw * For now hardcode the default scenario (single or dual omnis with tx/rx diversity working and tx antenna handled by session -hw keeps track on which antenna it got ack from each ap/station and maps each ap/station to one of the antennas-). Signed-off-by: Nick Kossifidis Signed-off-by: Bob Copeland Signed-off-by: John W. Linville --- drivers/net/wireless/ath/ath5k/ath5k.h | 25 ++++- drivers/net/wireless/ath/ath5k/attach.c | 1 - drivers/net/wireless/ath/ath5k/base.c | 66 +++++++++--- drivers/net/wireless/ath/ath5k/eeprom.c | 8 +- drivers/net/wireless/ath/ath5k/eeprom.h | 11 +- drivers/net/wireless/ath/ath5k/phy.c | 174 +++++++++++++++++++++++++++++++- drivers/net/wireless/ath/ath5k/reg.h | 9 +- drivers/net/wireless/ath/ath5k/reset.c | 28 ++--- 8 files changed, 271 insertions(+), 51 deletions(-) (limited to 'drivers/net/wireless/ath/ath5k/reset.c') diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h index 04b73453f6a..33da290e4bb 100644 --- a/drivers/net/wireless/ath/ath5k/ath5k.h +++ b/drivers/net/wireless/ath/ath5k/ath5k.h @@ -209,7 +209,6 @@ #define AR5K_TUNE_MAX_TXPOWER 63 #define AR5K_TUNE_DEFAULT_TXPOWER 25 #define AR5K_TUNE_TPC_TXPOWER false -#define AR5K_TUNE_ANT_DIVERSITY true #define AR5K_TUNE_HWTXTRIES 4 #define AR5K_INIT_CARR_SENSE_EN 1 @@ -420,6 +419,17 @@ enum ath5k_driver_mode { AR5K_MODE_MAX = 5 }; +enum ath5k_ant_mode { + AR5K_ANTMODE_DEFAULT = 0, /* default antenna setup */ + AR5K_ANTMODE_FIXED_A = 1, /* only antenna A is present */ + AR5K_ANTMODE_FIXED_B = 2, /* only antenna B is present */ + AR5K_ANTMODE_SINGLE_AP = 3, /* sta locked on a single ap */ + AR5K_ANTMODE_SECTOR_AP = 4, /* AP with tx antenna set on tx desc */ + AR5K_ANTMODE_SECTOR_STA = 5, /* STA with tx antenna set on tx desc */ + AR5K_ANTMODE_DEBUG = 6, /* Debug mode -A -> Rx, B-> Tx- */ + AR5K_ANTMODE_MAX, +}; + /****************\ TX DEFINITIONS @@ -1051,8 +1061,11 @@ struct ath5k_hw { bool ah_software_retry; u32 ah_limit_tx_retries; - u32 ah_antenna[AR5K_EEPROM_N_MODES][AR5K_ANT_MAX]; - bool ah_ant_diversity; + /* Antenna Control */ + u32 ah_ant_ctl[AR5K_EEPROM_N_MODES][AR5K_ANT_MAX]; + u8 ah_ant_mode; + u8 ah_tx_ant; + u8 ah_def_ant; u8 ah_sta_id[ETH_ALEN]; @@ -1267,9 +1280,11 @@ extern int ath5k_hw_phy_calibrate(struct ath5k_hw *ah, struct ieee80211_channel extern int ath5k_hw_noise_floor_calibration(struct ath5k_hw *ah, short freq); /* Misc PHY functions */ extern u16 ath5k_hw_radio_revision(struct ath5k_hw *ah, unsigned int chan); -extern void ath5k_hw_set_def_antenna(struct ath5k_hw *ah, unsigned int ant); -extern unsigned int ath5k_hw_get_def_antenna(struct ath5k_hw *ah); extern int ath5k_hw_phy_disable(struct ath5k_hw *ah); +/* Antenna control */ +extern void ath5k_hw_set_antenna_mode(struct ath5k_hw *ah, u8 ant_mode); +extern void ath5k_hw_set_def_antenna(struct ath5k_hw *ah, u8 ant); +extern unsigned int ath5k_hw_get_def_antenna(struct ath5k_hw *ah); /* TX power setup */ extern int ath5k_hw_txpower(struct ath5k_hw *ah, struct ieee80211_channel *channel, u8 ee_mode, u8 txpower); extern int ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, u8 txpower); diff --git a/drivers/net/wireless/ath/ath5k/attach.c b/drivers/net/wireless/ath/ath5k/attach.c index 70d376c63aa..c41ef58393e 100644 --- a/drivers/net/wireless/ath/ath5k/attach.c +++ b/drivers/net/wireless/ath/ath5k/attach.c @@ -133,7 +133,6 @@ struct ath5k_hw *ath5k_hw_attach(struct ath5k_softc *sc, u8 mac_version) ah->ah_cw_min = AR5K_TUNE_CWMIN; ah->ah_limit_tx_retries = AR5K_INIT_TX_RETRY; ah->ah_software_retry = false; - ah->ah_ant_diversity = AR5K_TUNE_ANT_DIVERSITY; /* * Set the mac version based on the pci id diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c index 912ffc5c020..6789c5dfcc7 100644 --- a/drivers/net/wireless/ath/ath5k/base.c +++ b/drivers/net/wireless/ath/ath5k/base.c @@ -1279,7 +1279,7 @@ ath5k_txbuf_setup(struct ath5k_softc *sc, struct ath5k_buf *bf) ieee80211_get_hdrlen_from_skb(skb), AR5K_PKT_TYPE_NORMAL, (sc->power_level * 2), hw_rate, - info->control.rates[0].count, keyidx, 0, flags, + info->control.rates[0].count, keyidx, ah->ah_tx_ant, flags, cts_rate, duration); if (ret) goto err_unmap; @@ -2009,7 +2009,8 @@ ath5k_beacon_setup(struct ath5k_softc *sc, struct ath5k_buf *bf) struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct ath5k_hw *ah = sc->ah; struct ath5k_desc *ds; - int ret, antenna = 0; + int ret = 0; + u8 antenna; u32 flags; bf->skbaddr = pci_map_single(sc->pdev, skb->data, skb->len, @@ -2023,23 +2024,35 @@ ath5k_beacon_setup(struct ath5k_softc *sc, struct ath5k_buf *bf) } ds = bf->desc; + antenna = ah->ah_tx_ant; flags = AR5K_TXDESC_NOACK; if (sc->opmode == NL80211_IFTYPE_ADHOC && ath5k_hw_hasveol(ah)) { ds->ds_link = bf->daddr; /* self-linked */ flags |= AR5K_TXDESC_VEOL; - /* - * Let hardware handle antenna switching if txantenna is not set - */ - } else { + } else ds->ds_link = 0; - /* - * Switch antenna every 4 beacons if txantenna is not set - * XXX assumes two antennas - */ - if (antenna == 0) - antenna = sc->bsent & 4 ? 2 : 1; - } + + /* + * If we use multiple antennas on AP and use + * the Sectored AP scenario, switch antenna every + * 4 beacons to make sure everybody hears our AP. + * When a client tries to associate, hw will keep + * track of the tx antenna to be used for this client + * automaticaly, based on ACKed packets. + * + * Note: AP still listens and transmits RTS on the + * default antenna which is supposed to be an omni. + * + * Note2: On sectored scenarios it's possible to have + * multiple antennas (1omni -the default- and 14 sectors) + * so if we choose to actually support this mode we need + * to allow user to set how many antennas we have and tweak + * the code below to send beacons on all of them. + */ + if (ah->ah_ant_mode == AR5K_ANTMODE_SECTOR_AP) + antenna = sc->bsent & 4 ? 2 : 1; + /* FIXME: If we are in g mode and rate is a CCK rate * subtract ah->ah_txpower.txp_cck_ofdm_pwr_delta @@ -2752,12 +2765,16 @@ ath5k_config(struct ieee80211_hw *hw, u32 changed) struct ath5k_softc *sc = hw->priv; struct ath5k_hw *ah = sc->ah; struct ieee80211_conf *conf = &hw->conf; - int ret; + int ret = 0; mutex_lock(&sc->lock); sc->bintval = conf->beacon_int; + ret = ath5k_chan_set(sc, conf->channel); + if (ret < 0) + return ret; + if ((changed & IEEE80211_CONF_CHANGE_POWER) && (sc->power_level != conf->power_level)) { sc->power_level = conf->power_level; @@ -2766,10 +2783,27 @@ ath5k_config(struct ieee80211_hw *hw, u32 changed) ath5k_hw_set_txpower_limit(ah, (conf->power_level * 2)); } - ret = ath5k_chan_set(sc, conf->channel); + /* TODO: + * 1) Move this on config_interface and handle each case + * separately eg. when we have only one STA vif, use + * AR5K_ANTMODE_SINGLE_AP + * + * 2) Allow the user to change antenna mode eg. when only + * one antenna is present + * + * 3) Allow the user to set default/tx antenna when possible + * + * 4) Default mode should handle 90% of the cases, together + * with fixed a/b and single AP modes we should be able to + * handle 99%. Sectored modes are extreme cases and i still + * haven't found a usage for them. If we decide to support them, + * then we must allow the user to set how many tx antennas we + * have available + */ + ath5k_hw_set_antenna_mode(ah, AR5K_ANTMODE_DEFAULT); mutex_unlock(&sc->lock); - return ret; + return 0; } #define SUPPORTED_FIF_FLAGS \ diff --git a/drivers/net/wireless/ath/ath5k/eeprom.c b/drivers/net/wireless/ath/ath5k/eeprom.c index 8c9dd019d76..c56b494d417 100644 --- a/drivers/net/wireless/ath/ath5k/eeprom.c +++ b/drivers/net/wireless/ath/ath5k/eeprom.c @@ -208,16 +208,16 @@ static int ath5k_eeprom_read_ants(struct ath5k_hw *ah, u32 *offset, ee->ee_ant_control[mode][i++] = (val >> 6) & 0x3f; ee->ee_ant_control[mode][i++] = val & 0x3f; - /* Get antenna modes */ - ah->ah_antenna[mode][0] = + /* Get antenna switch tables */ + ah->ah_ant_ctl[mode][AR5K_ANT_CTL] = (ee->ee_ant_control[mode][0] << 4); - ah->ah_antenna[mode][AR5K_ANT_FIXED_A] = + ah->ah_ant_ctl[mode][AR5K_ANT_SWTABLE_A] = ee->ee_ant_control[mode][1] | (ee->ee_ant_control[mode][2] << 6) | (ee->ee_ant_control[mode][3] << 12) | (ee->ee_ant_control[mode][4] << 18) | (ee->ee_ant_control[mode][5] << 24); - ah->ah_antenna[mode][AR5K_ANT_FIXED_B] = + ah->ah_ant_ctl[mode][AR5K_ANT_SWTABLE_B] = ee->ee_ant_control[mode][6] | (ee->ee_ant_control[mode][7] << 6) | (ee->ee_ant_control[mode][8] << 12) | diff --git a/drivers/net/wireless/ath/ath5k/eeprom.h b/drivers/net/wireless/ath/ath5k/eeprom.h index 46e4d22591f..64be73a5eda 100644 --- a/drivers/net/wireless/ath/ath5k/eeprom.h +++ b/drivers/net/wireless/ath/ath5k/eeprom.h @@ -240,11 +240,11 @@ enum ath5k_eeprom_freq_bands{ #define AR5K_EEPROM_READ_HDR(_o, _v) \ AR5K_EEPROM_READ(_o, ah->ah_capabilities.cap_eeprom._v); \ -enum ath5k_ant_setting { - AR5K_ANT_VARIABLE = 0, /* variable by programming */ - AR5K_ANT_FIXED_A = 1, /* fixed to 11a frequencies */ - AR5K_ANT_FIXED_B = 2, /* fixed to 11b frequencies */ - AR5K_ANT_MAX = 3, +enum ath5k_ant_table { + AR5K_ANT_CTL = 0, /* Idle switch table settings */ + AR5K_ANT_SWTABLE_A = 1, /* Switch table for antenna A */ + AR5K_ANT_SWTABLE_B = 2, /* Switch table for antenna B */ + AR5K_ANT_MAX, }; enum ath5k_ctl_mode { @@ -461,6 +461,7 @@ struct ath5k_eeprom_info { /* Spur mitigation data (fbin values for spur channels) */ u16 ee_spur_chans[AR5K_EEPROM_N_SPUR_CHANS][AR5K_EEPROM_N_FREQ_BANDS]; + /* Antenna raw switch tables */ u32 ee_antenna[AR5K_EEPROM_N_MODES][AR5K_ANT_MAX]; }; diff --git a/drivers/net/wireless/ath/ath5k/phy.c b/drivers/net/wireless/ath/ath5k/phy.c index bb61b8e2dce..fd93c4e2021 100644 --- a/drivers/net/wireless/ath/ath5k/phy.c +++ b/drivers/net/wireless/ath/ath5k/phy.c @@ -1414,25 +1414,189 @@ u16 ath5k_hw_radio_revision(struct ath5k_hw *ah, unsigned int chan) return ret; } +/*****************\ +* Antenna control * +\*****************/ + void /*TODO:Boundary check*/ -ath5k_hw_set_def_antenna(struct ath5k_hw *ah, unsigned int ant) +ath5k_hw_set_def_antenna(struct ath5k_hw *ah, u8 ant) { ATH5K_TRACE(ah->ah_sc); - /*Just a try M.F.*/ + if (ah->ah_version != AR5K_AR5210) - ath5k_hw_reg_write(ah, ant, AR5K_DEFAULT_ANTENNA); + ath5k_hw_reg_write(ah, ant & 0x7, AR5K_DEFAULT_ANTENNA); } unsigned int ath5k_hw_get_def_antenna(struct ath5k_hw *ah) { ATH5K_TRACE(ah->ah_sc); - /*Just a try M.F.*/ + if (ah->ah_version != AR5K_AR5210) - return ath5k_hw_reg_read(ah, AR5K_DEFAULT_ANTENNA); + return ath5k_hw_reg_read(ah, AR5K_DEFAULT_ANTENNA) & 0x7; return false; /*XXX: What do we return for 5210 ?*/ } +/* + * Enable/disable fast rx antenna diversity + */ +static void +ath5k_hw_set_fast_div(struct ath5k_hw *ah, u8 ee_mode, bool enable) +{ + switch (ee_mode) { + case AR5K_EEPROM_MODE_11G: + /* XXX: This is set to + * disabled on initvals !!! */ + case AR5K_EEPROM_MODE_11A: + if (enable) + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_OFDM_DIV_DIS); + else + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_OFDM_DIV_DIS); + break; + case AR5K_EEPROM_MODE_11B: + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_OFDM_DIV_DIS); + break; + default: + return; + } + + if (enable) { + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_RESTART, + AR5K_PHY_RESTART_DIV_GC, 0xc); + + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_FAST_ANT_DIV, + AR5K_PHY_FAST_ANT_DIV_EN); + } else { + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_RESTART, + AR5K_PHY_RESTART_DIV_GC, 0x8); + + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_FAST_ANT_DIV, + AR5K_PHY_FAST_ANT_DIV_EN); + } +} + +/* + * Set antenna operating mode + */ +void +ath5k_hw_set_antenna_mode(struct ath5k_hw *ah, u8 ant_mode) +{ + struct ieee80211_channel *channel = &ah->ah_current_channel; + bool use_def_for_tx, update_def_on_tx, use_def_for_rts, fast_div; + bool use_def_for_sg; + u8 def_ant, tx_ant, ee_mode; + u32 sta_id1 = 0; + + def_ant = ah->ah_def_ant; + + ATH5K_TRACE(ah->ah_sc); + + switch (channel->hw_value & CHANNEL_MODES) { + case CHANNEL_A: + case CHANNEL_T: + case CHANNEL_XR: + ee_mode = AR5K_EEPROM_MODE_11A; + break; + case CHANNEL_G: + case CHANNEL_TG: + ee_mode = AR5K_EEPROM_MODE_11G; + break; + case CHANNEL_B: + ee_mode = AR5K_EEPROM_MODE_11B; + break; + default: + ATH5K_ERR(ah->ah_sc, + "invalid channel: %d\n", channel->center_freq); + return; + } + + switch (ant_mode) { + case AR5K_ANTMODE_DEFAULT: + tx_ant = 0; + use_def_for_tx = false; + update_def_on_tx = false; + use_def_for_rts = false; + use_def_for_sg = false; + fast_div = true; + break; + case AR5K_ANTMODE_FIXED_A: + def_ant = 1; + tx_ant = 0; + use_def_for_tx = true; + update_def_on_tx = false; + use_def_for_rts = true; + use_def_for_sg = true; + fast_div = false; + break; + case AR5K_ANTMODE_FIXED_B: + def_ant = 2; + tx_ant = 0; + use_def_for_tx = true; + update_def_on_tx = false; + use_def_for_rts = true; + use_def_for_sg = true; + fast_div = false; + break; + case AR5K_ANTMODE_SINGLE_AP: + def_ant = 1; /* updated on tx */ + tx_ant = 0; + use_def_for_tx = true; + update_def_on_tx = true; + use_def_for_rts = true; + use_def_for_sg = true; + fast_div = true; + break; + case AR5K_ANTMODE_SECTOR_AP: + tx_ant = 1; /* variable */ + use_def_for_tx = false; + update_def_on_tx = false; + use_def_for_rts = true; + use_def_for_sg = false; + fast_div = false; + break; + case AR5K_ANTMODE_SECTOR_STA: + tx_ant = 1; /* variable */ + use_def_for_tx = true; + update_def_on_tx = false; + use_def_for_rts = true; + use_def_for_sg = false; + fast_div = true; + break; + case AR5K_ANTMODE_DEBUG: + def_ant = 1; + tx_ant = 2; + use_def_for_tx = false; + update_def_on_tx = false; + use_def_for_rts = false; + use_def_for_sg = false; + fast_div = false; + break; + default: + return; + } + + ah->ah_tx_ant = tx_ant; + ah->ah_ant_mode = ant_mode; + + sta_id1 |= use_def_for_tx ? AR5K_STA_ID1_DEFAULT_ANTENNA : 0; + sta_id1 |= update_def_on_tx ? AR5K_STA_ID1_DESC_ANTENNA : 0; + sta_id1 |= use_def_for_rts ? AR5K_STA_ID1_RTS_DEF_ANTENNA : 0; + sta_id1 |= use_def_for_sg ? AR5K_STA_ID1_SELFGEN_DEF_ANT : 0; + + AR5K_REG_DISABLE_BITS(ah, AR5K_STA_ID1, AR5K_STA_ID1_ANTENNA_SETTINGS); + + if (sta_id1) + AR5K_REG_ENABLE_BITS(ah, AR5K_STA_ID1, sta_id1); + + /* Note: set diversity before default antenna + * because it won't work correctly */ + ath5k_hw_set_fast_div(ah, ee_mode, fast_div); + ath5k_hw_set_def_antenna(ah, def_ant); +} + /****************\ * TX power setup * diff --git a/drivers/net/wireless/ath/ath5k/reg.h b/drivers/net/wireless/ath/ath5k/reg.h index 7070d1543cd..6809b54a2ad 100644 --- a/drivers/net/wireless/ath/ath5k/reg.h +++ b/drivers/net/wireless/ath/ath5k/reg.h @@ -1148,6 +1148,11 @@ #define AR5K_STA_ID1_CBCIV_ENDIAN 0x40000000 /* ??? */ #define AR5K_STA_ID1_KEYSRCH_MCAST 0x80000000 /* Do key cache search for mcast frames */ +#define AR5K_STA_ID1_ANTENNA_SETTINGS (AR5K_STA_ID1_DEFAULT_ANTENNA | \ + AR5K_STA_ID1_DESC_ANTENNA | \ + AR5K_STA_ID1_RTS_DEF_ANTENNA | \ + AR5K_STA_ID1_SELFGEN_DEF_ANT) + /* * First BSSID register (MAC address, lower 32bits) */ @@ -2028,7 +2033,9 @@ #define AR5K_PHY_AGCCTL 0x9860 /* Register address */ #define AR5K_PHY_AGCCTL_CAL 0x00000001 /* Enable PHY calibration */ #define AR5K_PHY_AGCCTL_NF 0x00000002 /* Enable Noise Floor calibration */ +#define AR5K_PHY_AGCCTL_OFDM_DIV_DIS 0x00000008 /* Disable antenna diversity on OFDM modes */ #define AR5K_PHY_AGCCTL_NF_EN 0x00008000 /* Enable nf calibration to happen (?) */ +#define AR5K_PHY_AGCTL_FLTR_CAL 0x00010000 /* Allow filter calibration (?) */ #define AR5K_PHY_AGCCTL_NF_NOUPDATE 0x00020000 /* Don't update nf automaticaly */ /* @@ -2528,7 +2535,7 @@ * PHY CCK Cross-correlator Barker RSSI threshold register [5212+] */ #define AR5K_PHY_CCK_CROSSCORR 0xa208 -#define AR5K_PHY_CCK_CROSSCORR_WEAK_SIG_THR 0x0000000f +#define AR5K_PHY_CCK_CROSSCORR_WEAK_SIG_THR 0x0000003f #define AR5K_PHY_CCK_CROSSCORR_WEAK_SIG_THR_S 0 /* Same address is used for antenna diversity activation */ diff --git a/drivers/net/wireless/ath/ath5k/reset.c b/drivers/net/wireless/ath/ath5k/reset.c index d419c6a3ded..0e075bca384 100644 --- a/drivers/net/wireless/ath/ath5k/reset.c +++ b/drivers/net/wireless/ath/ath5k/reset.c @@ -698,13 +698,13 @@ static void ath5k_hw_commit_eeprom_settings(struct ath5k_hw *ah, /* Set antenna idle switch table */ AR5K_REG_WRITE_BITS(ah, AR5K_PHY_ANT_CTL, AR5K_PHY_ANT_CTL_SWTABLE_IDLE, - (ah->ah_antenna[ee_mode][0] | + (ah->ah_ant_ctl[ee_mode][0] | AR5K_PHY_ANT_CTL_TXRX_EN)); - /* Set antenna switch table */ - ath5k_hw_reg_write(ah, ah->ah_antenna[ee_mode][ant[0]], + /* Set antenna switch tables */ + ath5k_hw_reg_write(ah, ah->ah_ant_ctl[ee_mode][ant[0]], AR5K_PHY_ANT_SWITCH_TABLE_0); - ath5k_hw_reg_write(ah, ah->ah_antenna[ee_mode][ant[1]], + ath5k_hw_reg_write(ah, ah->ah_ant_ctl[ee_mode][ant[1]], AR5K_PHY_ANT_SWITCH_TABLE_1); /* Noise floor threshold */ @@ -1042,17 +1042,15 @@ int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, /* * In case a fixed antenna was set as default - * write the same settings on both AR5K_PHY_ANT_SWITCH_TABLE - * registers. + * use the same switch table twice. */ - if (s_ant != 0) { - if (s_ant == AR5K_ANT_FIXED_A) /* 1 - Main */ - ant[0] = ant[1] = AR5K_ANT_FIXED_A; - else /* 2 - Aux */ - ant[0] = ant[1] = AR5K_ANT_FIXED_B; - } else { - ant[0] = AR5K_ANT_FIXED_A; - ant[1] = AR5K_ANT_FIXED_B; + if (ah->ah_ant_mode == AR5K_ANTMODE_FIXED_A) + ant[0] = ant[1] = AR5K_ANT_SWTABLE_A; + else if (ah->ah_ant_mode == AR5K_ANTMODE_FIXED_B) + ant[0] = ant[1] = AR5K_ANT_SWTABLE_B; + else { + ant[0] = AR5K_ANT_SWTABLE_A; + ant[1] = AR5K_ANT_SWTABLE_B; } /* Commit values from EEPROM */ @@ -1260,6 +1258,8 @@ int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, */ ath5k_hw_noise_floor_calibration(ah, channel->center_freq); + /* Restore antenna mode */ + ath5k_hw_set_antenna_mode(ah, ah->ah_ant_mode); /* * Configure QCUs/DCUs -- cgit v1.2.3-70-g09d2 From 57e6c56dbb52d680f61dd629759fe2974840ed93 Mon Sep 17 00:00:00 2001 From: Nick Kossifidis Date: Thu, 30 Apr 2009 15:55:50 -0400 Subject: ath5k: Add Spur filter support on newer chips * Add spur filter support for RF5413 and later chips Signed-off-by: Nick Kossifidis Signed-off-by: Bob Copeland Signed-off-by: John W. Linville --- drivers/net/wireless/ath/ath5k/ath5k.h | 5 + drivers/net/wireless/ath/ath5k/phy.c | 255 ++++++++++++++++++++++++++++++++- drivers/net/wireless/ath/ath5k/reset.c | 35 ++--- 3 files changed, 270 insertions(+), 25 deletions(-) (limited to 'drivers/net/wireless/ath/ath5k/reset.c') diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h index 33da290e4bb..81371821033 100644 --- a/drivers/net/wireless/ath/ath5k/ath5k.h +++ b/drivers/net/wireless/ath/ath5k/ath5k.h @@ -1278,6 +1278,11 @@ extern int ath5k_hw_channel(struct ath5k_hw *ah, struct ieee80211_channel *chann /* PHY calibration */ extern int ath5k_hw_phy_calibrate(struct ath5k_hw *ah, struct ieee80211_channel *channel); extern int ath5k_hw_noise_floor_calibration(struct ath5k_hw *ah, short freq); +/* Spur mitigation */ +bool ath5k_hw_chan_has_spur_noise(struct ath5k_hw *ah, + struct ieee80211_channel *channel); +void ath5k_hw_set_spur_mitigation_filter(struct ath5k_hw *ah, + struct ieee80211_channel *channel); /* Misc PHY functions */ extern u16 ath5k_hw_radio_revision(struct ath5k_hw *ah, unsigned int chan); extern int ath5k_hw_phy_disable(struct ath5k_hw *ah); diff --git a/drivers/net/wireless/ath/ath5k/phy.c b/drivers/net/wireless/ath/ath5k/phy.c index fd93c4e2021..6737ba0a4de 100644 --- a/drivers/net/wireless/ath/ath5k/phy.c +++ b/drivers/net/wireless/ath/ath5k/phy.c @@ -1353,6 +1353,257 @@ int ath5k_hw_phy_calibrate(struct ath5k_hw *ah, return ret; } +/***************************\ +* Spur mitigation functions * +\***************************/ + +bool ath5k_hw_chan_has_spur_noise(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + u8 refclk_freq; + + if ((ah->ah_radio == AR5K_RF5112) || + (ah->ah_radio == AR5K_RF5413) || + (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4))) + refclk_freq = 40; + else + refclk_freq = 32; + + if ((channel->center_freq % refclk_freq != 0) && + ((channel->center_freq % refclk_freq < 10) || + (channel->center_freq % refclk_freq > 22))) + return true; + else + return false; +} + +void +ath5k_hw_set_spur_mitigation_filter(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u32 mag_mask[4] = {0, 0, 0, 0}; + u32 pilot_mask[2] = {0, 0}; + /* Note: fbin values are scaled up by 2 */ + u16 spur_chan_fbin, chan_fbin, symbol_width, spur_detection_window; + s32 spur_delta_phase, spur_freq_sigma_delta; + s32 spur_offset, num_symbols_x16; + u8 num_symbol_offsets, i, freq_band; + + /* Convert current frequency to fbin value (the same way channels + * are stored on EEPROM, check out ath5k_eeprom_bin2freq) and scale + * up by 2 so we can compare it later */ + if (channel->hw_value & CHANNEL_2GHZ) { + chan_fbin = (channel->center_freq - 2300) * 10; + freq_band = AR5K_EEPROM_BAND_2GHZ; + } else { + chan_fbin = (channel->center_freq - 4900) * 10; + freq_band = AR5K_EEPROM_BAND_5GHZ; + } + + /* Check if any spur_chan_fbin from EEPROM is + * within our current channel's spur detection range */ + spur_chan_fbin = AR5K_EEPROM_NO_SPUR; + spur_detection_window = AR5K_SPUR_CHAN_WIDTH; + /* XXX: Half/Quarter channels ?*/ + if (channel->hw_value & CHANNEL_TURBO) + spur_detection_window *= 2; + + for (i = 0; i < AR5K_EEPROM_N_SPUR_CHANS; i++) { + spur_chan_fbin = ee->ee_spur_chans[i][freq_band]; + + /* Note: mask cleans AR5K_EEPROM_NO_SPUR flag + * so it's zero if we got nothing from EEPROM */ + if (spur_chan_fbin == AR5K_EEPROM_NO_SPUR) { + spur_chan_fbin &= AR5K_EEPROM_SPUR_CHAN_MASK; + break; + } + + if ((chan_fbin - spur_detection_window <= + (spur_chan_fbin & AR5K_EEPROM_SPUR_CHAN_MASK)) && + (chan_fbin + spur_detection_window >= + (spur_chan_fbin & AR5K_EEPROM_SPUR_CHAN_MASK))) { + spur_chan_fbin &= AR5K_EEPROM_SPUR_CHAN_MASK; + break; + } + } + + /* We need to enable spur filter for this channel */ + if (spur_chan_fbin) { + spur_offset = spur_chan_fbin - chan_fbin; + /* + * Calculate deltas: + * spur_freq_sigma_delta -> spur_offset / sample_freq << 21 + * spur_delta_phase -> spur_offset / chip_freq << 11 + * Note: Both values have 100KHz resolution + */ + /* XXX: Half/Quarter rate channels ? */ + switch (channel->hw_value) { + case CHANNEL_A: + /* Both sample_freq and chip_freq are 40MHz */ + spur_delta_phase = (spur_offset << 17) / 25; + spur_freq_sigma_delta = (spur_delta_phase >> 10); + symbol_width = AR5K_SPUR_SYMBOL_WIDTH_BASE_100Hz; + break; + case CHANNEL_G: + /* sample_freq -> 40MHz chip_freq -> 44MHz + * (for b compatibility) */ + spur_freq_sigma_delta = (spur_offset << 8) / 55; + spur_delta_phase = (spur_offset << 17) / 25; + symbol_width = AR5K_SPUR_SYMBOL_WIDTH_BASE_100Hz; + break; + case CHANNEL_T: + case CHANNEL_TG: + /* Both sample_freq and chip_freq are 80MHz */ + spur_delta_phase = (spur_offset << 16) / 25; + spur_freq_sigma_delta = (spur_delta_phase >> 10); + symbol_width = AR5K_SPUR_SYMBOL_WIDTH_TURBO_100Hz; + break; + default: + return; + } + + /* Calculate pilot and magnitude masks */ + + /* Scale up spur_offset by 1000 to switch to 100HZ resolution + * and divide by symbol_width to find how many symbols we have + * Note: number of symbols is scaled up by 16 */ + num_symbols_x16 = ((spur_offset * 1000) << 4) / symbol_width; + + /* Spur is on a symbol if num_symbols_x16 % 16 is zero */ + if (!(num_symbols_x16 & 0xF)) + /* _X_ */ + num_symbol_offsets = 3; + else + /* _xx_ */ + num_symbol_offsets = 4; + + for (i = 0; i < num_symbol_offsets; i++) { + + /* Calculate pilot mask */ + s32 curr_sym_off = + (num_symbols_x16 / 16) + i + 25; + + /* Pilot magnitude mask seems to be a way to + * declare the boundaries for our detection + * window or something, it's 2 for the middle + * value(s) where the symbol is expected to be + * and 1 on the boundary values */ + u8 plt_mag_map = + (i == 0 || i == (num_symbol_offsets - 1)) + ? 1 : 2; + + if (curr_sym_off >= 0 && curr_sym_off <= 32) { + if (curr_sym_off <= 25) + pilot_mask[0] |= 1 << curr_sym_off; + else if (curr_sym_off >= 27) + pilot_mask[0] |= 1 << (curr_sym_off - 1); + } else if (curr_sym_off >= 33 && curr_sym_off <= 52) + pilot_mask[1] |= 1 << (curr_sym_off - 33); + + /* Calculate magnitude mask (for viterbi decoder) */ + if (curr_sym_off >= -1 && curr_sym_off <= 14) + mag_mask[0] |= + plt_mag_map << (curr_sym_off + 1) * 2; + else if (curr_sym_off >= 15 && curr_sym_off <= 30) + mag_mask[1] |= + plt_mag_map << (curr_sym_off - 15) * 2; + else if (curr_sym_off >= 31 && curr_sym_off <= 46) + mag_mask[2] |= + plt_mag_map << (curr_sym_off - 31) * 2; + else if (curr_sym_off >= 46 && curr_sym_off <= 53) + mag_mask[3] |= + plt_mag_map << (curr_sym_off - 47) * 2; + + } + + /* Write settings on hw to enable spur filter */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK_CTL, + AR5K_PHY_BIN_MASK_CTL_RATE, 0xff); + /* XXX: Self correlator also ? */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_IQ, + AR5K_PHY_IQ_PILOT_MASK_EN | + AR5K_PHY_IQ_CHAN_MASK_EN | + AR5K_PHY_IQ_SPUR_FILT_EN); + + /* Set delta phase and freq sigma delta */ + ath5k_hw_reg_write(ah, + AR5K_REG_SM(spur_delta_phase, + AR5K_PHY_TIMING_11_SPUR_DELTA_PHASE) | + AR5K_REG_SM(spur_freq_sigma_delta, + AR5K_PHY_TIMING_11_SPUR_FREQ_SD) | + AR5K_PHY_TIMING_11_USE_SPUR_IN_AGC, + AR5K_PHY_TIMING_11); + + /* Write pilot masks */ + ath5k_hw_reg_write(ah, pilot_mask[0], AR5K_PHY_TIMING_7); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_8, + AR5K_PHY_TIMING_8_PILOT_MASK_2, + pilot_mask[1]); + + ath5k_hw_reg_write(ah, pilot_mask[0], AR5K_PHY_TIMING_9); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_10, + AR5K_PHY_TIMING_10_PILOT_MASK_2, + pilot_mask[1]); + + /* Write magnitude masks */ + ath5k_hw_reg_write(ah, mag_mask[0], AR5K_PHY_BIN_MASK_1); + ath5k_hw_reg_write(ah, mag_mask[1], AR5K_PHY_BIN_MASK_2); + ath5k_hw_reg_write(ah, mag_mask[2], AR5K_PHY_BIN_MASK_3); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK_CTL, + AR5K_PHY_BIN_MASK_CTL_MASK_4, + mag_mask[3]); + + ath5k_hw_reg_write(ah, mag_mask[0], AR5K_PHY_BIN_MASK2_1); + ath5k_hw_reg_write(ah, mag_mask[1], AR5K_PHY_BIN_MASK2_2); + ath5k_hw_reg_write(ah, mag_mask[2], AR5K_PHY_BIN_MASK2_3); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK2_4, + AR5K_PHY_BIN_MASK2_4_MASK_4, + mag_mask[3]); + + } else if (ath5k_hw_reg_read(ah, AR5K_PHY_IQ) & + AR5K_PHY_IQ_SPUR_FILT_EN) { + /* Clean up spur mitigation settings and disable fliter */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK_CTL, + AR5K_PHY_BIN_MASK_CTL_RATE, 0); + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_IQ, + AR5K_PHY_IQ_PILOT_MASK_EN | + AR5K_PHY_IQ_CHAN_MASK_EN | + AR5K_PHY_IQ_SPUR_FILT_EN); + ath5k_hw_reg_write(ah, 0, AR5K_PHY_TIMING_11); + + /* Clear pilot masks */ + ath5k_hw_reg_write(ah, 0, AR5K_PHY_TIMING_7); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_8, + AR5K_PHY_TIMING_8_PILOT_MASK_2, + 0); + + ath5k_hw_reg_write(ah, 0, AR5K_PHY_TIMING_9); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_10, + AR5K_PHY_TIMING_10_PILOT_MASK_2, + 0); + + /* Clear magnitude masks */ + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK_1); + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK_2); + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK_3); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK_CTL, + AR5K_PHY_BIN_MASK_CTL_MASK_4, + 0); + + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK2_1); + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK2_2); + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK2_3); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK2_4, + AR5K_PHY_BIN_MASK2_4_MASK_4, + 0); + } +} + +/********************\ + Misc PHY functions +\********************/ + int ath5k_hw_phy_disable(struct ath5k_hw *ah) { ATH5K_TRACE(ah->ah_sc); @@ -1362,10 +1613,6 @@ int ath5k_hw_phy_disable(struct ath5k_hw *ah) return 0; } -/********************\ - Misc PHY functions -\********************/ - /* * Get the PHY Chip revision */ diff --git a/drivers/net/wireless/ath/ath5k/reset.c b/drivers/net/wireless/ath/ath5k/reset.c index 0e075bca384..c1862f8a2e7 100644 --- a/drivers/net/wireless/ath/ath5k/reset.c +++ b/drivers/net/wireless/ath/ath5k/reset.c @@ -536,26 +536,6 @@ static void ath5k_hw_set_sleep_clock(struct ath5k_hw *ah, bool enable) return; } -static bool ath5k_hw_chan_has_spur_noise(struct ath5k_hw *ah, - struct ieee80211_channel *channel) -{ - u8 refclk_freq; - - if ((ah->ah_radio == AR5K_RF5112) || - (ah->ah_radio == AR5K_RF5413) || - (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4))) - refclk_freq = 40; - else - refclk_freq = 32; - - if ((channel->center_freq % refclk_freq != 0) && - ((channel->center_freq % refclk_freq < 10) || - (channel->center_freq % refclk_freq > 22))) - return true; - else - return false; -} - /* TODO: Half/Quarter rate */ static void ath5k_hw_tweak_initval_settings(struct ath5k_hw *ah, struct ieee80211_channel *channel) @@ -998,7 +978,7 @@ int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, ath5k_hw_tweak_initval_settings(ah, channel); /* - * Set TX power (FIXME) + * Set TX power */ ret = ath5k_hw_txpower(ah, channel, ee_mode, ah->ah_txpower.txp_max_pwr / 2); @@ -1024,9 +1004,22 @@ int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, /* Write OFDM timings on 5212*/ if (ah->ah_version == AR5K_AR5212 && channel->hw_value & CHANNEL_OFDM) { + struct ath5k_eeprom_info *ee = + &ah->ah_capabilities.cap_eeprom; + ret = ath5k_hw_write_ofdm_timings(ah, channel); if (ret) return ret; + + /* Note: According to docs we can have a newer + * EEPROM on old hardware, so we need to verify + * that our hardware is new enough to have spur + * mitigation registers (delta phase etc) */ + if (ah->ah_mac_srev >= AR5K_SREV_AR5424 || + (ah->ah_mac_srev >= AR5K_SREV_AR5424 && + ee->ee_version >= AR5K_EEPROM_VERSION_5_3)) + ath5k_hw_set_spur_mitigation_filter(ah, + channel); } /*Enable/disable 802.11b mode on 5111 -- cgit v1.2.3-70-g09d2 From e6a3b61681dcb963e6465ffbc4330b44824f35e3 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 9 Jun 2009 17:33:27 +0200 Subject: ath5k: added cfg80211 based rfkill support This patch introduces initial rfkill support for the ath5k driver based on rfkill support in the cfg80211 framework. All rfkill related code is separated into newly created rfkill.c. Changes to existing code are minimal: * added a new data structure ath5k_rfkill to the ath5k_softc structure * inserted calls to HW rfkill init/deinit routines * ath5k_intr() has been extended to handle AR5K_INT_GPIO interrupts Signed-off-by: John W. Linville --- drivers/net/wireless/ath/ath5k/Kconfig | 8 +++ drivers/net/wireless/ath/ath5k/Makefile | 1 + drivers/net/wireless/ath/ath5k/ath5k.h | 9 +++ drivers/net/wireless/ath/ath5k/base.c | 10 +++ drivers/net/wireless/ath/ath5k/base.h | 14 ++++ drivers/net/wireless/ath/ath5k/reset.c | 17 ----- drivers/net/wireless/ath/ath5k/rfkill.c | 121 ++++++++++++++++++++++++++++++++ 7 files changed, 163 insertions(+), 17 deletions(-) create mode 100644 drivers/net/wireless/ath/ath5k/rfkill.c (limited to 'drivers/net/wireless/ath/ath5k/reset.c') diff --git a/drivers/net/wireless/ath/ath5k/Kconfig b/drivers/net/wireless/ath/ath5k/Kconfig index 509b6f94f73..4863f4bbf76 100644 --- a/drivers/net/wireless/ath/ath5k/Kconfig +++ b/drivers/net/wireless/ath/ath5k/Kconfig @@ -39,3 +39,11 @@ config ATH5K_DEBUG modprobe ath5k debug=0x00000400 +config ATH5K_RFKILL + bool "Atheros 5xxx rfkill support" + depends on ATH5K + default y + ---help--- + Include support for enabling/disabling WiFi via rfkill switch + with Atheros 5xxx cards + diff --git a/drivers/net/wireless/ath/ath5k/Makefile b/drivers/net/wireless/ath/ath5k/Makefile index 84a74c5248e..f1e281c5a21 100644 --- a/drivers/net/wireless/ath/ath5k/Makefile +++ b/drivers/net/wireless/ath/ath5k/Makefile @@ -12,4 +12,5 @@ ath5k-y += attach.o ath5k-y += base.o ath5k-y += led.o ath5k-$(CONFIG_ATH5K_DEBUG) += debug.o +ath5k-$(CONFIG_ATH5K_RFKILL) += rfkill.o obj-$(CONFIG_ATH5K) += ath5k.o diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h index 81371821033..4c84e308763 100644 --- a/drivers/net/wireless/ath/ath5k/ath5k.h +++ b/drivers/net/wireless/ath/ath5k/ath5k.h @@ -1256,6 +1256,15 @@ extern u32 ath5k_hw_get_gpio(struct ath5k_hw *ah, u32 gpio); extern int ath5k_hw_set_gpio(struct ath5k_hw *ah, u32 gpio, u32 val); extern void ath5k_hw_set_gpio_intr(struct ath5k_hw *ah, unsigned int gpio, u32 interrupt_level); +/* rfkill Functions */ +#ifdef CONFIG_ATH5K_RFKILL +extern void ath5k_rfkill_hw_start(struct ath5k_hw *ah); +extern void ath5k_rfkill_hw_stop(struct ath5k_hw *ah); +#else +static inline void ath5k_rfkill_hw_start(struct ath5k_hw *ah) {} +static inline void ath5k_rfkill_hw_stop(struct ath5k_hw *ah) {} +#endif + /* Misc functions */ int ath5k_hw_set_capabilities(struct ath5k_hw *ah); extern int ath5k_hw_get_capability(struct ath5k_hw *ah, enum ath5k_capability_type cap_type, u32 capability, u32 *result); diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c index 85a00db4867..f55675c23f2 100644 --- a/drivers/net/wireless/ath/ath5k/base.c +++ b/drivers/net/wireless/ath/ath5k/base.c @@ -2360,6 +2360,8 @@ ath5k_init(struct ath5k_softc *sc) if (ret) goto done; + ath5k_rfkill_hw_start(ah); + /* * Reset the key cache since some parts do not reset the * contents on initial power up or resume from suspend. @@ -2468,6 +2470,8 @@ ath5k_stop_hw(struct ath5k_softc *sc) tasklet_kill(&sc->restq); tasklet_kill(&sc->beacontq); + ath5k_rfkill_hw_stop(sc->ah); + return ret; } @@ -2526,6 +2530,12 @@ ath5k_intr(int irq, void *dev_id) */ ath5k_hw_update_mib_counters(ah, &sc->ll_stats); } +#ifdef CONFIG_ATH5K_RFKILL + if (status & AR5K_INT_GPIO) + { + tasklet_schedule(&sc->rf_kill.toggleq); + } +#endif } } while (ath5k_hw_is_intr_pending(ah) && --counter > 0); diff --git a/drivers/net/wireless/ath/ath5k/base.h b/drivers/net/wireless/ath/ath5k/base.h index 852b2c189fd..7a0ecdd9c25 100644 --- a/drivers/net/wireless/ath/ath5k/base.h +++ b/drivers/net/wireless/ath/ath5k/base.h @@ -46,6 +46,7 @@ #include #include #include +#include #include "ath5k.h" #include "debug.h" @@ -91,6 +92,15 @@ struct ath5k_led struct led_classdev led_dev; /* led classdev */ }; +/* Rfkill */ +struct ath5k_rfkill { + /* GPIO PIN for rfkill */ + u16 gpio; + /* polarity of rfkill GPIO PIN */ + bool polarity; + /* RFKILL toggle tasklet */ + struct tasklet_struct toggleq; +}; #if CHAN_DEBUG #define ATH_CHAN_MAX (26+26+26+200+200) @@ -167,6 +177,10 @@ struct ath5k_softc { struct tasklet_struct txtq; /* tx intr tasklet */ struct ath5k_led tx_led; /* tx led */ +#ifdef CONFIG_ATH5K_RFKILL + struct ath5k_rfkill rf_kill; +#endif + spinlock_t block; /* protects beacon */ struct tasklet_struct beacontq; /* beacon intr tasklet */ struct ath5k_buf *bbuf; /* beacon buffer */ diff --git a/drivers/net/wireless/ath/ath5k/reset.c b/drivers/net/wireless/ath/ath5k/reset.c index 66067733ddd..bd0a97a38d3 100644 --- a/drivers/net/wireless/ath/ath5k/reset.c +++ b/drivers/net/wireless/ath/ath5k/reset.c @@ -1304,23 +1304,6 @@ int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, if (ah->ah_version != AR5K_AR5210) ath5k_hw_set_imr(ah, ah->ah_imr); - /* - * Setup RFKill interrupt if rfkill flag is set on eeprom. - * TODO: Use gpio pin and polarity infos from eeprom - * TODO: Handle this in ath5k_intr because it'll result - * a nasty interrupt storm. - */ -#if 0 - if (AR5K_EEPROM_HDR_RFKILL(ah->ah_capabilities.cap_eeprom.ee_header)) { - ath5k_hw_set_gpio_input(ah, 0); - ah->ah_gpio[0] = ath5k_hw_get_gpio(ah, 0); - if (ah->ah_gpio[0] == 0) - ath5k_hw_set_gpio_intr(ah, 0, 1); - else - ath5k_hw_set_gpio_intr(ah, 0, 0); - } -#endif - /* Enable 32KHz clock function for AR5212+ chips * Set clocks to 32KHz operation and use an * external 32KHz crystal when sleeping if one diff --git a/drivers/net/wireless/ath/ath5k/rfkill.c b/drivers/net/wireless/ath/ath5k/rfkill.c new file mode 100644 index 00000000000..492ada92db5 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/rfkill.c @@ -0,0 +1,121 @@ +/* + * RFKILL support for ath5k + * + * Copyright (c) 2009 Tobias Doerffel + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + */ + +#include "base.h" + + +static inline void ath5k_rfkill_disable(struct ath5k_softc *sc) +{ + ATH5K_DBG(sc, ATH5K_DEBUG_ANY, "rfkill disable (gpio:%d polarity:%d)\n", + sc->rf_kill.gpio, sc->rf_kill.polarity); + ath5k_hw_set_gpio_output(sc->ah, sc->rf_kill.gpio); + ath5k_hw_set_gpio(sc->ah, sc->rf_kill.gpio, !sc->rf_kill.polarity); +} + + +static inline void ath5k_rfkill_enable(struct ath5k_softc *sc) +{ + ATH5K_DBG(sc, ATH5K_DEBUG_ANY, "rfkill enable (gpio:%d polarity:%d)\n", + sc->rf_kill.gpio, sc->rf_kill.polarity); + ath5k_hw_set_gpio_output(sc->ah, sc->rf_kill.gpio); + ath5k_hw_set_gpio(sc->ah, sc->rf_kill.gpio, sc->rf_kill.polarity); +} + +static inline void ath5k_rfkill_set_intr(struct ath5k_softc *sc, bool enable) +{ + struct ath5k_hw *ah = sc->ah; + ath5k_hw_set_gpio_input(ah, sc->rf_kill.gpio); + ah->ah_gpio[0] = ath5k_hw_get_gpio(ah, sc->rf_kill.gpio); + ath5k_hw_set_gpio_intr(ah, sc->rf_kill.gpio, enable ? + !!ah->ah_gpio[0] : !ah->ah_gpio[0]); +} + +static bool +ath5k_is_rfkill_set(struct ath5k_softc *sc) +{ + /* configuring GPIO for input for some reason disables rfkill */ + /*ath5k_hw_set_gpio_input(sc->ah, sc->rf_kill.gpio);*/ + return ath5k_hw_get_gpio(sc->ah, sc->rf_kill.gpio) == + sc->rf_kill.polarity; +} + +static void +ath5k_tasklet_rfkill_toggle(unsigned long data) +{ + struct ath5k_softc *sc = (void *)data; + bool blocked; + + blocked = ath5k_is_rfkill_set(sc); + wiphy_rfkill_set_hw_state(sc->hw->wiphy, blocked); +} + + +void +ath5k_rfkill_hw_start(struct ath5k_hw *ah) +{ + struct ath5k_softc *sc = ah->ah_sc; + + /* read rfkill GPIO configuration from EEPROM header */ + sc->rf_kill.gpio = ah->ah_capabilities.cap_eeprom.ee_rfkill_pin; + sc->rf_kill.polarity = ah->ah_capabilities.cap_eeprom.ee_rfkill_pol; + + tasklet_init(&sc->rf_kill.toggleq, ath5k_tasklet_rfkill_toggle, + (unsigned long)sc); + + ath5k_rfkill_disable(sc); + + /* enable interrupt for rfkill switch */ + if (AR5K_EEPROM_HDR_RFKILL(ah->ah_capabilities.cap_eeprom.ee_header)) { + ath5k_rfkill_set_intr(sc, true); + } +} + + +void +ath5k_rfkill_hw_stop(struct ath5k_hw *ah) +{ + struct ath5k_softc *sc = ah->ah_sc; + + /* disable interrupt for rfkill switch */ + if (AR5K_EEPROM_HDR_RFKILL(ah->ah_capabilities.cap_eeprom.ee_header)) { + ath5k_rfkill_set_intr(sc, false); + } + + tasklet_kill(&sc->rf_kill.toggleq); + + /* enable RFKILL when stopping HW so Wifi LED is turned off */ + ath5k_rfkill_enable(sc); +} + -- cgit v1.2.3-70-g09d2