diff mbox series

[3/4] mmc: host: sdhci-esdhc-imx: save tuning value for the SDIO card as wakeup source

Message ID 20241014060130.1162629-4-haibo.chen@nxp.com
State New
Headers show
Series refactor the system PM logic for sdhci-esdhc-imx | expand

Commit Message

Bough Chen Oct. 14, 2024, 6:01 a.m. UTC
From: Haibo Chen <haibo.chen@nxp.com>

For some SoCs like imx6ul(l/z)/imx7d/imx93, during system PM, usdhc will
totally power off, so the internal tuning status will lost. Here add
save/restore the tuning value for any command after system resume back
when re-tuning hold.

The tipical case is for the SDIO which contain flag MMC_PM_KEEP_POWER,
and contain pm_flags MMC_PM_WAKE_SDIO_IRQ. in mmc_sdio_suspend(), SDIO
will switch to 1 bit mode, and switch back to 4 bit mode when resume back.
According to spec, tuning command do not support in 1 bit mode. So when
send cmd52 to switch back to 4 bit mode, need to hold re-tuning. But this
cmd52 still need a correct sample point, otherwise will meet command CRC
error, so need to keep the previous tuning value.

Signed-off-by: Haibo Chen <haibo.chen@nxp.com>
---
 drivers/mmc/host/sdhci-esdhc-imx.c | 94 +++++++++++++++++++++++++++++-
 1 file changed, 91 insertions(+), 3 deletions(-)

Comments

kernel test robot Oct. 15, 2024, 2:08 a.m. UTC | #1
Hi,

kernel test robot noticed the following build warnings:

[auto build test WARNING on shawnguo/for-next]
[also build test WARNING on linus/master v6.12-rc3 next-20241014]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/haibo-chen-nxp-com/mmc-sdhci-export-APIs-for-sdhci-irq-wakeup/20241014-140300
base:   https://git.kernel.org/pub/scm/linux/kernel/git/shawnguo/linux.git for-next
patch link:    https://lore.kernel.org/r/20241014060130.1162629-4-haibo.chen%40nxp.com
patch subject: [PATCH 3/4] mmc: host: sdhci-esdhc-imx: save tuning value for the SDIO card as wakeup source
config: alpha-allyesconfig (https://download.01.org/0day-ci/archive/20241015/202410150906.OEI0jyKN-lkp@intel.com/config)
compiler: alpha-linux-gcc (GCC) 13.3.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241015/202410150906.OEI0jyKN-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202410150906.OEI0jyKN-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/mmc/host/sdhci-esdhc-imx.c:1592:13: warning: 'sdhc_esdhc_tuning_restore' defined but not used [-Wunused-function]
    1592 | static void sdhc_esdhc_tuning_restore(struct sdhci_host *host)
         |             ^~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/mmc/host/sdhci-esdhc-imx.c:1569:13: warning: 'sdhc_esdhc_tuning_save' defined but not used [-Wunused-function]
    1569 | static void sdhc_esdhc_tuning_save(struct sdhci_host *host)
         |             ^~~~~~~~~~~~~~~~~~~~~~


vim +/sdhc_esdhc_tuning_restore +1592 drivers/mmc/host/sdhci-esdhc-imx.c

  1568	
> 1569	static void sdhc_esdhc_tuning_save(struct sdhci_host *host)
  1570	{
  1571		struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
  1572		struct pltfm_imx_data *imx_data = sdhci_pltfm_priv(pltfm_host);
  1573		u32 reg;
  1574	
  1575		/*
  1576		 * SD/eMMC do not need this tuning save because it will re-init
  1577		 * after system resume back.
  1578		 * Here save the tuning delay value for SDIO device since it may
  1579		 * keep power during system PM. And for usdhc, only SDR50 and
  1580		 * SDR104 mode for SDIO devide need to do tuning, and need to
  1581		 * save/restore.
  1582		 */
  1583		if ((host->timing == MMC_TIMING_UHS_SDR50) |
  1584				(host->timing == MMC_TIMING_UHS_SDR104)) {
  1585			reg = readl(host->ioaddr + ESDHC_TUNE_CTRL_STATUS);
  1586			reg = (reg & ESDHC_TUNE_CTRL_STATUS_TAP_SEL_PRE_MASK) >>
  1587					ESDHC_TUNE_CTRL_STATUS_TAP_SEL_PRE_SHIFT;
  1588			imx_data->boarddata.saved_tuning_delay_cell = reg;
  1589		}
  1590	}
  1591	
> 1592	static void sdhc_esdhc_tuning_restore(struct sdhci_host *host)
  1593	{
  1594		struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
  1595		struct pltfm_imx_data *imx_data = sdhci_pltfm_priv(pltfm_host);
  1596		u32 reg;
  1597	
  1598		if ((host->timing == MMC_TIMING_UHS_SDR50) |
  1599				(host->timing == MMC_TIMING_UHS_SDR104)) {
  1600			/*
  1601			 * restore the tuning delay value actually is a
  1602			 * manual tuning method, so clear the standard
  1603			 * tuning enable bit here. Will set back this
  1604			 * ESDHC_STD_TUNING_EN in esdhc_reset_tuning()
  1605			 * when trigger re-tuning.
  1606			 */
  1607			reg = readl(host->ioaddr + ESDHC_TUNING_CTRL);
  1608			reg &= ~ESDHC_STD_TUNING_EN;
  1609			writel(reg, host->ioaddr + ESDHC_TUNING_CTRL);
  1610	
  1611			reg = readl(host->ioaddr + ESDHC_MIX_CTRL);
  1612			reg |= ESDHC_MIX_CTRL_SMPCLK_SEL | ESDHC_MIX_CTRL_FBCLK_SEL;
  1613			writel(reg, host->ioaddr + ESDHC_MIX_CTRL);
  1614	
  1615			writel(imx_data->boarddata.saved_tuning_delay_cell <<
  1616					ESDHC_TUNE_CTRL_STATUS_DLY_CELL_SET_PRE_SHIFT,
  1617					host->ioaddr + ESDHC_TUNE_CTRL_STATUS);
  1618		}
  1619	}
  1620
diff mbox series

Patch

diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c
index 18febfeb60cf..4173967022d0 100644
--- a/drivers/mmc/host/sdhci-esdhc-imx.c
+++ b/drivers/mmc/host/sdhci-esdhc-imx.c
@@ -80,6 +80,9 @@ 
 #define  ESDHC_TUNE_CTRL_STEP		1
 #define  ESDHC_TUNE_CTRL_MIN		0
 #define  ESDHC_TUNE_CTRL_MAX		((1 << 7) - 1)
+#define ESDHC_TUNE_CTRL_STATUS_TAP_SEL_PRE_MASK		0x7f000000
+#define ESDHC_TUNE_CTRL_STATUS_TAP_SEL_PRE_SHIFT	24
+#define ESDHC_TUNE_CTRL_STATUS_DLY_CELL_SET_PRE_SHIFT	8
 
 /* strobe dll register */
 #define ESDHC_STROBE_DLL_CTRL		0x70
@@ -234,6 +237,7 @@  struct esdhc_platform_data {
 	unsigned int tuning_step;       /* The delay cell steps in tuning procedure */
 	unsigned int tuning_start_tap;	/* The start delay cell point in tuning procedure */
 	unsigned int strobe_dll_delay_target;	/* The delay cell for strobe pad (read clock) */
+	unsigned int saved_tuning_delay_cell;	/* save the value of tuning delay cell */
 };
 
 struct esdhc_soc_data {
@@ -1055,7 +1059,7 @@  static void esdhc_reset_tuning(struct sdhci_host *host)
 {
 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
 	struct pltfm_imx_data *imx_data = sdhci_pltfm_priv(pltfm_host);
-	u32 ctrl;
+	u32 ctrl, tuning_ctrl;
 	int ret;
 
 	/* Reset the tuning circuit */
@@ -1069,6 +1073,17 @@  static void esdhc_reset_tuning(struct sdhci_host *host)
 			writel(0, host->ioaddr + ESDHC_TUNE_CTRL_STATUS);
 		} else if (imx_data->socdata->flags & ESDHC_FLAG_STD_TUNING) {
 			writel(ctrl, host->ioaddr + ESDHC_MIX_CTRL);
+
+			/*
+			 * enable the std tuning just in case it cleared in
+			 * sdhc_esdhc_tuning_restore.
+			 */
+			tuning_ctrl = readl(host->ioaddr + ESDHC_TUNING_CTRL);
+			if (!(tuning_ctrl & ESDHC_STD_TUNING_EN)) {
+				tuning_ctrl |= ESDHC_STD_TUNING_EN;
+				writel(tuning_ctrl, host->ioaddr + ESDHC_TUNING_CTRL);
+			}
+
 			ctrl = readl(host->ioaddr + SDHCI_AUTO_CMD_STATUS);
 			ctrl &= ~ESDHC_MIX_CTRL_SMPCLK_SEL;
 			ctrl &= ~ESDHC_MIX_CTRL_EXE_TUNE;
@@ -1147,7 +1162,8 @@  static void esdhc_prepare_tuning(struct sdhci_host *host, u32 val)
 	reg |= ESDHC_MIX_CTRL_EXE_TUNE | ESDHC_MIX_CTRL_SMPCLK_SEL |
 			ESDHC_MIX_CTRL_FBCLK_SEL;
 	writel(reg, host->ioaddr + ESDHC_MIX_CTRL);
-	writel(val << 8, host->ioaddr + ESDHC_TUNE_CTRL_STATUS);
+	writel(val << ESDHC_TUNE_CTRL_STATUS_DLY_CELL_SET_PRE_SHIFT,
+				host->ioaddr + ESDHC_TUNE_CTRL_STATUS);
 	dev_dbg(mmc_dev(host->mmc),
 		"tuning with delay 0x%x ESDHC_TUNE_CTRL_STATUS 0x%x\n",
 			val, readl(host->ioaddr + ESDHC_TUNE_CTRL_STATUS));
@@ -1555,6 +1571,58 @@  static void sdhci_esdhc_imx_hwinit(struct sdhci_host *host)
 	}
 }
 
+static void sdhc_esdhc_tuning_save(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct pltfm_imx_data *imx_data = sdhci_pltfm_priv(pltfm_host);
+	u32 reg;
+
+	/*
+	 * SD/eMMC do not need this tuning save because it will re-init
+	 * after system resume back.
+	 * Here save the tuning delay value for SDIO device since it may
+	 * keep power during system PM. And for usdhc, only SDR50 and
+	 * SDR104 mode for SDIO devide need to do tuning, and need to
+	 * save/restore.
+	 */
+	if ((host->timing == MMC_TIMING_UHS_SDR50) |
+			(host->timing == MMC_TIMING_UHS_SDR104)) {
+		reg = readl(host->ioaddr + ESDHC_TUNE_CTRL_STATUS);
+		reg = (reg & ESDHC_TUNE_CTRL_STATUS_TAP_SEL_PRE_MASK) >>
+				ESDHC_TUNE_CTRL_STATUS_TAP_SEL_PRE_SHIFT;
+		imx_data->boarddata.saved_tuning_delay_cell = reg;
+	}
+}
+
+static void sdhc_esdhc_tuning_restore(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct pltfm_imx_data *imx_data = sdhci_pltfm_priv(pltfm_host);
+	u32 reg;
+
+	if ((host->timing == MMC_TIMING_UHS_SDR50) |
+			(host->timing == MMC_TIMING_UHS_SDR104)) {
+		/*
+		 * restore the tuning delay value actually is a
+		 * manual tuning method, so clear the standard
+		 * tuning enable bit here. Will set back this
+		 * ESDHC_STD_TUNING_EN in esdhc_reset_tuning()
+		 * when trigger re-tuning.
+		 */
+		reg = readl(host->ioaddr + ESDHC_TUNING_CTRL);
+		reg &= ~ESDHC_STD_TUNING_EN;
+		writel(reg, host->ioaddr + ESDHC_TUNING_CTRL);
+
+		reg = readl(host->ioaddr + ESDHC_MIX_CTRL);
+		reg |= ESDHC_MIX_CTRL_SMPCLK_SEL | ESDHC_MIX_CTRL_FBCLK_SEL;
+		writel(reg, host->ioaddr + ESDHC_MIX_CTRL);
+
+		writel(imx_data->boarddata.saved_tuning_delay_cell <<
+				ESDHC_TUNE_CTRL_STATUS_DLY_CELL_SET_PRE_SHIFT,
+				host->ioaddr + ESDHC_TUNE_CTRL_STATUS);
+	}
+}
+
 static void esdhc_cqe_enable(struct mmc_host *mmc)
 {
 	struct sdhci_host *host = mmc_priv(mmc);
@@ -1883,7 +1951,17 @@  static int sdhci_esdhc_suspend(struct device *dev)
 		(host->tuning_mode != SDHCI_TUNING_MODE_1)) {
 		mmc_retune_timer_stop(host->mmc);
 		mmc_retune_needed(host->mmc);
-	}
+
+		/*
+		 * For the SDIO device need to keep power during system PM, and enable
+		 * wakeup, need to save the tuning delay value just in case the retuning
+		 * is hold when SDIO resume, but still need to switch to 4 bit bus width.
+		 */
+		if (host->mmc->sdio_irqs && mmc_card_keep_power(host->mmc) &&
+				(esdhc_is_usdhc(imx_data)))
+			sdhc_esdhc_tuning_save(host);
+
+		}
 
 	if (device_may_wakeup(dev)) {
 		ret = sdhci_enable_irq_wakeups(host);
@@ -1903,6 +1981,8 @@  static int sdhci_esdhc_suspend(struct device *dev)
 static int sdhci_esdhc_resume(struct device *dev)
 {
 	struct sdhci_host *host = dev_get_drvdata(dev);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct pltfm_imx_data *imx_data = sdhci_pltfm_priv(pltfm_host);
 	int ret;
 
 	ret = mmc_gpio_set_cd_wake(host->mmc, false);
@@ -1915,6 +1995,14 @@  static int sdhci_esdhc_resume(struct device *dev)
 	if (host->irq_wake_enabled)
 		sdhci_disable_irq_wakeups(host);
 
+	/*
+	 * Restore the saved tuning delay value for the SDIO device
+	 * which enabled wakeup and keep power during system PM.
+	 */
+	if ((imx_data->socdata->flags & ESDHC_FLAG_STATE_LOST_IN_LPMODE) &&
+	    mmc_card_keep_power(host->mmc) && mmc_card_wake_sdio_irq(host->mmc))
+		sdhc_esdhc_tuning_restore(host);
+
 	pm_runtime_mark_last_busy(dev);
 	pm_runtime_put_autosuspend(dev);