Skip to content

Commit

Permalink
ath9k_htc: Add support for power save.
Browse files Browse the repository at this point in the history
Signed-off-by: Vivek Natarajan <vnatarajan@atheros.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
  • Loading branch information
Vivek Natarajan authored and John W. Linville committed Apr 7, 2010
1 parent 53bc7aa commit bde748a
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 6 deletions.
9 changes: 9 additions & 0 deletions drivers/net/wireless/ath/ath9k/htc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 7 additions & 1 deletion drivers/net/wireless/ath/ath9k/htc_drv_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -454,13 +454,15 @@ 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,
(unsigned long)priv);
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
Expand Down Expand Up @@ -515,12 +517,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;
Expand Down
93 changes: 91 additions & 2 deletions drivers/net/wireless/ath/ath9k/htc_drv_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
Expand All @@ -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;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -1112,15 +1174,18 @@ 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);
WMI_CMD(WMI_STOP_RECV_CMDID);
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);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;

Expand All @@ -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);
}

Expand Down Expand Up @@ -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:
Expand All @@ -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;
Expand All @@ -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 ?
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
Expand All @@ -1593,13 +1680,15 @@ 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;
spin_unlock_bh(&priv->beacon_lock);
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)
Expand Down
7 changes: 6 additions & 1 deletion drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
6 changes: 4 additions & 2 deletions drivers/net/wireless/ath/ath9k/hw.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit bde748a

Please sign in to comment.