| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * (C) Copyright 2016 Fuzhou Rockchip Electronics Co., Ltd | |
| * | |
| * Rockchip SD Host Controller Interface | |
| */ | |
| #include <clk.h> | |
| #include <dm.h> | |
| #include <dm/ofnode.h> | |
| #include <dt-structs.h> | |
| #include <linux/bitfield.h> | |
| #include <linux/delay.h> | |
| #include <linux/err.h> | |
| #include <linux/libfdt.h> | |
| #include <linux/iopoll.h> | |
| #include <malloc.h> | |
| #include <mapmem.h> | |
| #include "mmc_private.h" | |
| #include <sdhci.h> | |
| #include <syscon.h> | |
| #include <asm/arch-rockchip/clock.h> | |
| #include <asm/arch-rockchip/hardware.h> | |
| /* DWCMSHC specific Mode Select value */ | |
| #define DWCMSHC_CTRL_HS400 0x7 | |
| /* 400KHz is max freq for card ID etc. Use that as min */ | |
| #define EMMC_MIN_FREQ 400000 | |
| #define KHz (1000) | |
| #define MHz (1000 * KHz) | |
| #define SDHCI_TUNING_LOOP_COUNT 40 | |
| #define PHYCTRL_CALDONE_MASK 0x1 | |
| #define PHYCTRL_CALDONE_SHIFT 0x6 | |
| #define PHYCTRL_CALDONE_DONE 0x1 | |
| #define PHYCTRL_DLLRDY_MASK 0x1 | |
| #define PHYCTRL_DLLRDY_SHIFT 0x5 | |
| #define PHYCTRL_DLLRDY_DONE 0x1 | |
| #define PHYCTRL_FREQSEL_200M 0x0 | |
| #define PHYCTRL_FREQSEL_50M 0x1 | |
| #define PHYCTRL_FREQSEL_100M 0x2 | |
| #define PHYCTRL_FREQSEL_150M 0x3 | |
| #define PHYCTRL_DLL_LOCK_WO_TMOUT(x) \ | |
| ((((x) >> PHYCTRL_DLLRDY_SHIFT) & PHYCTRL_DLLRDY_MASK) ==\ | |
| PHYCTRL_DLLRDY_DONE) | |
| #define ARASAN_VENDOR_REGISTER 0x78 | |
| #define ARASAN_VENDOR_ENHANCED_STROBE BIT(0) | |
| /* Rockchip specific Registers */ | |
| #define DWCMSHC_EMMC_EMMC_CTRL 0x52c | |
| #define DWCMSHC_CARD_IS_EMMC BIT(0) | |
| #define DWCMSHC_ENHANCED_STROBE BIT(8) | |
| #define DWCMSHC_EMMC_AT_CTRL 0x540 | |
| #define EMMC_AT_CTRL_TUNE_CLK_STOP_EN BIT(16) | |
| #define EMMC_AT_CTRL_PRE_CHANGE_DLY 17 | |
| #define EMMC_AT_CTRL_POST_CHANGE_DLY 19 | |
| #define DWCMSHC_EMMC_DLL_CTRL 0x800 | |
| #define DWCMSHC_EMMC_DLL_CTRL_RESET BIT(1) | |
| #define DWCMSHC_EMMC_DLL_RXCLK 0x804 | |
| #define DWCMSHC_EMMC_DLL_TXCLK 0x808 | |
| #define DWCMSHC_EMMC_DLL_STRBIN 0x80c | |
| #define DWCMSHC_EMMC_DLL_CMDOUT 0x810 | |
| #define DWCMSHC_EMMC_DLL_STATUS0 0x840 | |
| #define DWCMSHC_EMMC_DLL_STATUS1 0x844 | |
| #define DWCMSHC_EMMC_DLL_START BIT(0) | |
| #define DWCMSHC_EMMC_DLL_LOCKED BIT(8) | |
| #define DWCMSHC_EMMC_DLL_TIMEOUT BIT(9) | |
| #define DWCMSHC_EMMC_DLL_START_POINT 16 | |
| #define DWCMSHC_EMMC_DLL_START_DEFAULT 5 | |
| #define DWCMSHC_EMMC_DLL_INC_VALUE 2 | |
| #define DWCMSHC_EMMC_DLL_INC 8 | |
| #define DWCMSHC_EMMC_DLL_BYPASS BIT(24) | |
| #define DWCMSHC_EMMC_DLL_DLYENA BIT(27) | |
| #define DLL_RXCLK_NO_INVERTER BIT(29) | |
| #define DLL_RXCLK_ORI_GATE BIT(31) | |
| #define DLL_TXCLK_TAPNUM_DEFAULT 0x10 | |
| #define DLL_TXCLK_TAPNUM_FROM_SW BIT(24) | |
| #define DLL_TXCLK_NO_INVERTER BIT(29) | |
| #define DLL_STRBIN_TAPNUM_DEFAULT 0x4 | |
| #define DLL_STRBIN_TAPNUM_FROM_SW BIT(24) | |
| #define DLL_STRBIN_DELAY_NUM_SEL BIT(26) | |
| #define DLL_STRBIN_DELAY_NUM_OFFSET 16 | |
| #define DLL_STRBIN_DELAY_NUM_DEFAULT 0x10 | |
| #define DLL_CMDOUT_TAPNUM_90_DEGREES 0x8 | |
| #define DLL_CMDOUT_TAPNUM_FROM_SW BIT(24) | |
| #define DLL_CMDOUT_SRC_CLK_NEG BIT(28) | |
| #define DLL_CMDOUT_EN_SRC_CLK_NEG BIT(29) | |
| #define DLL_CMDOUT_BOTH_CLK_EDGE BIT(30) | |
| #define DLL_TAPVALUE_FROM_SW BIT(25) | |
| #define DLL_TAP_VALUE_PREP(x) FIELD_PREP(GENMASK(15, 8), (x)) | |
| #define DLL_LOCK_VALUE_GET(x) FIELD_GET(GENMASK(7, 0), (x)) | |
| #define DLL_LOCK_WO_TMOUT(x) \ | |
| ((((x) & DWCMSHC_EMMC_DLL_LOCKED) == DWCMSHC_EMMC_DLL_LOCKED) && \ | |
| (((x) & DWCMSHC_EMMC_DLL_TIMEOUT) == 0)) | |
| #define ROCKCHIP_MAX_CLKS 3 | |
| #define FLAG_INVERTER_FLAG_IN_RXCLK BIT(0) | |
| #define FLAG_TAPVALUE_FROM_SW BIT(1) | |
| struct rockchip_sdhc_plat { | |
| struct mmc_config cfg; | |
| struct mmc mmc; | |
| }; | |
| struct rockchip_emmc_phy { | |
| u32 emmcphy_con[7]; | |
| u32 reserved; | |
| u32 emmcphy_status; | |
| }; | |
| struct rockchip_sdhc { | |
| struct sdhci_host host; | |
| struct udevice *dev; | |
| void *base; | |
| struct rockchip_emmc_phy *phy; | |
| struct clk emmc_clk; | |
| }; | |
| struct sdhci_data { | |
| int (*get_phy)(struct udevice *dev); | |
| /** | |
| * set_control_reg() - Set SDHCI control registers | |
| * | |
| * This is the set_control_reg() SDHCI operation that should be | |
| * used for the hardware this driver data is associated with. | |
| * Normally, this is used to set up control registers for | |
| * voltage level and UHS speed mode. | |
| * | |
| * @host: SDHCI host structure | |
| */ | |
| void (*set_control_reg)(struct sdhci_host *host); | |
| /** | |
| * set_ios_post() - Host specific hook after set_ios() calls | |
| * | |
| * This is the set_ios_post() SDHCI operation that should be | |
| * used for the hardware this driver data is associated with. | |
| * Normally, this is a hook that is called after sdhci_set_ios() | |
| * that does any necessary host-specific configuration. | |
| * | |
| * @host: SDHCI host structure | |
| * Return: 0 if successful, -ve on error | |
| */ | |
| int (*set_ios_post)(struct sdhci_host *host); | |
| void (*set_clock)(struct sdhci_host *host, u32 div); | |
| int (*config_dll)(struct sdhci_host *host, u32 clock, bool enable); | |
| /** | |
| * set_enhanced_strobe() - Set HS400 Enhanced Strobe config | |
| * | |
| * This is the set_enhanced_strobe() SDHCI operation that should | |
| * be used for the hardware this driver data is associated with. | |
| * Normally, this is used to set any host-specific configuration | |
| * necessary for HS400 ES. | |
| * | |
| * @host: SDHCI host structure | |
| * Return: 0 if successful, -ve on error | |
| */ | |
| int (*set_enhanced_strobe)(struct sdhci_host *host); | |
| u32 flags; | |
| u8 hs200_txclk_tapnum; | |
| u8 hs400_txclk_tapnum; | |
| u8 hs400_cmdout_tapnum; | |
| u8 hs400_strbin_tapnum; | |
| u8 ddr50_strbin_delay_num; | |
| }; | |
| static void rk3399_emmc_phy_power_on(struct rockchip_emmc_phy *phy, u32 clock) | |
| { | |
| u32 caldone, dllrdy, freqsel; | |
| writel(RK_CLRSETBITS(7 << 4, 0), &phy->emmcphy_con[6]); | |
| writel(RK_CLRSETBITS(1 << 11, 1 << 11), &phy->emmcphy_con[0]); | |
| writel(RK_CLRSETBITS(0xf << 7, 6 << 7), &phy->emmcphy_con[0]); | |
| /* | |
| * According to the user manual, calpad calibration | |
| * cycle takes more than 2us without the minimal recommended | |
| * value, so we may need a little margin here | |
| */ | |
| udelay(3); | |
| writel(RK_CLRSETBITS(1, 1), &phy->emmcphy_con[6]); | |
| /* | |
| * According to the user manual, it asks driver to | |
| * wait 5us for calpad busy trimming. But it seems that | |
| * 5us of caldone isn't enough for all cases. | |
| */ | |
| udelay(500); | |
| caldone = readl(&phy->emmcphy_status); | |
| caldone = (caldone >> PHYCTRL_CALDONE_SHIFT) & PHYCTRL_CALDONE_MASK; | |
| if (caldone != PHYCTRL_CALDONE_DONE) { | |
| printf("%s: caldone timeout.\n", __func__); | |
| return; | |
| } | |
| /* Set the frequency of the DLL operation */ | |
| if (clock < 75 * MHz) | |
| freqsel = PHYCTRL_FREQSEL_50M; | |
| else if (clock < 125 * MHz) | |
| freqsel = PHYCTRL_FREQSEL_100M; | |
| else if (clock < 175 * MHz) | |
| freqsel = PHYCTRL_FREQSEL_150M; | |
| else | |
| freqsel = PHYCTRL_FREQSEL_200M; | |
| /* Set the frequency of the DLL operation */ | |
| writel(RK_CLRSETBITS(3 << 12, freqsel << 12), &phy->emmcphy_con[0]); | |
| writel(RK_CLRSETBITS(1 << 1, 1 << 1), &phy->emmcphy_con[6]); | |
| /* REN Enable on STRB Line for HS400 */ | |
| writel(RK_CLRSETBITS(0, 1 << 9), &phy->emmcphy_con[2]); | |
| read_poll_timeout(readl, dllrdy, PHYCTRL_DLL_LOCK_WO_TMOUT(dllrdy), 1, | |
| 5000, &phy->emmcphy_status); | |
| } | |
| static void rk3399_emmc_phy_power_off(struct rockchip_emmc_phy *phy) | |
| { | |
| writel(RK_CLRSETBITS(1, 0), &phy->emmcphy_con[6]); | |
| writel(RK_CLRSETBITS(1 << 1, 0), &phy->emmcphy_con[6]); | |
| } | |
| static int rk3399_emmc_get_phy(struct udevice *dev) | |
| { | |
| struct rockchip_sdhc *priv = dev_get_priv(dev); | |
| ofnode phy_node; | |
| void *grf_base; | |
| u32 grf_phy_offset, phandle; | |
| phandle = dev_read_u32_default(dev, "phys", 0); | |
| phy_node = ofnode_get_by_phandle(phandle); | |
| if (!ofnode_valid(phy_node)) { | |
| debug("Not found emmc phy device\n"); | |
| return -ENODEV; | |
| } | |
| grf_base = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); | |
| if (IS_ERR_OR_NULL(grf_base)) { | |
| printf("%s: Get syscon grf failed\n", __func__); | |
| return -ENODEV; | |
| } | |
| grf_phy_offset = ofnode_read_u32_default(phy_node, "reg", 0); | |
| priv->phy = (struct rockchip_emmc_phy *)(grf_base + grf_phy_offset); | |
| return 0; | |
| } | |
| static int rk3399_sdhci_set_enhanced_strobe(struct sdhci_host *host) | |
| { | |
| struct mmc *mmc = host->mmc; | |
| u32 vendor; | |
| vendor = sdhci_readl(host, ARASAN_VENDOR_REGISTER); | |
| if (mmc->selected_mode == MMC_HS_400_ES) | |
| vendor |= ARASAN_VENDOR_ENHANCED_STROBE; | |
| else | |
| vendor &= ~ARASAN_VENDOR_ENHANCED_STROBE; | |
| sdhci_writel(host, vendor, ARASAN_VENDOR_REGISTER); | |
| return 0; | |
| } | |
| static void rk3399_sdhci_set_control_reg(struct sdhci_host *host) | |
| { | |
| struct rockchip_sdhc *priv = container_of(host, struct rockchip_sdhc, host); | |
| struct mmc *mmc = host->mmc; | |
| uint clock = mmc->tran_speed; | |
| int cycle_phy = host->clock != clock && clock > EMMC_MIN_FREQ; | |
| if (cycle_phy) | |
| rk3399_emmc_phy_power_off(priv->phy); | |
| sdhci_set_control_reg(host); | |
| /* | |
| * Reinitializing the device tries to set it to lower-speed modes | |
| * first, which fails if the Enhanced Strobe bit is set, making | |
| * the device impossible to use. Set the correct value here to | |
| * let reinitialization attempts succeed. | |
| */ | |
| if (CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT)) | |
| rk3399_sdhci_set_enhanced_strobe(host); | |
| }; | |
| static int rk3399_sdhci_set_ios_post(struct sdhci_host *host) | |
| { | |
| struct rockchip_sdhc *priv = container_of(host, struct rockchip_sdhc, host); | |
| struct mmc *mmc = host->mmc; | |
| uint clock = mmc->tran_speed; | |
| int cycle_phy = host->clock != clock && clock > EMMC_MIN_FREQ; | |
| if (!clock) | |
| clock = mmc->clock; | |
| if (cycle_phy) | |
| rk3399_emmc_phy_power_on(priv->phy, clock); | |
| return 0; | |
| } | |
| static void rk3568_sdhci_set_clock(struct sdhci_host *host, u32 div) | |
| { | |
| struct rockchip_sdhc *priv = container_of(host, struct rockchip_sdhc, host); | |
| struct mmc *mmc = host->mmc; | |
| ulong rate; | |
| rate = clk_set_rate(&priv->emmc_clk, mmc->clock); | |
| if (IS_ERR_VALUE(rate)) | |
| printf("%s: Set clock rate failed: %ld\n", __func__, (long)rate); | |
| } | |
| static int rk3568_sdhci_config_dll(struct sdhci_host *host, u32 clock, bool enable) | |
| { | |
| struct rockchip_sdhc *priv = container_of(host, struct rockchip_sdhc, host); | |
| struct sdhci_data *data = (struct sdhci_data *)dev_get_driver_data(priv->dev); | |
| struct mmc *mmc = host->mmc; | |
| int val, ret; | |
| u32 extra, txclk_tapnum, dll_tap_value; | |
| if (!enable) { | |
| sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_CTRL); | |
| return 0; | |
| } | |
| if (clock >= 100 * MHz) { | |
| /* reset DLL */ | |
| sdhci_writel(host, DWCMSHC_EMMC_DLL_CTRL_RESET, DWCMSHC_EMMC_DLL_CTRL); | |
| udelay(1); | |
| sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_CTRL); | |
| extra = 0x3 << EMMC_AT_CTRL_POST_CHANGE_DLY | | |
| 0x3 << EMMC_AT_CTRL_PRE_CHANGE_DLY | | |
| EMMC_AT_CTRL_TUNE_CLK_STOP_EN; | |
| sdhci_writel(host, extra, DWCMSHC_EMMC_AT_CTRL); | |
| /* Init DLL settings */ | |
| extra = DWCMSHC_EMMC_DLL_START_DEFAULT << DWCMSHC_EMMC_DLL_START_POINT | | |
| DWCMSHC_EMMC_DLL_INC_VALUE << DWCMSHC_EMMC_DLL_INC | | |
| DWCMSHC_EMMC_DLL_START; | |
| sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_CTRL); | |
| ret = read_poll_timeout(readl, val, DLL_LOCK_WO_TMOUT(val), 1, | |
| 500, | |
| host->ioaddr + DWCMSHC_EMMC_DLL_STATUS0); | |
| if (ret) | |
| return ret; | |
| if (data->flags & FLAG_TAPVALUE_FROM_SW) | |
| dll_tap_value = DLL_TAPVALUE_FROM_SW | | |
| DLL_TAP_VALUE_PREP(DLL_LOCK_VALUE_GET(val) * 2); | |
| else | |
| dll_tap_value = 0; | |
| extra = DWCMSHC_EMMC_DLL_DLYENA | | |
| DLL_RXCLK_ORI_GATE | | |
| dll_tap_value; | |
| if (data->flags & FLAG_INVERTER_FLAG_IN_RXCLK) | |
| extra |= DLL_RXCLK_NO_INVERTER; | |
| sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_RXCLK); | |
| txclk_tapnum = data->hs200_txclk_tapnum; | |
| if (mmc->selected_mode == MMC_HS_400 || | |
| mmc->selected_mode == MMC_HS_400_ES) { | |
| txclk_tapnum = data->hs400_txclk_tapnum; | |
| extra = DLL_CMDOUT_SRC_CLK_NEG | | |
| DLL_CMDOUT_BOTH_CLK_EDGE | | |
| DWCMSHC_EMMC_DLL_DLYENA | | |
| data->hs400_cmdout_tapnum | | |
| DLL_CMDOUT_TAPNUM_FROM_SW | | |
| dll_tap_value; | |
| sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_CMDOUT); | |
| } | |
| extra = DWCMSHC_EMMC_DLL_DLYENA | | |
| DLL_TXCLK_TAPNUM_FROM_SW | | |
| DLL_TXCLK_NO_INVERTER | | |
| txclk_tapnum | | |
| dll_tap_value; | |
| sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_TXCLK); | |
| extra = DWCMSHC_EMMC_DLL_DLYENA | | |
| data->hs400_strbin_tapnum | | |
| DLL_STRBIN_TAPNUM_FROM_SW | | |
| dll_tap_value; | |
| sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_STRBIN); | |
| } else { | |
| /* | |
| * Disable DLL and reset both of sample and drive clock. | |
| * The bypass bit and start bit need to be set if DLL is not locked. | |
| */ | |
| extra = DWCMSHC_EMMC_DLL_BYPASS | DWCMSHC_EMMC_DLL_START; | |
| sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_CTRL); | |
| sdhci_writel(host, DLL_RXCLK_ORI_GATE, DWCMSHC_EMMC_DLL_RXCLK); | |
| sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_TXCLK); | |
| sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_CMDOUT); | |
| /* | |
| * Before switching to hs400es mode, the driver will enable | |
| * enhanced strobe first. PHY needs to configure the parameters | |
| * of enhanced strobe first. | |
| */ | |
| extra = DWCMSHC_EMMC_DLL_DLYENA | | |
| DLL_STRBIN_DELAY_NUM_SEL | | |
| data->ddr50_strbin_delay_num << DLL_STRBIN_DELAY_NUM_OFFSET; | |
| sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_STRBIN); | |
| } | |
| return 0; | |
| } | |
| static int rk3568_sdhci_set_ios_post(struct sdhci_host *host) | |
| { | |
| struct mmc *mmc = host->mmc; | |
| struct rockchip_sdhc_plat *plat = dev_get_plat(mmc->dev); | |
| struct mmc_config *cfg = &plat->cfg; | |
| u32 reg; | |
| reg = sdhci_readw(host, SDHCI_HOST_CONTROL2); | |
| reg &= ~SDHCI_CTRL_UHS_MASK; | |
| switch (mmc->selected_mode) { | |
| case UHS_SDR25: | |
| case MMC_HS: | |
| case MMC_HS_52: | |
| reg |= SDHCI_CTRL_UHS_SDR25; | |
| break; | |
| case UHS_SDR50: | |
| reg |= SDHCI_CTRL_UHS_SDR50; | |
| break; | |
| case UHS_DDR50: | |
| case MMC_DDR_52: | |
| reg |= SDHCI_CTRL_UHS_DDR50; | |
| break; | |
| case UHS_SDR104: | |
| case MMC_HS_200: | |
| reg |= SDHCI_CTRL_UHS_SDR104; | |
| break; | |
| case MMC_HS_400: | |
| case MMC_HS_400_ES: | |
| reg |= DWCMSHC_CTRL_HS400; | |
| break; | |
| default: | |
| reg |= SDHCI_CTRL_UHS_SDR12; | |
| } | |
| sdhci_writew(host, reg, SDHCI_HOST_CONTROL2); | |
| reg = sdhci_readw(host, DWCMSHC_EMMC_EMMC_CTRL); | |
| if (IS_MMC(mmc)) | |
| reg |= DWCMSHC_CARD_IS_EMMC; | |
| else | |
| reg &= ~DWCMSHC_CARD_IS_EMMC; | |
| if (mmc->selected_mode == MMC_HS_400_ES) | |
| reg |= DWCMSHC_ENHANCED_STROBE; | |
| else | |
| reg &= ~DWCMSHC_ENHANCED_STROBE; | |
| sdhci_writew(host, reg, DWCMSHC_EMMC_EMMC_CTRL); | |
| /* | |
| * Reading more than 4 blocks with a single CMD18 command in PIO mode | |
| * triggers Data End Bit Error using a slower mode than HS200. Limit to | |
| * reading max 4 blocks in one command when using PIO mode. | |
| */ | |
| if (!(host->flags & USE_DMA)) { | |
| if (mmc->selected_mode == MMC_HS_200 || | |
| mmc->selected_mode == MMC_HS_400 || | |
| mmc->selected_mode == MMC_HS_400_ES) | |
| cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; | |
| else | |
| cfg->b_max = 4; | |
| } | |
| return 0; | |
| } | |
| static void rockchip_sdhci_set_control_reg(struct sdhci_host *host) | |
| { | |
| struct rockchip_sdhc *priv = container_of(host, struct rockchip_sdhc, host); | |
| struct sdhci_data *data = (struct sdhci_data *)dev_get_driver_data(priv->dev); | |
| if (data->set_control_reg) | |
| data->set_control_reg(host); | |
| } | |
| static int rockchip_sdhci_set_ios_post(struct sdhci_host *host) | |
| { | |
| struct rockchip_sdhc *priv = container_of(host, struct rockchip_sdhc, host); | |
| struct sdhci_data *data = (struct sdhci_data *)dev_get_driver_data(priv->dev); | |
| if (data->set_ios_post) | |
| return data->set_ios_post(host); | |
| return 0; | |
| } | |
| static void rockchip_sdhci_set_clock(struct sdhci_host *host, u32 div) | |
| { | |
| struct rockchip_sdhc *priv = container_of(host, struct rockchip_sdhc, host); | |
| struct sdhci_data *data = (struct sdhci_data *)dev_get_driver_data(priv->dev); | |
| if (data->set_clock) | |
| data->set_clock(host, div); | |
| } | |
| static int rockchip_sdhci_execute_tuning(struct mmc *mmc, u8 opcode) | |
| { | |
| struct rockchip_sdhc *priv = dev_get_priv(mmc->dev); | |
| struct sdhci_host *host = &priv->host; | |
| s8 tuning_loop_counter = SDHCI_TUNING_LOOP_COUNT; | |
| struct mmc_cmd cmd; | |
| u32 ctrl, blk_size; | |
| int ret; | |
| ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); | |
| ctrl |= SDHCI_CTRL_EXEC_TUNING; | |
| sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); | |
| sdhci_writel(host, SDHCI_INT_DATA_AVAIL, SDHCI_INT_ENABLE); | |
| blk_size = SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, 64); | |
| if (opcode == MMC_CMD_SEND_TUNING_BLOCK_HS200 && mmc->bus_width == 8) | |
| blk_size = SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, 128); | |
| sdhci_writew(host, blk_size, SDHCI_BLOCK_SIZE); | |
| sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE); | |
| cmd.cmdidx = opcode; | |
| cmd.resp_type = MMC_RSP_R1; | |
| cmd.cmdarg = 0; | |
| do { | |
| ret = mmc_send_cmd(mmc, &cmd, NULL); | |
| ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); | |
| if (ret || tuning_loop_counter-- == 0) | |
| break; | |
| } while (ctrl & SDHCI_CTRL_EXEC_TUNING); | |
| if (ret || tuning_loop_counter < 0 || !(ctrl & SDHCI_CTRL_TUNED_CLK)) { | |
| if (!ret) | |
| ret = -EIO; | |
| printf("%s: Tuning failed: %d\n", __func__, ret); | |
| ctrl &= ~SDHCI_CTRL_TUNED_CLK; | |
| ctrl &= ~SDHCI_CTRL_EXEC_TUNING; | |
| sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); | |
| } | |
| /* Enable only interrupts served by the SD controller */ | |
| sdhci_writel(host, SDHCI_INT_DATA_MASK | SDHCI_INT_CMD_MASK, SDHCI_INT_ENABLE); | |
| return ret; | |
| } | |
| static int rockchip_sdhci_config_dll(struct sdhci_host *host, u32 clock, bool enable) | |
| { | |
| struct rockchip_sdhc *priv = container_of(host, struct rockchip_sdhc, host); | |
| struct sdhci_data *data = (struct sdhci_data *)dev_get_driver_data(priv->dev); | |
| if (data->config_dll) | |
| return data->config_dll(host, clock, enable); | |
| return 0; | |
| } | |
| static int rockchip_sdhci_set_enhanced_strobe(struct sdhci_host *host) | |
| { | |
| struct rockchip_sdhc *priv = container_of(host, struct rockchip_sdhc, host); | |
| struct sdhci_data *data = (struct sdhci_data *)dev_get_driver_data(priv->dev); | |
| if (data->set_enhanced_strobe) | |
| return data->set_enhanced_strobe(host); | |
| return 0; | |
| } | |
| static struct sdhci_ops rockchip_sdhci_ops = { | |
| .set_control_reg = rockchip_sdhci_set_control_reg, | |
| .set_ios_post = rockchip_sdhci_set_ios_post, | |
| .set_clock = rockchip_sdhci_set_clock, | |
| .platform_execute_tuning = rockchip_sdhci_execute_tuning, | |
| .config_dll = rockchip_sdhci_config_dll, | |
| .set_enhanced_strobe = rockchip_sdhci_set_enhanced_strobe, | |
| }; | |
| static int rockchip_sdhci_probe(struct udevice *dev) | |
| { | |
| struct sdhci_data *data = (struct sdhci_data *)dev_get_driver_data(dev); | |
| struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); | |
| struct rockchip_sdhc_plat *plat = dev_get_plat(dev); | |
| struct rockchip_sdhc *priv = dev_get_priv(dev); | |
| struct mmc_config *cfg = &plat->cfg; | |
| struct sdhci_host *host = &priv->host; | |
| struct clk *clk = &priv->emmc_clk; | |
| int ret; | |
| host->max_clk = cfg->f_max; | |
| ret = clk_get_by_index(dev, 0, clk); | |
| if (!ret) { | |
| ret = clk_set_rate(clk, host->max_clk); | |
| if (IS_ERR_VALUE(ret)) | |
| printf("%s clk set rate fail!\n", __func__); | |
| } else if (ret != -ENOSYS) { | |
| printf("%s fail to get clk\n", __func__); | |
| } | |
| priv->dev = dev; | |
| if (data->get_phy) { | |
| ret = data->get_phy(dev); | |
| if (ret) | |
| return ret; | |
| } | |
| host->ops = &rockchip_sdhci_ops; | |
| host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD; | |
| host->mmc = &plat->mmc; | |
| host->mmc->priv = &priv->host; | |
| host->mmc->dev = dev; | |
| upriv->mmc = host->mmc; | |
| ret = sdhci_setup_cfg(cfg, host, cfg->f_max, EMMC_MIN_FREQ); | |
| if (ret) | |
| return ret; | |
| /* | |
| * Disable use of DMA and force use of PIO mode in SPL to fix an issue | |
| * where loading part of TF-A into SRAM using DMA silently fails. | |
| */ | |
| if (IS_ENABLED(CONFIG_XPL_BUILD) && | |
| dev_read_bool(dev, "u-boot,spl-fifo-mode")) | |
| host->flags &= ~USE_DMA; | |
| return sdhci_probe(dev); | |
| } | |
| static int rockchip_sdhci_of_to_plat(struct udevice *dev) | |
| { | |
| struct rockchip_sdhc_plat *plat = dev_get_plat(dev); | |
| struct rockchip_sdhc *priv = dev_get_priv(dev); | |
| struct mmc_config *cfg = &plat->cfg; | |
| struct sdhci_host *host = &priv->host; | |
| int ret; | |
| host->name = dev->name; | |
| host->ioaddr = dev_read_addr_ptr(dev); | |
| ret = mmc_of_parse(dev, cfg); | |
| if (ret) | |
| return ret; | |
| return 0; | |
| } | |
| static int rockchip_sdhci_bind(struct udevice *dev) | |
| { | |
| struct rockchip_sdhc_plat *plat = dev_get_plat(dev); | |
| return sdhci_bind(dev, &plat->mmc, &plat->cfg); | |
| } | |
| static const struct sdhci_data rk3399_data = { | |
| .get_phy = rk3399_emmc_get_phy, | |
| .set_control_reg = rk3399_sdhci_set_control_reg, | |
| .set_ios_post = rk3399_sdhci_set_ios_post, | |
| .set_enhanced_strobe = rk3399_sdhci_set_enhanced_strobe, | |
| }; | |
| static const struct sdhci_data rk3528_data = { | |
| .set_ios_post = rk3568_sdhci_set_ios_post, | |
| .set_clock = rk3568_sdhci_set_clock, | |
| .config_dll = rk3568_sdhci_config_dll, | |
| .flags = FLAG_TAPVALUE_FROM_SW, | |
| .hs200_txclk_tapnum = 0xc, | |
| .hs400_txclk_tapnum = 0x6, | |
| .hs400_cmdout_tapnum = 0x6, | |
| .hs400_strbin_tapnum = 0x3, | |
| .ddr50_strbin_delay_num = 0xa, | |
| }; | |
| static const struct sdhci_data rk3568_data = { | |
| .set_ios_post = rk3568_sdhci_set_ios_post, | |
| .set_clock = rk3568_sdhci_set_clock, | |
| .config_dll = rk3568_sdhci_config_dll, | |
| .flags = FLAG_INVERTER_FLAG_IN_RXCLK, | |
| .hs200_txclk_tapnum = DLL_TXCLK_TAPNUM_DEFAULT, | |
| .hs400_txclk_tapnum = 0x8, | |
| .hs400_cmdout_tapnum = DLL_CMDOUT_TAPNUM_90_DEGREES, | |
| .hs400_strbin_tapnum = DLL_STRBIN_TAPNUM_DEFAULT, | |
| .ddr50_strbin_delay_num = DLL_STRBIN_DELAY_NUM_DEFAULT, | |
| }; | |
| static const struct sdhci_data rk3576_data = { | |
| .set_ios_post = rk3568_sdhci_set_ios_post, | |
| .set_clock = rk3568_sdhci_set_clock, | |
| .config_dll = rk3568_sdhci_config_dll, | |
| .hs200_txclk_tapnum = DLL_TXCLK_TAPNUM_DEFAULT, | |
| .hs400_txclk_tapnum = 0x7, | |
| .hs400_cmdout_tapnum = 0x7, | |
| .hs400_strbin_tapnum = 0x5, | |
| .ddr50_strbin_delay_num = 0xa, | |
| }; | |
| static const struct sdhci_data rk3588_data = { | |
| .set_ios_post = rk3568_sdhci_set_ios_post, | |
| .set_clock = rk3568_sdhci_set_clock, | |
| .config_dll = rk3568_sdhci_config_dll, | |
| .hs200_txclk_tapnum = DLL_TXCLK_TAPNUM_DEFAULT, | |
| .hs400_txclk_tapnum = 0x9, | |
| .hs400_cmdout_tapnum = DLL_CMDOUT_TAPNUM_90_DEGREES, | |
| .hs400_strbin_tapnum = DLL_STRBIN_TAPNUM_DEFAULT, | |
| .ddr50_strbin_delay_num = DLL_STRBIN_DELAY_NUM_DEFAULT, | |
| }; | |
| static const struct udevice_id sdhci_ids[] = { | |
| { | |
| .compatible = "arasan,sdhci-5.1", | |
| .data = (ulong)&rk3399_data, | |
| }, | |
| { | |
| .compatible = "rockchip,rk3528-dwcmshc", | |
| .data = (ulong)&rk3528_data, | |
| }, | |
| { | |
| .compatible = "rockchip,rk3568-dwcmshc", | |
| .data = (ulong)&rk3568_data, | |
| }, | |
| { | |
| .compatible = "rockchip,rk3576-dwcmshc", | |
| .data = (ulong)&rk3576_data, | |
| }, | |
| { | |
| .compatible = "rockchip,rk3588-dwcmshc", | |
| .data = (ulong)&rk3588_data, | |
| }, | |
| { } | |
| }; | |
| U_BOOT_DRIVER(rockchip_sdhci_5_1_drv) = { | |
| .name = "rockchip_sdhci_5_1", | |
| .id = UCLASS_MMC, | |
| .of_match = sdhci_ids, | |
| .of_to_plat = rockchip_sdhci_of_to_plat, | |
| .ops = &sdhci_ops, | |
| .bind = rockchip_sdhci_bind, | |
| .probe = rockchip_sdhci_probe, | |
| .priv_auto = sizeof(struct rockchip_sdhc), | |
| .plat_auto = sizeof(struct rockchip_sdhc_plat), | |
| }; |