]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
mmc: sdhci-of-k1: add comprehensive SDR tuning support
authorIker Pedrosa <ikerpedrosam@gmail.com>
Mon, 11 May 2026 08:53:59 +0000 (10:53 +0200)
committerUlf Hansson <ulf.hansson@linaro.org>
Mon, 11 May 2026 15:35:47 +0000 (17:35 +0200)
Implement software tuning algorithm to enable UHS-I SDR modes for SD
card operation and HS200 mode for eMMC. This adds both TX and RX delay
line tuning based on the SpacemiT K1 controller capabilities.

Algorithm features:
- Add tuning register definitions (RX_CFG, DLINE_CTRL, DLINE_CFG)
- Conditional tuning: only for high-speed modes (≥100MHz)
- TX tuning: configure transmit delay line with optimal values
  (dline_reg=0, delaycode=127) to ensure optimal signal output timing
- RX tuning: single-pass window detection algorithm testing full
  delay range (0-255) to find optimal receive timing window
- Retry mechanism: multiple fallback delays within optimal window
  for improved reliability

Tested-by: Anand Moon <linux.amoon@gmail.com>
Acked-by: Adrian Hunter <adrian.hunter@intel.com>
Tested-by: Trevor Gamblin <tgamblin@baylibre.com>
Tested-by: Vincent Legoll <legoll@online.fr>
Signed-off-by: Iker Pedrosa <ikerpedrosam@gmail.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
drivers/mmc/host/sdhci-of-k1.c

index d9144537032a51a12d7480885f247f4c66583e59..37b0911e7cf2023ad440fbdbf504821457f061f6 100644 (file)
 #define  SDHC_PHY_DRIVE_SEL            GENMASK(2, 0)
 #define  SDHC_RX_BIAS_CTRL             BIT(5)
 
