diff mbox series

[2/3] mmc: sdhci-cadence: SD6 Controller support

Message ID 20230605135821.4213-3-pmalgujar@marvell.com
State New
Delegated to: Jaehoon Chung
Headers show
Series mmc: sdhci-cadence: SD6 controller support | expand

Commit Message

Piyush Malgujar June 5, 2023, 1:58 p.m. UTC
From: Dhananjay Kangude <dkangude@cadence.com>

Add support for SD6 controller along with:
- HS200, HS400 and HS400ES support
- Host side Slew and drive configuration

These changes to support SD6 cadence IP are isolated from the
existing support of SD4 controller.

Signed-off-by: Dhananjay Kangude <dkangude@cadence.com>
Co-developed-by: Jayanthi Annadurai <jannadurai@marvell.com>
Signed-off-by: Jayanthi Annadurai <jannadurai@marvell.com>
Signed-off-by: Piyush Malgujar <pmalgujar@marvell.com>
---
 drivers/mmc/Kconfig         |    1 +
 drivers/mmc/sdhci-cadence.c | 1317 ++++++++++++++++++++++++++++++++++-
 2 files changed, 1301 insertions(+), 17 deletions(-)

Comments

Jaehoon Chung July 3, 2023, 8:58 a.m. UTC | #1
Hi,

