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

From: haibo . chen
Date: Mon Oct 14 2024 - 02:01:20 EST


From: Haibo Chen <haibo.chen@xxxxxxx>

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@xxxxxxx>
---
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