Adding support for runtime power-management to the MMCIF driver allows
it to save power as long as no card is present. System-wide power
management has been verified with experimental PM patches on AP4-
based systems.

Signed-off-by: Guennadi Liakhovetski <[email protected]>
---

With this patch and with the patch to mmc/core/bus.c, that I've sent a 
couple of minutes ago

http://article.gmane.org/gmane.linux.kernel.mmc/7433

PM works correctly with all possible modprobe / rmmod, card-insert / 
eject, and STR scenarios, that I could come up with. The part in 
sh_mmcif_remove() is a bit rough though...

diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c
index d3871b6..23eb0ec 100644
--- a/drivers/mmc/host/sh_mmcif.c
+++ b/drivers/mmc/host/sh_mmcif.c
@@ -29,6 +29,7 @@
 #include <linux/mmc/sh_mmcif.h>
 #include <linux/pagemap.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/spinlock.h>
 
 #define DRIVER_NAME    "sh_mmcif"
@@ -173,6 +174,7 @@ struct sh_mmcif_host {
        struct completion intr_wait;
        enum mmcif_state state;
        spinlock_t lock;
+       bool power;
 
        /* DMA support */
        struct dma_chan         *chan_rx;
@@ -877,11 +879,21 @@ static void sh_mmcif_set_ios(struct mmc_host *mmc, struct 
mmc_ios *ios)
        if (ios->power_mode == MMC_POWER_UP) {
                if (p->set_pwr)
                        p->set_pwr(host->pd, ios->power_mode);
+               if (!host->power) {
+                       pm_runtime_get_sync(&host->pd->dev);
+                       host->power = true;
+               }
        } else if (ios->power_mode == MMC_POWER_OFF || !ios->clock) {
                /* clock stop */
                sh_mmcif_clock_control(host, 0);
-               if (ios->power_mode == MMC_POWER_OFF && p->down_pwr)
-                       p->down_pwr(host->pd);
+               if (ios->power_mode == MMC_POWER_OFF) {
+                       if (host->power) {
+                               pm_runtime_put(&host->pd->dev);
+                               host->power = false;
+                       }
+                       if (p->down_pwr)
+                               p->down_pwr(host->pd);
+               }
                host->state = STATE_IDLE;
                return;
        }
@@ -1053,6 +1065,12 @@ static int __devinit sh_mmcif_probe(struct 
platform_device *pdev)
        sh_mmcif_sync_reset(host);
        platform_set_drvdata(pdev, host);
 
+       pm_runtime_enable(&pdev->dev);
+       host->power = false;
+       ret = pm_runtime_resume(&pdev->dev);
+       if (ret < 0)
+               goto clean_up2;
+
        /* See if we also get DMA */
        sh_mmcif_request_dma(host, pd);
 
@@ -1063,13 +1081,13 @@ static int __devinit sh_mmcif_probe(struct 
platform_device *pdev)
        ret = request_irq(irq[0], sh_mmcif_intr, 0, "sh_mmc:error", host);
        if (ret) {
                dev_err(&pdev->dev, "request_irq error (sh_mmc:error)\n");
-               goto clean_up2;
+               goto clean_up3;
        }
        ret = request_irq(irq[1], sh_mmcif_intr, 0, "sh_mmc:int", host);
        if (ret) {
                free_irq(irq[0], host);
                dev_err(&pdev->dev, "request_irq error (sh_mmc:int)\n");
-               goto clean_up2;
+               goto clean_up3;
        }
 
        sh_mmcif_detect(host->mmc);
@@ -1079,7 +1097,10 @@ static int __devinit sh_mmcif_probe(struct 
platform_device *pdev)
                sh_mmcif_readl(host->addr, MMCIF_CE_VERSION) & 0x0000ffff);
        return ret;
 
+clean_up3:
+       pm_runtime_suspend(&pdev->dev);
 clean_up2:
+       pm_runtime_disable(&pdev->dev);
        clk_disable(host->hclk);
 clean_up1:
        mmc_free_host(mmc);
@@ -1112,15 +1133,54 @@ static int __devexit sh_mmcif_remove(struct 
platform_device *pdev)
 
        clk_disable(host->hclk);
        mmc_free_host(host->mmc);
+       pm_runtime_put_noidle(&pdev->dev);
+       pm_runtime_suspend(&pdev->dev);
+       pm_runtime_disable(&pdev->dev);
+       pm_runtime_get_noresume(&pdev->dev);
 
        return 0;
 }
 
+#ifdef CONFIG_PM
+static int sh_mmcif_suspend(struct device *dev)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct sh_mmcif_host *host = platform_get_drvdata(pdev);
+       int ret = mmc_suspend_host(host->mmc);
+
+       if (!ret) {
+               sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);
+               clk_disable(host->hclk);
+       }
+
+       return ret;
+}
+
+static int sh_mmcif_resume(struct device *dev)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct sh_mmcif_host *host = platform_get_drvdata(pdev);
+
+       clk_enable(host->hclk);
+
+       return mmc_resume_host(host->mmc);
+}
+#else
+#define sh_mmcif_suspend       NULL
+#define sh_mmcif_resume                NULL
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops sh_mmcif_dev_pm_ops = {
+       .suspend = sh_mmcif_suspend,
+       .resume = sh_mmcif_resume,
+};
+
 static struct platform_driver sh_mmcif_driver = {
        .probe          = sh_mmcif_probe,
        .remove         = sh_mmcif_remove,
        .driver         = {
                .name   = DRIVER_NAME,
+               .pm     = &sh_mmcif_dev_pm_ops,
        },
 };
 
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to