[PATCH 2/3] mmc: sdhci-cadence6: add Cadence SD6HC support

Alex Soo yuklin.soo at starfivetech.com
Wed Dec 27 22:53:21 PST 2023


Add a driver for the Cadence SD6HC SD/SDIO/eMMC controller.

Signed-off-by: Alex Soo <yuklin.soo at starfivetech.com>
---
 MAINTAINERS                           |   6 +
 drivers/mmc/host/Kconfig              |  11 +
 drivers/mmc/host/Makefile             |   2 +
 drivers/mmc/host/sdhci-cadence6-phy.c | 384 +++++++++++++++++++
 drivers/mmc/host/sdhci-cadence6.c     | 531 ++++++++++++++++++++++++++
 drivers/mmc/host/sdhci-cadence6.h     | 148 +++++++
 6 files changed, 1082 insertions(+)
 create mode 100644 drivers/mmc/host/sdhci-cadence6-phy.c
 create mode 100644 drivers/mmc/host/sdhci-cadence6.c
 create mode 100644 drivers/mmc/host/sdhci-cadence6.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 451ee21086a7..88e2120a4cef 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4537,6 +4537,12 @@ S:	Orphan
 F:	Documentation/devicetree/bindings/mtd/cadence-nand-controller.txt
 F:	drivers/mtd/nand/raw/cadence-nand-controller.c
 
+CADENCE SDHCI MMC HOST CONTROLLER VERSION 6 DRIVER
+M:	Alex Soo <yuklin.soo at starfivetech.com>
+S:	Supported
+F:	Documentation/devicetree/bindings/mmc/cdns,sd6hci.yaml
+F:	drivers/mmc/host/sdhci-cadence6*
+
 CADENCE USB3 DRD IP DRIVER
 M:	Peter Chen <peter.chen at kernel.org>
 M:	Pawel Laszczak <pawell at cadence.com>
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 58bd5fe4cd25..892923ee5a7a 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -263,6 +263,17 @@ config MMC_SDHCI_CADENCE
 
 	  If unsure, say N.
 
+config MMC_SDHCI_CADENCE6
+	tristate "SDHCI support for the Cadence SD/SDIO/eMMC (Version 6) controller"
+	depends on MMC_SDHCI_PLTFM
+	depends on OF
+	help
+	  This selects the Cadence SD/SDIO/eMMC (Version 6) driver.
+
+	  If you have a controller with this interface, say Y or M here.
+
+	  If unsure, say N.
+
 config MMC_SDHCI_ESDHC_MCF
 	tristate "SDHCI support for the Freescale eSDHC ColdFire controller"
 	depends on M5441x
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index d0be4465f3ec..9a4cbedfd196 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -109,3 +109,5 @@ endif
 
 obj-$(CONFIG_MMC_SDHCI_XENON)	+= sdhci-xenon-driver.o
 sdhci-xenon-driver-y		+= sdhci-xenon.o sdhci-xenon-phy.o
