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

From: Alex Soo
Date: Thu Dec 28 2023 - 01:54:18 EST


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

Signed-off-by: Alex Soo <yuklin.soo@xxxxxxxxxxxxxxxx>
---
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@xxxxxxxxxxxxxxxx>
+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@xxxxxxxxxx>
M: Pawel Laszczak <pawell@xxxxxxxxxxx>
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@xxxxxxxxxxxxxxxx>
+ *
+ */
+
+#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@xxxxxxxxxxxxxxxx>
+ *
+ */
+
+#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@xxxxxxxxxxxxxxxx>");
+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@xxxxxxxxxxxxxxxx>
+ *
+ */
+#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