+#define SPACEMIT_SDHC_RX_CFG_REG        0x118
+#define  SDHC_RX_SDCLK_SEL0_MASK        GENMASK(1, 0)
+#define  SDHC_RX_SDCLK_SEL1_MASK        GENMASK(3, 2)
+#define  SDHC_RX_SDCLK_SEL1             FIELD_PREP(SDHC_RX_SDCLK_SEL1_MASK, 1)
+
+#define SPACEMIT_SDHC_DLINE_CTRL_REG    0x130
+#define  SDHC_DLINE_PU                  BIT(0)
+#define  SDHC_RX_DLINE_CODE_MASK        GENMASK(23, 16)
+#define  SDHC_TX_DLINE_CODE_MASK        GENMASK(31, 24)
+
+#define SPACEMIT_SDHC_DLINE_CFG_REG     0x134
+#define  SDHC_RX_DLINE_REG_MASK         GENMASK(7, 0)
+#define  SDHC_RX_DLINE_GAIN             BIT(8)
+#define  SDHC_TX_DLINE_REG_MASK         GENMASK(23, 16)
+
+#define SPACEMIT_RX_DLINE_REG          9
+#define SPACEMIT_RX_TUNE_DELAY_MIN     0x0
+#define SPACEMIT_RX_TUNE_DELAY_MAX     0xFF
+
+#define SPACEMIT_TX_TUNING_DLINE_REG   0x00
+#define SPACEMIT_TX_TUNING_DELAYCODE   127
+
 struct spacemit_sdhci_host {
        struct clk *clk_core;
        struct clk *clk_io;
@@ -96,6 +118,50 @@ static inline void spacemit_sdhci_clrsetbits(struct sdhci_host *host, u32 clr, u
        sdhci_writel(host, val, reg);
 }
 
+static void spacemit_sdhci_set_rx_delay(struct sdhci_host *host, u8 delay)
+{
+       spacemit_sdhci_clrsetbits(host, SDHC_RX_DLINE_CODE_MASK,
+                                 FIELD_PREP(SDHC_RX_DLINE_CODE_MASK, delay),
+                                 SPACEMIT_SDHC_DLINE_CTRL_REG);
+}
+
+static void spacemit_sdhci_set_tx_delay(struct sdhci_host *host, u8 delay)
+{
+       spacemit_sdhci_clrsetbits(host, SDHC_TX_DLINE_CODE_MASK,
+                                 FIELD_PREP(SDHC_TX_DLINE_CODE_MASK, delay),
+                                 SPACEMIT_SDHC_DLINE_CTRL_REG);
+}
+
+static void spacemit_sdhci_set_tx_dline_reg(struct sdhci_host *host, u8 dline_reg)
+{
+       spacemit_sdhci_clrsetbits(host, SDHC_TX_DLINE_REG_MASK,
+                                 FIELD_PREP(SDHC_TX_DLINE_REG_MASK, dline_reg),
+                                 SPACEMIT_SDHC_DLINE_CFG_REG);
+}
+
+static void spacemit_sdhci_tx_tuning_prepare(struct sdhci_host *host)
+{
+       spacemit_sdhci_setbits(host, SDHC_TX_MUX_SEL, SPACEMIT_SDHC_TX_CFG_REG);
+       spacemit_sdhci_setbits(host, SDHC_DLINE_PU, SPACEMIT_SDHC_DLINE_CTRL_REG);
+       udelay(5);
+}
+
+static void spacemit_sdhci_prepare_tuning(struct sdhci_host *host)
+{
+       spacemit_sdhci_clrsetbits(host, SDHC_RX_DLINE_REG_MASK,
+                                 FIELD_PREP(SDHC_RX_DLINE_REG_MASK, SPACEMIT_RX_DLINE_REG),
+                                 SPACEMIT_SDHC_DLINE_CFG_REG);
+
+       spacemit_sdhci_setbits(host, SDHC_DLINE_PU, SPACEMIT_SDHC_DLINE_CTRL_REG);
+       udelay(5);
+
+       spacemit_sdhci_clrsetbits(host, SDHC_RX_SDCLK_SEL1_MASK, SDHC_RX_SDCLK_SEL1,
+                                 SPACEMIT_SDHC_RX_CFG_REG);
+
+       if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200)
+               spacemit_sdhci_setbits(host, SDHC_HS200_USE_RFIFO, SPACEMIT_SDHC_PHY_FUNC_REG);
+}
+
 static void spacemit_sdhci_reset(struct sdhci_host *host, u8 mask)
 {
        sdhci_reset(host, mask);
@@ -191,6 +257,111 @@ static unsigned int spacemit_sdhci_clk_get_max_clock(struct sdhci_host *host)
        return clk_get_rate(pltfm_host->clk);
 }
 