+obj-$(CONFIG_MMC_SDHCI_CADENCE6)	+= sdhci-cadence6-driver.o
+sdhci-cadence6-driver-y		+= sdhci-cadence6.o sdhci-cadence6-phy.o
diff --git a/drivers/mmc/host/sdhci-cadence6-phy.c b/drivers/mmc/host/sdhci-cadence6-phy.c
new file mode 100644
index 000000000000..8429f35c0c96
--- /dev/null
+++ b/drivers/mmc/host/sdhci-cadence6-phy.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PHY support for Cadence version 6 SDHC
+ *
+ * Copyright (C) 2022-2023 Shanghai StarFive Technology Co., Ltd.
+ * Author: Alex Soo <yuklin.soo at starfivetech.com>
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/iopoll.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+
+#include "sdhci-pltfm.h"
+#include "sdhci-cadence6.h"
+
+static struct sdhci_cdns_hrs_reg_vals hrs07_reg_cfg[] = {
+		{0x00090000},	/* MMC legacy or SD default */
+		{0x00090000},	/* MMC High Speed */
+		{0x00090001},	/* SD High Speed  */
+		{0x00090001},	/* SD UHS1 SDR12  */
+		{0x00090001},	/* SD UHS1 SDR25  */
+		{0x00090001},	/* SD UHS1 SDR50  */
+		{0x000a0001},	/* SD UHS1 SDR104 */
+		{0x00090001},	/* SD UHS1 DDR50  */
+		{0x00090001},	/* MMC DDR52 */
+		{0x00090000},	/* MMC HS200 */
+		{0x00090001},	/* MMC HS400 */
+};
+
+static struct sdhci_cdns_hrs_reg_vals hrs09_reg_cfg[] = {
+		{0x0001800C},	/* MMC legacy or SD default */
+		{0x0001800C},	/* MMC High Speed */
+		{0x0001800C},	/* SD High Speed  */
+		{0x0001800C},	/* SD UHS1 SDR12  */
+		{0x0001800C},	/* SD UHS1 SDR25  */
+		{0x0001800C},	/* SD UHS1 SDR50  */
+		{0x0000000C},	/* SD UHS1 SDR104 */
+		{0x0001800C},	/* SD UHS1 DDR50  */
+		{0x0001800C},	/* MMC DDR52 */
+		{0x0001800C},	/* MMC HS200 */
+		{0x0000000C},	/* MMC HS400 */
+};
+
+static struct sdhci_cdns_hrs_reg_vals hrs10_reg_cfg[] = {
+		{0x00010000},	/* MMC legacy or SD default */
+		{0x00010000},	/* MMC High Speed */
+		{0x00030000},	/* SD High Speed  */
+		{0x00020000},	/* SD UHS1 SDR12  */
+		{0x00030000},	/* SD UHS1 SDR25  */
+		{0x00060000},	/* SD UHS1 SDR50  */
+		{0x00090000},	/* SD UHS1 SDR104 */
+		{0x00020000},	/* SD UHS1 DDR50  */
+		{0x00020000},	/* MMC DDR52 */
+		{0x00080000},	/* MMC HS200 */
+		{0x00080000},	/* MMC HS400 */
+};
+
+static struct sdhci_cdns_hrs_reg_vals hrs16_reg_cfg[] = {
+		{0x00000101},	/* MMC legacy or SD default */
+		{0x00000101},	/* MMC High Speed */
+		{0x00000000},	/* SD High Speed  */
+		{0x00000000},	/* SD UHS1 SDR12  */
+		{0x00000000},	/* SD UHS1 SDR25  */
+		{0x00000000},	/* SD UHS1 SDR50  */
+		{0x00000101},	/* SD UHS1 SDR104 */
+		{0x11000000},	/* SD UHS1 DDR50  */
+		{0x11000001},	/* MMC DDR52 */
+		{0x00000101},	/* MMC HS200 */
+		{0x11000001},	/* MMC HS400 */
+};
+
+static struct sdhci_cdns_phy_reg_pairs sd_ds_mode_setting[] = {
+		{0x00380000, PHY_DQS_TIMING_REG_ADDR},
+		{0x01A00040, PHY_GATE_LPBK_CTRL_REG_ADDR},
+		{0x00000000, PHY_DLL_SLAVE_CTRL_REG_ADDR},
+		{0x00000180, PHY_CTRL_REG_ADDR},
+		{0x00000001, PHY_DQ_TIMING_REG_ADDR},
+};
+
+static struct sdhci_cdns_phy_reg_pairs sd_hs_mode_setting[] = {
+		{0x00380000, PHY_DQS_TIMING_REG_ADDR},
+		{0x01A00040, PHY_GATE_LPBK_CTRL_REG_ADDR},
+		{0x00000000, PHY_DLL_SLAVE_CTRL_REG_ADDR},
+		{0x00000180, PHY_CTRL_REG_ADDR},
+		{0x00000001, PHY_DQ_TIMING_REG_ADDR},
+};
+
+static struct sdhci_cdns_phy_reg_pairs sd_uhs_sdr12_mode_setting[] = {
+		{0x00380000, PHY_DQS_TIMING_REG_ADDR},
+		{0x01A00040, PHY_GATE_LPBK_CTRL_REG_ADDR},
+		{0x00000000, PHY_DLL_SLAVE_CTRL_REG_ADDR},
+		{0x00000180, PHY_CTRL_REG_ADDR},
+		{0x00000001, PHY_DQ_TIMING_REG_ADDR},
+};
+
+static struct sdhci_cdns_phy_reg_pairs sd_uhs_sdr25_mode_setting[] = {
+		{0x00380000, PHY_DQS_TIMING_REG_ADDR},
+		{0x01A00040, PHY_GATE_LPBK_CTRL_REG_ADDR},
+		{0x00000000, PHY_DLL_SLAVE_CTRL_REG_ADDR},
+		{0x00000180, PHY_CTRL_REG_ADDR},
+		{0x00000001, PHY_DQ_TIMING_REG_ADDR},
+};
+
+static struct sdhci_cdns_phy_reg_pairs sd_uhs_sdr50_mode_setting[] = {
+		{0x00380000, PHY_DQS_TIMING_REG_ADDR},
+		{0x01A00040, PHY_GATE_LPBK_CTRL_REG_ADDR},
+		{0x00000000, PHY_DLL_SLAVE_CTRL_REG_ADDR},
+		{0x00000180, PHY_CTRL_REG_ADDR},
+		{0x00000001, PHY_DQ_TIMING_REG_ADDR},
+};
+
+static struct sdhci_cdns_phy_reg_pairs sd_uhs_sdr104_mode_setting[] = {
+		{0x00380000, PHY_DQS_TIMING_REG_ADDR},
+		{0x01A00040, PHY_GATE_LPBK_CTRL_REG_ADDR},
+		{0x00004D00, PHY_DLL_SLAVE_CTRL_REG_ADDR},
+		{0x01000001, PHY_DQ_TIMING_REG_ADDR},
+};
+
+static struct sdhci_cdns_phy_reg_pairs sd_uhs_ddr50_mode_setting[] = {
+		{0x00380000, PHY_DQS_TIMING_REG_ADDR},
+		{0x01A00040, PHY_GATE_LPBK_CTRL_REG_ADDR},
+		{0x00000000, PHY_DLL_SLAVE_CTRL_REG_ADDR},
+		{0x00000180, PHY_CTRL_REG_ADDR},
+		{0x00000001, PHY_DQ_TIMING_REG_ADDR},
+};
+
+static struct sdhci_cdns_phy_reg_pairs emmc_sdr_mode_setting[] = {
+		{0x00380004, PHY_DQS_TIMING_REG_ADDR},
+		{0x01A00040, PHY_GATE_LPBK_CTRL_REG_ADDR},
+		{0x00000000, PHY_DLL_SLAVE_CTRL_REG_ADDR},
+		{0x00000001, PHY_DQ_TIMING_REG_ADDR},
+};
+
+static struct sdhci_cdns_phy_reg_pairs emmc_sdr_bc_mode_setting[] = {
+		{0x00380004, PHY_DQS_TIMING_REG_ADDR},
+		{0x01A00040, PHY_GATE_LPBK_CTRL_REG_ADDR},
+		{0x00000000, PHY_DLL_SLAVE_CTRL_REG_ADDR},
+		{0x00000001, PHY_DQ_TIMING_REG_ADDR},
+};
+
+static struct sdhci_cdns_phy_reg_pairs emmc_ddr_mode_setting[] = {
+		{0x00380004, PHY_DQS_TIMING_REG_ADDR},
+		{0x01A00040, PHY_GATE_LPBK_CTRL_REG_ADDR},
+		{0x00000000, PHY_DLL_SLAVE_CTRL_REG_ADDR},
+		{0x00000001, PHY_DQ_TIMING_REG_ADDR},
+};
+
+static struct sdhci_cdns_phy_reg_pairs emmc_hs200_mode_setting[] = {
+		{0x00380004, PHY_DQS_TIMING_REG_ADDR},
+		{0x01A00040, PHY_GATE_LPBK_CTRL_REG_ADDR},
+		{0x00DADA00, PHY_DLL_SLAVE_CTRL_REG_ADDR},
+		{0x00000001, PHY_DQ_TIMING_REG_ADDR},
+};
+
+static struct sdhci_cdns_phy_reg_pairs emmc_hs400_mode_setting[] = {
+		{0x00380004, PHY_DQS_TIMING_REG_ADDR},
+		{0x01A00040, PHY_GATE_LPBK_CTRL_REG_ADDR},
+		{0x00DAD800, PHY_DLL_SLAVE_CTRL_REG_ADDR},
+		{0x00000001, PHY_DQ_TIMING_REG_ADDR},
+};
+
+static struct sdhci_cdns_phy_reg_pairs emmc_hs400es_mode_setting[] = {
+		{0x00680000, PHY_DQS_TIMING_REG_ADDR},
+		{0x80A40040, PHY_GATE_LPBK_CTRL_REG_ADDR},
+		{0x04004B40, PHY_DLL_SLAVE_CTRL_REG_ADDR},
+		{0x08000001, PHY_DQ_TIMING_REG_ADDR},
+};
+
+static struct sdhci_cdns_phy_reg_vals sd_ds_mode_cfgs = {
+		.reg_pairs = sd_ds_mode_setting,
+		.num_regs = ARRAY_SIZE(sd_ds_mode_setting),
+};
+
+static struct sdhci_cdns_phy_reg_vals sd_hs_mode_cfgs = {
+		.reg_pairs = sd_hs_mode_setting,
+		.num_regs = ARRAY_SIZE(sd_hs_mode_setting),
+};
+
+static struct sdhci_cdns_phy_reg_vals sd_uhs_sdr12_mode_cfgs = {
+		.reg_pairs = sd_uhs_sdr12_mode_setting,
+		.num_regs = ARRAY_SIZE(sd_uhs_sdr12_mode_setting),
+};
+
+static struct sdhci_cdns_phy_reg_vals sd_uhs_sdr25_mode_cfgs = {
+		.reg_pairs = sd_uhs_sdr25_mode_setting,
+		.num_regs = ARRAY_SIZE(sd_uhs_sdr25_mode_setting),
+};
+
+static struct sdhci_cdns_phy_reg_vals sd_uhs_sdr50_mode_cfgs = {
+		.reg_pairs = sd_uhs_sdr50_mode_setting,
+		.num_regs = ARRAY_SIZE(sd_uhs_sdr50_mode_setting),
+};
+
+static struct sdhci_cdns_phy_reg_vals sd_uhs_sdr104_mode_cfgs = {
+		.reg_pairs = sd_uhs_sdr104_mode_setting,
+		.num_regs = ARRAY_SIZE(sd_uhs_sdr104_mode_setting),
+};
+
+static struct sdhci_cdns_phy_reg_vals sd_uhs_ddr50_mode_cfgs = {
+		.reg_pairs = sd_uhs_ddr50_mode_setting,
+		.num_regs = ARRAY_SIZE(sd_uhs_ddr50_mode_setting),
+};
+
+static struct sdhci_cdns_phy_reg_vals emmc_sdr_mode_cfgs = {
+		.reg_pairs = emmc_sdr_mode_setting,
+		.num_regs = ARRAY_SIZE(emmc_sdr_mode_setting),
+};
+
+static struct sdhci_cdns_phy_reg_vals emmc_sdr_bc_mode_cfgs = {
+		.reg_pairs = emmc_sdr_bc_mode_setting,
+		.num_regs = ARRAY_SIZE(emmc_sdr_bc_mode_setting),
+};
+
+static struct sdhci_cdns_phy_reg_vals emmc_ddr_mode_cfgs = {
+		.reg_pairs = emmc_ddr_mode_setting,
+		.num_regs = ARRAY_SIZE(emmc_ddr_mode_setting),
+};
+
+static struct sdhci_cdns_phy_reg_vals emmc_hs200_mode_cfgs = {
+		.reg_pairs = emmc_hs200_mode_setting,
+		.num_regs = ARRAY_SIZE(emmc_hs200_mode_setting),
+};
+
+static struct sdhci_cdns_phy_reg_vals emmc_hs400_mode_cfgs = {
+		.reg_pairs = emmc_hs400_mode_setting,
+		.num_regs = ARRAY_SIZE(emmc_hs400_mode_setting),
+};
+
+static struct sdhci_cdns_phy_reg_vals emmc_hs400es_mode_cfgs = {
+		.reg_pairs = emmc_hs400es_mode_setting,
+		.num_regs = ARRAY_SIZE(emmc_hs400es_mode_setting),
+};
+
+/* Cadence SDMMC version 6 Combo PHY registers (aligned 32-bit) read/write functions */
+
+unsigned int sdhci_cdns_read_phy_reg(struct sdhci_cdns_priv *priv, u32 address)
+{
+	writel(address, priv->hrs_addr + SDHCI_CDNS_HRS04);
+	return readl(priv->hrs_addr + SDHCI_CDNS_HRS05);
+}
+
+void sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, u32 address, u32 value)
+{
+	writel(address, priv->hrs_addr + SDHCI_CDNS_HRS04);
+	writel(value, priv->hrs_addr + SDHCI_CDNS_HRS05);
+}
+
+int sdhci_cdns_reset_phy_dll(struct sdhci_host *host, unsigned int reset)
+{
+	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS09;
+	u32 tmp;
+	int ret;
+
+	tmp = readl(reg);
+	tmp &= ~SDHCI_CDNS_HRS09_PHY_SW_RESET;
+
+	if (reset)	/* Switch On DLL Reset */
+		tmp |= FIELD_PREP(SDHCI_CDNS_HRS09_PHY_SW_RESET, 0);
+	else		/* Switch Off DLL Reset */
+		tmp |= FIELD_PREP(SDHCI_CDNS_HRS09_PHY_SW_RESET, 1);
+
+	writel(tmp, reg);
+
+	if (!reset) {
+		ret = readl_poll_timeout(reg, tmp, (tmp & SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE),
+					 0, 3000);
+	}
+
+	return ret;
+}
+
+int sdhci_cdns_phy_adj(struct sdhci_host *host, unsigned char timing)
+{
+	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	struct sdhci_cdns_phy_reg_vals *sdhci_cdns_mode_init;
+	struct sdhci_cdns_phy_reg_pairs *reg_pairs;
+	void __iomem *reg;
+	u32 num_regs, mode, tmp;
+	int i;
+
+	mode = sdhci_cdns_get_emmc_mode(priv);
+
+	/* Switch On the DLL Reset */
+	sdhci_cdns_reset_phy_dll(host, 1);
+
+	/* Initialize the PHY registers for different speed modes */
+	switch (timing) {
+	case MMC_TIMING_LEGACY:
+		if (mode == SDHCI_CDNS_HRS06_MODE_SD)
+			sdhci_cdns_mode_init = &sd_ds_mode_cfgs;
+		else
+			sdhci_cdns_mode_init = &emmc_sdr_bc_mode_cfgs;
+		break;
+	case MMC_TIMING_MMC_HS:
+		sdhci_cdns_mode_init = &emmc_sdr_mode_cfgs;
+		break;
+	case MMC_TIMING_SD_HS:
+		sdhci_cdns_mode_init = &sd_hs_mode_cfgs;
+		break;
+	case MMC_TIMING_UHS_SDR12:
+		sdhci_cdns_mode_init = &sd_uhs_sdr12_mode_cfgs;
+		break;
+	case MMC_TIMING_UHS_SDR25:
+		sdhci_cdns_mode_init = &sd_uhs_sdr25_mode_cfgs;
+		break;
+	case MMC_TIMING_UHS_SDR50:
+		sdhci_cdns_mode_init = &sd_uhs_sdr50_mode_cfgs;
+		break;
+	case MMC_TIMING_UHS_SDR104:
+		sdhci_cdns_mode_init = &sd_uhs_sdr104_mode_cfgs;
+		break;
+	case MMC_TIMING_UHS_DDR50:
+		sdhci_cdns_mode_init = &sd_uhs_ddr50_mode_cfgs;
+		break;
+	case MMC_TIMING_MMC_DDR52:
+		sdhci_cdns_mode_init = &emmc_ddr_mode_cfgs;
+		break;
+	case MMC_TIMING_MMC_HS200:
+		sdhci_cdns_mode_init = &emmc_hs200_mode_cfgs;
+		break;
+	case MMC_TIMING_MMC_HS400:
+		if (priv->enhanced_strobe)
+			sdhci_cdns_mode_init = &emmc_hs400es_mode_cfgs;
+		else
+			sdhci_cdns_mode_init = &emmc_hs400_mode_cfgs;
+		break;
+	default:
+		dev_err(mmc_dev(host->mmc), "%s: Invalid \"timing\" value\n",
+			mmc_hostname(host->mmc));
+		return -EINVAL;
+	}
+
+	reg_pairs = sdhci_cdns_mode_init->reg_pairs;
+	num_regs = sdhci_cdns_mode_init->num_regs;
+	for (i = 0; i < num_regs - 1; i++)
+		sdhci_cdns_write_phy_reg(priv, reg_pairs[i].off, reg_pairs[i].val);
+
+	/* Switch Off the DLL Reset */
+	sdhci_cdns_reset_phy_dll(host, 0);
+
+	/* Set PHY DQ TIMING control register */
+	sdhci_cdns_write_phy_reg(priv, reg_pairs[i].off, reg_pairs[i].val);
+
+	/* Set HRS09 register */
+	reg = priv->hrs_addr + SDHCI_CDNS_HRS09;
+	tmp = readl(reg);
+	tmp &= ~(SDHCI_CDNS_HRS09_EXTENDED_WR_MODE |
+		 SDHCI_CDNS_HRS09_EXTENDED_RD_MODE |
+		 SDHCI_CDNS_HRS09_RDDATA_EN |
+		 SDHCI_CDNS_HRS09_RDCMD_EN);
+	tmp |= hrs09_reg_cfg[timing].val;
+	writel(tmp, reg);
+
+	/* Set HRS10 register */
+	reg = priv->hrs_addr + SDHCI_CDNS_HRS10;
+	tmp = hrs10_reg_cfg[timing].val;
+	writel(tmp, reg);
+
+	/* Set HRS16 register */
+	reg = priv->hrs_addr + SDHCI_CDNS_HRS16;
+	tmp = readl(reg);
+	tmp &= ~(SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY |
+		 SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY |
+		 SDHCI_CDNS_HRS16_WRDATA0_DLY |
+		 SDHCI_CDNS_HRS16_WRCMD0_DLY);
+	tmp |= hrs16_reg_cfg[timing].val;
+	writel(tmp, reg);
+
+	/* Set HRS07 register */
+	reg = priv->hrs_addr + SDHCI_CDNS_HRS07;
+	tmp = readl(reg);
+	tmp &= ~(SDHCI_CDNS_HRS07_RW_COMPENSATE |
+		 SDHCI_CDNS_HRS07_IDELAY_VAL);
+	tmp |= hrs07_reg_cfg[timing].val;
+	writel(tmp, reg);
+
+	priv->timing = timing;
+
+	return 0;
+}
diff --git a/drivers/mmc/host/sdhci-cadence6.c b/drivers/mmc/host/sdhci-cadence6.c
new file mode 100644
index 000000000000..c9f5327a6f0e
--- /dev/null
+++ b/drivers/mmc/host/sdhci-cadence6.c
@@ -0,0 +1,531 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Cadence SD/SDIO/eMMC Host Controller Version 6
+ *
+ * Copyright (C) 2022-2023 Shanghai StarFive Technology Co., Ltd.
+ * Author: Alex Soo <yuklin.soo at starfivetech.com>
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+
+#include "sdhci-pltfm.h"
+#include "sdhci-cadence6.h"
+
+/* SRS - Slot Register Set (SDHCI-compatible) */
+#define SDHCI_CDNS_SRS_BASE		0x200
+
+static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_priv *priv, u32 mode)
+{
+	u32 tmp;
+
+	/* The speed mode for eMMC is selected by HRS06 register */
+	tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06);
+	tmp &= ~SDHCI_CDNS_HRS06_MODE;
+	tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_MODE, mode);
+	writel(tmp, priv->hrs_addr + SDHCI_CDNS_HRS06);
+}
+
+u32 sdhci_cdns_get_emmc_mode(struct sdhci_cdns_priv *priv)
+{
+	u32 tmp;
+
+	tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06);
+	return FIELD_GET(SDHCI_CDNS_HRS06_MODE, tmp);
+}
+
+static int sdhci_cdns_hs200_set_tune_val(struct sdhci_host *host, unsigned int val)
+{
+	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	u32 tmp, tuneval;
+
+	tuneval = (val * 256) / 40;
+
+	tmp = sdhci_cdns_read_phy_reg(priv, PHY_DLL_SLAVE_CTRL_REG_ADDR);
+	tmp &= ~(PHY_DLL_SLAVE_CTRL_REG_READ_DQS_CMD_DELAY |
+		 PHY_DLL_SLAVE_CTRL_REG_READ_DQS_DELAY);
+	tmp |= FIELD_PREP(PHY_DLL_SLAVE_CTRL_REG_READ_DQS_CMD_DELAY, tuneval) |
+		FIELD_PREP(PHY_DLL_SLAVE_CTRL_REG_READ_DQS_DELAY, tuneval);
+	sdhci_cdns_write_phy_reg(priv, PHY_DLL_SLAVE_CTRL_REG_ADDR, tmp);
+
+	return 0;
+}
+
+static int sdhci_cdns_execute_sd_tuning(struct sdhci_host *host, u32 opcode)
+{
+	u16 ctrl;
+	int i;
+
+	/* reset and restart the tuning block */
+	sdhci_start_tuning(host);
+
+	/* Start the SD tuning */
+	for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) {
+		sdhci_send_tuning(host, opcode);
+
+		if (!host->tuning_done) {
+			sdhci_abort_tuning(host, opcode);
+			break;
+		}
+
+		ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+		if (!(ctrl & SDHCI_CTRL_EXEC_TUNING)) {
+			if (ctrl & SDHCI_CTRL_TUNED_CLK)
+				return 0; /* Success! */
+			break;
+		}
+	}
+
+	if (!host->tuning_done) {
+		dev_err(mmc_dev(host->mmc), "%s: SD tuning timed out!\n",
+			mmc_hostname(host->mmc));
+		return -ETIMEDOUT;
+	}
+
+	dev_err(mmc_dev(host->mmc), "%s: SD tuning failed!\n",
+		mmc_hostname(host->mmc));
+	sdhci_reset_tuning(host);
+
+	return -EAGAIN;
+}
+
+static int sdhci_cdns_execute_emmc_tuning(struct sdhci_host *host, u32 opcode)
+{
+	int cur_streak = 0;
+	int max_streak = 0;
+	int end_of_streak = 0;
+	int i, ret = 0;
+
+	/* Start the eMMC tuning loop */
+	for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) {
+		/* Set tune value to adjust timing for data sampling */
+		if (sdhci_cdns_hs200_set_tune_val(host, i) ||
+		    mmc_send_tuning(host->mmc, opcode, NULL)) { /* bad */
+			cur_streak = 0;
+		} else { /* good */
+			cur_streak++;
+			if (cur_streak > max_streak) {
+				max_streak = cur_streak;
+				end_of_streak = i;
+			}
+		}
+	}
+
+	if (!max_streak) {
+		dev_err(mmc_dev(host->mmc), "%s: %s: eMMC tuning failed!\n",
+			__func__, mmc_hostname(host->mmc));
+		ret = -EIO;
+	}
+
+	return sdhci_cdns_hs200_set_tune_val(host, end_of_streak - max_streak / 2);
+}
+
+static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host)
+{
+	/* Timeout clock freq (KHz) */
+	return host->max_clk / 1000;
+}
+
+static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
+{
+	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	int ret = 0;
+
+	/*
+	 * execute tuning for UHS_SDR104 and MMC_HS200
+	 */
+	if (host->timing != MMC_TIMING_MMC_HS200 &&
+	    host->timing != MMC_TIMING_MMC_HS400 &&
+	    host->timing != MMC_TIMING_UHS_SDR104)
+		return 0;
+
+	if (host->timing == MMC_TIMING_MMC_HS400)
+		if (priv->enhanced_strobe)
+			return 0;
+
+	if (host->timing == MMC_TIMING_UHS_SDR104) {
+		host->tuning_err = sdhci_cdns_execute_sd_tuning(host, opcode);
+		sdhci_end_tuning(host);
+		return 0;
+	} else if (host->timing == MMC_TIMING_MMC_HS200 ||
+		   host->timing == MMC_TIMING_MMC_HS400) {
+		ret = sdhci_cdns_execute_emmc_tuning(host, opcode);
+	}
+
+	return ret;
+}
+
+static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host,
+					 unsigned int timing)
+{
+	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	u32 mode;
+
+	switch (timing) {
+	case MMC_TIMING_LEGACY:
+		mode = SDHCI_CDNS_HRS06_MODE_MMC_LEGACY;
+		break;
+	case MMC_TIMING_MMC_HS:
+		mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR;
+		break;
+	case MMC_TIMING_MMC_DDR52:
+		mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR;
+		break;
+	case MMC_TIMING_MMC_HS200:
+		mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200;
+		break;
+	case MMC_TIMING_MMC_HS400:
+		if (priv->enhanced_strobe)
+			mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400ES;
+		else
+			mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400;
+		break;
+	default:
+		mode = SDHCI_CDNS_HRS06_MODE_SD;
+		break;
+	}
+
+	sdhci_cdns_set_emmc_mode(priv, mode);
+
+	/* For SD, fall back to the default handler to set the UHS-I mode */
+	if (mode == SDHCI_CDNS_HRS06_MODE_SD)
+		sdhci_set_uhs_signaling(host, timing);
+
+	/* Set DLL PHY registers for different timing modes */
+	sdhci_cdns_phy_adj(host, timing);
+}
+
+static void sdhci_cdns_hw_reset(struct sdhci_host *host)
+{
+	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	void __iomem *reg;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: %s: sd/emmc card hardware reset\n",
+		__func__, mmc_hostname(host->mmc));
+
+	reg = priv->hrs_addr + SDHCI_CDNS_HRS11;
+	writel(SDHCI_CDNS_HRS11_EMMC_RST, reg);
+	udelay(9);
+	writel(!SDHCI_CDNS_HRS11_EMMC_RST, reg);
+	usleep_range(300, 1000);
+}
+
+static const struct sdhci_ops sdhci_cdns_ops = {
+	.set_clock = sdhci_set_clock,
+	.get_max_clock = sdhci_pltfm_clk_get_max_clock,
+	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
+	.set_bus_width = sdhci_set_bus_width,
+	.reset = sdhci_reset,
+	.platform_execute_tuning = sdhci_cdns_execute_tuning,
+	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
+	.hw_reset = sdhci_cdns_hw_reset,
+};
+
+static const struct sdhci_pltfm_data sdhci_cdns_jh8100_pltfm_data = {
+	.ops = &sdhci_cdns_ops,
+	.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
+};
+
+static const struct sdhci_pltfm_data sdhci_cdns_pltfm_data = {
+	.ops = &sdhci_cdns_ops,
+};
+
+static int sdhci_cdns_clk_enable(struct sdhci_host *host)
+{
+	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	int ret = 0;
+
+	ret = reset_control_deassert(priv->reset);
+	if (ret) {
+		dev_err(mmc_dev(host->mmc), "%s: failed to deassert main reset: %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(priv->clk);
+	if (ret) {
+		dev_err(mmc_dev(host->mmc), "%s: failed to enable main clock :%d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	return ret;
+}
+
+static void sdhci_cdns_clk_disable(struct sdhci_host *host)
+{
+	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+
+	clk_disable_unprepare(priv->clk);
+	clk_put(priv->clk);
+	reset_control_assert(priv->reset);
+}
+
+static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
+					     struct mmc_ios *ios)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
+	u32 mode;
+
+	priv->enhanced_strobe = ios->enhanced_strobe;
+
+	mode = sdhci_cdns_get_emmc_mode(priv);
+
+	if (mode == SDHCI_CDNS_HRS06_MODE_MMC_HS400 && ios->enhanced_strobe)
+		sdhci_cdns_set_emmc_mode(priv,
+					 SDHCI_CDNS_HRS06_MODE_MMC_HS400ES);
+
+	if (mode == SDHCI_CDNS_HRS06_MODE_MMC_HS400ES && !ios->enhanced_strobe)
+		sdhci_cdns_set_emmc_mode(priv,
+					 SDHCI_CDNS_HRS06_MODE_MMC_HS400);
+}
+
+static int sdhci_cdns_probe(struct platform_device *pdev)
+{
+	struct sdhci_host *host;
+	const struct sdhci_pltfm_data *data;
+	struct sdhci_pltfm_host *pltfm_host;
+	struct sdhci_cdns_priv *priv;
+	int ret;
+	struct device *dev = &pdev->dev;
+	static const u16 version = SDHCI_SPEC_420 << SDHCI_SPEC_VER_SHIFT;
+
+	data = of_device_get_match_data(dev);
+	if (!data)
+		data = &sdhci_cdns_pltfm_data;
+
+	host = sdhci_pltfm_init(pdev, data, 0);
+	if (IS_ERR(host)) {
+		ret = PTR_ERR(host);
+		goto free;
+	}
+
+	pltfm_host = sdhci_priv(host);
+
+	priv = sdhci_pltfm_priv(pltfm_host);
+	priv->hrs_addr = host->ioaddr;
+	priv->enhanced_strobe = false;
+
+	/* reset control line */
+	priv->reset = devm_reset_control_get_exclusive(dev, NULL);
+	if (IS_ERR(priv->reset)) {
+		dev_err(dev, "%s: request of reset line \"%s\" failed (%ld)\n",
+			__func__, "reset", PTR_ERR(priv->reset));
+		goto free;
+	}
+
+	/* SD master clock */
+	priv->sdmclk = devm_clk_get(dev, "sdmclk");
+	if (IS_ERR(priv->sdmclk)) {
+		dev_err(dev, "%s: request of SD master clock failed (%ld)\n",
+			__func__, PTR_ERR(priv->sdmclk));
+		goto err_clk_sdmclk;
+	}
+	pltfm_host->clk = priv->sdmclk;
+
+	/* main clock */
+	priv->clk = devm_clk_get(dev, "main");
+	if (IS_ERR(priv->clk)) {
+		dev_err(dev, "%s: request of main clock failed (%ld)\n",
+			__func__, PTR_ERR(priv->clk));
+		goto err_clk_main;
+	}
+
+	ret = sdhci_cdns_clk_enable(host);
+	if (ret)
+		goto err_clk_enable;
+
+	host->ioaddr += SDHCI_CDNS_SRS_BASE;
+	host->mmc_host_ops.hs400_enhanced_strobe =
+				sdhci_cdns_hs400_enhanced_strobe;
+	sdhci_enable_v4_mode(host);
+	__sdhci_read_caps(host, &version, NULL, NULL);
+
+	sdhci_get_of_property(pdev);
+
+	ret = mmc_of_parse(host->mmc);
+	if (ret)
+		goto disable_clk;
+
+	ret = sdhci_add_host(host);
+	if (ret)
+		goto disable_clk;
+
+	pm_runtime_enable(dev);
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_set_autosuspend_delay(dev, 250);
+
+	return 0;
+
+disable_clk:
+	clk_disable_unprepare(priv->clk);
+err_clk_enable:
+	clk_put(priv->clk);
+err_clk_main:
+	clk_put(priv->sdmclk);
+err_clk_sdmclk:
+	reset_control_put(priv->reset);
+free:
+	sdhci_pltfm_free(pdev);
+
+	return ret;
+}
+
+static int sdhci_cdns_remove(struct platform_device *pdev)
+{
+	struct sdhci_host *host = platform_get_drvdata(pdev);
+	int dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) ==
+		    0xffffffff);
+
+	sdhci_remove_host(host, dead);
+
+	pm_runtime_dont_use_autosuspend(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+
+	sdhci_cdns_clk_disable(host);
+
+	sdhci_pltfm_free(pdev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int __maybe_unused sdhci_cdns_suspend(struct device *dev)
+{
+	struct sdhci_host *host = dev_get_drvdata(dev);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_cdns_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	int ret;
+
+	if (host->tuning_mode != SDHCI_TUNING_MODE_3)
+		mmc_retune_needed(host->mmc);
+
+	ret = sdhci_suspend_host(host);
+	if (ret)
+		return ret;
+
+	clk_disable_unprepare(priv->clk);
+
+	return ret;
+}
+
+static int __maybe_unused sdhci_cdns_resume(struct device *dev)
+{
+	struct sdhci_host *host = dev_get_drvdata(dev);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_cdns_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	int ret;
+
+	ret = clk_prepare_enable(priv->clk);
+	if (ret) {
+		dev_err(dev, "can't enable the main clock\n");
+		return ret;
+	}
+
+	ret = sdhci_cdns_phy_adj(host, priv->timing);
+	if (ret)
+		goto disable_clk;
+
+	ret = sdhci_resume_host(host);
+	if (ret)
+		goto disable_clk;
+
+	return 0;
+
+disable_clk:
+	clk_disable_unprepare(priv->clk);
+
+	return ret;
+}
+#endif
+
+#ifdef CONFIG_PM
+static int sdhci_cdns_runtime_suspend(struct device *dev)
+{
+	struct sdhci_host *host = dev_get_drvdata(dev);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_cdns_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	int ret;
+
+	ret = sdhci_runtime_suspend_host(host);
+	if (ret)
+		return ret;
+
+	if (host->tuning_mode != SDHCI_TUNING_MODE_3)
+		mmc_retune_needed(host->mmc);
+
+	clk_disable_unprepare(priv->clk);
+
+	return 0;
+}
+
+static int sdhci_cdns_runtime_resume(struct device *dev)
+{
+	struct sdhci_host *host = dev_get_drvdata(dev);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_cdns_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	int ret;
+
+	ret = clk_prepare_enable(priv->clk);
+	if (ret) {
+		dev_err(dev, "can't enable the main clock\n");
+		return ret;
+	}
+
+	ret = sdhci_runtime_resume_host(host, 0);
+	if (ret)
+		goto disable_clk;
+	return 0;
+
+disable_clk:
+	clk_disable_unprepare(priv->clk);
+
+	return ret;
+}
+
+#endif
+
+static const struct dev_pm_ops sdhci_cdns_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(sdhci_cdns_suspend, sdhci_cdns_resume)
+	SET_RUNTIME_PM_OPS(sdhci_cdns_runtime_suspend,
+			   sdhci_cdns_runtime_resume, NULL)
+};
+
+static const struct of_device_id sdhci_cdns_match[] = {
+	{
+		.compatible = "starfive,jh8100-sd6hc",
+		.data = &sdhci_cdns_jh8100_pltfm_data,
+	},
+	{ .compatible = "cdns,sd6hc" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sdhci_cdns_match);
+
+static struct platform_driver sdhci_cdns_driver = {
+	.driver = {
+		.name = "sd6hci-cdns",
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+		.pm = &sdhci_cdns_pm_ops,
+		.of_match_table = sdhci_cdns_match,
+	},
+	.probe = sdhci_cdns_probe,
+	.remove = sdhci_cdns_remove,
+};
+module_platform_driver(sdhci_cdns_driver);
+
+MODULE_DESCRIPTION("Cadence SD/SDIO/eMMC Host Controller Version 6 Driver");
+MODULE_AUTHOR("Alex Soo <yuklin.soo at starfivetech.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/sdhci-cadence6.h b/drivers/mmc/host/sdhci-cadence6.h
new file mode 100644
index 000000000000..dc0201a32f80
--- /dev/null
+++ b/drivers/mmc/host/sdhci-cadence6.h
@@ -0,0 +1,148 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022-2023 Shanghai StarFive Technology Co., Ltd.
+ * Author: Alex Soo <yuklin.soo at starfivetech.com>
+ *
+ */
+#ifndef SDHCI_CADENCE_V6_H_
+#define SDHCI_CADENCE_V6_H_
+
+/* HRS - Host Register Set (specific to Cadence) */
+#define SDHCI_CDNS_HRS04		0x10		/* Combo PHY registers address */
+
+#define SDHCI_CDNS_HRS05		0x14		/* Combo PHY register data port */
+
+#define SDHCI_CDNS_HRS06		0x18		/* eMMC control */
+#define   SDHCI_CDNS_HRS06_MODE			GENMASK(2, 0)
+#define   SDHCI_CDNS_HRS06_MODE_SD		0x0
+#define   SDHCI_CDNS_HRS06_MODE_MMC_LEGACY	0x1
+#define   SDHCI_CDNS_HRS06_MODE_MMC_SDR		0x2
+#define   SDHCI_CDNS_HRS06_MODE_MMC_DDR		0x3
+#define   SDHCI_CDNS_HRS06_MODE_MMC_HS200	0x4
+#define   SDHCI_CDNS_HRS06_MODE_MMC_HS400	0x5
+#define   SDHCI_CDNS_HRS06_MODE_MMC_HS400ES	0x6
+
+#define SDHCI_CDNS_HRS07		0X1C		/* IO Delay Information */
+#define   SDHCI_CDNS_HRS07_RW_COMPENSATE		GENMASK(20, 16)
+#define   SDHCI_CDNS_HRS07_IDELAY_VAL			GENMASK(4, 0)
+
+#define SDHCI_CDNS_HRS09		0x24		/* PHY Control and Status */
+#define   SDHCI_CDNS_HRS09_RDDATA_EN		BIT(16)
+#define   SDHCI_CDNS_HRS09_RDCMD_EN		BIT(15)
+#define   SDHCI_CDNS_HRS09_EXTENDED_WR_MODE	BIT(3)
+#define   SDHCI_CDNS_HRS09_EXTENDED_RD_MODE	BIT(2)
+#define   SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE	BIT(1)
+#define   SDHCI_CDNS_HRS09_PHY_SW_RESET		BIT(0)
+
+#define SDHCI_CDNS_HRS10		0x28		/* SDCLK adjustment */
+#define   SDHCI_CDNS_HRS10_RDDATA_SWAP		BIT(22)
+#define   SDHCI_CDNS_HRS10_HCSDCLKADJ		GENMASK(19, 16)
+
+#define SDHCI_CDNS_HRS11		0x2C		/* eMMC reset */
+#define   SDHCI_CDNS_HRS11_EMMC_RST		BIT(0)
+
+#define SDHCI_CDNS_HRS16		0x40		/* CMD/DAT output delay */
+#define   SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY	GENMASK(31, 28)
+#define   SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY	GENMASK(27, 24)
+#define   SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY	GENMASK(23, 20)
+#define   SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY	GENMASK(19, 16)
+#define   SDHCI_CDNS_HRS16_WRDATA1_DLY		GENMASK(15, 12)
+#define   SDHCI_CDNS_HRS16_WRDATA0_DLY		GENMASK(11, 8)
+#define   SDHCI_CDNS_HRS16_WRCMD1_DLY		GENMASK(7, 4)
+#define   SDHCI_CDNS_HRS16_WRCMD0_DLY		GENMASK(3, 0)
+
+struct sdhci_cdns_hrs_reg_vals {
+	u32 val;
+};
+
+/* PHY Special Function Registers */
+#define DLL_PHY_REG_BASE		0x2000
+
+/* register to control the DQ related timing */
+#define PHY_DQ_TIMING_REG_ADDR		0x2000
+#define PHY_DQ_TIMING_REG_IO_MASK_ALWAYS_ON		BIT(31)
+#define PHY_DQ_TIMING_REG_IO_MASK_END			GENMASK(29, 27)
+#define PHY_DQ_TIMING_REG_IO_MASK_START			GENMASK(26, 24)
+#define PHY_DQ_TIMING_REG_DATA_SELECT_OE_END		GENMASK(2, 0)
+
+/* register to control the DQS related timing */
+#define PHY_DQS_TIMING_REG_ADDR		0x2004
+#define PHY_DQS_TIMING_REG_USE_EXT_LPBK_DQS		BIT(22)
+#define PHY_DQS_TIMING_REG_USE_LPBK_DQS			BIT(21)
+#define PHY_DQS_TIMING_REG_USE_PHONY_DQS		BIT(20)
+#define PHY_DQS_TIMING_REG_USE_PHONY_DQS_CMD		BIT(19)
+
+/* register to control the gate and loopback control related timing */
+#define PHY_GATE_LPBK_CTRL_REG_ADDR	0x2008
+#define PHY_GATE_LPBK_CTRL_REG_SYNC_METHOD		BIT(31)
+#define PHY_GATE_LPBK_CTRL_REG_RD_DEL_SEL		GENMASK(23, 19)
+#define PHY_GATE_LPBK_CTRL_REG_UNDERRUN_SUPPRESS	BIT(18)
+#define PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON		BIT(6)
+
+/* register to control the Master DLL logic */
+#define PHY_DLL_MASTER_CTRL_REG_ADDR	0x200C
+#define PHY_DLL_MASTER_CTRL_REG_PARAM_DLL_BYPASS_MODE	BIT(23)
+#define PHY_DLL_MASTER_CTRL_REG_PARAM_PHASE_DETECT_SEL	GENMASK(22, 20)
+#define PHY_DLL_MASTER_CTRL_REG_PARAM_DLL_LOCK_NUM	GENMASK(18, 16)
+#define PHY_DLL_MASTER_CTRL_REG_PARAM_DLL_START_POINT	GENMASK(7, 0)
+
+/* register to control the Slave DLL logic */
+#define PHY_DLL_SLAVE_CTRL_REG_ADDR	0x2010
+#define PHY_DLL_SLAVE_CTRL_REG_READ_DQS_CMD_DELAY	GENMASK(31, 24)
+#define PHY_DLL_SLAVE_CTRL_REG_CLK_WRITE_DELAY		GENMASK(15, 8)
+#define PHY_DLL_SLAVE_CTRL_REG_READ_DQS_DELAY		GENMASK(7, 0)
+
+/* register to control the global settings for PHY */
+#define PHY_CTRL_REG_ADDR		0x2080
+#define PHY_CTRL_REG_PU_PD_POLARITY			BIT(21)
+#define PHY_CTRL_REG_PHONY_DQS_TIMING			GENMASK(8, 4)
+#define PHY_CTRL_REG_CTRL_CLKPERIOD_DELAY		BIT(0)
+
+/*
+ * The tuned val register is 6 bit-wide, but not the whole of the range is
+ * available.  The range 0-42 seems to be available (then 43 wraps around to 0)
+ * but I am not quite sure if it is official.  Use only 0 to 39 for safety.
+ */
+#define SDHCI_CDNS_MAX_TUNING_LOOP	40
+
+struct sdhci_cdns_priv {
+	void			__iomem *hrs_addr;
+	bool			enhanced_strobe;
+	unsigned char		timing;
+	struct reset_control	*reset;
+	struct clk		*sdmclk;
+	struct clk		*clk;
+};
+
+enum sdhci_cdns_phy_reg {
+	PHY_DQS_TIMING,
+	PHY_GATE_LPBK_CTRL,
+	PHY_DLL_MASTER_CTRL,
+	PHY_DLL_SLAVE_CTRL,
+	PHY_CTRL,
+	PHY_DQ_TIMING,
+};
+
+struct sdhci_cdns_phy_reg_pairs {
+	u32 val;
+	u32 off;
+};
+
+struct sdhci_cdns_phy_reg_vals {
+	struct sdhci_cdns_phy_reg_pairs *reg_pairs;
+	u32 num_regs;
+};
+
+static inline void *sdhci_cdns_priv(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+
+	return sdhci_pltfm_priv(pltfm_host);
+}
+
+unsigned int sdhci_cdns_read_phy_reg(struct sdhci_cdns_priv *priv, u32 address);
+void sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, u32 address, u32 value);
+int sdhci_cdns_phy_adj(struct sdhci_host *host, unsigned char timing);
+u32 sdhci_cdns_get_emmc_mode(struct sdhci_cdns_priv *priv);
+
+#endif
-- 
2.25.1




More information about the linux-riscv mailing list