]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
mmc: host: renesas_sdhi: Fix incorrect auto retuning for an SDIO card
authorYoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Tue, 10 Jun 2025 07:25:45 +0000 (16:25 +0900)
committerUlf Hansson <ulf.hansson@linaro.org>
Tue, 24 Jun 2025 10:43:26 +0000 (12:43 +0200)
This host controller is possible to change incorrect tap if an SDIO
card is used because DAT1 is used for interrupt signal on SDIO standard
but the controller doesn't take care of it. So, in the worst case,
this behavior causes a CRC error.

To resolve the issue, this driver uses manual correction mode instead
of auto correction if an SDIO card is used. Also, even if DAT1 is
mismatched on an SDIO card, this driver will not change the TAP.

Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Reviewed-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Tested-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Link: https://lore.kernel.org/r/20250610072545.2001435-3-yoshihiro.shimoda.uh@renesas.com
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
drivers/mmc/host/renesas_sdhi.h
drivers/mmc/host/renesas_sdhi_core.c

index 291ddb4ad9bed40e91cb8018aabc3aaba57bcba3..084964cecf9d8e5b20e51a01f3177bf4ff49f6fa 100644 (file)
@@ -85,6 +85,7 @@ struct renesas_sdhi {
        u32 scc_tappos_hs400;
        const u8 *adjust_hs400_calib_table;
        bool needs_adjust_hs400;
+       bool card_is_sdio;
 
        /* Tuning values: 1 for success, 0 for failure */
        DECLARE_BITMAP(taps, BITS_PER_LONG);
index 4647d86e145eecae06bfe79b4efe77f209476ead..fb8ca03f661d7f392dc6a9ae42d1cca14fdd4791 100644 (file)
@@ -686,9 +686,8 @@ static int renesas_sdhi_select_tuning(struct tmio_mmc_host *host)
        /* Set SCC */
        sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, priv->tap_set);
 
-       /* Enable auto re-tuning */
        sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL,
-                      SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN |
+                      (priv->card_is_sdio ? 0 : SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN) |
                       sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL));
 
        return 0;
@@ -778,6 +777,14 @@ static bool renesas_sdhi_manual_correction(struct tmio_mmc_host *host, bool use_
                if (bad_taps & BIT(new_tap % priv->tap_num))
                        return test_bit(error_tap % priv->tap_num, priv->smpcmp);
        } else {
+               if (!priv->card_is_sdio &&
+                   !(val & SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR)) {
+                       u32 smpcmp = sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_SMPCMP);
+
+                       /* DAT1 is unmatched because of an SDIO irq */
+                       if (smpcmp & (BIT(17) | BIT(1)))
+                               return false;
+               }
                if (val & SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR)
                        return true;    /* need retune */
                else if (val & SH_MOBILE_SDHI_SCC_RVSREQ_REQTAPUP)
@@ -828,11 +835,14 @@ static bool renesas_sdhi_check_scc_error(struct tmio_mmc_host *host,
        if (mmc_doing_tune(host->mmc))
                return false;
 
-       if (((mrq->cmd->error == -ETIMEDOUT) ||
-            (mrq->data && mrq->data->error == -ETIMEDOUT)) &&
-           ((host->mmc->caps & MMC_CAP_NONREMOVABLE) ||
-            (host->ops.get_cd && host->ops.get_cd(host->mmc))))
-               ret |= true;
+       /* mrq can be NULL to check SCC error on SDIO irq without any request */
+       if (mrq) {
+               if (((mrq->cmd->error == -ETIMEDOUT) ||
+                    (mrq->data && mrq->data->error == -ETIMEDOUT)) &&
+                   ((host->mmc->caps & MMC_CAP_NONREMOVABLE) ||
+                    (host->ops.get_cd && host->ops.get_cd(host->mmc))))
+                       ret |= true;
+       }
 
        if (sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL) &
            SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN)
@@ -843,6 +853,28 @@ static bool renesas_sdhi_check_scc_error(struct tmio_mmc_host *host,
        return ret;
 }
 
+static void renesas_sdhi_init_card(struct mmc_host *mmc, struct mmc_card *card)
+{
+       struct tmio_mmc_host *host = mmc_priv(mmc);
+       struct renesas_sdhi *priv = host_to_priv(host);
+
+       /*
+        * This controller cannot do auto-retune with SDIO irqs, so we
+        * then need to enforce manual correction. However, when tuning,
+        * mmc->card is not populated yet, so we don't know if the card
+        * is SDIO. init_card provides this information earlier, so we
+        * keep a copy of it.
+        */
+       priv->card_is_sdio = mmc_card_sdio(card);
+}
+
+static void renesas_sdhi_sdio_irq(struct tmio_mmc_host *host)
+{
+       /* This controller requires retune when an SDIO irq occurs */
+       if (renesas_sdhi_check_scc_error(host, NULL))
+               mmc_retune_needed(host->mmc);
+}
+
 static int renesas_sdhi_wait_idle(struct tmio_mmc_host *host, u32 bit)
 {
        int timeout = 1000;
@@ -1227,6 +1259,8 @@ int renesas_sdhi_probe(struct platform_device *pdev,
                        dev_warn(&host->pdev->dev, "Unknown clock rate for tuning\n");
 
                host->check_retune = renesas_sdhi_check_scc_error;
+               host->sdio_irq = renesas_sdhi_sdio_irq;
+               host->ops.init_card = renesas_sdhi_init_card;
                host->ops.execute_tuning = renesas_sdhi_execute_tuning;
                host->ops.prepare_hs400_tuning = renesas_sdhi_prepare_hs400_tuning;
                host->ops.hs400_downgrade = renesas_sdhi_disable_scc;