From 57637b7f8a7b00798e6fa5eedcf7e1ef86ae8be6 Mon Sep 17 00:00:00 2001 From: Dmitry Tarnyagin Date: Tue, 16 Aug 2011 10:01:18 +0200 Subject: cw1200: Cancel suspend on late interrupts. * Code added to detect late (after suspend) interrupts and cancel suspend in that case. Applicable only for GPIO IRQs. Similar modification is required in the MMC/SDIO driver to handle late SDIO IRQs properly. * Code added to hold wakelock for 1 sec. when frame is sent from the driver up to userspace. Change-Id: Ia739c243164a6d35602dc9f634d3990214560eb9 Signed-off-by: Dmitry Tarnyagin Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28850 Reviewed-by: Bartosz MARKOWSKI Tested-by: Bartosz MARKOWSKI Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/29061 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33507 Reviewed-by: Philippe LANGLAIS --- drivers/staging/cw1200/cw1200.h | 4 +- drivers/staging/cw1200/main.c | 2 + drivers/staging/cw1200/pm.c | 119 +++++++++++++++++++++++++++++++++++++++- drivers/staging/cw1200/pm.h | 27 ++++++++- drivers/staging/cw1200/txrx.c | 10 ++++ 5 files changed, 156 insertions(+), 6 deletions(-) diff --git a/drivers/staging/cw1200/cw1200.h b/drivers/staging/cw1200/cw1200.h index 9bb68541035..80eb3c49f81 100644 --- a/drivers/staging/cw1200/cw1200.h +++ b/drivers/staging/cw1200/cw1200.h @@ -29,11 +29,11 @@ #include "scan.h" #include "txrx.h" #include "ht.h" +#include "pm.h" /* extern */ struct sbus_ops; /* extern */ struct task_struct; /* extern */ struct cw1200_debug_priv; -/* extern */ struct cw1200_suspend_state; /* extern */ struct firmware; #if defined(CONFIG_CW1200_TXRX_DEBUG) @@ -116,7 +116,7 @@ struct cw1200_common { struct wsm_beacon_filter_control bf_control; u8 ba_tid_mask; struct wsm_multicast_filter multicast_filter; - struct cw1200_suspend_state *suspend_state; + struct cw1200_pm_state pm_state; struct wsm_p2p_ps_modeinfo p2p_ps_modeinfo; /* BH */ diff --git a/drivers/staging/cw1200/main.c b/drivers/staging/cw1200/main.c index cddb9943ee6..8ba8629cb03 100644 --- a/drivers/staging/cw1200/main.c +++ b/drivers/staging/cw1200/main.c @@ -349,6 +349,7 @@ struct ieee80211_hw *cw1200_init_common(size_t priv_data_len) init_waitqueue_head(&priv->wsm_startup_done); wsm_buf_init(&priv->wsm_cmd_buf); tx_policy_init(priv); + cw1200_pm_init(&priv->pm_state, priv); return hw; } @@ -429,6 +430,7 @@ void cw1200_unregister_common(struct ieee80211_hw *dev) for (i = 0; i < 4; ++i) cw1200_queue_deinit(&priv->tx_queue[i]); cw1200_queue_stats_deinit(&priv->tx_queue_stats); + cw1200_pm_deinit(&priv->pm_state); } EXPORT_SYMBOL_GPL(cw1200_unregister_common); diff --git a/drivers/staging/cw1200/pm.c b/drivers/staging/cw1200/pm.c index 72f11e09d50..e27c4144f51 100644 --- a/drivers/staging/cw1200/pm.c +++ b/drivers/staging/cw1200/pm.c @@ -9,12 +9,15 @@ * published by the Free Software Foundation. */ +#include #include "cw1200.h" #include "pm.h" #include "sta.h" #include "bh.h" #include "sbus.h" +static int cw1200_suspend_late(struct device *dev); + /* private */ struct cw1200_suspend_state { unsigned long bss_loss_tmo; @@ -22,6 +25,94 @@ struct cw1200_suspend_state { unsigned long join_tmo; }; +static struct dev_pm_ops cw1200_pm_ops = { + .suspend_noirq = cw1200_suspend_late, +}; +static struct platform_driver cw1200_power_driver = { + .driver.name = "cw1200_power", + .driver.pm = &cw1200_pm_ops, +}; +static struct platform_device cw1200_power_device = { + .name = "cw1200_power", +}; + +static void cw1200_pm_init_common(struct cw1200_pm_state *pm, + struct cw1200_common *priv) +{ + spin_lock_init(&pm->lock); + cw1200_power_device.dev.platform_data = priv; + platform_device_register(&cw1200_power_device); + platform_driver_register(&cw1200_power_driver); +} + +static void cw1200_pm_deinit_common(struct cw1200_pm_state *pm) +{ + platform_driver_unregister(&cw1200_power_driver); + platform_device_unregister(&cw1200_power_device); +} + +#ifdef CONFIG_WAKELOCK + +void cw1200_pm_init(struct cw1200_pm_state *pm, + struct cw1200_common *priv) +{ + cw1200_pm_init_common(pm, priv); + wake_lock_init(&pm->wakelock, + WAKE_LOCK_SUSPEND, "cw1200_wlan"); +} + +void cw1200_pm_deinit(struct cw1200_pm_state *pm) +{ + if (wake_lock_active(&pm->wakelock)) + wake_unlock(&pm->wakelock); + wake_lock_destroy(&pm->wakelock); + cw1200_pm_deinit_common(pm); +} +void cw1200_pm_stay_awake(struct cw1200_pm_state *pm, + unsigned long tmo) +{ + long cur_tmo; + spin_lock_bh(&pm->lock); + cur_tmo = pm->wakelock.expires - jiffies; + if (!wake_lock_active(&pm->wakelock) || + cur_tmo < (long)tmo) + wake_lock_timeout(&pm->wakelock, tmo); + spin_unlock_bh(&pm->lock); +} + +#else /* CONFIG_WAKELOCK */ + +static void cw1200_pm_stay_awake_tmo(unsigned long) +{ +} + +void cw1200_pm_init(struct cw1200_pm_state *pm) +{ + cw1200_init_common(pm); + init_timer(&pm->stay_awake); + pm->stay_awake.data = (unsigned long)pm; + pm->stay_awake.function = cw1200_pm_stay_awake_tmo; +} + +void cw1200_pm_deinit(struct cw1200_pm_state *pm) +{ + del_timer_sync(&pm->stay_awake); +} + +void cw1200_pm_stay_awake(struct cw1200_pm_state *pm, + unsigned long tmo) +{ + long cur_tmo; + spin_lock_bh(&pm->lock); + cur_tmo = pm->stay_awake.expires - jiffies; + if (!timer_pending(&pm->stay_awake) || + cur_tmo < (long)tmo) + mod_timer(&pm->stay_awake, jiffies + tmo); + spin_unlock_bh(&pm->lock); +} + +#endif /* CONFIG_WAKELOCK */ + static long cw1200_suspend_work(struct delayed_work *work) { int ret = cancel_delayed_work(work); @@ -47,12 +138,33 @@ static int cw1200_resume_work(struct cw1200_common *priv, return queue_delayed_work(priv->workqueue, work, tmo); } +static int cw1200_suspend_late(struct device *dev) +{ + struct cw1200_common *priv = dev->platform_data; + if (atomic_read(&priv->bh_rx)) { + wiphy_dbg(priv->hw->wiphy, + "%s: Suspend interrupted.\n", + __func__); + return -EAGAIN; + } + return 0; +} + int cw1200_wow_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) { struct cw1200_common *priv = hw->priv; + struct cw1200_pm_state *pm_state = &priv->pm_state; struct cw1200_suspend_state *state; int ret; +#ifndef CONFIG_WAKELOCK + spin_lock_bh(&pm->lock); + ret = timer_pending(&pm->stay_awake); + spin_unlock_bh(&pm->lock); + if (ret) + return -EAGAIN; +#endif + /* Ensure pending operations are done. */ ret = wait_event_interruptible_timeout( priv->channel_switch_done, @@ -89,7 +201,7 @@ int cw1200_wow_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) cw1200_bh_suspend(priv); /* Store suspend state */ - priv->suspend_state = state; + pm_state->suspend_state = state; /* Enable IRQ wake */ ret = priv->sbus_ops->power_mgmt(priv->sbus_priv, true); @@ -113,10 +225,11 @@ int cw1200_wow_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) int cw1200_wow_resume(struct ieee80211_hw *hw) { struct cw1200_common *priv = hw->priv; + struct cw1200_pm_state *pm_state = &priv->pm_state; struct cw1200_suspend_state *state; - state = priv->suspend_state; - priv->suspend_state = NULL; + state = pm_state->suspend_state; + pm_state->suspend_state = NULL; /* Disable IRQ wake */ priv->sbus_ops->power_mgmt(priv->sbus_priv, false); diff --git a/drivers/staging/cw1200/pm.h b/drivers/staging/cw1200/pm.h index 841b609457f..433e5ec1139 100644 --- a/drivers/staging/cw1200/pm.h +++ b/drivers/staging/cw1200/pm.h @@ -12,12 +12,37 @@ #ifndef PM_H_INCLUDED #define PM_H_INCLUDED +#ifdef CONFIG_WAKELOCK +#include +#endif + /* ******************************************************************** */ /* mac80211 API */ #ifdef CONFIG_PM -int cw1200_wow_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan); + +/* extern */ struct cw1200_common; +/* private */ struct cw1200_suspend_state; + +struct cw1200_pm_state { + struct cw1200_suspend_state *suspend_state; +#ifdef CONFIG_WAKELOCK + struct wake_lock wakelock; +#else + struct timer_list stay_awake; +#endif + spinlock_t lock; +}; + +void cw1200_pm_init(struct cw1200_pm_state *pm, + struct cw1200_common *priv); +void cw1200_pm_deinit(struct cw1200_pm_state *pm); +void cw1200_pm_stay_awake(struct cw1200_pm_state *pm, + unsigned long tmo); +int cw1200_wow_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wowlan); int cw1200_wow_resume(struct ieee80211_hw *hw); + #endif /* CONFIG_PM */ #endif diff --git a/drivers/staging/cw1200/txrx.c b/drivers/staging/cw1200/txrx.c index 5201ffe65ab..18544011b16 100644 --- a/drivers/staging/cw1200/txrx.c +++ b/drivers/staging/cw1200/txrx.c @@ -719,6 +719,7 @@ void cw1200_rx_cb(struct cw1200_common *priv, { struct sk_buff *skb = *skb_p; struct ieee80211_rx_status *hdr = IEEE80211_SKB_RXCB(skb); + unsigned long grace_period; __le16 frame_control; hdr->flag = 0; @@ -825,6 +826,15 @@ void cw1200_rx_cb(struct cw1200_common *priv, if (cw1200_handle_action_rx(priv, skb)) return; + /* Stay awake for 1sec. after frame is received to give + * userspace chance to react and acquire appropriate + * wakelock. */ + if (ieee80211_is_auth(frame_control)) + grace_period = 5 * HZ; + else + grace_period = 1 * HZ; + cw1200_pm_stay_awake(&priv->pm_state, grace_period); + /* Not that we really need _irqsafe variant here, * but it offloads realtime bh thread and improve * system performance. */ -- cgit v1.2.3