diff options
author | Ulf Hansson <ulf.hansson@stericsson.com> | 2011-11-15 16:39:54 +0100 |
---|---|---|
committer | Philippe Langlais <philippe.langlais@stericsson.com> | 2012-05-22 11:02:59 +0200 |
commit | 7831c0b624cf6dcb4d860dd2452faed6c32e42d2 (patch) | |
tree | 4dbbe99300da3525b6464cefd16b408c3a4fa3c4 | |
parent | fa6bcad633752fc296e85f7eb46132626b91b857 (diff) |
mmc: Minimize resume-time by deferring resume
Typically an sd/mmc card takes around 200 - 1100 ms to
initialize when the power to the card has been cut, which
is what happens during a suspend/resume sequence.
All device's resume time adds up to the total kernel resume
time. Some use cases requires the kernel to be resumed fast,
to be able to meet deadlines. One use case example is WLAN
SOFT_AP, but there are certainly more.
This patch schedules a delayed work to do a deferred resume
of the mmc host, if the bus holds a card of SD or MMC type.
The reason for not supporting SDIO and SDcombo cards at this
stage, is because the SDIO API is synchronus, which complicates
request locking mechanism when waiting for a deferred resume to
be completed.
While waiting for a deferred resume to be completed, detect works
are prevented from doing a new rescan. If a mmcblk request arrives,
the deferred resume will be synced immediately.
The deferred resume is scheduled 3000 ms after the resume request
arrived. The idea behind this timer value is to let the mmc host
being able to accept a new suspend request before it has been
deferred resumed and thus not increase the resume to suspend time
if not really needed.
Change-Id: I7e97e59c8709cf5d8e3c76478771ddf6062a54ec
Signed-off-by: Ulf Hansson <ulf.hansson@stericsson.com>
Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/39692
-rw-r--r-- | drivers/mmc/card/block.c | 8 | ||||
-rw-r--r-- | drivers/mmc/core/core.c | 35 | ||||
-rw-r--r-- | drivers/mmc/core/core.h | 1 | ||||
-rw-r--r-- | drivers/mmc/core/host.c | 1 | ||||
-rw-r--r-- | include/linux/mmc/host.h | 17 |
5 files changed, 61 insertions, 1 deletions
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index dabec556ebb..a73621abc85 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1411,6 +1411,14 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) struct mmc_blk_data *md = mq->data; struct mmc_card *card = md->queue.card; + /* + * We must make sure we have not claimed the host before + * doing a flush to prevent deadlock, thus we check if + * the host needs a resume first. + */ + if (mmc_host_needs_resume(card->host)) + mmc_resume_host_sync(card->host); + if (req && !mq->mqrq_prev->req) /* claim host only for the first request */ mmc_claim_host(card->host); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index ba821fe70bc..9cce415f1ff 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2010,7 +2010,7 @@ void mmc_rescan(struct work_struct *work) container_of(work, struct mmc_host, detect.work); int i; - if (host->rescan_disable) + if (host->rescan_disable || mmc_host_needs_resume(host)) return; mmc_bus_get(host); @@ -2273,8 +2273,13 @@ int mmc_suspend_host(struct mmc_host *host) int err = 0; cancel_delayed_work(&host->detect); + cancel_delayed_work_sync(&host->resume); mmc_flush_scheduled_work(); + /* Skip suspend, if deferred resume were scheduled but not completed. */ + if (mmc_host_needs_resume(host)) + return 0; + err = mmc_cache_ctrl(host, 0); if (err) goto out; @@ -2300,6 +2305,10 @@ int mmc_suspend_host(struct mmc_host *host) mmc_release_host(host); host->pm_flags = 0; err = 0; + } else if (mmc_card_mmc(host->card) || + mmc_card_sd(host->card)) { + host->pm_state |= MMC_HOST_DEFERRED_RESUME | + MMC_HOST_NEEDS_RESUME; } } mmc_bus_put(host); @@ -2321,6 +2330,12 @@ int mmc_resume_host(struct mmc_host *host) { int err = 0; + if (mmc_host_deferred_resume(host)) { + mmc_schedule_delayed_work(&host->resume, + msecs_to_jiffies(3000)); + return 0; + } + mmc_bus_get(host); if (host->bus_ops && !host->bus_dead) { if (!mmc_card_keep_power(host)) { @@ -2355,6 +2370,24 @@ int mmc_resume_host(struct mmc_host *host) } EXPORT_SYMBOL(mmc_resume_host); +void mmc_resume_work(struct work_struct *work) +{ + struct mmc_host *host = + container_of(work, struct mmc_host, resume.work); + + host->pm_state &= ~MMC_HOST_DEFERRED_RESUME; + mmc_resume_host(host); + host->pm_state &= ~MMC_HOST_NEEDS_RESUME; + + mmc_detect_change(host, 0); +} + +void mmc_resume_host_sync(struct mmc_host *host) +{ + flush_delayed_work_sync(&host->resume); +} +EXPORT_SYMBOL(mmc_resume_host_sync); + /* Do the card removal on suspend if card is assumed removeable * Do that in pm notifier while userspace isn't yet frozen, so we will be able to sync the card. diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index 3bdafbca354..5796d2d85f4 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -59,6 +59,7 @@ static inline void mmc_delay(unsigned int ms) void mmc_rescan(struct work_struct *work); void mmc_start_host(struct mmc_host *host); void mmc_stop_host(struct mmc_host *host); +void mmc_resume_work(struct work_struct *work); int _mmc_detect_card_removed(struct mmc_host *host); diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 91c84c7a182..d87d1152ea2 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -330,6 +330,7 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) spin_lock_init(&host->lock); init_waitqueue_head(&host->wq); INIT_DELAYED_WORK(&host->detect, mmc_rescan); + INIT_DELAYED_WORK(&host->resume, mmc_resume_work); #ifdef CONFIG_PM host->pm_notify.notifier_call = mmc_pm_notify; #endif diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index cbde4b7e675..015eb334ce5 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -292,6 +292,11 @@ struct mmc_host { int detect_change; /* card detect flag */ struct mmc_hotplug hotplug; + struct delayed_work resume; /* deferred resume work */ + unsigned int pm_state; /* used for deferred resume */ +#define MMC_HOST_DEFERRED_RESUME (1 << 0) +#define MMC_HOST_NEEDS_RESUME (1 << 1) + const struct mmc_bus_ops *bus_ops; /* current bus driver */ unsigned int bus_refs; /* reference counter */ @@ -340,6 +345,7 @@ static inline void *mmc_priv(struct mmc_host *host) extern int mmc_suspend_host(struct mmc_host *); extern int mmc_resume_host(struct mmc_host *); +extern void mmc_resume_host_sync(struct mmc_host *); extern int mmc_power_save_host(struct mmc_host *host); extern int mmc_power_restore_host(struct mmc_host *host); @@ -429,4 +435,15 @@ static inline unsigned int mmc_host_clk_rate(struct mmc_host *host) return host->ios.clock; } #endif + +static inline int mmc_host_deferred_resume(struct mmc_host *host) +{ + return host->pm_state & MMC_HOST_DEFERRED_RESUME; +} + +static inline int mmc_host_needs_resume(struct mmc_host *host) +{ + return host->pm_state & MMC_HOST_NEEDS_RESUME; +} + #endif /* LINUX_MMC_HOST_H */ |