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 <[email protected]>
---
 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

Reply via email to