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