diff mbox

[09/13] rt2x00: Implement tx power temperature compensation

Message ID 201103281333.41794.IvDoorn@gmail.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Ivo van Doorn March 28, 2011, 11:33 a.m. UTC
None
diff mbox

Patch

diff --git a/drivers/net/wireless/rt2x00/rt2800.h b/drivers/net/wireless/rt2x00/rt2800.h
index 70b9abb..3b3d851 100644
--- a/drivers/net/wireless/rt2x00/rt2800.h
+++ b/drivers/net/wireless/rt2x00/rt2800.h
@@ -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
diff --git a/drivers/net/wireless/rt2x00/rt2800lib.c b/drivers/net/wireless/rt2x00/rt2800lib.c
index 0265133..59302fa 100644
--- a/drivers/net/wireless/rt2x00/rt2800lib.c
+++ b/drivers/net/wireless/rt2x00/rt2800lib.c
@@ -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)
diff --git a/drivers/net/wireless/rt2x00/rt2800lib.h b/drivers/net/wireless/rt2x00/rt2800lib.h
index 0c92d86..f2d1594 100644
--- a/drivers/net/wireless/rt2x00/rt2800lib.h
+++ b/drivers/net/wireless/rt2x00/rt2800lib.h
@@ -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);
diff --git a/drivers/net/wireless/rt2x00/rt2800pci.c b/drivers/net/wireless/rt2x00/rt2800pci.c
index d305514..adc3534 100644
--- a/drivers/net/wireless/rt2x00/rt2800pci.c
+++ b/drivers/net/wireless/rt2x00/rt2800pci.c
@@ -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,
diff --git a/drivers/net/wireless/rt2x00/rt2800usb.c b/drivers/net/wireless/rt2x00/rt2800usb.c
index f7737ca..1ff8b57 100644
--- a/drivers/net/wireless/rt2x00/rt2800usb.c
+++ b/drivers/net/wireless/rt2x00/rt2800usb.c
@@ -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,
diff --git a/drivers/net/wireless/rt2x00/rt2x00.h b/drivers/net/wireless/rt2x00/rt2x00.h
index 60b1cb0..dd0f66a 100644
--- a/drivers/net/wireless/rt2x00/rt2x00.h
+++ b/drivers/net/wireless/rt2x00/rt2x00.h
@@ -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.
diff --git a/drivers/net/wireless/rt2x00/rt2x00dev.c b/drivers/net/wireless/rt2x00/rt2x00dev.c
index 55c1d03..a98c434 100644
--- a/drivers/net/wireless/rt2x00/rt2x00dev.c
+++ b/drivers/net/wireless/rt2x00/rt2x00dev.c
@@ -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);
diff --git a/drivers/net/wireless/rt2x00/rt2x00lib.h b/drivers/net/wireless/rt2x00/rt2x00lib.h
index 63c40d4..88f2f92 100644
--- a/drivers/net/wireless/rt2x00/rt2x00lib.h
+++ b/drivers/net/wireless/rt2x00/rt2x00lib.h
@@ -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.
  *
diff --git a/drivers/net/wireless/rt2x00/rt2x00link.c b/drivers/net/wireless/rt2x00/rt2x00link.c
index fc8cee9..128b361 100644
--- a/drivers/net/wireless/rt2x00/rt2x00link.c
+++ b/drivers/net/wireless/rt2x00/rt2x00link.c
@@ -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);
 }