blob: 0c2489446bd73a525f8932885a2c27d5a421d653 [file] [log] [blame]
David Brazdil0f672f62019-12-10 10:32:29 +00001// SPDX-License-Identifier: GPL-2.0-or-later
Andrew Scullb4b6d4a2019-01-02 15:54:55 +00002/*
3 * Copyright (C) 2016 Socionext Inc.
4 * Author: Masahiro Yamada <yamada.masahiro@socionext.com>
Andrew Scullb4b6d4a2019-01-02 15:54:55 +00005 */
6
7#include <linux/bitfield.h>
David Brazdil0f672f62019-12-10 10:32:29 +00008#include <linux/bits.h>
Andrew Scullb4b6d4a2019-01-02 15:54:55 +00009#include <linux/iopoll.h>
10#include <linux/module.h>
11#include <linux/mmc/host.h>
12#include <linux/mmc/mmc.h>
13#include <linux/of.h>
Olivier Deprez0e641232021-09-23 10:07:05 +020014#include <linux/of_device.h>
Andrew Scullb4b6d4a2019-01-02 15:54:55 +000015
16#include "sdhci-pltfm.h"
17
18/* HRS - Host Register Set (specific to Cadence) */
19#define SDHCI_CDNS_HRS04 0x10 /* PHY access port */
20#define SDHCI_CDNS_HRS04_ACK BIT(26)
21#define SDHCI_CDNS_HRS04_RD BIT(25)
22#define SDHCI_CDNS_HRS04_WR BIT(24)
23#define SDHCI_CDNS_HRS04_RDATA GENMASK(23, 16)
24#define SDHCI_CDNS_HRS04_WDATA GENMASK(15, 8)
25#define SDHCI_CDNS_HRS04_ADDR GENMASK(5, 0)
26
27#define SDHCI_CDNS_HRS06 0x18 /* eMMC control */
28#define SDHCI_CDNS_HRS06_TUNE_UP BIT(15)
29#define SDHCI_CDNS_HRS06_TUNE GENMASK(13, 8)
30#define SDHCI_CDNS_HRS06_MODE GENMASK(2, 0)
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/*
56 * The tuned val register is 6 bit-wide, but not the whole of the range is
57 * available. The range 0-42 seems to be available (then 43 wraps around to 0)
58 * but I am not quite sure if it is official. Use only 0 to 39 for safety.
59 */
60#define SDHCI_CDNS_MAX_TUNING_LOOP 40
61
62struct sdhci_cdns_phy_param {
63 u8 addr;
64 u8 data;
65};
66
67struct sdhci_cdns_priv {
68 void __iomem *hrs_addr;
69 bool enhanced_strobe;
70 unsigned int nr_phy_params;
71 struct sdhci_cdns_phy_param phy_params[0];
72};
73
74struct sdhci_cdns_phy_cfg {
75 const char *property;
76 u8 addr;
77};
78
79static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = {
80 { "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, },
81 { "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, },
82 { "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, },
83 { "cdns,phy-input-delay-sd-uhs-sdr25", SDHCI_CDNS_PHY_DLY_UHS_SDR25, },
84 { "cdns,phy-input-delay-sd-uhs-sdr50", SDHCI_CDNS_PHY_DLY_UHS_SDR50, },
85 { "cdns,phy-input-delay-sd-uhs-ddr50", SDHCI_CDNS_PHY_DLY_UHS_DDR50, },
86 { "cdns,phy-input-delay-mmc-highspeed", SDHCI_CDNS_PHY_DLY_EMMC_SDR, },
87 { "cdns,phy-input-delay-mmc-ddr", SDHCI_CDNS_PHY_DLY_EMMC_DDR, },
88 { "cdns,phy-dll-delay-sdclk", SDHCI_CDNS_PHY_DLY_SDCLK, },
89 { "cdns,phy-dll-delay-sdclk-hsmmc", SDHCI_CDNS_PHY_DLY_HSMMC, },
90 { "cdns,phy-dll-delay-strobe", SDHCI_CDNS_PHY_DLY_STROBE, },
91};
92
93static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,
94 u8 addr, u8 data)
95{
96 void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04;
97 u32 tmp;
98 int ret;
99
100 tmp = FIELD_PREP(SDHCI_CDNS_HRS04_WDATA, data) |
101 FIELD_PREP(SDHCI_CDNS_HRS04_ADDR, addr);
102 writel(tmp, reg);
103
104 tmp |= SDHCI_CDNS_HRS04_WR;
105 writel(tmp, reg);
106
107 ret = readl_poll_timeout(reg, tmp, tmp & SDHCI_CDNS_HRS04_ACK, 0, 10);
108 if (ret)
109 return ret;
110
111 tmp &= ~SDHCI_CDNS_HRS04_WR;
112 writel(tmp, reg);
113
114 return 0;
115}
116
117static unsigned int sdhci_cdns_phy_param_count(struct device_node *np)
118{
119 unsigned int count = 0;
120 int i;
121
122 for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++)
123 if (of_property_read_bool(np, sdhci_cdns_phy_cfgs[i].property))
124 count++;
125
126 return count;
127}
128
129static void sdhci_cdns_phy_param_parse(struct device_node *np,
130 struct sdhci_cdns_priv *priv)
131{
132 struct sdhci_cdns_phy_param *p = priv->phy_params;
133 u32 val;
134 int ret, i;
135
136 for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) {
137 ret = of_property_read_u32(np, sdhci_cdns_phy_cfgs[i].property,
138 &val);
139 if (ret)
140 continue;
141
142 p->addr = sdhci_cdns_phy_cfgs[i].addr;
143 p->data = val;
144 p++;
145 }
146}
147
148static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv)
149{
150 int ret, i;
151
152 for (i = 0; i < priv->nr_phy_params; i++) {
153 ret = sdhci_cdns_write_phy_reg(priv, priv->phy_params[i].addr,
154 priv->phy_params[i].data);
155 if (ret)
156 return ret;
157 }
158
159 return 0;
160}
161
162static inline void *sdhci_cdns_priv(struct sdhci_host *host)
163{
164 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
165
166 return sdhci_pltfm_priv(pltfm_host);
167}
168
169static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host)
170{
171 /*
172 * Cadence's spec says the Timeout Clock Frequency is the same as the
173 * Base Clock Frequency.
174 */
175 return host->max_clk;
176}
177
178static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_priv *priv, u32 mode)
179{
180 u32 tmp;
181
182 /* The speed mode for eMMC is selected by HRS06 register */
183 tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06);
184 tmp &= ~SDHCI_CDNS_HRS06_MODE;
185 tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_MODE, mode);
186 writel(tmp, priv->hrs_addr + SDHCI_CDNS_HRS06);
187}
188
189static u32 sdhci_cdns_get_emmc_mode(struct sdhci_cdns_priv *priv)
190{
191 u32 tmp;
192
193 tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06);
194 return FIELD_GET(SDHCI_CDNS_HRS06_MODE, tmp);
195}
196
Olivier Deprez0e641232021-09-23 10:07:05 +0200197static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val)
198{
199 struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
200 void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06;
201 u32 tmp;
202 int i, ret;
203
204 if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val)))
205 return -EINVAL;
206
207 tmp = readl(reg);
208 tmp &= ~SDHCI_CDNS_HRS06_TUNE;
209 tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_TUNE, val);
210
211 /*
212 * Workaround for IP errata:
213 * The IP6116 SD/eMMC PHY design has a timing issue on receive data
214 * path. Send tune request twice.
215 */
216 for (i = 0; i < 2; i++) {
217 tmp |= SDHCI_CDNS_HRS06_TUNE_UP;
218 writel(tmp, reg);
219
220 ret = readl_poll_timeout(reg, tmp,
221 !(tmp & SDHCI_CDNS_HRS06_TUNE_UP),
222 0, 1);
223 if (ret)
224 return ret;
225 }
226
227 return 0;
228}
229
230/*
231 * In SD mode, software must not use the hardware tuning and instead perform
232 * an almost identical procedure to eMMC.
233 */
234static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
235{
236 int cur_streak = 0;
237 int max_streak = 0;
238 int end_of_streak = 0;
239 int i;
240
241 /*
242 * Do not execute tuning for UHS_SDR50 or UHS_DDR50.
243 * The delay is set by probe, based on the DT properties.
244 */
245 if (host->timing != MMC_TIMING_MMC_HS200 &&
246 host->timing != MMC_TIMING_UHS_SDR104)
247 return 0;
248
249 for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) {
250 if (sdhci_cdns_set_tune_val(host, i) ||
251 mmc_send_tuning(host->mmc, opcode, NULL)) { /* bad */
252 cur_streak = 0;
253 } else { /* good */
254 cur_streak++;
255 if (cur_streak > max_streak) {
256 max_streak = cur_streak;
257 end_of_streak = i;
258 }
259 }
260 }
261
262 if (!max_streak) {
263 dev_err(mmc_dev(host->mmc), "no tuning point found\n");
264 return -EIO;
265 }
266
267 return sdhci_cdns_set_tune_val(host, end_of_streak - max_streak / 2);
268}
269
Andrew Scullb4b6d4a2019-01-02 15:54:55 +0000270static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host,
271 unsigned int timing)
272{
273 struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
274 u32 mode;
275
276 switch (timing) {
277 case MMC_TIMING_MMC_HS:
278 mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR;
279 break;
280 case MMC_TIMING_MMC_DDR52:
281 mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR;
282 break;
283 case MMC_TIMING_MMC_HS200:
284 mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200;
285 break;
286 case MMC_TIMING_MMC_HS400:
287 if (priv->enhanced_strobe)
288 mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400ES;
289 else
290 mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400;
291 break;
292 default:
293 mode = SDHCI_CDNS_HRS06_MODE_SD;
294 break;
295 }
296
297 sdhci_cdns_set_emmc_mode(priv, mode);
298
299 /* For SD, fall back to the default handler */
300 if (mode == SDHCI_CDNS_HRS06_MODE_SD)
301 sdhci_set_uhs_signaling(host, timing);
302}
303
304static const struct sdhci_ops sdhci_cdns_ops = {
305 .set_clock = sdhci_set_clock,
306 .get_timeout_clock = sdhci_cdns_get_timeout_clock,
307 .set_bus_width = sdhci_set_bus_width,
308 .reset = sdhci_reset,
Olivier Deprez0e641232021-09-23 10:07:05 +0200309 .platform_execute_tuning = sdhci_cdns_execute_tuning,
Andrew Scullb4b6d4a2019-01-02 15:54:55 +0000310 .set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
311};
312
Olivier Deprez0e641232021-09-23 10:07:05 +0200313static const struct sdhci_pltfm_data sdhci_cdns_uniphier_pltfm_data = {
314 .ops = &sdhci_cdns_ops,
315 .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
316};
317
Andrew Scullb4b6d4a2019-01-02 15:54:55 +0000318static const struct sdhci_pltfm_data sdhci_cdns_pltfm_data = {
319 .ops = &sdhci_cdns_ops,
320};
321
Andrew Scullb4b6d4a2019-01-02 15:54:55 +0000322static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
323 struct mmc_ios *ios)
324{
325 struct sdhci_host *host = mmc_priv(mmc);
326 struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
327 u32 mode;
328
329 priv->enhanced_strobe = ios->enhanced_strobe;
330
331 mode = sdhci_cdns_get_emmc_mode(priv);
332
333 if (mode == SDHCI_CDNS_HRS06_MODE_MMC_HS400 && ios->enhanced_strobe)
334 sdhci_cdns_set_emmc_mode(priv,
335 SDHCI_CDNS_HRS06_MODE_MMC_HS400ES);
336
337 if (mode == SDHCI_CDNS_HRS06_MODE_MMC_HS400ES && !ios->enhanced_strobe)
338 sdhci_cdns_set_emmc_mode(priv,
339 SDHCI_CDNS_HRS06_MODE_MMC_HS400);
340}
341
342static int sdhci_cdns_probe(struct platform_device *pdev)
343{
344 struct sdhci_host *host;
Olivier Deprez0e641232021-09-23 10:07:05 +0200345 const struct sdhci_pltfm_data *data;
Andrew Scullb4b6d4a2019-01-02 15:54:55 +0000346 struct sdhci_pltfm_host *pltfm_host;
347 struct sdhci_cdns_priv *priv;
348 struct clk *clk;
Andrew Scullb4b6d4a2019-01-02 15:54:55 +0000349 unsigned int nr_phy_params;
350 int ret;
351 struct device *dev = &pdev->dev;
David Brazdil0f672f62019-12-10 10:32:29 +0000352 static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT;
Andrew Scullb4b6d4a2019-01-02 15:54:55 +0000353
354 clk = devm_clk_get(dev, NULL);
355 if (IS_ERR(clk))
356 return PTR_ERR(clk);
357
358 ret = clk_prepare_enable(clk);
359 if (ret)
360 return ret;
361
Olivier Deprez0e641232021-09-23 10:07:05 +0200362 data = of_device_get_match_data(dev);
363 if (!data)
364 data = &sdhci_cdns_pltfm_data;
365
Andrew Scullb4b6d4a2019-01-02 15:54:55 +0000366 nr_phy_params = sdhci_cdns_phy_param_count(dev->of_node);
Olivier Deprez0e641232021-09-23 10:07:05 +0200367 host = sdhci_pltfm_init(pdev, data,
David Brazdil0f672f62019-12-10 10:32:29 +0000368 struct_size(priv, phy_params, nr_phy_params));
Andrew Scullb4b6d4a2019-01-02 15:54:55 +0000369 if (IS_ERR(host)) {
370 ret = PTR_ERR(host);
371 goto disable_clk;
372 }
373
374 pltfm_host = sdhci_priv(host);
375 pltfm_host->clk = clk;
376
377 priv = sdhci_pltfm_priv(pltfm_host);
378 priv->nr_phy_params = nr_phy_params;
379 priv->hrs_addr = host->ioaddr;
380 priv->enhanced_strobe = false;
381 host->ioaddr += SDHCI_CDNS_SRS_BASE;
Andrew Scullb4b6d4a2019-01-02 15:54:55 +0000382 host->mmc_host_ops.hs400_enhanced_strobe =
383 sdhci_cdns_hs400_enhanced_strobe;
David Brazdil0f672f62019-12-10 10:32:29 +0000384 sdhci_enable_v4_mode(host);
385 __sdhci_read_caps(host, &version, NULL, NULL);
Andrew Scullb4b6d4a2019-01-02 15:54:55 +0000386
387 sdhci_get_of_property(pdev);
388
389 ret = mmc_of_parse(host->mmc);
390 if (ret)
391 goto free;
392
393 sdhci_cdns_phy_param_parse(dev->of_node, priv);
394
395 ret = sdhci_cdns_phy_init(priv);
396 if (ret)
397 goto free;
398
399 ret = sdhci_add_host(host);
400 if (ret)
401 goto free;
402
403 return 0;
404free:
405 sdhci_pltfm_free(pdev);
406disable_clk:
407 clk_disable_unprepare(clk);
408
409 return ret;
410}
411
412#ifdef CONFIG_PM_SLEEP
413static int sdhci_cdns_resume(struct device *dev)
414{
415 struct sdhci_host *host = dev_get_drvdata(dev);
416 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
417 struct sdhci_cdns_priv *priv = sdhci_pltfm_priv(pltfm_host);
418 int ret;
419
420 ret = clk_prepare_enable(pltfm_host->clk);
421 if (ret)
422 return ret;
423
424 ret = sdhci_cdns_phy_init(priv);
425 if (ret)
426 goto disable_clk;
427
428 ret = sdhci_resume_host(host);
429 if (ret)
430 goto disable_clk;
431
432 return 0;
433
434disable_clk:
435 clk_disable_unprepare(pltfm_host->clk);
436
437 return ret;
438}
439#endif
440
441static const struct dev_pm_ops sdhci_cdns_pm_ops = {
442 SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_suspend, sdhci_cdns_resume)
443};
444
445static const struct of_device_id sdhci_cdns_match[] = {
Olivier Deprez0e641232021-09-23 10:07:05 +0200446 {
447 .compatible = "socionext,uniphier-sd4hc",
448 .data = &sdhci_cdns_uniphier_pltfm_data,
449 },
Andrew Scullb4b6d4a2019-01-02 15:54:55 +0000450 { .compatible = "cdns,sd4hc" },
451 { /* sentinel */ }
452};
453MODULE_DEVICE_TABLE(of, sdhci_cdns_match);
454
455static struct platform_driver sdhci_cdns_driver = {
456 .driver = {
457 .name = "sdhci-cdns",
458 .pm = &sdhci_cdns_pm_ops,
459 .of_match_table = sdhci_cdns_match,
460 },
461 .probe = sdhci_cdns_probe,
462 .remove = sdhci_pltfm_unregister,
463};
464module_platform_driver(sdhci_cdns_driver);
465
466MODULE_AUTHOR("Masahiro Yamada <yamada.masahiro@socionext.com>");
467MODULE_DESCRIPTION("Cadence SD/SDIO/eMMC Host Controller Driver");
468MODULE_LICENSE("GPL");