162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2016 Socionext Inc. 462306a36Sopenharmony_ci * Author: Masahiro Yamada <yamada.masahiro@socionext.com> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/bitfield.h> 862306a36Sopenharmony_ci#include <linux/bits.h> 962306a36Sopenharmony_ci#include <linux/iopoll.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/mmc/host.h> 1262306a36Sopenharmony_ci#include <linux/mmc/mmc.h> 1362306a36Sopenharmony_ci#include <linux/of.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/reset.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include "sdhci-pltfm.h" 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* HRS - Host Register Set (specific to Cadence) */ 2062306a36Sopenharmony_ci#define SDHCI_CDNS_HRS04 0x10 /* PHY access port */ 2162306a36Sopenharmony_ci#define SDHCI_CDNS_HRS04_ACK BIT(26) 2262306a36Sopenharmony_ci#define SDHCI_CDNS_HRS04_RD BIT(25) 2362306a36Sopenharmony_ci#define SDHCI_CDNS_HRS04_WR BIT(24) 2462306a36Sopenharmony_ci#define SDHCI_CDNS_HRS04_RDATA GENMASK(23, 16) 2562306a36Sopenharmony_ci#define SDHCI_CDNS_HRS04_WDATA GENMASK(15, 8) 2662306a36Sopenharmony_ci#define SDHCI_CDNS_HRS04_ADDR GENMASK(5, 0) 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define SDHCI_CDNS_HRS06 0x18 /* eMMC control */ 2962306a36Sopenharmony_ci#define SDHCI_CDNS_HRS06_TUNE_UP BIT(15) 3062306a36Sopenharmony_ci#define SDHCI_CDNS_HRS06_TUNE GENMASK(13, 8) 3162306a36Sopenharmony_ci#define SDHCI_CDNS_HRS06_MODE GENMASK(2, 0) 3262306a36Sopenharmony_ci#define SDHCI_CDNS_HRS06_MODE_SD 0x0 3362306a36Sopenharmony_ci#define SDHCI_CDNS_HRS06_MODE_MMC_SDR 0x2 3462306a36Sopenharmony_ci#define SDHCI_CDNS_HRS06_MODE_MMC_DDR 0x3 3562306a36Sopenharmony_ci#define SDHCI_CDNS_HRS06_MODE_MMC_HS200 0x4 3662306a36Sopenharmony_ci#define SDHCI_CDNS_HRS06_MODE_MMC_HS400 0x5 3762306a36Sopenharmony_ci#define SDHCI_CDNS_HRS06_MODE_MMC_HS400ES 0x6 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci/* SRS - Slot Register Set (SDHCI-compatible) */ 4062306a36Sopenharmony_ci#define SDHCI_CDNS_SRS_BASE 0x200 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci/* PHY */ 4362306a36Sopenharmony_ci#define SDHCI_CDNS_PHY_DLY_SD_HS 0x00 4462306a36Sopenharmony_ci#define SDHCI_CDNS_PHY_DLY_SD_DEFAULT 0x01 4562306a36Sopenharmony_ci#define SDHCI_CDNS_PHY_DLY_UHS_SDR12 0x02 4662306a36Sopenharmony_ci#define SDHCI_CDNS_PHY_DLY_UHS_SDR25 0x03 4762306a36Sopenharmony_ci#define SDHCI_CDNS_PHY_DLY_UHS_SDR50 0x04 4862306a36Sopenharmony_ci#define SDHCI_CDNS_PHY_DLY_UHS_DDR50 0x05 4962306a36Sopenharmony_ci#define SDHCI_CDNS_PHY_DLY_EMMC_LEGACY 0x06 5062306a36Sopenharmony_ci#define SDHCI_CDNS_PHY_DLY_EMMC_SDR 0x07 5162306a36Sopenharmony_ci#define SDHCI_CDNS_PHY_DLY_EMMC_DDR 0x08 5262306a36Sopenharmony_ci#define SDHCI_CDNS_PHY_DLY_SDCLK 0x0b 5362306a36Sopenharmony_ci#define SDHCI_CDNS_PHY_DLY_HSMMC 0x0c 5462306a36Sopenharmony_ci#define SDHCI_CDNS_PHY_DLY_STROBE 0x0d 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci/* 5762306a36Sopenharmony_ci * The tuned val register is 6 bit-wide, but not the whole of the range is 5862306a36Sopenharmony_ci * available. The range 0-42 seems to be available (then 43 wraps around to 0) 5962306a36Sopenharmony_ci * but I am not quite sure if it is official. Use only 0 to 39 for safety. 6062306a36Sopenharmony_ci */ 6162306a36Sopenharmony_ci#define SDHCI_CDNS_MAX_TUNING_LOOP 40 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistruct sdhci_cdns_phy_param { 6462306a36Sopenharmony_ci u8 addr; 6562306a36Sopenharmony_ci u8 data; 6662306a36Sopenharmony_ci}; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistruct sdhci_cdns_priv { 6962306a36Sopenharmony_ci void __iomem *hrs_addr; 7062306a36Sopenharmony_ci void __iomem *ctl_addr; /* write control */ 7162306a36Sopenharmony_ci spinlock_t wrlock; /* write lock */ 7262306a36Sopenharmony_ci bool enhanced_strobe; 7362306a36Sopenharmony_ci void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg); 7462306a36Sopenharmony_ci struct reset_control *rst_hw; 7562306a36Sopenharmony_ci unsigned int nr_phy_params; 7662306a36Sopenharmony_ci struct sdhci_cdns_phy_param phy_params[]; 7762306a36Sopenharmony_ci}; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistruct sdhci_cdns_phy_cfg { 8062306a36Sopenharmony_ci const char *property; 8162306a36Sopenharmony_ci u8 addr; 8262306a36Sopenharmony_ci}; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistruct sdhci_cdns_drv_data { 8562306a36Sopenharmony_ci int (*init)(struct platform_device *pdev); 8662306a36Sopenharmony_ci const struct sdhci_pltfm_data pltfm_data; 8762306a36Sopenharmony_ci}; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = { 9062306a36Sopenharmony_ci { "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, }, 9162306a36Sopenharmony_ci { "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, }, 9262306a36Sopenharmony_ci { "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, }, 9362306a36Sopenharmony_ci { "cdns,phy-input-delay-sd-uhs-sdr25", SDHCI_CDNS_PHY_DLY_UHS_SDR25, }, 9462306a36Sopenharmony_ci { "cdns,phy-input-delay-sd-uhs-sdr50", SDHCI_CDNS_PHY_DLY_UHS_SDR50, }, 9562306a36Sopenharmony_ci { "cdns,phy-input-delay-sd-uhs-ddr50", SDHCI_CDNS_PHY_DLY_UHS_DDR50, }, 9662306a36Sopenharmony_ci { "cdns,phy-input-delay-mmc-highspeed", SDHCI_CDNS_PHY_DLY_EMMC_SDR, }, 9762306a36Sopenharmony_ci { "cdns,phy-input-delay-mmc-ddr", SDHCI_CDNS_PHY_DLY_EMMC_DDR, }, 9862306a36Sopenharmony_ci { "cdns,phy-dll-delay-sdclk", SDHCI_CDNS_PHY_DLY_SDCLK, }, 9962306a36Sopenharmony_ci { "cdns,phy-dll-delay-sdclk-hsmmc", SDHCI_CDNS_PHY_DLY_HSMMC, }, 10062306a36Sopenharmony_ci { "cdns,phy-dll-delay-strobe", SDHCI_CDNS_PHY_DLY_STROBE, }, 10162306a36Sopenharmony_ci}; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic inline void cdns_writel(struct sdhci_cdns_priv *priv, u32 val, 10462306a36Sopenharmony_ci void __iomem *reg) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci writel(val, reg); 10762306a36Sopenharmony_ci} 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, 11062306a36Sopenharmony_ci u8 addr, u8 data) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04; 11362306a36Sopenharmony_ci u32 tmp; 11462306a36Sopenharmony_ci int ret; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci ret = readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS04_ACK), 11762306a36Sopenharmony_ci 0, 10); 11862306a36Sopenharmony_ci if (ret) 11962306a36Sopenharmony_ci return ret; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci tmp = FIELD_PREP(SDHCI_CDNS_HRS04_WDATA, data) | 12262306a36Sopenharmony_ci FIELD_PREP(SDHCI_CDNS_HRS04_ADDR, addr); 12362306a36Sopenharmony_ci priv->priv_writel(priv, tmp, reg); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci tmp |= SDHCI_CDNS_HRS04_WR; 12662306a36Sopenharmony_ci priv->priv_writel(priv, tmp, reg); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci ret = readl_poll_timeout(reg, tmp, tmp & SDHCI_CDNS_HRS04_ACK, 0, 10); 12962306a36Sopenharmony_ci if (ret) 13062306a36Sopenharmony_ci return ret; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci tmp &= ~SDHCI_CDNS_HRS04_WR; 13362306a36Sopenharmony_ci priv->priv_writel(priv, tmp, reg); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci ret = readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS04_ACK), 13662306a36Sopenharmony_ci 0, 10); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci return ret; 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistatic unsigned int sdhci_cdns_phy_param_count(struct device_node *np) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci unsigned int count = 0; 14462306a36Sopenharmony_ci int i; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) 14762306a36Sopenharmony_ci if (of_property_read_bool(np, sdhci_cdns_phy_cfgs[i].property)) 14862306a36Sopenharmony_ci count++; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci return count; 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistatic void sdhci_cdns_phy_param_parse(struct device_node *np, 15462306a36Sopenharmony_ci struct sdhci_cdns_priv *priv) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci struct sdhci_cdns_phy_param *p = priv->phy_params; 15762306a36Sopenharmony_ci u32 val; 15862306a36Sopenharmony_ci int ret, i; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) { 16162306a36Sopenharmony_ci ret = of_property_read_u32(np, sdhci_cdns_phy_cfgs[i].property, 16262306a36Sopenharmony_ci &val); 16362306a36Sopenharmony_ci if (ret) 16462306a36Sopenharmony_ci continue; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci p->addr = sdhci_cdns_phy_cfgs[i].addr; 16762306a36Sopenharmony_ci p->data = val; 16862306a36Sopenharmony_ci p++; 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_cistatic int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv) 17362306a36Sopenharmony_ci{ 17462306a36Sopenharmony_ci int ret, i; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci for (i = 0; i < priv->nr_phy_params; i++) { 17762306a36Sopenharmony_ci ret = sdhci_cdns_write_phy_reg(priv, priv->phy_params[i].addr, 17862306a36Sopenharmony_ci priv->phy_params[i].data); 17962306a36Sopenharmony_ci if (ret) 18062306a36Sopenharmony_ci return ret; 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci return 0; 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_cistatic void *sdhci_cdns_priv(struct sdhci_host *host) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci return sdhci_pltfm_priv(pltfm_host); 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci /* 19662306a36Sopenharmony_ci * Cadence's spec says the Timeout Clock Frequency is the same as the 19762306a36Sopenharmony_ci * Base Clock Frequency. 19862306a36Sopenharmony_ci */ 19962306a36Sopenharmony_ci return host->max_clk; 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cistatic void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_priv *priv, u32 mode) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci u32 tmp; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci /* The speed mode for eMMC is selected by HRS06 register */ 20762306a36Sopenharmony_ci tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); 20862306a36Sopenharmony_ci tmp &= ~SDHCI_CDNS_HRS06_MODE; 20962306a36Sopenharmony_ci tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_MODE, mode); 21062306a36Sopenharmony_ci priv->priv_writel(priv, tmp, priv->hrs_addr + SDHCI_CDNS_HRS06); 21162306a36Sopenharmony_ci} 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_cistatic u32 sdhci_cdns_get_emmc_mode(struct sdhci_cdns_priv *priv) 21462306a36Sopenharmony_ci{ 21562306a36Sopenharmony_ci u32 tmp; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); 21862306a36Sopenharmony_ci return FIELD_GET(SDHCI_CDNS_HRS06_MODE, tmp); 21962306a36Sopenharmony_ci} 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistatic int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); 22462306a36Sopenharmony_ci void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06; 22562306a36Sopenharmony_ci u32 tmp; 22662306a36Sopenharmony_ci int i, ret; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val))) 22962306a36Sopenharmony_ci return -EINVAL; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci tmp = readl(reg); 23262306a36Sopenharmony_ci tmp &= ~SDHCI_CDNS_HRS06_TUNE; 23362306a36Sopenharmony_ci tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_TUNE, val); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci /* 23662306a36Sopenharmony_ci * Workaround for IP errata: 23762306a36Sopenharmony_ci * The IP6116 SD/eMMC PHY design has a timing issue on receive data 23862306a36Sopenharmony_ci * path. Send tune request twice. 23962306a36Sopenharmony_ci */ 24062306a36Sopenharmony_ci for (i = 0; i < 2; i++) { 24162306a36Sopenharmony_ci tmp |= SDHCI_CDNS_HRS06_TUNE_UP; 24262306a36Sopenharmony_ci priv->priv_writel(priv, tmp, reg); 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci ret = readl_poll_timeout(reg, tmp, 24562306a36Sopenharmony_ci !(tmp & SDHCI_CDNS_HRS06_TUNE_UP), 24662306a36Sopenharmony_ci 0, 1); 24762306a36Sopenharmony_ci if (ret) 24862306a36Sopenharmony_ci return ret; 24962306a36Sopenharmony_ci } 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci return 0; 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci/* 25562306a36Sopenharmony_ci * In SD mode, software must not use the hardware tuning and instead perform 25662306a36Sopenharmony_ci * an almost identical procedure to eMMC. 25762306a36Sopenharmony_ci */ 25862306a36Sopenharmony_cistatic int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode) 25962306a36Sopenharmony_ci{ 26062306a36Sopenharmony_ci int cur_streak = 0; 26162306a36Sopenharmony_ci int max_streak = 0; 26262306a36Sopenharmony_ci int end_of_streak = 0; 26362306a36Sopenharmony_ci int i; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci /* 26662306a36Sopenharmony_ci * Do not execute tuning for UHS_SDR50 or UHS_DDR50. 26762306a36Sopenharmony_ci * The delay is set by probe, based on the DT properties. 26862306a36Sopenharmony_ci */ 26962306a36Sopenharmony_ci if (host->timing != MMC_TIMING_MMC_HS200 && 27062306a36Sopenharmony_ci host->timing != MMC_TIMING_UHS_SDR104) 27162306a36Sopenharmony_ci return 0; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) { 27462306a36Sopenharmony_ci if (sdhci_cdns_set_tune_val(host, i) || 27562306a36Sopenharmony_ci mmc_send_tuning(host->mmc, opcode, NULL)) { /* bad */ 27662306a36Sopenharmony_ci cur_streak = 0; 27762306a36Sopenharmony_ci } else { /* good */ 27862306a36Sopenharmony_ci cur_streak++; 27962306a36Sopenharmony_ci if (cur_streak > max_streak) { 28062306a36Sopenharmony_ci max_streak = cur_streak; 28162306a36Sopenharmony_ci end_of_streak = i; 28262306a36Sopenharmony_ci } 28362306a36Sopenharmony_ci } 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci if (!max_streak) { 28762306a36Sopenharmony_ci dev_err(mmc_dev(host->mmc), "no tuning point found\n"); 28862306a36Sopenharmony_ci return -EIO; 28962306a36Sopenharmony_ci } 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci return sdhci_cdns_set_tune_val(host, end_of_streak - max_streak / 2); 29262306a36Sopenharmony_ci} 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_cistatic void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host, 29562306a36Sopenharmony_ci unsigned int timing) 29662306a36Sopenharmony_ci{ 29762306a36Sopenharmony_ci struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); 29862306a36Sopenharmony_ci u32 mode; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci switch (timing) { 30162306a36Sopenharmony_ci case MMC_TIMING_MMC_HS: 30262306a36Sopenharmony_ci mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR; 30362306a36Sopenharmony_ci break; 30462306a36Sopenharmony_ci case MMC_TIMING_MMC_DDR52: 30562306a36Sopenharmony_ci mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR; 30662306a36Sopenharmony_ci break; 30762306a36Sopenharmony_ci case MMC_TIMING_MMC_HS200: 30862306a36Sopenharmony_ci mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200; 30962306a36Sopenharmony_ci break; 31062306a36Sopenharmony_ci case MMC_TIMING_MMC_HS400: 31162306a36Sopenharmony_ci if (priv->enhanced_strobe) 31262306a36Sopenharmony_ci mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400ES; 31362306a36Sopenharmony_ci else 31462306a36Sopenharmony_ci mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400; 31562306a36Sopenharmony_ci break; 31662306a36Sopenharmony_ci default: 31762306a36Sopenharmony_ci mode = SDHCI_CDNS_HRS06_MODE_SD; 31862306a36Sopenharmony_ci break; 31962306a36Sopenharmony_ci } 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci sdhci_cdns_set_emmc_mode(priv, mode); 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci /* For SD, fall back to the default handler */ 32462306a36Sopenharmony_ci if (mode == SDHCI_CDNS_HRS06_MODE_SD) 32562306a36Sopenharmony_ci sdhci_set_uhs_signaling(host, timing); 32662306a36Sopenharmony_ci} 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci/* Elba control register bits [6:3] are byte-lane enables */ 32962306a36Sopenharmony_ci#define ELBA_BYTE_ENABLE_MASK(x) ((x) << 3) 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci/* 33262306a36Sopenharmony_ci * The Pensando Elba SoC explicitly controls byte-lane enabling on writes 33362306a36Sopenharmony_ci * which includes writes to the HRS registers. The write lock (wrlock) 33462306a36Sopenharmony_ci * is used to ensure byte-lane enable, using write control (ctl_addr), 33562306a36Sopenharmony_ci * occurs before the data write. 33662306a36Sopenharmony_ci */ 33762306a36Sopenharmony_cistatic void elba_priv_writel(struct sdhci_cdns_priv *priv, u32 val, 33862306a36Sopenharmony_ci void __iomem *reg) 33962306a36Sopenharmony_ci{ 34062306a36Sopenharmony_ci unsigned long flags; 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci spin_lock_irqsave(&priv->wrlock, flags); 34362306a36Sopenharmony_ci writel(GENMASK(7, 3), priv->ctl_addr); 34462306a36Sopenharmony_ci writel(val, reg); 34562306a36Sopenharmony_ci spin_unlock_irqrestore(&priv->wrlock, flags); 34662306a36Sopenharmony_ci} 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_cistatic void elba_write_l(struct sdhci_host *host, u32 val, int reg) 34962306a36Sopenharmony_ci{ 35062306a36Sopenharmony_ci elba_priv_writel(sdhci_cdns_priv(host), val, host->ioaddr + reg); 35162306a36Sopenharmony_ci} 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_cistatic void elba_write_w(struct sdhci_host *host, u16 val, int reg) 35462306a36Sopenharmony_ci{ 35562306a36Sopenharmony_ci struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); 35662306a36Sopenharmony_ci u32 shift = reg & GENMASK(1, 0); 35762306a36Sopenharmony_ci unsigned long flags; 35862306a36Sopenharmony_ci u32 byte_enables; 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci byte_enables = GENMASK(1, 0) << shift; 36162306a36Sopenharmony_ci spin_lock_irqsave(&priv->wrlock, flags); 36262306a36Sopenharmony_ci writel(ELBA_BYTE_ENABLE_MASK(byte_enables), priv->ctl_addr); 36362306a36Sopenharmony_ci writew(val, host->ioaddr + reg); 36462306a36Sopenharmony_ci spin_unlock_irqrestore(&priv->wrlock, flags); 36562306a36Sopenharmony_ci} 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_cistatic void elba_write_b(struct sdhci_host *host, u8 val, int reg) 36862306a36Sopenharmony_ci{ 36962306a36Sopenharmony_ci struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); 37062306a36Sopenharmony_ci u32 shift = reg & GENMASK(1, 0); 37162306a36Sopenharmony_ci unsigned long flags; 37262306a36Sopenharmony_ci u32 byte_enables; 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci byte_enables = BIT(0) << shift; 37562306a36Sopenharmony_ci spin_lock_irqsave(&priv->wrlock, flags); 37662306a36Sopenharmony_ci writel(ELBA_BYTE_ENABLE_MASK(byte_enables), priv->ctl_addr); 37762306a36Sopenharmony_ci writeb(val, host->ioaddr + reg); 37862306a36Sopenharmony_ci spin_unlock_irqrestore(&priv->wrlock, flags); 37962306a36Sopenharmony_ci} 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_cistatic const struct sdhci_ops sdhci_elba_ops = { 38262306a36Sopenharmony_ci .write_l = elba_write_l, 38362306a36Sopenharmony_ci .write_w = elba_write_w, 38462306a36Sopenharmony_ci .write_b = elba_write_b, 38562306a36Sopenharmony_ci .set_clock = sdhci_set_clock, 38662306a36Sopenharmony_ci .get_timeout_clock = sdhci_cdns_get_timeout_clock, 38762306a36Sopenharmony_ci .set_bus_width = sdhci_set_bus_width, 38862306a36Sopenharmony_ci .reset = sdhci_reset, 38962306a36Sopenharmony_ci .set_uhs_signaling = sdhci_cdns_set_uhs_signaling, 39062306a36Sopenharmony_ci}; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_cistatic int elba_drv_init(struct platform_device *pdev) 39362306a36Sopenharmony_ci{ 39462306a36Sopenharmony_ci struct sdhci_host *host = platform_get_drvdata(pdev); 39562306a36Sopenharmony_ci struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); 39662306a36Sopenharmony_ci void __iomem *ioaddr; 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci host->mmc->caps |= MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA; 39962306a36Sopenharmony_ci spin_lock_init(&priv->wrlock); 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci /* Byte-lane control register */ 40262306a36Sopenharmony_ci ioaddr = devm_platform_ioremap_resource(pdev, 1); 40362306a36Sopenharmony_ci if (IS_ERR(ioaddr)) 40462306a36Sopenharmony_ci return PTR_ERR(ioaddr); 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci priv->ctl_addr = ioaddr; 40762306a36Sopenharmony_ci priv->priv_writel = elba_priv_writel; 40862306a36Sopenharmony_ci writel(ELBA_BYTE_ENABLE_MASK(0xf), priv->ctl_addr); 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci return 0; 41162306a36Sopenharmony_ci} 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_cistatic const struct sdhci_ops sdhci_cdns_ops = { 41462306a36Sopenharmony_ci .set_clock = sdhci_set_clock, 41562306a36Sopenharmony_ci .get_timeout_clock = sdhci_cdns_get_timeout_clock, 41662306a36Sopenharmony_ci .set_bus_width = sdhci_set_bus_width, 41762306a36Sopenharmony_ci .reset = sdhci_reset, 41862306a36Sopenharmony_ci .platform_execute_tuning = sdhci_cdns_execute_tuning, 41962306a36Sopenharmony_ci .set_uhs_signaling = sdhci_cdns_set_uhs_signaling, 42062306a36Sopenharmony_ci}; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_cistatic const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = { 42362306a36Sopenharmony_ci .pltfm_data = { 42462306a36Sopenharmony_ci .ops = &sdhci_cdns_ops, 42562306a36Sopenharmony_ci .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, 42662306a36Sopenharmony_ci }, 42762306a36Sopenharmony_ci}; 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_cistatic const struct sdhci_cdns_drv_data sdhci_elba_drv_data = { 43062306a36Sopenharmony_ci .init = elba_drv_init, 43162306a36Sopenharmony_ci .pltfm_data = { 43262306a36Sopenharmony_ci .ops = &sdhci_elba_ops, 43362306a36Sopenharmony_ci }, 43462306a36Sopenharmony_ci}; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_cistatic const struct sdhci_cdns_drv_data sdhci_cdns_drv_data = { 43762306a36Sopenharmony_ci .pltfm_data = { 43862306a36Sopenharmony_ci .ops = &sdhci_cdns_ops, 43962306a36Sopenharmony_ci }, 44062306a36Sopenharmony_ci}; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_cistatic void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc, 44362306a36Sopenharmony_ci struct mmc_ios *ios) 44462306a36Sopenharmony_ci{ 44562306a36Sopenharmony_ci struct sdhci_host *host = mmc_priv(mmc); 44662306a36Sopenharmony_ci struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); 44762306a36Sopenharmony_ci u32 mode; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci priv->enhanced_strobe = ios->enhanced_strobe; 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci mode = sdhci_cdns_get_emmc_mode(priv); 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci if (mode == SDHCI_CDNS_HRS06_MODE_MMC_HS400 && ios->enhanced_strobe) 45462306a36Sopenharmony_ci sdhci_cdns_set_emmc_mode(priv, 45562306a36Sopenharmony_ci SDHCI_CDNS_HRS06_MODE_MMC_HS400ES); 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci if (mode == SDHCI_CDNS_HRS06_MODE_MMC_HS400ES && !ios->enhanced_strobe) 45862306a36Sopenharmony_ci sdhci_cdns_set_emmc_mode(priv, 45962306a36Sopenharmony_ci SDHCI_CDNS_HRS06_MODE_MMC_HS400); 46062306a36Sopenharmony_ci} 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_cistatic void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc) 46362306a36Sopenharmony_ci{ 46462306a36Sopenharmony_ci struct sdhci_host *host = mmc_priv(mmc); 46562306a36Sopenharmony_ci struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci dev_dbg(mmc_dev(host->mmc), "emmc hardware reset\n"); 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci reset_control_assert(priv->rst_hw); 47062306a36Sopenharmony_ci /* For eMMC, minimum is 1us but give it 3us for good measure */ 47162306a36Sopenharmony_ci udelay(3); 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci reset_control_deassert(priv->rst_hw); 47462306a36Sopenharmony_ci /* For eMMC, minimum is 200us but give it 300us for good measure */ 47562306a36Sopenharmony_ci usleep_range(300, 1000); 47662306a36Sopenharmony_ci} 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_cistatic int sdhci_cdns_probe(struct platform_device *pdev) 47962306a36Sopenharmony_ci{ 48062306a36Sopenharmony_ci struct sdhci_host *host; 48162306a36Sopenharmony_ci const struct sdhci_cdns_drv_data *data; 48262306a36Sopenharmony_ci struct sdhci_pltfm_host *pltfm_host; 48362306a36Sopenharmony_ci struct sdhci_cdns_priv *priv; 48462306a36Sopenharmony_ci struct clk *clk; 48562306a36Sopenharmony_ci unsigned int nr_phy_params; 48662306a36Sopenharmony_ci int ret; 48762306a36Sopenharmony_ci struct device *dev = &pdev->dev; 48862306a36Sopenharmony_ci static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci clk = devm_clk_get_enabled(dev, NULL); 49162306a36Sopenharmony_ci if (IS_ERR(clk)) 49262306a36Sopenharmony_ci return PTR_ERR(clk); 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci data = of_device_get_match_data(dev); 49562306a36Sopenharmony_ci if (!data) 49662306a36Sopenharmony_ci data = &sdhci_cdns_drv_data; 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci nr_phy_params = sdhci_cdns_phy_param_count(dev->of_node); 49962306a36Sopenharmony_ci host = sdhci_pltfm_init(pdev, &data->pltfm_data, 50062306a36Sopenharmony_ci struct_size(priv, phy_params, nr_phy_params)); 50162306a36Sopenharmony_ci if (IS_ERR(host)) 50262306a36Sopenharmony_ci return PTR_ERR(host); 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci pltfm_host = sdhci_priv(host); 50562306a36Sopenharmony_ci pltfm_host->clk = clk; 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci priv = sdhci_pltfm_priv(pltfm_host); 50862306a36Sopenharmony_ci priv->nr_phy_params = nr_phy_params; 50962306a36Sopenharmony_ci priv->hrs_addr = host->ioaddr; 51062306a36Sopenharmony_ci priv->enhanced_strobe = false; 51162306a36Sopenharmony_ci priv->priv_writel = cdns_writel; 51262306a36Sopenharmony_ci host->ioaddr += SDHCI_CDNS_SRS_BASE; 51362306a36Sopenharmony_ci host->mmc_host_ops.hs400_enhanced_strobe = 51462306a36Sopenharmony_ci sdhci_cdns_hs400_enhanced_strobe; 51562306a36Sopenharmony_ci if (data->init) { 51662306a36Sopenharmony_ci ret = data->init(pdev); 51762306a36Sopenharmony_ci if (ret) 51862306a36Sopenharmony_ci goto free; 51962306a36Sopenharmony_ci } 52062306a36Sopenharmony_ci sdhci_enable_v4_mode(host); 52162306a36Sopenharmony_ci __sdhci_read_caps(host, &version, NULL, NULL); 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci sdhci_get_of_property(pdev); 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci ret = mmc_of_parse(host->mmc); 52662306a36Sopenharmony_ci if (ret) 52762306a36Sopenharmony_ci goto free; 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci sdhci_cdns_phy_param_parse(dev->of_node, priv); 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci ret = sdhci_cdns_phy_init(priv); 53262306a36Sopenharmony_ci if (ret) 53362306a36Sopenharmony_ci goto free; 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci if (host->mmc->caps & MMC_CAP_HW_RESET) { 53662306a36Sopenharmony_ci priv->rst_hw = devm_reset_control_get_optional_exclusive(dev, NULL); 53762306a36Sopenharmony_ci if (IS_ERR(priv->rst_hw)) { 53862306a36Sopenharmony_ci ret = dev_err_probe(mmc_dev(host->mmc), PTR_ERR(priv->rst_hw), 53962306a36Sopenharmony_ci "reset controller error\n"); 54062306a36Sopenharmony_ci goto free; 54162306a36Sopenharmony_ci } 54262306a36Sopenharmony_ci if (priv->rst_hw) 54362306a36Sopenharmony_ci host->mmc_host_ops.card_hw_reset = sdhci_cdns_mmc_hw_reset; 54462306a36Sopenharmony_ci } 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci ret = sdhci_add_host(host); 54762306a36Sopenharmony_ci if (ret) 54862306a36Sopenharmony_ci goto free; 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci return 0; 55162306a36Sopenharmony_cifree: 55262306a36Sopenharmony_ci sdhci_pltfm_free(pdev); 55362306a36Sopenharmony_ci return ret; 55462306a36Sopenharmony_ci} 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 55762306a36Sopenharmony_cistatic int sdhci_cdns_resume(struct device *dev) 55862306a36Sopenharmony_ci{ 55962306a36Sopenharmony_ci struct sdhci_host *host = dev_get_drvdata(dev); 56062306a36Sopenharmony_ci struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 56162306a36Sopenharmony_ci struct sdhci_cdns_priv *priv = sdhci_pltfm_priv(pltfm_host); 56262306a36Sopenharmony_ci int ret; 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci ret = clk_prepare_enable(pltfm_host->clk); 56562306a36Sopenharmony_ci if (ret) 56662306a36Sopenharmony_ci return ret; 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci ret = sdhci_cdns_phy_init(priv); 56962306a36Sopenharmony_ci if (ret) 57062306a36Sopenharmony_ci goto disable_clk; 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci ret = sdhci_resume_host(host); 57362306a36Sopenharmony_ci if (ret) 57462306a36Sopenharmony_ci goto disable_clk; 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci return 0; 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_cidisable_clk: 57962306a36Sopenharmony_ci clk_disable_unprepare(pltfm_host->clk); 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci return ret; 58262306a36Sopenharmony_ci} 58362306a36Sopenharmony_ci#endif 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_cistatic const struct dev_pm_ops sdhci_cdns_pm_ops = { 58662306a36Sopenharmony_ci SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_suspend, sdhci_cdns_resume) 58762306a36Sopenharmony_ci}; 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_cistatic const struct of_device_id sdhci_cdns_match[] = { 59062306a36Sopenharmony_ci { 59162306a36Sopenharmony_ci .compatible = "socionext,uniphier-sd4hc", 59262306a36Sopenharmony_ci .data = &sdhci_cdns_uniphier_drv_data, 59362306a36Sopenharmony_ci }, 59462306a36Sopenharmony_ci { 59562306a36Sopenharmony_ci .compatible = "amd,pensando-elba-sd4hc", 59662306a36Sopenharmony_ci .data = &sdhci_elba_drv_data, 59762306a36Sopenharmony_ci }, 59862306a36Sopenharmony_ci { .compatible = "cdns,sd4hc" }, 59962306a36Sopenharmony_ci { /* sentinel */ } 60062306a36Sopenharmony_ci}; 60162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, sdhci_cdns_match); 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_cistatic struct platform_driver sdhci_cdns_driver = { 60462306a36Sopenharmony_ci .driver = { 60562306a36Sopenharmony_ci .name = "sdhci-cdns", 60662306a36Sopenharmony_ci .probe_type = PROBE_PREFER_ASYNCHRONOUS, 60762306a36Sopenharmony_ci .pm = &sdhci_cdns_pm_ops, 60862306a36Sopenharmony_ci .of_match_table = sdhci_cdns_match, 60962306a36Sopenharmony_ci }, 61062306a36Sopenharmony_ci .probe = sdhci_cdns_probe, 61162306a36Sopenharmony_ci .remove_new = sdhci_pltfm_remove, 61262306a36Sopenharmony_ci}; 61362306a36Sopenharmony_cimodule_platform_driver(sdhci_cdns_driver); 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_ciMODULE_AUTHOR("Masahiro Yamada <yamada.masahiro@socionext.com>"); 61662306a36Sopenharmony_ciMODULE_DESCRIPTION("Cadence SD/SDIO/eMMC Host Controller Driver"); 61762306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 618