linux-tegra.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] mmc: tegra: Add Runtime PM callbacks
@ 2020-07-27 12:47 Aniruddha Rao
  2020-07-27 13:10 ` Dmitry Osipenko
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Aniruddha Rao @ 2020-07-27 12:47 UTC (permalink / raw)
  To: adrian.hunter, ulf.hansson, thierry.reding, jonathanh, p.zabel
  Cc: linux-mmc, linux-tegra, linux-kernel, Aniruddha Rao

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@nvidia.com>
---
 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


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH] mmc: tegra: Add Runtime PM callbacks
  2020-07-27 12:47 [PATCH] mmc: tegra: Add Runtime PM callbacks Aniruddha Rao
@ 2020-07-27 13:10 ` Dmitry Osipenko
  2020-07-28 14:12 ` Jon Hunter
  2020-08-24  7:20 ` Ulf Hansson
  2 siblings, 0 replies; 4+ messages in thread
From: Dmitry Osipenko @ 2020-07-27 13:10 UTC (permalink / raw)
  To: Aniruddha Rao, adrian.hunter, ulf.hansson, thierry.reding,
	jonathanh, p.zabel
  Cc: linux-mmc, linux-tegra, linux-kernel

27.07.2020 15:47, Aniruddha Rao пишет:
> 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@nvidia.com>
> ---
>  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

Hello Aniruddha,

Does this register exist on older Tegra SoCs?

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] mmc: tegra: Add Runtime PM callbacks
  2020-07-27 12:47 [PATCH] mmc: tegra: Add Runtime PM callbacks Aniruddha Rao
  2020-07-27 13:10 ` Dmitry Osipenko
@ 2020-07-28 14:12 ` Jon Hunter
  2020-08-24  7:20 ` Ulf Hansson
  2 siblings, 0 replies; 4+ messages in thread
From: Jon Hunter @ 2020-07-28 14:12 UTC (permalink / raw)
  To: Aniruddha Rao, adrian.hunter, ulf.hansson, thierry.reding, p.zabel
  Cc: linux-mmc, linux-tegra, linux-kernel


On 27/07/2020 13:47, Aniruddha Rao wrote:
> 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@nvidia.com>


This patch is causing a boot regression on Tegra20 Ventana. Other boards
appear to be booting fine, but this is breaking something for Tegra20.
The bootlogs don't appear to show any particular crash, but I see a hang
when initialising the sdhci controllers.

Cheers
Jon

-- 
nvpublic

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] mmc: tegra: Add Runtime PM callbacks
  2020-07-27 12:47 [PATCH] mmc: tegra: Add Runtime PM callbacks Aniruddha Rao
  2020-07-27 13:10 ` Dmitry Osipenko
  2020-07-28 14:12 ` Jon Hunter
@ 2020-08-24  7:20 ` Ulf Hansson
  2 siblings, 0 replies; 4+ messages in thread
From: Ulf Hansson @ 2020-08-24  7:20 UTC (permalink / raw)
  To: Aniruddha Rao
  Cc: Adrian Hunter, Thierry Reding, Jon Hunter, Philipp Zabel,
	linux-mmc, linux-tegra, Linux Kernel Mailing List

[...]

> @@ -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);

Shouldn't you also call sdhci_runtime_suspend_host() somewhere around
here, to mask IRQs etc.

> +}
> +
> +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 */

I don't know the Tegra controller very well, but to me, looks odd that
the BG trimmer supply hasn't been handled before. Looks like you need
to enable that, even if you don't use runtime PM, no?

> +       err = tegra_sdhci_set_host_clock(host, true);
> +       if (!err) {
> +               /* Re-enable SDMMC internal clock */
> +               sdhci_set_clock(host, clk);
> +       }

Maybe you need to call sdhci_runtime_resume_host() somewhere around here?

> +
> +       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;
> +       }

So you need to re-enable the clock above, if it's been turned off in
runtime suspend, to complete the below operations.

That makes me wonder about the below operations. Why don't you need to
call cqhci_suspend() at runtime suspend?

> +
>         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
>

Kind regards
Uffe

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2020-08-24  7:20 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-07-27 12:47 [PATCH] mmc: tegra: Add Runtime PM callbacks Aniruddha Rao
2020-07-27 13:10 ` Dmitry Osipenko
2020-07-28 14:12 ` Jon Hunter
2020-08-24  7:20 ` Ulf Hansson

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).