From: Nick Kossifidis <mick@madwifi-project.org>
To: ath5k-devel@lists.ath5k.org, linux-wireless@vger.kernel.org
Cc: linville@tuxdriver.com, jirislaby@gmail.com, mcgrof@gmail.com,
me@bobcopeland.com, nbd@openwrt.org
Subject: [PATCH 3/5] ath5k: Update RF Buffer handling
Date: Sat, 31 Jan 2009 04:28:26 +0200 [thread overview]
Message-ID: <20090131022826.GC3342@makis> (raw)
* Use the new way to modify rf buffer and put some rf buffer documentation on rfbufer.h
* Merge all rf regs functions to one
* Sync with legacy HAL and Sam's HAL
* Set gain_F settings so that gain_F optimization engine works on RF5111/RF5112
(note that both HALs only use step 0 for RF5111 and they don't use gain_F optimization
for this chip, code is there but is never used)
Signed-Off-by: Nick Kossifidis <mickflemm@gmail.com>
---
drivers/net/wireless/ath5k/ath5k.h | 11 +-
drivers/net/wireless/ath5k/phy.c | 571 +++++++++++++++++----------------
drivers/net/wireless/ath5k/reg.h | 24 --
drivers/net/wireless/ath5k/reset.c | 2 +-
drivers/net/wireless/ath5k/rfbuffer.h | 88 +++++-
5 files changed, 379 insertions(+), 317 deletions(-)
diff --git a/drivers/net/wireless/ath5k/ath5k.h b/drivers/net/wireless/ath5k/ath5k.h
index c870e2a..5b2e0da 100644
--- a/drivers/net/wireless/ath5k/ath5k.h
+++ b/drivers/net/wireless/ath5k/ath5k.h
@@ -165,9 +165,6 @@
#define AR5K_INI_VAL_XR 0
#define AR5K_INI_VAL_MAX 5
-#define AR5K_RF5111_INI_RF_MAX_BANKS AR5K_MAX_RF_BANKS
-#define AR5K_RF5112_INI_RF_MAX_BANKS AR5K_MAX_RF_BANKS
-
/* Used for BSSID etc manipulation */
#define AR5K_LOW_ID(_a)( \
(_a)[0] | (_a)[1] << 8 | (_a)[2] << 16 | (_a)[3] << 24 \
@@ -342,6 +339,7 @@ struct ath5k_srev_name {
#define AR5K_SREV_PHY_5211 0x30
#define AR5K_SREV_PHY_5212 0x41
+#define AR5K_SREV_PHY_5212A 0x42
#define AR5K_SREV_PHY_2112B 0x43
#define AR5K_SREV_PHY_2413 0x45
#define AR5K_SREV_PHY_5413 0x61
@@ -1083,8 +1081,9 @@ struct ath5k_hw {
u32 ah_txq_isr;
u32 *ah_rf_banks;
size_t ah_rf_banks_size;
+ size_t ah_rf_regs_count;
struct ath5k_gain ah_gain;
- u32 ah_offset[AR5K_MAX_RF_BANKS];
+ u8 ah_offset[AR5K_MAX_RF_BANKS];
struct {
u16 txp_pcdac[AR5K_EEPROM_POWER_TABLE_SIZE];
@@ -1232,7 +1231,9 @@ extern int ath5k_hw_disable_pspoll(struct ath5k_hw *ah);
extern int ath5k_hw_write_initvals(struct ath5k_hw *ah, u8 mode, bool change_channel);
/* Initialize RF */
-extern int ath5k_hw_rfregs(struct ath5k_hw *ah, struct ieee80211_channel *channel, unsigned int mode);
+extern int ath5k_hw_rfregs_init(struct ath5k_hw *ah,
+ struct ieee80211_channel *channel,
+ unsigned int mode);
extern int ath5k_hw_rfgain_init(struct ath5k_hw *ah, unsigned int freq);
extern enum ath5k_rfgain ath5k_hw_gainf_calibrate(struct ath5k_hw *ah);
extern int ath5k_hw_rfgain_opt_init(struct ath5k_hw *ah);
diff --git a/drivers/net/wireless/ath5k/phy.c b/drivers/net/wireless/ath5k/phy.c
index 2543a71..81f5beb 100644
--- a/drivers/net/wireless/ath5k/phy.c
+++ b/drivers/net/wireless/ath5k/phy.c
@@ -32,48 +32,80 @@
/*
* Used to modify RF Banks before writing them to AR5K_RF_BUFFER
*/
-static unsigned int ath5k_hw_rfregs_op(u32 *rf, u32 offset, u32 reg, u32 bits,
- u32 first, u32 col, bool set)
+static unsigned int ath5k_hw_rfb_op(struct ath5k_hw *ah,
+ const struct ath5k_rf_reg *rf_regs,
+ u32 val, u8 reg_id, bool set)
{
- u32 mask, entry, last, data, shift, position;
- s32 left;
+ const struct ath5k_rf_reg *rfreg = NULL;
+ u8 offset, bank, num_bits, col, position;
+ u16 entry;
+ u32 mask, data, last_bit, bits_shifted, first_bit;
+ u32 *rfb;
+ s32 bits_left;
int i;
data = 0;
+ rfb = ah->ah_rf_banks;
- if (rf == NULL)
+ for (i = 0; i < ah->ah_rf_regs_count; i++) {
+ if (rf_regs[i].index == reg_id) {
+ rfreg = &rf_regs[i];
+ break;
+ }
+ }
+
+ if (rfb == NULL || rfreg == NULL) {
+ ATH5K_PRINTF("Rf register not found!\n");
/* should not happen */
return 0;
+ }
+
+ bank = rfreg->bank;
+ num_bits = rfreg->field.len;
+ first_bit = rfreg->field.pos;
+ col = rfreg->field.col;
+
+ /* first_bit is an offset from bank's
+ * start. Since we have all banks on
+ * the same array, we use this offset
+ * to mark each bank's start */
+ offset = ah->ah_offset[bank];
- if (!(col <= 3 && bits <= 32 && first + bits <= 319)) {
+ /* Boundary check */
+ if (!(col <= 3 && num_bits <= 32 && first_bit + num_bits <= 319)) {
ATH5K_PRINTF("invalid values at offset %u\n", offset);
return 0;
}
- entry = ((first - 1) / 8) + offset;
- position = (first - 1) % 8;
+ entry = ((first_bit - 1) / 8) + offset;
+ position = (first_bit - 1) % 8;
if (set)
- data = ath5k_hw_bitswap(reg, bits);
+ data = ath5k_hw_bitswap(val, num_bits);
+
+ for (bits_shifted = 0, bits_left = num_bits; bits_left > 0;
+ position = 0, entry++) {
+
+ last_bit = (position + bits_left > 8) ? 8 :
+ position + bits_left;
- for (i = shift = 0, left = bits; left > 0; position = 0, entry++, i++) {
- last = (position + left > 8) ? 8 : position + left;
- mask = (((1 << last) - 1) ^ ((1 << position) - 1)) << (col * 8);
+ mask = (((1 << last_bit) - 1) ^ ((1 << position) - 1)) <<
+ (col * 8);
if (set) {
- rf[entry] &= ~mask;
- rf[entry] |= ((data << position) << (col * 8)) & mask;
+ rfb[entry] &= ~mask;
+ rfb[entry] |= ((data << position) << (col * 8)) & mask;
data >>= (8 - position);
} else {
- data = (((rf[entry] & mask) >> (col * 8)) >> position)
- << shift;
- shift += last - position;
+ data |= (((rfb[entry] & mask) >> (col * 8)) >> position)
+ << bits_shifted;
+ bits_shifted += last_bit - position;
}
- left -= 8 - position;
+ bits_left -= 8 - position;
}
- data = set ? 1 : ath5k_hw_bitswap(data, bits);
+ data = set ? 1 : ath5k_hw_bitswap(data, num_bits);
return data;
}
@@ -167,6 +199,7 @@ static u32 ath5k_hw_rf_gainf_corr(struct ath5k_hw *ah)
u32 *rf;
const struct ath5k_gain_opt *go;
const struct ath5k_gain_opt_step *g_step;
+ const struct ath5k_rf_reg *rf_regs;
/* Only RF5112 Rev. 2 supports it */
if ((ah->ah_radio != AR5K_RF5112) ||
@@ -174,6 +207,8 @@ static u32 ath5k_hw_rf_gainf_corr(struct ath5k_hw *ah)
return 0;
go = &rfgain_opt_5112;
+ rf_regs = rf_regs_5112a;
+ ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5112a);
g_step = &go->go_step[ah->ah_gain.g_step_idx];
@@ -184,11 +219,11 @@ static u32 ath5k_hw_rf_gainf_corr(struct ath5k_hw *ah)
ah->ah_gain.g_f_corr = 0;
/* No VGA (Variable Gain Amplifier) override, skip */
- if (ath5k_hw_rfregs_op(rf, ah->ah_offset[7], 0, 1, 36, 0, false) != 1)
+ if (ath5k_hw_rfb_op(ah, rf_regs, 0, AR5K_RF_MIXVGA_OVR, false) != 1)
return 0;
/* Mix gain stepping */
- step = ath5k_hw_rfregs_op(rf, ah->ah_offset[7], 0, 4, 32, 0, false);
+ step = ath5k_hw_rfb_op(ah, rf_regs, 0, AR5K_RF_MIXGAIN_STEP, false);
/* Mix gain override */
mix = g_step->gos_param[0];
@@ -217,6 +252,7 @@ static u32 ath5k_hw_rf_gainf_corr(struct ath5k_hw *ah)
* their detection window) so we must ignore it */
static bool ath5k_hw_rf_check_gainf_readback(struct ath5k_hw *ah)
{
+ const struct ath5k_rf_reg *rf_regs;
u32 step, mix_ovr, level[4];
u32 *rf;
@@ -226,8 +262,13 @@ static bool ath5k_hw_rf_check_gainf_readback(struct ath5k_hw *ah)
rf = ah->ah_rf_banks;
if (ah->ah_radio == AR5K_RF5111) {
- step = ath5k_hw_rfregs_op(rf, ah->ah_offset[7], 0, 6, 37, 0,
- false);
+
+ rf_regs = rf_regs_5111;
+ ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5111);
+
+ step = ath5k_hw_rfb_op(ah, rf_regs, 0, AR5K_RF_RFGAIN_STEP,
+ false);
+
level[0] = 0;
level[1] = (step == 63) ? 50 : step + 4;
level[2] = (step != 63) ? 64 : level[0];
@@ -238,8 +279,13 @@ static bool ath5k_hw_rf_check_gainf_readback(struct ath5k_hw *ah)
ah->ah_gain.g_low = level[0] +
(step == 63 ? AR5K_GAIN_DYN_ADJUST_LO_MARGIN : 0);
} else {
- mix_ovr = ath5k_hw_rfregs_op(rf, ah->ah_offset[7], 0, 1, 36, 0,
- false);
+
+ rf_regs = rf_regs_5112;
+ ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5112);
+
+ mix_ovr = ath5k_hw_rfb_op(ah, rf_regs, 0, AR5K_RF_MIXVGA_OVR,
+ false);
+
level[0] = level[2] = 0;
if (mix_ovr == 1) {
@@ -451,341 +497,320 @@ int ath5k_hw_rfgain_init(struct ath5k_hw *ah, unsigned int freq)
* RF Registers setup *
\********************/
+
/*
- * Read EEPROM Calibration data, modify RF Banks and Initialize RF5111
+ * Setup RF registers by writing rf buffer on hw
*/
-static int ath5k_hw_rf5111_rfregs(struct ath5k_hw *ah,
- struct ieee80211_channel *channel, unsigned int mode)
+int ath5k_hw_rfregs_init(struct ath5k_hw *ah, struct ieee80211_channel *channel,
+ unsigned int mode)
{
+ const struct ath5k_rf_reg *rf_regs;
+ const struct ath5k_ini_rfbuffer *ini_rfb;
+ const struct ath5k_gain_opt *go = NULL;
+ const struct ath5k_gain_opt_step *g_step;
struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
- u32 *rf;
- const unsigned int rf_size = ARRAY_SIZE(rfb_5111);
- unsigned int i;
- int obdb = -1, bank = -1;
- u32 ee_mode;
+ u8 ee_mode = 0;
+ u32 *rfb;
+ int i, obdb = -1, bank = -1;
- AR5K_ASSERT_ENTRY(mode, AR5K_MODE_MAX);
+ switch (ah->ah_radio) {
+ case AR5K_RF5111:
+ rf_regs = rf_regs_5111;
+ ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5111);
+ ini_rfb = rfb_5111;
+ ah->ah_rf_banks_size = ARRAY_SIZE(rfb_5111);
+ go = &rfgain_opt_5111;
+ break;
+ case AR5K_RF5112:
+ if (ah->ah_radio_5ghz_revision >= AR5K_SREV_RAD_5112A) {
+ rf_regs = rf_regs_5112a;
+ ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5112a);
+ ini_rfb = rfb_5112a;
+ ah->ah_rf_banks_size = ARRAY_SIZE(rfb_5112a);
+ } else {
+ rf_regs = rf_regs_5112;
+ ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5112);
+ ini_rfb = rfb_5112;
+ ah->ah_rf_banks_size = ARRAY_SIZE(rfb_5112);
+ }
+ go = &rfgain_opt_5112;
+ break;
+ case AR5K_RF2413:
+ rf_regs = rf_regs_2413;
+ ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_2413);
+ ini_rfb = rfb_2413;
+ ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2413);
+ break;
+ case AR5K_RF2316:
+ rf_regs = rf_regs_2316;
+ ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_2316);
+ ini_rfb = rfb_2316;
+ ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2316);
+ break;
+ case AR5K_RF5413:
+ rf_regs = rf_regs_5413;
+ ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5413);
+ ini_rfb = rfb_5413;
+ ah->ah_rf_banks_size = ARRAY_SIZE(rfb_5413);
+ break;
+ case AR5K_RF2317:
+ rf_regs = rf_regs_2425;
+ ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_2425);
+ ini_rfb = rfb_2317;
+ ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2317);
+ break;
+ case AR5K_RF2425:
+ rf_regs = rf_regs_2425;
+ ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_2425);
+ if (ah->ah_mac_srev < AR5K_SREV_AR2417) {
+ ini_rfb = rfb_2425;
+ ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2425);
+ } else {
+ ini_rfb = rfb_2417;
+ ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2417);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
- rf = ah->ah_rf_banks;
+ /* If it's the first time we set rf buffer, allocate
+ * ah->ah_rf_banks based on ah->ah_rf_banks_size
+ * we set above */
+ if (ah->ah_rf_banks == NULL) {
+ ah->ah_rf_banks = kmalloc(sizeof(u32) * ah->ah_rf_banks_size,
+ GFP_KERNEL);
+ if (ah->ah_rf_banks == NULL) {
+ ATH5K_ERR(ah->ah_sc, "out of memory\n");
+ return -ENOMEM;
+ }
+ }
/* Copy values to modify them */
- for (i = 0; i < rf_size; i++) {
- if (rfb_5111[i].rfb_bank >= AR5K_RF5111_INI_RF_MAX_BANKS) {
+ rfb = ah->ah_rf_banks;
+
+ for (i = 0; i < ah->ah_rf_banks_size; i++) {
+ if (ini_rfb[i].rfb_bank >= AR5K_MAX_RF_BANKS) {
ATH5K_ERR(ah->ah_sc, "invalid bank\n");
return -EINVAL;
}
- if (bank != rfb_5111[i].rfb_bank) {
- bank = rfb_5111[i].rfb_bank;
+ /* Bank changed, write down the offset */
+ if (bank != ini_rfb[i].rfb_bank) {
+ bank = ini_rfb[i].rfb_bank;
ah->ah_offset[bank] = i;
}
- rf[i] = rfb_5111[i].rfb_mode_data[mode];
+ rfb[i] = ini_rfb[i].rfb_mode_data[mode];
}
- /* Modify bank 0 */
+ /* Set Output and Driver bias current (OB/DB) */
if (channel->hw_value & CHANNEL_2GHZ) {
+
if (channel->hw_value & CHANNEL_CCK)
ee_mode = AR5K_EEPROM_MODE_11B;
else
ee_mode = AR5K_EEPROM_MODE_11G;
- obdb = 0;
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[0],
- ee->ee_ob[ee_mode][obdb], 3, 119, 0, true))
- return -EINVAL;
+ /* For RF511X/RF211X combination we
+ * use b_OB and b_DB parameters stored
+ * in eeprom on ee->ee_ob[ee_mode][0]
+ *
+ * For all other chips we use OB/DB for 2Ghz
+ * stored in the b/g modal section just like
+ * 802.11a on ee->ee_ob[ee_mode][1] */
+ if ((ah->ah_radio == AR5K_RF5111) ||
+ (ah->ah_radio == AR5K_RF5112))
+ obdb = 0;
+ else
+ obdb = 1;
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[0],
- ee->ee_ob[ee_mode][obdb], 3, 122, 0, true))
- return -EINVAL;
+ ath5k_hw_rfb_op(ah, rf_regs, ee->ee_ob[ee_mode][obdb],
+ AR5K_RF_OB_2GHZ, true);
- obdb = 1;
- /* Modify bank 6 */
- } else {
- /* For 11a, Turbo and XR */
+ ath5k_hw_rfb_op(ah, rf_regs, ee->ee_db[ee_mode][obdb],
+ AR5K_RF_DB_2GHZ, true);
+
+ /* RF5111 always needs OB/DB for 5GHz, even if we use 2GHz */
+ } else if ((channel->hw_value & CHANNEL_5GHZ) ||
+ (ah->ah_radio == AR5K_RF5111)) {
+
+ /* For 11a, Turbo and XR we need to choose
+ * OB/DB based on frequency range */
ee_mode = AR5K_EEPROM_MODE_11A;
obdb = channel->center_freq >= 5725 ? 3 :
(channel->center_freq >= 5500 ? 2 :
(channel->center_freq >= 5260 ? 1 :
(channel->center_freq > 4000 ? 0 : -1)));
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[6],
- ee->ee_pwd_84, 1, 51, 3, true))
+ if (obdb < 0)
return -EINVAL;
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[6],
- ee->ee_pwd_90, 1, 45, 3, true))
- return -EINVAL;
- }
+ ath5k_hw_rfb_op(ah, rf_regs, ee->ee_ob[ee_mode][obdb],
+ AR5K_RF_OB_5GHZ, true);
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[6],
- !ee->ee_xpd[ee_mode], 1, 95, 0, true))
- return -EINVAL;
+ ath5k_hw_rfb_op(ah, rf_regs, ee->ee_db[ee_mode][obdb],
+ AR5K_RF_DB_5GHZ, true);
+ }
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[6],
- ee->ee_x_gain[ee_mode], 4, 96, 0, true))
- return -EINVAL;
+ g_step = &go->go_step[ah->ah_gain.g_step_idx];
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[6], obdb >= 0 ?
- ee->ee_ob[ee_mode][obdb] : 0, 3, 104, 0, true))
- return -EINVAL;
+ /* Bank Modifications (chip-specific) */
+ if (ah->ah_radio == AR5K_RF5111) {
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[6], obdb >= 0 ?
- ee->ee_db[ee_mode][obdb] : 0, 3, 107, 0, true))
- return -EINVAL;
+ /* Set gain_F settings according to current step */
+ if (channel->hw_value & CHANNEL_OFDM) {
- /* Modify bank 7 */
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[7],
- ee->ee_i_gain[ee_mode], 6, 29, 0, true))
- return -EINVAL;
+ AR5K_REG_WRITE_BITS(ah, AR5K_PHY_FRAME_CTL,
+ AR5K_PHY_FRAME_CTL_TX_CLIP,
+ g_step->gos_param[0]);
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[7],
- ee->ee_xpd[ee_mode], 1, 4, 0, true))
- return -EINVAL;
+ ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[1],
+ AR5K_RF_PWD_90, true);
- /* Write RF values */
- for (i = 0; i < rf_size; i++) {
- AR5K_REG_WAIT(i);
- ath5k_hw_reg_write(ah, rf[i], rfb_5111[i].rfb_ctrl_register);
- }
+ ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[2],
+ AR5K_RF_PWD_84, true);
- ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE;
+ ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[3],
+ AR5K_RF_RFGAIN_SEL, true);
- return 0;
-}
+ /* We programmed gain_F parameters, switch back
+ * to active state */
+ ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE;
-/*
- * Read EEPROM Calibration data, modify RF Banks and Initialize RF5112
- */
-static int ath5k_hw_rf5112_rfregs(struct ath5k_hw *ah,
- struct ieee80211_channel *channel, unsigned int mode)
-{
- const struct ath5k_ini_rfbuffer *rf_ini;
- struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
- u32 *rf;
- unsigned int rf_size, i;
- int obdb = -1, bank = -1;
- u32 ee_mode;
+ }
- AR5K_ASSERT_ENTRY(mode, AR5K_MODE_MAX);
+ /* Bank 6/7 setup */
- rf = ah->ah_rf_banks;
+ ath5k_hw_rfb_op(ah, rf_regs, !ee->ee_xpd[ee_mode],
+ AR5K_RF_PWD_XPD, true);
- if (ah->ah_radio_5ghz_revision >= AR5K_SREV_RAD_5112A) {
- rf_ini = rfb_5112a;
- rf_size = ARRAY_SIZE(rfb_5112a);
- } else {
- rf_ini = rfb_5112;
- rf_size = ARRAY_SIZE(rfb_5112);
- }
+ ath5k_hw_rfb_op(ah, rf_regs, ee->ee_x_gain[ee_mode],
+ AR5K_RF_XPD_GAIN, true);
- /* Copy values to modify them */
- for (i = 0; i < rf_size; i++) {
- if (rf_ini[i].rfb_bank >= AR5K_RF5112_INI_RF_MAX_BANKS) {
- ATH5K_ERR(ah->ah_sc, "invalid bank\n");
- return -EINVAL;
- }
+ ath5k_hw_rfb_op(ah, rf_regs, ee->ee_i_gain[ee_mode],
+ AR5K_RF_GAIN_I, true);
- if (bank != rf_ini[i].rfb_bank) {
- bank = rf_ini[i].rfb_bank;
- ah->ah_offset[bank] = i;
- }
+ ath5k_hw_rfb_op(ah, rf_regs, ee->ee_xpd[ee_mode],
+ AR5K_RF_PLO_SEL, true);
- rf[i] = rf_ini[i].rfb_mode_data[mode];
+ /* TODO: Half/quarter channel support */
}
- /* Modify bank 6 */
- if (channel->hw_value & CHANNEL_2GHZ) {
- if (channel->hw_value & CHANNEL_OFDM)
- ee_mode = AR5K_EEPROM_MODE_11G;
- else
- ee_mode = AR5K_EEPROM_MODE_11B;
- obdb = 0;
+ if (ah->ah_radio == AR5K_RF5112) {
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[6],
- ee->ee_ob[ee_mode][obdb], 3, 287, 0, true))
- return -EINVAL;
+ /* Set gain_F settings according to current step */
+ if (channel->hw_value & CHANNEL_OFDM) {
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[6],
- ee->ee_ob[ee_mode][obdb], 3, 290, 0, true))
- return -EINVAL;
- } else {
- /* For 11a, Turbo and XR */
- ee_mode = AR5K_EEPROM_MODE_11A;
- obdb = channel->center_freq >= 5725 ? 3 :
- (channel->center_freq >= 5500 ? 2 :
- (channel->center_freq >= 5260 ? 1 :
- (channel->center_freq > 4000 ? 0 : -1)));
+ ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[0],
+ AR5K_RF_MIXGAIN_OVR, true);
- if (obdb == -1)
- return -EINVAL;
+ ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[1],
+ AR5K_RF_PWD_138, true);
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[6],
- ee->ee_ob[ee_mode][obdb], 3, 279, 0, true))
- return -EINVAL;
+ ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[2],
+ AR5K_RF_PWD_137, true);
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[6],
- ee->ee_ob[ee_mode][obdb], 3, 282, 0, true))
- return -EINVAL;
- }
+ ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[3],
+ AR5K_RF_PWD_136, true);
- ath5k_hw_rfregs_op(rf, ah->ah_offset[6],
- ee->ee_x_gain[ee_mode], 2, 270, 0, true);
- ath5k_hw_rfregs_op(rf, ah->ah_offset[6],
- ee->ee_x_gain[ee_mode], 2, 257, 0, true);
+ ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[4],
+ AR5K_RF_PWD_132, true);
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[6],
- ee->ee_xpd[ee_mode], 1, 302, 0, true))
- return -EINVAL;
+ ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[5],
+ AR5K_RF_PWD_131, true);
- /* Modify bank 7 */
- if (!ath5k_hw_rfregs_op(rf, ah->ah_offset[7],
- ee->ee_i_gain[ee_mode], 6, 14, 0, true))
- return -EINVAL;
+ ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[6],
+ AR5K_RF_PWD_130, true);
- /* Write RF values */
- for (i = 0; i < rf_size; i++)
- ath5k_hw_reg_write(ah, rf[i], rf_ini[i].rfb_ctrl_register);
+ /* We programmed gain_F parameters, switch back
+ * to active state */
+ ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE;
+ }
+ /* Bank 6/7 setup */
- ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE;
+ ath5k_hw_rfb_op(ah, rf_regs, ee->ee_xpd[ee_mode],
+ AR5K_RF_XPD_SEL, true);
- return 0;
-}
+ if (ah->ah_radio_5ghz_revision < AR5K_SREV_RAD_5112A) {
+ /* Rev. 1 supports only one xpd */
+ ath5k_hw_rfb_op(ah, rf_regs,
+ ee->ee_x_gain[ee_mode],
+ AR5K_RF_XPD_GAIN, true);
-/*
- * Initialize RF5413/5414 and future chips
- * (until we come up with a better solution)
- */
-static int ath5k_hw_rf5413_rfregs(struct ath5k_hw *ah,
- struct ieee80211_channel *channel, unsigned int mode)
-{
- const struct ath5k_ini_rfbuffer *rf_ini;
- u32 *rf;
- unsigned int rf_size, i;
- int bank = -1;
+ } else {
+ /* TODO: Set high and low gain bits */
+ ath5k_hw_rfb_op(ah, rf_regs,
+ ee->ee_x_gain[ee_mode],
+ AR5K_RF_PD_GAIN_LO, true);
+ ath5k_hw_rfb_op(ah, rf_regs,
+ ee->ee_x_gain[ee_mode],
+ AR5K_RF_PD_GAIN_HI, true);
- AR5K_ASSERT_ENTRY(mode, AR5K_MODE_MAX);
+ /* Lower synth voltage on Rev 2 */
+ ath5k_hw_rfb_op(ah, rf_regs, 2,
+ AR5K_RF_HIGH_VC_CP, true);
- rf = ah->ah_rf_banks;
+ ath5k_hw_rfb_op(ah, rf_regs, 2,
+ AR5K_RF_MID_VC_CP, true);
- switch (ah->ah_radio) {
- case AR5K_RF5413:
- rf_ini = rfb_5413;
- rf_size = ARRAY_SIZE(rfb_5413);
- break;
- case AR5K_RF2413:
- rf_ini = rfb_2413;
- rf_size = ARRAY_SIZE(rfb_2413);
+ ath5k_hw_rfb_op(ah, rf_regs, 2,
+ AR5K_RF_LOW_VC_CP, true);
- if (mode < 2) {
- ATH5K_ERR(ah->ah_sc,
- "invalid channel mode: %i\n", mode);
- return -EINVAL;
- }
+ ath5k_hw_rfb_op(ah, rf_regs, 2,
+ AR5K_RF_PUSH_UP, true);
- break;
- case AR5K_RF2425:
- rf_ini = rfb_2425;
- rf_size = ARRAY_SIZE(rfb_2425);
+ /* Decrease power consumption on 5213+ BaseBand */
+ if (ah->ah_phy_revision >= AR5K_SREV_PHY_5212A) {
+ ath5k_hw_rfb_op(ah, rf_regs, 1,
+ AR5K_RF_PAD2GND, true);
- if (mode < 2) {
- ATH5K_ERR(ah->ah_sc,
- "invalid channel mode: %i\n", mode);
- return -EINVAL;
- }
+ ath5k_hw_rfb_op(ah, rf_regs, 1,
+ AR5K_RF_XB2_LVL, true);
- break;
- default:
- return -EINVAL;
- }
+ ath5k_hw_rfb_op(ah, rf_regs, 1,
+ AR5K_RF_XB5_LVL, true);
- /* Copy values to modify them */
- for (i = 0; i < rf_size; i++) {
- if (rf_ini[i].rfb_bank >= AR5K_RF5112_INI_RF_MAX_BANKS) {
- ATH5K_ERR(ah->ah_sc, "invalid bank\n");
- return -EINVAL;
- }
+ ath5k_hw_rfb_op(ah, rf_regs, 1,
+ AR5K_RF_PWD_167, true);
- if (bank != rf_ini[i].rfb_bank) {
- bank = rf_ini[i].rfb_bank;
- ah->ah_offset[bank] = i;
+ ath5k_hw_rfb_op(ah, rf_regs, 1,
+ AR5K_RF_PWD_166, true);
+ }
}
- rf[i] = rf_ini[i].rfb_mode_data[mode];
- }
+ ath5k_hw_rfb_op(ah, rf_regs, ee->ee_i_gain[ee_mode],
+ AR5K_RF_GAIN_I, true);
- /*
- * After compairing dumps from different cards
- * we get the same RF_BUFFER settings (diff returns
- * 0 lines). It seems that RF_BUFFER settings are static
- * and are written unmodified (no EEPROM stuff
- * is used because calibration data would be
- * different between different cards and would result
- * different RF_BUFFER settings)
- */
+ /* TODO: Half/quarter channel support */
- /* Write RF values */
- for (i = 0; i < rf_size; i++)
- ath5k_hw_reg_write(ah, rf[i], rf_ini[i].rfb_ctrl_register);
+ }
- return 0;
-}
+ if (ah->ah_radio == AR5K_RF5413 &&
+ channel->hw_value & CHANNEL_2GHZ) {
-/*
- * Initialize RF
- */
-int ath5k_hw_rfregs(struct ath5k_hw *ah, struct ieee80211_channel *channel,
- unsigned int mode)
-{
- int (*func)(struct ath5k_hw *, struct ieee80211_channel *, unsigned int);
- int ret;
+ ath5k_hw_rfb_op(ah, rf_regs, 1, AR5K_RF_DERBY_CHAN_SEL_MODE,
+ true);
- switch (ah->ah_radio) {
- case AR5K_RF5111:
- ah->ah_rf_banks_size = sizeof(rfb_5111);
- func = ath5k_hw_rf5111_rfregs;
- break;
- case AR5K_RF5112:
- if (ah->ah_radio_5ghz_revision >= AR5K_SREV_RAD_5112A)
- ah->ah_rf_banks_size = sizeof(rfb_5112a);
- else
- ah->ah_rf_banks_size = sizeof(rfb_5112);
- func = ath5k_hw_rf5112_rfregs;
- break;
- case AR5K_RF5413:
- ah->ah_rf_banks_size = sizeof(rfb_5413);
- func = ath5k_hw_rf5413_rfregs;
- break;
- case AR5K_RF2413:
- ah->ah_rf_banks_size = sizeof(rfb_2413);
- func = ath5k_hw_rf5413_rfregs;
- break;
- case AR5K_RF2425:
- ah->ah_rf_banks_size = sizeof(rfb_2425);
- func = ath5k_hw_rf5413_rfregs;
- break;
- default:
- return -EINVAL;
- }
+ /* Set optimum value for early revisions (on pci-e chips) */
+ if (ah->ah_mac_srev >= AR5K_SREV_AR5424 &&
+ ah->ah_mac_srev < AR5K_SREV_AR5413)
+ ath5k_hw_rfb_op(ah, rf_regs, ath5k_hw_bitswap(6, 3),
+ AR5K_RF_PWD_ICLOBUF_2G, true);
- if (ah->ah_rf_banks == NULL) {
- /* XXX do extra checks? */
- ah->ah_rf_banks = kmalloc(ah->ah_rf_banks_size, GFP_KERNEL);
- if (ah->ah_rf_banks == NULL) {
- ATH5K_ERR(ah->ah_sc, "out of memory\n");
- return -ENOMEM;
- }
}
- ret = func(ah, channel, mode);
+ /* Write RF banks on hw */
+ for (i = 0; i < ah->ah_rf_banks_size; i++) {
+ AR5K_REG_WAIT(i);
+ ath5k_hw_reg_write(ah, rfb[i], ini_rfb[i].rfb_ctrl_register);
+ }
- return ret;
+ return 0;
}
-
-
/**************************\
PHY/RF channel functions
\**************************/
diff --git a/drivers/net/wireless/ath5k/reg.h b/drivers/net/wireless/ath5k/reg.h
index 9189ab1..9aa22ef 100644
--- a/drivers/net/wireless/ath5k/reg.h
+++ b/drivers/net/wireless/ath5k/reg.h
@@ -2101,34 +2101,10 @@
/*
* RF Buffer register
*
- * There are some special control registers on the RF chip
- * that hold various operation settings related mostly to
- * the analog parts (channel, gain adjustment etc).
- *
- * We don't write on those registers directly but
- * we send a data packet on the buffer register and
- * then write on another special register to notify hw
- * to apply the settings. This is done so that control registers
- * can be dynamicaly programmed during operation and the settings
- * are applied faster on the hw.
- *
- * We sent such data packets during rf initialization and channel change
- * through ath5k_hw_rf*_rfregs and ath5k_hw_rf*_channel functions.
- *
- * The data packets we send during initializadion are inside ath5k_ini_rf
- * struct (see ath5k_hw.h) and each one is related to an "rf register bank".
- * We use *rfregs functions to modify them acording to current operation
- * mode and eeprom values and pass them all together to the chip.
- *
* It's obvious from the code that 0x989c is the buffer register but
* for the other special registers that we write to after sending each
* packet, i have no idea. So i'll name them BUFFER_CONTROL_X registers
* for now. It's interesting that they are also used for some other operations.
- *
- * Also check out hw.h and U.S. Patent 6677779 B1 (about buffer
- * registers and control registers):
- *
- * http://www.google.com/patents?id=qNURAAAAEBAJ
*/
#define AR5K_RF_BUFFER 0x989c
diff --git a/drivers/net/wireless/ath5k/reset.c b/drivers/net/wireless/ath5k/reset.c
index f7ce80e..40cf4c7 100644
--- a/drivers/net/wireless/ath5k/reset.c
+++ b/drivers/net/wireless/ath5k/reset.c
@@ -600,7 +600,7 @@ int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode,
/*
* Write RF registers
*/
- ret = ath5k_hw_rfregs(ah, channel, mode);
+ ret = ath5k_hw_rfregs_init(ah, channel, mode);
if (ret)
return ret;
diff --git a/drivers/net/wireless/ath5k/rfbuffer.h b/drivers/net/wireless/ath5k/rfbuffer.h
index 28b3016..e50baff 100644
--- a/drivers/net/wireless/ath5k/rfbuffer.h
+++ b/drivers/net/wireless/ath5k/rfbuffer.h
@@ -17,6 +17,33 @@
*
*/
+
+/*
+ * There are some special registers on the RF chip
+ * that control various operation settings related mostly to
+ * the analog parts (channel, gain adjustment etc).
+ *
+ * We don't write on those registers directly but
+ * we send a data packet on the chip, using a special register,
+ * that holds all the settings we need. After we 've sent the
+ * data packet, we write on another special register to notify hw
+ * to apply the settings. This is done so that control registers
+ * can be dynamicaly programmed during operation and the settings
+ * are applied faster on the hw.
+ *
+ * We call each data packet an "RF Bank" and all the data we write
+ * (all RF Banks) "RF Buffer". This file holds initial RF Buffer
+ * data for the different RF chips, and various info to match RF
+ * Buffer offsets with specific RF registers so that we can access
+ * them. We tweak these settings on rfregs_init function.
+ *
+ * Also check out reg.h and U.S. Patent 6677779 B1 (about buffer
+ * registers and control registers):
+ *
+ * http://www.google.com/patents?id=qNURAAAAEBAJ
+ */
+
+
/*
* Struct to hold default mode specific RF
* register values (RF Banks)
@@ -72,15 +99,28 @@ enum ath5k_rf_regs_idx {
AR5K_RF_XB2_LVL,
AR5K_RF_XB5_LVL,
AR5K_RF_PWD_ICLOBUF_2G,
+ AR5K_RF_PWD_84,
+ AR5K_RF_PWD_90,
+ AR5K_RF_PWD_130,
+ AR5K_RF_PWD_131,
+ AR5K_RF_PWD_132,
+ AR5K_RF_PWD_136,
+ AR5K_RF_PWD_137,
+ AR5K_RF_PWD_138,
+ AR5K_RF_PWD_166,
+ AR5K_RF_PWD_167,
AR5K_RF_DERBY_CHAN_SEL_MODE,
/* BANK 7 */
AR5K_RF_GAIN_I,
AR5K_RF_PLO_SEL,
AR5K_RF_RFGAIN_SEL,
+ AR5K_RF_RFGAIN_STEP,
AR5K_RF_WAIT_S,
AR5K_RF_WAIT_I,
AR5K_RF_MAX_TIME,
+ AR5K_RF_MIXVGA_OVR,
AR5K_RF_MIXGAIN_OVR,
+ AR5K_RF_MIXGAIN_STEP,
AR5K_RF_PD_DELAY_A,
AR5K_RF_PD_DELAY_B,
AR5K_RF_PD_DELAY_XR,
@@ -118,19 +158,21 @@ enum ath5k_rf_regs_idx {
#define AR5K_RF5111_MAX_TIME { 2, 49, 0 }
static const struct ath5k_rf_reg rf_regs_5111[] = {
- {6, AR5K_RF_OB_2GHZ, AR5K_RF5111_OB_2GHZ},
- {6, AR5K_RF_DB_2GHZ, AR5K_RF5111_DB_2GHZ},
- {6, AR5K_RF_OB_5GHZ, AR5K_RF5111_OB_5GHZ},
- {6, AR5K_RF_DB_5GHZ, AR5K_RF5111_DB_5GHZ},
- {6, AR5K_RF_PWD_XPD, AR5K_RF5111_PWD_XPD},
- {6, AR5K_RF_XPD_GAIN, AR5K_RF5111_XPD_GAIN},
- {7, AR5K_RF_GAIN_I, AR5K_RF5111_GAIN_I},
- {7, AR5K_RF_PLO_SEL, AR5K_RF5111_PLO_SEL},
- {7, AR5K_RF_RFGAIN_SEL, AR5K_RF5111_RFGAIN_SEL},
- {7, AR5K_RF_WAIT_S, AR5K_RF5111_WAIT_S},
- {7, AR5K_RF_WAIT_I, AR5K_RF5111_WAIT_I},
- {7, AR5K_RF_MAX_TIME, AR5K_RF5111_MAX_TIME}
-
+ {6, AR5K_RF_OB_2GHZ, AR5K_RF5111_OB_2GHZ},
+ {6, AR5K_RF_DB_2GHZ, AR5K_RF5111_DB_2GHZ},
+ {6, AR5K_RF_OB_5GHZ, AR5K_RF5111_OB_5GHZ},
+ {6, AR5K_RF_DB_5GHZ, AR5K_RF5111_DB_5GHZ},
+ {6, AR5K_RF_PWD_XPD, AR5K_RF5111_PWD_XPD},
+ {6, AR5K_RF_XPD_GAIN, AR5K_RF5111_XPD_GAIN},
+ {6, AR5K_RF_PWD_84, AR5K_RF5111_PWD(84)},
+ {6, AR5K_RF_PWD_90, AR5K_RF5111_PWD(90)},
+ {7, AR5K_RF_GAIN_I, AR5K_RF5111_GAIN_I},
+ {7, AR5K_RF_PLO_SEL, AR5K_RF5111_PLO_SEL},
+ {7, AR5K_RF_RFGAIN_SEL, AR5K_RF5111_RFGAIN_SEL},
+ {7, AR5K_RF_RFGAIN_STEP, AR5K_RF5111_RFGAIN_STEP},
+ {7, AR5K_RF_WAIT_S, AR5K_RF5111_WAIT_S},
+ {7, AR5K_RF_WAIT_I, AR5K_RF5111_WAIT_I},
+ {7, AR5K_RF_MAX_TIME, AR5K_RF5111_MAX_TIME}
};
/* Default mode specific settings */
@@ -273,8 +315,16 @@ static const struct ath5k_rf_reg rf_regs_5112[] = {
{6, AR5K_RF_FIXED_BIAS_B, AR5K_RF5112_FIXED_BIAS_B},
{6, AR5K_RF_XPD_SEL, AR5K_RF5112_XPD_SEL},
{6, AR5K_RF_XPD_GAIN, AR5K_RF5112_XPD_GAIN},
+ {6, AR5K_RF_PWD_130, AR5K_RF5112_PWD(130)},
+ {6, AR5K_RF_PWD_131, AR5K_RF5112_PWD(131)},
+ {6, AR5K_RF_PWD_132, AR5K_RF5112_PWD(132)},
+ {6, AR5K_RF_PWD_136, AR5K_RF5112_PWD(136)},
+ {6, AR5K_RF_PWD_137, AR5K_RF5112_PWD(137)},
+ {6, AR5K_RF_PWD_138, AR5K_RF5112_PWD(138)},
{7, AR5K_RF_GAIN_I, AR5K_RF5112X_GAIN_I},
+ {7, AR5K_RF_MIXVGA_OVR, AR5K_RF5112X_MIXVGA_OVR},
{7, AR5K_RF_MIXGAIN_OVR, AR5K_RF5112X_MIXGAIN_OVR},
+ {7, AR5K_RF_MIXGAIN_STEP, AR5K_RF5112X_MIXGAIN_STEP},
{7, AR5K_RF_PD_DELAY_A, AR5K_RF5112X_PD_DELAY_A},
{7, AR5K_RF_PD_DELAY_B, AR5K_RF5112X_PD_DELAY_B},
{7, AR5K_RF_PD_DELAY_XR, AR5K_RF5112X_PD_DELAY_XR},
@@ -419,7 +469,7 @@ static const struct ath5k_ini_rfbuffer rfb_5112[] = {
#define AR5K_RF5112A_HIGH_VC_CP { 2, 90, 2 }
#define AR5K_RF5112A_MID_VC_CP { 2, 92, 2 }
#define AR5K_RF5112A_LOW_VC_CP { 2, 94, 2 }
-#define AR5K_RF5112A_PUSH_UP { 2, 94, 2 }
+#define AR5K_RF5112A_PUSH_UP { 1, 254, 2 }
/* Power consumption */
#define AR5K_RF5112A_PAD2GND { 1, 281, 1 }
@@ -436,6 +486,14 @@ static const struct ath5k_rf_reg rf_regs_5112a[] = {
{6, AR5K_RF_XPD_SEL, AR5K_RF5112A_XPD_SEL},
{6, AR5K_RF_PD_GAIN_LO, AR5K_RF5112A_PDGAINLO},
{6, AR5K_RF_PD_GAIN_HI, AR5K_RF5112A_PDGAINHI},
+ {6, AR5K_RF_PWD_130, AR5K_RF5112A_PWD(130)},
+ {6, AR5K_RF_PWD_131, AR5K_RF5112A_PWD(131)},
+ {6, AR5K_RF_PWD_132, AR5K_RF5112A_PWD(132)},
+ {6, AR5K_RF_PWD_136, AR5K_RF5112A_PWD(136)},
+ {6, AR5K_RF_PWD_137, AR5K_RF5112A_PWD(137)},
+ {6, AR5K_RF_PWD_138, AR5K_RF5112A_PWD(138)},
+ {6, AR5K_RF_PWD_166, AR5K_RF5112A_PWD(166)},
+ {6, AR5K_RF_PWD_167, AR5K_RF5112A_PWD(167)},
{6, AR5K_RF_HIGH_VC_CP, AR5K_RF5112A_HIGH_VC_CP},
{6, AR5K_RF_MID_VC_CP, AR5K_RF5112A_MID_VC_CP},
{6, AR5K_RF_LOW_VC_CP, AR5K_RF5112A_LOW_VC_CP},
@@ -444,7 +502,9 @@ static const struct ath5k_rf_reg rf_regs_5112a[] = {
{6, AR5K_RF_XB2_LVL, AR5K_RF5112A_XB2_LVL},
{6, AR5K_RF_XB5_LVL, AR5K_RF5112A_XB5_LVL},
{7, AR5K_RF_GAIN_I, AR5K_RF5112X_GAIN_I},
+ {7, AR5K_RF_MIXVGA_OVR, AR5K_RF5112X_MIXVGA_OVR},
{7, AR5K_RF_MIXGAIN_OVR, AR5K_RF5112X_MIXGAIN_OVR},
+ {7, AR5K_RF_MIXGAIN_STEP, AR5K_RF5112X_MIXGAIN_STEP},
{7, AR5K_RF_PD_DELAY_A, AR5K_RF5112X_PD_DELAY_A},
{7, AR5K_RF_PD_DELAY_B, AR5K_RF5112X_PD_DELAY_B},
{7, AR5K_RF_PD_DELAY_XR, AR5K_RF5112X_PD_DELAY_XR},
reply other threads:[~2009-01-31 2:28 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20090131022826.GC3342@makis \
--to=mick@madwifi-project.org \
--cc=ath5k-devel@lists.ath5k.org \
--cc=jirislaby@gmail.com \
--cc=linux-wireless@vger.kernel.org \
--cc=linville@tuxdriver.com \
--cc=mcgrof@gmail.com \
--cc=me@bobcopeland.com \
--cc=nbd@openwrt.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.