Skip to content

Commit ff6af28

Browse files
masahir0ystorulf
authored andcommitted
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>
1 parent 85a882c commit ff6af28

File tree

4 files changed

+325
-0
lines changed

4 files changed

+325
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
* Cadence SD/SDIO/eMMC Host Controller
2+
3+
Required 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+
9+
Optional properties:
10+
For eMMC configuration, supported speed modes are not indicated by the SDHCI
11+
Capabilities Register. Instead, the following properties should be specified
12+
if 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+
20+
Example:
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+
};

drivers/mmc/host/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,17 @@ config MMC_SDHCI_OF_HLWD
165165

166166
If unsure, say N.
167167

168+
config 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+
168179
config MMC_SDHCI_CNS3XXX
169180
tristate "SDHCI support on the Cavium Networks CNS3xxx SoC"
170181
depends on ARCH_CNS3XXX

drivers/mmc/host/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o
6363
obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o
6464

6565
obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o
66+
obj-$(CONFIG_MMC_SDHCI_CADENCE) += sdhci-cadence.o
6667
obj-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o
6768
obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o
6869
obj-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o

drivers/mmc/host/sdhci-cadence.c

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
64+
struct sdhci_cdns_priv {
65+
void __iomem *hrs_addr;
66+
};
67+
68+
static 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+
85+
static 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+
94+
static 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+
101+
static 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+
110+
static 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+
145+
static 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+
153+
static const struct sdhci_pltfm_data sdhci_cdns_pltfm_data = {
154+
.ops = &sdhci_cdns_ops,
155+
};
156+
157+
static 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+
176+
static 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+
215+
static 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;
256+
free:
257+
sdhci_pltfm_free(pdev);
258+
disable_clk:
259+
clk_disable_unprepare(clk);
260+
261+
return ret;
262+
}
263+
264+
static const struct of_device_id sdhci_cdns_match[] = {
265+
{ .compatible = "cdns,sd4hc" },
266+
{ /* sentinel */ }
267+
};
268+
MODULE_DEVICE_TABLE(of, sdhci_cdns_match);
269+
270+
static 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+
};
279+
module_platform_driver(sdhci_cdns_driver);
280+
281+
MODULE_AUTHOR("Masahiro Yamada <yamada.masahiro@socionext.com>");
282+
MODULE_DESCRIPTION("Cadence SD/SDIO/eMMC Host Controller Driver");
283+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)