diff mbox series

[1/4] mmc: cv1800b: Add sdhci driver support for cv1800b SoC

Message ID 20240202094418.334221-2-seashell11234455@gmail.com
State Superseded
Delegated to: Andes
Headers show
Series mmc: sophgo: milkv_duo: Add SD card support for Milk-V Duo board | expand

Commit Message

Kongyang Liu Feb. 2, 2024, 9:43 a.m. UTC
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 mbox series

Patch

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,
+};