rt2x00: Implement tx power temperature compensation
authorHelmut Schaa <helmut.schaa@googlemail.com>
Mon, 28 Mar 2011 11:33:40 +0000 (13:33 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Mon, 4 Apr 2011 20:20:03 +0000 (16:20 -0400)
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: Helmut Schaa <helmut.schaa@googlemail.com>
Signed-off-by: Ivo van Doorn <IvDoorn@gmail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/rt2x00/rt2800.h
drivers/net/wireless/rt2x00/rt2800lib.c
drivers/net/wireless/rt2x00/rt2800lib.h
drivers/net/wireless/rt2x00/rt2800pci.c
drivers/net/wireless/rt2x00/rt2800usb.c
drivers/net/wireless/rt2x00/rt2x00.h
drivers/net/wireless/rt2x00/rt2x00dev.c
drivers/net/wireless/rt2x00/rt2x00lib.h
drivers/net/wireless/rt2x00/rt2x00link.c

index 70b9abb..3b3d851 100644 (file)
@@ -2104,6 +2104,59 @@ struct mac_iveiv_entry {
 #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
  */
 #define EEPROM_TXPOWER_A1              0x003c
@@ -2113,6 +2166,59 @@ struct mac_iveiv_entry {
 #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
  */
 #define EEPROM_TXPOWER_BYRATE          0x006f
index 0265133..59302fa 100644 (file)
@@ -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;
 
        /*
@@ -1922,6 +2031,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
         */
        rt2800_bbp_read(rt2x00dev, 1, &r1);
@@ -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)
index 0c92d86..f2d1594 100644 (file)
@@ -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);
index d305514..adc3534 100644 (file)
@@ -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,
index 1c99a4f..8b3ab3f 100644 (file)
@@ -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,
index 60b1cb0..dd0f66a 100644 (file)
@@ -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.
index 55c1d03..a98c434 100644 (file)
@@ -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);
index 63c40d4..88f2f92 100644 (file)
@@ -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
@@ -271,6 +272,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.
  *
index fc8cee9..128b361 100644 (file)
@@ -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);
 }