From patchwork Mon Jun 5 13:58:20 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Piyush Malgujar X-Patchwork-Id: 1790454 X-Patchwork-Delegate: jh80.chung@samsung.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=marvell.com header.i=@marvell.com header.a=rsa-sha256 header.s=pfpt0220 header.b=YQXujr35; dkim-atps=neutral Received: from phobos.denx.de (phobos.denx.de [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4QZb3B5bxvz20QH for ; Tue, 6 Jun 2023 00:05:10 +1000 (AEST) Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id 104A085EF6; Mon, 5 Jun 2023 16:02:38 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=marvell.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Authentication-Results: phobos.denx.de; dkim=pass (2048-bit key; unprotected) header.d=marvell.com header.i=@marvell.com header.b="YQXujr35"; dkim-atps=neutral Received: by phobos.denx.de (Postfix, from userid 109) id 39B2F8469C; Mon, 5 Jun 2023 15:58:59 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_NONE,SPF_PASS, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.2 Received: from mx0b-0016f401.pphosted.com (mx0b-0016f401.pphosted.com [67.231.156.173]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id 290E084687 for ; Mon, 5 Jun 2023 15:58:54 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=marvell.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=prvs=1520340d6b=pmalgujar@marvell.com Received: from pps.filterd (m0045851.ppops.net [127.0.0.1]) by mx0b-0016f401.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 355C4t6Y015996; Mon, 5 Jun 2023 06:58:50 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=marvell.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=pfpt0220; bh=22C0HPw7oQ/0o6DR7ZAxN1F5+rWE/8o5rYsj62Manz0=; b=YQXujr35vsBHMwX+Oc1GsR1V9H/skVJ2+6k+eTpaHcwxeQroWNTzRoaGK8jwMWedw09b 2+3jdBMaidcT3cDKqKKDkg+rsfO3GawfFJz7faZoXGqe6j3/irSUzPB95ST6gJje8DAl b+mo3kqJAMjuVM3aw0ltfEysmMBN0gqdsI63RYDRQSxqwX/5wqHTkmwAQIm2LVt2ypXs UUv3KpaAGtNHdu0Y+zZ4B1ODRg3Qo1l9x4Fu2+pa6pgQU5FFdgaaoUIBCHVRHxwp4yd5 oOIair6vDcGEQD/JaNP+/8Pn/i+p/50MimGgayxdkxSQsv9Lqd+WvVaWGs+iVYfe9GBk sw== Received: from pps.reinject (localhost [127.0.0.1]) by mx0b-0016f401.pphosted.com (PPS) with ESMTPS id 3r051k592d-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-SHA384 bits=256 verify=NOT); Mon, 05 Jun 2023 06:58:50 -0700 Received: from m0045851.ppops.net (m0045851.ppops.net [127.0.0.1]) by pps.reinject (8.17.1.5/8.17.1.5) with ESMTP id 355DtuQq023949; Mon, 5 Jun 2023 06:58:50 -0700 Received: from dc5-exch01.marvell.com ([199.233.59.181]) by mx0b-0016f401.pphosted.com (PPS) with ESMTPS id 3r051k592a-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-SHA384 bits=256 verify=NOT); Mon, 05 Jun 2023 06:58:49 -0700 Received: from DC5-EXCH02.marvell.com (10.69.176.39) by DC5-EXCH01.marvell.com (10.69.176.38) with Microsoft SMTP Server (TLS) id 15.0.1497.48; Mon, 5 Jun 2023 06:58:47 -0700 Received: from maili.marvell.com (10.69.176.80) by DC5-EXCH02.marvell.com (10.69.176.39) with Microsoft SMTP Server id 15.0.1497.48 via Frontend Transport; Mon, 5 Jun 2023 06:58:47 -0700 Received: from localhost.localdomain (unknown [10.110.150.250]) by maili.marvell.com (Postfix) with ESMTP id A2AC73F708E; Mon, 5 Jun 2023 06:58:47 -0700 (PDT) From: Piyush Malgujar To: , , CC: , , Dhananjay Kangude , Piyush Malgujar Subject: [PATCH 2/3] mmc: sdhci-cadence: SD6 Controller support Date: Mon, 5 Jun 2023 06:58:20 -0700 Message-ID: <20230605135821.4213-3-pmalgujar@marvell.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20230605135821.4213-1-pmalgujar@marvell.com> References: <20230605135821.4213-1-pmalgujar@marvell.com> MIME-Version: 1.0 X-Proofpoint-GUID: C_BZlAl66GWHK9evX4xUK1Su12os2Uf0 X-Proofpoint-ORIG-GUID: gTIQto3IeK4aWJSHmpZ7BAhBMdlkgiQ3 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.957,Hydra:6.0.573,FMLib:17.11.176.26 definitions=2023-06-05_28,2023-06-02_02,2023-05-22_02 X-Mailman-Approved-At: Mon, 05 Jun 2023 16:02:09 +0200 X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.39 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.103.8 at phobos.denx.de X-Virus-Status: Clean From: Dhananjay Kangude 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 Co-developed-by: Jayanthi Annadurai Signed-off-by: Jayanthi Annadurai Signed-off-by: Piyush Malgujar --- 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 #include +#include #include #include #include @@ -18,8 +19,19 @@ #include #include +#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 */ } };