[PATCH] mmc: tegra: Add Runtime PM callbacks

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add runtime suspend/resume callbacks to save power
when the bus is not in use.
In runtime suspend
- Turn off the SDMMC host CAR clock.
- Turn off the trimmer/DLL circuit(BG) power supply(VREG).
- Turn off the SDMMC host internal clocks.

Re-enable all the disabled clocks/regulators in runtime resume.

Signed-off-by: Aniruddha Rao <anrao@xxxxxxxxxx>
---
 drivers/mmc/host/sdhci-tegra.c | 149 ++++++++++++++++++++++++++++++++++++++---
 1 file changed, 140 insertions(+), 9 deletions(-)

diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c
index 0a3f9d0..1b4b245 100644
--- a/drivers/mmc/host/sdhci-tegra.c
+++ b/drivers/mmc/host/sdhci-tegra.c
@@ -23,6 +23,7 @@
 #include <linux/mmc/slot-gpio.h>
 #include <linux/gpio/consumer.h>
 #include <linux/ktime.h>
+#include <linux/pm_runtime.h>
 
 #include "sdhci-pltfm.h"
 #include "cqhci.h"
@@ -36,6 +37,7 @@
 #define SDHCI_CLOCK_CTRL_SDR50_TUNING_OVERRIDE		BIT(5)
 #define SDHCI_CLOCK_CTRL_PADPIPE_CLKEN_OVERRIDE		BIT(3)
 #define SDHCI_CLOCK_CTRL_SPI_MODE_CLKEN_OVERRIDE	BIT(2)
+#define SDHCI_CLOCK_CTRL_SDMMC_CLK			BIT(0)
 
 #define SDHCI_TEGRA_VENDOR_SYS_SW_CTRL			0x104
 #define SDHCI_TEGRA_SYS_SW_CTRL_ENHANCED_STROBE		BIT(31)
@@ -51,6 +53,9 @@
 #define SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300		0x20
 #define SDHCI_MISC_CTRL_ENABLE_DDR50			0x200
 
+#define SDHCI_TEGRA_VENDOR_IO_TRIM_CTRL_0		0x1AC
+#define SDHCI_TEGRA_IO_TRIM_CTRL_0_SEL_VREG_MASK	0x4
+
 #define SDHCI_TEGRA_VENDOR_DLLCAL_CFG			0x1b0
 #define SDHCI_TEGRA_DLLCAL_CALIBRATE			BIT(31)
 
@@ -113,6 +118,9 @@
 /* SDMMC CQE Base Address for Tegra Host Ver 4.1 and Higher */
 #define SDHCI_TEGRA_CQE_BASE_ADDR			0xF000
 
+#define SDHCI_TEGRA_FALLBACK_CLK_HZ			400000
+#define SDHCI_TEGRA_RTPM_MSEC_TMOUT			10
+
 struct sdhci_tegra_soc_data {
 	const struct sdhci_pltfm_data *pdata;
 	u64 dma_mask;
@@ -743,6 +751,24 @@ static void tegra_sdhci_parse_dt(struct sdhci_host *host)
 	tegra_sdhci_parse_tap_and_trim(host);
 }
 
+static void tegra_sdhci_set_bg_trimmer_supply(struct sdhci_host *host,
+					      bool enable)
+{
+	unsigned int misc_ctrl;
+
+	misc_ctrl = sdhci_readl(host, SDHCI_TEGRA_VENDOR_IO_TRIM_CTRL_0);
+	if (enable) {
+		misc_ctrl &= ~(SDHCI_TEGRA_IO_TRIM_CTRL_0_SEL_VREG_MASK);
+		sdhci_writel(host, misc_ctrl, SDHCI_TEGRA_VENDOR_IO_TRIM_CTRL_0);
+		udelay(3);
+		sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
+	} else {
+		misc_ctrl |= (SDHCI_TEGRA_IO_TRIM_CTRL_0_SEL_VREG_MASK);
+		sdhci_writel(host, misc_ctrl, SDHCI_TEGRA_VENDOR_IO_TRIM_CTRL_0);
+		udelay(1);
+	}
+}
+
 static void tegra_sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
 {
 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
@@ -780,6 +806,57 @@ static void tegra_sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
 	}
 }
 