On 6/5/23 22:58, Piyush Malgujar wrote:
> From: Dhananjay Kangude <dkangude@cadence.com>
> 
> Add support for SD6 controller along with:
> - HS200, HS400 and HS400ES support
> - Host side Slew and drive configuration
> 
> These changes to support SD6 cadence IP are isolated from the
> existing support of SD4 controller.
> 
> Signed-off-by: Dhananjay Kangude <dkangude@cadence.com>
> Co-developed-by: Jayanthi Annadurai <jannadurai@marvell.com>
> Signed-off-by: Jayanthi Annadurai <jannadurai@marvell.com>
> Signed-off-by: Piyush Malgujar <pmalgujar@marvell.com>
> ---
>  drivers/mmc/Kconfig         |    1 +
>  drivers/mmc/sdhci-cadence.c | 1317 ++++++++++++++++++++++++++++++++++-
>  2 files changed, 1301 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig
> index de01b9687bad28f3b493208c145f5c5dd2f59f78..6b724f1d1ed050b49e31a6cbe5f898b4b636c330 100644
> --- a/drivers/mmc/Kconfig
> +++ b/drivers/mmc/Kconfig
> @@ -566,6 +566,7 @@ config MMC_SDHCI_CADENCE
>  	depends on BLK && DM_MMC
>  	depends on MMC_SDHCI
>  	depends on OF_CONTROL
> +	select MMC_SDHCI_IO_ACCESSORS
>  	help
>  	  This selects the Cadence SD/SDIO/eMMC driver.
>  
> diff --git a/drivers/mmc/sdhci-cadence.c b/drivers/mmc/sdhci-cadence.c
> index 0bb258da63e442232310d9433b7b6882992bd45d..1d8e8e177eeeb6464ba9ade5fd63c1cca4554bb6 100644
> --- a/drivers/mmc/sdhci-cadence.c
> +++ b/drivers/mmc/sdhci-cadence.c
> @@ -6,6 +6,7 @@
>  
>  #include <common.h>
>  #include <dm.h>
> +#include <div64.h>
>  #include <asm/global_data.h>
>  #include <dm/device_compat.h>
>  #include <linux/bitfield.h>
> @@ -18,8 +19,19 @@
>  #include <mmc.h>
>  #include <sdhci.h>
>  
> +#define SDHCI_CDNS_SD6_MAXCLK		200000000
> +
> +#define DEFAULT_CMD_DELAY		16
> +#define SDHCI_CDNS_TUNE_START		16
> +#define SDHCI_CDNS_TUNE_STEP		6
> +#define SDHCI_CDNS_TUNE_ITERATIONS	40
> +
> +#define SDHCI_CDNS_HRS00			0x00
> +#define SDHCI_CDNS_HRS00_SWR			BIT(0)
> +
> +#define SDHCI_CDNS_HRS02			0x08		/* PHY access port */
> +#define SDHCI_CDNS_HRS04			0x10		/* PHY access port */
>  /* SD 4.0 Controller HRS - Host Register Set (specific to Cadence) */
> -#define SDHCI_CDNS_HRS04		0x10		/* PHY access port */
>  #define SDHCI_CDNS_SD4_HRS04_ACK		BIT(26)
>  #define SDHCI_CDNS_SD4_HRS04_RD			BIT(25)
>  #define SDHCI_CDNS_SD4_HRS04_WR			BIT(24)
> @@ -32,12 +44,93 @@
>  #define   SDHCI_CDNS_HRS06_TUNE			GENMASK(13, 8)
>  #define   SDHCI_CDNS_HRS06_MODE			GENMASK(2, 0)
>  #define   SDHCI_CDNS_HRS06_MODE_SD		0x0
> +#define SDHCI_CDNS_HRS06_MODE_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
>  
> +/* SD 6.0 Controller HRS - Host Register Set (Specific to Cadence) */
> +#define SDHCI_CDNS_SD6_HRS04_ADDR		GENMASK(15, 0)
> +
> +#define SDHCI_CDNS_HRS05			0x14
> +
> +#define SDHCI_CDNS_HRS07			0x1C
> +#define	SDHCI_CDNS_HRS07_RW_COMPENSATE		GENMASK(20, 16)
> +#define	SDHCI_CDNS_HRS07_IDELAY_VAL		GENMASK(4, 0)
> +
> +#define SDHCI_CDNS_HRS09			0x24
> +#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
> +#define	SDHCI_CDNS_HRS10_HCSDCLKADJ		GENMASK(19, 16)
> +
> +#define SDHCI_CDNS_SRS11			0x2c
> +#define SDHCI_CDNS_SRS11_SW_RESET_ALL		BIT(24)
> +#define SDHCI_CDNS_SRS11_SW_RESET_CMD		BIT(25)
> +#define SDHCI_CDNS_SRS11_SW_RESET_DAT		BIT(26)
> +
> +#define SDHCI_CDNS_HRS16			0x40
> +#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)
> +
> +/* PHY registers for SD6 controller */
> +#define SDHCI_CDNS_SD6_PHY_DQ_TIMING				0x2000
> +#define	SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON		BIT(31)
> +#define	SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_END		GENMASK(29, 27)
> +#define	SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_START		GENMASK(26, 24)
> +#define	SDHCI_CDNS_SD6_PHY_DQ_TIMING_DATA_SELECT_OE_END		GENMASK(2, 0)
> +
> +#define SDHCI_CDNS_SD6_PHY_DQS_TIMING				0x2004
> +#define	SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS		BIT(22)
> +#define	SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_LPBK_DQS		BIT(21)
> +#define	SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS		BIT(20)
> +#define	SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD		BIT(19)
> +
> +#define SDHCI_CDNS_SD6_PHY_GATE_LPBK				0x2008
> +#define	SDHCI_CDNS_SD6_PHY_GATE_LPBK_SYNC_METHOD		BIT(31)
> +#define	SDHCI_CDNS_SD6_PHY_GATE_LPBK_SW_HALF_CYCLE_SHIFT	BIT(28)
> +#define	SDHCI_CDNS_SD6_PHY_GATE_LPBK_RD_DEL_SEL			GENMASK(24, 19)
> +#define SDHCI_CDNS_SD6_PHY_GATE_LPBK_UNDERRUN_SUPPRESS		BIT(18)
> +#define	SDHCI_CDNS_SD6_PHY_GATE_LPBK_GATE_CFG_ALWAYS_ON		BIT(6)
> +
> +#define SDHCI_CDNS_SD6_PHY_DLL_MASTER				0x200C
> +#define	SDHCI_CDNS_SD6_PHY_DLL_MASTER_BYPASS_MODE		BIT(23)
> +#define	SDHCI_CDNS_SD6_PHY_DLL_MASTER_PHASE_DETECT_SEL		GENMASK(22, 20)
> +#define	SDHCI_CDNS_SD6_PHY_DLL_MASTER_DLL_LOCK_NUM		GENMASK(18, 16)
> +#define	SDHCI_CDNS_SD6_PHY_DLL_MASTER_DLL_START_POINT		GENMASK(7, 0)
> +
> +#define SDHCI_CDNS_SD6_PHY_DLL_SLAVE				0x2010
> +#define	SDHCI_CDNS_SD6_PHY_DLL_SLAVE_READ_DQS_CMD_DELAY		GENMASK(31, 24)
> +#define	SDHCI_CDNS_SD6_PHY_DLL_SLAVE_CLK_WRDQS_DELAY		GENMASK(23, 16)
> +#define	SDHCI_CDNS_SD6_PHY_DLL_SLAVE_CLK_WR_DELAY		GENMASK(15, 8)
> +#define	SDHCI_CDNS_SD6_PHY_DLL_SLAVE_READ_DQS_DELAY		GENMASK(7, 0)
> +
> +#define SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0				0x201C
> +#define SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0_DLL_LOCK_MODE		GENMASK(2, 1)
> +
> +#define SDHCI_CDNS_SD6_PHY_CTRL					0x2080
> +#define	SDHCI_CDNS_SD6_PHY_CTRL_PHONY_DQS_TIMING		GENMASK(9, 4)
> +
> +#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0				0x2088
> +#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_CLK_OVR_EN		BIT(7)
> +#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_DRV			GENMASK(6, 5)
> +#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_DRV_OVR_EN		BIT(4)
> +#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_SLEW			GENMASK(2, 1)
> +#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_SLEW_OVR_EN		BIT(0)
> +
>  /* SRS - Slot Register Set (SDHCI-compatible) */
>  #define SDHCI_CDNS_SRS_BASE		0x200
>  
> @@ -62,15 +155,269 @@
>   */
>  #define SDHCI_CDNS_MAX_TUNING_LOOP	40
>  
> +struct sdhci_cdns_phy_cfg {
> +	const char *property;
> +	u8 addr;
> +};
> +
> +enum sdhci_cdns_sd6_phy_lock_mode {
> +	SDHCI_CDNS_SD6_PHY_LOCK_MODE_FULL_CLK = 0,
> +	SDHCI_CDNS_SD6_PHY_LOCK_MODE_HALF_CLK = 2,
> +	SDHCI_CDNS_SD6_PHY_LOCK_MODE_SATURATION = 3,
> +};
> +
> +struct sdhci_cdns_sd6_phy_timings {
> +	u32 t_cmd_output_min;
> +	u32 t_cmd_output_max;
> +	u32 t_dat_output_min;
> +	u32 t_dat_output_max;
> +	u32 t_cmd_input_min;
> +	u32 t_cmd_input_max;
> +	u32 t_dat_input_min;
> +	u32 t_dat_input_max;
> +	u32 t_sdclk_min;
> +	u32 t_sdclk_max;
> +};
> +
> +struct sdhci_cdns_sd6_phy_delays {
> +	u32 phy_sdclk_delay;
> +	u32 phy_cmd_o_delay;
> +	u32 phy_dat_o_delay;
> +	u32 iocell_input_delay;
> +	u32 iocell_output_delay;
> +	u32 delay_element_org;
> +	u32 delay_element;
> +};
> +
> +struct sdhci_cdns_sd6_phy_settings {
> +	/* SDHCI_CDNS_SD6_PHY_DLL_SLAVE */
> +	u32 cp_read_dqs_cmd_delay;
> +	u32 cp_read_dqs_delay;
> +	u32 cp_clk_wr_delay;
> +	u32 cp_clk_wrdqs_delay;
> +
> +	/* SDHCI_CDNS_SD6_PHY_DLL_MASTER */
> +	u32 cp_dll_bypass_mode;
> +	u32 cp_dll_start_point;
> +
> +	/* SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0 */
> +	u32 cp_dll_locked_mode;
> +
> +	/* SDHCI_CDNS_SD6_PHY_GATE_LPBK */
> +	u32 cp_gate_cfg_always_on;
> +	u32 cp_sync_method;
> +	u32 cp_rd_del_sel;
> +	u32 cp_sw_half_cycle_shift;
> +	u32 cp_underrun_suppress;
> +
> +	/* SDHCI_CDNS_SD6_PHY_DQ_TIMING */
> +	u32 cp_io_mask_always_on;
> +	u32 cp_io_mask_end;
> +	u32 cp_io_mask_start;
> +	u32 cp_data_select_oe_end;
> +
> +	/* SDHCI_CDNS_SD6_PHY_DQS_TIMING */
> +	u32 cp_use_ext_lpbk_dqs;
> +	u32 cp_use_lpbk_dqs;
> +	u8 cp_use_phony_dqs;
> +	u8 cp_use_phony_dqs_cmd;
> +
> +	/* HRS 09 */
> +	u8 sdhc_extended_rd_mode;
> +	u8 sdhc_extended_wr_mode;
> +	u32 sdhc_rdcmd_en;
> +	u32 sdhc_rddata_en;
> +
> +	/* HRS10 */
> +	u32 sdhc_hcsdclkadj;
> +
> +	/* HRS 07 */
> +	u32 sdhc_idelay_val;
> +	u32 sdhc_rw_compensate;
> +
> +	/* SRS 11 */
> +	u32 sdhc_sdcfsh;
> +	u32 sdhc_sdcfsl;
> +
> +	/* HRS 16 */
> +	u32 sdhc_wrcmd0_dly;
> +	u32 sdhc_wrcmd0_sdclk_dly;
> +	u32 sdhc_wrcmd1_dly;
> +	u32 sdhc_wrcmd1_sdclk_dly;
> +	u32 sdhc_wrdata0_dly;
> +	u32 sdhc_wrdata0_sdclk_dly;
> +	u32 sdhc_wrdata1_dly;
> +	u32 sdhc_wrdata1_sdclk_dly;
> +
> +	u32 hs200_tune_val;
> +	u32 drive;
> +	u32 slew;
> +};
> +
> +struct sdhci_cdns_sd6_phy_intermediate_results {
> +	u32 t_sdmclk_calc;
> +	u32 dll_max_value;
> +};
> +
> +struct sdhci_cdns_sd6_phy {
> +	struct sdhci_cdns_sd6_phy_timings t;
> +	struct sdhci_cdns_sd6_phy_delays d;
> +	u32 t_sdmclk;
> +	struct sdhci_cdns_sd6_phy_settings settings;
> +	struct sdhci_cdns_sd6_phy_intermediate_results vars;
> +	bool ddr;
> +	bool tune_cmd;
> +	bool tune_dat;
> +	bool strobe_cmd;
> +	bool strobe_dat;
> +	int mode;
> +	int t_sdclk;
> +};
> +
>  struct sdhci_cdns_plat {
>  	struct mmc_config cfg;
>  	struct mmc mmc;
>  	void __iomem *hrs_addr;
> +	bool enhanced_strobe;
> +	void *priv;
>  };
>  
> -struct sdhci_cdns_phy_cfg {
> -	const char *property;
> -	u8 addr;
> +static u32 tune_val_start = SDHCI_CDNS_TUNE_START;
> +static u32 tune_val_step = SDHCI_CDNS_TUNE_STEP;
> +static u32 max_tune_iter = SDHCI_CDNS_TUNE_ITERATIONS;
> +static u32 read_dqs_cmd_delay;
> +static struct sdhci_cdns_sd6_phy sd6_phy_config;
> +
> +/* Flag for SD6 controller */
> +static bool sd6_ctrl;
> +
> +static void init_hs(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
> +{
> +	*t = (struct sdhci_cdns_sd6_phy_timings){
> +		.t_cmd_output_min = 2000, .t_cmd_output_max = t_sdclk - 6000,
> +		.t_dat_output_min = 2000, .t_dat_output_max = t_sdclk - 6000,
> +		.t_cmd_input_min = 14000, .t_cmd_input_max = t_sdclk + 2500,
> +		.t_dat_input_min = 14000, .t_dat_input_max = t_sdclk + 2500,
> +		.t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4
> +	};
> +}
> +
> +static void init_uhs_sdr12(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
> +{
> +	*t = (struct sdhci_cdns_sd6_phy_timings){
> +		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 3000,
> +		.t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 3000,
> +		.t_cmd_input_min = 14000, .t_cmd_input_max = t_sdclk + 1500,
> +		.t_dat_input_min = 14000, .t_dat_input_max = t_sdclk + 1500,
> +		.t_sdclk_min = 1000000 / 25, .t_sdclk_max = 1000000 / 0.4
> +	};
> +}
> +
> +static void init_uhs_sdr25(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
> +{
> +	*t = (struct sdhci_cdns_sd6_phy_timings){
> +		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 3000,
> +		.t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 3000,
> +		.t_cmd_input_min = 14000, .t_cmd_input_max = t_sdclk + 1500,
> +		.t_dat_input_min = 14000, .t_dat_input_max = t_sdclk + 1500,
> +		.t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4
> +	};
> +}
> +
> +static void init_uhs_sdr50(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
> +{
> +	*t = (struct sdhci_cdns_sd6_phy_timings){
> +		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 3000,
> +		.t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 3000,
> +		.t_cmd_input_min = 7500, .t_cmd_input_max = t_sdclk + 1500,
> +		.t_dat_input_min = 7500, .t_dat_input_max = t_sdclk + 1500,
> +		.t_sdclk_min = 1000000 / 100, .t_sdclk_max = 1000000 / 0.4
> +	};
> +}
> +
> +static void init_uhs_sdr104(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
> +{
> +	*t = (struct sdhci_cdns_sd6_phy_timings){
> +		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 1400,
> +		.t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 1400,
> +		.t_cmd_input_min = 1000, .t_cmd_input_max = t_sdclk + 1000,
> +		.t_dat_input_min = 1000, .t_dat_input_max = t_sdclk + 1000,
> +		.t_sdclk_min = 1000000 / 200, .t_sdclk_max = 1000000 / 100
> +	};
> +}
> +
> +static void init_uhs_ddr50(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
> +{
> +	*t = (struct sdhci_cdns_sd6_phy_timings){
> +		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 3000,
> +		.t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 3000,
> +		.t_cmd_input_min = 13700, .t_cmd_input_max = t_sdclk + 1500,
> +		.t_dat_input_min = 7000, .t_dat_input_max = t_sdclk + 1500,
> +		.t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4
> +	};
> +}
> +
> +static void init_emmc_legacy(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
> +{
> +	*t = (struct sdhci_cdns_sd6_phy_timings){
> +		.t_cmd_output_min = 3000, .t_cmd_output_max = t_sdclk - 3000,
> +		.t_dat_output_min = 3000, .t_dat_output_max = t_sdclk - 3000,
> +		.t_cmd_input_min = 11700, .t_cmd_input_max = t_sdclk + 8300,
> +		.t_dat_input_min = 11700, .t_dat_input_max = t_sdclk + 8300,
> +		.t_sdclk_min = 1000000 / 25, .t_sdclk_max = 1000000 / 0.4
> +	};
> +}
> +
> +static void init_emmc_sdr(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
> +{
> +	*t = (struct sdhci_cdns_sd6_phy_timings){
> +		.t_cmd_output_min = 3000, .t_cmd_output_max = t_sdclk - 3000,
> +		.t_dat_output_min = 3000, .t_dat_output_max = t_sdclk - 3000,
> +		.t_cmd_input_min = 13700, .t_cmd_input_max = t_sdclk + 2500,
> +		.t_dat_input_min = 13700, .t_dat_input_max = t_sdclk + 2500,
> +		.t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4
> +	};
> +}
> +
> +static void init_emmc_ddr(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
> +{
> +	*t = (struct sdhci_cdns_sd6_phy_timings){
> +		.t_cmd_output_min = 3000, .t_cmd_output_max = t_sdclk - 3000,
> +		.t_dat_output_min = 2500, .t_dat_output_max = t_sdclk - 2500,
> +		.t_cmd_input_min = 13700, .t_cmd_input_max = t_sdclk + 2500,
> +		.t_dat_input_min = 7000, .t_dat_input_max = t_sdclk + 1500,
> +		.t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4
> +	};
> +}
> +
> +static void init_emmc_hs200(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
> +{
> +	*t = (struct sdhci_cdns_sd6_phy_timings){
> +		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 1400,
> +		.t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 1400,
> +		.t_cmd_input_min = 1000, .t_cmd_input_max = t_sdclk + 1000,
> +		.t_dat_input_min = 1000, .t_dat_input_max = t_sdclk + 1000,
> +		.t_sdclk_min = 1000000 / 200, .t_sdclk_max = 1000000 / 100
> +	};
> +}
> +
> +/* HS400 and HS400ES */
> +static void init_emmc_hs400(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
> +{
> +	*t = (struct sdhci_cdns_sd6_phy_timings){
> +		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 1400,
> +		.t_dat_output_min = 400, .t_dat_output_max = t_sdclk - 400,
> +		.t_cmd_input_min = 1000, .t_cmd_input_max = t_sdclk + 1000,
> +		.t_dat_input_min = 1000, .t_dat_input_max = t_sdclk + 1000,
> +		.t_sdclk_min = 1000000 / 200, .t_sdclk_max = 1000000 / 100
> +	};
> +}
> +
> +static void (*init_timings[])(struct sdhci_cdns_sd6_phy_timings*, int) = {
> +	&init_emmc_legacy, &init_emmc_legacy, &init_hs, &init_emmc_sdr,
> +	&init_emmc_ddr, &init_uhs_sdr12, &init_uhs_sdr25, &init_uhs_sdr50,
> +	&init_uhs_ddr50, &init_uhs_sdr104, &init_emmc_hs200, &init_emmc_hs400,
> +	&init_emmc_hs400,
>  };
>  
>  static const struct sdhci_cdns_phy_cfg sdhci_cdns_sd4_phy_cfgs[] = {
> @@ -133,10 +480,405 @@ static int sdhci_cdns_sd4_phy_init(struct sdhci_cdns_plat *plat,
>  	return 0;
>  }
>  
> +static u32 sdhci_cdns_sd6_readl(struct sdhci_host *host, int reg)
> +{
> +	return readl(host->ioaddr + reg);
> +}
> +
> +static u32 sdhci_cdns_sd6_read_phy_reg(struct sdhci_cdns_plat *plat,
> +				       u32 addr)
> +{
> +	writel(addr, plat->hrs_addr + SDHCI_CDNS_HRS04);
> +	return readl(plat->hrs_addr + SDHCI_CDNS_HRS05);
> +}
> +
> +static void sdhci_cdns_sd6_writel(struct sdhci_host *host, u32 val, int reg)
> +{
> +	writel(val, host->ioaddr + reg);
> +}
> +
> +static u16 sdhci_cdns_sd6_readw(struct sdhci_host *host, int reg)
> +{
> +	u32 val, regoff;
> +
> +	regoff = reg & ~3;

How about using macro? I don't know what 0x3 is meaning.

#define XXX(x) (x & ~0x3)

> +
> +	val = readl(host->ioaddr + regoff);
> +	if ((reg & 0x3) == 0)
> +		return (val & 0xFFFF);


Use define macro instead of 0xFFFF.

> +	else
> +		return ((val >> 16) & 0xFFFF);
> +}
> +
> +static void sdhci_cdns_sd6_writew(struct sdhci_host *host, u16 val, int reg)
> +{
> +	writew(val, host->ioaddr + reg);
> +}
> +
> +static u8 sdhci_cdns_sd6_readb(struct sdhci_host *host, int reg)
> +{
> +	u32 val, regoff;
> +
> +	regoff = reg & ~3;

ditto. If using macro, it can be replaced.

> +
> +	val = readl(host->ioaddr + regoff);
> +	switch (reg & 3) {

Use 0x3 and meaningful macro. 

> +	case 0:
> +		return (val & 0xFF);
> +	case 1:
> +		return ((val >> 8) & 0xFF);
> +	case 2:
> +		return ((val >> 16) & 0xFF);
> +	case 3:
> +		return ((val >> 24) & 0xFF);
> +	}
> +	return 0;

Never come to here. reg & 0x3 should be 0,1,2 or 3.


> +}
> +
> +static void sdhci_cdns_sd6_writeb(struct sdhci_host *host, u8 val, int reg)
> +{
> +	writeb(val, host->ioaddr + reg);
> +}
> +
> +static int sdhci_cdns_sd6_get_delay_params(struct udevice *dev, struct sdhci_cdns_plat *plat)

Not need to use int type? it can be void.


> +{
> +	struct sdhci_cdns_sd6_phy *phy = plat->priv;
> +	int ret;
> +
> +	ret = dev_read_u32(dev, "cdns,read_dqs_cmd_delay",
> +			   &phy->settings.cp_read_dqs_cmd_delay);
> +	if (ret)
> +		phy->settings.cp_read_dqs_cmd_delay = DEFAULT_CMD_DELAY;
> +
> +	ret = dev_read_u32(dev, "cdns,tune_val_start", &tune_val_start);
> +	if (ret)
> +		tune_val_start = SDHCI_CDNS_TUNE_START;
> +
> +	ret = dev_read_u32(dev, "cdns,tune_val_step", &tune_val_step);
> +	if (ret)
> +		tune_val_step = SDHCI_CDNS_TUNE_STEP;
> +
> +	ret = dev_read_u32(dev, "cdns,max_tune_iter", &max_tune_iter);
> +	if (ret)
> +		max_tune_iter = SDHCI_CDNS_TUNE_ITERATIONS;
> +
> +	read_dqs_cmd_delay = phy->settings.cp_read_dqs_cmd_delay;
> +	return 0;
> +}
> +
> +#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT)
> +static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_plat *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);
> +}
> +#endif
> +
> +void sdhci_cdns_sd6_fullsw_reset(struct sdhci_cdns_plat *plat)
> +{
> +	u32 regval;
> +
> +	regval = readl(plat->hrs_addr + SDHCI_CDNS_HRS00);
> +	regval |= SDHCI_CDNS_HRS00_SWR;
> +	writel(regval, plat->hrs_addr + SDHCI_CDNS_HRS00);
> +
> +	do {
> +		regval = readl(plat->hrs_addr + SDHCI_CDNS_HRS00);
> +	} while (regval & SDHCI_CDNS_HRS00_SWR);

readl_poll_timeout()?

> +}
> +
> +void sdhci_cdns_sd6_stop_clock(struct sdhci_cdns_plat *plat)
> +{
> +	u32 reg_srs11 = 0;
> +
> +	reg_srs11 = readl(plat->hrs_addr + SDHCI_CDNS_SRS_BASE + SDHCI_CDNS_SRS11);
> +	reg_srs11 &= ~5;

Use macro instead of 5. (otherwise 0x5)

> +	writel(reg_srs11, plat->hrs_addr + SDHCI_CDNS_SRS_BASE + SDHCI_CDNS_SRS11);
> +}
> +
> +void sdhci_cdns_sd6_set_volt(struct sdhci_cdns_plat *plat)
> +{
> +	u32 controller_setting = 0;
> +
> +	controller_setting = readl(plat->hrs_addr + SDHCI_CDNS_SRS_BASE + SDHCI_CDNS_HRS10);
> +	controller_setting &= ~(7 << 9);
> +
> +	controller_setting |= (7 << 9);
> +	controller_setting |= (1 << 8);

Ditto. Use macro about (7 << 9) and (1 << 8)

> +
> +	writel(controller_setting, plat->hrs_addr + SDHCI_CDNS_SRS_BASE + SDHCI_CDNS_HRS10);
> +	controller_setting = readl(plat->hrs_addr + SDHCI_CDNS_SRS_BASE + SDHCI_CDNS_HRS10);
> +}
> +
> +static int sdhci_cdns_sd6_get_fdt_params(struct udevice *dev, struct sdhci_cdns_plat *plat)
> +{
> +	struct sdhci_cdns_sd6_phy *phy = plat->priv;
> +	const char *mode_name;
> +	int ret;
> +
> +	dev_read_u32(dev, "cdns,iocell_input_delay", &phy->d.iocell_input_delay);
> +
> +	dev_read_u32(dev, "cdns,iocell_output_delay", &phy->d.iocell_output_delay);
> +
> +	dev_read_u32(dev, "cdns,delay_element", &phy->d.delay_element);
> +
> +	ret = dev_read_u32(dev, "cdns,host_slew", &phy->settings.slew);

dev_read_u32_default()?

> +	if (ret)
> +		phy->settings.slew = 3;
> +
> +	ret = dev_read_u32(dev, "cdns,host_drive", &phy->settings.drive);
> +	if (ret)
> +		phy->settings.drive = 2;
> +
> +	mode_name = dev_read_string(dev, "cdns,mode");
> +
> +	if (mode_name != NULL) {

It cna be "if (mode_name)"

> +		if (!strcmp("emmc_sdr", mode_name))
> +			phy->mode = MMC_HS_52;
> +		else if (!strcmp("emmc_ddr", mode_name))
> +			phy->mode = MMC_DDR_52;
> +		else if (!strcmp("emmc_hs200", mode_name))
> +			phy->mode = MMC_HS_200;
> +		else if (!strcmp("emmc_hs400", mode_name))
> +			phy->mode = MMC_HS_400;
> +		else if (!strcmp("emmc_hs400_es", mode_name))
> +			phy->mode = MMC_HS_400_ES;
> +		else if (!strcmp("sd_hs", mode_name))
> +			phy->mode = SD_HS;
> +		else
> +			phy->mode = MMC_HS;
> +	} else {
> +		phy->mode = MMC_HS;
> +	}
> +
> +	sdhci_cdns_sd6_get_delay_params(dev, plat);
> +	return 0;
> +}
> +
> +static void sdhci_cdns_sd6_write_phy_reg(struct sdhci_cdns_plat *plat,
> +					 u32 addr, u32 data)
> +{
> +	writel(addr, plat->hrs_addr + SDHCI_CDNS_HRS04);
> +	writel(data, plat->hrs_addr + SDHCI_CDNS_HRS05);
> +}
> +
> +static int sdhci_cdns_sd6_dll_reset(struct sdhci_cdns_plat *plat, bool doreset)
> +{
> +	u32 reg;
> +	int ret = 0;
> +
> +	reg = readl(plat->hrs_addr + SDHCI_CDNS_HRS09);
> +	if (doreset)
> +		reg &= ~SDHCI_CDNS_HRS09_PHY_SW_RESET;
> +	else
> +		reg |= SDHCI_CDNS_HRS09_PHY_SW_RESET;
> +	writel(reg, plat->hrs_addr + SDHCI_CDNS_HRS09);
> +
> +	if (!doreset) {
> +		do {
> +			reg = readl(plat->hrs_addr + SDHCI_CDNS_HRS09);
> +		} while ((reg & SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE) == 0);
> +	}
> +
> +	return ret;
> +}
> +
> +static int sdhci_cdns_sd6_phy_init(struct udevice *dev, struct sdhci_cdns_plat *plat)
> +{
> +	int ret;
> +	u32 reg;
> +	struct sdhci_cdns_sd6_phy *phy = plat->priv;
> +
> +	if ((phy->mode == -1) || (phy->t_sdclk == -1))
> +		return 0;
> +
> +	sdhci_cdns_sd6_dll_reset(plat, true);
> +
> +	/* cp_use_phony_dqs SDHCI_CDNS_SD6_PHY_DQS_TIMING */
> +	reg = sdhci_cdns_sd6_read_phy_reg(plat, SDHCI_CDNS_SD6_PHY_DQS_TIMING);
> +	reg &= ~SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS;
> +	reg &= ~SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_LPBK_DQS;
> +	reg &= ~SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS;
> +	reg &= ~SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD;
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS,
> +			phy->settings.cp_use_ext_lpbk_dqs);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_LPBK_DQS,
> +			phy->settings.cp_use_lpbk_dqs);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS,
> +			phy->settings.cp_use_phony_dqs);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD,
> +			phy->settings.cp_use_phony_dqs_cmd);
> +
> +	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_DQS_TIMING, reg);
> +
> +	/* SDHCI_CDNS_SD6_PHY_GATE_LPBK */
> +	reg = sdhci_cdns_sd6_read_phy_reg(plat, SDHCI_CDNS_SD6_PHY_GATE_LPBK);
> +	reg &= ~SDHCI_CDNS_SD6_PHY_GATE_LPBK_SYNC_METHOD;
> +	reg &= ~SDHCI_CDNS_SD6_PHY_GATE_LPBK_SW_HALF_CYCLE_SHIFT;
> +	reg &= ~SDHCI_CDNS_SD6_PHY_GATE_LPBK_RD_DEL_SEL;
> +	reg &= ~SDHCI_CDNS_SD6_PHY_GATE_LPBK_GATE_CFG_ALWAYS_ON;
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_SYNC_METHOD,
> +			phy->settings.cp_sync_method);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_SW_HALF_CYCLE_SHIFT,
> +			phy->settings.cp_sw_half_cycle_shift);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_RD_DEL_SEL,
> +			phy->settings.cp_rd_del_sel);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_GATE_CFG_ALWAYS_ON,
> +			phy->settings.cp_gate_cfg_always_on);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_UNDERRUN_SUPPRESS,
> +			phy->settings.cp_underrun_suppress);
> +
> +	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_GATE_LPBK, reg);
> +
> +	/* SDHCI_CDNS_SD6_PHY_DLL_MASTER */
> +	reg = 0x0;
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_MASTER_BYPASS_MODE,
> +			 phy->settings.cp_dll_bypass_mode);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_MASTER_PHASE_DETECT_SEL, 2);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_MASTER_DLL_START_POINT,
> +			phy->settings.cp_dll_start_point);
> +
> +	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_DLL_MASTER, reg);
> +
> +	/* SDHCI_CDNS_SD6_PHY_DLL_SLAVE */
> +	reg = 0x0;
> +	reg = FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_SLAVE_READ_DQS_CMD_DELAY,
> +			 phy->settings.cp_read_dqs_cmd_delay);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_SLAVE_CLK_WRDQS_DELAY,
> +			phy->settings.cp_clk_wrdqs_delay);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_SLAVE_CLK_WR_DELAY,
> +			phy->settings.cp_clk_wr_delay);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_SLAVE_READ_DQS_DELAY,
> +			phy->settings.cp_read_dqs_delay);
> +
> +	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_DLL_SLAVE, reg);
> +
> +	/* SDHCI_CDNS_SD6_PHY_CTRL */
> +	reg = sdhci_cdns_sd6_read_phy_reg(plat, SDHCI_CDNS_SD6_PHY_CTRL);
> +	reg &= ~SDHCI_CDNS_SD6_PHY_CTRL_PHONY_DQS_TIMING;
> +	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_CTRL, reg);
> +
> +	reg = sdhci_cdns_sd6_read_phy_reg(plat, SDHCI_CDNS_SD6_PHY_GPIO_CTRL0);
> +	reg |= SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_CLK_OVR_EN;
> +	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_GPIO_CTRL0, reg);
> +
> +	reg = sdhci_cdns_sd6_read_phy_reg(plat, SDHCI_CDNS_SD6_PHY_GPIO_CTRL0);
> +	/* Clear the drive and slew fields */
> +	reg &= ~0x77;

