Commit | Line | Data |
---|---|---|
c942fddf | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
ff6af28f MY |
2 | /* |
3 | * Copyright (C) 2016 Socionext Inc. | |
4 | * Author: Masahiro Yamada <yamada.masahiro@socionext.com> | |
ff6af28f MY |
5 | */ |
6 | ||
f02cebdf | 7 | #include <linux/bitfield.h> |
cd7a0d21 | 8 | #include <linux/bits.h> |
ff6af28f MY |
9 | #include <linux/iopoll.h> |
10 | #include <linux/module.h> | |
11 | #include <linux/mmc/host.h> | |
963836ad | 12 | #include <linux/mmc/mmc.h> |
a89c472d | 13 | #include <linux/of.h> |
c62da8a8 | 14 | #include <linux/platform_device.h> |
aad53d4e | 15 | #include <linux/reset.h> |
ff6af28f MY |
16 | |
17 | #include "sdhci-pltfm.h" | |
18 | ||
19 | /* HRS - Host Register Set (specific to Cadence) */ | |
20 | #define SDHCI_CDNS_HRS04 0x10 /* PHY access port */ | |
21 | #define SDHCI_CDNS_HRS04_ACK BIT(26) | |
22 | #define SDHCI_CDNS_HRS04_RD BIT(25) | |
23 | #define SDHCI_CDNS_HRS04_WR BIT(24) | |
f02cebdf MY |
24 | #define SDHCI_CDNS_HRS04_RDATA GENMASK(23, 16) |
25 | #define SDHCI_CDNS_HRS04_WDATA GENMASK(15, 8) | |
26 | #define SDHCI_CDNS_HRS04_ADDR GENMASK(5, 0) | |
ff6af28f MY |
27 | |
28 | #define SDHCI_CDNS_HRS06 0x18 /* eMMC control */ | |
29 | #define SDHCI_CDNS_HRS06_TUNE_UP BIT(15) | |
f02cebdf MY |
30 | #define SDHCI_CDNS_HRS06_TUNE GENMASK(13, 8) |
31 | #define SDHCI_CDNS_HRS06_MODE GENMASK(2, 0) | |
ff6af28f MY |
32 | #define SDHCI_CDNS_HRS06_MODE_SD 0x0 |
33 | #define SDHCI_CDNS_HRS06_MODE_MMC_SDR 0x2 | |
34 | #define SDHCI_CDNS_HRS06_MODE_MMC_DDR 0x3 | |
35 | #define SDHCI_CDNS_HRS06_MODE_MMC_HS200 0x4 | |
36 | #define SDHCI_CDNS_HRS06_MODE_MMC_HS400 0x5 | |
d12990f9 | 37 | #define SDHCI_CDNS_HRS06_MODE_MMC_HS400ES 0x6 |
ff6af28f MY |
38 | |
39 | /* SRS - Slot Register Set (SDHCI-compatible) */ | |
40 | #define SDHCI_CDNS_SRS_BASE 0x200 | |
41 | ||
42 | /* PHY */ | |
43 | #define SDHCI_CDNS_PHY_DLY_SD_HS 0x00 | |
44 | #define SDHCI_CDNS_PHY_DLY_SD_DEFAULT 0x01 | |
45 | #define SDHCI_CDNS_PHY_DLY_UHS_SDR12 0x02 | |
46 | #define SDHCI_CDNS_PHY_DLY_UHS_SDR25 0x03 | |
47 | #define SDHCI_CDNS_PHY_DLY_UHS_SDR50 0x04 | |
48 | #define SDHCI_CDNS_PHY_DLY_UHS_DDR50 0x05 | |
49 | #define SDHCI_CDNS_PHY_DLY_EMMC_LEGACY 0x06 | |
50 | #define SDHCI_CDNS_PHY_DLY_EMMC_SDR 0x07 | |
51 | #define SDHCI_CDNS_PHY_DLY_EMMC_DDR 0x08 | |
a89c472d PS |
52 | #define SDHCI_CDNS_PHY_DLY_SDCLK 0x0b |
53 | #define SDHCI_CDNS_PHY_DLY_HSMMC 0x0c | |
54 | #define SDHCI_CDNS_PHY_DLY_STROBE 0x0d | |
ff6af28f MY |
55 | |
56 | /* | |
57 | * The tuned val register is 6 bit-wide, but not the whole of the range is | |
58 | * available. The range 0-42 seems to be available (then 43 wraps around to 0) | |
59 | * but I am not quite sure if it is official. Use only 0 to 39 for safety. | |
60 | */ | |
61 | #define SDHCI_CDNS_MAX_TUNING_LOOP 40 | |
62 | ||
a232a8f2 MY |
63 | struct sdhci_cdns_phy_param { |
64 | u8 addr; | |
65 | u8 data; | |
66 | }; | |
67 | ||
ff6af28f MY |
68 | struct sdhci_cdns_priv { |
69 | void __iomem *hrs_addr; | |
b5dbcf1f BL |
70 | void __iomem *ctl_addr; /* write control */ |
71 | spinlock_t wrlock; /* write lock */ | |
d12990f9 | 72 | bool enhanced_strobe; |
d3e32f84 | 73 | void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg); |
aad53d4e | 74 | struct reset_control *rst_hw; |
a232a8f2 | 75 | unsigned int nr_phy_params; |
1a91a36a | 76 | struct sdhci_cdns_phy_param phy_params[]; |
ff6af28f MY |
77 | }; |
78 | ||
a89c472d PS |
79 | struct sdhci_cdns_phy_cfg { |
80 | const char *property; | |
81 | u8 addr; | |
82 | }; | |
83 | ||
e095b78e BL |
84 | struct sdhci_cdns_drv_data { |
85 | int (*init)(struct platform_device *pdev); | |
86 | const struct sdhci_pltfm_data pltfm_data; | |
87 | }; | |
88 | ||
a89c472d PS |
89 | static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = { |
90 | { "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, }, | |
91 | { "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, }, | |
92 | { "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, }, | |
93 | { "cdns,phy-input-delay-sd-uhs-sdr25", SDHCI_CDNS_PHY_DLY_UHS_SDR25, }, | |
94 | { "cdns,phy-input-delay-sd-uhs-sdr50", SDHCI_CDNS_PHY_DLY_UHS_SDR50, }, | |
95 | { "cdns,phy-input-delay-sd-uhs-ddr50", SDHCI_CDNS_PHY_DLY_UHS_DDR50, }, | |
96 | { "cdns,phy-input-delay-mmc-highspeed", SDHCI_CDNS_PHY_DLY_EMMC_SDR, }, | |
97 | { "cdns,phy-input-delay-mmc-ddr", SDHCI_CDNS_PHY_DLY_EMMC_DDR, }, | |
98 | { "cdns,phy-dll-delay-sdclk", SDHCI_CDNS_PHY_DLY_SDCLK, }, | |
99 | { "cdns,phy-dll-delay-sdclk-hsmmc", SDHCI_CDNS_PHY_DLY_HSMMC, }, | |
100 | { "cdns,phy-dll-delay-strobe", SDHCI_CDNS_PHY_DLY_STROBE, }, | |
101 | }; | |
102 | ||
d3e32f84 BL |
103 | static inline void cdns_writel(struct sdhci_cdns_priv *priv, u32 val, |
104 | void __iomem *reg) | |
105 | { | |
106 | writel(val, reg); | |
107 | } | |
108 | ||
a0f82432 PS |
109 | static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, |
110 | u8 addr, u8 data) | |
ff6af28f MY |
111 | { |
112 | void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04; | |
113 | u32 tmp; | |
a0f82432 | 114 | int ret; |
ff6af28f | 115 | |
f6bc8186 VK |
116 | ret = readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS04_ACK), |
117 | 0, 10); | |
118 | if (ret) | |
119 | return ret; | |
120 | ||
f02cebdf MY |
121 | tmp = FIELD_PREP(SDHCI_CDNS_HRS04_WDATA, data) | |
122 | FIELD_PREP(SDHCI_CDNS_HRS04_ADDR, addr); | |
d3e32f84 | 123 | priv->priv_writel(priv, tmp, reg); |
ff6af28f MY |
124 | |
125 | tmp |= SDHCI_CDNS_HRS04_WR; | |
d3e32f84 | 126 | priv->priv_writel(priv, tmp, reg); |
ff6af28f | 127 | |
a0f82432 PS |
128 | ret = readl_poll_timeout(reg, tmp, tmp & SDHCI_CDNS_HRS04_ACK, 0, 10); |
129 | if (ret) | |
130 | return ret; | |
131 | ||
ff6af28f | 132 | tmp &= ~SDHCI_CDNS_HRS04_WR; |
d3e32f84 | 133 | priv->priv_writel(priv, tmp, reg); |
a0f82432 | 134 | |
f6bc8186 VK |
135 | ret = readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS04_ACK), |
136 | 0, 10); | |
137 | ||
138 | return ret; | |
ff6af28f MY |
139 | } |
140 | ||
a232a8f2 | 141 | static unsigned int sdhci_cdns_phy_param_count(struct device_node *np) |
ff6af28f | 142 | { |
a232a8f2 MY |
143 | unsigned int count = 0; |
144 | int i; | |
145 | ||
146 | for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) | |
147 | if (of_property_read_bool(np, sdhci_cdns_phy_cfgs[i].property)) | |
148 | count++; | |
149 | ||
150 | return count; | |
151 | } | |
152 | ||
153 | static void sdhci_cdns_phy_param_parse(struct device_node *np, | |
154 | struct sdhci_cdns_priv *priv) | |
155 | { | |
156 | struct sdhci_cdns_phy_param *p = priv->phy_params; | |
a89c472d PS |
157 | u32 val; |
158 | int ret, i; | |
159 | ||
160 | for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) { | |
161 | ret = of_property_read_u32(np, sdhci_cdns_phy_cfgs[i].property, | |
162 | &val); | |
163 | if (ret) | |
164 | continue; | |
165 | ||
a232a8f2 MY |
166 | p->addr = sdhci_cdns_phy_cfgs[i].addr; |
167 | p->data = val; | |
168 | p++; | |
169 | } | |
170 | } | |
171 | ||
172 | static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv) | |
173 | { | |
174 | int ret, i; | |
175 | ||
176 | for (i = 0; i < priv->nr_phy_params; i++) { | |
177 | ret = sdhci_cdns_write_phy_reg(priv, priv->phy_params[i].addr, | |
178 | priv->phy_params[i].data); | |
a89c472d PS |
179 | if (ret) |
180 | return ret; | |
181 | } | |
182 | ||
183 | return 0; | |
ff6af28f MY |
184 | } |
185 | ||
1d45a3f4 | 186 | static void *sdhci_cdns_priv(struct sdhci_host *host) |
ff6af28f MY |
187 | { |
188 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | |
189 | ||
190 | return sdhci_pltfm_priv(pltfm_host); | |
191 | } | |
192 | ||
193 | static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host) | |
194 | { | |
195 | /* | |
196 | * Cadence's spec says the Timeout Clock Frequency is the same as the | |
8cc35289 | 197 | * Base Clock Frequency. |
ff6af28f | 198 | */ |
8cc35289 | 199 | return host->max_clk; |
ff6af28f MY |
200 | } |
201 | ||
d12990f9 PS |
202 | static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_priv *priv, u32 mode) |
203 | { | |
204 | u32 tmp; | |
205 | ||
206 | /* The speed mode for eMMC is selected by HRS06 register */ | |
207 | tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); | |
f02cebdf MY |
208 | tmp &= ~SDHCI_CDNS_HRS06_MODE; |
209 | tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_MODE, mode); | |
d3e32f84 | 210 | priv->priv_writel(priv, tmp, priv->hrs_addr + SDHCI_CDNS_HRS06); |
d12990f9 PS |
211 | } |
212 | ||
213 | static u32 sdhci_cdns_get_emmc_mode(struct sdhci_cdns_priv *priv) | |
214 | { | |
215 | u32 tmp; | |
216 | ||
217 | tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); | |
f02cebdf | 218 | return FIELD_GET(SDHCI_CDNS_HRS06_MODE, tmp); |
d12990f9 PS |
219 | } |
220 | ||
ff6af28f MY |
221 | static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val) |
222 | { | |
223 | struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); | |
224 | void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06; | |
225 | u32 tmp; | |
ef6b7567 | 226 | int i, ret; |
ff6af28f | 227 | |
f02cebdf | 228 | if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val))) |
ff6af28f MY |
229 | return -EINVAL; |
230 | ||
231 | tmp = readl(reg); | |
f02cebdf MY |
232 | tmp &= ~SDHCI_CDNS_HRS06_TUNE; |
233 | tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_TUNE, val); | |
ff6af28f | 234 | |
ef6b7567 MY |
235 | /* |
236 | * Workaround for IP errata: | |
237 | * The IP6116 SD/eMMC PHY design has a timing issue on receive data | |
238 | * path. Send tune request twice. | |
239 | */ | |
240 | for (i = 0; i < 2; i++) { | |
241 | tmp |= SDHCI_CDNS_HRS06_TUNE_UP; | |
d3e32f84 | 242 | priv->priv_writel(priv, tmp, reg); |
ef6b7567 MY |
243 | |
244 | ret = readl_poll_timeout(reg, tmp, | |
245 | !(tmp & SDHCI_CDNS_HRS06_TUNE_UP), | |
246 | 0, 1); | |
3f1109d1 GS |
247 | if (ret) |
248 | return ret; | |
ef6b7567 MY |
249 | } |
250 | ||
251 | return 0; | |
ff6af28f MY |
252 | } |
253 | ||
adc40a51 MY |
254 | /* |
255 | * In SD mode, software must not use the hardware tuning and instead perform | |
256 | * an almost identical procedure to eMMC. | |
257 | */ | |
258 | static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode) | |
ff6af28f | 259 | { |
ff6af28f MY |
260 | int cur_streak = 0; |
261 | int max_streak = 0; | |
262 | int end_of_streak = 0; | |
263 | int i; | |
264 | ||
265 | /* | |
adc40a51 MY |
266 | * Do not execute tuning for UHS_SDR50 or UHS_DDR50. |
267 | * The delay is set by probe, based on the DT properties. | |
ff6af28f | 268 | */ |
adc40a51 MY |
269 | if (host->timing != MMC_TIMING_MMC_HS200 && |
270 | host->timing != MMC_TIMING_UHS_SDR104) | |
271 | return 0; | |
ff6af28f MY |
272 | |
273 | for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) { | |
274 | if (sdhci_cdns_set_tune_val(host, i) || | |
275 | mmc_send_tuning(host->mmc, opcode, NULL)) { /* bad */ | |
276 | cur_streak = 0; | |
277 | } else { /* good */ | |
278 | cur_streak++; | |
279 | if (cur_streak > max_streak) { | |
280 | max_streak = cur_streak; | |
281 | end_of_streak = i; | |
282 | } | |
283 | } | |
284 | } | |
285 | ||
286 | if (!max_streak) { | |
287 | dev_err(mmc_dev(host->mmc), "no tuning point found\n"); | |
288 | return -EIO; | |
289 | } | |
290 | ||
291 | return sdhci_cdns_set_tune_val(host, end_of_streak - max_streak / 2); | |
292 | } | |
293 | ||
adc40a51 MY |
294 | static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host, |
295 | unsigned int timing) | |
296 | { | |
297 | struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); | |
298 | u32 mode; | |
299 | ||
300 | switch (timing) { | |
301 | case MMC_TIMING_MMC_HS: | |
302 | mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR; | |
303 | break; | |
304 | case MMC_TIMING_MMC_DDR52: | |
305 | mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR; | |
306 | break; | |
307 | case MMC_TIMING_MMC_HS200: | |
308 | mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200; | |
309 | break; | |
310 | case MMC_TIMING_MMC_HS400: | |
311 | if (priv->enhanced_strobe) | |
312 | mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400ES; | |
313 | else | |
314 | mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400; | |
315 | break; | |
316 | default: | |
317 | mode = SDHCI_CDNS_HRS06_MODE_SD; | |
318 | break; | |
319 | } | |
320 | ||
321 | sdhci_cdns_set_emmc_mode(priv, mode); | |
322 | ||
323 | /* For SD, fall back to the default handler */ | |
324 | if (mode == SDHCI_CDNS_HRS06_MODE_SD) | |
325 | sdhci_set_uhs_signaling(host, timing); | |
326 | } | |
327 | ||
b5dbcf1f BL |
328 | /* Elba control register bits [6:3] are byte-lane enables */ |
329 | #define ELBA_BYTE_ENABLE_MASK(x) ((x) << 3) | |
330 | ||
331 | /* | |
332 | * The Pensando Elba SoC explicitly controls byte-lane enabling on writes | |
333 | * which includes writes to the HRS registers. The write lock (wrlock) | |
334 | * is used to ensure byte-lane enable, using write control (ctl_addr), | |
335 | * occurs before the data write. | |
336 | */ | |
337 | static void elba_priv_writel(struct sdhci_cdns_priv *priv, u32 val, | |
338 | void __iomem *reg) | |
339 | { | |
340 | unsigned long flags; | |
341 | ||
342 | spin_lock_irqsave(&priv->wrlock, flags); | |
343 | writel(GENMASK(7, 3), priv->ctl_addr); | |
344 | writel(val, reg); | |
345 | spin_unlock_irqrestore(&priv->wrlock, flags); | |
346 | } | |
347 | ||
348 | static void elba_write_l(struct sdhci_host *host, u32 val, int reg) | |
349 | { | |
350 | elba_priv_writel(sdhci_cdns_priv(host), val, host->ioaddr + reg); | |
351 | } | |
352 | ||
353 | static void elba_write_w(struct sdhci_host *host, u16 val, int reg) | |
354 | { | |
355 | struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); | |
356 | u32 shift = reg & GENMASK(1, 0); | |
357 | unsigned long flags; | |
358 | u32 byte_enables; | |
359 | ||
360 | byte_enables = GENMASK(1, 0) << shift; | |
361 | spin_lock_irqsave(&priv->wrlock, flags); | |
362 | writel(ELBA_BYTE_ENABLE_MASK(byte_enables), priv->ctl_addr); | |
363 | writew(val, host->ioaddr + reg); | |
364 | spin_unlock_irqrestore(&priv->wrlock, flags); | |
365 | } | |
366 | ||
367 | static void elba_write_b(struct sdhci_host *host, u8 val, int reg) | |
368 | { | |
369 | struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); | |
370 | u32 shift = reg & GENMASK(1, 0); | |
371 | unsigned long flags; | |
372 | u32 byte_enables; | |
373 | ||
374 | byte_enables = BIT(0) << shift; | |
375 | spin_lock_irqsave(&priv->wrlock, flags); | |
376 | writel(ELBA_BYTE_ENABLE_MASK(byte_enables), priv->ctl_addr); | |
377 | writeb(val, host->ioaddr + reg); | |
378 | spin_unlock_irqrestore(&priv->wrlock, flags); | |
379 | } | |
380 | ||
381 | static const struct sdhci_ops sdhci_elba_ops = { | |
382 | .write_l = elba_write_l, | |
383 | .write_w = elba_write_w, | |
384 | .write_b = elba_write_b, | |
385 | .set_clock = sdhci_set_clock, | |
386 | .get_timeout_clock = sdhci_cdns_get_timeout_clock, | |
387 | .set_bus_width = sdhci_set_bus_width, | |
388 | .reset = sdhci_reset, | |
389 | .set_uhs_signaling = sdhci_cdns_set_uhs_signaling, | |
390 | }; | |
391 | ||
392 | static int elba_drv_init(struct platform_device *pdev) | |
393 | { | |
394 | struct sdhci_host *host = platform_get_drvdata(pdev); | |
395 | struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); | |
396 | void __iomem *ioaddr; | |
397 | ||
398 | host->mmc->caps |= MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA; | |
399 | spin_lock_init(&priv->wrlock); | |
400 | ||
401 | /* Byte-lane control register */ | |
402 | ioaddr = devm_platform_ioremap_resource(pdev, 1); | |
403 | if (IS_ERR(ioaddr)) | |
404 | return PTR_ERR(ioaddr); | |
405 | ||
406 | priv->ctl_addr = ioaddr; | |
407 | priv->priv_writel = elba_priv_writel; | |
408 | writel(ELBA_BYTE_ENABLE_MASK(0xf), priv->ctl_addr); | |
409 | ||
410 | return 0; | |
411 | } | |
412 | ||
adc40a51 MY |
413 | static const struct sdhci_ops sdhci_cdns_ops = { |
414 | .set_clock = sdhci_set_clock, | |
415 | .get_timeout_clock = sdhci_cdns_get_timeout_clock, | |
416 | .set_bus_width = sdhci_set_bus_width, | |
417 | .reset = sdhci_reset, | |
418 | .platform_execute_tuning = sdhci_cdns_execute_tuning, | |
419 | .set_uhs_signaling = sdhci_cdns_set_uhs_signaling, | |
420 | }; | |
421 | ||
e095b78e BL |
422 | static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = { |
423 | .pltfm_data = { | |
424 | .ops = &sdhci_cdns_ops, | |
425 | .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, | |
426 | }, | |
adc40a51 MY |
427 | }; |
428 | ||
b5dbcf1f BL |
429 | static const struct sdhci_cdns_drv_data sdhci_elba_drv_data = { |
430 | .init = elba_drv_init, | |
431 | .pltfm_data = { | |
432 | .ops = &sdhci_elba_ops, | |
433 | }, | |
434 | }; | |
435 | ||
e095b78e BL |
436 | static const struct sdhci_cdns_drv_data sdhci_cdns_drv_data = { |
437 | .pltfm_data = { | |
438 | .ops = &sdhci_cdns_ops, | |
439 | }, | |
adc40a51 MY |
440 | }; |
441 | ||
d12990f9 PS |
442 | static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc, |
443 | struct mmc_ios *ios) | |
444 | { | |
445 | struct sdhci_host *host = mmc_priv(mmc); | |
446 | struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); | |
447 | u32 mode; | |
448 | ||
449 | priv->enhanced_strobe = ios->enhanced_strobe; | |
450 | ||
451 | mode = sdhci_cdns_get_emmc_mode(priv); | |
452 | ||
453 | if (mode == SDHCI_CDNS_HRS06_MODE_MMC_HS400 && ios->enhanced_strobe) | |
454 | sdhci_cdns_set_emmc_mode(priv, | |
455 | SDHCI_CDNS_HRS06_MODE_MMC_HS400ES); | |
456 | ||
457 | if (mode == SDHCI_CDNS_HRS06_MODE_MMC_HS400ES && !ios->enhanced_strobe) | |
458 | sdhci_cdns_set_emmc_mode(priv, | |
459 | SDHCI_CDNS_HRS06_MODE_MMC_HS400); | |
460 | } | |
461 | ||
aad53d4e BL |
462 | static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc) |
463 | { | |
464 | struct sdhci_host *host = mmc_priv(mmc); | |
465 | struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); | |
466 | ||
467 | dev_dbg(mmc_dev(host->mmc), "emmc hardware reset\n"); | |
468 | ||
469 | reset_control_assert(priv->rst_hw); | |
470 | /* For eMMC, minimum is 1us but give it 3us for good measure */ | |
471 | udelay(3); | |
472 | ||
473 | reset_control_deassert(priv->rst_hw); | |
474 | /* For eMMC, minimum is 200us but give it 300us for good measure */ | |
475 | usleep_range(300, 1000); | |
476 | } | |
477 | ||
ff6af28f MY |
478 | static int sdhci_cdns_probe(struct platform_device *pdev) |
479 | { | |
480 | struct sdhci_host *host; | |
e095b78e | 481 | const struct sdhci_cdns_drv_data *data; |
ff6af28f MY |
482 | struct sdhci_pltfm_host *pltfm_host; |
483 | struct sdhci_cdns_priv *priv; | |
484 | struct clk *clk; | |
a232a8f2 | 485 | unsigned int nr_phy_params; |
ff6af28f | 486 | int ret; |
a89c472d | 487 | struct device *dev = &pdev->dev; |
12a632e6 | 488 | static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT; |
ff6af28f | 489 | |
6996beab | 490 | clk = devm_clk_get_enabled(dev, NULL); |
ff6af28f MY |
491 | if (IS_ERR(clk)) |
492 | return PTR_ERR(clk); | |
493 | ||
18b587b4 MY |
494 | data = of_device_get_match_data(dev); |
495 | if (!data) | |
e095b78e | 496 | data = &sdhci_cdns_drv_data; |
18b587b4 | 497 | |
a232a8f2 | 498 | nr_phy_params = sdhci_cdns_phy_param_count(dev->of_node); |
e095b78e | 499 | host = sdhci_pltfm_init(pdev, &data->pltfm_data, |
159a8b46 | 500 | struct_size(priv, phy_params, nr_phy_params)); |
6996beab AH |
501 | if (IS_ERR(host)) |
502 | return PTR_ERR(host); | |
ff6af28f MY |
503 | |
504 | pltfm_host = sdhci_priv(host); | |
505 | pltfm_host->clk = clk; | |
506 | ||
a232a8f2 MY |
507 | priv = sdhci_pltfm_priv(pltfm_host); |
508 | priv->nr_phy_params = nr_phy_params; | |
ff6af28f | 509 | priv->hrs_addr = host->ioaddr; |
d12990f9 | 510 | priv->enhanced_strobe = false; |
d3e32f84 | 511 | priv->priv_writel = cdns_writel; |
ff6af28f | 512 | host->ioaddr += SDHCI_CDNS_SRS_BASE; |
d12990f9 PS |
513 | host->mmc_host_ops.hs400_enhanced_strobe = |
514 | sdhci_cdns_hs400_enhanced_strobe; | |
e095b78e BL |
515 | if (data->init) { |
516 | ret = data->init(pdev); | |
517 | if (ret) | |
518 | goto free; | |
519 | } | |
e73a3896 | 520 | sdhci_enable_v4_mode(host); |
12a632e6 | 521 | __sdhci_read_caps(host, &version, NULL, NULL); |
ff6af28f | 522 | |
861183f1 PS |
523 | sdhci_get_of_property(pdev); |
524 | ||
ff6af28f MY |
525 | ret = mmc_of_parse(host->mmc); |
526 | if (ret) | |
527 | goto free; | |
528 | ||
a232a8f2 MY |
529 | sdhci_cdns_phy_param_parse(dev->of_node, priv); |
530 | ||
531 | ret = sdhci_cdns_phy_init(priv); | |
a89c472d PS |
532 | if (ret) |
533 | goto free; | |
ff6af28f | 534 | |
aad53d4e BL |
535 | if (host->mmc->caps & MMC_CAP_HW_RESET) { |
536 | priv->rst_hw = devm_reset_control_get_optional_exclusive(dev, NULL); | |
e5bce3c1 CJ |
537 | if (IS_ERR(priv->rst_hw)) { |
538 | ret = dev_err_probe(mmc_dev(host->mmc), PTR_ERR(priv->rst_hw), | |
539 | "reset controller error\n"); | |
540 | goto free; | |
541 | } | |
aad53d4e BL |
542 | if (priv->rst_hw) |
543 | host->mmc_host_ops.card_hw_reset = sdhci_cdns_mmc_hw_reset; | |
544 | } | |
545 | ||
ff6af28f MY |
546 | ret = sdhci_add_host(host); |
547 | if (ret) | |
548 | goto free; | |
549 | ||
550 | return 0; | |
551 | free: | |
552 | sdhci_pltfm_free(pdev); | |
ff6af28f MY |
553 | return ret; |
554 | } | |
555 | ||
a232a8f2 | 556 | #ifdef CONFIG_PM_SLEEP |
a232a8f2 MY |
557 | static int sdhci_cdns_resume(struct device *dev) |
558 | { | |
559 | struct sdhci_host *host = dev_get_drvdata(dev); | |
560 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | |
561 | struct sdhci_cdns_priv *priv = sdhci_pltfm_priv(pltfm_host); | |
562 | int ret; | |
563 | ||
564 | ret = clk_prepare_enable(pltfm_host->clk); | |
565 | if (ret) | |
566 | return ret; | |
567 | ||
568 | ret = sdhci_cdns_phy_init(priv); | |
569 | if (ret) | |
570 | goto disable_clk; | |
571 | ||
572 | ret = sdhci_resume_host(host); | |
573 | if (ret) | |
574 | goto disable_clk; | |
575 | ||
576 | return 0; | |
577 | ||
578 | disable_clk: | |
579 | clk_disable_unprepare(pltfm_host->clk); | |
580 | ||
581 | return ret; | |
582 | } | |
583 | #endif | |
584 | ||
585 | static const struct dev_pm_ops sdhci_cdns_pm_ops = { | |
83a7b32a | 586 | SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_suspend, sdhci_cdns_resume) |
a232a8f2 MY |
587 | }; |
588 | ||
ff6af28f | 589 | static const struct of_device_id sdhci_cdns_match[] = { |
18b587b4 MY |
590 | { |
591 | .compatible = "socionext,uniphier-sd4hc", | |
e095b78e | 592 | .data = &sdhci_cdns_uniphier_drv_data, |
18b587b4 | 593 | }, |
b5dbcf1f BL |
594 | { |
595 | .compatible = "amd,pensando-elba-sd4hc", | |
596 | .data = &sdhci_elba_drv_data, | |
597 | }, | |
ff6af28f MY |
598 | { .compatible = "cdns,sd4hc" }, |
599 | { /* sentinel */ } | |
600 | }; | |
601 | MODULE_DEVICE_TABLE(of, sdhci_cdns_match); | |
602 | ||
603 | static struct platform_driver sdhci_cdns_driver = { | |
604 | .driver = { | |
605 | .name = "sdhci-cdns", | |
7320915c | 606 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
a232a8f2 | 607 | .pm = &sdhci_cdns_pm_ops, |
ff6af28f MY |
608 | .of_match_table = sdhci_cdns_match, |
609 | }, | |
610 | .probe = sdhci_cdns_probe, | |
6996beab | 611 | .remove_new = sdhci_pltfm_remove, |
ff6af28f MY |
612 | }; |
613 | module_platform_driver(sdhci_cdns_driver); | |
614 | ||
615 | MODULE_AUTHOR("Masahiro Yamada <yamada.masahiro@socionext.com>"); | |
616 | MODULE_DESCRIPTION("Cadence SD/SDIO/eMMC Host Controller Driver"); | |
617 | MODULE_LICENSE("GPL"); |