eMMC HS400 transitions through HS and 8-bit DDR per JEDEC, on top of
a card already tuned in HS200. Wire that up in the core:

- mmc_select_max_dtr() now records EXT_CSD_CARD_TYPE_HS400_{1_8V,1_2V}
  in mmc_avail_type when both the host (mmc-hs400-1_8v / mmc-hs400-1_2v
  in DT) and the card advertise it. HS400 reuses hs200_max_dtr (200 MHz)
  since it's the same SDCLK rate, just DDR-sampled.

- mmc_set_bus_speed() treats HS200 and HS400 as the same rate tier.

- mmc_select_hs400() implements the JEDEC sequence:
    1. CMD6 EXT_CSD_HS_TIMING = HS
    2. host: MMC_TIMING_MMC_HS, clock = hs_max_dtr
    3. CMD6 EXT_CSD_BUS_WIDTH = EXT_CSD_DDR_BUS_WIDTH_8
    4. CMD6 EXT_CSD_HS_TIMING = HS400
    5. host: MMC_TIMING_MMC_HS400, clock back to 200 MHz

  Bails early if the card lacks HS400 capability or the bus isn't 8-bit.

- mci_startup_mmc() runs mmc_select_hs400() right after a successful
  HS200 tuning when EXT_CSD_CARD_TYPE_HS400 is in mmc_avail_type.

- mmc_card_hs400() helper added in mci.h, mirroring mmc_card_hs200().

Host drivers must additionally handle MMC_TIMING_MMC_HS400 in their
set_ios / set_clock paths (e.g. setting the controller-specific HS400
bit in HOST_CONTROL2, configuring data-strobe sampling). Without that
the core will run the transition but the host won't sample correctly.

Assisted-by: Claude Opus 4.7
Signed-off-by: Sascha Hauer <[email protected]>
---
 drivers/mci/mci-core.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++---
 include/mci.h          |  5 +++
 2 files changed, 86 insertions(+), 4 deletions(-)

diff --git a/drivers/mci/mci-core.c b/drivers/mci/mci-core.c
index 23b9117869..8647cfb220 100644
--- a/drivers/mci/mci-core.c
+++ b/drivers/mci/mci-core.c
@@ -126,7 +126,8 @@ static int mci_set_blocklen(struct mci *mci, unsigned len)
 {
        struct mci_cmd cmd = {0};
 
-       if (mci->host->ios.timing == MMC_TIMING_MMC_DDR52)
+       if (mci->host->ios.timing == MMC_TIMING_MMC_DDR52 ||
+           mmc_card_hs400(mci))
                return 0;
 
        mci_setup_cmd(&cmd, MMC_CMD_SET_BLOCKLEN, len, MMC_RSP_R1);
@@ -1051,6 +1052,8 @@ static const char *mci_timing_tostr(unsigned timing)
                return "MMC DDR52";
        case MMC_TIMING_MMC_HS200:
                return "HS200";
+       case MMC_TIMING_MMC_HS400:
+               return "HS400";
        default:
                return "unknown"; /* shouldn't happen */
        }
@@ -1783,6 +1786,18 @@ static void mmc_select_max_dtr(struct mci *mci)
                avail_type |= EXT_CSD_CARD_TYPE_HS200_1_2V;
        }
 
+       if ((caps2 & MMC_CAP2_HS400_1_8V) &&
+           (card_type & EXT_CSD_CARD_TYPE_HS400_1_8V)) {
+               hs200_max_dtr = MMC_HS200_MAX_DTR;
+               avail_type |= EXT_CSD_CARD_TYPE_HS400_1_8V;
+       }
+
+       if ((caps2 & MMC_CAP2_HS400_1_2V) &&
+           (card_type & EXT_CSD_CARD_TYPE_HS400_1_2V)) {
+               hs200_max_dtr = MMC_HS200_MAX_DTR;
+               avail_type |= EXT_CSD_CARD_TYPE_HS400_1_2V;
+       }
+
        mci->host->hs200_max_dtr = hs200_max_dtr;
        mci->host->hs_max_dtr = hs_max_dtr;
        mci->host->mmc_avail_type = avail_type;
@@ -1875,7 +1890,7 @@ static void mmc_set_bus_speed(struct mci *mci)
 {
        unsigned int max_dtr = (unsigned int)-1;
 
-       if (mmc_card_hs200(mci) &&
+       if ((mmc_card_hs200(mci) || mmc_card_hs400(mci)) &&
                max_dtr > mci->host->hs200_max_dtr)
                max_dtr = mci->host->hs200_max_dtr;
        else if (mmc_card_hs(mci) && max_dtr > mci->host->hs_max_dtr)
@@ -1921,6 +1936,62 @@ int mmc_hs200_tuning(struct mci *mci)
        return mci_execute_tuning(mci);
 }
 
+/*
+ * Switch from HS200 to HS400 per JEDEC. The card must already be in HS200
+ * (with successful tuning) and 8-bit bus width. The transition sequence is:
+ *
+ *   1. Switch the card back to HS timing.
+ *   2. Drop the host clock to HS rate (<= 52 MHz).
+ *   3. Switch the card to 8-bit DDR bus width.
+ *   4. Switch the card to HS400 timing.
+ *   5. Switch the host to HS400 timing and ramp the clock back to HS200 rate
+ *      (200 MHz, used as DDR so effective 400 MHz / 8-bit data lane).
+ */
+static int mmc_select_hs400(struct mci *mci)
+{
+       struct mci_host *host = mci->host;
+       int err;
+       u8 val;
+
+       if (!(host->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400) ||
+           host->ios.bus_width != MMC_BUS_WIDTH_8)
+               return 0;
+
+       /* Step 1: switch card back to HS timing */
+       err = mci_switch(mci, EXT_CSD_HS_TIMING, EXT_CSD_TIMING_HS);
+       if (err) {
+               dev_err(&mci->dev, "switch to HS from HS200 failed: %d\n", err);
+               return err;
+       }
+
+       /* Step 2: drop host clock to HS rate */
+       mci_set_timing(mci, MMC_TIMING_MMC_HS);
+       mci_set_clock(mci, host->hs_max_dtr);
+
+       /* Step 3: switch card to 8-bit DDR */
+       err = mci_switch(mci, EXT_CSD_BUS_WIDTH, EXT_CSD_DDR_BUS_WIDTH_8);
+       if (err) {
+               dev_err(&mci->dev, "switch to DDR for HS400 failed: %d\n", err);
+               return err;
+       }
+
+       /* Step 4: switch card to HS400 timing */
+       val = EXT_CSD_TIMING_HS400 | (host->drive_strength << 
EXT_CSD_DRV_STR_SHIFT);
+       err = mci_switch(mci, EXT_CSD_HS_TIMING, val);
+       if (err) {
+               dev_err(&mci->dev, "switch to HS400 failed: %d\n", err);
+               return err;
+       }
+
+       /* Step 5: switch host to HS400 timing and ramp clock */
+       mci_set_timing(mci, MMC_TIMING_MMC_HS400);
+       mmc_set_bus_speed(mci);
+
+       dev_dbg(&mci->dev, "HS400 selected\n");
+
+       return 0;
+}
+
 static int mci_startup_mmc(struct mci *mci)
 {
        struct mci_host *host = mci->host;
@@ -1947,6 +2018,10 @@ static int mci_startup_mmc(struct mci *mci)
                        ret = mmc_hs200_tuning(mci);
                        if (!ret) {
                                dev_dbg(&mci->dev, "HS200 tuning succeeded\n");
+
+                               if (host->mmc_avail_type & 
EXT_CSD_CARD_TYPE_HS400)
+                                       mmc_select_hs400(mci);
+
                                return 0;
                        }
 
@@ -2490,7 +2565,7 @@ static int mci_sd_read(struct block_device *blk, void 
*buffer, sector_t block,
 
 static void mci_print_caps(unsigned caps, unsigned caps2)
 {
-       printf("  capabilities: %s%s%s%s%s%s%s%s%s%s\n",
+       printf("  capabilities: %s%s%s%s%s%s%s%s%s%s%s%s\n",
                caps & MMC_CAP_4_BIT_DATA ? "4bit " : "",
                caps & MMC_CAP_8_BIT_DATA ? "8bit " : "",
                caps & MMC_CAP_SD_HIGHSPEED ? "sd-hs " : "",
@@ -2500,7 +2575,9 @@ static void mci_print_caps(unsigned caps, unsigned caps2)
                caps & MMC_CAP_1_8V_DDR ? "ddr-1.8v " : "",
                caps & MMC_CAP_1_2V_DDR ? "ddr-1.2v " : "",
                caps2 & MMC_CAP2_HS200_1_8V_SDR ? "hs200-1.8v " : "",
-               caps2 & MMC_CAP2_HS200_1_2V_SDR ? "hs200-1.2v " : "");
+               caps2 & MMC_CAP2_HS200_1_2V_SDR ? "hs200-1.2v " : "",
+               caps2 & MMC_CAP2_HS400_1_8V ? "hs400-1.8v " : "",
+               caps2 & MMC_CAP2_HS400_1_2V ? "hs400-1.2v " : "");
 }
 
 /*
diff --git a/include/mci.h b/include/mci.h
index 8348f97432..a7cd0c5ae2 100644
--- a/include/mci.h
+++ b/include/mci.h
@@ -802,4 +802,9 @@ static inline bool mmc_card_hs200(struct mci *mci)
        return mci->host->ios.timing == MMC_TIMING_MMC_HS200;
 }
 
+static inline bool mmc_card_hs400(struct mci *mci)
+{
+       return mci->host->ios.timing == MMC_TIMING_MMC_HS400;
+}
+
 #endif /* _MCI_H_ */

-- 
2.47.3


Reply via email to