aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMasahiro Yamada <yamada.masahiro@socionext.com>2016-12-08 07:50:55 -0500
committerUlf Hansson <ulf.hansson@linaro.org>2016-12-08 09:02:52 -0500
commitff6af28faff53a7389230026b83e543385f7b21d (patch)
tree4288154b42e994ef80285dd108fbf3296197f6d3
parent85a882c2e91d3655927ecdc1db823d1420a65b8f (diff)
mmc: sdhci-cadence: add Cadence SD4HC support
Add a driver for the Cadence SD4HC SD/SDIO/eMMC Controller. For SD, it basically relies on the SDHCI standard code. For eMMC, this driver provides some callbacks to support the hardware part that is specific to this IP design. Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com> Acked-by: Adrian Hunter <adrian.hunter@intel.com> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
-rw-r--r--Documentation/devicetree/bindings/mmc/sdhci-cadence.txt30
-rw-r--r--drivers/mmc/host/Kconfig11
-rw-r--r--drivers/mmc/host/Makefile1
-rw-r--r--drivers/mmc/host/sdhci-cadence.c283
4 files changed, 325 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt b/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt
new file mode 100644
index 000000000000..750374fc9d94
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt
@@ -0,0 +1,30 @@
1* Cadence SD/SDIO/eMMC Host Controller
2
3Required properties:
4- compatible: should be "cdns,sd4hc".
5- reg: offset and length of the register set for the device.
6- interrupts: a single interrupt specifier.
7- clocks: phandle to the input clock.
8
9Optional properties:
10For eMMC configuration, supported speed modes are not indicated by the SDHCI
11Capabilities Register. Instead, the following properties should be specified
12if supported. See mmc.txt for details.
13- mmc-ddr-1_8v
14- mmc-ddr-1_2v
15- mmc-hs200-1_8v
16- mmc-hs200-1_2v
17- mmc-hs400-1_8v
18- mmc-hs400-1_2v
19
20Example:
21 emmc: sdhci@5a000000 {
22 compatible = "cdns,sd4hc";
23 reg = <0x5a000000 0x400>;
24 interrupts = <0 78 4>;
25 clocks = <&clk 4>;
26 bus-width = <8>;
27 mmc-ddr-1_8v;
28 mmc-hs200-1_8v;
29 mmc-hs400-1_8v;
30 };
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index bdf33fb45230..2eb97014dc3f 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -165,6 +165,17 @@ config MMC_SDHCI_OF_HLWD
165 165
166 If unsure, say N. 166 If unsure, say N.
167 167
168config MMC_SDHCI_CADENCE
169 tristate "SDHCI support for the Cadence SD/SDIO/eMMC controller"
170 depends on MMC_SDHCI_PLTFM
171 depends on OF
172 help
173 This selects the Cadence SD/SDIO/eMMC driver.
174
175 If you have a controller with this interface, say Y or M here.
176
177 If unsure, say N.
178
168config MMC_SDHCI_CNS3XXX 179config MMC_SDHCI_CNS3XXX
169 tristate "SDHCI support on the Cavium Networks CNS3xxx SoC" 180 tristate "SDHCI support on the Cavium Networks CNS3xxx SoC"
170 depends on ARCH_CNS3XXX 181 depends on ARCH_CNS3XXX
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e609bf04346b..ccc9c4cba154 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -63,6 +63,7 @@ obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o
63obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o 63obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o
64 64
65obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o 65obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o
66obj-$(CONFIG_MMC_SDHCI_CADENCE) += sdhci-cadence.o
66obj-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o 67obj-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o
67obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o 68obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o
68obj-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o 69obj-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o
diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c
new file mode 100644
index 000000000000..1501cfdac473
--- /dev/null
+++ b/drivers/mmc/host/sdhci-cadence.c
@@ -0,0 +1,283 @@
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
21#include "sdhci-pltfm.h"
22
23/* HRS - Host Register Set (specific to Cadence) */
24#define SDHCI_CDNS_HRS04 0x10 /* PHY access port */
25#define SDHCI_CDNS_HRS04_ACK BIT(26)
26#define SDHCI_CDNS_HRS04_RD BIT(25)
27#define SDHCI_CDNS_HRS04_WR BIT(24)
28#define SDHCI_CDNS_HRS04_RDATA_SHIFT 12
29#define SDHCI_CDNS_HRS04_WDATA_SHIFT 8
30#define SDHCI_CDNS_HRS04_ADDR_SHIFT 0
31
32#define SDHCI_CDNS_HRS06 0x18 /* eMMC control */
33#define SDHCI_CDNS_HRS06_TUNE_UP BIT(15)
34#define SDHCI_CDNS_HRS06_TUNE_SHIFT 8
35#define SDHCI_CDNS_HRS06_TUNE_MASK 0x3f
36#define SDHCI_CDNS_HRS06_MODE_MASK 0x7
37#define SDHCI_CDNS_HRS06_MODE_SD 0x0
38#define SDHCI_CDNS_HRS06_MODE_MMC_SDR 0x2
39#define SDHCI_CDNS_HRS06_MODE_MMC_DDR 0x3
40#define SDHCI_CDNS_HRS06_MODE_MMC_HS200 0x4
41#define SDHCI_CDNS_HRS06_MODE_MMC_HS400 0x5
42
43/* SRS - Slot Register Set (SDHCI-compatible) */
44#define SDHCI_CDNS_SRS_BASE 0x200
45
46/* PHY */
47#define SDHCI_CDNS_PHY_DLY_SD_HS 0x00
48#define SDHCI_CDNS_PHY_DLY_SD_DEFAULT 0x01
49#define SDHCI_CDNS_PHY_DLY_UHS_SDR12 0x02
50#define SDHCI_CDNS_PHY_DLY_UHS_SDR25 0x03
51#define SDHCI_CDNS_PHY_DLY_UHS_SDR50 0x04
52#define SDHCI_CDNS_PHY_DLY_UHS_DDR50 0x05
53#define SDHCI_CDNS_PHY_DLY_EMMC_LEGACY 0x06
54#define SDHCI_CDNS_PHY_DLY_EMMC_SDR 0x07
55#define SDHCI_CDNS_PHY_DLY_EMMC_DDR 0x08
56
57/*
58 * The tuned val register is 6 bit-wide, but not the whole of the range is
59 * available. The range 0-42 seems to be available (then 43 wraps around to 0)
60 * but I am not quite sure if it is official. Use only 0 to 39 for safety.
61 */
62#define SDHCI_CDNS_MAX_TUNING_LOOP 40
63
64struct sdhci_cdns_priv {
65 void __iomem *hrs_addr;
66};
67
68static void sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,
69 u8 addr, u8 data)
70{
71 void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04;
72 u32 tmp;
73
74 tmp = (data << SDHCI_CDNS_HRS04_WDATA_SHIFT) |
75 (addr << SDHCI_CDNS_HRS04_ADDR_SHIFT);
76 writel(tmp, reg);
77
78 tmp |= SDHCI_CDNS_HRS04_WR;
79 writel(tmp, reg);
80
81 tmp &= ~SDHCI_CDNS_HRS04_WR;
82 writel(tmp, reg);
83}
84
85static void sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv)
86{
87 sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_SD_HS, 4);
88 sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_SD_DEFAULT, 4);
89 sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_LEGACY, 9);
90 sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_SDR, 2);
91 sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_DDR, 3);
92}
93
94static inline void *sdhci_cdns_priv(struct sdhci_host *host)
95{
96 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
97
98 return sdhci_pltfm_priv(pltfm_host);
99}
100
101static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host)
102{
103 /*
104 * Cadence's spec says the Timeout Clock Frequency is the same as the
105 * Base Clock Frequency. Divide it by 1000 to return a value in kHz.
106 */
107 return host->max_clk / 1000;
108}
109
110static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host,
111 unsigned int timing)
112{
113 struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
114 u32 mode, tmp;
115
116 switch (timing) {
117 case MMC_TIMING_MMC_HS:
118 mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR;
119 break;
120 case MMC_TIMING_MMC_DDR52:
121 mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR;
122 break;
123 case MMC_TIMING_MMC_HS200:
124 mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200;
125 break;
126 case MMC_TIMING_MMC_HS400:
127 mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400;
128 break;
129 default:
130 mode = SDHCI_CDNS_HRS06_MODE_SD;
131 break;
132 }
133
134 /* The speed mode for eMMC is selected by HRS06 register */
135 tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06);
136 tmp &= ~SDHCI_CDNS_HRS06_MODE_MASK;
137 tmp |= mode;
138 writel(tmp, priv->hrs_addr + SDHCI_CDNS_HRS06);
139
140 /* For SD, fall back to the default handler */
141 if (mode == SDHCI_CDNS_HRS06_MODE_SD)
142 sdhci_set_uhs_signaling(host, timing);
143}
144
145static const struct sdhci_ops sdhci_cdns_ops = {
146 .set_clock = sdhci_set_clock,
147 .get_timeout_clock = sdhci_cdns_get_timeout_clock,
148 .set_bus_width = sdhci_set_bus_width,
149 .reset = sdhci_reset,
150 .set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
151};
152
153static const struct sdhci_pltfm_data sdhci_cdns_pltfm_data = {
154 .ops = &sdhci_cdns_ops,
155};
156
157static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val)
158{
159 struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
160 void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06;
161 u32 tmp;
162
163 if (WARN_ON(val > SDHCI_CDNS_HRS06_TUNE_MASK))
164 return -EINVAL;
165
166 tmp = readl(reg);
167 tmp &= ~(SDHCI_CDNS_HRS06_TUNE_MASK << SDHCI_CDNS_HRS06_TUNE_SHIFT);
168 tmp |= val << SDHCI_CDNS_HRS06_TUNE_SHIFT;
169 tmp |= SDHCI_CDNS_HRS06_TUNE_UP;
170 writel(tmp, reg);
171
172 return readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS06_TUNE_UP),
173 0, 1);
174}
175
176static int sdhci_cdns_execute_tuning(struct mmc_host *mmc, u32 opcode)
177{
178 struct sdhci_host *host = mmc_priv(mmc);
179 int cur_streak = 0;
180 int max_streak = 0;
181 int end_of_streak = 0;
182 int i;
183
184 /*
185 * This handler only implements the eMMC tuning that is specific to
186 * this controller. Fall back to the standard method for SD timing.
187 */
188 if (host->timing != MMC_TIMING_MMC_HS200)
189 return sdhci_execute_tuning(mmc, opcode);
190
191 if (WARN_ON(opcode != MMC_SEND_TUNING_BLOCK_HS200))
192 return -EINVAL;
193
194 for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) {
195 if (sdhci_cdns_set_tune_val(host, i) ||
196 mmc_send_tuning(host->mmc, opcode, NULL)) { /* bad */
197 cur_streak = 0;
198 } else { /* good */
199 cur_streak++;
200 if (cur_streak > max_streak) {
201 max_streak = cur_streak;
202 end_of_streak = i;
203 }
204 }
205 }
206
207 if (!max_streak) {
208 dev_err(mmc_dev(host->mmc), "no tuning point found\n");
209 return -EIO;
210 }
211
212 return sdhci_cdns_set_tune_val(host, end_of_streak - max_streak / 2);
213}
214
215static int sdhci_cdns_probe(struct platform_device *pdev)
216{
217 struct sdhci_host *host;
218 struct sdhci_pltfm_host *pltfm_host;
219 struct sdhci_cdns_priv *priv;
220 struct clk *clk;
221 int ret;
222
223 clk = devm_clk_get(&pdev->dev, NULL);
224 if (IS_ERR(clk))
225 return PTR_ERR(clk);
226
227 ret = clk_prepare_enable(clk);
228 if (ret)
229 return ret;
230
231 host = sdhci_pltfm_init(pdev, &sdhci_cdns_pltfm_data, sizeof(*priv));
232 if (IS_ERR(host)) {
233 ret = PTR_ERR(host);
234 goto disable_clk;
235 }
236
237 pltfm_host = sdhci_priv(host);
238 pltfm_host->clk = clk;
239
240 priv = sdhci_cdns_priv(host);
241 priv->hrs_addr = host->ioaddr;
242 host->ioaddr += SDHCI_CDNS_SRS_BASE;
243 host->mmc_host_ops.execute_tuning = sdhci_cdns_execute_tuning;
244
245 ret = mmc_of_parse(host->mmc);
246 if (ret)
247 goto free;
248
249 sdhci_cdns_phy_init(priv);
250
251 ret = sdhci_add_host(host);
252 if (ret)
253 goto free;
254
255 return 0;
256free:
257 sdhci_pltfm_free(pdev);
258disable_clk:
259 clk_disable_unprepare(clk);
260
261 return ret;
262}
263
264static const struct of_device_id sdhci_cdns_match[] = {
265 { .compatible = "cdns,sd4hc" },
266 { /* sentinel */ }
267};
268MODULE_DEVICE_TABLE(of, sdhci_cdns_match);
269
270static struct platform_driver sdhci_cdns_driver = {
271 .driver = {
272 .name = "sdhci-cdns",
273 .pm = &sdhci_pltfm_pmops,
274 .of_match_table = sdhci_cdns_match,
275 },
276 .probe = sdhci_cdns_probe,
277 .remove = sdhci_pltfm_unregister,
278};
279module_platform_driver(sdhci_cdns_driver);
280
281MODULE_AUTHOR("Masahiro Yamada <yamada.masahiro@socionext.com>");
282MODULE_DESCRIPTION("Cadence SD/SDIO/eMMC Host Controller Driver");
283MODULE_LICENSE("GPL");