Merge tag 'mmc-v5.7' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc
[platform/kernel/linux-rpi.git] / drivers / mmc / host / sdhci-esdhc-imx.c
index b2bdf50..5ec8e4b 100644 (file)
@@ -9,6 +9,7 @@
  */
 
 #include <linux/io.h>
+#include <linux/iopoll.h>
 #include <linux/delay.h>
 #include <linux/err.h>
 #include <linux/clk.h>
@@ -73,6 +74,7 @@
 #define ESDHC_STROBE_DLL_CTRL          0x70
 #define ESDHC_STROBE_DLL_CTRL_ENABLE   (1 << 0)
 #define ESDHC_STROBE_DLL_CTRL_RESET    (1 << 1)
+#define ESDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_DEFAULT   0x7
 #define ESDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_SHIFT     3
 #define ESDHC_STROBE_DLL_CTRL_SLV_UPDATE_INT_DEFAULT   (4 << 20)
 
 #define ESDHC_FLAG_CQHCI               BIT(12)
 /* need request pmqos during low power */
 #define ESDHC_FLAG_PMQOS               BIT(13)
+/* The IP state got lost in low power mode */
+#define ESDHC_FLAG_STATE_LOST_IN_LPMODE                BIT(14)
+/* The IP lost clock rate in PM_RUNTIME */
+#define ESDHC_FLAG_CLK_RATE_LOST_IN_PM_RUNTIME BIT(15)
+/*
+ * The IP do not support the ACMD23 feature completely when use ADMA mode.
+ * In ADMA mode, it only use the 16 bit block count of the register 0x4
+ * (BLOCK_ATT) as the CMD23's argument for ACMD23 mode, which means it will
+ * ignore the upper 16 bit of the CMD23's argument. This will block the reliable
+ * write operation in RPMB, because RPMB reliable write need to set the bit31
+ * of the CMD23's argument.
+ * imx6qpdl/imx6sx/imx6sl/imx7d has this limitation only for ADMA mode, SDMA
+ * do not has this limitation. so when these SoC use ADMA mode, it need to
+ * disable the ACMD23 feature.
+ */
+#define ESDHC_FLAG_BROKEN_AUTO_CMD23   BIT(16)
 
 struct esdhc_soc_data {
        u32 flags;
@@ -182,43 +200,67 @@ static const struct esdhc_soc_data esdhc_imx53_data = {
 };
 
 static const struct esdhc_soc_data usdhc_imx6q_data = {
-       .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_MAN_TUNING,
+       .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_MAN_TUNING
+                       | ESDHC_FLAG_BROKEN_AUTO_CMD23,
 };
 
 static const struct esdhc_soc_data usdhc_imx6sl_data = {
        .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING
                        | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_ERR004536
-                       | ESDHC_FLAG_HS200,
+                       | ESDHC_FLAG_HS200
+                       | ESDHC_FLAG_BROKEN_AUTO_CMD23,
+};
+
+static const struct esdhc_soc_data usdhc_imx6sll_data = {
+       .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING
+                       | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200
+                       | ESDHC_FLAG_STATE_LOST_IN_LPMODE,
 };
 
 static const struct esdhc_soc_data usdhc_imx6sx_data = {
        .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING
-                       | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200,
+                       | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200
+                       | ESDHC_FLAG_STATE_LOST_IN_LPMODE
+                       | ESDHC_FLAG_BROKEN_AUTO_CMD23,
 };
 
 static const struct esdhc_soc_data usdhc_imx6ull_data = {
        .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING
                        | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200
-                       | ESDHC_FLAG_ERR010450,
+                       | ESDHC_FLAG_ERR010450
+                       | ESDHC_FLAG_STATE_LOST_IN_LPMODE,
 };
 
 static const struct esdhc_soc_data usdhc_imx7d_data = {
        .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING
                        | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200
-                       | ESDHC_FLAG_HS400,
+                       | ESDHC_FLAG_HS400
+                       | ESDHC_FLAG_STATE_LOST_IN_LPMODE
+                       | ESDHC_FLAG_BROKEN_AUTO_CMD23,
 };
 
 static struct esdhc_soc_data usdhc_imx7ulp_data = {
        .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING
                        | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200
-                       | ESDHC_FLAG_PMQOS | ESDHC_FLAG_HS400,
+                       | ESDHC_FLAG_PMQOS | ESDHC_FLAG_HS400
+                       | ESDHC_FLAG_STATE_LOST_IN_LPMODE,
 };
 
 static struct esdhc_soc_data usdhc_imx8qxp_data = {
        .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING
                        | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200
                        | ESDHC_FLAG_HS400 | ESDHC_FLAG_HS400_ES
-                       | ESDHC_FLAG_CQHCI,
+                       | ESDHC_FLAG_CQHCI
+                       | ESDHC_FLAG_STATE_LOST_IN_LPMODE
+                       | ESDHC_FLAG_CLK_RATE_LOST_IN_PM_RUNTIME,
+};
+
+static struct esdhc_soc_data usdhc_imx8mm_data = {
+       .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING
+                       | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200
+                       | ESDHC_FLAG_HS400 | ESDHC_FLAG_HS400_ES
+                       | ESDHC_FLAG_CQHCI
+                       | ESDHC_FLAG_STATE_LOST_IN_LPMODE,
 };
 
 struct pltfm_imx_data {
@@ -264,11 +306,13 @@ static const struct of_device_id imx_esdhc_dt_ids[] = {
        { .compatible = "fsl,imx53-esdhc", .data = &esdhc_imx53_data, },
        { .compatible = "fsl,imx6sx-usdhc", .data = &usdhc_imx6sx_data, },
        { .compatible = "fsl,imx6sl-usdhc", .data = &usdhc_imx6sl_data, },
+       { .compatible = "fsl,imx6sll-usdhc", .data = &usdhc_imx6sll_data, },
        { .compatible = "fsl,imx6q-usdhc", .data = &usdhc_imx6q_data, },
        { .compatible = "fsl,imx6ull-usdhc", .data = &usdhc_imx6ull_data, },
        { .compatible = "fsl,imx7d-usdhc", .data = &usdhc_imx7d_data, },
        { .compatible = "fsl,imx7ulp-usdhc", .data = &usdhc_imx7ulp_data, },
        { .compatible = "fsl,imx8qxp-usdhc", .data = &usdhc_imx8qxp_data, },
+       { .compatible = "fsl,imx8mm-usdhc", .data = &usdhc_imx8mm_data, },
        { /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, imx_esdhc_dt_ids);
@@ -301,6 +345,17 @@ static inline void esdhc_clrset_le(struct sdhci_host *host, u32 mask, u32 val, i
        writel(((readl(base) & ~(mask << shift)) | (val << shift)), base);
 }
 
+static inline void esdhc_wait_for_card_clock_gate_off(struct sdhci_host *host)
+{
+       u32 present_state;
+       int ret;
+
+       ret = readl_poll_timeout(host->ioaddr + ESDHC_PRSSTAT, present_state,
+                               (present_state & ESDHC_CLOCK_GATE_OFF), 2, 100);
+       if (ret == -ETIMEDOUT)
+               dev_warn(mmc_dev(host->mmc), "%s: card clock still not gate off in 100us!.\n", __func__);
+}
+
 static u32 esdhc_readl_le(struct sdhci_host *host, int reg)
 {
        struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
@@ -514,6 +569,8 @@ static void esdhc_writew_le(struct sdhci_host *host, u16 val, int reg)
                else
                        new_val &= ~ESDHC_VENDOR_SPEC_FRC_SDCLK_ON;
                writel(new_val, host->ioaddr + ESDHC_VENDOR_SPEC);
+               if (!(new_val & ESDHC_VENDOR_SPEC_FRC_SDCLK_ON))
+                       esdhc_wait_for_card_clock_gate_off(host);
                return;
        case SDHCI_HOST_CONTROL2:
                new_val = readl(host->ioaddr + ESDHC_VENDOR_SPEC);
@@ -582,10 +639,24 @@ static void esdhc_writew_le(struct sdhci_host *host, u16 val, int reg)
                         * For DMA access restore the levels to default value.
                         */
                        m = readl(host->ioaddr + ESDHC_WTMK_LVL);
-                       if (val & SDHCI_TRNS_DMA)
+                       if (val & SDHCI_TRNS_DMA) {
                                wml = ESDHC_WTMK_LVL_WML_VAL_DEF;
-                       else
+                       } else {
+                               u8 ctrl;
                                wml = ESDHC_WTMK_LVL_WML_VAL_MAX;
+
+                               /*
+                                * Since already disable DMA mode, so also need
+                                * to clear the DMASEL. Otherwise, for standard
+                                * tuning, when send tuning command, usdhc will
+                                * still prefetch the ADMA script from wrong
+                                * DMA address, then we will see IOMMU report
+                                * some error which show lack of TLB mapping.
+                                */
+                               ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
+                               ctrl &= ~SDHCI_CTRL_DMA_MASK;
+                               sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+                       }
                        m &= ~(ESDHC_WTMK_LVL_RD_WML_MASK |
                               ESDHC_WTMK_LVL_WR_WML_MASK);
                        m |= (wml << ESDHC_WTMK_LVL_RD_WML_SHIFT) |
@@ -742,12 +813,14 @@ static inline void esdhc_pltfm_set_clock(struct sdhci_host *host,
        int ddr_pre_div = imx_data->is_ddr ? 2 : 1;
        int pre_div = 1;
        int div = 1;
+       int ret;
        u32 temp, val;
 
        if (esdhc_is_usdhc(imx_data)) {
                val = readl(host->ioaddr + ESDHC_VENDOR_SPEC);
                writel(val & ~ESDHC_VENDOR_SPEC_FRC_SDCLK_ON,
                        host->ioaddr + ESDHC_VENDOR_SPEC);
+               esdhc_wait_for_card_clock_gate_off(host);
        }
 
        if (clock == 0) {
@@ -802,13 +875,18 @@ static inline void esdhc_pltfm_set_clock(struct sdhci_host *host,
                | (pre_div << ESDHC_PREDIV_SHIFT));
        sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL);
 
+       /* need to wait the bit 3 of the PRSSTAT to be set, make sure card clock is stable */
+       ret = readl_poll_timeout(host->ioaddr + ESDHC_PRSSTAT, temp,
+                               (temp & ESDHC_CLOCK_STABLE), 2, 100);
+       if (ret == -ETIMEDOUT)
+               dev_warn(mmc_dev(host->mmc), "card clock still not stable in 100us!.\n");
+
        if (esdhc_is_usdhc(imx_data)) {
                val = readl(host->ioaddr + ESDHC_VENDOR_SPEC);
                writel(val | ESDHC_VENDOR_SPEC_FRC_SDCLK_ON,
                        host->ioaddr + ESDHC_VENDOR_SPEC);
        }
 
-       mdelay(1);
 }
 
 static unsigned int esdhc_pltfm_get_ro(struct sdhci_host *host)
@@ -983,12 +1061,17 @@ static int esdhc_change_pinstate(struct sdhci_host *host,
  */
 static void esdhc_set_strobe_dll(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 strobe_delay;
        u32 v;
+       int ret;
 
        /* disable clock before enabling strobe dll */
        writel(readl(host->ioaddr + ESDHC_VENDOR_SPEC) &
                ~ESDHC_VENDOR_SPEC_FRC_SDCLK_ON,
                host->ioaddr + ESDHC_VENDOR_SPEC);
+       esdhc_wait_for_card_clock_gate_off(host);
 
        /* force a reset on strobe dll */
        writel(ESDHC_STROBE_DLL_CTRL_RESET,
@@ -1000,19 +1083,21 @@ static void esdhc_set_strobe_dll(struct sdhci_host *host)
         * enable strobe dll ctrl and adjust the delay target
         * for the uSDHC loopback read clock
         */
+       if (imx_data->boarddata.strobe_dll_delay_target)
+               strobe_delay = imx_data->boarddata.strobe_dll_delay_target;
+       else
+               strobe_delay = ESDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_DEFAULT;
        v = ESDHC_STROBE_DLL_CTRL_ENABLE |
                ESDHC_STROBE_DLL_CTRL_SLV_UPDATE_INT_DEFAULT |
-               (7 << ESDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_SHIFT);
+               (strobe_delay << ESDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_SHIFT);
        writel(v, host->ioaddr + ESDHC_STROBE_DLL_CTRL);
-       /* wait 5us to make sure strobe dll status register stable */
-       udelay(5);
-       v = readl(host->ioaddr + ESDHC_STROBE_DLL_STATUS);
-       if (!(v & ESDHC_STROBE_DLL_STS_REF_LOCK))
-               dev_warn(mmc_dev(host->mmc),
-               "warning! HS400 strobe DLL status REF not lock!\n");
-       if (!(v & ESDHC_STROBE_DLL_STS_SLV_LOCK))
+
+       /* wait max 50us to get the REF/SLV lock */
+       ret = readl_poll_timeout(host->ioaddr + ESDHC_STROBE_DLL_STATUS, v,
+               ((v & ESDHC_STROBE_DLL_STS_REF_LOCK) && (v & ESDHC_STROBE_DLL_STS_SLV_LOCK)), 1, 50);
+       if (ret == -ETIMEDOUT)
                dev_warn(mmc_dev(host->mmc),
-               "warning! HS400 strobe DLL status SLV not lock!\n");
+               "warning! HS400 strobe DLL status REF/SLV not lock in 50us, STROBE DLL status is %x!\n", v);
 }
 
 static void esdhc_reset_tuning(struct sdhci_host *host)
@@ -1162,6 +1247,7 @@ static void sdhci_esdhc_imx_hwinit(struct sdhci_host *host)
 {
        struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
        struct pltfm_imx_data *imx_data = sdhci_pltfm_priv(pltfm_host);
+       struct cqhci_host *cq_host = host->mmc->cqe_private;
        int tmp;
 
        if (esdhc_is_usdhc(imx_data)) {
@@ -1238,6 +1324,21 @@ static void sdhci_esdhc_imx_hwinit(struct sdhci_host *host)
                        tmp &= ~ESDHC_STD_TUNING_EN;
                        writel(tmp, host->ioaddr + ESDHC_TUNING_CTRL);
                }
+
+               /*
+                * On i.MX8MM, we are running Dual Linux OS, with 1st Linux using SD Card
+                * as rootfs storage, 2nd Linux using eMMC as rootfs storage. We let the
+                * the 1st linux configure power/clock for the 2nd Linux.
+                *
+                * When the 2nd Linux is booting into rootfs stage, we let the 1st Linux
+                * to destroy the 2nd linux, then restart the 2nd linux, we met SDHCI dump.
+                * After we clear the pending interrupt and halt CQCTL, issue gone.
+                */
+               if (cq_host) {
+                       tmp = cqhci_readl(cq_host, CQHCI_IS);
+                       cqhci_writel(cq_host, tmp, CQHCI_IS);
+                       cqhci_writel(cq_host, CQHCI_HALT, CQHCI_CTL);
+               }
        }
 }
 
@@ -1328,6 +1429,8 @@ sdhci_esdhc_imx_probe_dt(struct platform_device *pdev,
        of_property_read_u32(np, "fsl,tuning-start-tap",
                             &boarddata->tuning_start_tap);
 
+       of_property_read_u32(np, "fsl,strobe-dll-delay-target",
+                               &boarddata->strobe_dll_delay_target);
        if (of_find_property(np, "no-1-8-v", NULL))
                host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V;
 
@@ -1487,7 +1590,7 @@ static int sdhci_esdhc_imx_probe(struct platform_device *pdev)
        imx_data->pinctrl = devm_pinctrl_get(&pdev->dev);
        if (IS_ERR(imx_data->pinctrl)) {
                err = PTR_ERR(imx_data->pinctrl);
-               goto disable_ahb_clk;
+               dev_warn(mmc_dev(host->mmc), "could not get pinctrl\n");
        }
 
        if (esdhc_is_usdhc(imx_data)) {
@@ -1518,6 +1621,9 @@ static int sdhci_esdhc_imx_probe(struct platform_device *pdev)
        if (imx_data->socdata->flags & ESDHC_FLAG_HS400)
                host->quirks2 |= SDHCI_QUIRK2_CAPS_BIT63_FOR_HS400;
 
+       if (imx_data->socdata->flags & ESDHC_FLAG_BROKEN_AUTO_CMD23)
+               host->quirks2 |= SDHCI_QUIRK2_ACMD23_BROKEN;
+
        if (imx_data->socdata->flags & ESDHC_FLAG_HS400_ES) {
                host->mmc->caps2 |= MMC_CAP2_HS400_ES;
                host->mmc_host_ops.hs400_enhanced_strobe =
@@ -1605,6 +1711,8 @@ static int sdhci_esdhc_imx_remove(struct platform_device *pdev)
 static int sdhci_esdhc_suspend(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;
 
        if (host->mmc->caps2 & MMC_CAP2_CQE) {
@@ -1613,10 +1721,20 @@ static int sdhci_esdhc_suspend(struct device *dev)
                        return ret;
        }
 
+       if ((imx_data->socdata->flags & ESDHC_FLAG_STATE_LOST_IN_LPMODE) &&
+               (host->tuning_mode != SDHCI_TUNING_MODE_1)) {
+               mmc_retune_timer_stop(host->mmc);
+               mmc_retune_needed(host->mmc);
+       }
+
        if (host->tuning_mode != SDHCI_TUNING_MODE_3)
                mmc_retune_needed(host->mmc);
 
-       return sdhci_suspend_host(host);
+       ret = sdhci_suspend_host(host);
+       if (!ret)
+               return pinctrl_pm_select_sleep_state(dev);
+
+       return ret;
 }
 
 static int sdhci_esdhc_resume(struct device *dev)
@@ -1624,6 +1742,10 @@ static int sdhci_esdhc_resume(struct device *dev)
        struct sdhci_host *host = dev_get_drvdata(dev);
        int ret;
 
+       ret = pinctrl_pm_select_default_state(dev);
+       if (ret)
+               return ret;
+
        /* re-initialize hw state in case it's lost in low power mode */
        sdhci_esdhc_imx_hwinit(host);
 
@@ -1681,6 +1803,9 @@ static int sdhci_esdhc_runtime_resume(struct device *dev)
        if (imx_data->socdata->flags & ESDHC_FLAG_PMQOS)
                cpu_latency_qos_add_request(&imx_data->pm_qos_req, 0);
 
+       if (imx_data->socdata->flags & ESDHC_FLAG_CLK_RATE_LOST_IN_PM_RUNTIME)
+               clk_set_rate(imx_data->clk_per, pltfm_host->clock);
+
        err = clk_prepare_enable(imx_data->clk_ahb);
        if (err)
                goto remove_pm_qos_request;