mmc: sdhci: add power management capability.

Current mmc stack doesn't use the framework provided by power
management subsystem. It doesn't let each device suspend itself
and the pm operations are solely handled by the platform driver.
This may lead to races, since the concurrency of pm framework is
not used. The pm core does its best to reduce the probability of
a race between system suspend/resume and runtime PM by
decrementing/incrementing the usage counters of respective
devices during system-pm operations.
Moreover, it disables runtime PM altogether after suspending the
device and re-enables the same on resume.

To avoid this, the parent child relationship between the platform,
mmc_host and mmc_card devices is used. In this case, the relation
is defined as,
mmc_card -> (child of) -> mmc_host -> (child of) -> platform_dev

Each device is now responsible for its power management.
 * mmc_card
	-> schedules the runtime-suspend
 * mmc_host
	-> actually suspends/resume the host & card i.e. invokes
		mmc_[suspend/resume]_host
 * pltform_dev
	-> disables irqs

Typically, the card device serves as a trigger for scheduling the
runtime-suspend and invoking runtime-resume.

Two new runtime-pm functions have been introduced:
 * mmc_rpm_hold
	-> resumes the device passed as a parameter
 * mmc_rpm_release
	-> suspends the device passed as a parameter

The above two functions are invoked from the below contexts:
 * mmc_rescan
 * bkops
 * mmc-queue

Change-Id: Icf9dd34a445abfaf8dbb974ab1255feeda2581c9
Signed-off-by: Asutosh Das <asutoshd@codeaurora.org>
This commit is contained in:
Asutosh Das 2013-02-11 15:31:35 +05:30 committed by Stephen Boyd
parent 8ebf379082
commit 6dd7712d7b
12 changed files with 336 additions and 18 deletions

View File

@ -34,6 +34,7 @@
#include <linux/delay.h>
#include <linux/capability.h>
#include <linux/compat.h>
#include <linux/pm_runtime.h>
#define CREATE_TRACE_POINTS
#include <trace/events/mmc.h>
@ -2400,6 +2401,7 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
#endif
if (req && !mq->mqrq_prev->req) {
mmc_rpm_hold(host, &card->dev);
/* claim host only for the first request */
mmc_claim_host(card->host);
if (card->ext_csd.bkops_en)
@ -2453,6 +2455,7 @@ out:
* the 'mmc_blk_issue_rq' with 'mqrq_prev->req'.
*/
mmc_release_host(card->host);
mmc_rpm_release(host, &card->dev);
}
return ret;
}

View File

@ -2695,6 +2695,7 @@ static void mmc_test_run(struct mmc_test_card *test, int testcase)
pr_info("%s: Starting tests of card %s...\n",
mmc_hostname(test->card->host), mmc_card_id(test->card));
mmc_rpm_hold(test->card->host, &test->card->dev);
mmc_claim_host(test->card->host);
for (i = 0;i < ARRAY_SIZE(mmc_test_cases);i++) {
@ -2778,6 +2779,7 @@ static void mmc_test_run(struct mmc_test_card *test, int testcase)
}
mmc_release_host(test->card->host);
mmc_rpm_release(test->card->host, &test->card->dev);
pr_info("%s: Tests completed.\n",
mmc_hostname(test->card->host));

View File

