Add sdhci driver for cv1800b SoC.

Signed-off-by: Kongyang Liu <seashell11234...@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 <seashell11234...@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,
+};
-- 
2.41.0

Reply via email to