[PATCH 1/4] mmc: cv1800b: Add sdhci driver support for cv1800b SoC
Kongyang Liu
seashell11234455 at gmail.com
Fri Feb 2 10:43:33 CET 2024
Add sdhci driver for cv1800b SoC.
Signed-off-by: Kongyang Liu <seashell11234455 at 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 at 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
More information about the U-Boot
mailing list