Not use magicode as 0x77.

> +	/* Use the drive and slew from settings */
> +	reg |= SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_DRV_OVR_EN |
> +		SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_SLEW_OVR_EN;
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_DRV,
> +			phy->settings.drive);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_SLEW,
> +		phy->settings.slew);
> +
> +	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_GPIO_CTRL0, reg);
> +
> +	ret = sdhci_cdns_sd6_dll_reset(plat, false);
> +	if (ret)
> +		return ret;
> +
> +	reg = sdhci_cdns_sd6_read_phy_reg(plat, SDHCI_CDNS_SD6_PHY_DQ_TIMING);
> +	reg &= ~SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON;
> +	reg &= ~SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_END;
> +	reg &= ~SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_START;
> +	reg &= ~SDHCI_CDNS_SD6_PHY_DQ_TIMING_DATA_SELECT_OE_END;
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON,
> +			phy->settings.cp_io_mask_always_on);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_END,
> +			phy->settings.cp_io_mask_end);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_START,
> +			phy->settings.cp_io_mask_start);
> +	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQ_TIMING_DATA_SELECT_OE_END,
> +			phy->settings.cp_data_select_oe_end);
> +
> +	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_DQ_TIMING, reg);
> +
> +	reg = readl(plat->hrs_addr + SDHCI_CDNS_HRS09);
> +	if (phy->settings.sdhc_extended_wr_mode)
> +		reg |= SDHCI_CDNS_HRS09_EXTENDED_WR_MODE;
> +	else
> +		reg &= ~SDHCI_CDNS_HRS09_EXTENDED_WR_MODE;
> +
> +	if (phy->settings.sdhc_extended_rd_mode)
> +		reg |= SDHCI_CDNS_HRS09_EXTENDED_RD_MODE;
> +	else
> +		reg &= ~SDHCI_CDNS_HRS09_EXTENDED_RD_MODE;
> +
> +	if (phy->settings.sdhc_rddata_en)
> +		reg |= SDHCI_CDNS_HRS09_RDDATA_EN;
> +	else
> +		reg &= ~SDHCI_CDNS_HRS09_RDDATA_EN;
> +
> +	if (phy->settings.sdhc_rdcmd_en)
> +		reg |= SDHCI_CDNS_HRS09_RDCMD_EN;
> +	else
> +		reg &= ~SDHCI_CDNS_HRS09_RDCMD_EN;
> +
> +	writel(reg, plat->hrs_addr + SDHCI_CDNS_HRS09);
> +
> +	writel(0x30004, plat->hrs_addr + SDHCI_CDNS_HRS02);

 don't like the using magicode. Could you use macro instead of 0x30004?

