[PATCH 0/4] mmc: sophgo: milkv_duo: Add SD card support for Milk-V Duo board

This series add sdhci driver for cv1800b SoC and enable SD card support for Sophgo Milk-V Duo board.
Kongyang Liu (4): mmc: cv1800b: Add sdhci driver support for cv1800b SoC riscv: dts: sophgo: Add clk node riscv: dts: sophgo: Add sdhci node configs: milkv_duo: Add SD card configs
arch/riscv/dts/cv1800b.dtsi | 27 ++++ arch/riscv/dts/cv18xx.dtsi | 6 + configs/milkv_duo_defconfig | 16 ++- drivers/mmc/Kconfig | 13 ++ drivers/mmc/Makefile | 1 + drivers/mmc/cv1800b_sdhci.c | 243 ++++++++++++++++++++++++++++++++++++ 6 files changed, 303 insertions(+), 3 deletions(-) create mode 100644 drivers/mmc/cv1800b_sdhci.c

Add sdhci driver for cv1800b SoC.
Signed-off-by: Kongyang Liu seashell11234455@gmail.com
---
drivers/mmc/Kconfig | 13 ++ drivers/mmc/Makefile | 1 + drivers/mmc/cv1800b_sdhci.c | 243 ++++++++++++++++++++++++++++++++++++ 3 files changed, 257 insertions(+) create mode 100644 drivers/mmc/cv1800b_sdhci.c
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index 17618c3bdc..6d5b997fa5 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -568,6 +568,19 @@ config MMC_SDHCI_CADENCE
If unsure, say N.
+config MMC_SDHCI_CV1800B + bool "SDHCI support for the CV1800B SD/SDIO/eMMC controller" + depends on BLK && DM_MMC + depends on MMC_SDHCI + depends on OF_CONTROL + help + This selects the CV1800B SD/SDIO/eMMC driver. + + If you have a controller with this interface, + say Y here. + + If unsure, say N. + config MMC_SDHCI_AM654 bool "SDHCI Controller on TI's Am654 devices" depends on ARCH_K3 diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index e9cf1fcc64..3374321e29 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -60,6 +60,7 @@ obj-$(CONFIG_MMC_SDHCI_ATMEL) += atmel_sdhci.o obj-$(CONFIG_MMC_SDHCI_BCM2835) += bcm2835_sdhci.o obj-$(CONFIG_MMC_SDHCI_BCMSTB) += bcmstb_sdhci.o obj-$(CONFIG_MMC_SDHCI_CADENCE) += sdhci-cadence.o +obj-$(CONFIG_MMC_SDHCI_CV1800B) += cv1800b_sdhci.o obj-$(CONFIG_MMC_SDHCI_AM654) += am654_sdhci.o obj-$(CONFIG_MMC_SDHCI_IPROC) += iproc_sdhci.o obj-$(CONFIG_MMC_SDHCI_KONA) += kona_sdhci.o diff --git a/drivers/mmc/cv1800b_sdhci.c b/drivers/mmc/cv1800b_sdhci.c new file mode 100644 index 0000000000..0de1a2d916 --- /dev/null +++ b/drivers/mmc/cv1800b_sdhci.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2024, Kongyang Liu seashell11234455@gmail.com + */ + +#include <dm.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/sizes.h> +#include <linux/libfdt.h> +#include <reset.h> +#include <mmc.h> +#include <sdhci.h> + +#define SDHCI_VENDOR_OFFSET 0x200 +#define SDHCI_PHY_TX_RX_DLY (SDHCI_VENDOR_OFFSET + 0x40) +#define SDHCI_PHY_CONFIG (SDHCI_VENDOR_OFFSET + 0x4C) + +#define MMC_MAX_CLOCK 375000000 +#define MMC_MAX_CLOCK_DIV_VALUE 0x40009 + +#define REG_CLOCK_BYPASS_SELECT (void *)0x03002030 +#define REG_TOP_SD_PWRSW_CTRL (void *)0x030001F4 +#define REG_PWRSW_AUTO BIT(3) +#define REG_PWRSW_DISC BIT(2) +/* REG_PWRSW_VSEL=1: 1.8V, REG_PWRSW_VSEL=0: 3.0V */ +#define REG_PWRSW_VSEL BIT(1) +#define REG_EN_PWRSW BIT(0) + +/* SD Tap Delay Config */ +#define MAX_TUNING_CMD_RETRY_COUNT 50 +#define TUNE_MAX_PHCODE 128 +#define TAP_WINDOW_THLD 20 + +struct cv1800b_sdhci_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct cv1800b_sdhci_host { + struct sdhci_host host; + u32 pll_index; + u64 pll_reg; + bool no_1_8_v; + bool reset_tx_rx_phy; + u32 mmc_fmax_freq; + u32 mmc_fmin_freq; +}; + +static inline void sdhci_setbits(struct sdhci_host *host, int reg, u32 mask) +{ + u32 val; + + val = sdhci_readl(host, reg); + val |= mask; + sdhci_writel(host, val, reg); +} + +static inline void sdhci_clrbits(struct sdhci_host *host, int reg, u32 mask) +{ + u32 val; + + val = sdhci_readl(host, reg); + val &= ~mask; + sdhci_writel(host, val, reg); +} + +static void cv1800b_set_tap_delay(struct sdhci_host *host, u16 tap) +{ + sdhci_clrbits(host, SDHCI_CLOCK_CONTROL, SDHCI_CLOCK_CARD_EN); + + sdhci_writel(host, 0, SDHCI_VENDOR_OFFSET); + sdhci_writel(host, BIT(8) | tap << 16, SDHCI_PHY_TX_RX_DLY); + sdhci_writel(host, 0, SDHCI_PHY_CONFIG); + + sdhci_setbits(host, SDHCI_CLOCK_CONTROL, SDHCI_CLOCK_CARD_EN); +} + +int cv1800b_get_cd(struct sdhci_host *host) +{ + return sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT; +} + +int cv1800b_general_execute_tuning(struct mmc *mmc, u8 opcode) +{ + struct cv1800b_sdhci_host *priv = dev_get_priv(mmc->dev); + struct sdhci_host *host = &priv->host; + + int ret; + + u16 tap = 0; + u32 retry_cnt = 0; + + int cur_window_idx = -1; + int max_window_size = 0; + int cur_window_size = 0; + int final_tap = -1; + + sdhci_clrbits(host, SDHCI_HOST_CONTROL2, SDHCI_CTRL_TUNED_CLK | SDHCI_CTRL_DRV_TYPE_MASK); + + for (tap = 0; tap < TUNE_MAX_PHCODE; tap++) { + sdhci_writew(host, BIT(2), SDHCI_VENDOR_OFFSET); + cv1800b_set_tap_delay(host, tap); + + for (retry_cnt = 0; retry_cnt < MAX_TUNING_CMD_RETRY_COUNT; retry_cnt++) { + ret = mmc_send_tuning(host->mmc, opcode, NULL); + if (ret) + break; + } + + /* Find a final tap as median of maximum window */ + if (ret) { + cur_window_idx = -1; + continue; + } + + if (-1 == cur_window_idx) { + cur_window_idx = tap; + cur_window_size = 0; + } + cur_window_size++; + + if (cur_window_size > max_window_size) { + max_window_size = cur_window_size; + if (max_window_size >= TAP_WINDOW_THLD) + final_tap = cur_window_idx + (max_window_size / 2); + } + } + + sdhci_clrbits(host, SDHCI_INT_STATUS, SDHCI_INT_DATA_AVAIL); + + sdhci_setbits(host, SDHCI_SOFTWARE_RESET, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & (SDHCI_RESET_CMD | SDHCI_RESET_DATA)) + ; + + cv1800b_set_tap_delay(host, final_tap); + + sdhci_clrbits(host, SDHCI_HOST_CONTROL2, SDHCI_CTRL_EXEC_TUNING); + + return ret; +} + +const struct sdhci_ops cv1800b_sdhci_sd_ops = { + .get_cd = cv1800b_get_cd, + .platform_execute_tuning = cv1800b_general_execute_tuning, +}; + +static int cv1800b_ofdata_to_platdata(struct udevice *dev) +{ + struct cv1800b_sdhci_host *priv = dev_get_priv(dev); + struct sdhci_host *host = &priv->host; + + host->name = strdup(dev->name); + host->ioaddr = (void *)devfdt_get_addr(dev); + host->bus_width = dev_read_s32_default(dev, "bus-width", 4); + host->max_clk = dev_read_u32_default(dev, "src-frequency", 0); + + priv->mmc_fmin_freq = dev_read_u32_default(dev, "tap-frequency", 200000); + priv->mmc_fmax_freq = dev_read_u32_default(dev, "max-frequency", 0); + priv->reset_tx_rx_phy = dev_read_bool(dev, "reset_tx_rx_phy"); + priv->no_1_8_v = dev_read_bool(dev, "no-1-8-v"); + priv->pll_index = dev_read_u32_default(dev, "pll_index", 0); + priv->pll_reg = dev_read_u64_default(dev, "pll_reg", 0); + + if (priv->no_1_8_v) + host->quirks |= SDHCI_QUIRK_NO_1_8_V; + + if (host->ioaddr == (void *)FDT_ADDR_T_NONE) + return -EINVAL; + + return 0; +} + +static int cv1800b_sdhci_bind(struct udevice *dev) +{ + struct cv1800b_sdhci_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static int cv1800b_sdhci_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct cv1800b_sdhci_plat *plat = dev_get_plat(dev); + struct cv1800b_sdhci_host *priv = dev_get_priv(dev); + struct sdhci_host *host = &priv->host; + int ret; + + upriv->mmc = &plat->mmc; + host->mmc = &plat->mmc; + host->mmc->priv = host; + host->mmc->dev = dev; + host->ops = &cv1800b_sdhci_sd_ops; + + ret = sdhci_setup_cfg(&plat->cfg, host, priv->mmc_fmax_freq, priv->mmc_fmin_freq); + + if (ret) + return ret; + + if (cv1800b_get_cd(host)) { + /* Voltage switching flow (3.3) */ + writel(REG_PWRSW_AUTO | REG_EN_PWRSW, REG_TOP_SD_PWRSW_CTRL); + } else { + /* Voltage close flow */ + writel(REG_PWRSW_AUTO | REG_PWRSW_DISC | REG_PWRSW_VSEL, REG_TOP_SD_PWRSW_CTRL); + } + + ret = sdhci_probe(dev); + + if (host->max_clk == MMC_MAX_CLOCK) { + /* set IP clock to 375Mhz */ + writel(MMC_MAX_CLOCK_DIV_VALUE, (void *)priv->pll_reg); + /* switch clock source to PLL */ + writel(readl(REG_CLOCK_BYPASS_SELECT) & ~BIT(priv->pll_index), + REG_CLOCK_BYPASS_SELECT); + } + + if (priv->reset_tx_rx_phy) { + /* Default value */ + sdhci_writel(host, 2, SDHCI_VENDOR_OFFSET); + sdhci_writel(host, 0x01000100, SDHCI_PHY_TX_RX_DLY); + sdhci_writel(host, 0x00000001, SDHCI_PHY_CONFIG); + } + + return ret; +} + +static const struct udevice_id cv1800b_sdhci_match[] = { + { .compatible = "sophgo,cv1800b-sdhci" }, + { } +}; + +U_BOOT_DRIVER(cv1800b_sdhci) = { + .name = "sdhci-cv1800b", + .id = UCLASS_MMC, + .of_match = cv1800b_sdhci_match, + .of_to_plat = cv1800b_ofdata_to_platdata, + .bind = cv1800b_sdhci_bind, + .probe = cv1800b_sdhci_probe, + .priv_auto = sizeof(struct cv1800b_sdhci_host), + .plat_auto = sizeof(struct cv1800b_sdhci_plat), + .ops = &sdhci_ops, +};

