1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (C) 2016 Socionext Inc. 4 * Author: Masahiro Yamada <yamada.masahiro@socionext.com> 5 */ 6 7 #include <common.h> 8 #include <dm.h> 9 #include <linux/bitfield.h> 10 #include <linux/io.h> 11 #include <linux/iopoll.h> 12 #include <linux/sizes.h> 13 #include <linux/libfdt.h> 14 #include <mmc.h> 15 #include <sdhci.h> 16 17 /* HRS - Host Register Set (specific to Cadence) */ 18 #define SDHCI_CDNS_HRS04 0x10 /* PHY access port */ 19 #define SDHCI_CDNS_HRS04_ACK BIT(26) 20 #define SDHCI_CDNS_HRS04_RD BIT(25) 21 #define SDHCI_CDNS_HRS04_WR BIT(24) 22 #define SDHCI_CDNS_HRS04_RDATA GENMASK(23, 16) 23 #define SDHCI_CDNS_HRS04_WDATA GENMASK(15, 8) 24 #define SDHCI_CDNS_HRS04_ADDR GENMASK(5, 0) 25 26 #define SDHCI_CDNS_HRS06 0x18 /* eMMC control */ 27 #define SDHCI_CDNS_HRS06_TUNE_UP BIT(15) 28 #define SDHCI_CDNS_HRS06_TUNE GENMASK(13, 8) 29 #define SDHCI_CDNS_HRS06_MODE GENMASK(2, 0) 30 #define SDHCI_CDNS_HRS06_MODE_SD 0x0 31 #define SDHCI_CDNS_HRS06_MODE_MMC_SDR 0x2 32 #define SDHCI_CDNS_HRS06_MODE_MMC_DDR 0x3 33 #define SDHCI_CDNS_HRS06_MODE_MMC_HS200 0x4 34 #define SDHCI_CDNS_HRS06_MODE_MMC_HS400 0x5 35 #define SDHCI_CDNS_HRS06_MODE_MMC_HS400ES 0x6 36 37 /* SRS - Slot Register Set (SDHCI-compatible) */ 38 #define SDHCI_CDNS_SRS_BASE 0x200 39 40 /* PHY */ 41 #define SDHCI_CDNS_PHY_DLY_SD_HS 0x00 42 #define SDHCI_CDNS_PHY_DLY_SD_DEFAULT 0x01 43 #define SDHCI_CDNS_PHY_DLY_UHS_SDR12 0x02 44 #define SDHCI_CDNS_PHY_DLY_UHS_SDR25 0x03 45 #define SDHCI_CDNS_PHY_DLY_UHS_SDR50 0x04 46 #define SDHCI_CDNS_PHY_DLY_UHS_DDR50 0x05 47 #define SDHCI_CDNS_PHY_DLY_EMMC_LEGACY 0x06 48 #define SDHCI_CDNS_PHY_DLY_EMMC_SDR 0x07 49 #define SDHCI_CDNS_PHY_DLY_EMMC_DDR 0x08 50 #define SDHCI_CDNS_PHY_DLY_SDCLK 0x0b 51 #define SDHCI_CDNS_PHY_DLY_HSMMC 0x0c 52 #define SDHCI_CDNS_PHY_DLY_STROBE 0x0d 53 54 /* 55 * The tuned val register is 6 bit-wide, but not the whole of the range is 56 * available. The range 0-42 seems to be available (then 43 wraps around to 0) 57 * but I am not quite sure if it is official. Use only 0 to 39 for safety. 58 */ 59 #define SDHCI_CDNS_MAX_TUNING_LOOP 40 60 61 struct sdhci_cdns_plat { 62 struct mmc_config cfg; 63 struct mmc mmc; 64 void __iomem *hrs_addr; 65 }; 66 67 struct sdhci_cdns_phy_cfg { 68 const char *property; 69 u8 addr; 70 }; 71 72 static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = { 73 { "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, }, 74 { "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, }, 75 { "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, }, 76 { "cdns,phy-input-delay-sd-uhs-sdr25", SDHCI_CDNS_PHY_DLY_UHS_SDR25, }, 77 { "cdns,phy-input-delay-sd-uhs-sdr50", SDHCI_CDNS_PHY_DLY_UHS_SDR50, }, 78 { "cdns,phy-input-delay-sd-uhs-ddr50", SDHCI_CDNS_PHY_DLY_UHS_DDR50, }, 79 { "cdns,phy-input-delay-mmc-highspeed", SDHCI_CDNS_PHY_DLY_EMMC_SDR, }, 80 { "cdns,phy-input-delay-mmc-ddr", SDHCI_CDNS_PHY_DLY_EMMC_DDR, }, 81 { "cdns,phy-dll-delay-sdclk", SDHCI_CDNS_PHY_DLY_SDCLK, }, 82 { "cdns,phy-dll-delay-sdclk-hsmmc", SDHCI_CDNS_PHY_DLY_HSMMC, }, 83 { "cdns,phy-dll-delay-strobe", SDHCI_CDNS_PHY_DLY_STROBE, }, 84 }; 85 86 static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_plat *plat, 87 u8 addr, u8 data) 88 { 89 void __iomem *reg = plat->hrs_addr + SDHCI_CDNS_HRS04; 90 u32 tmp; 91 int ret; 92 93 tmp = FIELD_PREP(SDHCI_CDNS_HRS04_WDATA, data) | 94 FIELD_PREP(SDHCI_CDNS_HRS04_ADDR, addr); 95 writel(tmp, reg); 96 97 tmp |= SDHCI_CDNS_HRS04_WR; 98 writel(tmp, reg); 99 100 ret = readl_poll_timeout(reg, tmp, tmp & SDHCI_CDNS_HRS04_ACK, 10); 101 if (ret) 102 return ret; 103 104 tmp &= ~SDHCI_CDNS_HRS04_WR; 105 writel(tmp, reg); 106 107 return 0; 108 } 109 110 static int sdhci_cdns_phy_init(struct sdhci_cdns_plat *plat, 111 const void *fdt, int nodeoffset) 112 { 113 const fdt32_t *prop; 114 int ret, i; 115 116 for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) { 117 prop = fdt_getprop(fdt, nodeoffset, 118 sdhci_cdns_phy_cfgs[i].property, NULL); 119 if (!prop) 120 continue; 121 122 ret = sdhci_cdns_write_phy_reg(plat, 123 sdhci_cdns_phy_cfgs[i].addr, 124 fdt32_to_cpu(*prop)); 125 if (ret) 126 return ret; 127 } 128 129 return 0; 130 } 131 132 static void sdhci_cdns_set_control_reg(struct sdhci_host *host) 133 { 134 struct mmc *mmc = host->mmc; 135 struct sdhci_cdns_plat *plat = dev_get_platdata(mmc->dev); 136 unsigned int clock = mmc->clock; 137 u32 mode, tmp; 138 139 /* 140 * REVISIT: 141 * The mode should be decided by MMC_TIMING_* like Linux, but 142 * U-Boot does not support timing. Use the clock frequency instead. 143 */ 144 if (clock <= 26000000) { 145 mode = SDHCI_CDNS_HRS06_MODE_SD; /* use this for Legacy */ 146 } else if (clock <= 52000000) { 147 if (mmc->ddr_mode) 148 mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR; 149 else 150 mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR; 151 } else { 152 if (mmc->ddr_mode) 153 mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400; 154 else 155 mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200; 156 } 157 158 tmp = readl(plat->hrs_addr + SDHCI_CDNS_HRS06); 159 tmp &= ~SDHCI_CDNS_HRS06_MODE; 160 tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_MODE, mode); 161 writel(tmp, plat->hrs_addr + SDHCI_CDNS_HRS06); 162 } 163 164 static const struct sdhci_ops sdhci_cdns_ops = { 165 .set_control_reg = sdhci_cdns_set_control_reg, 166 }; 167 168 static int sdhci_cdns_set_tune_val(struct sdhci_cdns_plat *plat, 169 unsigned int val) 170 { 171 void __iomem *reg = plat->hrs_addr + SDHCI_CDNS_HRS06; 172 u32 tmp; 173 174 if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val))) 175 return -EINVAL; 176 177 tmp = readl(reg); 178 tmp &= ~SDHCI_CDNS_HRS06_TUNE; 179 tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_TUNE, val); 180 tmp |= SDHCI_CDNS_HRS06_TUNE_UP; 181 writel(tmp, reg); 182 183 return readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS06_TUNE_UP), 184 1); 185 } 186 187 static int __maybe_unused sdhci_cdns_execute_tuning(struct udevice *dev, 188 unsigned int opcode) 189 { 190 struct sdhci_cdns_plat *plat = dev_get_platdata(dev); 191 struct mmc *mmc = &plat->mmc; 192 int cur_streak = 0; 193 int max_streak = 0; 194 int end_of_streak = 0; 195 int i; 196 197 /* 198 * This handler only implements the eMMC tuning that is specific to 199 * this controller. The tuning for SD timing should be handled by the 200 * SDHCI core. 201 */ 202 if (!IS_MMC(mmc)) 203 return -ENOTSUPP; 204 205 if (WARN_ON(opcode != MMC_CMD_SEND_TUNING_BLOCK_HS200)) 206 return -EINVAL; 207 208 for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) { 209 if (sdhci_cdns_set_tune_val(plat, i) || 210 mmc_send_tuning(mmc, opcode, NULL)) { /* bad */ 211 cur_streak = 0; 212 } else { /* good */ 213 cur_streak++; 214 if (cur_streak > max_streak) { 215 max_streak = cur_streak; 216 end_of_streak = i; 217 } 218 } 219 } 220 221 if (!max_streak) { 222 dev_err(dev, "no tuning point found\n"); 223 return -EIO; 224 } 225 226 return sdhci_cdns_set_tune_val(plat, end_of_streak - max_streak / 2); 227 } 228 229 static struct dm_mmc_ops sdhci_cdns_mmc_ops; 230 231 static int sdhci_cdns_bind(struct udevice *dev) 232 { 233 struct sdhci_cdns_plat *plat = dev_get_platdata(dev); 234 235 return sdhci_bind(dev, &plat->mmc, &plat->cfg); 236 } 237 238 static int sdhci_cdns_probe(struct udevice *dev) 239 { 240 DECLARE_GLOBAL_DATA_PTR; 241 struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); 242 struct sdhci_cdns_plat *plat = dev_get_platdata(dev); 243 struct sdhci_host *host = dev_get_priv(dev); 244 fdt_addr_t base; 245 int ret; 246 247 base = devfdt_get_addr(dev); 248 if (base == FDT_ADDR_T_NONE) 249 return -EINVAL; 250 251 plat->hrs_addr = devm_ioremap(dev, base, SZ_1K); 252 if (!plat->hrs_addr) 253 return -ENOMEM; 254 255 host->name = dev->name; 256 host->ioaddr = plat->hrs_addr + SDHCI_CDNS_SRS_BASE; 257 host->ops = &sdhci_cdns_ops; 258 host->quirks |= SDHCI_QUIRK_WAIT_SEND_CMD; 259 sdhci_cdns_mmc_ops = sdhci_ops; 260 #ifdef MMC_SUPPORTS_TUNING 261 sdhci_cdns_mmc_ops.execute_tuning = sdhci_cdns_execute_tuning; 262 #endif 263 264 ret = mmc_of_parse(dev, &plat->cfg); 265 if (ret) 266 return ret; 267 268 ret = sdhci_cdns_phy_init(plat, gd->fdt_blob, dev_of_offset(dev)); 269 if (ret) 270 return ret; 271 272 ret = sdhci_setup_cfg(&plat->cfg, host, 0, 0); 273 if (ret) 274 return ret; 275 276 upriv->mmc = &plat->mmc; 277 host->mmc = &plat->mmc; 278 host->mmc->priv = host; 279 280 return sdhci_probe(dev); 281 } 282 283 static const struct udevice_id sdhci_cdns_match[] = { 284 { .compatible = "socionext,uniphier-sd4hc" }, 285 { .compatible = "cdns,sd4hc" }, 286 { /* sentinel */ } 287 }; 288 289 U_BOOT_DRIVER(sdhci_cdns) = { 290 .name = "sdhci-cdns", 291 .id = UCLASS_MMC, 292 .of_match = sdhci_cdns_match, 293 .bind = sdhci_cdns_bind, 294 .probe = sdhci_cdns_probe, 295 .priv_auto_alloc_size = sizeof(struct sdhci_host), 296 .platdata_auto_alloc_size = sizeof(struct sdhci_cdns_plat), 297 .ops = &sdhci_cdns_mmc_ops, 298 }; 299