> +
> +	reg = 0x0;
> +	reg = FIELD_PREP(SDHCI_CDNS_HRS10_HCSDCLKADJ, phy->settings.sdhc_hcsdclkadj);
> +	writel(reg, plat->hrs_addr + SDHCI_CDNS_HRS10);
> +
> +	if (phy->mode == MMC_HS_52 || phy->mode == MMC_DDR_52) {
> +		reg = 0x202;

Ditto.

> +	} else {
> +		reg = 0x0;
> +		reg = FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY,
> +				 phy->settings.sdhc_wrdata1_sdclk_dly);
> +		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY,
> +				  phy->settings.sdhc_wrdata0_sdclk_dly);
> +		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY,
> +				  phy->settings.sdhc_wrcmd1_sdclk_dly);
> +		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY,
> +				  phy->settings.sdhc_wrcmd0_sdclk_dly);
> +		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_DLY,
> +				  phy->settings.sdhc_wrdata1_dly);
> +		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_DLY,
> +				  phy->settings.sdhc_wrdata0_dly);
> +		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_DLY,
> +				  phy->settings.sdhc_wrcmd1_dly);
> +		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_DLY,
> +				  phy->settings.sdhc_wrcmd0_dly);
> +	}
> +
> +	writel(reg, plat->hrs_addr + SDHCI_CDNS_HRS16);
> +
> +	reg = 0x0;
> +	reg = FIELD_PREP(SDHCI_CDNS_HRS07_RW_COMPENSATE,
> +			 phy->settings.sdhc_rw_compensate);
> +	reg |= FIELD_PREP(SDHCI_CDNS_HRS07_IDELAY_VAL,
> +			 phy->settings.sdhc_idelay_val);
> +	writel(reg, plat->hrs_addr + SDHCI_CDNS_HRS07);
> +	return 0;
> +}
> +
> +static int sdhci_cdns_sd6_set_tune_val(struct sdhci_cdns_plat *plat,
> +				       unsigned int val)
> +{
> +	struct sdhci_cdns_sd6_phy *phy = plat->priv;
> +
> +	phy->settings.hs200_tune_val = val;
> +	phy->settings.cp_read_dqs_cmd_delay = val;
> +	phy->settings.cp_read_dqs_delay = val;
> +
> +	return sdhci_cdns_sd6_phy_init(NULL, plat);
> +}
> +
>  static void sdhci_cdns_set_control_reg(struct sdhci_host *host)
>  {
>  	struct mmc *mmc = host->mmc;
>  	struct sdhci_cdns_plat *plat = dev_get_plat(mmc->dev);
> +	struct sdhci_cdns_sd6_phy *phy = plat->priv;
>  	unsigned int clock = mmc->clock;
>  	u32 mode, tmp;
>  
> @@ -146,17 +888,43 @@ static void sdhci_cdns_set_control_reg(struct sdhci_host *host)
>  	 * U-Boot does not support timing.  Use the clock frequency instead.
>  	 */
>  	if (clock <= 26000000) {
> -		mode = SDHCI_CDNS_HRS06_MODE_SD; /* use this for Legacy */
> +		phy->mode = MMC_LEGACY; /* use this for Legacy */
>  	} else if (clock <= 52000000) {
>  		if (mmc->ddr_mode)
> -			mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR;
> +			phy->mode = MMC_DDR_52;
>  		else
> -			mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR;
> +			phy->mode = MMC_HS_52;
>  	} else {
>  		if (mmc->ddr_mode)
> -			mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400;
> +			phy->mode = MMC_HS_400;
>  		else
> -			mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200;
> +			phy->mode = MMC_HS_200;
> +	}
> +
> +	switch (phy->mode) {
> +	case MMC_LEGACY:
> +	case MMC_HS:
> +		mode = SDHCI_CDNS_HRS06_MODE_LEGACY;
> +		break;
> +	case MMC_HS_52:
> +		mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR;
> +		break;
> +	case MMC_DDR_52:
> +		mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR;
> +		break;
> +	case MMC_HS_200:
> +		mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200;
> +		break;
> +	case MMC_HS_400:
> +		mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400;
> +		break;
> +	case MMC_HS_400_ES:
> +		mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400ES;
> +		break;
> +	default:
> +		/* All other modes treated as SD */
> +		mode = SDHCI_CDNS_HRS06_MODE_SD;
> +		break;
>  	}
>  
>  	tmp = readl(plat->hrs_addr + SDHCI_CDNS_HRS06);
> @@ -165,10 +933,6 @@ static void sdhci_cdns_set_control_reg(struct sdhci_host *host)
>  	writel(tmp, plat->hrs_addr + SDHCI_CDNS_HRS06);
>  }
>  
> -static const struct sdhci_ops sdhci_cdns_ops = {
> -	.set_control_reg = sdhci_cdns_set_control_reg,
> -};
> -
>  static int sdhci_cdns_set_tune_val(struct sdhci_cdns_plat *plat,
>  				   unsigned int val)
>  {
> @@ -201,6 +965,443 @@ static int sdhci_cdns_set_tune_val(struct sdhci_cdns_plat *plat,
>  	return 0;
>  }
>  
> +static int sdhci_cdns_sd6_phy_lock_dll(struct sdhci_cdns_sd6_phy *phy)
> +{
> +	u32 delay_element = phy->d.delay_element_org;
> +	u32 delay_elements_in_sdmclk;
> +	enum sdhci_cdns_sd6_phy_lock_mode mode;
> +
> +	delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk, delay_element);
> +	if (delay_elements_in_sdmclk > 256) {
> +		delay_element *= 2;
> +		delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk,
> +							delay_element);
> +
> +		if (delay_elements_in_sdmclk > 256) {
> +			return -1;
> +		} else {

not need to use "else"


> +			mode = SDHCI_CDNS_SD6_PHY_LOCK_MODE_HALF_CLK;
> +			phy->vars.dll_max_value = 127;
> +		}
> +	} else {
> +		mode = SDHCI_CDNS_SD6_PHY_LOCK_MODE_FULL_CLK;
> +		phy->vars.dll_max_value = 255;
> +	}
> +
> +	phy->vars.t_sdmclk_calc = delay_element * delay_elements_in_sdmclk;
> +	phy->d.delay_element = delay_element;
> +	phy->settings.cp_dll_locked_mode = mode;
> +	phy->settings.cp_dll_bypass_mode = 0;
> +
> +	return 0;
> +}
> +
> +static void sdhci_cdns_sd6_phy_dll_bypass(struct sdhci_cdns_sd6_phy *phy)
> +{
> +	phy->vars.dll_max_value = 256;
> +	phy->settings.cp_dll_bypass_mode = 1;
> +	phy->settings.cp_dll_locked_mode =
> +		SDHCI_CDNS_SD6_PHY_LOCK_MODE_SATURATION;
> +}
> +
> +static void sdhci_cdns_sd6_phy_configure_dll(struct sdhci_cdns_sd6_phy *phy)
> +{
> +	if (phy->settings.sdhc_extended_wr_mode == 0) {
> +		if (sdhci_cdns_sd6_phy_lock_dll(phy) == 0)

if (... || ...)
	return;


> +			return;
> +	}
> +	sdhci_cdns_sd6_phy_dll_bypass(phy);
> +}
> +
> +static void sdhci_cdns_sd6_phy_calc_out(struct sdhci_cdns_sd6_phy *phy,
> +					bool cmd_not_dat)
> +{
> +	u32 wr0_dly = 0, wr1_dly = 0, output_min, output_max, phy_o_delay;
> +	u32 clk_wr_delay = 0, wr0_sdclk_dly = 0, wr1_sdclk_dly = 0;
> +	bool data_ddr = phy->ddr && !cmd_not_dat;
> +	int t;
> +
> +	if (cmd_not_dat) {
> +		output_min = phy->t.t_cmd_output_min;
> +		output_max = phy->t.t_cmd_output_max;
> +		phy_o_delay = phy->d.phy_cmd_o_delay;
> +	} else {
> +		output_min = phy->t.t_dat_output_min;
> +		output_max = phy->t.t_dat_output_max;
> +		phy_o_delay = phy->d.phy_dat_o_delay;
> +	}
> +
> +	clk_wr_delay = 0;
> +	if (data_ddr)
> +		wr0_sdclk_dly = wr1_sdclk_dly = 1;


CHECK: multiple assignments should be avoided
#972: FILE: drivers/mmc/sdhci-cadence.c:1036:
+		wr0_sdclk_dly = wr1_sdclk_dly = 1;

> +
> +	t = phy_o_delay - phy->d.phy_sdclk_delay - output_min;
> +	if (t < 0 && phy->settings.sdhc_extended_wr_mode == 1) {
> +		u32 n_half_cycle = DIV_ROUND_UP(-t * 2, phy->t_sdmclk);
> +
> +		wr0_dly = (n_half_cycle + 1) / 2;
> +		if (data_ddr)
> +			wr1_dly = (n_half_cycle + 1) / 2;
> +		else
> +			wr1_dly = (n_half_cycle + 1) % 2 + wr0_dly - 1;
> +	}
> +
> +	if (phy->settings.sdhc_extended_wr_mode == 0) {
> +		u32 out_hold, out_setup, out_hold_margin;
> +		u32 n;
> +
> +		if (!data_ddr)
> +			wr0_dly = 1;
> +
> +		out_setup = output_max;
> +		out_hold = output_min;
> +		out_hold_margin = DIV_ROUND_UP(out_setup - out_hold, 4);
> +		out_hold += out_hold_margin;
> +
> +		if (phy->settings.cp_dll_bypass_mode == 0)
> +			n = DIV_ROUND_UP(256 * out_hold, phy->vars.t_sdmclk_calc);
> +		else
> +			n = DIV_ROUND_UP(out_hold, phy->d.delay_element) - 1;
> +
> +		if (n <= phy->vars.dll_max_value) {
> +			clk_wr_delay = n;
> +		} else {
> +			clk_wr_delay = 255;
> +			/* no dll setting*/

Remove comment. Is it necessary?

> +		}
> +	} else {
> +		/*  sdhc_extended_wr_mode = 1 - PHY IO cell work in SDR mode */
> +		clk_wr_delay = 0;
> +	}
> +
> +	if (cmd_not_dat) {
> +		phy->settings.sdhc_wrcmd0_dly = wr0_dly;
> +		phy->settings.sdhc_wrcmd1_dly = wr1_dly;
> +		phy->settings.cp_clk_wrdqs_delay = clk_wr_delay;
> +		phy->settings.sdhc_wrcmd0_sdclk_dly = wr0_sdclk_dly;
> +		phy->settings.sdhc_wrcmd1_sdclk_dly = wr1_sdclk_dly;
> +	} else {
> +		phy->settings.sdhc_wrdata0_dly = wr0_dly;
> +		phy->settings.sdhc_wrdata1_dly = wr1_dly;
> +		phy->settings.cp_clk_wr_delay = clk_wr_delay;
> +		phy->settings.sdhc_wrdata0_sdclk_dly = wr0_sdclk_dly;
> +		phy->settings.sdhc_wrdata1_sdclk_dly = wr1_sdclk_dly;
> +	}
> +}
> +
> +static void sdhci_cdns_sd6_phy_calc_cmd_out(struct sdhci_cdns_sd6_phy *phy)
> +{
> +	sdhci_cdns_sd6_phy_calc_out(phy, true);
> +}
> +
> +static void sdhci_cdns_sd6_phy_calc_cmd_in(struct sdhci_cdns_sd6_phy *phy)
> +{
> +	phy->settings.cp_io_mask_end =
> +		((phy->d.iocell_output_delay + phy->d.iocell_input_delay) * 2)
> +		/ phy->t_sdmclk;
> +
> +	if (phy->settings.cp_io_mask_end >= 8)
> +		phy->settings.cp_io_mask_end = 7;
> +
> +	if (phy->strobe_cmd && phy->settings.cp_io_mask_end > 0)
> +		phy->settings.cp_io_mask_end--;
> +
> +	if (phy->strobe_cmd) {
> +		phy->settings.cp_use_phony_dqs_cmd = 0;
> +		phy->settings.cp_read_dqs_cmd_delay = 64;
> +	} else {
> +		phy->settings.cp_use_phony_dqs_cmd = 1;
> +		phy->settings.cp_read_dqs_cmd_delay = 0;
> +	}
> +
> +	if ((phy->mode == MMC_HS_400 && !phy->strobe_cmd) ||
> +	    phy->mode == MMC_HS_200)
> +		phy->settings.cp_read_dqs_cmd_delay =
> +			phy->settings.hs200_tune_val;
> +}
> +
> +static void sdhci_cdns_sd6_phy_calc_dat_in(struct sdhci_cdns_sd6_phy *phy)
> +{
> +	u32 hcsdclkadj = 0;


Is there any meaning about hcsdclkadj?

> +
> +	if (phy->strobe_dat) {
> +		phy->settings.cp_use_phony_dqs = 0;
> +		phy->settings.cp_read_dqs_delay = 64;
> +	} else {
> +		phy->settings.cp_use_phony_dqs = 1;
> +		phy->settings.cp_read_dqs_delay = 0;
> +	}
> +
> +	if (phy->mode == MMC_HS_200)
> +		phy->settings.cp_read_dqs_delay =
> +			phy->settings.hs200_tune_val;
> +
> +	if (phy->strobe_dat) {
> +		/* dqs loopback input via IO cell */
> +		hcsdclkadj += phy->d.iocell_input_delay;
> +		/* dfi_dqs_in: mem_dqs -> clean_dqs_mod; delay of hic_dll_dqs_nand2 */
> +		hcsdclkadj += phy->d.delay_element / 2;
> +		/* delay line */
> +		hcsdclkadj += phy->t_sdclk / 2;
> +		/* PHY FIFO write pointer */
> +		hcsdclkadj += phy->t_sdclk / 2 + phy->d.delay_element;
> +		/* 1st synchronizer */
> +		hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk)
> +			* phy->t_sdmclk - hcsdclkadj;
> +		/*
> +		 * 2nd synchronizer + PHY FIFO read pointer + PHY rddata
> +		 * + PHY rddata registered, + FIFO 1st ciu_en
> +		 */
> +		hcsdclkadj += 5 * phy->t_sdmclk;
> +		/* FIFO 2st ciu_en */
> +		hcsdclkadj += phy->t_sdclk;
> +
> +		hcsdclkadj /= phy->t_sdclk;
> +	} else {
> +		u32 n;
> +
> +		/* rebar PHY delay */
> +		hcsdclkadj += 2 * phy->t_sdmclk;
> +		/* rebar output via IO cell */
> +		hcsdclkadj += phy->d.iocell_output_delay;
> +		/* dqs loopback input via IO cell */
> +		hcsdclkadj += phy->d.iocell_input_delay;
> +		/* dfi_dqs_in: mem_dqs -> clean_dqs_mod delay of hic_dll_dqs_nand2 */
> +		hcsdclkadj += phy->d.delay_element / 2;
> +		/* dll: one delay element between SIGI_0 and SIGO_0 */
> +		hcsdclkadj += phy->d.delay_element;
> +		/* dfi_dqs_in: mem_dqs_delayed -> clk_dqs delay of hic_dll_dqs_nand2 */
> +		hcsdclkadj += phy->d.delay_element / 2;
> +		/* deskew DLL: clk_dqs -> clk_dqN: one delay element */
> +		hcsdclkadj += phy->d.delay_element;
> +
> +		if (phy->t_sdclk == phy->t_sdmclk)
> +			n = (hcsdclkadj - 2 * phy->t_sdmclk) / phy->t_sdclk;
> +		else
> +			n = hcsdclkadj / phy->t_sdclk;
> +
> +		/* phase shift within one t_sdclk clock cycle caused by rebar - lbk dqs delay */
> +		hcsdclkadj = hcsdclkadj % phy->t_sdclk;
> +		/* PHY FIFO write pointer */
> +		hcsdclkadj += phy->t_sdclk / 2;
> +		/* 1st synchronizer */
> +		hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk)
> +			* phy->t_sdmclk - hcsdclkadj;
> +		/*
> +		 * 2nd synchronizer + PHY FIFO read pointer + PHY rddata
> +		 * + PHY rddata registered
> +		 */
> +		hcsdclkadj += 4 * phy->t_sdmclk;
> +
> +		if ((phy->t_sdclk / phy->t_sdmclk) > 1) {
> +			u32 tmp1, tmp2;
> +
> +			tmp1 = hcsdclkadj;
> +			tmp2 = (hcsdclkadj / phy->t_sdclk) * phy->t_sdclk
> +				+ phy->t_sdclk - phy->t_sdmclk;
> +			if (tmp1 == tmp2)
> +				tmp2 += phy->t_sdclk;
> +
> +			/* FIFO aligns to clock cycle before ciu_en */
> +			hcsdclkadj += tmp2 - tmp1;
> +		}
> +
> +		/* FIFO 1st ciu_en */
> +		hcsdclkadj += phy->t_sdmclk;
> +		/* FIFO 2nd ciu_en */
> +		hcsdclkadj += phy->t_sdclk;
> +
> +		hcsdclkadj /= phy->t_sdclk;
> +
> +		hcsdclkadj += n;
> +
> +		if ((phy->t_sdclk / phy->t_sdmclk) >= 2) {
> +			if (phy->mode == UHS_DDR50 || phy->mode == MMC_DDR_52)
> +				hcsdclkadj -= 2;
> +			else
> +				hcsdclkadj -= 1;
> +		} else if ((phy->t_sdclk / phy->t_sdmclk) == 1) {
> +			hcsdclkadj += 2;
> +		}
> +
> +		if (phy->tune_dat)
> +			hcsdclkadj -= 1;
> +	}
> +
> +	if (hcsdclkadj > 15)
> +		hcsdclkadj = 15;
> +
> +	phy->settings.sdhc_hcsdclkadj = hcsdclkadj;
> +}
> +
> +static void sdhci_cdns_sd6_phy_calc_dat_out(struct sdhci_cdns_sd6_phy *phy)
> +{
> +	sdhci_cdns_sd6_phy_calc_out(phy, false);
> +}
> +
> +static void sdhci_cdns_sd6_phy_calc_io(struct sdhci_cdns_sd6_phy *phy)
> +{
> +	u32 rw_compensate;
> +
> +	rw_compensate = (phy->d.iocell_input_delay + phy->d.iocell_output_delay)
> +		/ phy->t_sdmclk + phy->settings.sdhc_wrdata0_dly + 5 + 3;
> +
> +	phy->settings.sdhc_idelay_val = (2 * phy->d.iocell_input_delay)
> +		/ phy->t_sdmclk;
> +
> +	phy->settings.cp_io_mask_start = 0;
> +	if (phy->t_sdclk == phy->t_sdmclk && rw_compensate > 10)
> +		phy->settings.cp_io_mask_start = 2 * (rw_compensate - 10);
> +
> +	if (phy->mode == UHS_SDR104)
> +		phy->settings.cp_io_mask_start++;
> +
> +	if (phy->t_sdclk == phy->t_sdmclk && phy->mode == UHS_SDR50)
> +		phy->settings.cp_io_mask_start++;
> +
> +	phy->settings.sdhc_rw_compensate = rw_compensate;
> +}
> +
> +static void sdhci_cdns_sd6_phy_calc_settings(struct sdhci_cdns_sd6_phy *phy)
> +{
> +	sdhci_cdns_sd6_phy_calc_cmd_out(phy);
> +	sdhci_cdns_sd6_phy_calc_cmd_in(phy);
> +	sdhci_cdns_sd6_phy_calc_dat_out(phy);
> +	sdhci_cdns_sd6_phy_calc_dat_in(phy);
> +	sdhci_cdns_sd6_phy_calc_io(phy);
> +}
> +
> +static int sdhci_cdns_sd6_phy_update_timings(struct sdhci_cdns_plat *plat)
> +{
> +	struct sdhci_cdns_sd6_phy *phy = plat->priv;
> +	int t_sdmclk = phy->t_sdmclk;
> +
> +	/* initialize input */
> +	init_timings[phy->mode](&phy->t, phy->t_sdclk);
> +
> +	/* Reset the following setting before the switch statement to make sure
> +	 * they are correct if we change speeds like after a mmc rescan
> +	 */
> +	phy->tune_cmd = false;
> +	phy->ddr = false;
> +	phy->strobe_dat = false;
> +	phy->tune_dat = false;
> +
> +	switch (phy->mode) {
> +	case UHS_SDR104:
> +		phy->tune_cmd = true;
> +		phy->tune_dat = true;
> +		break;
> +	case UHS_DDR50:
> +		phy->ddr = true;
> +		break;
> +	case MMC_DDR_52:
> +		phy->ddr = true;
> +		break;
> +	case MMC_HS_200:
> +		phy->tune_dat = true;
> +		phy->tune_cmd = true;
> +		break;
> +	case MMC_HS_400:
> +	case MMC_HS_400_ES:
> +		phy->tune_cmd = true;
> +		phy->ddr = true;
> +		phy->strobe_dat = true;
> +		break;
> +	}
> +
> +	if (plat->enhanced_strobe)
> +		phy->strobe_cmd = true;
> +
> +	phy->d.phy_sdclk_delay = 2 * t_sdmclk;
> +	phy->d.phy_cmd_o_delay = 2 * t_sdmclk + t_sdmclk / 2;
> +	phy->d.phy_dat_o_delay = 2 * t_sdmclk + t_sdmclk / 2;
> +
> +	if (phy->t_sdclk == phy->t_sdmclk) {
> +		phy->settings.sdhc_extended_wr_mode = 0;
> +		phy->settings.sdhc_extended_rd_mode = 0;
> +	} else {
> +		phy->settings.sdhc_extended_wr_mode = 1;
> +		phy->settings.sdhc_extended_rd_mode = 1;
> +	}
> +
> +	phy->settings.cp_gate_cfg_always_on = 1;
> +
> +	sdhci_cdns_sd6_phy_configure_dll(phy);
> +
> +	sdhci_cdns_sd6_phy_calc_settings(phy);
> +
> +	return 0;
> +}
> +
> +static void sdhci_cdns_sd6_set_clock(struct sdhci_host *host, unsigned int div)
> +{
> +	struct udevice *dev = host->mmc->dev;
> +	struct sdhci_cdns_plat *plat = dev_get_plat(dev);
> +	struct sdhci_cdns_sd6_phy *phy = plat->priv;
> +	unsigned int clock;
> +
> +	if (!div)
> +		div = 1;
> +	else
> +		div <<= 1;
> +
> +	clock = DIV_ROUND_DOWN_ULL(SDHCI_CDNS_SD6_MAXCLK, div);
> +
> +	sdhci_cdns_set_control_reg(host);
> +
> +	phy->t_sdclk = DIV_ROUND_DOWN_ULL(1e12, clock);
> +
> +	if (sdhci_cdns_sd6_phy_update_timings(plat))
> +		debug("%s: update timings failed\n", __func__);
> +	else
> +		host->clock = clock;
> +
> +	if (sdhci_cdns_sd6_phy_init(dev, plat))
> +		debug("%s: phy init failed\n", __func__);
> +}
> +
> +static int sdhci_cdns_sd6_plat_init(struct udevice *dev, struct sdhci_cdns_plat *plat)
> +{
> +	struct sdhci_cdns_sd6_phy *phy = plat->priv;
> +
> +	memset(phy, 0, sizeof(struct sdhci_cdns_sd6_phy));
> +
> +	sdhci_cdns_sd6_fullsw_reset(plat);
> +	sdhci_cdns_sd6_set_volt(plat);
> +
> +	sdhci_cdns_sd6_get_fdt_params(dev, plat);
> +
> +	phy->t_sdmclk = 5000;
> +
> +	phy->d.delay_element_org = phy->d.delay_element;
> +	phy->settings.cp_sync_method = 1;
> +	phy->settings.cp_rd_del_sel = 52;
> +	phy->settings.cp_use_ext_lpbk_dqs = 1;
> +	phy->settings.cp_use_lpbk_dqs = 1;
> +	phy->settings.cp_data_select_oe_end = 1;
> +	phy->settings.cp_dll_start_point = 4;
> +
> +	phy->settings.cp_use_phony_dqs = 1;
> +	phy->settings.cp_use_phony_dqs_cmd = 1;
> +	phy->settings.cp_dll_bypass_mode = 1;
> +
> +	phy->settings.cp_read_dqs_cmd_delay = 0;
> +	phy->settings.cp_clk_wrdqs_delay = 0;
> +	phy->settings.cp_clk_wr_delay = 0;
> +	phy->settings.cp_read_dqs_delay = 0;
> +	phy->settings.cp_io_mask_end = 5;
> +	phy->settings.cp_io_mask_start = 0;
> +	phy->settings.sdhc_extended_rd_mode = 1;
> +	phy->settings.sdhc_extended_wr_mode = 1;
> +	phy->settings.sdhc_hcsdclkadj = 6;
> +	phy->settings.sdhc_rw_compensate = 10;
> +	phy->settings.sdhc_idelay_val = 0;
> +
> +	sdhci_cdns_sd6_phy_update_timings(plat);
> +	return 0;
> +}
> +
>  static int __maybe_unused sdhci_cdns_execute_tuning(struct udevice *dev,
>  						    unsigned int opcode)
>  {
> @@ -243,8 +1444,64 @@ static int __maybe_unused sdhci_cdns_execute_tuning(struct udevice *dev,
>  	return sdhci_cdns_set_tune_val(plat, end_of_streak - max_streak / 2);
>  }
>  
> +#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT)
> +static int sdhci_cdns_hs400_enhanced_strobe(struct udevice *dev)
> +{
> +	struct sdhci_cdns_plat *plat = dev_get_plat(dev);
> +
> +	plat->enhanced_strobe = 1;
> +	sdhci_cdns_set_emmc_mode(plat, SDHCI_CDNS_HRS06_MODE_MMC_HS400ES);
> +
> +	return 0;
> +}
> +#endif
> +
> +static int __maybe_unused sdhci_cdns_sd6_execute_tuning(struct mmc *mmc, unsigned char opcode)
> +{
> +	struct udevice *dev = mmc->dev;
> +	struct sdhci_cdns_plat *plat = dev_get_plat(dev);
> +	int cur_streak = 0;
> +	int max_streak = 0;
> +	int end_of_streak = 0;
> +	int cnt = 0, midpoint, iter = 0;
> +
> +	for (cnt = tune_val_start; iter < max_tune_iter; iter++, cnt += tune_val_step) {
> +		if (sdhci_cdns_sd6_set_tune_val(plat, cnt) ||
> +		    mmc_send_tuning(mmc, opcode, NULL)) { /* bad */
> +			cur_streak = 0;
> +		} else { /* good */
> +			cur_streak++;
> +			if (cur_streak > max_streak) {
> +				max_streak = cur_streak;
> +				end_of_streak = cnt;
> +			}
> +		}
> +	}
> +
> +	if (!max_streak)
> +		return -EIO;
> +
> +	midpoint = end_of_streak - (((max_streak - 1) * tune_val_step) / 2);
> +
> +	return sdhci_cdns_sd6_set_tune_val(plat, midpoint);
> +}
> +
>  static struct dm_mmc_ops sdhci_cdns_mmc_ops;
>  
> +static const struct sdhci_ops sdhci_cdns_ops = {
> +	.set_control_reg = sdhci_cdns_set_control_reg,
> +	.write_l = sdhci_cdns_sd6_writel,
> +	.read_l = sdhci_cdns_sd6_readl,
> +	.write_w = sdhci_cdns_sd6_writew,
> +	.read_w = sdhci_cdns_sd6_readw,
> +	.write_b = sdhci_cdns_sd6_writeb,
> +	.read_b = sdhci_cdns_sd6_readb,
> +	.set_clock = sdhci_cdns_sd6_set_clock,
> +#ifdef MMC_SUPPORTS_TUNING
> +	.platform_execute_tuning = sdhci_cdns_sd6_execute_tuning,
> +#endif
> +};
> +
>  static int sdhci_cdns_bind(struct udevice *dev)
>  {
>  	struct sdhci_cdns_plat *plat = dev_get_plat(dev);
> @@ -255,11 +1512,18 @@ static int sdhci_cdns_bind(struct udevice *dev)
>  static int sdhci_cdns_probe(struct udevice *dev)
>  {
>  	DECLARE_GLOBAL_DATA_PTR;
> +
> +	ofnode node = dev_ofnode(dev);
> +
> +	if (ofnode_device_is_compatible(node, "cdns,sd6hc"))
> +		sd6_ctrl = 1;
> +
>  	struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
>  	struct sdhci_cdns_plat *plat = dev_get_plat(dev);
>  	struct sdhci_host *host = dev_get_priv(dev);
>  	fdt_addr_t base;
>  	int ret;
> +	struct sdhci_cdns_sd6_phy *phy;
>  
>  	base = dev_read_addr(dev);
>  	if (base == FDT_ADDR_T_NONE)
> @@ -269,25 +1533,43 @@ static int sdhci_cdns_probe(struct udevice *dev)
>  	if (!plat->hrs_addr)
>  		return -ENOMEM;
>  
> +	plat->priv = &sd6_phy_config;
> +
>  	host->name = dev->name;
>  	host->ioaddr = plat->hrs_addr + SDHCI_CDNS_SRS_BASE;
>  	host->ops = &sdhci_cdns_ops;
>  	host->quirks |= SDHCI_QUIRK_WAIT_SEND_CMD;
>  	sdhci_cdns_mmc_ops = sdhci_ops;
>  #ifdef MMC_SUPPORTS_TUNING
> -	sdhci_cdns_mmc_ops.execute_tuning = sdhci_cdns_execute_tuning;
> +	if (!sd6_ctrl)
> +		sdhci_cdns_mmc_ops.execute_tuning = sdhci_cdns_execute_tuning;
>  #endif
>  
>  	ret = mmc_of_parse(dev, &plat->cfg);
>  	if (ret)
>  		return ret;
>  
> -	ret = sdhci_cdns_sd4_phy_init(plat, gd->fdt_blob, dev_of_offset(dev));
> -	if (ret)
> -		return ret;
> +#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT)
> +	sdhci_cdns_mmc_ops.set_enhanced_strobe = sdhci_cdns_hs400_enhanced_strobe;
> +#endif
> +
> +	phy = plat->priv;
> +
> +	if (sd6_ctrl) {
> +		sdhci_cdns_sd6_plat_init(dev, plat);
> +
> +		ret = sdhci_cdns_sd6_phy_init(dev, plat);
> +		if (ret)
> +			return ret;
> +	} else {
> +		ret = sdhci_cdns_sd4_phy_init(plat, gd->fdt_blob, dev_of_offset(dev));
> +		if (ret)
> +			return ret;
> +	}
>  
>  	host->mmc = &plat->mmc;
>  	host->mmc->dev = dev;
> +
>  	ret = sdhci_setup_cfg(&plat->cfg, host, 0, 0);
>  	if (ret)
>  		return ret;
> @@ -301,6 +1583,7 @@ static int sdhci_cdns_probe(struct udevice *dev)
>  static const struct udevice_id sdhci_cdns_match[] = {
>  	{ .compatible = "socionext,uniphier-sd4hc" },
>  	{ .compatible = "cdns,sd4hc" },
> +	{ .compatible = "cdns,sd6hc" },
>  	{ /* sentinel */ }
>  };
>
diff mbox series

Patch

diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig
index de01b9687bad28f3b493208c145f5c5dd2f59f78..6b724f1d1ed050b49e31a6cbe5f898b4b636c330 100644
--- a/drivers/mmc/Kconfig
+++ b/drivers/mmc/Kconfig
@@ -566,6 +566,7 @@  config MMC_SDHCI_CADENCE
 	depends on BLK && DM_MMC
 	depends on MMC_SDHCI
 	depends on OF_CONTROL
+	select MMC_SDHCI_IO_ACCESSORS
 	help
 	  This selects the Cadence SD/SDIO/eMMC driver.
 
diff --git a/drivers/mmc/sdhci-cadence.c b/drivers/mmc/sdhci-cadence.c
index 0bb258da63e442232310d9433b7b6882992bd45d..1d8e8e177eeeb6464ba9ade5fd63c1cca4554bb6 100644
--- a/drivers/mmc/sdhci-cadence.c
+++ b/drivers/mmc/sdhci-cadence.c
@@ -6,6 +6,7 @@ 
 
 #include <common.h>
 #include <dm.h>
+#include <div64.h>
 #include <asm/global_data.h>
 #include <dm/device_compat.h>
 #include <linux/bitfield.h>
@@ -18,8 +19,19 @@ 
 #include <mmc.h>
 #include <sdhci.h>
 
+#define SDHCI_CDNS_SD6_MAXCLK		200000000
+
+#define DEFAULT_CMD_DELAY		16
+#define SDHCI_CDNS_TUNE_START		16
+#define SDHCI_CDNS_TUNE_STEP		6
+#define SDHCI_CDNS_TUNE_ITERATIONS	40
+
+#define SDHCI_CDNS_HRS00			0x00
+#define SDHCI_CDNS_HRS00_SWR			BIT(0)
+
+#define SDHCI_CDNS_HRS02			0x08		/* PHY access port */
+#define SDHCI_CDNS_HRS04			0x10		/* PHY access port */
 /* SD 4.0 Controller HRS - Host Register Set (specific to Cadence) */
-#define SDHCI_CDNS_HRS04		0x10		/* PHY access port */
 #define SDHCI_CDNS_SD4_HRS04_ACK		BIT(26)
 #define SDHCI_CDNS_SD4_HRS04_RD			BIT(25)
 #define SDHCI_CDNS_SD4_HRS04_WR			BIT(24)
@@ -32,12 +44,93 @@ 
 #define   SDHCI_CDNS_HRS06_TUNE			GENMASK(13, 8)
 #define   SDHCI_CDNS_HRS06_MODE			GENMASK(2, 0)
 #define   SDHCI_CDNS_HRS06_MODE_SD		0x0
+#define SDHCI_CDNS_HRS06_MODE_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
 
+/* SD 6.0 Controller HRS - Host Register Set (Specific to Cadence) */
+#define SDHCI_CDNS_SD6_HRS04_ADDR		GENMASK(15, 0)
+
+#define SDHCI_CDNS_HRS05			0x14
+
+#define SDHCI_CDNS_HRS07			0x1C
+#define	SDHCI_CDNS_HRS07_RW_COMPENSATE		GENMASK(20, 16)
+#define	SDHCI_CDNS_HRS07_IDELAY_VAL		GENMASK(4, 0)
+
+#define SDHCI_CDNS_HRS09			0x24
+#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
+#define	SDHCI_CDNS_HRS10_HCSDCLKADJ		GENMASK(19, 16)
+
+#define SDHCI_CDNS_SRS11			0x2c
+#define SDHCI_CDNS_SRS11_SW_RESET_ALL		BIT(24)
+#define SDHCI_CDNS_SRS11_SW_RESET_CMD		BIT(25)
+#define SDHCI_CDNS_SRS11_SW_RESET_DAT		BIT(26)
+
+#define SDHCI_CDNS_HRS16			0x40
+#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)
+
+/* PHY registers for SD6 controller */
+#define SDHCI_CDNS_SD6_PHY_DQ_TIMING				0x2000
+#define	SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON		BIT(31)
+#define	SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_END		GENMASK(29, 27)
+#define	SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_START		GENMASK(26, 24)
+#define	SDHCI_CDNS_SD6_PHY_DQ_TIMING_DATA_SELECT_OE_END		GENMASK(2, 0)
+
+#define SDHCI_CDNS_SD6_PHY_DQS_TIMING				0x2004
+#define	SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS		BIT(22)
+#define	SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_LPBK_DQS		BIT(21)
+#define	SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS		BIT(20)
+#define	SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD		BIT(19)
+
+#define SDHCI_CDNS_SD6_PHY_GATE_LPBK				0x2008
+#define	SDHCI_CDNS_SD6_PHY_GATE_LPBK_SYNC_METHOD		BIT(31)
+#define	SDHCI_CDNS_SD6_PHY_GATE_LPBK_SW_HALF_CYCLE_SHIFT	BIT(28)
+#define	SDHCI_CDNS_SD6_PHY_GATE_LPBK_RD_DEL_SEL			GENMASK(24, 19)
+#define SDHCI_CDNS_SD6_PHY_GATE_LPBK_UNDERRUN_SUPPRESS		BIT(18)
+#define	SDHCI_CDNS_SD6_PHY_GATE_LPBK_GATE_CFG_ALWAYS_ON		BIT(6)
+
+#define SDHCI_CDNS_SD6_PHY_DLL_MASTER				0x200C
+#define	SDHCI_CDNS_SD6_PHY_DLL_MASTER_BYPASS_MODE		BIT(23)
+#define	SDHCI_CDNS_SD6_PHY_DLL_MASTER_PHASE_DETECT_SEL		GENMASK(22, 20)
+#define	SDHCI_CDNS_SD6_PHY_DLL_MASTER_DLL_LOCK_NUM		GENMASK(18, 16)
+#define	SDHCI_CDNS_SD6_PHY_DLL_MASTER_DLL_START_POINT		GENMASK(7, 0)
+
+#define SDHCI_CDNS_SD6_PHY_DLL_SLAVE				0x2010
+#define	SDHCI_CDNS_SD6_PHY_DLL_SLAVE_READ_DQS_CMD_DELAY		GENMASK(31, 24)
+#define	SDHCI_CDNS_SD6_PHY_DLL_SLAVE_CLK_WRDQS_DELAY		GENMASK(23, 16)
+#define	SDHCI_CDNS_SD6_PHY_DLL_SLAVE_CLK_WR_DELAY		GENMASK(15, 8)
+#define	SDHCI_CDNS_SD6_PHY_DLL_SLAVE_READ_DQS_DELAY		GENMASK(7, 0)
+
+#define SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0				0x201C
+#define SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0_DLL_LOCK_MODE		GENMASK(2, 1)
+
+#define SDHCI_CDNS_SD6_PHY_CTRL					0x2080
+#define	SDHCI_CDNS_SD6_PHY_CTRL_PHONY_DQS_TIMING		GENMASK(9, 4)
+
+#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0				0x2088
+#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_CLK_OVR_EN		BIT(7)
+#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_DRV			GENMASK(6, 5)
+#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_DRV_OVR_EN		BIT(4)
+#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_SLEW			GENMASK(2, 1)
+#define SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_SLEW_OVR_EN		BIT(0)
+
 /* SRS - Slot Register Set (SDHCI-compatible) */
 #define SDHCI_CDNS_SRS_BASE		0x200
 
@@ -62,15 +155,269 @@ 
  */
 #define SDHCI_CDNS_MAX_TUNING_LOOP	40
 
+struct sdhci_cdns_phy_cfg {
+	const char *property;
+	u8 addr;
+};
+
+enum sdhci_cdns_sd6_phy_lock_mode {
+	SDHCI_CDNS_SD6_PHY_LOCK_MODE_FULL_CLK = 0,
+	SDHCI_CDNS_SD6_PHY_LOCK_MODE_HALF_CLK = 2,
+	SDHCI_CDNS_SD6_PHY_LOCK_MODE_SATURATION = 3,
+};
+
+struct sdhci_cdns_sd6_phy_timings {
+	u32 t_cmd_output_min;
+	u32 t_cmd_output_max;
+	u32 t_dat_output_min;
+	u32 t_dat_output_max;
+	u32 t_cmd_input_min;
+	u32 t_cmd_input_max;
+	u32 t_dat_input_min;
+	u32 t_dat_input_max;
+	u32 t_sdclk_min;
+	u32 t_sdclk_max;
+};
+
+struct sdhci_cdns_sd6_phy_delays {
+	u32 phy_sdclk_delay;
+	u32 phy_cmd_o_delay;
+	u32 phy_dat_o_delay;
+	u32 iocell_input_delay;
+	u32 iocell_output_delay;
+	u32 delay_element_org;
+	u32 delay_element;
+};
+
+struct sdhci_cdns_sd6_phy_settings {
+	/* SDHCI_CDNS_SD6_PHY_DLL_SLAVE */
+	u32 cp_read_dqs_cmd_delay;
+	u32 cp_read_dqs_delay;
+	u32 cp_clk_wr_delay;
+	u32 cp_clk_wrdqs_delay;
+
+	/* SDHCI_CDNS_SD6_PHY_DLL_MASTER */
+	u32 cp_dll_bypass_mode;
+	u32 cp_dll_start_point;
+
+	/* SDHCI_CDNS_SD6_PHY_DLL_OBS_REG0 */
+	u32 cp_dll_locked_mode;
+
+	/* SDHCI_CDNS_SD6_PHY_GATE_LPBK */
+	u32 cp_gate_cfg_always_on;
+	u32 cp_sync_method;
+	u32 cp_rd_del_sel;
+	u32 cp_sw_half_cycle_shift;
+	u32 cp_underrun_suppress;
+
+	/* SDHCI_CDNS_SD6_PHY_DQ_TIMING */
+	u32 cp_io_mask_always_on;
+	u32 cp_io_mask_end;
+	u32 cp_io_mask_start;
+	u32 cp_data_select_oe_end;
+
+	/* SDHCI_CDNS_SD6_PHY_DQS_TIMING */
+	u32 cp_use_ext_lpbk_dqs;
+	u32 cp_use_lpbk_dqs;
+	u8 cp_use_phony_dqs;
+	u8 cp_use_phony_dqs_cmd;
+
+	/* HRS 09 */
+	u8 sdhc_extended_rd_mode;
+	u8 sdhc_extended_wr_mode;
+	u32 sdhc_rdcmd_en;
+	u32 sdhc_rddata_en;
+
+	/* HRS10 */
+	u32 sdhc_hcsdclkadj;
+
+	/* HRS 07 */
+	u32 sdhc_idelay_val;
+	u32 sdhc_rw_compensate;
+
+	/* SRS 11 */
+	u32 sdhc_sdcfsh;
+	u32 sdhc_sdcfsl;
+
+	/* HRS 16 */
+	u32 sdhc_wrcmd0_dly;
+	u32 sdhc_wrcmd0_sdclk_dly;
+	u32 sdhc_wrcmd1_dly;
+	u32 sdhc_wrcmd1_sdclk_dly;
+	u32 sdhc_wrdata0_dly;
+	u32 sdhc_wrdata0_sdclk_dly;
+	u32 sdhc_wrdata1_dly;
+	u32 sdhc_wrdata1_sdclk_dly;
+
+	u32 hs200_tune_val;
+	u32 drive;
+	u32 slew;
+};
+
+struct sdhci_cdns_sd6_phy_intermediate_results {
+	u32 t_sdmclk_calc;
+	u32 dll_max_value;
+};
+
+struct sdhci_cdns_sd6_phy {
+	struct sdhci_cdns_sd6_phy_timings t;
+	struct sdhci_cdns_sd6_phy_delays d;
+	u32 t_sdmclk;
+	struct sdhci_cdns_sd6_phy_settings settings;
+	struct sdhci_cdns_sd6_phy_intermediate_results vars;
+	bool ddr;
+	bool tune_cmd;
+	bool tune_dat;
+	bool strobe_cmd;
+	bool strobe_dat;
+	int mode;
+	int t_sdclk;
+};
+
 struct sdhci_cdns_plat {
 	struct mmc_config cfg;
 	struct mmc mmc;
 	void __iomem *hrs_addr;
+	bool enhanced_strobe;
+	void *priv;
 };
 
-struct sdhci_cdns_phy_cfg {
-	const char *property;
-	u8 addr;
+static u32 tune_val_start = SDHCI_CDNS_TUNE_START;
+static u32 tune_val_step = SDHCI_CDNS_TUNE_STEP;
+static u32 max_tune_iter = SDHCI_CDNS_TUNE_ITERATIONS;
+static u32 read_dqs_cmd_delay;
+static struct sdhci_cdns_sd6_phy sd6_phy_config;
+
+/* Flag for SD6 controller */
+static bool sd6_ctrl;
+
+static void init_hs(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
+{
+	*t = (struct sdhci_cdns_sd6_phy_timings){
+		.t_cmd_output_min = 2000, .t_cmd_output_max = t_sdclk - 6000,
+		.t_dat_output_min = 2000, .t_dat_output_max = t_sdclk - 6000,
+		.t_cmd_input_min = 14000, .t_cmd_input_max = t_sdclk + 2500,
+		.t_dat_input_min = 14000, .t_dat_input_max = t_sdclk + 2500,
+		.t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4
+	};
+}
+
+static void init_uhs_sdr12(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
+{
+	*t = (struct sdhci_cdns_sd6_phy_timings){
+		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 3000,
+		.t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 3000,
+		.t_cmd_input_min = 14000, .t_cmd_input_max = t_sdclk + 1500,
+		.t_dat_input_min = 14000, .t_dat_input_max = t_sdclk + 1500,
+		.t_sdclk_min = 1000000 / 25, .t_sdclk_max = 1000000 / 0.4
+	};
+}
+
+static void init_uhs_sdr25(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
+{
+	*t = (struct sdhci_cdns_sd6_phy_timings){
+		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 3000,
+		.t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 3000,
+		.t_cmd_input_min = 14000, .t_cmd_input_max = t_sdclk + 1500,
+		.t_dat_input_min = 14000, .t_dat_input_max = t_sdclk + 1500,
+		.t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4
+	};
+}
+
+static void init_uhs_sdr50(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
+{
+	*t = (struct sdhci_cdns_sd6_phy_timings){
+		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 3000,
+		.t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 3000,
+		.t_cmd_input_min = 7500, .t_cmd_input_max = t_sdclk + 1500,
+		.t_dat_input_min = 7500, .t_dat_input_max = t_sdclk + 1500,
+		.t_sdclk_min = 1000000 / 100, .t_sdclk_max = 1000000 / 0.4
+	};
+}
+
+static void init_uhs_sdr104(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
+{
+	*t = (struct sdhci_cdns_sd6_phy_timings){
+		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 1400,
+		.t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 1400,
+		.t_cmd_input_min = 1000, .t_cmd_input_max = t_sdclk + 1000,
+		.t_dat_input_min = 1000, .t_dat_input_max = t_sdclk + 1000,
+		.t_sdclk_min = 1000000 / 200, .t_sdclk_max = 1000000 / 100
+	};
+}
+
+static void init_uhs_ddr50(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
+{
+	*t = (struct sdhci_cdns_sd6_phy_timings){
+		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 3000,
+		.t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 3000,
+		.t_cmd_input_min = 13700, .t_cmd_input_max = t_sdclk + 1500,
+		.t_dat_input_min = 7000, .t_dat_input_max = t_sdclk + 1500,
+		.t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4
+	};
+}
+
+static void init_emmc_legacy(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
+{
+	*t = (struct sdhci_cdns_sd6_phy_timings){
+		.t_cmd_output_min = 3000, .t_cmd_output_max = t_sdclk - 3000,
+		.t_dat_output_min = 3000, .t_dat_output_max = t_sdclk - 3000,
+		.t_cmd_input_min = 11700, .t_cmd_input_max = t_sdclk + 8300,
+		.t_dat_input_min = 11700, .t_dat_input_max = t_sdclk + 8300,
+		.t_sdclk_min = 1000000 / 25, .t_sdclk_max = 1000000 / 0.4
+	};
+}
+
+static void init_emmc_sdr(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
+{
+	*t = (struct sdhci_cdns_sd6_phy_timings){
+		.t_cmd_output_min = 3000, .t_cmd_output_max = t_sdclk - 3000,
+		.t_dat_output_min = 3000, .t_dat_output_max = t_sdclk - 3000,
+		.t_cmd_input_min = 13700, .t_cmd_input_max = t_sdclk + 2500,
+		.t_dat_input_min = 13700, .t_dat_input_max = t_sdclk + 2500,
+		.t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4
+	};
+}
+
+static void init_emmc_ddr(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
+{
+	*t = (struct sdhci_cdns_sd6_phy_timings){
+		.t_cmd_output_min = 3000, .t_cmd_output_max = t_sdclk - 3000,
+		.t_dat_output_min = 2500, .t_dat_output_max = t_sdclk - 2500,
+		.t_cmd_input_min = 13700, .t_cmd_input_max = t_sdclk + 2500,
+		.t_dat_input_min = 7000, .t_dat_input_max = t_sdclk + 1500,
+		.t_sdclk_min = 1000000 / 50, .t_sdclk_max = 1000000 / 0.4
+	};
+}
+
+static void init_emmc_hs200(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
+{
+	*t = (struct sdhci_cdns_sd6_phy_timings){
+		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 1400,
+		.t_dat_output_min = 800, .t_dat_output_max = t_sdclk - 1400,
+		.t_cmd_input_min = 1000, .t_cmd_input_max = t_sdclk + 1000,
+		.t_dat_input_min = 1000, .t_dat_input_max = t_sdclk + 1000,
+		.t_sdclk_min = 1000000 / 200, .t_sdclk_max = 1000000 / 100
+	};
+}
+
+/* HS400 and HS400ES */
+static void init_emmc_hs400(struct sdhci_cdns_sd6_phy_timings *t, int t_sdclk)
+{
+	*t = (struct sdhci_cdns_sd6_phy_timings){
+		.t_cmd_output_min = 800, .t_cmd_output_max = t_sdclk - 1400,
+		.t_dat_output_min = 400, .t_dat_output_max = t_sdclk - 400,
+		.t_cmd_input_min = 1000, .t_cmd_input_max = t_sdclk + 1000,
+		.t_dat_input_min = 1000, .t_dat_input_max = t_sdclk + 1000,
+		.t_sdclk_min = 1000000 / 200, .t_sdclk_max = 1000000 / 100
+	};
+}
+
+static void (*init_timings[])(struct sdhci_cdns_sd6_phy_timings*, int) = {
+	&init_emmc_legacy, &init_emmc_legacy, &init_hs, &init_emmc_sdr,
+	&init_emmc_ddr, &init_uhs_sdr12, &init_uhs_sdr25, &init_uhs_sdr50,
+	&init_uhs_ddr50, &init_uhs_sdr104, &init_emmc_hs200, &init_emmc_hs400,
+	&init_emmc_hs400,
 };
 
 static const struct sdhci_cdns_phy_cfg sdhci_cdns_sd4_phy_cfgs[] = {
@@ -133,10 +480,405 @@  static int sdhci_cdns_sd4_phy_init(struct sdhci_cdns_plat *plat,
 	return 0;
 }
 
+static u32 sdhci_cdns_sd6_readl(struct sdhci_host *host, int reg)
+{
+	return readl(host->ioaddr + reg);
+}
+
+static u32 sdhci_cdns_sd6_read_phy_reg(struct sdhci_cdns_plat *plat,
+				       u32 addr)
+{
+	writel(addr, plat->hrs_addr + SDHCI_CDNS_HRS04);
+	return readl(plat->hrs_addr + SDHCI_CDNS_HRS05);
+}
+
+static void sdhci_cdns_sd6_writel(struct sdhci_host *host, u32 val, int reg)
+{
+	writel(val, host->ioaddr + reg);
+}
+
+static u16 sdhci_cdns_sd6_readw(struct sdhci_host *host, int reg)
+{
+	u32 val, regoff;
+
+	regoff = reg & ~3;
+
+	val = readl(host->ioaddr + regoff);
+	if ((reg & 0x3) == 0)
+		return (val & 0xFFFF);
+	else
+		return ((val >> 16) & 0xFFFF);
+}
+
+static void sdhci_cdns_sd6_writew(struct sdhci_host *host, u16 val, int reg)
+{
+	writew(val, host->ioaddr + reg);
+}
+
+static u8 sdhci_cdns_sd6_readb(struct sdhci_host *host, int reg)
+{
+	u32 val, regoff;
+
+	regoff = reg & ~3;
+
+	val = readl(host->ioaddr + regoff);
+	switch (reg & 3) {
+	case 0:
+		return (val & 0xFF);
+	case 1:
+		return ((val >> 8) & 0xFF);
+	case 2:
+		return ((val >> 16) & 0xFF);
+	case 3:
+		return ((val >> 24) & 0xFF);
+	}
+	return 0;
+}
+
+static void sdhci_cdns_sd6_writeb(struct sdhci_host *host, u8 val, int reg)
+{
+	writeb(val, host->ioaddr + reg);
+}
+
+static int sdhci_cdns_sd6_get_delay_params(struct udevice *dev, struct sdhci_cdns_plat *plat)
+{
+	struct sdhci_cdns_sd6_phy *phy = plat->priv;
+	int ret;
+
+	ret = dev_read_u32(dev, "cdns,read_dqs_cmd_delay",
+			   &phy->settings.cp_read_dqs_cmd_delay);
+	if (ret)
+		phy->settings.cp_read_dqs_cmd_delay = DEFAULT_CMD_DELAY;
+
+	ret = dev_read_u32(dev, "cdns,tune_val_start", &tune_val_start);
+	if (ret)
+		tune_val_start = SDHCI_CDNS_TUNE_START;
+
+	ret = dev_read_u32(dev, "cdns,tune_val_step", &tune_val_step);
+	if (ret)
+		tune_val_step = SDHCI_CDNS_TUNE_STEP;
+
+	ret = dev_read_u32(dev, "cdns,max_tune_iter", &max_tune_iter);
+	if (ret)
+		max_tune_iter = SDHCI_CDNS_TUNE_ITERATIONS;
+
+	read_dqs_cmd_delay = phy->settings.cp_read_dqs_cmd_delay;
+	return 0;
+}
+
+#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT)
+static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_plat *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);
+}
+#endif
+
+void sdhci_cdns_sd6_fullsw_reset(struct sdhci_cdns_plat *plat)
+{
+	u32 regval;
+
+	regval = readl(plat->hrs_addr + SDHCI_CDNS_HRS00);
+	regval |= SDHCI_CDNS_HRS00_SWR;
+	writel(regval, plat->hrs_addr + SDHCI_CDNS_HRS00);
+
+	do {
+		regval = readl(plat->hrs_addr + SDHCI_CDNS_HRS00);
+	} while (regval & SDHCI_CDNS_HRS00_SWR);
+}
+
+void sdhci_cdns_sd6_stop_clock(struct sdhci_cdns_plat *plat)
+{
+	u32 reg_srs11 = 0;
+
+	reg_srs11 = readl(plat->hrs_addr + SDHCI_CDNS_SRS_BASE + SDHCI_CDNS_SRS11);
+	reg_srs11 &= ~5;
+	writel(reg_srs11, plat->hrs_addr + SDHCI_CDNS_SRS_BASE + SDHCI_CDNS_SRS11);
+}
+
+void sdhci_cdns_sd6_set_volt(struct sdhci_cdns_plat *plat)
+{
+	u32 controller_setting = 0;
+
+	controller_setting = readl(plat->hrs_addr + SDHCI_CDNS_SRS_BASE + SDHCI_CDNS_HRS10);
+	controller_setting &= ~(7 << 9);
+
+	controller_setting |= (7 << 9);
+	controller_setting |= (1 << 8);
+
+	writel(controller_setting, plat->hrs_addr + SDHCI_CDNS_SRS_BASE + SDHCI_CDNS_HRS10);
+	controller_setting = readl(plat->hrs_addr + SDHCI_CDNS_SRS_BASE + SDHCI_CDNS_HRS10);
+}
+
+static int sdhci_cdns_sd6_get_fdt_params(struct udevice *dev, struct sdhci_cdns_plat *plat)
+{
+	struct sdhci_cdns_sd6_phy *phy = plat->priv;
+	const char *mode_name;
+	int ret;
+
+	dev_read_u32(dev, "cdns,iocell_input_delay", &phy->d.iocell_input_delay);
+
+	dev_read_u32(dev, "cdns,iocell_output_delay", &phy->d.iocell_output_delay);
+
+	dev_read_u32(dev, "cdns,delay_element", &phy->d.delay_element);
+
+	ret = dev_read_u32(dev, "cdns,host_slew", &phy->settings.slew);
+	if (ret)
+		phy->settings.slew = 3;
+
+	ret = dev_read_u32(dev, "cdns,host_drive", &phy->settings.drive);
+	if (ret)
+		phy->settings.drive = 2;
+
+	mode_name = dev_read_string(dev, "cdns,mode");
+
+	if (mode_name != NULL) {
+		if (!strcmp("emmc_sdr", mode_name))
+			phy->mode = MMC_HS_52;
+		else if (!strcmp("emmc_ddr", mode_name))
+			phy->mode = MMC_DDR_52;
+		else if (!strcmp("emmc_hs200", mode_name))
+			phy->mode = MMC_HS_200;
+		else if (!strcmp("emmc_hs400", mode_name))
+			phy->mode = MMC_HS_400;
+		else if (!strcmp("emmc_hs400_es", mode_name))
+			phy->mode = MMC_HS_400_ES;
+		else if (!strcmp("sd_hs", mode_name))
+			phy->mode = SD_HS;
+		else
+			phy->mode = MMC_HS;
+	} else {
+		phy->mode = MMC_HS;
+	}
+
+	sdhci_cdns_sd6_get_delay_params(dev, plat);
+	return 0;
+}
+
+static void sdhci_cdns_sd6_write_phy_reg(struct sdhci_cdns_plat *plat,
+					 u32 addr, u32 data)
+{
+	writel(addr, plat->hrs_addr + SDHCI_CDNS_HRS04);
+	writel(data, plat->hrs_addr + SDHCI_CDNS_HRS05);
+}
+
+static int sdhci_cdns_sd6_dll_reset(struct sdhci_cdns_plat *plat, bool doreset)
+{
+	u32 reg;
+	int ret = 0;
+
+	reg = readl(plat->hrs_addr + SDHCI_CDNS_HRS09);
+	if (doreset)
+		reg &= ~SDHCI_CDNS_HRS09_PHY_SW_RESET;
+	else
+		reg |= SDHCI_CDNS_HRS09_PHY_SW_RESET;
+	writel(reg, plat->hrs_addr + SDHCI_CDNS_HRS09);
+
+	if (!doreset) {
+		do {
+			reg = readl(plat->hrs_addr + SDHCI_CDNS_HRS09);
+		} while ((reg & SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE) == 0);
+	}
+
+	return ret;
+}
+
+static int sdhci_cdns_sd6_phy_init(struct udevice *dev, struct sdhci_cdns_plat *plat)
+{
+	int ret;
+	u32 reg;
+	struct sdhci_cdns_sd6_phy *phy = plat->priv;
+
+	if ((phy->mode == -1) || (phy->t_sdclk == -1))
+		return 0;
+
+	sdhci_cdns_sd6_dll_reset(plat, true);
+
+	/* cp_use_phony_dqs SDHCI_CDNS_SD6_PHY_DQS_TIMING */
+	reg = sdhci_cdns_sd6_read_phy_reg(plat, SDHCI_CDNS_SD6_PHY_DQS_TIMING);
+	reg &= ~SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS;
+	reg &= ~SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_LPBK_DQS;
+	reg &= ~SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS;
+	reg &= ~SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD;
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS,
+			phy->settings.cp_use_ext_lpbk_dqs);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_LPBK_DQS,
+			phy->settings.cp_use_lpbk_dqs);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS,
+			phy->settings.cp_use_phony_dqs);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD,
+			phy->settings.cp_use_phony_dqs_cmd);
+
+	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_DQS_TIMING, reg);
+
+	/* SDHCI_CDNS_SD6_PHY_GATE_LPBK */
+	reg = sdhci_cdns_sd6_read_phy_reg(plat, SDHCI_CDNS_SD6_PHY_GATE_LPBK);
+	reg &= ~SDHCI_CDNS_SD6_PHY_GATE_LPBK_SYNC_METHOD;
+	reg &= ~SDHCI_CDNS_SD6_PHY_GATE_LPBK_SW_HALF_CYCLE_SHIFT;
+	reg &= ~SDHCI_CDNS_SD6_PHY_GATE_LPBK_RD_DEL_SEL;
+	reg &= ~SDHCI_CDNS_SD6_PHY_GATE_LPBK_GATE_CFG_ALWAYS_ON;
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_SYNC_METHOD,
+			phy->settings.cp_sync_method);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_SW_HALF_CYCLE_SHIFT,
+			phy->settings.cp_sw_half_cycle_shift);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_RD_DEL_SEL,
+			phy->settings.cp_rd_del_sel);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_GATE_CFG_ALWAYS_ON,
+			phy->settings.cp_gate_cfg_always_on);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GATE_LPBK_UNDERRUN_SUPPRESS,
+			phy->settings.cp_underrun_suppress);
+
+	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_GATE_LPBK, reg);
+
+	/* SDHCI_CDNS_SD6_PHY_DLL_MASTER */
+	reg = 0x0;
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_MASTER_BYPASS_MODE,
+			 phy->settings.cp_dll_bypass_mode);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_MASTER_PHASE_DETECT_SEL, 2);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_MASTER_DLL_START_POINT,
+			phy->settings.cp_dll_start_point);
+
+	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_DLL_MASTER, reg);
+
+	/* SDHCI_CDNS_SD6_PHY_DLL_SLAVE */
+	reg = 0x0;
+	reg = FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_SLAVE_READ_DQS_CMD_DELAY,
+			 phy->settings.cp_read_dqs_cmd_delay);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_SLAVE_CLK_WRDQS_DELAY,
+			phy->settings.cp_clk_wrdqs_delay);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_SLAVE_CLK_WR_DELAY,
+			phy->settings.cp_clk_wr_delay);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DLL_SLAVE_READ_DQS_DELAY,
+			phy->settings.cp_read_dqs_delay);
+
+	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_DLL_SLAVE, reg);
+
+	/* SDHCI_CDNS_SD6_PHY_CTRL */
+	reg = sdhci_cdns_sd6_read_phy_reg(plat, SDHCI_CDNS_SD6_PHY_CTRL);
+	reg &= ~SDHCI_CDNS_SD6_PHY_CTRL_PHONY_DQS_TIMING;
+	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_CTRL, reg);
+
+	reg = sdhci_cdns_sd6_read_phy_reg(plat, SDHCI_CDNS_SD6_PHY_GPIO_CTRL0);
+	reg |= SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_CLK_OVR_EN;
+	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_GPIO_CTRL0, reg);
+
+	reg = sdhci_cdns_sd6_read_phy_reg(plat, SDHCI_CDNS_SD6_PHY_GPIO_CTRL0);
+	/* Clear the drive and slew fields */
+	reg &= ~0x77;
+	/* Use the drive and slew from settings */
+	reg |= SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_DRV_OVR_EN |
+		SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_SLEW_OVR_EN;
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_DRV,
+			phy->settings.drive);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_GPIO_CTRL0_SLEW,
+		phy->settings.slew);
+
+	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_GPIO_CTRL0, reg);
+
+	ret = sdhci_cdns_sd6_dll_reset(plat, false);
+	if (ret)
+		return ret;
+
+	reg = sdhci_cdns_sd6_read_phy_reg(plat, SDHCI_CDNS_SD6_PHY_DQ_TIMING);
+	reg &= ~SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON;
+	reg &= ~SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_END;
+	reg &= ~SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_START;
+	reg &= ~SDHCI_CDNS_SD6_PHY_DQ_TIMING_DATA_SELECT_OE_END;
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON,
+			phy->settings.cp_io_mask_always_on);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_END,
+			phy->settings.cp_io_mask_end);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQ_TIMING_IO_MASK_START,
+			phy->settings.cp_io_mask_start);
+	reg |= FIELD_PREP(SDHCI_CDNS_SD6_PHY_DQ_TIMING_DATA_SELECT_OE_END,
+			phy->settings.cp_data_select_oe_end);
+
+	sdhci_cdns_sd6_write_phy_reg(plat, SDHCI_CDNS_SD6_PHY_DQ_TIMING, reg);
+
+	reg = readl(plat->hrs_addr + SDHCI_CDNS_HRS09);
+	if (phy->settings.sdhc_extended_wr_mode)
+		reg |= SDHCI_CDNS_HRS09_EXTENDED_WR_MODE;
+	else
+		reg &= ~SDHCI_CDNS_HRS09_EXTENDED_WR_MODE;
+
+	if (phy->settings.sdhc_extended_rd_mode)
+		reg |= SDHCI_CDNS_HRS09_EXTENDED_RD_MODE;
+	else
+		reg &= ~SDHCI_CDNS_HRS09_EXTENDED_RD_MODE;
+
+	if (phy->settings.sdhc_rddata_en)
+		reg |= SDHCI_CDNS_HRS09_RDDATA_EN;
+	else
+		reg &= ~SDHCI_CDNS_HRS09_RDDATA_EN;
+
+	if (phy->settings.sdhc_rdcmd_en)
+		reg |= SDHCI_CDNS_HRS09_RDCMD_EN;
+	else
+		reg &= ~SDHCI_CDNS_HRS09_RDCMD_EN;
+
+	writel(reg, plat->hrs_addr + SDHCI_CDNS_HRS09);
+
+	writel(0x30004, plat->hrs_addr + SDHCI_CDNS_HRS02);
+
+	reg = 0x0;
+	reg = FIELD_PREP(SDHCI_CDNS_HRS10_HCSDCLKADJ, phy->settings.sdhc_hcsdclkadj);
+	writel(reg, plat->hrs_addr + SDHCI_CDNS_HRS10);
+
+	if (phy->mode == MMC_HS_52 || phy->mode == MMC_DDR_52) {
+		reg = 0x202;
+	} else {
+		reg = 0x0;
+		reg = FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY,
+				 phy->settings.sdhc_wrdata1_sdclk_dly);
+		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY,
+				  phy->settings.sdhc_wrdata0_sdclk_dly);
+		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY,
+				  phy->settings.sdhc_wrcmd1_sdclk_dly);
+		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY,
+				  phy->settings.sdhc_wrcmd0_sdclk_dly);
+		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_DLY,
+				  phy->settings.sdhc_wrdata1_dly);
+		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_DLY,
+				  phy->settings.sdhc_wrdata0_dly);
+		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_DLY,
+				  phy->settings.sdhc_wrcmd1_dly);
+		reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_DLY,
+				  phy->settings.sdhc_wrcmd0_dly);
+	}
+
+	writel(reg, plat->hrs_addr + SDHCI_CDNS_HRS16);
+
+	reg = 0x0;
+	reg = FIELD_PREP(SDHCI_CDNS_HRS07_RW_COMPENSATE,
+			 phy->settings.sdhc_rw_compensate);
+	reg |= FIELD_PREP(SDHCI_CDNS_HRS07_IDELAY_VAL,
+			 phy->settings.sdhc_idelay_val);
+	writel(reg, plat->hrs_addr + SDHCI_CDNS_HRS07);
+	return 0;
+}
+
+static int sdhci_cdns_sd6_set_tune_val(struct sdhci_cdns_plat *plat,
+				       unsigned int val)
+{
+	struct sdhci_cdns_sd6_phy *phy = plat->priv;
+
+	phy->settings.hs200_tune_val = val;
+	phy->settings.cp_read_dqs_cmd_delay = val;
+	phy->settings.cp_read_dqs_delay = val;
+
+	return sdhci_cdns_sd6_phy_init(NULL, plat);
+}
+
 static void sdhci_cdns_set_control_reg(struct sdhci_host *host)
 {
 	struct mmc *mmc = host->mmc;
 	struct sdhci_cdns_plat *plat = dev_get_plat(mmc->dev);
+	struct sdhci_cdns_sd6_phy *phy = plat->priv;
 	unsigned int clock = mmc->clock;
 	u32 mode, tmp;
 
@@ -146,17 +888,43 @@  static void sdhci_cdns_set_control_reg(struct sdhci_host *host)
 	 * U-Boot does not support timing.  Use the clock frequency instead.
 	 */
 	if (clock <= 26000000) {
-		mode = SDHCI_CDNS_HRS06_MODE_SD; /* use this for Legacy */
+		phy->mode = MMC_LEGACY; /* use this for Legacy */
 	} else if (clock <= 52000000) {
 		if (mmc->ddr_mode)
-			mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR;
+			phy->mode = MMC_DDR_52;
 		else
-			mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR;
+			phy->mode = MMC_HS_52;
 	} else {
 		if (mmc->ddr_mode)
-			mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400;
+			phy->mode = MMC_HS_400;
 		else
-			mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200;
+			phy->mode = MMC_HS_200;
+	}
+
+	switch (phy->mode) {
+	case MMC_LEGACY:
+	case MMC_HS:
+		mode = SDHCI_CDNS_HRS06_MODE_LEGACY;
+		break;
+	case MMC_HS_52:
+		mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR;
+		break;
+	case MMC_DDR_52:
+		mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR;
+		break;
+	case MMC_HS_200:
+		mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200;
+		break;
+	case MMC_HS_400:
+		mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400;
+		break;
+	case MMC_HS_400_ES:
+		mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400ES;
+		break;
+	default:
+		/* All other modes treated as SD */
+		mode = SDHCI_CDNS_HRS06_MODE_SD;
+		break;
 	}
 
 	tmp = readl(plat->hrs_addr + SDHCI_CDNS_HRS06);
@@ -165,10 +933,6 @@  static void sdhci_cdns_set_control_reg(struct sdhci_host *host)
 	writel(tmp, plat->hrs_addr + SDHCI_CDNS_HRS06);
 }
 