+static int tegra_sdhci_set_host_clock(struct sdhci_host *host, bool enable)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host);
+	u8 vndr_ctrl;
+	int err;
+
+	if (!enable) {
+		dev_dbg(mmc_dev(host->mmc), "Disabling clk\n");
+
+		/*
+		 * Power down BG trimmer supply(VREG).
+		 * Ensure SDMMC host internal clocks are
+		 * turned off before calling this function.
+		 */
+		tegra_sdhci_set_bg_trimmer_supply(host, false);
+
+		/* Update SDMMC host CAR clock status */
+		vndr_ctrl = sdhci_readb(host, SDHCI_TEGRA_VENDOR_CLOCK_CTRL);
+		vndr_ctrl &= ~SDHCI_CLOCK_CTRL_SDMMC_CLK;
+		sdhci_writeb(host, vndr_ctrl, SDHCI_TEGRA_VENDOR_CLOCK_CTRL);
+
+		/* Disable SDMMC host CAR clock */
+		clk_disable_unprepare(pltfm_host->clk);
+	} else {
+		dev_dbg(mmc_dev(host->mmc), "Enabling clk\n");
+
+		/* Enable SDMMC host CAR clock */
+		err = clk_prepare_enable(pltfm_host->clk);
+		if (err) {
+			dev_err(mmc_dev(host->mmc),
+				"clk enable failed %d\n", err);
+			return err;
+		}
+
+		/* Reset SDMMC host CAR clock status */
+		vndr_ctrl = sdhci_readb(host, SDHCI_TEGRA_VENDOR_CLOCK_CTRL);
+		vndr_ctrl |= SDHCI_CLOCK_CTRL_SDMMC_CLK;
+		sdhci_writeb(host, vndr_ctrl, SDHCI_TEGRA_VENDOR_CLOCK_CTRL);
+
+		/*
+		 * Power up BG trimmer supply(VREG).
+		 * Ensure SDMMC host internal clocks are
+		 * turned off before calling this function.
+		 */
+		tegra_sdhci_set_bg_trimmer_supply(host, true);
+	}
+
+	return 0;
+}
+
 static unsigned int tegra_sdhci_get_max_clock(struct sdhci_host *host)
 {
 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
@@ -1622,7 +1699,6 @@ static int sdhci_tegra_probe(struct platform_device *pdev)
 
 		goto err_clk_get;
 	}
-	clk_prepare_enable(clk);
 	pltfm_host->clk = clk;
 
 	tegra_host->rst = devm_reset_control_get_exclusive(&pdev->dev,
@@ -1645,16 +1721,29 @@ static int sdhci_tegra_probe(struct platform_device *pdev)
 
 	usleep_range(2000, 4000);
 
+	pm_runtime_enable(&pdev->dev);
+	rc = pm_runtime_get_sync(&pdev->dev);
+	if (rc < 0)
+		goto pm_disable;
+	pm_runtime_set_autosuspend_delay(&pdev->dev,
+					 SDHCI_TEGRA_RTPM_MSEC_TMOUT);
+	pm_runtime_use_autosuspend(&pdev->dev);
+
 	rc = sdhci_tegra_add_host(host);
 	if (rc)
 		goto err_add_host;
 
+	pm_runtime_mark_last_busy(&pdev->dev);
+	pm_runtime_put_autosuspend(&pdev->dev);
+
 	return 0;
 
 err_add_host:
 	reset_control_assert(tegra_host->rst);
+	pm_runtime_put_autosuspend(&pdev->dev);
+pm_disable:
+	pm_runtime_disable(&pdev->dev);
 err_rst_get:
-	clk_disable_unprepare(pltfm_host->clk);
 err_clk_get:
 err_power_req:
 err_parse_dt:
@@ -1679,6 +1768,41 @@ static int sdhci_tegra_remove(struct platform_device *pdev)
 	return 0;
 }
 
