| // 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/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_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_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) |
| |
| 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; |
| }; |
| |
| 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; |
| |
| 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); |
| |
| /* 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; |
| |
| extra = DWCMSHC_EMMC_DLL_DLYENA | DLL_RXCLK_ORI_GATE; |
| 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 | |
| DLL_CMDOUT_TAPNUM_90_DEGREES | |
| DLL_CMDOUT_TAPNUM_FROM_SW; |
| sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_CMDOUT); |
| } |
| |
| extra = DWCMSHC_EMMC_DLL_DLYENA | |
| DLL_TXCLK_TAPNUM_FROM_SW | |
| DLL_TXCLK_NO_INVERTER | |
| txclk_tapnum; |
| sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_TXCLK); |
| |
| extra = DWCMSHC_EMMC_DLL_DLYENA | |
| DLL_STRBIN_TAPNUM_DEFAULT | |
| DLL_STRBIN_TAPNUM_FROM_SW; |
| 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 | |
| DLL_STRBIN_DELAY_NUM_DEFAULT << 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; |
| char 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 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, |
| }; |
| |
| 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, |
| }; |
| |
| static const struct udevice_id sdhci_ids[] = { |
| { |
| .compatible = "arasan,sdhci-5.1", |
| .data = (ulong)&rk3399_data, |
| }, |
| { |
| .compatible = "rockchip,rk3568-dwcmshc", |
| .data = (ulong)&rk3568_data, |
| }, |
| { |
| .compatible = "rockchip,rk3588-dwcmshc", |
| .data = (ulong)&rk3588_data, |
| }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(arasan_sdhci_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), |
| }; |