1 /* 2 * Copyright (C) 2016 Socionext Inc. 3 * Author: Masahiro Yamada <yamada.masahiro@socionext.com> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 */ 15 16 #include <linux/bitops.h> 17 #include <linux/iopoll.h> 18 #include <linux/module.h> 19 #include <linux/mmc/host.h> 20 #include <linux/mmc/mmc.h> 21 22 #include "sdhci-pltfm.h" 23 24 /* HRS - Host Register Set (specific to Cadence) */ 25 #define SDHCI_CDNS_HRS04 0x10 /* PHY access port */ 26 #define SDHCI_CDNS_HRS04_ACK BIT(26) 27 #define SDHCI_CDNS_HRS04_RD BIT(25) 28 #define SDHCI_CDNS_HRS04_WR BIT(24) 29 #define SDHCI_CDNS_HRS04_RDATA_SHIFT 16 30 #define SDHCI_CDNS_HRS04_WDATA_SHIFT 8 31 #define SDHCI_CDNS_HRS04_ADDR_SHIFT 0 32 33 #define SDHCI_CDNS_HRS06 0x18 /* eMMC control */ 34 #define SDHCI_CDNS_HRS06_TUNE_UP BIT(15) 35 #define SDHCI_CDNS_HRS06_TUNE_SHIFT 8 36 #define SDHCI_CDNS_HRS06_TUNE_MASK 0x3f 37 #define SDHCI_CDNS_HRS06_MODE_MASK 0x7 38 #define SDHCI_CDNS_HRS06_MODE_SD 0x0 39 #define SDHCI_CDNS_HRS06_MODE_MMC_SDR 0x2 40 #define SDHCI_CDNS_HRS06_MODE_MMC_DDR 0x3 41 #define SDHCI_CDNS_HRS06_MODE_MMC_HS200 0x4 42 #define SDHCI_CDNS_HRS06_MODE_MMC_HS400 0x5 43 44 /* SRS - Slot Register Set (SDHCI-compatible) */ 45 #define SDHCI_CDNS_SRS_BASE 0x200 46 47 /* PHY */ 48 #define SDHCI_CDNS_PHY_DLY_SD_HS 0x00 49 #define SDHCI_CDNS_PHY_DLY_SD_DEFAULT 0x01 50 #define SDHCI_CDNS_PHY_DLY_UHS_SDR12 0x02 51 #define SDHCI_CDNS_PHY_DLY_UHS_SDR25 0x03 52 #define SDHCI_CDNS_PHY_DLY_UHS_SDR50 0x04 53 #define SDHCI_CDNS_PHY_DLY_UHS_DDR50 0x05 54 #define SDHCI_CDNS_PHY_DLY_EMMC_LEGACY 0x06 55 #define SDHCI_CDNS_PHY_DLY_EMMC_SDR 0x07 56 #define SDHCI_CDNS_PHY_DLY_EMMC_DDR 0x08 57 58 /* 59 * The tuned val register is 6 bit-wide, but not the whole of the range is 60 * available. The range 0-42 seems to be available (then 43 wraps around to 0) 61 * but I am not quite sure if it is official. Use only 0 to 39 for safety. 62 */ 63 #define SDHCI_CDNS_MAX_TUNING_LOOP 40 64 65 struct sdhci_cdns_priv { 66 void __iomem *hrs_addr; 67 }; 68 69 static void sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, 70 u8 addr, u8 data) 71 { 72 void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04; 73 u32 tmp; 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 tmp &= ~SDHCI_CDNS_HRS04_WR; 83 writel(tmp, reg); 84 } 85 86 static void sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv) 87 { 88 sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_SD_HS, 4); 89 sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_SD_DEFAULT, 4); 90 sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_LEGACY, 9); 91 sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_SDR, 2); 92 sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_DDR, 3); 93 } 94 95 static inline void *sdhci_cdns_priv(struct sdhci_host *host) 96 { 97 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 98 99 return sdhci_pltfm_priv(pltfm_host); 100 } 101 102 static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host) 103 { 104 /* 105 * Cadence's spec says the Timeout Clock Frequency is the same as the 106 * Base Clock Frequency. Divide it by 1000 to return a value in kHz. 107 */ 108 return host->max_clk / 1000; 109 } 110 111 static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host, 112 unsigned int timing) 113 { 114 struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); 115 u32 mode, tmp; 116 117 switch (timing) { 118 case MMC_TIMING_MMC_HS: 119 mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR; 120 break; 121 case MMC_TIMING_MMC_DDR52: 122 mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR; 123 break; 124 case MMC_TIMING_MMC_HS200: 125 mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200; 126 break; 127 case MMC_TIMING_MMC_HS400: 128 mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400; 129 break; 130 default: 131 mode = SDHCI_CDNS_HRS06_MODE_SD; 132 break; 133 } 134 135 /* The speed mode for eMMC is selected by HRS06 register */ 136 tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); 137 tmp &= ~SDHCI_CDNS_HRS06_MODE_MASK; 138 tmp |= mode; 139 writel(tmp, priv->hrs_addr + SDHCI_CDNS_HRS06); 140 141 /* For SD, fall back to the default handler */ 142 if (mode == SDHCI_CDNS_HRS06_MODE_SD) 143 sdhci_set_uhs_signaling(host, timing); 144 } 145 146 static const struct sdhci_ops sdhci_cdns_ops = { 147 .set_clock = sdhci_set_clock, 148 .get_timeout_clock = sdhci_cdns_get_timeout_clock, 149 .set_bus_width = sdhci_set_bus_width, 150 .reset = sdhci_reset, 151 .set_uhs_signaling = sdhci_cdns_set_uhs_signaling, 152 }; 153 154 static const struct sdhci_pltfm_data sdhci_cdns_pltfm_data = { 155 .ops = &sdhci_cdns_ops, 156 }; 157 158 static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val) 159 { 160 struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); 161 void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06; 162 u32 tmp; 163 164 if (WARN_ON(val > SDHCI_CDNS_HRS06_TUNE_MASK)) 165 return -EINVAL; 166 167 tmp = readl(reg); 168 tmp &= ~(SDHCI_CDNS_HRS06_TUNE_MASK << SDHCI_CDNS_HRS06_TUNE_SHIFT); 169 tmp |= val << SDHCI_CDNS_HRS06_TUNE_SHIFT; 170 tmp |= SDHCI_CDNS_HRS06_TUNE_UP; 171 writel(tmp, reg); 172 173 return readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS06_TUNE_UP), 174 0, 1); 175 } 176 177 static int sdhci_cdns_execute_tuning(struct mmc_host *mmc, u32 opcode) 178 { 179 struct sdhci_host *host = mmc_priv(mmc); 180 int cur_streak = 0; 181 int max_streak = 0; 182 int end_of_streak = 0; 183 int i; 184 185 /* 186 * This handler only implements the eMMC tuning that is specific to 187 * this controller. Fall back to the standard method for SD timing. 188 */ 189 if (host->timing != MMC_TIMING_MMC_HS200) 190 return sdhci_execute_tuning(mmc, opcode); 191 192 if (WARN_ON(opcode != MMC_SEND_TUNING_BLOCK_HS200)) 193 return -EINVAL; 194 195 for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) { 196 if (sdhci_cdns_set_tune_val(host, i) || 197 mmc_send_tuning(host->mmc, opcode, NULL)) { /* bad */ 198 cur_streak = 0; 199 } else { /* good */ 200 cur_streak++; 201 if (cur_streak > max_streak) { 202 max_streak = cur_streak; 203 end_of_streak = i; 204 } 205 } 206 } 207 208 if (!max_streak) { 209 dev_err(mmc_dev(host->mmc), "no tuning point found\n"); 210 return -EIO; 211 } 212 213 return sdhci_cdns_set_tune_val(host, end_of_streak - max_streak / 2); 214 } 215 216 static int sdhci_cdns_probe(struct platform_device *pdev) 217 { 218 struct sdhci_host *host; 219 struct sdhci_pltfm_host *pltfm_host; 220 struct sdhci_cdns_priv *priv; 221 struct clk *clk; 222 int ret; 223 224 clk = devm_clk_get(&pdev->dev, NULL); 225 if (IS_ERR(clk)) 226 return PTR_ERR(clk); 227 228 ret = clk_prepare_enable(clk); 229 if (ret) 230 return ret; 231 232 host = sdhci_pltfm_init(pdev, &sdhci_cdns_pltfm_data, sizeof(*priv)); 233 if (IS_ERR(host)) { 234 ret = PTR_ERR(host); 235 goto disable_clk; 236 } 237 238 pltfm_host = sdhci_priv(host); 239 pltfm_host->clk = clk; 240 241 priv = sdhci_cdns_priv(host); 242 priv->hrs_addr = host->ioaddr; 243 host->ioaddr += SDHCI_CDNS_SRS_BASE; 244 host->mmc_host_ops.execute_tuning = sdhci_cdns_execute_tuning; 245 246 ret = mmc_of_parse(host->mmc); 247 if (ret) 248 goto free; 249 250 sdhci_cdns_phy_init(priv); 251 252 ret = sdhci_add_host(host); 253 if (ret) 254 goto free; 255 256 return 0; 257 free: 258 sdhci_pltfm_free(pdev); 259 disable_clk: 260 clk_disable_unprepare(clk); 261 262 return ret; 263 } 264 265 static const struct of_device_id sdhci_cdns_match[] = { 266 { .compatible = "socionext,uniphier-sd4hc" }, 267 { .compatible = "cdns,sd4hc" }, 268 { /* sentinel */ } 269 }; 270 MODULE_DEVICE_TABLE(of, sdhci_cdns_match); 271 272 static struct platform_driver sdhci_cdns_driver = { 273 .driver = { 274 .name = "sdhci-cdns", 275 .pm = &sdhci_pltfm_pmops, 276 .of_match_table = sdhci_cdns_match, 277 }, 278 .probe = sdhci_cdns_probe, 279 .remove = sdhci_pltfm_unregister, 280 }; 281 module_platform_driver(sdhci_cdns_driver); 282 283 MODULE_AUTHOR("Masahiro Yamada <yamada.masahiro@socionext.com>"); 284 MODULE_DESCRIPTION("Cadence SD/SDIO/eMMC Host Controller Driver"); 285 MODULE_LICENSE("GPL"); 286