diff mbox

[2/3] ath9k_htc: Add support for power save.

Message ID 1270459086-2504-2-git-send-email-vnatarajan@atheros.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Vivek Natarajan April 5, 2010, 9:18 a.m. UTC
None
diff mbox

Patch

diff --git a/drivers/net/wireless/ath/ath9k/htc.h b/drivers/net/wireless/ath/ath9k/htc.h
index e09c6c2..0160e83 100644
--- a/drivers/net/wireless/ath/ath9k/htc.h
+++ b/drivers/net/wireless/ath/ath9k/htc.h
@@ -363,6 +363,11 @@  struct ath9k_htc_priv {
 	struct ath9k_htc_aggr_work aggr_work;
 	struct delayed_work ath9k_aggr_work;
 	struct delayed_work ath9k_ani_work;
+	struct work_struct ps_work;
+
+	struct mutex htc_pm_lock;
+	unsigned long ps_usecount;
+	bool ps_enabled;
 
 	struct ath_led radio_led;
 	struct ath_led assoc_led;
@@ -420,6 +425,10 @@  void ath9k_host_rx_init(struct ath9k_htc_priv *priv);
 void ath9k_rx_tasklet(unsigned long data);
 u32 ath9k_htc_calcrxfilter(struct ath9k_htc_priv *priv);
 
+void ath9k_htc_ps_wakeup(struct ath9k_htc_priv *priv);
+void ath9k_htc_ps_restore(struct ath9k_htc_priv *priv);
+void ath9k_ps_work(struct work_struct *work);
+
 void ath9k_start_rfkill_poll(struct ath9k_htc_priv *priv);
 void ath9k_init_leds(struct ath9k_htc_priv *priv);
 void ath9k_deinit_leds(struct ath9k_htc_priv *priv);
diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_init.c b/drivers/net/wireless/ath/ath9k/htc_drv_init.c
index 90cfd90..1c6c99e 100644
--- a/drivers/net/wireless/ath/ath9k/htc_drv_init.c
+++ b/drivers/net/wireless/ath/ath9k/htc_drv_init.c
@@ -453,6 +453,7 @@  static int ath9k_init_priv(struct ath9k_htc_priv *priv, u16 devid)
 	spin_lock_init(&priv->tx_lock);
 	mutex_init(&priv->mutex);
 	mutex_init(&priv->aggr_work.mutex);
+	mutex_init(&priv->htc_pm_lock);
 	tasklet_init(&priv->wmi_tasklet, ath9k_wmi_tasklet,
 		     (unsigned long)priv);
 	tasklet_init(&priv->rx_tasklet, ath9k_rx_tasklet,
@@ -460,6 +461,7 @@  static int ath9k_init_priv(struct ath9k_htc_priv *priv, u16 devid)
 	tasklet_init(&priv->tx_tasklet, ath9k_tx_tasklet, (unsigned long)priv);
 	INIT_DELAYED_WORK(&priv->ath9k_aggr_work, ath9k_htc_aggr_work);
 	INIT_DELAYED_WORK(&priv->ath9k_ani_work, ath9k_ani_work);
+	INIT_WORK(&priv->ps_work, ath9k_ps_work);
 
 	/*
 	 * Cache line size is used to size and align various
@@ -514,12 +516,16 @@  static void ath9k_set_hw_capab(struct ath9k_htc_priv *priv,
 		IEEE80211_HW_AMPDU_AGGREGATION |
 		IEEE80211_HW_SPECTRUM_MGMT |
 		IEEE80211_HW_HAS_RATE_CONTROL |
-		IEEE80211_HW_RX_INCLUDES_FCS;
+		IEEE80211_HW_RX_INCLUDES_FCS |
+		IEEE80211_HW_SUPPORTS_PS |
+		IEEE80211_HW_PS_NULLFUNC_STACK;
 
 	hw->wiphy->interface_modes =
 		BIT(NL80211_IFTYPE_STATION) |
 		BIT(NL80211_IFTYPE_ADHOC);
 
+	hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT;
+
 	hw->queues = 4;
 	hw->channel_change_time = 5000;
 	hw->max_listen_interval = 10;
diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_main.c b/drivers/net/wireless/ath/ath9k/htc_drv_main.c
index 63f032d..e04452f 100644
--- a/drivers/net/wireless/ath/ath9k/htc_drv_main.c
+++ b/drivers/net/wireless/ath/ath9k/htc_drv_main.c
@@ -65,6 +65,56 @@  static enum htc_phymode ath9k_htc_get_curmode(struct ath9k_htc_priv *priv,
 	return mode;
 }
 
+static bool ath9k_htc_setpower(struct ath9k_htc_priv *priv,
+			       enum ath9k_power_mode mode)
+{
+	bool ret;
+
+	mutex_lock(&priv->htc_pm_lock);
+	ret = ath9k_hw_setpower(priv->ah, mode);
+	mutex_unlock(&priv->htc_pm_lock);
+
+	return ret;
+}
+
+void ath9k_htc_ps_wakeup(struct ath9k_htc_priv *priv)
+{
+	mutex_lock(&priv->htc_pm_lock);
+	if (++priv->ps_usecount != 1)
+		goto unlock;
+	ath9k_hw_setpower(priv->ah, ATH9K_PM_AWAKE);
+
+unlock:
+	mutex_unlock(&priv->htc_pm_lock);
+}
+
+void ath9k_htc_ps_restore(struct ath9k_htc_priv *priv)
+{
+	mutex_lock(&priv->htc_pm_lock);
+	if (--priv->ps_usecount != 0)
+		goto unlock;
+
+	if (priv->ps_enabled)
+		ath9k_hw_setpower(priv->ah, ATH9K_PM_NETWORK_SLEEP);
+unlock:
+	mutex_unlock(&priv->htc_pm_lock);
+}
+
+void ath9k_ps_work(struct work_struct *work)
+{
+	struct ath9k_htc_priv *priv =
+		container_of(work, struct ath9k_htc_priv,
+			     ps_work);
+	ath9k_htc_setpower(priv, ATH9K_PM_AWAKE);
+
+	/* The chip wakes up after receiving the first beacon
+	   while network sleep is enabled. For the driver to
+	   be in sync with the hw, set the chip to awake and
+	   only then set it to sleep.
+	 */
+	ath9k_htc_setpower(priv, ATH9K_PM_NETWORK_SLEEP);
+}
+
 static int ath9k_htc_set_channel(struct ath9k_htc_priv *priv,
 				 struct ieee80211_hw *hw,
 				 struct ath9k_channel *hchan)
@@ -87,7 +137,7 @@  static int ath9k_htc_set_channel(struct ath9k_htc_priv *priv,
 
 	/* Fiddle around with fastcc later on, for now just use full reset */
 	fastcc = false;
-
+	ath9k_htc_ps_wakeup(priv);
 	htc_stop(priv->htc);
 	WMI_CMD(WMI_DISABLE_INTR_CMDID);
 	WMI_CMD(WMI_DRAIN_TXQ_ALL_CMDID);
@@ -103,6 +153,7 @@  static int ath9k_htc_set_channel(struct ath9k_htc_priv *priv,
 		ath_print(common, ATH_DBG_FATAL,
 			  "Unable to reset channel (%u Mhz) "
 			  "reset status %d\n", channel->center_freq, ret);
+		ath9k_htc_ps_restore(priv);
 		goto err;
 	}
 
@@ -128,6 +179,7 @@  static int ath9k_htc_set_channel(struct ath9k_htc_priv *priv,
 
 	priv->op_flags &= ~OP_FULL_RESET;
 err:
+	ath9k_htc_ps_restore(priv);
 	return ret;
 }
 
@@ -693,6 +745,10 @@  void ath9k_ani_work(struct work_struct *work)
 
 	short_cal_interval = ATH_STA_SHORT_CALINTERVAL;
 
+	/* Only calibrate if awake */
+	if (ah->power_mode != ATH9K_PM_AWAKE)
+		goto set_timer;
+
 	/* Long calibration runs independently of short calibration. */
 	if ((timestamp - common->ani.longcal_timer) >= ATH_LONG_CALINTERVAL) {
 		longcal = true;
@@ -727,6 +783,9 @@  void ath9k_ani_work(struct work_struct *work)
 
 	/* Skip all processing if there's nothing to do. */
 	if (longcal || shortcal || aniflag) {
+
+		ath9k_htc_ps_wakeup(priv);
+
 		/* Call ANI routine if necessary */
 		if (aniflag)
 			ath9k_hw_ani_monitor(ah, ah->curchan);
@@ -748,8 +807,11 @@  void ath9k_ani_work(struct work_struct *work)
 				  ah->curchan->channelFlags,
 				  common->ani.noise_floor);
 		}
+
+		ath9k_htc_ps_restore(priv);
 	}
 
+set_timer:
 	/*
 	* Set timer interval based on previous results.
 	* The interval must be the shortest necessary to satisfy ANI,
@@ -1112,6 +1174,7 @@  static void ath9k_htc_stop(struct ieee80211_hw *hw)
 		return;
 	}
 
+	ath9k_htc_ps_wakeup(priv);
 	htc_stop(priv->htc);
 	WMI_CMD(WMI_DISABLE_INTR_CMDID);
 	WMI_CMD(WMI_DRAIN_TXQ_ALL_CMDID);
@@ -1119,8 +1182,10 @@  static void ath9k_htc_stop(struct ieee80211_hw *hw)
 	ath9k_hw_phy_disable(ah);
 	ath9k_hw_disable(ah);
 	ath9k_hw_configpcipowersave(ah, 1, 1);
-	ath9k_hw_setpower(ah, ATH9K_PM_FULL_SLEEP);
+	ath9k_htc_ps_restore(priv);
+	ath9k_htc_setpower(priv, ATH9K_PM_FULL_SLEEP);
 
+	cancel_work_sync(&priv->ps_work);
 	cancel_delayed_work_sync(&priv->ath9k_ani_work);
 	cancel_delayed_work_sync(&priv->ath9k_aggr_work);
 	cancel_delayed_work_sync(&priv->ath9k_led_blink_work);
@@ -1161,6 +1226,7 @@  static int ath9k_htc_add_interface(struct ieee80211_hw *hw,
 		goto out;
 	}
 
+	ath9k_htc_ps_wakeup(priv);
 	memset(&hvif, 0, sizeof(struct ath9k_htc_target_vif));
 	memcpy(&hvif.myaddr, vif->addr, ETH_ALEN);
 
@@ -1207,6 +1273,7 @@  static int ath9k_htc_add_interface(struct ieee80211_hw *hw,
 
 	priv->vif = vif;
 out:
+	ath9k_htc_ps_restore(priv);
 	mutex_unlock(&priv->mutex);
 	return ret;
 }
@@ -1275,6 +1342,16 @@  static int ath9k_htc_config(struct ieee80211_hw *hw, u32 changed)
 		}
 
 	}
+	if (changed & IEEE80211_CONF_CHANGE_PS) {
+		if (conf->flags & IEEE80211_CONF_PS) {
+			ath9k_htc_setpower(priv, ATH9K_PM_NETWORK_SLEEP);
+			priv->ps_enabled = true;
+		} else {
+			priv->ps_enabled = false;
+			cancel_work_sync(&priv->ps_work);
+			ath9k_htc_setpower(priv, ATH9K_PM_AWAKE);
+		}
+	}
 
 	if (changed & IEEE80211_CONF_CHANGE_MONITOR) {
 		if (conf->flags & IEEE80211_CONF_MONITOR) {
@@ -1311,6 +1388,7 @@  static void ath9k_htc_configure_filter(struct ieee80211_hw *hw,
 
 	mutex_lock(&priv->mutex);
 
+	ath9k_htc_ps_wakeup(priv);
 	changed_flags &= SUPPORTED_FILTERS;
 	*total_flags &= SUPPORTED_FILTERS;
 
@@ -1321,6 +1399,7 @@  static void ath9k_htc_configure_filter(struct ieee80211_hw *hw,
 	ath_print(ath9k_hw_common(priv->ah), ATH_DBG_CONFIG,
 		  "Set HW RX filter: 0x%x\n", rfilt);
 
+	ath9k_htc_ps_restore(priv);
 	mutex_unlock(&priv->mutex);
 }
 
@@ -1398,6 +1477,7 @@  static int ath9k_htc_set_key(struct ieee80211_hw *hw,
 
 	mutex_lock(&priv->mutex);
 	ath_print(common, ATH_DBG_CONFIG, "Set HW Key\n");
+	ath9k_htc_ps_wakeup(priv);
 
 	switch (cmd) {
 	case SET_KEY:
@@ -1420,6 +1500,7 @@  static int ath9k_htc_set_key(struct ieee80211_hw *hw,
 		ret = -EINVAL;
 	}
 
+	ath9k_htc_ps_restore(priv);
 	mutex_unlock(&priv->mutex);
 
 	return ret;
@@ -1435,6 +1516,7 @@  static void ath9k_htc_bss_info_changed(struct ieee80211_hw *hw,
 	struct ath_common *common = ath9k_hw_common(ah);
 
 	mutex_lock(&priv->mutex);
+	ath9k_htc_ps_wakeup(priv);
 
 	if (changed & BSS_CHANGED_ASSOC) {
 		common->curaid = bss_conf->assoc ?
@@ -1447,6 +1529,7 @@  static void ath9k_htc_bss_info_changed(struct ieee80211_hw *hw,
 			ath_start_ani(priv);
 		} else {
 			priv->op_flags &= ~OP_ASSOCIATED;
+			cancel_work_sync(&priv->ps_work);
 			cancel_delayed_work_sync(&priv->ath9k_ani_work);
 		}
 	}
@@ -1506,6 +1589,7 @@  static void ath9k_htc_bss_info_changed(struct ieee80211_hw *hw,
 		ath9k_hw_init_global_settings(ah);
 	}
 
+	ath9k_htc_ps_restore(priv);
 	mutex_unlock(&priv->mutex);
 }
 
@@ -1534,9 +1618,11 @@  static void ath9k_htc_reset_tsf(struct ieee80211_hw *hw)
 {
 	struct ath9k_htc_priv *priv = hw->priv;
 
+	ath9k_htc_ps_wakeup(priv);
 	mutex_lock(&priv->mutex);
 	ath9k_hw_reset_tsf(priv->ah);
 	mutex_unlock(&priv->mutex);
+	ath9k_htc_ps_restore(priv);
 }
 
 static int ath9k_htc_ampdu_action(struct ieee80211_hw *hw,
@@ -1585,6 +1671,7 @@  static void ath9k_htc_sw_scan_start(struct ieee80211_hw *hw)
 	spin_lock_bh(&priv->beacon_lock);
 	priv->op_flags |= OP_SCANNING;
 	spin_unlock_bh(&priv->beacon_lock);
+	cancel_work_sync(&priv->ps_work);
 	cancel_delayed_work_sync(&priv->ath9k_ani_work);
 	mutex_unlock(&priv->mutex);
 }
@@ -1593,6 +1680,7 @@  static void ath9k_htc_sw_scan_complete(struct ieee80211_hw *hw)
 {
 	struct ath9k_htc_priv *priv = hw->priv;
 
+	ath9k_htc_ps_wakeup(priv);
 	mutex_lock(&priv->mutex);
 	spin_lock_bh(&priv->beacon_lock);
 	priv->op_flags &= ~OP_SCANNING;
@@ -1600,6 +1688,7 @@  static void ath9k_htc_sw_scan_complete(struct ieee80211_hw *hw)
 	priv->op_flags |= OP_FULL_RESET;
 	ath_start_ani(priv);
 	mutex_unlock(&priv->mutex);
+	ath9k_htc_ps_restore(priv);
 }
 
 static int ath9k_htc_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
index f1e3d83..0a7cb30 100644
--- a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
+++ b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
@@ -553,7 +553,7 @@  void ath9k_rx_tasklet(unsigned long data)
 	struct ieee80211_rx_status rx_status;
 	struct sk_buff *skb;
 	unsigned long flags;
-
+	struct ieee80211_hdr *hdr;
 
 	do {
 		spin_lock_irqsave(&priv->rx.rxbuflock, flags);
@@ -580,6 +580,11 @@  void ath9k_rx_tasklet(unsigned long data)
 		memcpy(IEEE80211_SKB_RXCB(rxbuf->skb), &rx_status,
 		       sizeof(struct ieee80211_rx_status));
 		skb = rxbuf->skb;
+		hdr = (struct ieee80211_hdr *) skb->data;
+
+		if (ieee80211_is_beacon(hdr->frame_control) && priv->ps_enabled)
+				ieee80211_queue_work(priv->hw, &priv->ps_work);
+
 		spin_unlock_irqrestore(&priv->rx.rxbuflock, flags);
 
 		ieee80211_rx(priv->hw, skb);
diff --git a/drivers/net/wireless/ath/ath9k/hw.c b/drivers/net/wireless/ath/ath9k/hw.c
index a4826d5..338a8fc 100644
--- a/drivers/net/wireless/ath/ath9k/hw.c
+++ b/drivers/net/wireless/ath/ath9k/hw.c
@@ -3245,8 +3245,10 @@  int ath9k_hw_fill_cap_info(struct ath_hw *ah)
 		pCap->hw_caps |= ATH9K_HW_CAP_RFSILENT;
 	}
 #endif
-
-	pCap->hw_caps &= ~ATH9K_HW_CAP_AUTOSLEEP;
+	if (AR_SREV_9271(ah))
+		pCap->hw_caps |= ATH9K_HW_CAP_AUTOSLEEP;
+	else
+		pCap->hw_caps &= ~ATH9K_HW_CAP_AUTOSLEEP;
 
 	if (AR_SREV_9280(ah) || AR_SREV_9285(ah))
 		pCap->hw_caps &= ~ATH9K_HW_CAP_4KB_SPLITTRANS;