* [PATCH 1/8] spacemit: k1: select boot device via config registers
2026-06-12 20:18 [PATCH 0/8] Add SD card and eMMC support for SpacemiT K1 Raymond Mao
@ 2026-06-12 20:18 ` Raymond Mao
2026-06-13 3:50 ` Yao Zi
2026-06-12 20:18 ` [PATCH 2/8] mmc: k1: add sdhci platform driver Raymond Mao
` (6 subsequent siblings)
7 siblings, 1 reply; 11+ messages in thread
From: Raymond Mao @ 2026-06-12 20:18 UTC (permalink / raw)
To: u-boot
Cc: uboot, u-boot-spacemit, raymond.mao, rick, ycliang, trini, lukma,
hs, jh80.chung, peng.fan, xypron.glpk, randolph, dlan, junhui.liu,
neil.armstrong, quentin.schulz, samuel, raymondmaoca, Guodong Xu
From: Guodong Xu <guodong@riscstar.com>
Add logic to determine the current boot device by reading the
SoC's configuration registers, rather than using a hardcoded
default.
Signed-off-by: Guodong Xu <guodong@riscstar.com>
Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
board/spacemit/k1/spl.c | 77 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 76 insertions(+), 1 deletion(-)
diff --git a/board/spacemit/k1/spl.c b/board/spacemit/k1/spl.c
index 0db362130e4..d5e87d79fa2 100644
--- a/board/spacemit/k1/spl.c
+++ b/board/spacemit/k1/spl.c
@@ -21,6 +21,17 @@
#include <tlv_eeprom.h>
#include "tlv_codes.h"
+/* boot mode configs */
+#define BOOT_DEV_FLAG_REG 0xd4282d10
+#define BOOT_PIN_SEL_REG 0xd4282c20
+
+#define BOOT_STRAP_MODE_OFFSET 9
+#define BOOT_STRAP_MODE_MASK 3
+#define BOOT_STRAP_MODE_EMMC 0
+#define BOOT_STRAP_MODE_SPI 1
+#define BOOT_STRAP_MODE_NAND 2
+#define BOOT_STRAP_MODE_SD 3
+
#define MUX_MODE4 4
#define EDGE_NONE BIT(6)
#define PULL_UP (6 << 13) /* bit[15:13] 110 */
@@ -45,6 +56,17 @@ typedef void (*puts_func_t)(const char *s);
typedef int (*ddr_init_func_t)(u64 ddr_base, u32 cs_num, u32 data_rate,
puts_func_t puts);
+enum board_boot_mode {
+ BOOT_MODE_NONE = 0,
+ BOOT_MODE_USB = 0x55a,
+ BOOT_MODE_EMMC,
+ BOOT_MODE_NAND,
+ BOOT_MODE_SPI,
+ BOOT_MODE_SD,
+ BOOT_MODE_SHELL = 0x55f,
+ BOOT_MODE_BOOTSTRAP,
+};
+
struct ddr_cfg {
u32 data_rate;
u32 cs_num;
@@ -379,7 +401,60 @@ void board_init_f(ulong dummy)
u32 spl_boot_device(void)
{
- return BOOT_DEVICE_SPI;
+ void __iomem *boot_dev = (void __iomem *)BOOT_DEV_FLAG_REG;
+ void __iomem *boot_strap = (void __iomem *)BOOT_PIN_SEL_REG;
+ u32 mode, sel, ret = 0;
+
+ mode = readl(boot_dev);
+ if (mode == BOOT_MODE_NONE || mode > BOOT_MODE_SD) {
+ sel = readl(boot_strap);
+ sel >>= BOOT_STRAP_MODE_OFFSET;
+ sel &= BOOT_STRAP_MODE_MASK;
+ switch (sel) {
+ case BOOT_STRAP_MODE_EMMC:
+ mode = BOOT_MODE_EMMC;
+ break;
+ case BOOT_STRAP_MODE_NAND:
+ mode = BOOT_MODE_NAND;
+ break;
+ case BOOT_STRAP_MODE_SPI:
+ mode = BOOT_MODE_SPI;
+ break;
+ case BOOT_STRAP_MODE_SD:
+ default:
+ mode = BOOT_MODE_SD;
+ break;
+ }
+ }
+ /* TODO:
+ * The current upstream DTS file only contains the eMMC node. When
+ * the SD node is added via an overlay, the eMMC device ends up as
+ * MMC1 in SPL.
+ * However, the SD device should be the first device (MMC1).
+ * This sequence needs to be corrected once the SD node is merged
+ * into the upstream U-Boot DTS file.
+ */
+ switch (mode) {
+ case BOOT_MODE_EMMC:
+ ret = BOOT_DEVICE_MMC1;
+ break;
+ case BOOT_MODE_NAND:
+ ret = BOOT_DEVICE_NAND;
+ break;
+ case BOOT_MODE_SPI:
+ ret = BOOT_DEVICE_SPI;
+ break;
+ case BOOT_MODE_USB:
+ ret = BOOT_DEVICE_USB;
+ break;
+ case BOOT_MODE_SD:
+ ret = BOOT_DEVICE_MMC2;
+ break;
+ default:
+ ret = BOOT_DEVICE_MMC1;
+ break;
+ }
+ return ret;
}
void spl_board_init(void)
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH 1/8] spacemit: k1: select boot device via config registers
2026-06-12 20:18 ` [PATCH 1/8] spacemit: k1: select boot device via config registers Raymond Mao
@ 2026-06-13 3:50 ` Yao Zi
0 siblings, 0 replies; 11+ messages in thread
From: Yao Zi @ 2026-06-13 3:50 UTC (permalink / raw)
To: Raymond Mao, u-boot
Cc: uboot, u-boot-spacemit, raymond.mao, rick, ycliang, trini, lukma,
hs, jh80.chung, peng.fan, xypron.glpk, randolph, dlan, junhui.liu,
neil.armstrong, quentin.schulz, samuel, Guodong Xu, Yao Zi
On Fri, Jun 12, 2026 at 04:18:54PM -0400, Raymond Mao wrote:
> From: Guodong Xu <guodong@riscstar.com>
>
> Add logic to determine the current boot device by reading the
> SoC's configuration registers, rather than using a hardcoded
> default.
>
> Signed-off-by: Guodong Xu <guodong@riscstar.com>
> Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
> ---
> board/spacemit/k1/spl.c | 77 ++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 76 insertions(+), 1 deletion(-)
>
> diff --git a/board/spacemit/k1/spl.c b/board/spacemit/k1/spl.c
> index 0db362130e4..d5e87d79fa2 100644
> --- a/board/spacemit/k1/spl.c
> +++ b/board/spacemit/k1/spl.c
...
> @@ -379,7 +401,60 @@ void board_init_f(ulong dummy)
>
> u32 spl_boot_device(void)
> {
> - return BOOT_DEVICE_SPI;
> + void __iomem *boot_dev = (void __iomem *)BOOT_DEV_FLAG_REG;
> + void __iomem *boot_strap = (void __iomem *)BOOT_PIN_SEL_REG;
> + u32 mode, sel, ret = 0;
> +
> + mode = readl(boot_dev);
> + if (mode == BOOT_MODE_NONE || mode > BOOT_MODE_SD) {
> + sel = readl(boot_strap);
> + sel >>= BOOT_STRAP_MODE_OFFSET;
> + sel &= BOOT_STRAP_MODE_MASK;
Is FIELD_GET() helpful here?
> + switch (sel) {
> + case BOOT_STRAP_MODE_EMMC:
> + mode = BOOT_MODE_EMMC;
> + break;
> + case BOOT_STRAP_MODE_NAND:
> + mode = BOOT_MODE_NAND;
> + break;
> + case BOOT_STRAP_MODE_SPI:
> + mode = BOOT_MODE_SPI;
> + break;
> + case BOOT_STRAP_MODE_SD:
> + default:
> + mode = BOOT_MODE_SD;
> + break;
> + }
> + }
> + /* TODO:
> + * The current upstream DTS file only contains the eMMC node. When
> + * the SD node is added via an overlay, the eMMC device ends up as
> + * MMC1 in SPL.
Why not fix this with an extra alias in the overlay dt?
> + * However, the SD device should be the first device (MMC1).
> + * This sequence needs to be corrected once the SD node is merged
> + * into the upstream U-Boot DTS file.
> + */
> + switch (mode) {
> + case BOOT_MODE_EMMC:
> + ret = BOOT_DEVICE_MMC1;
> + break;
> + case BOOT_MODE_NAND:
> + ret = BOOT_DEVICE_NAND;
> + break;
> + case BOOT_MODE_SPI:
> + ret = BOOT_DEVICE_SPI;
> + break;
> + case BOOT_MODE_USB:
> + ret = BOOT_DEVICE_USB;
> + break;
> + case BOOT_MODE_SD:
> + ret = BOOT_DEVICE_MMC2;
> + break;
> + default:
> + ret = BOOT_DEVICE_MMC1;
> + break;
> + }
> + return ret;
> }
>
> void spl_board_init(void)
> --
> 2.25.1
>
Regards,
Yao Zi
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 2/8] mmc: k1: add sdhci platform driver
2026-06-12 20:18 [PATCH 0/8] Add SD card and eMMC support for SpacemiT K1 Raymond Mao
2026-06-12 20:18 ` [PATCH 1/8] spacemit: k1: select boot device via config registers Raymond Mao
@ 2026-06-12 20:18 ` Raymond Mao
2026-06-13 6:43 ` Yao Zi
2026-06-12 20:18 ` [PATCH 3/8] dts: k1: add SD card support in u-boot overlay Raymond Mao
` (5 subsequent siblings)
7 siblings, 1 reply; 11+ messages in thread
From: Raymond Mao @ 2026-06-12 20:18 UTC (permalink / raw)
To: u-boot
Cc: uboot, u-boot-spacemit, raymond.mao, rick, ycliang, trini, lukma,
hs, jh80.chung, peng.fan, xypron.glpk, randolph, dlan, junhui.liu,
neil.armstrong, quentin.schulz, samuel, raymondmaoca, Guodong Xu
From: Guodong Xu <guodong@riscstar.com>
Add SDHCI platform driver support for SpacemiT K1 SoC. This driver
implements the necessary platform-specific operations for the SDHCI
controller, enabling MMC/SD card functionality on K1-based platforms.
Signed-off-by: Guodong Xu <guodong@riscstar.com>
Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
drivers/mmc/Kconfig | 7 +
drivers/mmc/Makefile | 1 +
drivers/mmc/spacemit_sdhci.c | 934 +++++++++++++++++++++++++++++++++++
3 files changed, 942 insertions(+)
create mode 100644 drivers/mmc/spacemit_sdhci.c
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig
index 24bd16ad5f3..7914305aae1 100644
--- a/drivers/mmc/Kconfig
+++ b/drivers/mmc/Kconfig
@@ -722,6 +722,13 @@ config MMC_SDHCI_SNPS
If unsure, say N.
+config MMC_SDHCI_SPACEMIT
+ bool "Spacemit SDHCI controller"
+ depends on MMC_SDHCI
+ help
+ Support for Secure Digital Host Controller Interface (SDHCI) on
+ Spacemit K1 SoC.
+
config MMC_SDHCI_STI
bool "SDHCI support for STMicroelectronics SoC"
depends on MMC_SDHCI && OF_CONTROL && ARCH_STI
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
index a23336d7d8d..aa05cec23be 100644
--- a/drivers/mmc/Makefile
+++ b/drivers/mmc/Makefile
@@ -71,6 +71,7 @@ obj-$(CONFIG_MMC_SDHCI_ROCKCHIP) += rockchip_sdhci.o
obj-$(CONFIG_MMC_SDHCI_ADI) += adi_sdhci.o
obj-$(CONFIG_MMC_SDHCI_S5P) += s5p_sdhci.o
obj-$(CONFIG_MMC_SDHCI_SNPS) += snps_sdhci.o
+obj-$(CONFIG_MMC_SDHCI_SPACEMIT) += spacemit_sdhci.o
obj-$(CONFIG_MMC_SDHCI_STI) += sti_sdhci.o
obj-$(CONFIG_MMC_SDHCI_TANGIER) += tangier_sdhci.o
obj-$(CONFIG_MMC_SDHCI_TEGRA) += tegra_mmc.o
diff --git a/drivers/mmc/spacemit_sdhci.c b/drivers/mmc/spacemit_sdhci.c
new file mode 100644
index 00000000000..392ca389fa9
--- /dev/null
+++ b/drivers/mmc/spacemit_sdhci.c
@@ -0,0 +1,934 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Spacemit K1x Mobile Storage Host Controller
+ *
+ * Copyright (C) 2023 Spacemit Inc.
+ * Copyright (C) 2026 RISCstar Ltd.
+ */
+
+#define LOG_CATEGORY UCLASS_MMC
+
+#include <clk.h>
+#include <dm.h>
+#include <dm/pinctrl.h>
+#include <fdtdec.h>
+#include <linux/libfdt.h>
+#include <linux/delay.h>
+#include <log.h>
+#include <malloc.h>
+#include <sdhci.h>
+#include <reset-uclass.h>
+#include <power/regulator.h>
+#include <mapmem.h>
+
+/* SDH register definitions */
+#define SDHC_OP_EXT_REG 0x108
+#define OVRRD_CLK_OEN 0x0800
+#define FORCE_CLK_ON 0x1000
+
+#define SDHC_LEGACY_CTRL_REG 0x10C
+#define GEN_PAD_CLK_ON 0x0040
+
+#define SDHC_MMC_CTRL_REG 0x114
+#define MISC_INT_EN 0x0002
+#define MISC_INT 0x0004
+#define ENHANCE_STROBE_EN 0x0100
+#define MMC_HS400 0x0200
+#define MMC_HS200 0x0400
+#define MMC_CARD_MODE 0x1000
+
+#define SDHC_RX_CFG_REG 0x118
+#define RX_SDCLK_SEL0_MASK 0x03
+#define RX_SDCLK_SEL0_SHIFT 0x00
+#define RX_SDCLK_SEL0 0x02
+#define RX_SDCLK_SEL1_MASK 0x03
+#define RX_SDCLK_SEL1_SHIFT 0x02
+#define RX_SDCLK_SEL1 0x01
+
+#define SDHC_TX_CFG_REG 0x11C
+#define TX_INT_CLK_SEL 0x40000000
+#define TX_MUX_SEL 0x80000000
+
+#define SDHC_DLINE_CTRL_REG 0x130
+#define DLINE_PU 0x01
+#define RX_DLINE_CODE_MASK 0xFF
+#define RX_DLINE_CODE_SHIFT 0x10
+#define TX_DLINE_CODE_MASK 0xFF
+#define TX_DLINE_CODE_SHIFT 0x18
+
+#define SDHC_DLINE_CFG_REG 0x134
+#define RX_DLINE_REG_MASK 0xFF
+#define RX_DLINE_REG_SHIFT 0x00
+#define RX_DLINE_GAIN_MASK 0x1
+#define RX_DLINE_GAIN_SHIFT 0x8
+#define RX_DLINE_GAIN 0x1
+#define TX_DLINE_REG_MASK 0xFF
+#define TX_DLINE_REG_SHIFT 0x10
+
+#define SDHC_PHY_CTRL_REG 0x160
+#define PHY_FUNC_EN 0x0001
+#define PHY_PLL_LOCK 0x0002
+#define HOST_LEGACY_MODE 0x80000000
+
+#define SDHC_PHY_FUNC_REG 0x164
+#define PHY_TEST_EN 0x0080
+#define HS200_USE_RFIFO 0x8000
+
+#define SDHC_PHY_DLLCFG 0x168
+#define DLL_PREDLY_NUM 0x04
+#define DLL_FULLDLY_RANGE 0x10
+#define DLL_VREG_CTRL 0x40
+#define DLL_ENABLE 0x80000000
+#define DLL_REFRESH_SWEN_SHIFT 0x1C
+#define DLL_REFRESH_SW_SHIFT 0x1D
+
+#define SDHC_PHY_DLLCFG1 0x16C
+#define DLL_REG2_CTRL 0x0C
+#define DLL_REG3_CTRL_MASK 0xFF
+#define DLL_REG3_CTRL_SHIFT 0x10
+#define DLL_REG2_CTRL_MASK 0xFF
+#define DLL_REG2_CTRL_SHIFT 0x08
+#define DLL_REG1_CTRL 0x92
+#define DLL_REG1_CTRL_MASK 0xFF
+#define DLL_REG1_CTRL_SHIFT 0x00
+
+#define SDHC_PHY_DLLSTS 0x170
+#define DLL_LOCK_STATE 0x01
+
+#define SDHC_PHY_DLLSTS1 0x174
+#define DLL_MASTER_DELAY_MASK 0xFF
+#define DLL_MASTER_DELAY_SHIFT 0x10
+
+#define SDHC_PHY_PADCFG_REG 0x178
+#define RX_BIAS_CTRL_SHIFT 0x5
+#define PHY_DRIVE_SEL_SHIFT 0x0
+#define PHY_DRIVE_SEL_MASK 0x7
+#define PHY_DRIVE_SEL_DEFAULT 0x4
+
+#define MMC1_IO_V18EN 0x04
+#define AKEY_ASFAR 0xBABA
+#define AKEY_ASSAR 0xEB10
+
+#define SDHC_RX_TUNE_DELAY_MIN 0x0
+#define SDHC_RX_TUNE_DELAY_MAX 0xFF
+#define SDHC_RX_TUNE_DELAY_STEP 0x1
+
+#define CANDIDATE_WIN_NUM 3
+#define SELECT_DELAY_NUM 9
+#define WINDOW_1ST 0
+#define WINDOW_2ND 1
+#define WINDOW_3RD 2
+
+#define RX_TUNING_WINDOW_THRESHOLD 80
+#define RX_TUNING_DLINE_REG 0x09
+#define TX_TUNING_DLINE_REG 0x00
+#define TX_TUNING_DELAYCODE 127
+
+enum window_type {
+ LEFT_WINDOW = 0,
+ MIDDLE_WINDOW = 1,
+ RIGHT_WINDOW = 2,
+};
+
+struct tuning_window {
+ u8 type;
+ u8 min_delay;
+ u8 max_delay;
+};
+
+struct rx_tuning {
+ u8 rx_dline_reg;
+ u8 select_delay_num;
+ /* 0: biggest window, 1: bigger, 2: small */
+ struct tuning_window windows[CANDIDATE_WIN_NUM];
+ u8 select_delay[SELECT_DELAY_NUM];
+
+ u8 window_limit;
+};
+
+struct spacemit_sdhci_plat {
+ struct mmc_config cfg;
+ struct mmc mmc;
+ struct reset_ctl_bulk resets;
+ struct clk_bulk clks;
+
+ u32 aib_mmc1_io_reg;
+ u32 apbc_asfar_reg;
+ u32 apbc_assar_reg;
+
+ u8 tx_dline_reg;
+ u8 tx_delaycode;
+ struct rx_tuning rxtuning;
+};
+
+struct spacemit_sdhci_priv {
+ struct sdhci_host host;
+};
+
+static const u32 tuning_pattern4[16] = {
+ 0x00ff0fff, 0xccc3ccff, 0xffcc3cc3, 0xeffefffe,
+ 0xddffdfff, 0xfbfffbff, 0xff7fffbf, 0xefbdf777,
+ 0xf0fff0ff, 0x3cccfc0f, 0xcfcc33cc, 0xeeffefff,
+ 0xfdfffdff, 0xffbfffdf, 0xfff7ffbb, 0xde7b7ff7,
+};
+
+static const u32 tuning_pattern8[32] = {
+ 0xff00ffff, 0x0000ffff, 0xccccffff, 0xcccc33cc,
+ 0xcc3333cc, 0xffffcccc, 0xffffeeff, 0xffeeeeff,
+ 0xffddffff, 0xddddffff, 0xbbffffff, 0xbbffffff,
+ 0xffffffbb, 0xffffff77, 0x77ff7777, 0xffeeddbb,
+ 0x00ffffff, 0x00ffffff, 0xccffff00, 0xcc33cccc,
+ 0x3333cccc, 0xffcccccc, 0xffeeffff, 0xeeeeffff,
+ 0xddffffff, 0xddffffff, 0xffffffdd, 0xffffffbb,
+ 0xffffbbbb, 0xffff77ff, 0xff7777ff, 0xeeddbb77,
+};
+
+/*
+ * Reference: PMU_SDH0_CLK_RES_CTRL (0x054), SDH0_CLK_SEL=0x0,
+ * SDH0_CLK_DIV=0x1. The default clock source is 204.8 MHz
+ * (pll1_d6_409p6Mhz / 2).
+ *
+ * During start-up, use a 200 kHz frequency.
+ */
+#define SDHC_DEFAULT_MAX_CLOCK (204800000)
+#define SDHC_MIN_CLOCK (200 * 1000)
+
+static void spacemit_sdhci_phy_init(struct udevice *dev,
+ struct sdhci_host *host)
+{
+ u32 reg = 0;
+
+ if (dev_read_bool(dev, "no-sd") && dev_read_bool(dev, "no-sdio")) {
+ /* MMC card mode */
+ reg = sdhci_readl(host, SDHC_MMC_CTRL_REG);
+ reg |= MMC_CARD_MODE;
+ sdhci_writel(host, reg, SDHC_MMC_CTRL_REG);
+
+ /* Use PHY functional mode */
+ reg = sdhci_readl(host, SDHC_PHY_CTRL_REG);
+ reg |= (PHY_FUNC_EN | PHY_PLL_LOCK);
+ sdhci_writel(host, reg, SDHC_PHY_CTRL_REG);
+
+ reg = sdhci_readl(host, SDHC_PHY_PADCFG_REG);
+ reg |= (1 << RX_BIAS_CTRL_SHIFT);
+ sdhci_writel(host, reg, SDHC_PHY_PADCFG_REG);
+ } else {
+ reg = sdhci_readl(host, SDHC_TX_CFG_REG);
+ reg |= TX_INT_CLK_SEL;
+ sdhci_writel(host, reg, SDHC_TX_CFG_REG);
+ }
+
+ reg = sdhci_readl(host, SDHC_MMC_CTRL_REG);
+ reg &= ~ENHANCE_STROBE_EN;
+ sdhci_writel(host, reg, SDHC_MMC_CTRL_REG);
+}
+
+static int spacemit_sdhci_set_vqmmc_voltage(struct mmc *mmc, int voltage)
+{
+#if CONFIG_IS_ENABLED(DM_REGULATOR)
+ int ret;
+
+ if (!mmc->vqmmc_supply)
+ return 0;
+
+ ret = regulator_set_value(mmc->vqmmc_supply, voltage);
+ if (ret) {
+ log_err("failed to set vqmmc voltage to %d.%dV\n",
+ voltage / 1000000, (voltage / 100000) % 10);
+ return ret;
+ }
+ ret = regulator_set_enable_if_allowed(mmc->vqmmc_supply, true);
+ if (ret) {
+ log_err("failed to enable vqmmc supply\n");
+ return ret;
+ }
+#endif
+ return 0;
+}
+
+static void spacemit_sdhci_set_voltage(struct sdhci_host *host)
+{
+ if (IS_ENABLED(CONFIG_MMC_IO_VOLTAGE)) {
+ struct mmc *mmc = (struct mmc *)host->mmc;
+ u32 ctrl;
+
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+
+ switch (mmc->signal_voltage) {
+ case MMC_SIGNAL_VOLTAGE_330:
+ case MMC_SIGNAL_VOLTAGE_180: {
+ bool to_180 = mmc->signal_voltage ==
+ MMC_SIGNAL_VOLTAGE_180;
+ bool ok;
+ int voltage_mv = to_180 ? 1800000 : 3300000;
+
+ if (spacemit_sdhci_set_vqmmc_voltage(mmc, voltage_mv))
+ return;
+ if (!IS_SD(mmc))
+ return;
+ if (to_180)
+ ctrl |= SDHCI_CTRL_VDD_180;
+ else
+ ctrl &= ~SDHCI_CTRL_VDD_180;
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+
+ mdelay(5);
+
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ ok = !!(ctrl & SDHCI_CTRL_VDD_180) == to_180;
+ if (ok)
+ return;
+
+ log_err("%d.%dV regulator output not stable\n",
+ voltage_mv / 1000000,
+ (voltage_mv / 100000) % 10);
+ break;
+ }
+ default:
+ /* No signal voltage switch required */
+ return;
+ }
+ }
+}
+
+static void spacemit_sdhci_set_aib_mmc1_io(struct sdhci_host *host, int voltage)
+{
+ struct mmc *mmc = host->mmc;
+ struct spacemit_sdhci_plat *plat = dev_get_plat(mmc->dev);
+ void __iomem *aib_mmc1_io, *apbc_asfar, *apbc_assar;
+ u32 reg;
+
+ if (!plat->aib_mmc1_io_reg || !plat->apbc_asfar_reg ||
+ !plat->apbc_assar_reg)
+ return;
+
+ aib_mmc1_io = map_sysmem((uintptr_t)plat->aib_mmc1_io_reg,
+ sizeof(u32));
+ apbc_asfar = map_sysmem((uintptr_t)plat->apbc_asfar_reg,
+ sizeof(u32));
+ apbc_assar = map_sysmem((uintptr_t)plat->apbc_assar_reg,
+ sizeof(u32));
+
+ writel(AKEY_ASFAR, apbc_asfar);
+ writel(AKEY_ASSAR, apbc_assar);
+ reg = readl(aib_mmc1_io);
+
+ switch (voltage) {
+ case MMC_SIGNAL_VOLTAGE_180:
+ reg |= MMC1_IO_V18EN;
+ break;
+ default:
+ reg &= ~MMC1_IO_V18EN;
+ break;
+ }
+ writel(AKEY_ASFAR, apbc_asfar);
+ writel(AKEY_ASSAR, apbc_assar);
+ writel(reg, aib_mmc1_io);
+}
+
+static void spacemit_sdhci_set_clk_gate(struct sdhci_host *host, int auto_gate)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_OP_EXT_REG);
+ if (auto_gate)
+ reg &= ~(OVRRD_CLK_OEN | FORCE_CLK_ON);
+ else
+ reg |= (OVRRD_CLK_OEN | FORCE_CLK_ON);
+ sdhci_writel(host, reg, SDHC_OP_EXT_REG);
+}
+
+static bool spacemit_sdhci_is_voltage_switch_cmd(struct sdhci_host *host)
+{
+ struct mmc *mmc = host->mmc;
+ u32 cmd;
+
+ if (!IS_SD(mmc))
+ return false;
+
+ cmd = SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND));
+ return cmd == SD_CMD_SWITCH_UHS18V &&
+ mmc->signal_voltage == MMC_SIGNAL_VOLTAGE_180;
+}
+
+static int spacemit_sdhci_wait_dat0(struct udevice *dev, int state,
+ int timeout_us)
+{
+ struct mmc *mmc = mmc_get_mmc_dev(dev);
+ struct sdhci_host *host = mmc->priv;
+ unsigned long timeout = timer_get_us() + timeout_us;
+ u32 tmp;
+
+ /*
+ * readx_poll_timeout is unsuitable because sdhci_readl accepts
+ * two arguments
+ */
+ do {
+ tmp = sdhci_readl(host, SDHCI_PRESENT_STATE);
+ if (!!(tmp & SDHCI_DATA_0_LVL_MASK) == !!state) {
+ if (spacemit_sdhci_is_voltage_switch_cmd(host))
+ spacemit_sdhci_set_clk_gate(host, 1);
+ return 0;
+ }
+ } while (!timeout_us || !time_after(timer_get_us(), timeout));
+
+ return -ETIMEDOUT;
+}
+
+static void spacemit_sdhci_set_control_reg(struct sdhci_host *host)
+{
+ struct mmc *mmc = host->mmc;
+ u32 reg;
+
+ spacemit_sdhci_set_voltage(host);
+ spacemit_sdhci_set_aib_mmc1_io(host, mmc->signal_voltage);
+
+ if (spacemit_sdhci_is_voltage_switch_cmd(host))
+ spacemit_sdhci_set_clk_gate(host, 0);
+
+ /*
+ * Set TX_INT_CLK_SEL to guarantee hold time at default speed,
+ * HS, SDR12/SDR25/SDR50 modes. See SDHC_TX_CFG_REG (0x11c).
+ */
+ reg = sdhci_readl(host, SDHC_TX_CFG_REG);
+ if (mmc->selected_mode == MMC_LEGACY ||
+ mmc->selected_mode == MMC_HS ||
+ mmc->selected_mode == SD_HS ||
+ mmc->selected_mode == UHS_SDR12 ||
+ mmc->selected_mode == UHS_SDR25 ||
+ mmc->selected_mode == UHS_SDR50) {
+ reg |= TX_INT_CLK_SEL;
+ } else {
+ reg &= ~TX_INT_CLK_SEL;
+ }
+ sdhci_writel(host, reg, SDHC_TX_CFG_REG);
+
+ /* Set pinctrl state */
+ if (IS_ENABLED(CONFIG_PINCTRL)) {
+ if (mmc->clock >= 200000000)
+ pinctrl_select_state(mmc->dev, "fast");
+ else
+ pinctrl_select_state(mmc->dev, "default");
+ }
+
+ if (mmc->selected_mode == MMC_HS_200 ||
+ mmc->selected_mode == MMC_HS_400 ||
+ mmc->selected_mode == MMC_HS_400_ES) {
+ reg = sdhci_readw(host, SDHC_MMC_CTRL_REG);
+ if (mmc->selected_mode == MMC_HS_200)
+ reg |= MMC_HS200;
+ else
+ reg |= MMC_HS400;
+ sdhci_writew(host, reg, SDHC_MMC_CTRL_REG);
+ } else {
+ reg = sdhci_readw(host, SDHC_MMC_CTRL_REG);
+ reg &= ~(MMC_HS200 | MMC_HS400 | ENHANCE_STROBE_EN);
+ sdhci_writew(host, reg, SDHC_MMC_CTRL_REG);
+ }
+
+ sdhci_set_uhs_timing(host);
+}
+
+static void spacemit_sdhci_rx_tuning_prepare(struct sdhci_host *host,
+ u8 dline_reg)
+{
+ struct mmc *mmc = host->mmc;
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_DLINE_CFG_REG);
+ reg &= ~(RX_DLINE_REG_MASK << RX_DLINE_REG_SHIFT);
+ reg |= dline_reg << RX_DLINE_REG_SHIFT;
+
+ reg &= ~(RX_DLINE_GAIN_MASK << RX_DLINE_GAIN_SHIFT);
+ if (mmc->selected_mode == UHS_SDR50 && (reg & 0x40))
+ reg |= RX_DLINE_GAIN << RX_DLINE_GAIN_SHIFT;
+
+ sdhci_writel(host, reg, SDHC_DLINE_CFG_REG);
+
+ reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG);
+ reg |= DLINE_PU;
+ sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG);
+ udelay(5);
+
+ reg = sdhci_readl(host, SDHC_RX_CFG_REG);
+ reg &= ~(RX_SDCLK_SEL1_MASK << RX_SDCLK_SEL1_SHIFT);
+ reg |= RX_SDCLK_SEL1 << RX_SDCLK_SEL1_SHIFT;
+ sdhci_writel(host, reg, SDHC_RX_CFG_REG);
+
+ if (mmc->selected_mode == MMC_HS_200) {
+ reg = sdhci_readl(host, SDHC_PHY_FUNC_REG);
+ reg |= HS200_USE_RFIFO;
+ sdhci_writel(host, reg, SDHC_PHY_FUNC_REG);
+ }
+}
+
+static void spacemit_sdhci_rx_set_delaycode(struct sdhci_host *host, u32 delay)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG);
+ reg &= ~(RX_DLINE_CODE_MASK << RX_DLINE_CODE_SHIFT);
+ reg |= (delay & RX_DLINE_CODE_MASK) << RX_DLINE_CODE_SHIFT;
+ sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG);
+}
+
+static void spacemit_sdhci_tx_tuning_prepare(struct sdhci_host *host)
+{
+ u32 reg;
+
+ /* Set TX_MUX_SEL */
+ reg = sdhci_readl(host, SDHC_TX_CFG_REG);
+ reg |= TX_MUX_SEL;
+ sdhci_writel(host, reg, SDHC_TX_CFG_REG);
+
+ reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG);
+ reg |= DLINE_PU;
+ sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG);
+ udelay(5);
+}
+
+static void spacemit_sdhci_tx_set_dlinereg(struct sdhci_host *host,
+ u8 dline_reg)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_DLINE_CFG_REG);
+ reg &= ~(TX_DLINE_REG_MASK << TX_DLINE_REG_SHIFT);
+ reg |= dline_reg << TX_DLINE_REG_SHIFT;
+ sdhci_writel(host, reg, SDHC_DLINE_CFG_REG);
+}
+
+static void spacemit_sdhci_tx_set_delaycode(struct sdhci_host *host, u32 delay)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG);
+ reg &= ~(TX_DLINE_CODE_MASK << TX_DLINE_CODE_SHIFT);
+ reg |= (delay & TX_DLINE_CODE_MASK) << TX_DLINE_CODE_SHIFT;
+ sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG);
+}
+
+static void spacemit_sdhci_clear_set_irqs(struct sdhci_host *host,
+ u32 clr, u32 set)
+{
+ u32 ier;
+
+ ier = sdhci_readl(host, SDHCI_INT_ENABLE);
+ ier &= ~clr;
+ ier |= set;
+ sdhci_writel(host, ier, SDHCI_INT_ENABLE);
+ sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
+}
+
+static int spacemit_sdhci_tuning_pattern_check(struct sdhci_host *host)
+{
+ struct mmc *mmc = host->mmc;
+ u32 read_pattern;
+ unsigned int i;
+ u32 *tuning_pattern;
+ int pattern_len;
+ int err = 0;
+
+ if (mmc->bus_width == 8) {
+ tuning_pattern = (u32 *)tuning_pattern8;
+ pattern_len = ARRAY_SIZE(tuning_pattern8);
+ } else {
+ tuning_pattern = (u32 *)tuning_pattern4;
+ pattern_len = ARRAY_SIZE(tuning_pattern4);
+ }
+
+ for (i = 0; i < pattern_len; i++) {
+ read_pattern = sdhci_readl(host, SDHCI_BUFFER);
+ if (read_pattern != tuning_pattern[i])
+ err++;
+ }
+
+ return err;
+}
+
+static int spacemit_sdhci_send_tuning(struct sdhci_host *host, u32 opcode,
+ int *cmd_error)
+{
+ struct mmc *mmc = host->mmc;
+ struct mmc_cmd cmd;
+ int size, blk_size, err;
+
+ size = sizeof(tuning_pattern4);
+ cmd.cmdidx = opcode;
+ cmd.cmdarg = 0;
+ cmd.resp_type = MMC_RSP_R1;
+
+ blk_size = SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, 64);
+ sdhci_writew(host, blk_size, SDHCI_BLOCK_SIZE);
+ sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE);
+
+ err = mmc_send_cmd(mmc, &cmd, NULL);
+ if (err) {
+ log_warning("%s: tuning send cmd err: %d\n", host->name, err);
+ return err;
+ }
+ return 0;
+}
+
+static int spacemit_sdhci_send_tuning_cmd(struct sdhci_host *host, u32 opcode)
+{
+ int err = 0;
+
+ err = spacemit_sdhci_send_tuning(host, opcode, NULL);
+ if (err) {
+ log_warning("%s: send tuning err:%d\n", host->name, err);
+ return err;
+ }
+
+ err = spacemit_sdhci_tuning_pattern_check(host);
+ return err;
+}
+
+static void spacemit_sdhci_clear_tuned_clk(struct sdhci_host *host)
+{
+ u16 ctrl;
+
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ ctrl &= ~(SDHCI_CTRL_TUNED_CLK | SDHCI_CTRL_EXEC_TUNING);
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+}
+
+static int spacemit_sdhci_rx_select_window(struct sdhci_host *host, u32 opcode)
+{
+ int min, max;
+ int i, j, len;
+ int err = 0;
+ u32 ier;
+ struct tuning_window tmp;
+ struct mmc *mmc = host->mmc;
+ struct spacemit_sdhci_plat *plat = dev_get_plat(mmc->dev);
+ struct rx_tuning *rxtuning = &plat->rxtuning;
+
+ /* Change to PIO mode during tuning */
+ ier = sdhci_readl(host, SDHCI_INT_ENABLE);
+ spacemit_sdhci_clear_set_irqs(host, ier, SDHCI_INT_DATA_AVAIL);
+
+ min = SDHC_RX_TUNE_DELAY_MIN;
+ do {
+ /* Find the minimum delay that can pass tuning */
+ while (min < SDHC_RX_TUNE_DELAY_MAX) {
+ spacemit_sdhci_rx_set_delaycode(host, min);
+ err = spacemit_sdhci_send_tuning_cmd(host, opcode);
+ if (!err)
+ break;
+ spacemit_sdhci_clear_tuned_clk(host);
+ min += SDHC_RX_TUNE_DELAY_STEP;
+ }
+
+ /* Find the maximum delay that cannot pass tuning */
+ max = min + SDHC_RX_TUNE_DELAY_STEP;
+ while (max < SDHC_RX_TUNE_DELAY_MAX) {
+ spacemit_sdhci_rx_set_delaycode(host, max);
+ err = spacemit_sdhci_send_tuning_cmd(host, opcode);
+ if (err) {
+ spacemit_sdhci_clear_tuned_clk(host);
+ break;
+ }
+ max += SDHC_RX_TUNE_DELAY_STEP;
+ }
+
+ log_info("%s: pass window [%d %d)\n", host->name, min, max);
+ /* Store the top 3 windows */
+ if ((max - min) >= rxtuning->window_limit) {
+ tmp.max_delay = max;
+ tmp.min_delay = min;
+ tmp.type = MIDDLE_WINDOW;
+ for (i = 0; i < CANDIDATE_WIN_NUM; i++) {
+ len = rxtuning->windows[i].max_delay -
+ rxtuning->windows[i].min_delay;
+ if ((tmp.max_delay - tmp.min_delay) > len) {
+ for (j = CANDIDATE_WIN_NUM - 1;
+ j > i; j--)
+ rxtuning->windows[j] =
+ rxtuning->windows[j - 1];
+ rxtuning->windows[i] = tmp;
+ break;
+ }
+ }
+ }
+ min = max + SDHC_RX_TUNE_DELAY_STEP;
+ } while (min < SDHC_RX_TUNE_DELAY_MAX);
+
+ spacemit_sdhci_clear_set_irqs(host, SDHCI_INT_DATA_AVAIL, ier);
+ return 0;
+}
+
+static int spacemit_sdhci_rx_select_delay(struct sdhci_host *host)
+{
+ int i;
+ int win_len, min, max, mid;
+ u8 n;
+ struct tuning_window *window;
+ struct mmc *mmc = host->mmc;
+ struct spacemit_sdhci_plat *plat = dev_get_plat(mmc->dev);
+ struct rx_tuning *tuning = &plat->rxtuning;
+
+ for (i = 0; i < CANDIDATE_WIN_NUM; i++) {
+ window = &tuning->windows[i];
+ min = window->min_delay;
+ max = window->max_delay;
+ mid = (min + max - 1) / 2;
+ win_len = max - min;
+ if (win_len < tuning->window_limit)
+ continue;
+
+ n = tuning->select_delay_num;
+ if (window->type == LEFT_WINDOW) {
+ tuning->select_delay[n++] = min + win_len / 3;
+ tuning->select_delay[n++] = min + win_len / 2;
+ } else if (window->type == RIGHT_WINDOW) {
+ tuning->select_delay[n++] = max - win_len / 4;
+ tuning->select_delay[n++] = min - win_len / 3;
+ } else {
+ tuning->select_delay[n++] = mid;
+ tuning->select_delay[n++] = mid + win_len / 4;
+ tuning->select_delay[n++] = mid - win_len / 4;
+ }
+ tuning->select_delay_num = n;
+ }
+
+ return tuning->select_delay_num;
+}
+
+static int spacemit_sdhci_execute_tuning(struct mmc *mmc, u8 opcode)
+{
+ struct sdhci_host *host = mmc->priv;
+ struct spacemit_sdhci_plat *plat = dev_get_plat(mmc->dev);
+ struct rx_tuning *rxtuning = &plat->rxtuning;
+ int ret;
+
+ /*
+ * Tuning is required for SDR50/SDR104 mode
+ */
+ if (!IS_SD(host->mmc) ||
+ !(mmc->selected_mode == UHS_SDR50 ||
+ mmc->selected_mode == UHS_SDR104))
+ return 0;
+
+ /* TX tuning config */
+ if (IS_SD(host->mmc)) {
+ spacemit_sdhci_tx_set_dlinereg(host, plat->tx_dline_reg);
+ spacemit_sdhci_tx_set_delaycode(host, plat->tx_delaycode);
+ log_info("%s: set tx_delaycode: %d\n",
+ host->name, plat->tx_delaycode);
+ spacemit_sdhci_tx_tuning_prepare(host);
+ }
+
+ rxtuning->select_delay_num = 0;
+ memset(rxtuning->windows, 0, sizeof(rxtuning->windows));
+ memset(rxtuning->select_delay, 0xFF, sizeof(rxtuning->select_delay));
+
+ spacemit_sdhci_rx_tuning_prepare(host, rxtuning->rx_dline_reg);
+ ret = spacemit_sdhci_rx_select_window(host, opcode);
+ if (ret) {
+ log_warning("%s: abort tuning, err:%d\n", host->name, ret);
+ return ret;
+ }
+
+ if (!spacemit_sdhci_rx_select_delay(host)) {
+ log_warning("%s: fail to get delaycode\n", host->name);
+ return -EIO;
+ }
+
+ spacemit_sdhci_rx_set_delaycode(host, rxtuning->select_delay[0]);
+ log_info("%s: tuning done, use the firstly delay_code:%d\n",
+ host->name, rxtuning->select_delay[0]);
+ return 0;
+}
+
+#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT)
+static int spacemit_sdhci_phy_dll_init(struct sdhci_host *host)
+{
+ u32 reg;
+ int i;
+
+ /* Configure DLL_REG1 and DLL_REG2 */
+ reg = sdhci_readl(host, SDHC_PHY_DLLCFG);
+ reg |= (DLL_PREDLY_NUM | DLL_FULLDLY_RANGE | DLL_VREG_CTRL);
+ sdhci_writel(host, reg, SDHC_PHY_DLLCFG);
+
+ reg = sdhci_readl(host, SDHC_PHY_DLLCFG1);
+ reg |= (DLL_REG1_CTRL & DLL_REG1_CTRL_MASK);
+ sdhci_writel(host, reg, SDHC_PHY_DLLCFG1);
+
+ /* Enable DLL */
+ reg = sdhci_readl(host, SDHC_PHY_DLLCFG);
+ reg |= DLL_ENABLE;
+ sdhci_writel(host, reg, SDHC_PHY_DLLCFG);
+
+ /* Wait for DLL lock */
+ i = 0;
+ while (i++ < 100) {
+ if (sdhci_readl(host, SDHC_PHY_DLLSTS) & DLL_LOCK_STATE)
+ break;
+ udelay(10);
+ }
+ if (i == 100) {
+ log_err("%s: phy dll lock timeout\n", host->name);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int spacemit_sdhci_hs400_enhanced_strobe(struct sdhci_host *host)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_MMC_CTRL_REG);
+ reg |= ENHANCE_STROBE_EN;
+ sdhci_writel(host, reg, SDHC_MMC_CTRL_REG);
+
+ return spacemit_sdhci_phy_dll_init(host);
+}
+#endif
+
+const struct sdhci_ops spacemit_sdhci_ops = {
+ .set_control_reg = spacemit_sdhci_set_control_reg,
+ .platform_execute_tuning = spacemit_sdhci_execute_tuning,
+#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT)
+ .set_enhanced_strobe = spacemit_sdhci_hs400_enhanced_strobe,
+#endif
+};
+
+static struct dm_mmc_ops spacemit_mmc_ops;
+
+static int spacemit_sdhci_probe(struct udevice *dev)
+{
+ struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
+ struct spacemit_sdhci_priv *priv = dev_get_priv(dev);
+ struct spacemit_sdhci_plat *plat = dev_get_plat(dev);
+ struct sdhci_host *host = &priv->host;
+ struct clk clk;
+ int ret = 0;
+
+ host->mmc = &plat->mmc;
+ host->mmc->priv = host;
+ host->mmc->dev = dev;
+ upriv->mmc = host->mmc;
+
+ spacemit_mmc_ops = sdhci_ops;
+ spacemit_mmc_ops.wait_dat0 = spacemit_sdhci_wait_dat0;
+
+ ret = clk_get_bulk(dev, &plat->clks);
+ if (ret) {
+ log_err("Can't get clk: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_enable_bulk(&plat->clks);
+ if (ret) {
+ log_err("Failed to enable clk: %d\n", ret);
+ return ret;
+ }
+
+ ret = reset_get_bulk(dev, &plat->resets);
+ if (ret) {
+ log_err("Can't get reset: %d\n", ret);
+ return ret;
+ }
+
+ ret = reset_deassert_bulk(&plat->resets);
+ if (ret) {
+ log_err("Failed to reset: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_get_by_index(dev, 1, &clk);
+ if (ret) {
+ log_err("Can't get io clk: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_set_rate(&clk, plat->cfg.f_max);
+ if (ret) {
+ log_err("Failed to set io clk: %d\n", ret);
+ return ret;
+ }
+
+ /* Set quirks */
+ if (IS_ENABLED(CONFIG_SPL_BUILD))
+ host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD;
+ else
+ host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD |
+ SDHCI_QUIRK_32BIT_DMA_ADDR;
+ host->host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz;
+ host->max_clk = plat->cfg.f_max;
+
+ plat->cfg.f_min = SDHC_MIN_CLOCK;
+ host->ops = &spacemit_sdhci_ops;
+
+ ret = sdhci_setup_cfg(&plat->cfg, host, plat->cfg.f_max,
+ SDHC_MIN_CLOCK);
+ if (ret)
+ return ret;
+
+ ret = sdhci_probe(dev);
+ if (ret)
+ return ret;
+
+ spacemit_sdhci_phy_init(dev, host);
+ return ret;
+}
+
+static int spacemit_sdhci_of_to_plat(struct udevice *dev)
+{
+ struct spacemit_sdhci_plat *plat = dev_get_plat(dev);
+ struct spacemit_sdhci_priv *priv = dev_get_priv(dev);
+ struct sdhci_host *host = &priv->host;
+ int ret = 0;
+
+ host->name = dev->name;
+ host->ioaddr = (void *)dev_read_addr(dev);
+
+ ret = mmc_of_parse(dev, &plat->cfg);
+
+ return ret;
+}
+
+static int spacemit_sdhci_bind(struct udevice *dev)
+{
+ struct spacemit_sdhci_plat *drv_data;
+ struct spacemit_sdhci_plat *plat = dev_get_plat(dev);
+
+ drv_data = (struct spacemit_sdhci_plat *)dev_get_driver_data(dev);
+ memcpy(plat, drv_data, sizeof(struct spacemit_sdhci_plat));
+ return sdhci_bind(dev, &plat->mmc, &plat->cfg);
+}
+
+static const struct spacemit_sdhci_plat k1_data = {
+ .aib_mmc1_io_reg = 0xd401e81c,
+ .apbc_asfar_reg = 0xd4015050,
+ .apbc_assar_reg = 0xd4015054,
+ .tx_dline_reg = TX_TUNING_DLINE_REG,
+ .tx_delaycode = 0x5f,
+ .rxtuning = {
+ .rx_dline_reg = RX_TUNING_DLINE_REG,
+ .window_limit = 50,
+ },
+};
+
+static const struct udevice_id spacemit_sdhci_ids[] = {
+ {
+ .compatible = "spacemit,k1-sdhci",
+ .data = (ulong)&k1_data,
+ }, {
+ }
+};
+
+U_BOOT_DRIVER(spacemit_sdhci_drv) = {
+ .name = "spacemit_sdhci",
+ .id = UCLASS_MMC,
+ .of_match = spacemit_sdhci_ids,
+ .of_to_plat = spacemit_sdhci_of_to_plat,
+ .ops = &spacemit_mmc_ops,
+ .bind = spacemit_sdhci_bind,
+ .probe = spacemit_sdhci_probe,
+ .priv_auto = sizeof(struct spacemit_sdhci_priv),
+ .plat_auto = sizeof(struct spacemit_sdhci_plat),
+};
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH 2/8] mmc: k1: add sdhci platform driver
2026-06-12 20:18 ` [PATCH 2/8] mmc: k1: add sdhci platform driver Raymond Mao
@ 2026-06-13 6:43 ` Yao Zi
0 siblings, 0 replies; 11+ messages in thread
From: Yao Zi @ 2026-06-13 6:43 UTC (permalink / raw)
To: Raymond Mao, u-boot
Cc: uboot, u-boot-spacemit, raymond.mao, rick, ycliang, trini, lukma,
hs, jh80.chung, peng.fan, xypron.glpk, randolph, dlan, junhui.liu,
neil.armstrong, quentin.schulz, samuel, Guodong Xu, Yao Zi
On Fri, Jun 12, 2026 at 04:18:55PM -0400, Raymond Mao wrote:
> From: Guodong Xu <guodong@riscstar.com>
>
> Add SDHCI platform driver support for SpacemiT K1 SoC. This driver
> implements the necessary platform-specific operations for the SDHCI
> controller, enabling MMC/SD card functionality on K1-based platforms.
Is there any reason not to re-use Linux-side K1 MMC driver,
sdhci-of-k1.c, and its companion ABI? Since Linux commit e9cb83c10071
(mmc: sdhci-of-k1: add comprehensive SDR tuning support, 2026-05-11)
it is now capable of operating on SD cards, too.
> Signed-off-by: Guodong Xu <guodong@riscstar.com>
> Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
> ---
> drivers/mmc/Kconfig | 7 +
> drivers/mmc/Makefile | 1 +
> drivers/mmc/spacemit_sdhci.c | 934 +++++++++++++++++++++++++++++++++++
> 3 files changed, 942 insertions(+)
> create mode 100644 drivers/mmc/spacemit_sdhci.c
...
> diff --git a/drivers/mmc/spacemit_sdhci.c b/drivers/mmc/spacemit_sdhci.c
> new file mode 100644
> index 00000000000..392ca389fa9
> --- /dev/null
> +++ b/drivers/mmc/spacemit_sdhci.c
...
> +static void spacemit_sdhci_set_aib_mmc1_io(struct sdhci_host *host, int voltage)
> +{
> + struct mmc *mmc = host->mmc;
> + struct spacemit_sdhci_plat *plat = dev_get_plat(mmc->dev);
> + void __iomem *aib_mmc1_io, *apbc_asfar, *apbc_assar;
> + u32 reg;
> +
> + if (!plat->aib_mmc1_io_reg || !plat->apbc_asfar_reg ||
> + !plat->apbc_assar_reg)
> + return;
> +
> + aib_mmc1_io = map_sysmem((uintptr_t)plat->aib_mmc1_io_reg,
> + sizeof(u32));
> + apbc_asfar = map_sysmem((uintptr_t)plat->apbc_asfar_reg,
> + sizeof(u32));
> + apbc_assar = map_sysmem((uintptr_t)plat->apbc_assar_reg,
> + sizeof(u32));
AFAIK aib_mmc1_io region is related to pinctrl settings, and shouldn't
be handled by the MMC driver directly. Quoting my own reply[1] to the
series adding SD support to sdhci-of-k1.c in Linux side,
> Also, the pin controller on K1 SoC seems to have some undocumented
> registers to select the IO voltage of SD pins, which should be
> adjusted when switching IO voltage.
>
> I think these pins should be implemented in the pinctrl driver, then
> you could create two pinctrl states, one for 1.8v operation, one for
> 3.3v, and switch between them through pinctrl_lookup_state() when
> changing IO voltage.
This idea is later confirmed by SpacemiT guy's series[2],
> On K1, IO domain power control registers determine whether a GPIO bank
> operates at 1.8V or 3.3V. These registers default to 3.3V operation,
> which may lead to functional failures when GPIO banks are externally
> supplied with 1.8V but internally remain configured for 3.3V.
>
> The IO power domain registers are implemented as secure registers and
> require an explicit unlock sequence via the AIB Secure Access Register
> (ASAR), located in the APBC register space.
Thus I think you should move the logic to pinctrl driver instead, like
what has been done in the Linux upstream driver.
> + writel(AKEY_ASFAR, apbc_asfar);
> + writel(AKEY_ASSAR, apbc_assar);
> + reg = readl(aib_mmc1_io);
> +
> + switch (voltage) {
> + case MMC_SIGNAL_VOLTAGE_180:
> + reg |= MMC1_IO_V18EN;
> + break;
> + default:
> + reg &= ~MMC1_IO_V18EN;
> + break;
> + }
> + writel(AKEY_ASFAR, apbc_asfar);
> + writel(AKEY_ASSAR, apbc_assar);
> + writel(reg, aib_mmc1_io);
> +}
...
> +static void spacemit_sdhci_set_control_reg(struct sdhci_host *host)
> +{
...
> + /* Set pinctrl state */
> + if (IS_ENABLED(CONFIG_PINCTRL)) {
> + if (mmc->clock >= 200000000)
> + pinctrl_select_state(mmc->dev, "fast");
> + else
> + pinctrl_select_state(mmc->dev, "default");
> + }
This doesn't match Linux side ABI, where when card operates in UHS mode,
pinctrl state "uhs" is selected.
Regards,
Yao Zi
[1]: https://lore.kernel.org/linux-riscv/aUDwDkd8k_4gD1yc@pie/
[2]: https://lore.kernel.org/linux-riscv/20260108-kx-pinctrl-aib-io-pwr-domain-v2-0-6bcb46146e53@linux.spacemit.com
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 3/8] dts: k1: add SD card support in u-boot overlay
2026-06-12 20:18 [PATCH 0/8] Add SD card and eMMC support for SpacemiT K1 Raymond Mao
2026-06-12 20:18 ` [PATCH 1/8] spacemit: k1: select boot device via config registers Raymond Mao
2026-06-12 20:18 ` [PATCH 2/8] mmc: k1: add sdhci platform driver Raymond Mao
@ 2026-06-12 20:18 ` Raymond Mao
2026-06-12 20:18 ` [PATCH 4/8] configs: k1: enable SD and eMMC support Raymond Mao
` (4 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Raymond Mao @ 2026-06-12 20:18 UTC (permalink / raw)
To: u-boot
Cc: uboot, u-boot-spacemit, raymond.mao, rick, ycliang, trini, lukma,
hs, jh80.chung, peng.fan, xypron.glpk, randolph, dlan, junhui.liu,
neil.armstrong, quentin.schulz, samuel, raymondmaoca, Guodong Xu
From: Guodong Xu <guodong@riscstar.com>
Add the SDH0 controller node and its pinctrl groups to the u-boot
overlay. The upstream DTS only contains the eMMC node; the SD card
controller (sdhci@d4280000) and its MMC1 pinctrl configuration are
missing. Place the new node inside the storage-bus via path-based
merge so it inherits the dma-ranges from the parent bus.
Signed-off-by: Guodong Xu <guodong@riscstar.com>
Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
arch/riscv/dts/k1-bananapi-f3-u-boot.dtsi | 87 ++++++++++++++++++++++-
arch/riscv/dts/k1-musepi-pro-u-boot.dtsi | 82 ++++++++++++++++++++-
2 files changed, 165 insertions(+), 4 deletions(-)
diff --git a/arch/riscv/dts/k1-bananapi-f3-u-boot.dtsi b/arch/riscv/dts/k1-bananapi-f3-u-boot.dtsi
index 7f9443d6951..af61f44a1ac 100644
--- a/arch/riscv/dts/k1-bananapi-f3-u-boot.dtsi
+++ b/arch/riscv/dts/k1-bananapi-f3-u-boot.dtsi
@@ -6,6 +6,11 @@
#include "binman.dtsi"
/ {
+ aliases {
+ mmc0 = &sd;
+ mmc1 = &emmc;
+ };
+
memory@0 {
device_type = "memory";
reg = <0x00000000 0x00000000 0x00000000 0x80000000>;
@@ -76,12 +81,14 @@
bootph-pre-ram;
};
- buck3 {
+ buck3_1v8: buck3 {
regulator-name = "vdd_1v8";
bootph-pre-ram;
};
- aldo1 {
+ buck4_3v3: buck4 { };
+
+ aldo1: aldo1 {
regulator-name = "vdd_1v8_mmc";
bootph-pre-ram;
};
@@ -89,6 +96,82 @@
};
};
+/ {
+ soc {
+ storage-bus {
+ sd: mmc@d4280000 {
+ bootph-pre-ram;
+ compatible = "spacemit,k1-sdhci";
+ reg = <0x0 0xd4280000 0x0 0x200>;
+ clocks = <&syscon_apmu CLK_SDH_AXI>,
+ <&syscon_apmu CLK_SDH0>;
+ clock-names = "core", "io";
+ interrupts = <99>;
+ resets = <&syscon_apmu RESET_SDH_AXI>,
+ <&syscon_apmu RESET_SDH0>;
+ reset-names = "sdh_axi", "sdh0";
+ bus-width = <4>;
+ max-frequency = <204800000>;
+ cd-gpios = <&gpio K1_GPIO(80) GPIO_ACTIVE_LOW>;
+ pinctrl-names = "default", "fast";
+ pinctrl-0 = <&sdhci0_0_cfg>;
+ pinctrl-1 = <&sdhci0_1_cfg>;
+ status = "okay";
+ };
+ };
+ };
+};
+
+/* SD card pinctrl groups -- not present in upstream k1-pinctrl.dtsi */
+&pinctrl {
+ sdhci0_0_cfg: sdhci0-0-cfg {
+ grp_cmd_data {
+ pinmux = <K1_PADCONF(104, 0)>, /* MMC1_DAT3 */
+ <K1_PADCONF(105, 0)>, /* MMC1_DAT2 */
+ <K1_PADCONF(106, 0)>, /* MMC1_DAT1 */
+ <K1_PADCONF(107, 0)>, /* MMC1_DAT0 */
+ <K1_PADCONF(108, 0)>; /* MMC1_CMD */
+ bias-pull-up = <1>;
+ drive-strength = <19>;
+ };
+ grp_clk {
+ pinmux = <K1_PADCONF(109, 0)>; /* MMC1_CLK */
+ bias-pull-down = <1>;
+ drive-strength = <19>;
+ };
+ };
+
+ sdhci0_1_cfg: sdhci0-1-cfg { /* fast */
+ grp_cmd_data {
+ pinmux = <K1_PADCONF(104, 0)>, /* MMC1_DAT3 */
+ <K1_PADCONF(105, 0)>, /* MMC1_DAT2 */
+ <K1_PADCONF(106, 0)>, /* MMC1_DAT1 */
+ <K1_PADCONF(107, 0)>, /* MMC1_DAT0 */
+ <K1_PADCONF(108, 0)>; /* MMC1_CMD */
+ bias-pull-up = <1>;
+ drive-strength = <42>;
+ };
+ grp_clk {
+ pinmux = <K1_PADCONF(109, 0)>; /* MMC1_CLK */
+ bias-pull-down = <1>;
+ drive-strength = <42>;
+ };
+ };
+};
+
+&emmc {
+ bootph-pre-ram;
+ resets = <&syscon_apmu RESET_SDH_AXI>, <&syscon_apmu RESET_SDH2>;
+ reset-names = "sdh_axi", "sdh2";
+ vqmmc-supply = <&buck3_1v8>;
+ vmmc-supply = <&aldo1>;
+ max-frequency = <204800000>;
+};
+
+&sd {
+ vqmmc-supply = <&buck4_3v3>;
+};
+
&binman {
u-boot-spl-ddr {
type = "section";
diff --git a/arch/riscv/dts/k1-musepi-pro-u-boot.dtsi b/arch/riscv/dts/k1-musepi-pro-u-boot.dtsi
index 8a9a2a09de9..b5ba302943c 100644
--- a/arch/riscv/dts/k1-musepi-pro-u-boot.dtsi
+++ b/arch/riscv/dts/k1-musepi-pro-u-boot.dtsi
@@ -10,6 +10,8 @@
aliases {
console = &uart0;
serial0 = &uart0;
+ mmc0 = &sd;
+ mmc1 = &emmc;
};
chosen {
@@ -147,7 +149,7 @@
regulator-always-on;
};
- buck4 {
+ buck4_3v3: buck4 {
regulator-min-microvolt = <500000>;
regulator-max-microvolt = <3300000>;
regulator-ramp-delay = <5000>;
@@ -168,7 +170,7 @@
regulator-always-on;
};
- aldo1 {
+ aldo1: aldo1 {
bootph-pre-ram;
regulator-name = "vdd_1v8_mmc";
regulator-min-microvolt = <500000>;
@@ -249,6 +251,82 @@
};
};
+/ {
+ soc {
+ storage-bus {
+ sd: mmc@d4280000 {
+ bootph-pre-ram;
+ compatible = "spacemit,k1-sdhci";
+ reg = <0x0 0xd4280000 0x0 0x200>;
+ clocks = <&syscon_apmu CLK_SDH_AXI>,
+ <&syscon_apmu CLK_SDH0>;
+ clock-names = "core", "io";
+ interrupts = <99>;
+ resets = <&syscon_apmu RESET_SDH_AXI>,
+ <&syscon_apmu RESET_SDH0>;
+ reset-names = "sdh_axi", "sdh0";
+ bus-width = <4>;
+ max-frequency = <204800000>;
+ cd-gpios = <&gpio K1_GPIO(80) GPIO_ACTIVE_LOW>;
+ pinctrl-names = "default", "fast";
+ pinctrl-0 = <&sdhci0_0_cfg>;
+ pinctrl-1 = <&sdhci0_1_cfg>;
+ status = "okay";
+ };
+ };
+ };
+};
+
+/* SD card pinctrl groups -- not present in upstream k1-pinctrl.dtsi */
+&pinctrl {
+ sdhci0_0_cfg: sdhci0-0-cfg {
+ grp_cmd_data {
+ pinmux = <K1_PADCONF(104, 0)>, /* MMC1_DAT3 */
+ <K1_PADCONF(105, 0)>, /* MMC1_DAT2 */
+ <K1_PADCONF(106, 0)>, /* MMC1_DAT1 */
+ <K1_PADCONF(107, 0)>, /* MMC1_DAT0 */
+ <K1_PADCONF(108, 0)>; /* MMC1_CMD */
+ bias-pull-up = <1>;
+ drive-strength = <19>;
+ };
+ grp_clk {
+ pinmux = <K1_PADCONF(109, 0)>; /* MMC1_CLK */
+ bias-pull-down = <1>;
+ drive-strength = <19>;
+ };
+ };
+
+ sdhci0_1_cfg: sdhci0-1-cfg { /* fast */
+ grp_cmd_data {
+ pinmux = <K1_PADCONF(104, 0)>, /* MMC1_DAT3 */
+ <K1_PADCONF(105, 0)>, /* MMC1_DAT2 */
+ <K1_PADCONF(106, 0)>, /* MMC1_DAT1 */
+ <K1_PADCONF(107, 0)>, /* MMC1_DAT0 */
+ <K1_PADCONF(108, 0)>; /* MMC1_CMD */
+ bias-pull-up = <1>;
+ drive-strength = <42>;
+ };
+ grp_clk {
+ pinmux = <K1_PADCONF(109, 0)>; /* MMC1_CLK */
+ bias-pull-down = <1>;
+ drive-strength = <42>;
+ };
+ };
+};
+
+&emmc {
+ bootph-pre-ram;
+ resets = <&syscon_apmu RESET_SDH_AXI>, <&syscon_apmu RESET_SDH2>;
+ reset-names = "sdh_axi", "sdh2";
+ vqmmc-supply = <&buck3_1v8>;
+ vmmc-supply = <&aldo1>;
+ max-frequency = <204800000>;
+};
+
+&sd {
+ vqmmc-supply = <&buck4_3v3>;
+};
+
&binman {
u-boot-spl-ddr {
type = "section";
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 4/8] configs: k1: enable SD and eMMC support
2026-06-12 20:18 [PATCH 0/8] Add SD card and eMMC support for SpacemiT K1 Raymond Mao
` (2 preceding siblings ...)
2026-06-12 20:18 ` [PATCH 3/8] dts: k1: add SD card support in u-boot overlay Raymond Mao
@ 2026-06-12 20:18 ` Raymond Mao
2026-06-12 20:18 ` [PATCH 5/8] doc: spacemit: flash on K1 SoC based boards Raymond Mao
` (3 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Raymond Mao @ 2026-06-12 20:18 UTC (permalink / raw)
To: u-boot
Cc: uboot, u-boot-spacemit, raymond.mao, rick, ycliang, trini, lukma,
hs, jh80.chung, peng.fan, xypron.glpk, randolph, dlan, junhui.liu,
neil.armstrong, quentin.schulz, samuel, raymondmaoca, Guodong Xu
From: Guodong Xu <guodong@riscstar.com>
Enable high-speed MMC modes for PIO.
Signed-off-by: Guodong Xu <guodong@riscstar.com>
Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
board/spacemit/k1/MAINTAINERS | 1 +
configs/spacemit_k1_defconfig | 17 +++++++++++++++++
2 files changed, 18 insertions(+)
diff --git a/board/spacemit/k1/MAINTAINERS b/board/spacemit/k1/MAINTAINERS
index 32d47ecc8f1..ca994e2a1fb 100644
--- a/board/spacemit/k1/MAINTAINERS
+++ b/board/spacemit/k1/MAINTAINERS
@@ -9,6 +9,7 @@ F: configs/spacemit_k1_defconfig
F: doc/board/spacemit/bananapi-f3.rst
F: drivers/gpio/spacemit_gpio.c
F: drivers/i2c/k1_i2c.c
+F: drivers/mmc/spacemit_sdhci.c
F: drivers/pinctrl/spacemit/
F: drivers/power/pmic/pmic_spacemit_p1.c
F: drivers/power/regulator/spacemit_p1_regulator.c
diff --git a/configs/spacemit_k1_defconfig b/configs/spacemit_k1_defconfig
index 81409c844a6..a44e657c0dc 100644
--- a/configs/spacemit_k1_defconfig
+++ b/configs/spacemit_k1_defconfig
@@ -87,6 +87,20 @@ CONFIG_SPI_FLASH_WINBOND=y
CONFIG_SPI_MEM=y
CONFIG_SYS_SPI_U_BOOT_OFFS=0x000a0000
CONFIG_CMD_SPI=y
+CONFIG_MMC=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_SPACEMIT=y
+CONFIG_MMC_IO_VOLTAGE=y
+CONFIG_MMC_HS200_SUPPORT=y
+CONFIG_MMC_HS400_SUPPORT=y
+CONFIG_MMC_HS400_ES_SUPPORT=y
+CONFIG_SUPPORT_EMMC_BOOT=y
+CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION=y
+CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_PARTITION=0x4
+CONFIG_CMD_MMC=y
+CONFIG_CMD_MEMINFO=y
+CONFIG_CMD_MEMINFO_MAP=y
+CONFIG_EFI_PARTITION=y
CONFIG_SPL_SPI=y
CONFIG_SPL_DM_SPI=y
CONFIG_SPL_DM_SPI_FLASH=y
@@ -94,3 +108,6 @@ CONFIG_SPL_SPI_LOAD=y
CONFIG_SPL_SPI_FLASH_TINY=y
CONFIG_SPL_SPI_FLASH_SUPPORT=y
CONFIG_SPL_BOARD_INIT=y
+CONFIG_SPL_MMC=y
+CONFIG_SPL_MMC_TINY=y
+CONFIG_SPL_SYS_MMCSD_RAW_MODE=y
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 5/8] doc: spacemit: flash on K1 SoC based boards
2026-06-12 20:18 [PATCH 0/8] Add SD card and eMMC support for SpacemiT K1 Raymond Mao
` (3 preceding siblings ...)
2026-06-12 20:18 ` [PATCH 4/8] configs: k1: enable SD and eMMC support Raymond Mao
@ 2026-06-12 20:18 ` Raymond Mao
2026-06-12 20:18 ` [PATCH 6/8] cmd: meminfo: widen memory map addresses to phys_addr_t Raymond Mao
` (2 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Raymond Mao @ 2026-06-12 20:18 UTC (permalink / raw)
To: u-boot
Cc: uboot, u-boot-spacemit, raymond.mao, rick, ycliang, trini, lukma,
hs, jh80.chung, peng.fan, xypron.glpk, randolph, dlan, junhui.liu,
neil.armstrong, quentin.schulz, samuel, raymondmaoca, Guodong Xu
From: Guodong Xu <guodong@riscstar.com>
Add document on how to flash images into eMMC of K1 SoC based boards.
Signed-off-by: Guodong Xu <guodong@riscstar.com>
Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
board/spacemit/k1/MAINTAINERS | 2 +-
doc/board/spacemit/index.rst | 1 +
doc/board/spacemit/k1-flash.rst | 157 ++++++++++++++++++++++++++++++++
3 files changed, 159 insertions(+), 1 deletion(-)
create mode 100644 doc/board/spacemit/k1-flash.rst
diff --git a/board/spacemit/k1/MAINTAINERS b/board/spacemit/k1/MAINTAINERS
index ca994e2a1fb..7a34476ea7c 100644
--- a/board/spacemit/k1/MAINTAINERS
+++ b/board/spacemit/k1/MAINTAINERS
@@ -6,7 +6,7 @@ S: Maintained
F: arch/riscv/dts/k1-*-u-boot.dtsi
F: board/spacemit/k1/
F: configs/spacemit_k1_defconfig
-F: doc/board/spacemit/bananapi-f3.rst
+F: doc/board/spacemit/
F: drivers/gpio/spacemit_gpio.c
F: drivers/i2c/k1_i2c.c
F: drivers/mmc/spacemit_sdhci.c
diff --git a/doc/board/spacemit/index.rst b/doc/board/spacemit/index.rst
index a5e35ee12ab..186c9b13602 100644
--- a/doc/board/spacemit/index.rst
+++ b/doc/board/spacemit/index.rst
@@ -6,5 +6,6 @@ SpacemiT
:maxdepth: 1
bananapi-f3
+ k1-flash
k1-spl
diff --git a/doc/board/spacemit/k1-flash.rst b/doc/board/spacemit/k1-flash.rst
new file mode 100644
index 00000000000..c33625772b6
--- /dev/null
+++ b/doc/board/spacemit/k1-flash.rst
@@ -0,0 +1,157 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+SpacemiT K1 U-Boot Flash Guide
+==============================
+
+This guide explains how to flash U-Boot on SpacemiT K1 based boards. It covers
+flashing images via USB fastboot.
+
+Tested boards: Banana Pi BPI-F3, MusePi Pro.
+
+.. note::
+
+ This procedure flashes images to eMMC over USB fastboot. The fastboot
+ function is not enabled in our SPL yet, so the download stage runs the
+ SpacemiT released SPL; our built FSBL.bin and fit.itb are the images
+ written to eMMC and used on the next normal boot.
+
+Prerequisites
+~~~~~~~~~~~~~
+
+- A SpacemiT K1 board with USB Type-C and UART access
+- USB-to-UART adapter (3.3V TTL)
+- ``minicom`` or equivalent serial terminal, configured at 115200 8N1
+- ``fastboot`` and ``flashserver`` tool on the host
+
+Hardware Setup
+~~~~~~~~~~~~~~
+
+Refer to k1-spl.rst.
+
+Flash images on eMMC
+~~~~~~~~~~~~~~~~~~~~
+
+**1. Obtain the release images**
+
+Get the release package from Spacemit website. It contains SPL image, and so on.
+
+https://archive.spacemit.com/image/k1/version/bianbu/v2.3.3/Bianbu-Minimal-K1-V2.3.3-20260128183217.zip
+
+Unzip images and store them into a directory.
+
+**2. Obtain flashserver tool**
+
+Get ``flashserver`` from Spacemit website.
+
+.. code-block:: bash
+
+ $wget https://cdn-resource.spacemit.com/file/flash/flashserver
+ $chmod +x flashserver
+ $mv flashserver {flash image path}/
+
+**3. Copy built SPL and U-Boot images**
+
+Build U-Boot as mentioned in k1-spl.rst. Create a new directory to save.
+The official u-boot.itb is used to download images. So the built U-Boot should
+not replace the official one.
+
+.. code-block:: bash
+
+ $mkdir {flash image path}/build
+ $cd {flash image path}
+ $ln -sf {path to FSBL.bin} ./build/
+ $ln -sf {path to u-boot.itb} ./build/fit.itb
+
+``{path to FSBL.bin}`` is the signed FSBL produced by ``fsbl.sh`` in
+k1-spl.rst, e.g. ``~/uboot-2022.10/spl_bin/FSBL.bin``.
+``{path to u-boot.itb}`` is the U-Boot build output, e.g.
+``~/u-boot/u-boot.itb``.
+
+**4. Update configuration files**
+
+The ``partition_2M.json`` and ``partition_universal.json`` files come from
+the release package. Patch the ``fsbl`` and ``uboot`` entries to point at
+the images staged under ``build/`` (pick the layout that matches your eMMC):
+
+.. code-block:: diff
+
+ diff -puNr bianbu-25/partition_2M.json clean/partition_2M.json
+ --- bianbu-25/partition_2M.json 2026-03-02 11:55:58.631116807 +0800
+ +++ clean/partition_2M.json 2026-05-20 11:25:21.683801401 +0800
+ @@ -13,7 +13,7 @@
+ "name": "fsbl",
+ "offset": "128K",
+ "size": "256K",
+ - "image": "factory/FSBL.bin"
+ + "image": "build/FSBL.bin"
+ },
+ {
+ "name": "env",
+ @@ -31,7 +31,7 @@
+ "name": "uboot",
+ "offset": "640K",
+ "size": "-",
+ - "image": "u-boot.itb"
+ + "image": "build/fit.itb"
+ }
+ ]
+ }
+ diff -puNr bianbu-25/partition_universal.json clean/partition_universal.json
+ --- bianbu-25/partition_universal.json 2026-03-02 11:55:58.642116862 +0800
+ +++ clean/partition_universal.json 2026-05-20 11:26:23.932581853 +0800
+ @@ -14,7 +14,7 @@
+ "name": "fsbl",
+ "offset": "128K",
+ "size": "256K",
+ - "image": "factory/FSBL.bin"
+ + "image": "build/FSBL.bin"
+ },
+ {
+ "name": "env",
+ @@ -32,7 +32,7 @@
+ "name": "uboot",
+ "offset": "2M",
+ "size": "2M",
+ - "image": "u-boot.itb"
+ + "image": "build/fit.itb"
+ },
+ {
+ "name": "bootfs",
+
+Deploying via USB Fastboot
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To enter BootROM fastboot mode:
+
+1. Power off the board by unplugging its power supply.
+2. **Press and hold** the FDL button (called "Boot Key" on some boards;
+ see the board layout above for the BPI-F3).
+3. While holding the button, use a USB cable to connect the OTG port to
+ your host. This cable is also used by fastboot to upload the firmware.
+4. Release the button.
+
+On the host, ``fastboot devices`` should list the board::
+
+ dfu-device DFU download
+
+The serial console shows the BootROM's USB download handler trace,
+including a line like::
+
+ usb2d_initialize : enter
+
+This indicates the board is ready to accept an image via USB.
+
+.. tip::
+
+ If you are worried about insufficient USB power, you can first plug
+ in the power, then release the button, and then plug in the USB
+ cable.
+
+On the host:
+
+.. code-block:: console
+
+ $sudo ./flashserver
+
+When ``flashserver`` is running, it lists the detected fastboot devices.
+Enter the corresponding number to select one.
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 6/8] cmd: meminfo: widen memory map addresses to phys_addr_t
2026-06-12 20:18 [PATCH 0/8] Add SD card and eMMC support for SpacemiT K1 Raymond Mao
` (4 preceding siblings ...)
2026-06-12 20:18 ` [PATCH 5/8] doc: spacemit: flash on K1 SoC based boards Raymond Mao
@ 2026-06-12 20:18 ` Raymond Mao
2026-06-12 20:19 ` [PATCH 7/8] cmd: meminfo: fix the lmb info for large DRAM Raymond Mao
2026-06-12 20:19 ` [PATCH 8/8] cmd: tlv_eeprom: fix accessing invalid parameter Raymond Mao
7 siblings, 0 replies; 11+ messages in thread
From: Raymond Mao @ 2026-06-12 20:18 UTC (permalink / raw)
To: u-boot
Cc: uboot, u-boot-spacemit, raymond.mao, rick, ycliang, trini, lukma,
hs, jh80.chung, peng.fan, xypron.glpk, randolph, dlan, junhui.liu,
neil.armstrong, quentin.schulz, samuel, raymondmaoca, Guodong Xu
From: Guodong Xu <guodong@riscstar.com>
On 32-bit builds 'ulong' is only 32 bits wide, so the memory map
truncates any address or size at or above 4 GiB even though the
columns already allow 13 hex digits. Boards with large DRAM therefore
print wrong bases, sizes and gaps.
Use phys_addr_t for every region address and print it as a 64-bit
value so the map is correct regardless of the build's long width.
Signed-off-by: Guodong Xu <guodong@riscstar.com>
Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
cmd/meminfo.c | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/cmd/meminfo.c b/cmd/meminfo.c
index 69a5b1b51a2..537ef2a0e13 100644
--- a/cmd/meminfo.c
+++ b/cmd/meminfo.c
@@ -21,18 +21,23 @@ void __weak arch_dump_mem_attrs(void)
{
}
-static void print_region(const char *name, ulong base, ulong size, ulong *uptop)
+static void print_region(const char *name, phys_addr_t base, phys_addr_t size,
+ phys_addr_t *uptop)
{
- ulong end = base + size;
+ phys_addr_t end = base + size;
- printf("%-12s %13lx %13lx %13lx", name, base, size, end);
+ printf("%-12s %13llx %13llx %13llx",
+ name,
+ (unsigned long long)base,
+ (unsigned long long)size,
+ (unsigned long long)end);
if (*uptop)
- printf(" %13lx", *uptop - end);
+ printf(" %13llx", (unsigned long long)(*uptop - end));
putc('\n');
*uptop = base;
}
-static void show_lmb(const struct lmb *lmb, ulong *uptop)
+static void show_lmb(const struct lmb *lmb, phys_addr_t *uptop)
{
int i;
@@ -56,7 +61,7 @@ static void show_lmb(const struct lmb *lmb, ulong *uptop)
static int do_meminfo(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
- ulong upto, stk_bot;
+ phys_addr_t upto, stk_bot;
puts("DRAM: ");
print_size(gd->ram_size, "\n");
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 7/8] cmd: meminfo: fix the lmb info for large DRAM
2026-06-12 20:18 [PATCH 0/8] Add SD card and eMMC support for SpacemiT K1 Raymond Mao
` (5 preceding siblings ...)
2026-06-12 20:18 ` [PATCH 6/8] cmd: meminfo: widen memory map addresses to phys_addr_t Raymond Mao
@ 2026-06-12 20:19 ` Raymond Mao
2026-06-12 20:19 ` [PATCH 8/8] cmd: tlv_eeprom: fix accessing invalid parameter Raymond Mao
7 siblings, 0 replies; 11+ messages in thread
From: Raymond Mao @ 2026-06-12 20:19 UTC (permalink / raw)
To: u-boot
Cc: uboot, u-boot-spacemit, raymond.mao, rick, ycliang, trini, lukma,
hs, jh80.chung, peng.fan, xypron.glpk, randolph, dlan, junhui.liu,
neil.armstrong, quentin.schulz, samuel, raymondmaoca, Guodong Xu
From: Guodong Xu <guodong@riscstar.com>
When DRAM size exceeds 4GB, it may be split into two separate banks,
leaving an address hole between them.
To properly handle this configuration, LMB regions must be calculated
individually for each memory bank. Additionally, all address
calculations should support 64-bit to accommodate large memory ranges.
Signed-off-by: Guodong Xu <guodong@riscstar.com>
Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
cmd/meminfo.c | 34 ++++++++++++++++++++++++++++------
1 file changed, 28 insertions(+), 6 deletions(-)
diff --git a/cmd/meminfo.c b/cmd/meminfo.c
index 537ef2a0e13..c5cf01f62ef 100644
--- a/cmd/meminfo.c
+++ b/cmd/meminfo.c
@@ -31,31 +31,53 @@ static void print_region(const char *name, phys_addr_t base, phys_addr_t size,
(unsigned long long)base,
(unsigned long long)size,
(unsigned long long)end);
- if (*uptop)
+ if (*uptop > end)
printf(" %13llx", (unsigned long long)(*uptop - end));
+ else
+ printf(" %13llx", (unsigned long long)(end - end));
putc('\n');
*uptop = base;
}
static void show_lmb(const struct lmb *lmb, phys_addr_t *uptop)
{
- int i;
+ int i, j;
+ phys_addr_t bank_top[CONFIG_NR_DRAM_BANKS];
+ struct bd_info *bd = gd->bd;
+ bool get_bank;
+
+ for (j = 0; j < CONFIG_NR_DRAM_BANKS; j++)
+ bank_top[j] = bd->bi_dram[j].start + bd->bi_dram[j].size;
for (i = lmb->used_mem.count - 1; i >= 0; i--) {
const struct lmb_region *rgn = alist_get(&lmb->used_mem, i,
struct lmb_region);
+ for (j = 0, get_bank = false; j < CONFIG_NR_DRAM_BANKS; j++) {
+ if (rgn->base >= bd->bi_dram[j].start &&
+ rgn->base < bank_top[j]) {
+ get_bank = true;
+ break;
+ }
+ }
+ if (!get_bank) {
+ log_err("The region (base:0x%llx, size:0x%llx) does not exists in any DRAM bank.\n",
+ (unsigned long long)rgn->base, (unsigned long long)rgn->size);
+ return;
+ }
/*
* Assume that the top lmb region is the U-Boot region, so just
* take account of the memory not already reported
*/
if (lmb->used_mem.count - 1)
- print_region("lmb", rgn->base, *uptop - rgn->base,
- uptop);
+ print_region("lmb", rgn->base, bank_top[j] - rgn->base,
+ &bank_top[j]);
else
- print_region("lmb", rgn->base, rgn->size, uptop);
- *uptop = rgn->base;
+ print_region("lmb", rgn->base, rgn->size, &bank_top[j]);
+ bank_top[j] = rgn->base;
}
+
+ *uptop = bank_top[0];
}
static int do_meminfo(struct cmd_tbl *cmdtp, int flag, int argc,
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 8/8] cmd: tlv_eeprom: fix accessing invalid parameter
2026-06-12 20:18 [PATCH 0/8] Add SD card and eMMC support for SpacemiT K1 Raymond Mao
` (6 preceding siblings ...)
2026-06-12 20:19 ` [PATCH 7/8] cmd: meminfo: fix the lmb info for large DRAM Raymond Mao
@ 2026-06-12 20:19 ` Raymond Mao
7 siblings, 0 replies; 11+ messages in thread
From: Raymond Mao @ 2026-06-12 20:19 UTC (permalink / raw)
To: u-boot
Cc: uboot, u-boot-spacemit, raymond.mao, rick, ycliang, trini, lukma,
hs, jh80.chung, peng.fan, xypron.glpk, randolph, dlan, junhui.liu,
neil.armstrong, quentin.schulz, samuel, raymondmaoca, Guodong Xu
From: Guodong Xu <guodong@riscstar.com>
=> tlv_eeprom dev
Unhandled exception: Load access fault
EPC: 000000007ffb351a RA: 000000007ff7c302 TVAL: 0000000000000000
EPC: 000000000024b51a RA: 0000000000214302 reloc adjusted
SP: 000000007ef3ec90 GP: 000000007ef48e40 TP: 0000000000000000
T0: 000000007ef3eda0 T1: 0000000000000000 T2: 0000000000000000
S0: 0000000000000002 S1: 000000007f05c0a0 A0: 0000000000000000
A1: 0000000000000000 A2: 0000000000000000 A3: 00000000d4017000
A4: 0000000000000002 A5: 00000000d4017000 A6: 0000000000000001
A7: 0000000000000000 S2: 0000000000000001 S3: 000000007f05c0a0
S4: 0000000000000002 S5: 000000007fffbbec S6: 0000000000000000
S7: 000000007f05c080 S8: 000000007f05c0a0 S9: 0000000000000000
S10: 0000000000000000 S11: 000000007f05afa0 T3: 0000000000000005
T4: 0000000000000009 T5: 0000000000000006 T6: 000000007ef3ed80
Code: 0007 3513 0015 70a2 7402 64e2 6145 8082 (4683 0005)
Command "tlv_eeprom dev" should not access argv[2]. argv[2] should be
only accessed by command "tlv_eeprom dev [dev]" command.
So add a guard to prevent this invalid access.
Signed-off-by: Guodong Xu <guodong@riscstar.com>
Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
cmd/tlv_eeprom.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cmd/tlv_eeprom.c b/cmd/tlv_eeprom.c
index 3127660dd9e..b2b0b5a6fab 100644
--- a/cmd/tlv_eeprom.c
+++ b/cmd/tlv_eeprom.c
@@ -450,7 +450,7 @@ int do_tlv_eeprom(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
cmd = argv[1][0];
// select device
- if (cmd == 'd') {
+ if (cmd == 'd' && argc > 2) {
/* 'dev' command */
unsigned int devnum;
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread