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(-)
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);
--
2.34.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 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
© 2016 - 2024 Red Hat, Inc.