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 /* SRS - Slot Register Set (SDHCI-compatible) */ 27 #define SDHCI_CDNS_SRS_BASE 0x200 28 29 /* PHY */ 30 #define SDHCI_CDNS_PHY_DLY_SD_HS 0x00 31 #define SDHCI_CDNS_PHY_DLY_SD_DEFAULT 0x01 32 #define SDHCI_CDNS_PHY_DLY_UHS_SDR12 0x02 33 #define SDHCI_CDNS_PHY_DLY_UHS_SDR25 0x03 34 #define SDHCI_CDNS_PHY_DLY_UHS_SDR50 0x04 35 #define SDHCI_CDNS_PHY_DLY_UHS_DDR50 0x05 36 #define SDHCI_CDNS_PHY_DLY_EMMC_LEGACY 0x06 37 #define SDHCI_CDNS_PHY_DLY_EMMC_SDR 0x07 38 #define SDHCI_CDNS_PHY_DLY_EMMC_DDR 0x08 39 #define SDHCI_CDNS_PHY_DLY_SDCLK 0x0b 40 #define SDHCI_CDNS_PHY_DLY_HSMMC 0x0c 41 #define SDHCI_CDNS_PHY_DLY_STROBE 0x0d 42 43 struct sdhci_cdns_plat { 44 struct mmc_config cfg; 45 struct mmc mmc; 46 void __iomem *hrs_addr; 47 }; 48 49 struct sdhci_cdns_phy_cfg { 50 const char *property; 51 u8 addr; 52 }; 53 54 static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = { 55 { "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, }, 56 { "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, }, 57 { "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, }, 58 { "cdns,phy-input-delay-sd-uhs-sdr25", SDHCI_CDNS_PHY_DLY_UHS_SDR25, }, 59 { "cdns,phy-input-delay-sd-uhs-sdr50", SDHCI_CDNS_PHY_DLY_UHS_SDR50, }, 60 { "cdns,phy-input-delay-sd-uhs-ddr50", SDHCI_CDNS_PHY_DLY_UHS_DDR50, }, 61 { "cdns,phy-input-delay-mmc-highspeed", SDHCI_CDNS_PHY_DLY_EMMC_SDR, }, 62 { "cdns,phy-input-delay-mmc-ddr", SDHCI_CDNS_PHY_DLY_EMMC_DDR, }, 63 { "cdns,phy-dll-delay-sdclk", SDHCI_CDNS_PHY_DLY_SDCLK, }, 64 { "cdns,phy-dll-delay-sdclk-hsmmc", SDHCI_CDNS_PHY_DLY_HSMMC, }, 65 { "cdns,phy-dll-delay-strobe", SDHCI_CDNS_PHY_DLY_STROBE, }, 66 }; 67 68 static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_plat *plat, 69 u8 addr, u8 data) 70 { 71 void __iomem *reg = plat->hrs_addr + SDHCI_CDNS_HRS04; 72 u32 tmp; 73 int ret; 74 75 tmp = (data << SDHCI_CDNS_HRS04_WDATA_SHIFT) | 76 (addr << SDHCI_CDNS_HRS04_ADDR_SHIFT); 77 writel(tmp, reg); 78 79 tmp |= SDHCI_CDNS_HRS04_WR; 80 writel(tmp, reg); 81 82 ret = readl_poll_timeout(reg, tmp, tmp & SDHCI_CDNS_HRS04_ACK, 10); 83 if (ret) 84 return ret; 85 86 tmp &= ~SDHCI_CDNS_HRS04_WR; 87 writel(tmp, reg); 88 89 return 0; 90 } 91 92 static int sdhci_cdns_phy_init(struct sdhci_cdns_plat *plat, 93 const void *fdt, int nodeoffset) 94 { 95 const u32 *prop; 96 int ret, i; 97 98 for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) { 99 prop = fdt_getprop(fdt, nodeoffset, 100 sdhci_cdns_phy_cfgs[i].property, NULL); 101 if (!prop) 102 continue; 103 104 ret = sdhci_cdns_write_phy_reg(plat, 105 sdhci_cdns_phy_cfgs[i].addr, 106 fdt32_to_cpu(*prop)); 107 if (ret) 108 return ret; 109 } 110 111 return 0; 112 } 113 114 static int sdhci_cdns_bind(struct udevice *dev) 115 { 116 struct sdhci_cdns_plat *plat = dev_get_platdata(dev); 117 118 return sdhci_bind(dev, &plat->mmc, &plat->cfg); 119 } 120 121 static int sdhci_cdns_probe(struct udevice *dev) 122 { 123 DECLARE_GLOBAL_DATA_PTR; 124 struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); 125 struct sdhci_cdns_plat *plat = dev_get_platdata(dev); 126 struct sdhci_host *host = dev_get_priv(dev); 127 fdt_addr_t base; 128 int ret; 129 130 base = devfdt_get_addr(dev); 131 if (base == FDT_ADDR_T_NONE) 132 return -EINVAL; 133 134 plat->hrs_addr = devm_ioremap(dev, base, SZ_1K); 135 if (!plat->hrs_addr) 136 return -ENOMEM; 137 138 host->name = dev->name; 139 host->ioaddr = plat->hrs_addr + SDHCI_CDNS_SRS_BASE; 140 host->quirks |= SDHCI_QUIRK_WAIT_SEND_CMD; 141 142 ret = sdhci_cdns_phy_init(plat, gd->fdt_blob, dev_of_offset(dev)); 143 if (ret) 144 return ret; 145 146 ret = sdhci_setup_cfg(&plat->cfg, host, 0, 0); 147 if (ret) 148 return ret; 149 150 upriv->mmc = &plat->mmc; 151 host->mmc = &plat->mmc; 152 host->mmc->priv = host; 153 154 return sdhci_probe(dev); 155 } 156 157 static const struct udevice_id sdhci_cdns_match[] = { 158 { .compatible = "socionext,uniphier-sd4hc" }, 159 { .compatible = "cdns,sd4hc" }, 160 { /* sentinel */ } 161 }; 162 163 U_BOOT_DRIVER(sdhci_cdns) = { 164 .name = "sdhci-cdns", 165 .id = UCLASS_MMC, 166 .of_match = sdhci_cdns_match, 167 .bind = sdhci_cdns_bind, 168 .probe = sdhci_cdns_probe, 169 .priv_auto_alloc_size = sizeof(struct sdhci_host), 170 .platdata_auto_alloc_size = sizeof(struct sdhci_cdns_plat), 171 .ops = &sdhci_ops, 172 }; 173