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/io.h> 11 #include <linux/iopoll.h> 12 #include <linux/sizes.h> 13 #include <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_SHIFT 16 23 #define SDHCI_CDNS_HRS04_WDATA_SHIFT 8 24 #define SDHCI_CDNS_HRS04_ADDR_SHIFT 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_SHIFT 8 29 #define SDHCI_CDNS_HRS06_TUNE_MASK 0x3f 30 #define SDHCI_CDNS_HRS06_MODE_MASK 0x7 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 struct sdhci_cdns_plat { 56 struct mmc_config cfg; 57 struct mmc mmc; 58 void __iomem *hrs_addr; 59 }; 60 61 struct sdhci_cdns_phy_cfg { 62 const char *property; 63 u8 addr; 64 }; 65 66 static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = { 67 { "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, }, 68 { "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, }, 69 { "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, }, 70 { "cdns,phy-input-delay-sd-uhs-sdr25", SDHCI_CDNS_PHY_DLY_UHS_SDR25, }, 71 { "cdns,phy-input-delay-sd-uhs-sdr50", SDHCI_CDNS_PHY_DLY_UHS_SDR50, }, 72 { "cdns,phy-input-delay-sd-uhs-ddr50", SDHCI_CDNS_PHY_DLY_UHS_DDR50, }, 73 { "cdns,phy-input-delay-mmc-highspeed", SDHCI_CDNS_PHY_DLY_EMMC_SDR, }, 74 { "cdns,phy-input-delay-mmc-ddr", SDHCI_CDNS_PHY_DLY_EMMC_DDR, }, 75 { "cdns,phy-dll-delay-sdclk", SDHCI_CDNS_PHY_DLY_SDCLK, }, 76 { "cdns,phy-dll-delay-sdclk-hsmmc", SDHCI_CDNS_PHY_DLY_HSMMC, }, 77 { "cdns,phy-dll-delay-strobe", SDHCI_CDNS_PHY_DLY_STROBE, }, 78 }; 79 80 static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_plat *plat, 81 u8 addr, u8 data) 82 { 83 void __iomem *reg = plat->hrs_addr + SDHCI_CDNS_HRS04; 84 u32 tmp; 85 int ret; 86 87 tmp = (data << SDHCI_CDNS_HRS04_WDATA_SHIFT) | 88 (addr << SDHCI_CDNS_HRS04_ADDR_SHIFT); 89 writel(tmp, reg); 90 91 tmp |= SDHCI_CDNS_HRS04_WR; 92 writel(tmp, reg); 93 94 ret = readl_poll_timeout(reg, tmp, tmp & SDHCI_CDNS_HRS04_ACK, 10); 95 if (ret) 96 return ret; 97 98 tmp &= ~SDHCI_CDNS_HRS04_WR; 99 writel(tmp, reg); 100 101 return 0; 102 } 103 104 static int sdhci_cdns_phy_init(struct sdhci_cdns_plat *plat, 105 const void *fdt, int nodeoffset) 106 { 107 const fdt32_t *prop; 108 int ret, i; 109 110 for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) { 111 prop = fdt_getprop(fdt, nodeoffset, 112 sdhci_cdns_phy_cfgs[i].property, NULL); 113 if (!prop) 114 continue; 115 116 ret = sdhci_cdns_write_phy_reg(plat, 117 sdhci_cdns_phy_cfgs[i].addr, 118 fdt32_to_cpu(*prop)); 119 if (ret) 120 return ret; 121 } 122 123 return 0; 124 } 125 126 static void sdhci_cdns_set_control_reg(struct sdhci_host *host) 127 { 128 struct mmc *mmc = host->mmc; 129 struct sdhci_cdns_plat *plat = dev_get_platdata(mmc->dev); 130 unsigned int clock = mmc->clock; 131 u32 mode, tmp; 132 133 /* 134 * REVISIT: 135 * The mode should be decided by MMC_TIMING_* like Linux, but 136 * U-Boot does not support timing. Use the clock frequency instead. 137 */ 138 if (clock <= 26000000) 139 mode = SDHCI_CDNS_HRS06_MODE_SD; /* use this for Legacy */ 140 else if (clock <= 52000000) { 141 if (mmc->ddr_mode) 142 mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR; 143 else 144 mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR; 145 } else { 146 /* 147 * REVISIT: 148 * The IP supports HS200/HS400, revisit once U-Boot support it 149 */ 150 printf("unsupported frequency %d\n", clock); 151 return; 152 } 153 154 tmp = readl(plat->hrs_addr + SDHCI_CDNS_HRS06); 155 tmp &= ~SDHCI_CDNS_HRS06_MODE_MASK; 156 tmp |= mode; 157 writel(tmp, plat->hrs_addr + SDHCI_CDNS_HRS06); 158 } 159 160 static const struct sdhci_ops sdhci_cdns_ops = { 161 .set_control_reg = sdhci_cdns_set_control_reg, 162 }; 163 164 static int sdhci_cdns_bind(struct udevice *dev) 165 { 166 struct sdhci_cdns_plat *plat = dev_get_platdata(dev); 167 168 return sdhci_bind(dev, &plat->mmc, &plat->cfg); 169 } 170 171 static int sdhci_cdns_probe(struct udevice *dev) 172 { 173 DECLARE_GLOBAL_DATA_PTR; 174 struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); 175 struct sdhci_cdns_plat *plat = dev_get_platdata(dev); 176 struct sdhci_host *host = dev_get_priv(dev); 177 fdt_addr_t base; 178 int ret; 179 180 base = devfdt_get_addr(dev); 181 if (base == FDT_ADDR_T_NONE) 182 return -EINVAL; 183 184 plat->hrs_addr = devm_ioremap(dev, base, SZ_1K); 185 if (!plat->hrs_addr) 186 return -ENOMEM; 187 188 host->name = dev->name; 189 host->ioaddr = plat->hrs_addr + SDHCI_CDNS_SRS_BASE; 190 host->ops = &sdhci_cdns_ops; 191 host->quirks |= SDHCI_QUIRK_WAIT_SEND_CMD; 192 193 ret = sdhci_cdns_phy_init(plat, gd->fdt_blob, dev_of_offset(dev)); 194 if (ret) 195 return ret; 196 197 ret = sdhci_setup_cfg(&plat->cfg, host, 0, 0); 198 if (ret) 199 return ret; 200 201 upriv->mmc = &plat->mmc; 202 host->mmc = &plat->mmc; 203 host->mmc->priv = host; 204 205 return sdhci_probe(dev); 206 } 207 208 static const struct udevice_id sdhci_cdns_match[] = { 209 { .compatible = "socionext,uniphier-sd4hc" }, 210 { .compatible = "cdns,sd4hc" }, 211 { /* sentinel */ } 212 }; 213 214 U_BOOT_DRIVER(sdhci_cdns) = { 215 .name = "sdhci-cdns", 216 .id = UCLASS_MMC, 217 .of_match = sdhci_cdns_match, 218 .bind = sdhci_cdns_bind, 219 .probe = sdhci_cdns_probe, 220 .priv_auto_alloc_size = sizeof(struct sdhci_host), 221 .platdata_auto_alloc_size = sizeof(struct sdhci_cdns_plat), 222 .ops = &sdhci_ops, 223 }; 224