@ -26,6 +26,7 @@
#include "bus.h"
#define to_mmc_driver(d) container_of(d, struct mmc_driver, drv)
#define RUNTIME_SUSPEND_DELAY_MS 10000
static ssize_t mmc_type_show(struct device *dev,
struct device_attribute *attr, char *buf)
@ -152,19 +153,38 @@ static int mmc_runtime_suspend(struct device *dev)
{
struct mmc_card *card = mmc_dev_to_card(dev);
return mmc_power_save_host(card->host);
if (mmc_use_core_runtime_pm(card->host))
return 0;
else
return mmc_power_save_host(card->host);
}
static int mmc_runtime_resume(struct device *dev)
{
struct mmc_card *card = mmc_dev_to_card(dev);
return mmc_power_restore_host(card->host);
if (mmc_use_core_runtime_pm(card->host))
return 0;
else
return mmc_power_restore_host(card->host);
}
static int mmc_runtime_idle(struct device *dev)
{
return pm_runtime_suspend(dev);
struct mmc_card *card = mmc_dev_to_card(dev);
struct mmc_host *host = card->host;
int ret = 0;
if (mmc_use_core_runtime_pm(card->host)) {
ret = pm_schedule_suspend(dev, card->idle_timeout);
if (ret) {
pr_err("%s: %s: pm_schedule_suspend failed: err: %d\n",
mmc_hostname(host), __func__, ret);
return ret;
}
}
return ret;
}
#endif /* !CONFIG_PM_RUNTIME */
@ -175,6 +195,42 @@ static const struct dev_pm_ops mmc_bus_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(mmc_bus_suspend, mmc_bus_resume)
};
static ssize_t show_rpm_delay(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct mmc_card *card = mmc_dev_to_card(dev);
if (!card) {
pr_err("%s: %s: card is NULL\n", dev_name(dev), __func__);
return -EINVAL;
}
return snprintf(buf, PAGE_SIZE, "%u\n", card->idle_timeout);
}
static ssize_t store_rpm_delay(struct device *dev, struct device_attribute
*attr, const char *buf, size_t count)
{
struct mmc_card *card = mmc_dev_to_card(dev);
unsigned int delay;
if (!card) {
pr_err("%s: %s: card is NULL\n", dev_name(dev), __func__);
return -EINVAL;
}
if (!kstrtou32(buf, 0, &delay)) {
if (delay < 2000) {
pr_err("%s: %s: less than 2 sec delay is unsupported\n",
mmc_hostname(card->host), __func__);
return -EINVAL;
}
card->idle_timeout = delay;
}
return count;
}
static struct bus_type mmc_bus_type = {
.name = "mmc",
.dev_attrs = mmc_dev_attrs,
@ -326,10 +382,34 @@ int mmc_add_card(struct mmc_card *card)
#endif
mmc_init_context_info(card->host);
if (mmc_use_core_runtime_pm(card->host)) {
ret = pm_runtime_set_active(&card->dev);
if (ret)
pr_err("%s: %s: failed setting runtime active: ret: %d\n",
mmc_hostname(card->host), __func__, ret);
else
pm_runtime_enable(&card->dev);
}
ret = device_add(&card->dev);
if (ret)
return ret;
if (mmc_use_core_runtime_pm(card->host)) {
card->rpm_attrib.show = show_rpm_delay;
card->rpm_attrib.store = store_rpm_delay;
sysfs_attr_init(&card->rpm_attrib.attr);
card->rpm_attrib.attr.name = "runtime_pm_timeout";
card->rpm_attrib.attr.mode = S_IRUGO | S_IWUSR;
ret = device_create_file(&card->dev, &card->rpm_attrib);
if (ret)
pr_err("%s: %s: creating runtime pm sysfs entry: failed: %d\n",
mmc_hostname(card->host), __func__, ret);
/* Default timeout is 10 seconds */
card->idle_timeout = RUNTIME_SUSPEND_DELAY_MS;
}
mmc_card_set_present(card);
return 0;

View File

@ -408,9 +408,12 @@ void mmc_start_bkops(struct mmc_card *card, bool from_exception)
return;
}
mmc_rpm_hold(card->host, &card->dev);
/* In case of delayed bkops we might be in race with suspend. */
if (!mmc_try_claim_host(card->host))
if (!mmc_try_claim_host(card->host)) {
mmc_rpm_release(card->host, &card->dev);
return;
}
/*
* Since the cancel_delayed_work can be changed while we are waiting
@ -485,6 +488,7 @@ void mmc_start_bkops(struct mmc_card *card, bool from_exception)
out:
mmc_release_host(card->host);
mmc_rpm_release(card->host, &card->dev);
}
EXPORT_SYMBOL(mmc_start_bkops);
@ -526,6 +530,7 @@ void mmc_bkops_completion_polling(struct work_struct *work)
* the host from getting into suspend
*/
do {
mmc_rpm_hold(card->host, &card->dev);
mmc_claim_host(card->host);
if (!mmc_card_doing_bkops(card))
@ -552,6 +557,7 @@ void mmc_bkops_completion_polling(struct work_struct *work)
}
mmc_release_host(card->host);
mmc_rpm_release(card->host, &card->dev);
/*
* Sleep before checking the card status again to allow the
@ -570,6 +576,7 @@ void mmc_bkops_completion_polling(struct work_struct *work)
return;
out:
mmc_release_host(card->host);
mmc_rpm_release(card->host, &card->dev);
}
/**
@ -2714,8 +2721,9 @@ static void mmc_clk_scale_work(struct work_struct *work)
if (!host->card || !host->bus_ops ||
!host->bus_ops->change_bus_speed ||
!host->clk_scaling.enable || !host->ios.clock)
goto out;
return;
mmc_rpm_hold(host, &host->card->dev);
if (!mmc_try_claim_host(host)) {
/* retry after a timer tick */
queue_delayed_work(system_nrt_wq, &host->clk_scaling.work, 1);
@ -2725,6 +2733,7 @@ static void mmc_clk_scale_work(struct work_struct *work)
mmc_clk_scaling(host, true);
mmc_release_host(host);
out:
mmc_rpm_release(host, &host->card->dev);
return;
}
@ -3086,11 +3095,12 @@ void mmc_rescan(struct work_struct *work)
goto out;
}
mmc_rpm_hold(host, &host->class_dev);
mmc_claim_host(host);
if (!mmc_rescan_try_freq(host, host->f_min))
extend_wakelock = true;
mmc_release_host(host);
mmc_rpm_release(host, &host->class_dev);
out:
if (extend_wakelock)
wake_lock_timeout(&host->detect_wake_lock, HZ / 2);
@ -3319,9 +3329,6 @@ int mmc_suspend_host(struct mmc_host *host)
if (mmc_bus_needs_resume(host))
return 0;
cancel_delayed_work(&host->detect);
mmc_flush_scheduled_work();
mmc_bus_get(host);
if (host->bus_ops && !host->bus_dead) {
/*
@ -3517,6 +3524,37 @@ int mmc_pm_notify(struct notifier_block *notify_block,
}
#endif
void mmc_rpm_hold(struct mmc_host *host, struct device *dev)
{
int ret = 0;
if (!mmc_use_core_runtime_pm(host))
return;
ret = pm_runtime_get_sync(dev);
if (ret < 0) {
pr_err("%s: %s: %s: error resuming device: %d\n",
dev_name(dev), mmc_hostname(host), __func__, ret);
if (pm_runtime_suspended(dev))
BUG_ON(1);
}
}
EXPORT_SYMBOL(mmc_rpm_hold);
void mmc_rpm_release(struct mmc_host *host, struct device *dev)
{
int ret = 0;
if (!mmc_use_core_runtime_pm(host))
return;
ret = pm_runtime_put_sync(dev);
if (ret < 0 && ret != -EBUSY)
pr_err("%s: %s: %s: put sync ret: %d\n",
dev_name(dev), mmc_hostname(host), __func__, ret);
}
EXPORT_SYMBOL(mmc_rpm_release);
/**
* mmc_init_context_info() - init synchronization context
* @host: mmc host

View File

@ -23,6 +23,7 @@
#include <linux/leds.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/pm_runtime.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
@ -41,9 +42,73 @@ static void mmc_host_classdev_release(struct device *dev)
kfree(host);
}
static int mmc_host_runtime_suspend(struct device *dev)
{
struct mmc_host *host = cls_dev_to_mmc_host(dev);
int ret = 0;
ret = mmc_suspend_host(host);
if (ret < 0)
pr_err("%s: %s: suspend host failed: %d\n", mmc_hostname(host),
__func__, ret);
return ret;
}
static int mmc_host_runtime_resume(struct device *dev)
{
struct mmc_host *host = cls_dev_to_mmc_host(dev);
int ret = 0;
ret = mmc_resume_host(host);
if (ret < 0) {
pr_err("%s: %s: resume host: failed: ret: %d\n",
mmc_hostname(host), __func__, ret);
if (pm_runtime_suspended(dev))
BUG_ON(1);
}
return ret;
}
static int mmc_host_suspend(struct device *dev)
{
struct mmc_host *host = cls_dev_to_mmc_host(dev);
int ret = 0;
if (!pm_runtime_suspended(dev)) {
ret = mmc_suspend_host(host);
if (ret < 0)
pr_err("%s: %s: failed: ret: %d\n", mmc_hostname(host),
__func__, ret);
}
return ret;
}
static int mmc_host_resume(struct device *dev)
{
struct mmc_host *host = cls_dev_to_mmc_host(dev);
int ret = 0;
if (!pm_runtime_suspended(dev)) {
ret = mmc_resume_host(host);
if (ret < 0)
pr_err("%s: %s: failed: ret: %d\n", mmc_hostname(host),
__func__, ret);
}
return ret;
}
static const struct dev_pm_ops mmc_host_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(mmc_host_suspend, mmc_host_resume)
SET_RUNTIME_PM_OPS(mmc_host_runtime_suspend, mmc_host_runtime_resume,
pm_generic_runtime_idle)
};
static struct class mmc_host_class = {
.name = "mmc_host",
.dev_release = mmc_host_classdev_release,
.pm = &mmc_host_pm_ops,
};
int mmc_register_host_class(void)
@ -731,6 +796,14 @@ int mmc_add_host(struct mmc_host *host)
WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
!host->ops->enable_sdio_irq);
if (mmc_use_core_runtime_pm(host)) {
err = pm_runtime_set_active(&host->class_dev);
if (err)
pr_err("%s: %s: failed setting runtime active: err: %d\n",
mmc_hostname(host), __func__, err);
else
pm_runtime_enable(&host->class_dev);
}
err = device_add(&host->class_dev);
if (err)
return err;

View File

@ -17,6 +17,7 @@
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/mmc/mmc.h>
#include <linux/pm_runtime.h>
#include "core.h"
#include "bus.h"
@ -1503,6 +1504,7 @@ static void mmc_detect(struct mmc_host *host)
BUG_ON(!host);
BUG_ON(!host->card);
mmc_rpm_hold(host, &host->card->dev);
mmc_claim_host(host);
/*
@ -1512,6 +1514,13 @@ static void mmc_detect(struct mmc_host *host)
mmc_release_host(host);
/*
* if detect fails, the device would be removed anyway;
* the rpm framework would mark the device state suspended.
*/
if (!err)
mmc_rpm_release(host, &host->card->dev);
if (err) {
mmc_remove(host);

View File

@ -18,6 +18,7 @@
#include <linux/mmc/card.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sd.h>
#include <linux/pm_runtime.h>
#include "core.h"
#include "bus.h"
@ -1135,6 +1136,7 @@ static void mmc_sd_detect(struct mmc_host *host)
BUG_ON(!host);
BUG_ON(!host->card);
mmc_rpm_hold(host, &host->card->dev);
mmc_claim_host(host);
/*
@ -1161,6 +1163,13 @@ static void mmc_sd_detect(struct mmc_host *host)
mmc_release_host(host);
/*
* if detect fails, the device would be removed anyway;
* the rpm framework would mark the device state suspended.
*/
if (!err)
mmc_rpm_release(host, &host->card->dev);
if (err) {
mmc_sd_remove(host);

View File

@ -32,6 +32,8 @@
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/mmc/mmc.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <mach/gpio.h>
#include <mach/msm_bus.h>
@ -216,6 +218,7 @@ struct sdhci_msm_bus_vote {
struct sdhci_msm_host {
struct platform_device *pdev;
void __iomem *core_mem; /* MSM SDCC mapped address */
int pwr_irq; /* power irq */
struct clk *clk; /* main SD/MMC bus clock */
struct clk *pclk; /* SDHC peripheral bus clock */
struct clk *bus_clk; /* SDHC bus voter clock */
@ -1852,7 +1855,7 @@ static int sdhci_msm_probe(struct platform_device *pdev)
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_msm_host *msm_host;
struct resource *core_memres = NULL;
int ret = 0, pwr_irq = 0, dead = 0;
int ret = 0, dead = 0;
u32 host_version;
pr_debug("%s: Enter %s\n", dev_name(&pdev->dev), __func__);
@ -1974,18 +1977,18 @@ static int sdhci_msm_probe(struct platform_device *pdev)
}
/* Setup PWRCTL irq */
pwr_irq = platform_get_irq_byname(pdev, "pwr_irq");
if (pwr_irq < 0) {
msm_host->pwr_irq = platform_get_irq_byname(pdev, "pwr_irq");
if (msm_host->pwr_irq < 0) {
dev_err(&pdev->dev, "Failed to get pwr_irq by name (%d)\n",
pwr_irq);
msm_host->pwr_irq);
goto vreg_deinit;
}
ret = devm_request_threaded_irq(&pdev->dev, pwr_irq, NULL,
ret = devm_request_threaded_irq(&pdev->dev, msm_host->pwr_irq, NULL,
sdhci_msm_pwr_irq, IRQF_ONESHOT,
dev_name(&pdev->dev), host);
if (ret) {
dev_err(&pdev->dev, "Request threaded irq(%d) failed (%d)\n",
pwr_irq, ret);
msm_host->pwr_irq, ret);
goto vreg_deinit;
}
@ -2000,6 +2003,7 @@ static int sdhci_msm_probe(struct platform_device *pdev)
msm_host->mmc->caps |= msm_host->pdata->caps;
msm_host->mmc->caps |= MMC_CAP_HW_RESET;
msm_host->mmc->caps2 |= msm_host->pdata->caps2;
msm_host->mmc->caps2 |= MMC_CAP2_CORE_RUNTIME_PM;
msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR;
msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR_CONTROL;
msm_host->mmc->caps2 |= (MMC_CAP2_BOOTPART_NOACC |
@ -2046,6 +2050,13 @@ static int sdhci_msm_probe(struct platform_device *pdev)
if (ret)
goto remove_host;
ret = pm_runtime_set_active(&pdev->dev);
if (ret)
pr_err("%s: %s: pm_runtime_set_active failed: err: %d\n",
mmc_hostname(host->mmc), __func__, ret);
else
pm_runtime_enable(&pdev->dev);
/* Successful initialization */
goto out;
@ -2084,6 +2095,7 @@ static int sdhci_msm_remove(struct platform_device *pdev)
pr_debug("%s: %s\n", dev_name(&pdev->dev), __func__);
device_remove_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw);
sdhci_remove_host(host, dead);
pm_runtime_disable(&pdev->dev);
sdhci_pltfm_free(pdev);
sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false);
@ -2097,6 +2109,77 @@ static int sdhci_msm_remove(struct platform_device *pdev)
return 0;
}
static int sdhci_msm_runtime_suspend(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = pltfm_host->priv;
disable_irq(host->irq);
disable_irq(msm_host->pwr_irq);
return 0;
}
static int sdhci_msm_runtime_resume(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = pltfm_host->priv;
enable_irq(msm_host->pwr_irq);
enable_irq(host->irq);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int sdhci_msm_suspend(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
int ret = 0;
if (pm_runtime_suspended(dev)) {
pr_debug("%s: %s: already runtime suspended\n",
mmc_hostname(host->mmc), __func__);
goto out;
}
return sdhci_msm_runtime_suspend(dev);
out:
return ret;
}
static int sdhci_msm_resume(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
int ret = 0;
if (pm_runtime_suspended(dev)) {
pr_debug("%s: %s: runtime suspended, defer system resume\n",
mmc_hostname(host->mmc), __func__);
goto out;
}
return sdhci_msm_runtime_resume(dev);
out:
return ret;
}
#endif
#ifdef CONFIG_PM
static const struct dev_pm_ops sdhci_msm_pmops = {
SET_SYSTEM_SLEEP_PM_OPS(sdhci_msm_suspend, sdhci_msm_resume)
SET_RUNTIME_PM_OPS(sdhci_msm_runtime_suspend, sdhci_msm_runtime_resume,
NULL)
};
#define SDHCI_MSM_PMOPS (&sdhci_msm_pmops)
#else
#define SDHCI_PM_OPS NULL
#endif
static const struct of_device_id sdhci_msm_dt_match[] = {
{.compatible = "qcom,sdhci-msm"},
};
@ -2109,6 +2192,7 @@ static struct platform_driver sdhci_msm_driver = {
.name = "sdhci_msm",
.owner = THIS_MODULE,
.of_match_table = sdhci_msm_dt_match,
.pm = SDHCI_MSM_PMOPS,
},
};

View File

@ -2782,13 +2782,20 @@ EXPORT_SYMBOL_GPL(sdhci_resume_host);
static int sdhci_runtime_pm_get(struct sdhci_host *host)
{
return pm_runtime_get_sync(host->mmc->parent);
if (!mmc_use_core_runtime_pm(host->mmc))
return pm_runtime_get_sync(host->mmc->parent);
else
return 0;
}
static int sdhci_runtime_pm_put(struct sdhci_host *host)
{
pm_runtime_mark_last_busy(host->mmc->parent);
return pm_runtime_put_autosuspend(host->mmc->parent);
if (!mmc_use_core_runtime_pm(host->mmc)) {
pm_runtime_mark_last_busy(host->mmc->parent);
return pm_runtime_put_autosuspend(host->mmc->parent);
} else {
return 0;
}
}
int sdhci_runtime_suspend_host(struct sdhci_host *host)

View File

@ -384,6 +384,9 @@ struct mmc_card {
struct mmc_wr_pack_stats wr_pack_stats; /* packed commands stats*/
struct mmc_bkops_info bkops_info;
struct device_attribute rpm_attrib;
unsigned int idle_timeout;
};
/*

View File

@ -169,6 +169,8 @@ extern int mmc_flush_cache(struct mmc_card *);
extern int mmc_detect_card_removed(struct mmc_host *host);
extern void mmc_blk_init_bkops_statistics(struct mmc_card *card);
extern void mmc_rpm_hold(struct mmc_host *host, struct device *dev);
extern void mmc_rpm_release(struct mmc_host *host, struct device *dev);
/**
* mmc_claim_host - exclusively claim a host

View File

@ -287,6 +287,8 @@ struct mmc_host {
#define MMC_CAP2_INIT_BKOPS (1 << 15) /* Need to set BKOPS_EN */
#define MMC_CAP2_PACKED_WR_CONTROL (1 << 16) /* Allow write packing control */
#define MMC_CAP2_CLK_SCALE (1 << 17) /* Allow dynamic clk scaling */
/* Use runtime PM framework provided by MMC core */
#define MMC_CAP2_CORE_RUNTIME_PM (1 << 19)
mmc_pm_flag_t pm_caps; /* supported pm features */
int clk_requests; /* internal reference counter */
@ -555,4 +557,10 @@ static inline unsigned int mmc_host_clk_rate(struct mmc_host *host)
return host->ios.clock;
}
#endif
static inline int mmc_use_core_runtime_pm(struct mmc_host *host)
{
return host->caps2 & MMC_CAP2_CORE_RUNTIME_PM;
}
#endif /* LINUX_MMC_HOST_H */