-static const struct sdhci_ops sdhci_cdns_ops = {
-	.set_control_reg = sdhci_cdns_set_control_reg,
-};
-
 static int sdhci_cdns_set_tune_val(struct sdhci_cdns_plat *plat,
 				   unsigned int val)
 {
@@ -201,6 +965,443 @@  static int sdhci_cdns_set_tune_val(struct sdhci_cdns_plat *plat,
 	return 0;
 }
 
+static int sdhci_cdns_sd6_phy_lock_dll(struct sdhci_cdns_sd6_phy *phy)
+{
+	u32 delay_element = phy->d.delay_element_org;
+	u32 delay_elements_in_sdmclk;
+	enum sdhci_cdns_sd6_phy_lock_mode mode;
+
+	delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk, delay_element);
+	if (delay_elements_in_sdmclk > 256) {
+		delay_element *= 2;
+		delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk,
+							delay_element);
+
+		if (delay_elements_in_sdmclk > 256) {
+			return -1;
+		} else {
+			mode = SDHCI_CDNS_SD6_PHY_LOCK_MODE_HALF_CLK;
+			phy->vars.dll_max_value = 127;
+		}
+	} else {
+		mode = SDHCI_CDNS_SD6_PHY_LOCK_MODE_FULL_CLK;
+		phy->vars.dll_max_value = 255;
+	}
+
+	phy->vars.t_sdmclk_calc = delay_element * delay_elements_in_sdmclk;
+	phy->d.delay_element = delay_element;
+	phy->settings.cp_dll_locked_mode = mode;
+	phy->settings.cp_dll_bypass_mode = 0;
+
+	return 0;
+}
+
+static void sdhci_cdns_sd6_phy_dll_bypass(struct sdhci_cdns_sd6_phy *phy)
+{
+	phy->vars.dll_max_value = 256;
+	phy->settings.cp_dll_bypass_mode = 1;
+	phy->settings.cp_dll_locked_mode =
+		SDHCI_CDNS_SD6_PHY_LOCK_MODE_SATURATION;
+}
+
+static void sdhci_cdns_sd6_phy_configure_dll(struct sdhci_cdns_sd6_phy *phy)
+{
+	if (phy->settings.sdhc_extended_wr_mode == 0) {
+		if (sdhci_cdns_sd6_phy_lock_dll(phy) == 0)
+			return;
+	}
+	sdhci_cdns_sd6_phy_dll_bypass(phy);
+}
+
+static void sdhci_cdns_sd6_phy_calc_out(struct sdhci_cdns_sd6_phy *phy,
+					bool cmd_not_dat)
+{
+	u32 wr0_dly = 0, wr1_dly = 0, output_min, output_max, phy_o_delay;
+	u32 clk_wr_delay = 0, wr0_sdclk_dly = 0, wr1_sdclk_dly = 0;
+	bool data_ddr = phy->ddr && !cmd_not_dat;
+	int t;
+
+	if (cmd_not_dat) {
+		output_min = phy->t.t_cmd_output_min;
+		output_max = phy->t.t_cmd_output_max;
+		phy_o_delay = phy->d.phy_cmd_o_delay;
+	} else {
+		output_min = phy->t.t_dat_output_min;
+		output_max = phy->t.t_dat_output_max;
+		phy_o_delay = phy->d.phy_dat_o_delay;
+	}
+
+	clk_wr_delay = 0;
+	if (data_ddr)
+		wr0_sdclk_dly = wr1_sdclk_dly = 1;
+
+	t = phy_o_delay - phy->d.phy_sdclk_delay - output_min;
+	if (t < 0 && phy->settings.sdhc_extended_wr_mode == 1) {
+		u32 n_half_cycle = DIV_ROUND_UP(-t * 2, phy->t_sdmclk);
+
+		wr0_dly = (n_half_cycle + 1) / 2;
+		if (data_ddr)
+			wr1_dly = (n_half_cycle + 1) / 2;
+		else
+			wr1_dly = (n_half_cycle + 1) % 2 + wr0_dly - 1;
+	}
+
+	if (phy->settings.sdhc_extended_wr_mode == 0) {
+		u32 out_hold, out_setup, out_hold_margin;
+		u32 n;
+
+		if (!data_ddr)
+			wr0_dly = 1;
+
+		out_setup = output_max;
+		out_hold = output_min;
+		out_hold_margin = DIV_ROUND_UP(out_setup - out_hold, 4);
+		out_hold += out_hold_margin;
+
+		if (phy->settings.cp_dll_bypass_mode == 0)
+			n = DIV_ROUND_UP(256 * out_hold, phy->vars.t_sdmclk_calc);
+		else
+			n = DIV_ROUND_UP(out_hold, phy->d.delay_element) - 1;
+
+		if (n <= phy->vars.dll_max_value) {
+			clk_wr_delay = n;
+		} else {
+			clk_wr_delay = 255;
+			/* no dll setting*/
+		}
+	} else {
+		/*  sdhc_extended_wr_mode = 1 - PHY IO cell work in SDR mode */
+		clk_wr_delay = 0;
+	}
+
+	if (cmd_not_dat) {
+		phy->settings.sdhc_wrcmd0_dly = wr0_dly;
+		phy->settings.sdhc_wrcmd1_dly = wr1_dly;
+		phy->settings.cp_clk_wrdqs_delay = clk_wr_delay;
+		phy->settings.sdhc_wrcmd0_sdclk_dly = wr0_sdclk_dly;
+		phy->settings.sdhc_wrcmd1_sdclk_dly = wr1_sdclk_dly;
+	} else {
+		phy->settings.sdhc_wrdata0_dly = wr0_dly;
+		phy->settings.sdhc_wrdata1_dly = wr1_dly;
+		phy->settings.cp_clk_wr_delay = clk_wr_delay;
+		phy->settings.sdhc_wrdata0_sdclk_dly = wr0_sdclk_dly;
+		phy->settings.sdhc_wrdata1_sdclk_dly = wr1_sdclk_dly;
+	}
+}
+
+static void sdhci_cdns_sd6_phy_calc_cmd_out(struct sdhci_cdns_sd6_phy *phy)
+{
+	sdhci_cdns_sd6_phy_calc_out(phy, true);
+}
+
+static void sdhci_cdns_sd6_phy_calc_cmd_in(struct sdhci_cdns_sd6_phy *phy)
+{
+	phy->settings.cp_io_mask_end =
+		((phy->d.iocell_output_delay + phy->d.iocell_input_delay) * 2)
+		/ phy->t_sdmclk;
+
+	if (phy->settings.cp_io_mask_end >= 8)
+		phy->settings.cp_io_mask_end = 7;
+
+	if (phy->strobe_cmd && phy->settings.cp_io_mask_end > 0)
+		phy->settings.cp_io_mask_end--;
+
+	if (phy->strobe_cmd) {
+		phy->settings.cp_use_phony_dqs_cmd = 0;
+		phy->settings.cp_read_dqs_cmd_delay = 64;
+	} else {
+		phy->settings.cp_use_phony_dqs_cmd = 1;
+		phy->settings.cp_read_dqs_cmd_delay = 0;
+	}
+
+	if ((phy->mode == MMC_HS_400 && !phy->strobe_cmd) ||
+	    phy->mode == MMC_HS_200)
+		phy->settings.cp_read_dqs_cmd_delay =
+			phy->settings.hs200_tune_val;
+}
+
+static void sdhci_cdns_sd6_phy_calc_dat_in(struct sdhci_cdns_sd6_phy *phy)
+{
+	u32 hcsdclkadj = 0;
+
+	if (phy->strobe_dat) {
+		phy->settings.cp_use_phony_dqs = 0;
+		phy->settings.cp_read_dqs_delay = 64;
+	} else {
+		phy->settings.cp_use_phony_dqs = 1;
+		phy->settings.cp_read_dqs_delay = 0;
+	}
+
+	if (phy->mode == MMC_HS_200)
+		phy->settings.cp_read_dqs_delay =
+			phy->settings.hs200_tune_val;
+
+	if (phy->strobe_dat) {
+		/* dqs loopback input via IO cell */
+		hcsdclkadj += phy->d.iocell_input_delay;
+		/* dfi_dqs_in: mem_dqs -> clean_dqs_mod; delay of hic_dll_dqs_nand2 */
+		hcsdclkadj += phy->d.delay_element / 2;
+		/* delay line */
+		hcsdclkadj += phy->t_sdclk / 2;
+		/* PHY FIFO write pointer */
+		hcsdclkadj += phy->t_sdclk / 2 + phy->d.delay_element;
+		/* 1st synchronizer */
+		hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk)
+			* phy->t_sdmclk - hcsdclkadj;
+		/*
+		 * 2nd synchronizer + PHY FIFO read pointer + PHY rddata
+		 * + PHY rddata registered, + FIFO 1st ciu_en
+		 */
+		hcsdclkadj += 5 * phy->t_sdmclk;
+		/* FIFO 2st ciu_en */
+		hcsdclkadj += phy->t_sdclk;
+
+		hcsdclkadj /= phy->t_sdclk;
+	} else {
+		u32 n;
+
+		/* rebar PHY delay */
+		hcsdclkadj += 2 * phy->t_sdmclk;
+		/* rebar output via IO cell */
+		hcsdclkadj += phy->d.iocell_output_delay;
+		/* dqs loopback input via IO cell */
+		hcsdclkadj += phy->d.iocell_input_delay;
+		/* dfi_dqs_in: mem_dqs -> clean_dqs_mod delay of hic_dll_dqs_nand2 */
+		hcsdclkadj += phy->d.delay_element / 2;
+		/* dll: one delay element between SIGI_0 and SIGO_0 */
+		hcsdclkadj += phy->d.delay_element;
+		/* dfi_dqs_in: mem_dqs_delayed -> clk_dqs delay of hic_dll_dqs_nand2 */
+		hcsdclkadj += phy->d.delay_element / 2;
+		/* deskew DLL: clk_dqs -> clk_dqN: one delay element */
+		hcsdclkadj += phy->d.delay_element;
+
+		if (phy->t_sdclk == phy->t_sdmclk)
+			n = (hcsdclkadj - 2 * phy->t_sdmclk) / phy->t_sdclk;
+		else
+			n = hcsdclkadj / phy->t_sdclk;
+
+		/* phase shift within one t_sdclk clock cycle caused by rebar - lbk dqs delay */
+		hcsdclkadj = hcsdclkadj % phy->t_sdclk;
+		/* PHY FIFO write pointer */
+		hcsdclkadj += phy->t_sdclk / 2;
+		/* 1st synchronizer */
+		hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk)
+			* phy->t_sdmclk - hcsdclkadj;
+		/*
+		 * 2nd synchronizer + PHY FIFO read pointer + PHY rddata
+		 * + PHY rddata registered
+		 */
+		hcsdclkadj += 4 * phy->t_sdmclk;
+
+		if ((phy->t_sdclk / phy->t_sdmclk) > 1) {
+			u32 tmp1, tmp2;
+
+			tmp1 = hcsdclkadj;
+			tmp2 = (hcsdclkadj / phy->t_sdclk) * phy->t_sdclk
+				+ phy->t_sdclk - phy->t_sdmclk;
+			if (tmp1 == tmp2)
+				tmp2 += phy->t_sdclk;
+
+			/* FIFO aligns to clock cycle before ciu_en */
+			hcsdclkadj += tmp2 - tmp1;
+		}
+
+		/* FIFO 1st ciu_en */
+		hcsdclkadj += phy->t_sdmclk;
+		/* FIFO 2nd ciu_en */
+		hcsdclkadj += phy->t_sdclk;
+
+		hcsdclkadj /= phy->t_sdclk;
+
+		hcsdclkadj += n;
+
+		if ((phy->t_sdclk / phy->t_sdmclk) >= 2) {
+			if (phy->mode == UHS_DDR50 || phy->mode == MMC_DDR_52)
+				hcsdclkadj -= 2;
+			else
+				hcsdclkadj -= 1;
+		} else if ((phy->t_sdclk / phy->t_sdmclk) == 1) {
+			hcsdclkadj += 2;
+		}
+
+		if (phy->tune_dat)
+			hcsdclkadj -= 1;
+	}
+
+	if (hcsdclkadj > 15)
+		hcsdclkadj = 15;
+
+	phy->settings.sdhc_hcsdclkadj = hcsdclkadj;
+}
+
+static void sdhci_cdns_sd6_phy_calc_dat_out(struct sdhci_cdns_sd6_phy *phy)
+{
+	sdhci_cdns_sd6_phy_calc_out(phy, false);
+}
+
+static void sdhci_cdns_sd6_phy_calc_io(struct sdhci_cdns_sd6_phy *phy)
+{
+	u32 rw_compensate;
+
+	rw_compensate = (phy->d.iocell_input_delay + phy->d.iocell_output_delay)
+		/ phy->t_sdmclk + phy->settings.sdhc_wrdata0_dly + 5 + 3;
+
+	phy->settings.sdhc_idelay_val = (2 * phy->d.iocell_input_delay)
+		/ phy->t_sdmclk;
+
+	phy->settings.cp_io_mask_start = 0;
+	if (phy->t_sdclk == phy->t_sdmclk && rw_compensate > 10)
+		phy->settings.cp_io_mask_start = 2 * (rw_compensate - 10);
+
+	if (phy->mode == UHS_SDR104)
+		phy->settings.cp_io_mask_start++;
+
+	if (phy->t_sdclk == phy->t_sdmclk && phy->mode == UHS_SDR50)
+		phy->settings.cp_io_mask_start++;
+
+	phy->settings.sdhc_rw_compensate = rw_compensate;
+}
+
+static void sdhci_cdns_sd6_phy_calc_settings(struct sdhci_cdns_sd6_phy *phy)
+{
+	sdhci_cdns_sd6_phy_calc_cmd_out(phy);
+	sdhci_cdns_sd6_phy_calc_cmd_in(phy);
+	sdhci_cdns_sd6_phy_calc_dat_out(phy);
+	sdhci_cdns_sd6_phy_calc_dat_in(phy);
+	sdhci_cdns_sd6_phy_calc_io(phy);
+}
+
+static int sdhci_cdns_sd6_phy_update_timings(struct sdhci_cdns_plat *plat)
+{
+	struct sdhci_cdns_sd6_phy *phy = plat->priv;
+	int t_sdmclk = phy->t_sdmclk;
+
+	/* initialize input */
+	init_timings[phy->mode](&phy->t, phy->t_sdclk);
+
+	/* Reset the following setting before the switch statement to make sure
+	 * they are correct if we change speeds like after a mmc rescan
+	 */
+	phy->tune_cmd = false;
+	phy->ddr = false;
+	phy->strobe_dat = false;
+	phy->tune_dat = false;
+
+	switch (phy->mode) {
+	case UHS_SDR104:
+		phy->tune_cmd = true;
+		phy->tune_dat = true;
+		break;
+	case UHS_DDR50:
+		phy->ddr = true;
+		break;
+	case MMC_DDR_52:
+		phy->ddr = true;
+		break;
+	case MMC_HS_200:
+		phy->tune_dat = true;
+		phy->tune_cmd = true;
+		break;
+	case MMC_HS_400:
+	case MMC_HS_400_ES:
+		phy->tune_cmd = true;
+		phy->ddr = true;
+		phy->strobe_dat = true;
+		break;
+	}
+
+	if (plat->enhanced_strobe)
+		phy->strobe_cmd = true;
+
+	phy->d.phy_sdclk_delay = 2 * t_sdmclk;
+	phy->d.phy_cmd_o_delay = 2 * t_sdmclk + t_sdmclk / 2;
+	phy->d.phy_dat_o_delay = 2 * t_sdmclk + t_sdmclk / 2;
+
+	if (phy->t_sdclk == phy->t_sdmclk) {
+		phy->settings.sdhc_extended_wr_mode = 0;
+		phy->settings.sdhc_extended_rd_mode = 0;
+	} else {
+		phy->settings.sdhc_extended_wr_mode = 1;
+		phy->settings.sdhc_extended_rd_mode = 1;
+	}
+
+	phy->settings.cp_gate_cfg_always_on = 1;
+
+	sdhci_cdns_sd6_phy_configure_dll(phy);
+
+	sdhci_cdns_sd6_phy_calc_settings(phy);
+
+	return 0;
+}
+
+static void sdhci_cdns_sd6_set_clock(struct sdhci_host *host, unsigned int div)
+{
+	struct udevice *dev = host->mmc->dev;
+	struct sdhci_cdns_plat *plat = dev_get_plat(dev);
+	struct sdhci_cdns_sd6_phy *phy = plat->priv;
+	unsigned int clock;
+
+	if (!div)
+		div = 1;
+	else
+		div <<= 1;
+
+	clock = DIV_ROUND_DOWN_ULL(SDHCI_CDNS_SD6_MAXCLK, div);
+
+	sdhci_cdns_set_control_reg(host);
+
+	phy->t_sdclk = DIV_ROUND_DOWN_ULL(1e12, clock);
+
+	if (sdhci_cdns_sd6_phy_update_timings(plat))
+		debug("%s: update timings failed\n", __func__);
+	else
+		host->clock = clock;
+
+	if (sdhci_cdns_sd6_phy_init(dev, plat))
+		debug("%s: phy init failed\n", __func__);
+}
+
+static int sdhci_cdns_sd6_plat_init(struct udevice *dev, struct sdhci_cdns_plat *plat)
+{
+	struct sdhci_cdns_sd6_phy *phy = plat->priv;
+
+	memset(phy, 0, sizeof(struct sdhci_cdns_sd6_phy));
+
+	sdhci_cdns_sd6_fullsw_reset(plat);
+	sdhci_cdns_sd6_set_volt(plat);
+
+	sdhci_cdns_sd6_get_fdt_params(dev, plat);
+
+	phy->t_sdmclk = 5000;
+
+	phy->d.delay_element_org = phy->d.delay_element;
+	phy->settings.cp_sync_method = 1;
+	phy->settings.cp_rd_del_sel = 52;
+	phy->settings.cp_use_ext_lpbk_dqs = 1;
+	phy->settings.cp_use_lpbk_dqs = 1;
+	phy->settings.cp_data_select_oe_end = 1;
+	phy->settings.cp_dll_start_point = 4;
+
+	phy->settings.cp_use_phony_dqs = 1;
+	phy->settings.cp_use_phony_dqs_cmd = 1;
+	phy->settings.cp_dll_bypass_mode = 1;
+
+	phy->settings.cp_read_dqs_cmd_delay = 0;
+	phy->settings.cp_clk_wrdqs_delay = 0;
+	phy->settings.cp_clk_wr_delay = 0;
+	phy->settings.cp_read_dqs_delay = 0;
+	phy->settings.cp_io_mask_end = 5;
+	phy->settings.cp_io_mask_start = 0;
+	phy->settings.sdhc_extended_rd_mode = 1;
+	phy->settings.sdhc_extended_wr_mode = 1;
+	phy->settings.sdhc_hcsdclkadj = 6;
+	phy->settings.sdhc_rw_compensate = 10;
+	phy->settings.sdhc_idelay_val = 0;
+
+	sdhci_cdns_sd6_phy_update_timings(plat);
+	return 0;
+}
+
 static int __maybe_unused sdhci_cdns_execute_tuning(struct udevice *dev,
 						    unsigned int opcode)
 {
@@ -243,8 +1444,64 @@  static int __maybe_unused sdhci_cdns_execute_tuning(struct udevice *dev,
 	return sdhci_cdns_set_tune_val(plat, end_of_streak - max_streak / 2);
 }
 
+#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT)
+static int sdhci_cdns_hs400_enhanced_strobe(struct udevice *dev)
+{
+	struct sdhci_cdns_plat *plat = dev_get_plat(dev);
+
+	plat->enhanced_strobe = 1;
+	sdhci_cdns_set_emmc_mode(plat, SDHCI_CDNS_HRS06_MODE_MMC_HS400ES);
+
+	return 0;
+}
+#endif
+
+static int __maybe_unused sdhci_cdns_sd6_execute_tuning(struct mmc *mmc, unsigned char opcode)
+{
+	struct udevice *dev = mmc->dev;
+	struct sdhci_cdns_plat *plat = dev_get_plat(dev);
+	int cur_streak = 0;
+	int max_streak = 0;
+	int end_of_streak = 0;
+	int cnt = 0, midpoint, iter = 0;
+
+	for (cnt = tune_val_start; iter < max_tune_iter; iter++, cnt += tune_val_step) {
+		if (sdhci_cdns_sd6_set_tune_val(plat, cnt) ||
+		    mmc_send_tuning(mmc, opcode, NULL)) { /* bad */
+			cur_streak = 0;
+		} else { /* good */
+			cur_streak++;
+			if (cur_streak > max_streak) {
+				max_streak = cur_streak;
+				end_of_streak = cnt;
+			}
+		}
+	}
+
+	if (!max_streak)
+		return -EIO;
+
+	midpoint = end_of_streak - (((max_streak - 1) * tune_val_step) / 2);
+
+	return sdhci_cdns_sd6_set_tune_val(plat, midpoint);
+}
+
 static struct dm_mmc_ops sdhci_cdns_mmc_ops;
 
+static const struct sdhci_ops sdhci_cdns_ops = {
+	.set_control_reg = sdhci_cdns_set_control_reg,
+	.write_l = sdhci_cdns_sd6_writel,
+	.read_l = sdhci_cdns_sd6_readl,
+	.write_w = sdhci_cdns_sd6_writew,
+	.read_w = sdhci_cdns_sd6_readw,
+	.write_b = sdhci_cdns_sd6_writeb,
+	.read_b = sdhci_cdns_sd6_readb,
+	.set_clock = sdhci_cdns_sd6_set_clock,
+#ifdef MMC_SUPPORTS_TUNING
+	.platform_execute_tuning = sdhci_cdns_sd6_execute_tuning,
+#endif
+};
+
 static int sdhci_cdns_bind(struct udevice *dev)
 {
 	struct sdhci_cdns_plat *plat = dev_get_plat(dev);
@@ -255,11 +1512,18 @@  static int sdhci_cdns_bind(struct udevice *dev)
 static int sdhci_cdns_probe(struct udevice *dev)
 {
 	DECLARE_GLOBAL_DATA_PTR;
+
+	ofnode node = dev_ofnode(dev);
+
+	if (ofnode_device_is_compatible(node, "cdns,sd6hc"))
+		sd6_ctrl = 1;
+
 	struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
 	struct sdhci_cdns_plat *plat = dev_get_plat(dev);
 	struct sdhci_host *host = dev_get_priv(dev);
 	fdt_addr_t base;
 	int ret;
+	struct sdhci_cdns_sd6_phy *phy;
 
 	base = dev_read_addr(dev);
 	if (base == FDT_ADDR_T_NONE)
@@ -269,25 +1533,43 @@  static int sdhci_cdns_probe(struct udevice *dev)
 	if (!plat->hrs_addr)
 		return -ENOMEM;
 
+	plat->priv = &sd6_phy_config;
+
 	host->name = dev->name;
 	host->ioaddr = plat->hrs_addr + SDHCI_CDNS_SRS_BASE;
 	host->ops = &sdhci_cdns_ops;
 	host->quirks |= SDHCI_QUIRK_WAIT_SEND_CMD;
 	sdhci_cdns_mmc_ops = sdhci_ops;
 #ifdef MMC_SUPPORTS_TUNING
-	sdhci_cdns_mmc_ops.execute_tuning = sdhci_cdns_execute_tuning;
+	if (!sd6_ctrl)
+		sdhci_cdns_mmc_ops.execute_tuning = sdhci_cdns_execute_tuning;
 #endif
 
 	ret = mmc_of_parse(dev, &plat->cfg);
 	if (ret)
 		return ret;
 
-	ret = sdhci_cdns_sd4_phy_init(plat, gd->fdt_blob, dev_of_offset(dev));
-	if (ret)
-		return ret;
+#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT)
+	sdhci_cdns_mmc_ops.set_enhanced_strobe = sdhci_cdns_hs400_enhanced_strobe;
+#endif
+
+	phy = plat->priv;
+
+	if (sd6_ctrl) {
+		sdhci_cdns_sd6_plat_init(dev, plat);
+
+		ret = sdhci_cdns_sd6_phy_init(dev, plat);
+		if (ret)
+			return ret;
+	} else {
+		ret = sdhci_cdns_sd4_phy_init(plat, gd->fdt_blob, dev_of_offset(dev));
+		if (ret)
+			return ret;
+	}
 
 	host->mmc = &plat->mmc;
 	host->mmc->dev = dev;
+
 	ret = sdhci_setup_cfg(&plat->cfg, host, 0, 0);
 	if (ret)
 		return ret;
@@ -301,6 +1583,7 @@  static int sdhci_cdns_probe(struct udevice *dev)
 static const struct udevice_id sdhci_cdns_match[] = {
 	{ .compatible = "socionext,uniphier-sd4hc" },
 	{ .compatible = "cdns,sd4hc" },
+	{ .compatible = "cdns,sd6hc" },
 	{ /* sentinel */ }
 };