+static int spacemit_sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
+{
+       int current_len = 0, current_start = 0;
+       int max_pass_len = 0, max_pass_start = 0;
+       struct mmc_host *mmc = host->mmc;
+       struct mmc_ios ios = mmc->ios;
+       u8 final_delay;
+       int ret = 0;
+       int i;
+
+       /*
+        * Tuning is required for SDR50/SDR104, HS200/HS400 cards and
+        * if clock frequency is greater than 100MHz in these modes.
+        */
+       if (host->clock < 100 * 1000 * 1000 ||
+           !(ios.timing == MMC_TIMING_MMC_HS200 ||
+             ios.timing == MMC_TIMING_UHS_SDR50 ||
+             ios.timing == MMC_TIMING_UHS_SDR104))
+               return 0;
+
+       if (mmc->caps2 & MMC_CAP2_NO_MMC) {
+               spacemit_sdhci_set_tx_dline_reg(host, SPACEMIT_TX_TUNING_DLINE_REG);
+               spacemit_sdhci_set_tx_delay(host, SPACEMIT_TX_TUNING_DELAYCODE);
+               spacemit_sdhci_tx_tuning_prepare(host);
+
+               dev_dbg(mmc_dev(host->mmc), "TX tuning: dline_reg=%d, delaycode=%d\n",
+                       SPACEMIT_TX_TUNING_DLINE_REG, SPACEMIT_TX_TUNING_DELAYCODE);
+       }
+
+       spacemit_sdhci_prepare_tuning(host);
+
+       for (i = SPACEMIT_RX_TUNE_DELAY_MIN; i <= SPACEMIT_RX_TUNE_DELAY_MAX; i++) {
+               spacemit_sdhci_set_rx_delay(host, i);
+               ret = mmc_send_tuning(host->mmc, opcode, NULL);
+
+               dev_dbg(mmc_dev(host->mmc), "RX delay %d: %s\n",
+                       i, ret == 0 ? "pass" : "fail");
+
+               if (ret == 0) {
+                       /* Test passed - extend current window */
+                       if (current_len == 0)
+                               current_start = i;
+                       current_len++;
+               } else {
+                       /* Test failed - check if current window is best so far */
+                       if (current_len > max_pass_len) {
+                               max_pass_len = current_len;
+                               max_pass_start = current_start;
+                       }
+                       current_len = 0;
+               }
+       }
+
+       if (current_len > max_pass_len) {
+               max_pass_len = current_len;
+               max_pass_start = current_start;
+       }
+
+       if (max_pass_len < 3) {
+               dev_err(mmc_dev(host->mmc), "Tuning failed: no stable window found\n");
+               return -EIO;
+       }
+
+       final_delay = max_pass_start + max_pass_len / 2;
+       spacemit_sdhci_set_rx_delay(host, final_delay);
+       ret = mmc_send_tuning(host->mmc, opcode, NULL);
+       if (ret) {
+               u8 retry_delays[] = {
+                       max_pass_start + max_pass_len / 4,
+                       max_pass_start + (3 * max_pass_len) / 4,
+                       max_pass_start,
+                       max_pass_start + max_pass_len - 1
+               };
+               int retry_count = ARRAY_SIZE(retry_delays);
+
+               dev_warn(mmc_dev(mmc), "Primary delay %d failed, trying alternatives\n",
+                        final_delay);
+
+               for (i = 0; i < retry_count; i++) {
+                       if (retry_delays[i] >= SPACEMIT_RX_TUNE_DELAY_MIN &&
+                           retry_delays[i] <= SPACEMIT_RX_TUNE_DELAY_MAX) {
+                               spacemit_sdhci_set_rx_delay(host, retry_delays[i]);
+                               ret = mmc_send_tuning(host->mmc, opcode, NULL);
+                               if (!ret) {
+                                       final_delay = retry_delays[i];
+                                       dev_info(mmc_dev(mmc), "Retry successful with delay %d\n",
+                                                final_delay);
+                                       break;
+                               }
+                       }
+               }
+
+               if (ret) {
+                       dev_err(mmc_dev(mmc), "All retry attempts failed\n");
+                       return -EIO;
+               }
+       }
+
+       dev_dbg(mmc_dev(host->mmc),
+               "Tuning successful: window %d-%d, using delay %d\n",
+               max_pass_start, max_pass_start + max_pass_len - 1, final_delay);
+
+       return 0;
+}
+
 static int spacemit_sdhci_pre_select_hs400(struct mmc_host *mmc)
 {
        struct sdhci_host *host = mmc_priv(mmc);
@@ -326,6 +497,7 @@ static const struct sdhci_ops spacemit_sdhci_ops = {
        .set_bus_width          = sdhci_set_bus_width,
        .set_clock              = spacemit_sdhci_set_clock,
        .set_uhs_signaling      = spacemit_sdhci_set_uhs_signaling,
+       .platform_execute_tuning = spacemit_sdhci_execute_tuning,
 };
 
 static const struct sdhci_pltfm_data spacemit_sdhci_k1_pdata = {