diff options
author | Arend van Spriel <arend@broadcom.com> | 2015-02-06 18:36:45 +0100 |
---|---|---|
committer | Kalle Valo <kvalo@codeaurora.org> | 2015-02-26 15:14:19 +0200 |
commit | 9982464379e81ece51ced03ebecbbcd34ea367a6 (patch) | |
tree | e9b04bac5108dc61da220dbabdcfae0cd8dd729a /drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c | |
parent | c7cd5d27d848d6afb38abad1c345cd42ebfd61e4 (diff) | |
download | lwn-9982464379e81ece51ced03ebecbbcd34ea367a6.tar.gz lwn-9982464379e81ece51ced03ebecbbcd34ea367a6.zip |
brcmfmac: make sdio suspend wait for threads to freeze
Borrowed the idea of the PM freezer to make sdio suspend wait for
watchdog and DPC thread to freeze at a safe point in their thread
routine. The suspend takes 20-25 msec.
Reviewed-by: Hante Meuleman <meuleman@broadcom.com>
Reviewed-by: Daniel (Deognyoun) Kim <dekim@broadcom.com>
Reviewed-by: Franky (Zhenhui) Lin <frankyl@broadcom.com>
Reviewed-by: Pieter-Paul Giesberts <pieterpg@broadcom.com>
Signed-off-by: Arend van Spriel <arend@broadcom.com>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
Diffstat (limited to 'drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c')
-rw-r--r-- | drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c | 125 |
1 files changed, 108 insertions, 17 deletions
diff --git a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c index 1f7e2f245e9e..c438ccdb6ed8 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c @@ -58,6 +58,14 @@ #define BRCMF_DEFAULT_TXGLOM_SIZE 32 /* max tx frames in glom chain */ #define BRCMF_DEFAULT_RXGLOM_SIZE 32 /* max rx frames in glom chain */ +struct brcmf_sdiod_freezer { + atomic_t freezing; + atomic_t thread_count; + u32 frozen_count; + wait_queue_head_t thread_freeze; + struct completion resumed; +}; + static int brcmf_sdiod_txglomsz = BRCMF_DEFAULT_TXGLOM_SIZE; module_param_named(txglomsz, brcmf_sdiod_txglomsz, int, 0); MODULE_PARM_DESC(txglomsz, "maximum tx packet chain size [SDIO]"); @@ -895,6 +903,87 @@ static void brcmf_sdiod_sgtable_alloc(struct brcmf_sdio_dev *sdiodev) sdiodev->txglomsz = brcmf_sdiod_txglomsz; } +#ifdef CONFIG_PM_SLEEP +static int brcmf_sdiod_freezer_attach(struct brcmf_sdio_dev *sdiodev) +{ + sdiodev->freezer = kzalloc(sizeof(*sdiodev->freezer), GFP_KERNEL); + if (!sdiodev->freezer) + return -ENOMEM; + atomic_set(&sdiodev->freezer->thread_count, 0); + atomic_set(&sdiodev->freezer->freezing, 0); + init_waitqueue_head(&sdiodev->freezer->thread_freeze); + init_completion(&sdiodev->freezer->resumed); + return 0; +} + +static void brcmf_sdiod_freezer_detach(struct brcmf_sdio_dev *sdiodev) +{ + if (sdiodev->freezer) { + WARN_ON(atomic_read(&sdiodev->freezer->freezing)); + kfree(sdiodev->freezer); + } +} + +static int brcmf_sdiod_freezer_on(struct brcmf_sdio_dev *sdiodev) +{ + atomic_t *expect = &sdiodev->freezer->thread_count; + int res = 0; + + sdiodev->freezer->frozen_count = 0; + reinit_completion(&sdiodev->freezer->resumed); + atomic_set(&sdiodev->freezer->freezing, 1); + brcmf_sdio_trigger_dpc(sdiodev->bus); + wait_event(sdiodev->freezer->thread_freeze, + atomic_read(expect) == sdiodev->freezer->frozen_count); + sdio_claim_host(sdiodev->func[1]); + res = brcmf_sdio_sleep(sdiodev->bus, true); + sdio_release_host(sdiodev->func[1]); + return res; +} + +static void brcmf_sdiod_freezer_off(struct brcmf_sdio_dev *sdiodev) +{ + sdio_claim_host(sdiodev->func[1]); + brcmf_sdio_sleep(sdiodev->bus, false); + sdio_release_host(sdiodev->func[1]); + atomic_set(&sdiodev->freezer->freezing, 0); + complete_all(&sdiodev->freezer->resumed); +} + +bool brcmf_sdiod_freezing(struct brcmf_sdio_dev *sdiodev) +{ + return atomic_read(&sdiodev->freezer->freezing); +} + +void brcmf_sdiod_try_freeze(struct brcmf_sdio_dev *sdiodev) +{ + if (!brcmf_sdiod_freezing(sdiodev)) + return; + sdiodev->freezer->frozen_count++; + wake_up(&sdiodev->freezer->thread_freeze); + wait_for_completion(&sdiodev->freezer->resumed); +} + +void brcmf_sdiod_freezer_count(struct brcmf_sdio_dev *sdiodev) +{ + atomic_inc(&sdiodev->freezer->thread_count); +} + +void brcmf_sdiod_freezer_uncount(struct brcmf_sdio_dev *sdiodev) +{ + atomic_dec(&sdiodev->freezer->thread_count); +} +#else +static int brcmf_sdiod_freezer_attach(struct brcmf_sdio_dev *sdiodev) +{ + return 0; +} + +static void brcmf_sdiod_freezer_detach(struct brcmf_sdio_dev *sdiodev) +{ +} +#endif /* CONFIG_PM_SLEEP */ + static int brcmf_sdiod_remove(struct brcmf_sdio_dev *sdiodev) { if (sdiodev->bus) { @@ -902,6 +991,8 @@ static int brcmf_sdiod_remove(struct brcmf_sdio_dev *sdiodev) sdiodev->bus = NULL; } + brcmf_sdiod_freezer_detach(sdiodev); + /* Disable Function 2 */ sdio_claim_host(sdiodev->func[2]); sdio_disable_func(sdiodev->func[2]); @@ -973,6 +1064,10 @@ static int brcmf_sdiod_probe(struct brcmf_sdio_dev *sdiodev) */ brcmf_sdiod_sgtable_alloc(sdiodev); + ret = brcmf_sdiod_freezer_attach(sdiodev); + if (ret) + goto out; + /* try to attach to the target device */ sdiodev->bus = brcmf_sdio_probe(sdiodev); if (!sdiodev->bus) { @@ -1069,9 +1164,6 @@ static int brcmf_ops_sdio_probe(struct sdio_func *func, #endif brcmf_sdiod_change_state(sdiodev, BRCMF_SDIOD_DOWN); - sdiodev->sleeping = false; - atomic_set(&sdiodev->suspend, false); - init_waitqueue_head(&sdiodev->idle_wait); brcmf_dbg(SDIO, "F2 found, calling brcmf_sdiod_probe...\n"); err = brcmf_sdiod_probe(sdiodev); @@ -1133,24 +1225,22 @@ void brcmf_sdio_wowl_config(struct device *dev, bool enabled) #ifdef CONFIG_PM_SLEEP static int brcmf_ops_sdio_suspend(struct device *dev) { + struct sdio_func *func; struct brcmf_bus *bus_if; struct brcmf_sdio_dev *sdiodev; mmc_pm_flag_t sdio_flags; - brcmf_dbg(SDIO, "Enter\n"); + func = container_of(dev, struct sdio_func, dev); + brcmf_dbg(SDIO, "Enter: F%d\n", func->num); + if (func->num != SDIO_FUNC_1) + return 0; + bus_if = dev_get_drvdata(dev); sdiodev = bus_if->bus_priv.sdio; - /* wait for watchdog to go idle */ - if (wait_event_timeout(sdiodev->idle_wait, sdiodev->sleeping, - msecs_to_jiffies(3 * BRCMF_WD_POLL_MS)) == 0) { - brcmf_err("bus still active\n"); - return -EBUSY; - } - /* disable watchdog */ + brcmf_sdiod_freezer_on(sdiodev); brcmf_sdio_wd_timer(sdiodev->bus, 0); - atomic_set(&sdiodev->suspend, true); if (sdiodev->wowl_enabled) { sdio_flags = MMC_PM_KEEP_POWER; @@ -1168,12 +1258,13 @@ static int brcmf_ops_sdio_resume(struct device *dev) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; + struct sdio_func *func = container_of(dev, struct sdio_func, dev); - brcmf_dbg(SDIO, "Enter\n"); - if (sdiodev->pdata && sdiodev->pdata->oob_irq_supported) - disable_irq_wake(sdiodev->pdata->oob_irq_nr); - brcmf_sdio_wd_timer(sdiodev->bus, BRCMF_WD_POLL_MS); - atomic_set(&sdiodev->suspend, false); + brcmf_dbg(SDIO, "Enter: F%d\n", func->num); + if (func->num != SDIO_FUNC_2) + return 0; + + brcmf_sdiod_freezer_off(sdiodev); return 0; } |