|
@@ -29,6 +29,7 @@
|
|
#include <linux/mmc/sh_mmcif.h>
|
|
#include <linux/mmc/sh_mmcif.h>
|
|
#include <linux/pagemap.h>
|
|
#include <linux/pagemap.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
+#include <linux/pm_runtime.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/spinlock.h>
|
|
|
|
|
|
#define DRIVER_NAME "sh_mmcif"
|
|
#define DRIVER_NAME "sh_mmcif"
|
|
@@ -173,6 +174,7 @@ struct sh_mmcif_host {
|
|
struct completion intr_wait;
|
|
struct completion intr_wait;
|
|
enum mmcif_state state;
|
|
enum mmcif_state state;
|
|
spinlock_t lock;
|
|
spinlock_t lock;
|
|
|
|
+ bool power;
|
|
|
|
|
|
/* DMA support */
|
|
/* DMA support */
|
|
struct dma_chan *chan_rx;
|
|
struct dma_chan *chan_rx;
|
|
@@ -877,11 +879,24 @@ static void sh_mmcif_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
if (ios->power_mode == MMC_POWER_UP) {
|
|
if (ios->power_mode == MMC_POWER_UP) {
|
|
if (p->set_pwr)
|
|
if (p->set_pwr)
|
|
p->set_pwr(host->pd, ios->power_mode);
|
|
p->set_pwr(host->pd, ios->power_mode);
|
|
|
|
+ if (!host->power) {
|
|
|
|
+ /* See if we also get DMA */
|
|
|
|
+ sh_mmcif_request_dma(host, host->pd->dev.platform_data);
|
|
|
|
+ pm_runtime_get_sync(&host->pd->dev);
|
|
|
|
+ host->power = true;
|
|
|
|
+ }
|
|
} else if (ios->power_mode == MMC_POWER_OFF || !ios->clock) {
|
|
} else if (ios->power_mode == MMC_POWER_OFF || !ios->clock) {
|
|
/* clock stop */
|
|
/* clock stop */
|
|
sh_mmcif_clock_control(host, 0);
|
|
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);
|
|
|
|
+ sh_mmcif_release_dma(host);
|
|
|
|
+ host->power = false;
|
|
|
|
+ }
|
|
|
|
+ if (p->down_pwr)
|
|
|
|
+ p->down_pwr(host->pd);
|
|
|
|
+ }
|
|
host->state = STATE_IDLE;
|
|
host->state = STATE_IDLE;
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
@@ -957,7 +972,7 @@ static irqreturn_t sh_mmcif_intr(int irq, void *dev_id)
|
|
sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, state);
|
|
sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, state);
|
|
err = 1;
|
|
err = 1;
|
|
} else {
|
|
} else {
|
|
- dev_dbg(&host->pd->dev, "Not support int\n");
|
|
|
|
|
|
+ dev_dbg(&host->pd->dev, "Unsupported interrupt: 0x%x\n", state);
|
|
sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~state);
|
|
sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~state);
|
|
sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, state);
|
|
sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, state);
|
|
err = 1;
|
|
err = 1;
|
|
@@ -1053,8 +1068,12 @@ static int __devinit sh_mmcif_probe(struct platform_device *pdev)
|
|
sh_mmcif_sync_reset(host);
|
|
sh_mmcif_sync_reset(host);
|
|
platform_set_drvdata(pdev, host);
|
|
platform_set_drvdata(pdev, host);
|
|
|
|
|
|
- /* See if we also get DMA */
|
|
|
|
- sh_mmcif_request_dma(host, pd);
|
|
|
|
|
|
+ pm_runtime_enable(&pdev->dev);
|
|
|
|
+ host->power = false;
|
|
|
|
+
|
|
|
|
+ ret = pm_runtime_resume(&pdev->dev);
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ goto clean_up2;
|
|
|
|
|
|
mmc_add_host(mmc);
|
|
mmc_add_host(mmc);
|
|
|
|
|
|
@@ -1063,13 +1082,13 @@ static int __devinit sh_mmcif_probe(struct platform_device *pdev)
|
|
ret = request_irq(irq[0], sh_mmcif_intr, 0, "sh_mmc:error", host);
|
|
ret = request_irq(irq[0], sh_mmcif_intr, 0, "sh_mmc:error", host);
|
|
if (ret) {
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "request_irq error (sh_mmc:error)\n");
|
|
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);
|
|
ret = request_irq(irq[1], sh_mmcif_intr, 0, "sh_mmc:int", host);
|
|
if (ret) {
|
|
if (ret) {
|
|
free_irq(irq[0], host);
|
|
free_irq(irq[0], host);
|
|
dev_err(&pdev->dev, "request_irq error (sh_mmc:int)\n");
|
|
dev_err(&pdev->dev, "request_irq error (sh_mmc:int)\n");
|
|
- goto clean_up2;
|
|
|
|
|
|
+ goto clean_up3;
|
|
}
|
|
}
|
|
|
|
|
|
sh_mmcif_detect(host->mmc);
|
|
sh_mmcif_detect(host->mmc);
|
|
@@ -1079,7 +1098,11 @@ static int __devinit sh_mmcif_probe(struct platform_device *pdev)
|
|
sh_mmcif_readl(host->addr, MMCIF_CE_VERSION) & 0x0000ffff);
|
|
sh_mmcif_readl(host->addr, MMCIF_CE_VERSION) & 0x0000ffff);
|
|
return ret;
|
|
return ret;
|
|
|
|
|
|
|
|
+clean_up3:
|
|
|
|
+ mmc_remove_host(mmc);
|
|
|
|
+ pm_runtime_suspend(&pdev->dev);
|
|
clean_up2:
|
|
clean_up2:
|
|
|
|
+ pm_runtime_disable(&pdev->dev);
|
|
clk_disable(host->hclk);
|
|
clk_disable(host->hclk);
|
|
clean_up1:
|
|
clean_up1:
|
|
mmc_free_host(mmc);
|
|
mmc_free_host(mmc);
|
|
@@ -1094,9 +1117,9 @@ static int __devexit sh_mmcif_remove(struct platform_device *pdev)
|
|
struct sh_mmcif_host *host = platform_get_drvdata(pdev);
|
|
struct sh_mmcif_host *host = platform_get_drvdata(pdev);
|
|
int irq[2];
|
|
int irq[2];
|
|
|
|
|
|
- mmc_remove_host(host->mmc);
|
|
|
|
- sh_mmcif_release_dma(host);
|
|
|
|
|
|
+ pm_runtime_get_sync(&pdev->dev);
|
|
|
|
|
|
|
|
+ mmc_remove_host(host->mmc);
|
|
sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);
|
|
sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);
|
|
|
|
|
|
if (host->addr)
|
|
if (host->addr)
|
|
@@ -1112,15 +1135,52 @@ static int __devexit sh_mmcif_remove(struct platform_device *pdev)
|
|
|
|
|
|
clk_disable(host->hclk);
|
|
clk_disable(host->hclk);
|
|
mmc_free_host(host->mmc);
|
|
mmc_free_host(host->mmc);
|
|
|
|
+ pm_runtime_put_sync(&pdev->dev);
|
|
|
|
+ pm_runtime_disable(&pdev->dev);
|
|
|
|
|
|
return 0;
|
|
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 = {
|
|
static struct platform_driver sh_mmcif_driver = {
|
|
.probe = sh_mmcif_probe,
|
|
.probe = sh_mmcif_probe,
|
|
.remove = sh_mmcif_remove,
|
|
.remove = sh_mmcif_remove,
|
|
.driver = {
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
.name = DRIVER_NAME,
|
|
|
|
+ .pm = &sh_mmcif_dev_pm_ops,
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
|