Commit 9e33a355 authored by Helmut Schaa's avatar Helmut Schaa Committed by John W. Linville
Browse files

rt2x00: Implement tx power temperature compensation



rt2800 devices should adjust their tx power in accordance with the
eeproms temperature calibration values. Add a new driver callback
gain_calibration that is called every 4 seconds.

The rt2800 gain calibration routine simply runs the tx power
configuration that takes care of calculating the temperature
compensation delta.

We don't need to synchronize the calls to rt2800_config_txpower
as they should all happen from mac80211's single threaded workqueue.
Signed-off-by: default avatarHelmut Schaa <helmut.schaa@googlemail.com>
Signed-off-by: default avatarIvo van Doorn <IvDoorn@gmail.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 2f2bb7e8
......@@ -2103,6 +2103,59 @@ struct mac_iveiv_entry {
#define EEPROM_TXPOWER_BG_1 FIELD16(0x00ff)
#define EEPROM_TXPOWER_BG_2 FIELD16(0xff00)
/*
* EEPROM temperature compensation boundaries 802.11BG
* MINUS4: If the actual TSSI is below this boundary, tx power needs to be
* reduced by (agc_step * -4)
* MINUS3: If the actual TSSI is below this boundary, tx power needs to be
* reduced by (agc_step * -3)
*/
#define EEPROM_TSSI_BOUND_BG1 0x0037
#define EEPROM_TSSI_BOUND_BG1_MINUS4 FIELD16(0x00ff)
#define EEPROM_TSSI_BOUND_BG1_MINUS3 FIELD16(0xff00)
/*
* EEPROM temperature compensation boundaries 802.11BG
* MINUS2: If the actual TSSI is below this boundary, tx power needs to be
* reduced by (agc_step * -2)
* MINUS1: If the actual TSSI is below this boundary, tx power needs to be
* reduced by (agc_step * -1)
*/
#define EEPROM_TSSI_BOUND_BG2 0x0038
#define EEPROM_TSSI_BOUND_BG2_MINUS2 FIELD16(0x00ff)
#define EEPROM_TSSI_BOUND_BG2_MINUS1 FIELD16(0xff00)
/*
* EEPROM temperature compensation boundaries 802.11BG
* REF: Reference TSSI value, no tx power changes needed
* PLUS1: If the actual TSSI is above this boundary, tx power needs to be
* increased by (agc_step * 1)
*/
#define EEPROM_TSSI_BOUND_BG3 0x0039
#define EEPROM_TSSI_BOUND_BG3_REF FIELD16(0x00ff)
#define EEPROM_TSSI_BOUND_BG3_PLUS1 FIELD16(0xff00)
/*
* EEPROM temperature compensation boundaries 802.11BG
* PLUS2: If the actual TSSI is above this boundary, tx power needs to be
* increased by (agc_step * 2)
* PLUS3: If the actual TSSI is above this boundary, tx power needs to be
* increased by (agc_step * 3)
*/
#define EEPROM_TSSI_BOUND_BG4 0x003a
#define EEPROM_TSSI_BOUND_BG4_PLUS2 FIELD16(0x00ff)
#define EEPROM_TSSI_BOUND_BG4_PLUS3 FIELD16(0xff00)
/*
* EEPROM temperature compensation boundaries 802.11BG
* PLUS4: If the actual TSSI is above this boundary, tx power needs to be
* increased by (agc_step * 4)
* AGC_STEP: Temperature compensation step.
*/
#define EEPROM_TSSI_BOUND_BG5 0x003b
#define EEPROM_TSSI_BOUND_BG5_PLUS4 FIELD16(0x00ff)
#define EEPROM_TSSI_BOUND_BG5_AGC_STEP FIELD16(0xff00)
/*
* EEPROM TXPOWER 802.11A
*/
......@@ -2112,6 +2165,59 @@ struct mac_iveiv_entry {
#define EEPROM_TXPOWER_A_1 FIELD16(0x00ff)
#define EEPROM_TXPOWER_A_2 FIELD16(0xff00)
/*
* EEPROM temperature compensation boundaries 802.11A
* MINUS4: If the actual TSSI is below this boundary, tx power needs to be
* reduced by (agc_step * -4)
* MINUS3: If the actual TSSI is below this boundary, tx power needs to be
* reduced by (agc_step * -3)
*/
#define EEPROM_TSSI_BOUND_A1 0x006a
#define EEPROM_TSSI_BOUND_A1_MINUS4 FIELD16(0x00ff)
#define EEPROM_TSSI_BOUND_A1_MINUS3 FIELD16(0xff00)
/*
* EEPROM temperature compensation boundaries 802.11A
* MINUS2: If the actual TSSI is below this boundary, tx power needs to be
* reduced by (agc_step * -2)
* MINUS1: If the actual TSSI is below this boundary, tx power needs to be
* reduced by (agc_step * -1)
*/
#define EEPROM_TSSI_BOUND_A2 0x006b
#define EEPROM_TSSI_BOUND_A2_MINUS2 FIELD16(0x00ff)
#define EEPROM_TSSI_BOUND_A2_MINUS1 FIELD16(0xff00)
/*
* EEPROM temperature compensation boundaries 802.11A
* REF: Reference TSSI value, no tx power changes needed
* PLUS1: If the actual TSSI is above this boundary, tx power needs to be
* increased by (agc_step * 1)
*/
#define EEPROM_TSSI_BOUND_A3 0x006c
#define EEPROM_TSSI_BOUND_A3_REF FIELD16(0x00ff)
#define EEPROM_TSSI_BOUND_A3_PLUS1 FIELD16(0xff00)
/*
* EEPROM temperature compensation boundaries 802.11A
* PLUS2: If the actual TSSI is above this boundary, tx power needs to be
* increased by (agc_step * 2)
* PLUS3: If the actual TSSI is above this boundary, tx power needs to be
* increased by (agc_step * 3)
*/
#define EEPROM_TSSI_BOUND_A4 0x006d
#define EEPROM_TSSI_BOUND_A4_PLUS2 FIELD16(0x00ff)
#define EEPROM_TSSI_BOUND_A4_PLUS3 FIELD16(0xff00)
/*
* EEPROM temperature compensation boundaries 802.11A
* PLUS4: If the actual TSSI is above this boundary, tx power needs to be
* increased by (agc_step * 4)
* AGC_STEP: Temperature compensation step.
*/
#define EEPROM_TSSI_BOUND_A5 0x006e
#define EEPROM_TSSI_BOUND_A5_PLUS4 FIELD16(0x00ff)
#define EEPROM_TSSI_BOUND_A5_AGC_STEP FIELD16(0xff00)
/*
* EEPROM TXPOWER by rate: tx power per tx rate for HT20 mode
*/
......
......@@ -1813,6 +1813,116 @@ static void rt2800_config_channel(struct rt2x00_dev *rt2x00dev,
rt2800_register_read(rt2x00dev, CH_BUSY_STA_SEC, &reg);
}
static int rt2800_get_gain_calibration_delta(struct rt2x00_dev *rt2x00dev)
{
u8 tssi_bounds[9];
u8 current_tssi;
u16 eeprom;
u8 step;
int i;
/*
* Read TSSI boundaries for temperature compensation from
* the EEPROM.
*
* Array idx 0 1 2 3 4 5 6 7 8
* Matching Delta value -4 -3 -2 -1 0 +1 +2 +3 +4
* Example TSSI bounds 0xF0 0xD0 0xB5 0xA0 0x88 0x45 0x25 0x15 0x00
*/
if (rt2x00dev->curr_band == IEEE80211_BAND_2GHZ) {
rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_BG1, &eeprom);
tssi_bounds[0] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_BG1_MINUS4);
tssi_bounds[1] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_BG1_MINUS3);
rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_BG2, &eeprom);
tssi_bounds[2] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_BG2_MINUS2);
tssi_bounds[3] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_BG2_MINUS1);
rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_BG3, &eeprom);
tssi_bounds[4] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_BG3_REF);
tssi_bounds[5] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_BG3_PLUS1);
rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_BG4, &eeprom);
tssi_bounds[6] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_BG4_PLUS2);
tssi_bounds[7] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_BG4_PLUS3);
rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_BG5, &eeprom);
tssi_bounds[8] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_BG5_PLUS4);
step = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_BG5_AGC_STEP);
} else {
rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_A1, &eeprom);
tssi_bounds[0] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_A1_MINUS4);
tssi_bounds[1] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_A1_MINUS3);
rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_A2, &eeprom);
tssi_bounds[2] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_A2_MINUS2);
tssi_bounds[3] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_A2_MINUS1);
rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_A3, &eeprom);
tssi_bounds[4] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_A3_REF);
tssi_bounds[5] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_A3_PLUS1);
rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_A4, &eeprom);
tssi_bounds[6] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_A4_PLUS2);
tssi_bounds[7] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_A4_PLUS3);
rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_A5, &eeprom);
tssi_bounds[8] = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_A5_PLUS4);
step = rt2x00_get_field16(eeprom,
EEPROM_TSSI_BOUND_A5_AGC_STEP);
}
/*
* Check if temperature compensation is supported.
*/
if (tssi_bounds[4] == 0xff)
return 0;
/*
* Read current TSSI (BBP 49).
*/
rt2800_bbp_read(rt2x00dev, 49, &current_tssi);
/*
* Compare TSSI value (BBP49) with the compensation boundaries
* from the EEPROM and increase or decrease tx power.
*/
for (i = 0; i <= 3; i++) {
if (current_tssi > tssi_bounds[i])
break;
}
if (i == 4) {
for (i = 8; i >= 5; i--) {
if (current_tssi < tssi_bounds[i])
break;
}
}
return (i - 4) * step;
}
static int rt2800_get_txpower_bw_comp(struct rt2x00_dev *rt2x00dev,
enum ieee80211_band band)
{
......@@ -1904,7 +2014,8 @@ static u8 rt2800_compensate_txpower(struct rt2x00_dev *rt2x00dev, int is_rate_b,
}
static void rt2800_config_txpower(struct rt2x00_dev *rt2x00dev,
struct ieee80211_conf *conf)
enum ieee80211_band band,
int power_level)
{
u8 txpower;
u16 eeprom;
......@@ -1912,8 +2023,6 @@ static void rt2800_config_txpower(struct rt2x00_dev *rt2x00dev,
u32 reg;
u8 r1;
u32 offset;
enum ieee80211_band band = conf->channel->band;
int power_level = conf->power_level;
int delta;
/*
......@@ -1921,6 +2030,11 @@ static void rt2800_config_txpower(struct rt2x00_dev *rt2x00dev,
*/
delta = rt2800_get_txpower_bw_comp(rt2x00dev, band);
/*
* calculate temperature compensation delta
*/
delta += rt2800_get_gain_calibration_delta(rt2x00dev);
/*
* set to normal bbp tx power control mode: +/- 0dBm
*/
......@@ -2041,6 +2155,13 @@ static void rt2800_config_txpower(struct rt2x00_dev *rt2x00dev,
}
}
void rt2800_gain_calibration(struct rt2x00_dev *rt2x00dev)
{
rt2800_config_txpower(rt2x00dev, rt2x00dev->curr_band,
rt2x00dev->tx_power);
}
EXPORT_SYMBOL_GPL(rt2800_gain_calibration);
static void rt2800_config_retry_limit(struct rt2x00_dev *rt2x00dev,
struct rt2x00lib_conf *libconf)
{
......@@ -2094,10 +2215,12 @@ void rt2800_config(struct rt2x00_dev *rt2x00dev,
if (flags & IEEE80211_CONF_CHANGE_CHANNEL) {
rt2800_config_channel(rt2x00dev, libconf->conf,
&libconf->rf, &libconf->channel);
rt2800_config_txpower(rt2x00dev, libconf->conf);
rt2800_config_txpower(rt2x00dev, libconf->conf->channel->band,
libconf->conf->power_level);
}
if (flags & IEEE80211_CONF_CHANGE_POWER)
rt2800_config_txpower(rt2x00dev, libconf->conf);
rt2800_config_txpower(rt2x00dev, libconf->conf->channel->band,
libconf->conf->power_level);
if (flags & IEEE80211_CONF_CHANGE_RETRY_LIMITS)
rt2800_config_retry_limit(rt2x00dev, libconf);
if (flags & IEEE80211_CONF_CHANGE_PS)
......
......@@ -181,6 +181,7 @@ void rt2800_link_stats(struct rt2x00_dev *rt2x00dev, struct link_qual *qual);
void rt2800_reset_tuner(struct rt2x00_dev *rt2x00dev, struct link_qual *qual);
void rt2800_link_tuner(struct rt2x00_dev *rt2x00dev, struct link_qual *qual,
const u32 count);
void rt2800_gain_calibration(struct rt2x00_dev *rt2x00dev);
int rt2800_enable_radio(struct rt2x00_dev *rt2x00dev);
void rt2800_disable_radio(struct rt2x00_dev *rt2x00dev);
......
......@@ -1053,6 +1053,7 @@ static const struct rt2x00lib_ops rt2800pci_rt2x00_ops = {
.link_stats = rt2800_link_stats,
.reset_tuner = rt2800_reset_tuner,
.link_tuner = rt2800_link_tuner,
.gain_calibration = rt2800_gain_calibration,
.start_queue = rt2800pci_start_queue,
.kick_queue = rt2800pci_kick_queue,
.stop_queue = rt2800pci_stop_queue,
......
......@@ -629,6 +629,7 @@ static const struct rt2x00lib_ops rt2800usb_rt2x00_ops = {
.link_stats = rt2800_link_stats,
.reset_tuner = rt2800_reset_tuner,
.link_tuner = rt2800_link_tuner,
.gain_calibration = rt2800_gain_calibration,
.watchdog = rt2800usb_watchdog,
.start_queue = rt2800usb_start_queue,
.kick_queue = rt2x00usb_kick_queue,
......
......@@ -348,6 +348,11 @@ struct link {
* to bring the device/driver back into the desired state.
*/
struct delayed_work watchdog_work;
/*
* Work structure for scheduling periodic AGC adjustments.
*/
struct delayed_work agc_work;
};
enum rt2x00_delayed_flags {
......@@ -556,6 +561,7 @@ struct rt2x00lib_ops {
struct link_qual *qual);
void (*link_tuner) (struct rt2x00_dev *rt2x00dev,
struct link_qual *qual, const u32 count);
void (*gain_calibration) (struct rt2x00_dev *rt2x00dev);
/*
* Data queue handlers.
......
......@@ -71,6 +71,7 @@ int rt2x00lib_enable_radio(struct rt2x00_dev *rt2x00dev)
*/
rt2x00queue_start_queues(rt2x00dev);
rt2x00link_start_tuner(rt2x00dev);
rt2x00link_start_agc(rt2x00dev);
/*
* Start watchdog monitoring.
......@@ -93,6 +94,7 @@ void rt2x00lib_disable_radio(struct rt2x00_dev *rt2x00dev)
/*
* Stop all queues
*/
rt2x00link_stop_agc(rt2x00dev);
rt2x00link_stop_tuner(rt2x00dev);
rt2x00queue_stop_queues(rt2x00dev);
rt2x00queue_flush_queues(rt2x00dev, true);
......
......@@ -32,6 +32,7 @@
*/
#define WATCHDOG_INTERVAL round_jiffies_relative(HZ)
#define LINK_TUNE_INTERVAL round_jiffies_relative(HZ)
#define AGC_INTERVAL round_jiffies_relative(4 * HZ)
/*
* rt2x00_rate: Per rate device information
......@@ -270,6 +271,18 @@ void rt2x00link_start_watchdog(struct rt2x00_dev *rt2x00dev);
*/
void rt2x00link_stop_watchdog(struct rt2x00_dev *rt2x00dev);
/**
* rt2x00link_start_agc - Start periodic gain calibration
* @rt2x00dev: Pointer to &struct rt2x00_dev.
*/
void rt2x00link_start_agc(struct rt2x00_dev *rt2x00dev);
/**
* rt2x00link_stop_agc - Stop periodic gain calibration
* @rt2x00dev: Pointer to &struct rt2x00_dev.
*/
void rt2x00link_stop_agc(struct rt2x00_dev *rt2x00dev);
/**
* rt2x00link_register - Initialize link tuning & watchdog functionality
* @rt2x00dev: Pointer to &struct rt2x00_dev.
......
......@@ -446,8 +446,46 @@ static void rt2x00link_watchdog(struct work_struct *work)
WATCHDOG_INTERVAL);
}
void rt2x00link_start_agc(struct rt2x00_dev *rt2x00dev)
{
struct link *link = &rt2x00dev->link;
if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags) &&
rt2x00dev->ops->lib->gain_calibration)
ieee80211_queue_delayed_work(rt2x00dev->hw,
&link->agc_work,
AGC_INTERVAL);
}
void rt2x00link_stop_agc(struct rt2x00_dev *rt2x00dev)
{
cancel_delayed_work_sync(&rt2x00dev->link.agc_work);
}
static void rt2x00link_agc(struct work_struct *work)
{
struct rt2x00_dev *rt2x00dev =
container_of(work, struct rt2x00_dev, link.agc_work.work);
struct link *link = &rt2x00dev->link;
/*
* When the radio is shutting down we should
* immediately cease the watchdog monitoring.
*/
if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
return;
rt2x00dev->ops->lib->gain_calibration(rt2x00dev);
if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags))
ieee80211_queue_delayed_work(rt2x00dev->hw,
&link->agc_work,
AGC_INTERVAL);
}
void rt2x00link_register(struct rt2x00_dev *rt2x00dev)
{
INIT_DELAYED_WORK(&rt2x00dev->link.agc_work, rt2x00link_agc);
INIT_DELAYED_WORK(&rt2x00dev->link.watchdog_work, rt2x00link_watchdog);
INIT_DELAYED_WORK(&rt2x00dev->link.work, rt2x00link_tuner);
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment