summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Tarnyagin <dmitry.tarnyagin@stericsson.com>2011-08-16 10:01:18 +0200
committerPhilippe LANGLAIS <philippe.langlais@stericsson.com>2011-10-13 10:02:34 +0200
commit57637b7f8a7b00798e6fa5eedcf7e1ef86ae8be6 (patch)
treed995f3050985f7871c7528020f56dee0426e830c
parent0957a526f0ed442372ea7d4da8f0651a07c52e25 (diff)
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 <dmitry.tarnyagin@stericsson.com> Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28850 Reviewed-by: Bartosz MARKOWSKI <bartosz.markowski@tieto.com> Tested-by: Bartosz MARKOWSKI <bartosz.markowski@tieto.com> Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/29061 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33507 Reviewed-by: Philippe LANGLAIS <philippe.langlais@stericsson.com>
-rw-r--r--drivers/staging/cw1200/cw1200.h4
-rw-r--r--drivers/staging/cw1200/main.c2
-rw-r--r--drivers/staging/cw1200/pm.c119
-rw-r--r--drivers/staging/cw1200/pm.h27
-rw-r--r--drivers/staging/cw1200/txrx.c10
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 <linux/platform_device.h>
#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 <linux/wakelock.h>
+#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. */