Skip to content

Commit

Permalink
wl12xx: prevent scheduling while suspending (WoW enabled)
Browse files Browse the repository at this point in the history
When WoW is enabled, the interface will stay up and the chip will
be powered on, so we have to flush/cancel any remaining work, and
prevent the irq handler from scheduling a new work until the system
is resumed.

Add 2 new flags:
* WL1271_FLAG_SUSPENDED - the system is (about to be) suspended.
* WL1271_FLAG_PENDING_WORK - there is a pending irq work which
   should be scheduled when the system is being resumed.

In order to wake-up the system while getting an irq, we initialize
the device as wakeup device, and calling pm_wakeup_event() upon
getting the interrupt (while the system is about to be suspended)

Signed-off-by: Eliad Peller <eliad@wizery.com>
Signed-off-by: Luciano Coelho <coelho@ti.com>
  • Loading branch information
Eliad Peller authored and Luciano Coelho committed May 13, 2011
1 parent 039bdb1 commit f44e586
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 0 deletions.
46 changes: 46 additions & 0 deletions drivers/net/wireless/wl12xx/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,28 @@ static int wl1271_op_suspend(struct ieee80211_hw *hw,
struct wl1271 *wl = hw->priv;
wl1271_debug(DEBUG_MAC80211, "mac80211 suspend wow=%d", !!wow);
wl->wow_enabled = !!wow;
if (wl->wow_enabled) {
/* flush any remaining work */
wl1271_debug(DEBUG_MAC80211, "flushing remaining works");
flush_delayed_work(&wl->scan_complete_work);

/*
* disable and re-enable interrupts in order to flush
* the threaded_irq
*/
wl1271_disable_interrupts(wl);

/*
* set suspended flag to avoid triggering a new threaded_irq
* work. no need for spinlock as interrupts are disabled.
*/
set_bit(WL1271_FLAG_SUSPENDED, &wl->flags);

wl1271_enable_interrupts(wl);
flush_work(&wl->tx_work);
flush_delayed_work(&wl->pspoll_work);
flush_delayed_work(&wl->elp_work);
}
return 0;
}

Expand All @@ -1364,6 +1386,30 @@ static int wl1271_op_resume(struct ieee80211_hw *hw)
struct wl1271 *wl = hw->priv;
wl1271_debug(DEBUG_MAC80211, "mac80211 resume wow=%d",
wl->wow_enabled);

/*
* re-enable irq_work enqueuing, and call irq_work directly if
* there is a pending work.
*/
if (wl->wow_enabled) {
struct wl1271 *wl = hw->priv;
unsigned long flags;
bool run_irq_work = false;

spin_lock_irqsave(&wl->wl_lock, flags);
clear_bit(WL1271_FLAG_SUSPENDED, &wl->flags);
if (test_and_clear_bit(WL1271_FLAG_PENDING_WORK, &wl->flags))
run_irq_work = true;
spin_unlock_irqrestore(&wl->wl_lock, flags);

if (run_irq_work) {
wl1271_debug(DEBUG_MAC80211,
"run postponed irq_work directly");
wl1271_irq(0, wl);
wl1271_enable_interrupts(wl);
}
}

return 0;
}

Expand Down
24 changes: 24 additions & 0 deletions drivers/net/wireless/wl12xx/sdio.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ static irqreturn_t wl1271_hardirq(int irq, void *cookie)
complete(wl->elp_compl);
wl->elp_compl = NULL;
}

if (test_bit(WL1271_FLAG_SUSPENDED, &wl->flags)) {
/* don't enqueue a work right now. mark it as pending */
set_bit(WL1271_FLAG_PENDING_WORK, &wl->flags);
wl1271_debug(DEBUG_IRQ, "should not enqueue work");
disable_irq_nosync(wl->irq);
pm_wakeup_event(wl1271_sdio_wl_to_dev(wl), 0);
spin_unlock_irqrestore(&wl->wl_lock, flags);
return IRQ_HANDLED;
}
spin_unlock_irqrestore(&wl->wl_lock, flags);

return IRQ_WAKE_THREAD;
Expand Down Expand Up @@ -268,6 +278,7 @@ static int __devinit wl1271_probe(struct sdio_func *func,
}

enable_irq_wake(wl->irq);
device_init_wakeup(wl1271_sdio_wl_to_dev(wl), 1);

disable_irq(wl->irq);

Expand Down Expand Up @@ -305,6 +316,7 @@ static void __devexit wl1271_remove(struct sdio_func *func)
pm_runtime_get_noresume(&func->dev);

wl1271_unregister_hw(wl);
device_init_wakeup(wl1271_sdio_wl_to_dev(wl), 0);
disable_irq_wake(wl->irq);
free_irq(wl->irq, wl);
wl1271_free_hw(wl);
Expand Down Expand Up @@ -339,13 +351,25 @@ static int wl1271_suspend(struct device *dev)
wl1271_error("error while trying to keep power");
goto out;
}

/* release host */
sdio_release_host(func);
}
out:
return ret;
}

static int wl1271_resume(struct device *dev)
{
struct sdio_func *func = dev_to_sdio_func(dev);
struct wl1271 *wl = sdio_get_drvdata(func);

wl1271_debug(DEBUG_MAC80211, "wl1271 resume");
if (wl->wow_enabled) {
/* claim back host */
sdio_claim_host(func);
}

return 0;
}

Expand Down
2 changes: 2 additions & 0 deletions drivers/net/wireless/wl12xx/wl12xx.h
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@ enum wl12xx_flags {
WL1271_FLAG_AP_STARTED,
WL1271_FLAG_IF_INITIALIZED,
WL1271_FLAG_DUMMY_PACKET_PENDING,
WL1271_FLAG_SUSPENDED,
WL1271_FLAG_PENDING_WORK,
};

struct wl1271_link {
Expand Down

0 comments on commit f44e586

Please sign in to comment.