diff options
Diffstat (limited to 'drivers/mmc/core/core.c')
-rw-r--r-- | drivers/mmc/core/core.c | 71 |
1 files changed, 50 insertions, 21 deletions
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 132378b89d7..976856af759 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -243,16 +243,17 @@ static void mmc_wait_done(struct mmc_request *mrq) complete(&mrq->completion); } -static void __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq) +static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq) { init_completion(&mrq->completion); mrq->done = mmc_wait_done; if (mmc_card_removed(host->card)) { mrq->cmd->error = -ENOMEDIUM; complete(&mrq->completion); - return; + return -ENOMEDIUM; } mmc_start_request(host, mrq); + return 0; } static void mmc_wait_for_req_done(struct mmc_host *host, @@ -336,6 +337,7 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, struct mmc_async_req *areq, int *error) { int err = 0; + int start_err = 0; struct mmc_async_req *data = host->areq; /* Prepare a new request */ @@ -345,30 +347,23 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, if (host->areq) { mmc_wait_for_req_done(host, host->areq->mrq); err = host->areq->err_check(host->card, host->areq); - if (err) { - /* post process the completed failed request */ - mmc_post_req(host, host->areq->mrq, 0); - if (areq) - /* - * Cancel the new prepared request, because - * it can't run until the failed - * request has been properly handled. - */ - mmc_post_req(host, areq->mrq, -EINVAL); - - host->areq = NULL; - goto out; - } } - if (areq) - __mmc_start_req(host, areq->mrq); + if (!err && areq) + start_err = __mmc_start_req(host, areq->mrq); if (host->areq) mmc_post_req(host, host->areq->mrq, 0); - host->areq = areq; - out: + /* Cancel a prepared request if it was not started. */ + if ((err || start_err) && areq) + mmc_post_req(host, areq->mrq, -EINVAL); + + if (err) + host->areq = NULL; + else + host->areq = areq; + if (error) *error = err; return data; @@ -2137,7 +2132,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); @@ -2402,7 +2397,13 @@ int mmc_suspend_host(struct mmc_host *host) if (host->caps & MMC_CAP_DISABLE) cancel_delayed_work(&host->disable); 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; + if (mmc_try_claim_host(host)) { err = mmc_cache_ctrl(host, 0); mmc_do_release_host(host); @@ -2443,6 +2444,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; } } else { err = -EBUSY; @@ -2467,6 +2472,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)) { @@ -2501,6 +2512,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. |