+static int sdhci_tegra_runtime_suspend(struct device *dev)
+{
+	struct sdhci_host *host = dev_get_drvdata(dev);
+
+	/* Disable SDMMC internal clock */
+	sdhci_set_clock(host, 0);
+
+	/* Disable SDMMC host CAR clock and BG trimmer supply */
+	return tegra_sdhci_set_host_clock(host, false);
+}
+
+static int sdhci_tegra_runtime_resume(struct device *dev)
+{
+	struct sdhci_host *host = dev_get_drvdata(dev);
+	unsigned int clk;
+	int err = 0;
+
+	/* Clock enable should be invoked with a non-zero freq */
+	if (host->clock)
+		clk = host->clock;
+	else if (host->mmc->ios.clock)
+		clk = host->mmc->ios.clock;
+	else
+		clk = SDHCI_TEGRA_FALLBACK_CLK_HZ;
+
+	/* Enable SDMMC host CAR clock and BG trimmer supply */
+	err = tegra_sdhci_set_host_clock(host, true);
+	if (!err) {
+		/* Re-enable SDMMC internal clock */
+		sdhci_set_clock(host, clk);
+	}
+
+	return err;
+}
+
 #ifdef CONFIG_PM_SLEEP
 static int __maybe_unused sdhci_tegra_suspend(struct device *dev)
 {
@@ -1686,6 +1810,12 @@ static int __maybe_unused sdhci_tegra_suspend(struct device *dev)
 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
 	int ret;
 
+	if (pm_runtime_status_suspended(dev)) {
+		ret = tegra_sdhci_set_host_clock(host, true);
+		if (ret)
+			return ret;
+	}
+
 	if (host->mmc->caps2 & MMC_CAP2_CQE) {
 		ret = cqhci_suspend(host->mmc);
 		if (ret)
@@ -1698,8 +1828,7 @@ static int __maybe_unused sdhci_tegra_suspend(struct device *dev)
 		return ret;
 	}
 
-	clk_disable_unprepare(pltfm_host->clk);
-	return 0;
+	return tegra_sdhci_set_host_clock(host, false);
 }
 
 static int __maybe_unused sdhci_tegra_resume(struct device *dev)
@@ -1708,7 +1837,7 @@ static int __maybe_unused sdhci_tegra_resume(struct device *dev)
 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
 	int ret;
 
-	ret = clk_prepare_enable(pltfm_host->clk);
+	ret = tegra_sdhci_set_host_clock(host, true);
 	if (ret)
 		return ret;
 
@@ -1727,13 +1856,15 @@ static int __maybe_unused sdhci_tegra_resume(struct device *dev)
 suspend_host:
 	sdhci_suspend_host(host);
 disable_clk:
-	clk_disable_unprepare(pltfm_host->clk);
-	return ret;
+	return tegra_sdhci_set_host_clock(host, false);
 }
 #endif
 
-static SIMPLE_DEV_PM_OPS(sdhci_tegra_dev_pm_ops, sdhci_tegra_suspend,
-			 sdhci_tegra_resume);
+const struct dev_pm_ops sdhci_tegra_dev_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(sdhci_tegra_suspend, sdhci_tegra_resume)
+	SET_RUNTIME_PM_OPS(sdhci_tegra_runtime_suspend,
+			   sdhci_tegra_runtime_resume, NULL)
+};
 
 static struct platform_driver sdhci_tegra_driver = {
 	.driver		= {
-- 
2.7.4




[Index of Archives]     [ARM Kernel]     [Linux ARM]     [Linux ARM MSM]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux