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