diff options
Diffstat (limited to 'drivers/mmc/core/core.c')
-rw-r--r-- | drivers/mmc/core/core.c | 318 |
1 files changed, 301 insertions, 17 deletions
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index d84c880fac8..7dab2e5f4bc 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -344,6 +344,101 @@ unsigned int mmc_align_data_size(struct mmc_card *card, unsigned int sz) EXPORT_SYMBOL(mmc_align_data_size); /** + * mmc_host_enable - enable a host. + * @host: mmc host to enable + * + * Hosts that support power saving can use the 'enable' and 'disable' + * methods to exit and enter power saving states. For more information + * see comments for struct mmc_host_ops. + */ +int mmc_host_enable(struct mmc_host *host) +{ + if (!(host->caps & MMC_CAP_DISABLE)) + return 0; + + if (host->en_dis_recurs) + return 0; + + if (host->nesting_cnt++) + return 0; + + cancel_delayed_work_sync(&host->disable); + + if (host->enabled) + return 0; + + if (host->ops->enable) { + int err; + + host->en_dis_recurs = 1; + err = host->ops->enable(host); + host->en_dis_recurs = 0; + + if (err) { + pr_debug("%s: enable error %d\n", + mmc_hostname(host), err); + return err; + } + } + host->enabled = 1; + return 0; +} +EXPORT_SYMBOL(mmc_host_enable); + +static int mmc_host_do_disable(struct mmc_host *host, int lazy) +{ + if (host->ops->disable) { + int err; + + host->en_dis_recurs = 1; + err = host->ops->disable(host, lazy); + host->en_dis_recurs = 0; + + if (err < 0) { + pr_debug("%s: disable error %d\n", + mmc_hostname(host), err); + return err; + } + if (err > 0) { + unsigned long delay = msecs_to_jiffies(err); + + mmc_schedule_delayed_work(&host->disable, delay); + } + } + host->enabled = 0; + return 0; +} + +/** + * mmc_host_disable - disable a host. + * @host: mmc host to disable + * + * Hosts that support power saving can use the 'enable' and 'disable' + * methods to exit and enter power saving states. For more information + * see comments for struct mmc_host_ops. + */ +int mmc_host_disable(struct mmc_host *host) +{ + int err; + + if (!(host->caps & MMC_CAP_DISABLE)) + return 0; + + if (host->en_dis_recurs) + return 0; + + if (--host->nesting_cnt) + return 0; + + if (!host->enabled) + return 0; + + err = mmc_host_do_disable(host, 0); + return err; +} +EXPORT_SYMBOL(mmc_host_disable); + +/** * __mmc_claim_host - exclusively claim a host * @host: mmc host to claim * @abort: whether or not the operation should be aborted @@ -366,25 +461,111 @@ int __mmc_claim_host(struct mmc_host *host, atomic_t *abort) while (1) { set_current_state(TASK_UNINTERRUPTIBLE); stop = abort ? atomic_read(abort) : 0; - if (stop || !host->claimed) + if (stop || !host->claimed || host->claimer == current) break; spin_unlock_irqrestore(&host->lock, flags); schedule(); spin_lock_irqsave(&host->lock, flags); } set_current_state(TASK_RUNNING); - if (!stop) + if (!stop) { host->claimed = 1; - else + host->claimer = current; + host->claim_cnt += 1; + } else wake_up(&host->wq); spin_unlock_irqrestore(&host->lock, flags); remove_wait_queue(&host->wq, &wait); + if (!stop) + mmc_host_enable(host); return stop; } EXPORT_SYMBOL(__mmc_claim_host); /** + * mmc_try_claim_host - try exclusively to claim a host + * @host: mmc host to claim + * + * Returns %1 if the host is claimed, %0 otherwise. + */ +int mmc_try_claim_host(struct mmc_host *host) +{ + int claimed_host = 0; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + if (!host->claimed || host->claimer == current) { + host->claimed = 1; + host->claimer = current; + host->claim_cnt += 1; + claimed_host = 1; + } + spin_unlock_irqrestore(&host->lock, flags); + return claimed_host; +} +EXPORT_SYMBOL(mmc_try_claim_host); + +static void mmc_do_release_host(struct mmc_host *host) +{ + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + if (--host->claim_cnt) { + /* Release for nested claim */ + spin_unlock_irqrestore(&host->lock, flags); + } else { + host->claimed = 0; + host->claimer = NULL; + spin_unlock_irqrestore(&host->lock, flags); + wake_up(&host->wq); + } +} + +void mmc_host_deeper_disable(struct work_struct *work) +{ + struct mmc_host *host = + container_of(work, struct mmc_host, disable.work); + + /* If the host is claimed then we do not want to disable it anymore */ + if (!mmc_try_claim_host(host)) + return; + mmc_host_do_disable(host, 1); + mmc_do_release_host(host); +} + +/** + * mmc_host_lazy_disable - lazily disable a host. + * @host: mmc host to disable + * + * Hosts that support power saving can use the 'enable' and 'disable' + * methods to exit and enter power saving states. For more information + * see comments for struct mmc_host_ops. + */ +int mmc_host_lazy_disable(struct mmc_host *host) +{ + if (!(host->caps & MMC_CAP_DISABLE)) + return 0; + + if (host->en_dis_recurs) + return 0; + + if (--host->nesting_cnt) + return 0; + + if (!host->enabled) + return 0; + + if (host->disable_delay) { + mmc_schedule_delayed_work(&host->disable, + msecs_to_jiffies(host->disable_delay)); + return 0; + } else + return mmc_host_do_disable(host, 1); +} +EXPORT_SYMBOL(mmc_host_lazy_disable); + +/** * mmc_release_host - release a host * @host: mmc host to release * @@ -393,15 +574,11 @@ EXPORT_SYMBOL(__mmc_claim_host); */ void mmc_release_host(struct mmc_host *host) { - unsigned long flags; - WARN_ON(!host->claimed); - spin_lock_irqsave(&host->lock, flags); - host->claimed = 0; - spin_unlock_irqrestore(&host->lock, flags); + mmc_host_lazy_disable(host); - wake_up(&host->wq); + mmc_do_release_host(host); } EXPORT_SYMBOL(mmc_release_host); @@ -687,7 +864,13 @@ void mmc_set_timing(struct mmc_host *host, unsigned int timing) */ static void mmc_power_up(struct mmc_host *host) { - int bit = fls(host->ocr_avail) - 1; + int bit; + + /* If ocr is set, we use it */ + if (host->ocr) + bit = ffs(host->ocr) - 1; + else + bit = fls(host->ocr_avail) - 1; host->ios.vdd = bit; if (mmc_host_is_spi(host)) { @@ -947,6 +1130,8 @@ void mmc_stop_host(struct mmc_host *host) spin_unlock_irqrestore(&host->lock, flags); #endif + if (host->caps & MMC_CAP_DISABLE) + cancel_delayed_work(&host->disable); cancel_delayed_work(&host->detect); mmc_flush_scheduled_work(); @@ -958,6 +1143,8 @@ void mmc_stop_host(struct mmc_host *host) mmc_claim_host(host); mmc_detach_bus(host); mmc_release_host(host); + mmc_bus_put(host); + return; } mmc_bus_put(host); @@ -966,6 +1153,80 @@ void mmc_stop_host(struct mmc_host *host) mmc_power_off(host); } +void mmc_power_save_host(struct mmc_host *host) +{ + mmc_bus_get(host); + + if (!host->bus_ops || host->bus_dead || !host->bus_ops->power_restore) { + mmc_bus_put(host); + return; + } + + if (host->bus_ops->power_save) + host->bus_ops->power_save(host); + + mmc_bus_put(host); + + mmc_power_off(host); +} +EXPORT_SYMBOL(mmc_power_save_host); + +void mmc_power_restore_host(struct mmc_host *host) +{ + mmc_bus_get(host); + + if (!host->bus_ops || host->bus_dead || !host->bus_ops->power_restore) { + mmc_bus_put(host); + return; + } + + mmc_power_up(host); + host->bus_ops->power_restore(host); + + mmc_bus_put(host); +} +EXPORT_SYMBOL(mmc_power_restore_host); + +int mmc_card_awake(struct mmc_host *host) +{ + int err = -ENOSYS; + + mmc_bus_get(host); + + if (host->bus_ops && !host->bus_dead && host->bus_ops->awake) + err = host->bus_ops->awake(host); + + mmc_bus_put(host); + + return err; +} +EXPORT_SYMBOL(mmc_card_awake); + +int mmc_card_sleep(struct mmc_host *host) +{ + int err = -ENOSYS; + + mmc_bus_get(host); + + if (host->bus_ops && !host->bus_dead && host->bus_ops->awake) + err = host->bus_ops->sleep(host); + + mmc_bus_put(host); + + return err; +} +EXPORT_SYMBOL(mmc_card_sleep); + +int mmc_card_can_sleep(struct mmc_host *host) +{ + struct mmc_card *card = host->card; + + if (card && mmc_card_mmc(card) && card->ext_csd.rev >= 3) + return 1; + return 0; +} +EXPORT_SYMBOL(mmc_card_can_sleep); + #ifdef CONFIG_PM /** @@ -975,27 +1236,36 @@ void mmc_stop_host(struct mmc_host *host) */ int mmc_suspend_host(struct mmc_host *host, pm_message_t state) { + int err = 0; + + if (host->caps & MMC_CAP_DISABLE) + cancel_delayed_work(&host->disable); cancel_delayed_work(&host->detect); mmc_flush_scheduled_work(); mmc_bus_get(host); if (host->bus_ops && !host->bus_dead) { if (host->bus_ops->suspend) - host->bus_ops->suspend(host); - if (!host->bus_ops->resume) { + err = host->bus_ops->suspend(host); + if (err == -ENOSYS || !host->bus_ops->resume) { + /* + * We simply "remove" the card in this case. + * It will be redetected on resume. + */ if (host->bus_ops->remove) host->bus_ops->remove(host); - mmc_claim_host(host); mmc_detach_bus(host); mmc_release_host(host); + err = 0; } } mmc_bus_put(host); - mmc_power_off(host); + if (!err) + mmc_power_off(host); - return 0; + return err; } EXPORT_SYMBOL(mmc_suspend_host); @@ -1006,12 +1276,26 @@ EXPORT_SYMBOL(mmc_suspend_host); */ int mmc_resume_host(struct mmc_host *host) { + int err = 0; + mmc_bus_get(host); if (host->bus_ops && !host->bus_dead) { mmc_power_up(host); mmc_select_voltage(host, host->ocr); BUG_ON(!host->bus_ops->resume); - host->bus_ops->resume(host); + err = host->bus_ops->resume(host); + if (err) { + printk(KERN_WARNING "%s: error %d during resume " + "(card was removed?)\n", + mmc_hostname(host), err); + if (host->bus_ops->remove) + host->bus_ops->remove(host); + mmc_claim_host(host); + mmc_detach_bus(host); + mmc_release_host(host); + /* no need to bother upper layers */ + err = 0; + } } mmc_bus_put(host); @@ -1021,7 +1305,7 @@ int mmc_resume_host(struct mmc_host *host) */ mmc_detect_change(host, 1); - return 0; + return err; } EXPORT_SYMBOL(mmc_resume_host); |