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