Add clk node for cv18xx SoCs according to patch from Linux kernel.
Link: https://lore.kernel.org/all/IA1PR20MB4953355805F79ABDD7FE9129BB6D2@IA1PR20MB...
Signed-off-by: Kongyang Liu seashell11234455@gmail.com ---
arch/riscv/dts/cv1800b.dtsi | 4 ++++ arch/riscv/dts/cv18xx.dtsi | 6 ++++++ 2 files changed, 10 insertions(+)
diff --git a/arch/riscv/dts/cv1800b.dtsi b/arch/riscv/dts/cv1800b.dtsi index 165e9e320a..baf641829e 100644 --- a/arch/riscv/dts/cv1800b.dtsi +++ b/arch/riscv/dts/cv1800b.dtsi @@ -16,3 +16,7 @@ &clint { compatible = "sophgo,cv1800b-clint", "thead,c900-clint"; }; + +&clk { + compatible = "sophgo,cv1800-clk"; +}; diff --git a/arch/riscv/dts/cv18xx.dtsi b/arch/riscv/dts/cv18xx.dtsi index 2d6f4a4b1e..6ea1b2784d 100644 --- a/arch/riscv/dts/cv18xx.dtsi +++ b/arch/riscv/dts/cv18xx.dtsi @@ -53,6 +53,12 @@ dma-noncoherent; ranges;
+ clk: clock-controller@3002000 { + reg = <0x03002000 0x1000>; + clocks = <&osc>; + #clock-cells = <1>; + }; + gpio0: gpio@3020000 { compatible = "snps,dw-apb-gpio"; reg = <0x3020000 0x1000>;

Add sdhci node for cv1800b SoC.
Signed-off-by: Kongyang Liu seashell11234455@gmail.com ---
arch/riscv/dts/cv1800b.dtsi | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
diff --git a/arch/riscv/dts/cv1800b.dtsi b/arch/riscv/dts/cv1800b.dtsi index baf641829e..11b84b5e79 100644 --- a/arch/riscv/dts/cv1800b.dtsi +++ b/arch/riscv/dts/cv1800b.dtsi @@ -7,6 +7,29 @@
/ { compatible = "sophgo,cv1800b"; + + sd: sdhci@4310000 { + compatible = "sophgo,cv1800b-sdhci"; + reg = <0x4310000 0x1000>; + reg-names = "core_mem"; + bus-width = <4>; + cap-sd-highspeed; + cap-mmc-highspeed; + sd-uhs-sdr12; + sd-uhs-sdr25; + sd-uhs-sdr50; + sd-uhs-sdr104; + no-sdio; + no-mmc; + no-1-8-v; + src-frequency = <375000000>; + min-frequency = <400000>; + max-frequency = <200000000>; + reset_tx_rx_phy; + reset-names = "sdhci"; + pll_index = <0x6>; + pll_reg = <0x3002070>; + }; };
&plic {

Add configs related to sdhci and mmc for Sophgo Milk-V Duo board
Signed-off-by: Kongyang Liu seashell11234455@gmail.com ---
configs/milkv_duo_defconfig | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/configs/milkv_duo_defconfig b/configs/milkv_duo_defconfig index 9eca6abfbc..60eff78ae3 100644 --- a/configs/milkv_duo_defconfig +++ b/configs/milkv_duo_defconfig @@ -11,13 +11,23 @@ CONFIG_TARGET_MILKV_DUO=y CONFIG_ARCH_RV64I=y CONFIG_RISCV_SMODE=y CONFIG_FIT=y +CONFIG_SYS_BOOTM_LEN=0x4000000 CONFIG_SUPPORT_RAW_INITRD=y +CONFIG_SYS_CBSIZE=512 +CONFIG_SYS_PBSIZE=544 CONFIG_HUSH_PARSER=y CONFIG_SYS_PROMPT="milkv_duo# " CONFIG_SYS_MAXARGS=64 -CONFIG_SYS_CBSIZE=512 -CONFIG_SYS_PBSIZE=544 -CONFIG_SYS_BOOTM_LEN=0x4000000 +CONFIG_CMD_MMC=y +CONFIG_CMD_PART=y +CONFIG_CMD_FAT=y +CONFIG_CMD_FS_GENERIC=y CONFIG_ENV_OVERWRITE=y +CONFIG_MMC=y +CONFIG_MMC_IO_VOLTAGE=y +CONFIG_MMC_UHS_SUPPORT=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_ADMA=y +CONFIG_MMC_SDHCI_CV1800B=y CONFIG_SYS_NS16550=y CONFIG_SYS_NS16550_MEM32=y
participants (1)
-
Kongyang Liu