From: Sascha Hauer <s.hauer@pengutronix.de>
To: BAREBOX <barebox@lists.infradead.org>
Subject: [PATCH v2 04/10] mci: add HS400 mode selection
Date: Mon, 18 May 2026 15:13:39 +0200 [thread overview]
Message-ID: <20260518-rockchip-emmc-hs400-v2-4-789ce495f70b@pengutronix.de> (raw)
In-Reply-To: <20260518-rockchip-emmc-hs400-v2-0-789ce495f70b@pengutronix.de>
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 <s.hauer@pengutronix.de>
---
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
next prev parent reply other threads:[~2026-05-18 13:15 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-18 13:13 [PATCH v2 00/10] mci: rockchip-dwcmshc: add HS400(ES) support Sascha Hauer
2026-05-18 13:13 ` [PATCH v2 01/10] mci: sdhci: define VDD_180 and shrink UHS_MASK to bits 0..2 Sascha Hauer
2026-05-18 13:13 ` [PATCH v2 02/10] mci: mmc_send_tuning: actually point data.dest at the buffer Sascha Hauer
2026-05-18 13:13 ` [PATCH v2 03/10] mci: sdhci: add ADMA2 descriptor helpers Sascha Hauer
2026-05-18 13:13 ` Sascha Hauer [this message]
2026-05-18 13:13 ` [PATCH v2 05/10] mci: add HS400 Enhanced Strobe (HS400ES) selection Sascha Hauer
2026-05-18 13:13 ` [PATCH v2 06/10] mci: rockchip-dwcmshc-sdhci: use ADMA2 Sascha Hauer
2026-05-18 13:18 ` Ahmad Fatoum
2026-05-18 13:13 ` [PATCH v2 07/10] mci: sdhci: rockchip: set TX-path source-select bit in DWCMSHC_EMMC_DLL_TXCLK Sascha Hauer
2026-05-18 13:13 ` [PATCH v2 08/10] mci: sdhci: rockchip: distinguish IP revision 0 (rk3568) from 1 (rk3576/rk3588) Sascha Hauer
2026-05-18 13:13 ` [PATCH v2 09/10] mci: sdhci: rockchip: support HS400 Sascha Hauer
2026-05-18 13:13 ` [PATCH v2 10/10] mci: sdhci: rockchip: support HS400 Enhanced Strobe Sascha Hauer
2026-05-19 6:16 ` [PATCH v2 00/10] mci: rockchip-dwcmshc: add HS400(ES) support Sascha Hauer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260518-rockchip-emmc-hs400-v2-4-789ce495f70b@pengutronix.de \
--to=s.hauer@pengutronix.de \
--cc=barebox@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox