diff options
author | Scott Branden <sbranden@broadcom.com> | 2015-02-09 19:06:30 -0500 |
---|---|---|
committer | Ulf Hansson <ulf.hansson@linaro.org> | 2015-03-23 09:13:41 -0400 |
commit | b580c52d58d92f1e054c8b4515cf0fa617a77a26 (patch) | |
tree | 2ec9b22e7bf982fdd8c98f64ad7f577a4557b6c6 | |
parent | 61fe2f80f82d7596c9046e90794e1675617c9a26 (diff) |
mmc: sdhci-iproc: add IPROC SDHCI driver
Add IPROC SDHCI driver for IPROC family of Broadcom devices.
Acked-by: Ray Jui <rjui@broadcom.com>
Signed-off-by: Corneliu Doban <cdoban@broadcom.com>
Signed-off-by: Scott Branden <sbranden@broadcom.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
-rw-r--r-- | drivers/mmc/host/Kconfig | 14 | ||||
-rw-r--r-- | drivers/mmc/host/Makefile | 1 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-iproc.c | 241 |
3 files changed, 256 insertions, 0 deletions
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 61ac63a3776a..355db8155a5b 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig | |||
@@ -307,6 +307,20 @@ config MMC_SDHCI_F_SDH30 | |||
307 | 307 | ||
308 | If unsure, say N. | 308 | If unsure, say N. |
309 | 309 | ||
310 | config MMC_SDHCI_IPROC | ||
311 | tristate "SDHCI platform support for the iProc SD/MMC Controller" | ||
312 | depends on ARCH_BCM_IPROC || COMPILE_TEST | ||
313 | depends on MMC_SDHCI_PLTFM | ||
314 | default ARCH_BCM_IPROC | ||
315 | select MMC_SDHCI_IO_ACCESSORS | ||
316 | help | ||
317 | This selects the iProc SD/MMC controller. | ||
318 | |||
319 | If you have an IPROC platform with SD or MMC devices, | ||
320 | say Y or M here. | ||
321 | |||
322 | If unsure, say N. | ||
323 | |||
310 | config MMC_MOXART | 324 | config MMC_MOXART |
311 | tristate "MOXART SD/MMC Host Controller support" | 325 | tristate "MOXART SD/MMC Host Controller support" |
312 | depends on ARCH_MOXART && MMC | 326 | depends on ARCH_MOXART && MMC |
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 6a7cfe0de332..711e913450f5 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile | |||
@@ -71,6 +71,7 @@ obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o | |||
71 | obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o | 71 | obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o |
72 | obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o | 72 | obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o |
73 | obj-$(CONFIG_MMC_SDHCI_BCM2835) += sdhci-bcm2835.o | 73 | obj-$(CONFIG_MMC_SDHCI_BCM2835) += sdhci-bcm2835.o |
74 | obj-$(CONFIG_MMC_SDHCI_IPROC) += sdhci-iproc.o | ||
74 | obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o | 75 | obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o |
75 | obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o | 76 | obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o |
76 | 77 | ||
diff --git a/drivers/mmc/host/sdhci-iproc.c b/drivers/mmc/host/sdhci-iproc.c new file mode 100644 index 000000000000..4139d34971ad --- /dev/null +++ b/drivers/mmc/host/sdhci-iproc.c | |||
@@ -0,0 +1,241 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Broadcom Corporation | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License as | ||
6 | * published by the Free Software Foundation version 2. | ||
7 | * | ||
8 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any | ||
9 | * kind, whether express or implied; without even the implied warranty | ||
10 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | * GNU General Public License for more details. | ||
12 | */ | ||
13 | |||
14 | /* | ||
15 | * iProc SDHCI platform driver | ||
16 | */ | ||
17 | |||
18 | #include <linux/delay.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/mmc/host.h> | ||
21 | #include <linux/of.h> | ||
22 | #include <linux/of_device.h> | ||
23 | #include "sdhci-pltfm.h" | ||
24 | |||
25 | struct sdhci_iproc_data { | ||
26 | const struct sdhci_pltfm_data *pdata; | ||
27 | u32 caps; | ||
28 | u32 caps1; | ||
29 | }; | ||
30 | |||
31 | struct sdhci_iproc_host { | ||
32 | const struct sdhci_iproc_data *data; | ||
33 | u32 shadow_cmd; | ||
34 | u32 shadow_blk; | ||
35 | }; | ||
36 | |||
37 | #define REG_OFFSET_IN_BITS(reg) ((reg) << 3 & 0x18) | ||
38 | |||
39 | static inline u32 sdhci_iproc_readl(struct sdhci_host *host, int reg) | ||
40 | { | ||
41 | u32 val = readl(host->ioaddr + reg); | ||
42 | |||
43 | pr_debug("%s: readl [0x%02x] 0x%08x\n", | ||
44 | mmc_hostname(host->mmc), reg, val); | ||
45 | return val; | ||
46 | } | ||
47 | |||
48 | static u16 sdhci_iproc_readw(struct sdhci_host *host, int reg) | ||
49 | { | ||
50 | u32 val = sdhci_iproc_readl(host, (reg & ~3)); | ||
51 | u16 word = val >> REG_OFFSET_IN_BITS(reg) & 0xffff; | ||
52 | return word; | ||
53 | } | ||
54 | |||
55 | static u8 sdhci_iproc_readb(struct sdhci_host *host, int reg) | ||
56 | { | ||
57 | u32 val = sdhci_iproc_readl(host, (reg & ~3)); | ||
58 | u8 byte = val >> REG_OFFSET_IN_BITS(reg) & 0xff; | ||
59 | return byte; | ||
60 | } | ||
61 | |||
62 | static inline void sdhci_iproc_writel(struct sdhci_host *host, u32 val, int reg) | ||
63 | { | ||
64 | pr_debug("%s: writel [0x%02x] 0x%08x\n", | ||
65 | mmc_hostname(host->mmc), reg, val); | ||
66 | |||
67 | writel(val, host->ioaddr + reg); | ||
68 | |||
69 | if (host->clock <= 400000) { | ||
70 | /* Round up to micro-second four SD clock delay */ | ||
71 | if (host->clock) | ||
72 | udelay((4 * 1000000 + host->clock - 1) / host->clock); | ||
73 | else | ||
74 | udelay(10); | ||
75 | } | ||
76 | } | ||
77 | |||
78 | /* | ||
79 | * The Arasan has a bugette whereby it may lose the content of successive | ||
80 | * writes to the same register that are within two SD-card clock cycles of | ||
81 | * each other (a clock domain crossing problem). The data | ||
82 | * register does not have this problem, which is just as well - otherwise we'd | ||
83 | * have to nobble the DMA engine too. | ||
84 | * | ||
85 | * This wouldn't be a problem with the code except that we can only write the | ||
86 | * controller with 32-bit writes. So two different 16-bit registers are | ||
87 | * written back to back creates the problem. | ||
88 | * | ||
89 | * In reality, this only happens when SDHCI_BLOCK_SIZE and SDHCI_BLOCK_COUNT | ||
90 | * are written followed by SDHCI_TRANSFER_MODE and SDHCI_COMMAND. | ||
91 | * The BLOCK_SIZE and BLOCK_COUNT are meaningless until a command issued so | ||
92 | * the work around can be further optimized. We can keep shadow values of | ||
93 | * BLOCK_SIZE, BLOCK_COUNT, and TRANSFER_MODE until a COMMAND is issued. | ||
94 | * Then, write the BLOCK_SIZE+BLOCK_COUNT in a single 32-bit write followed | ||
95 | * by the TRANSFER+COMMAND in another 32-bit write. | ||
96 | */ | ||
97 | static void sdhci_iproc_writew(struct sdhci_host *host, u16 val, int reg) | ||
98 | { | ||
99 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | ||
100 | struct sdhci_iproc_host *iproc_host = pltfm_host->priv; | ||
101 | u32 word_shift = REG_OFFSET_IN_BITS(reg); | ||
102 | u32 mask = 0xffff << word_shift; | ||
103 | u32 oldval, newval; | ||
104 | |||
105 | if (reg == SDHCI_COMMAND) { | ||
106 | /* Write the block now as we are issuing a command */ | ||
107 | if (iproc_host->shadow_blk != 0) { | ||
108 | sdhci_iproc_writel(host, iproc_host->shadow_blk, | ||
109 | SDHCI_BLOCK_SIZE); | ||
110 | iproc_host->shadow_blk = 0; | ||
111 | } | ||
112 | oldval = iproc_host->shadow_cmd; | ||
113 | } else if (reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) { | ||
114 | /* Block size and count are stored in shadow reg */ | ||
115 | oldval = iproc_host->shadow_blk; | ||
116 | } else { | ||
117 | /* Read reg, all other registers are not shadowed */ | ||
118 | oldval = sdhci_iproc_readl(host, (reg & ~3)); | ||
119 | } | ||
120 | newval = (oldval & ~mask) | (val << word_shift); | ||
121 | |||
122 | if (reg == SDHCI_TRANSFER_MODE) { | ||
123 | /* Save the transfer mode until the command is issued */ | ||
124 | iproc_host->shadow_cmd = newval; | ||
125 | } else if (reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) { | ||
126 | /* Save the block info until the command is issued */ | ||
127 | iproc_host->shadow_blk = newval; | ||
128 | } else { | ||
129 | /* Command or other regular 32-bit write */ | ||
130 | sdhci_iproc_writel(host, newval, reg & ~3); | ||
131 | } | ||
132 | } | ||
133 | |||
134 | static void sdhci_iproc_writeb(struct sdhci_host *host, u8 val, int reg) | ||
135 | { | ||
136 | u32 oldval = sdhci_iproc_readl(host, (reg & ~3)); | ||
137 | u32 byte_shift = REG_OFFSET_IN_BITS(reg); | ||
138 | u32 mask = 0xff << byte_shift; | ||
139 | u32 newval = (oldval & ~mask) | (val << byte_shift); | ||
140 | |||
141 | sdhci_iproc_writel(host, newval, reg & ~3); | ||
142 | } | ||
143 | |||
144 | static const struct sdhci_ops sdhci_iproc_ops = { | ||
145 | .read_l = sdhci_iproc_readl, | ||
146 | .read_w = sdhci_iproc_readw, | ||
147 | .read_b = sdhci_iproc_readb, | ||
148 | .write_l = sdhci_iproc_writel, | ||
149 | .write_w = sdhci_iproc_writew, | ||
150 | .write_b = sdhci_iproc_writeb, | ||
151 | .set_clock = sdhci_set_clock, | ||
152 | .get_max_clock = sdhci_pltfm_clk_get_max_clock, | ||
153 | .set_bus_width = sdhci_set_bus_width, | ||
154 | .reset = sdhci_reset, | ||
155 | .set_uhs_signaling = sdhci_set_uhs_signaling, | ||
156 | }; | ||
157 | |||
158 | static const struct sdhci_pltfm_data sdhci_iproc_pltfm_data = { | ||
159 | .quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK, | ||
160 | .quirks2 = SDHCI_QUIRK2_ACMD23_BROKEN, | ||
161 | .ops = &sdhci_iproc_ops, | ||
162 | }; | ||
163 | |||
164 | static const struct sdhci_iproc_data iproc_data = { | ||
165 | .pdata = &sdhci_iproc_pltfm_data, | ||
166 | .caps = 0x05E90000, | ||
167 | .caps1 = 0x00000064, | ||
168 | }; | ||
169 | |||
170 | static const struct of_device_id sdhci_iproc_of_match[] = { | ||
171 | { .compatible = "brcm,sdhci-iproc-cygnus", .data = &iproc_data }, | ||
172 | { } | ||
173 | }; | ||
174 | MODULE_DEVICE_TABLE(of, sdhci_iproc_of_match); | ||
175 | |||
176 | static int sdhci_iproc_probe(struct platform_device *pdev) | ||
177 | { | ||
178 | const struct of_device_id *match; | ||
179 | const struct sdhci_iproc_data *iproc_data; | ||
180 | struct sdhci_host *host; | ||
181 | struct sdhci_iproc_host *iproc_host; | ||
182 | struct sdhci_pltfm_host *pltfm_host; | ||
183 | int ret; | ||
184 | |||
185 | match = of_match_device(sdhci_iproc_of_match, &pdev->dev); | ||
186 | if (!match) | ||
187 | return -EINVAL; | ||
188 | iproc_data = match->data; | ||
189 | |||
190 | host = sdhci_pltfm_init(pdev, iproc_data->pdata, sizeof(*iproc_host)); | ||
191 | if (IS_ERR(host)) | ||
192 | return PTR_ERR(host); | ||
193 | |||
194 | pltfm_host = sdhci_priv(host); | ||
195 | iproc_host = sdhci_pltfm_priv(pltfm_host); | ||
196 | |||
197 | iproc_host->data = iproc_data; | ||
198 | |||
199 | mmc_of_parse(host->mmc); | ||
200 | sdhci_get_of_property(pdev); | ||
201 | |||
202 | /* Enable EMMC 1/8V DDR capable */ | ||
203 | host->mmc->caps |= MMC_CAP_1_8V_DDR; | ||
204 | |||
205 | pltfm_host->clk = devm_clk_get(&pdev->dev, NULL); | ||
206 | if (IS_ERR(pltfm_host->clk)) { | ||
207 | ret = PTR_ERR(pltfm_host->clk); | ||
208 | goto err; | ||
209 | } | ||
210 | |||
211 | if (iproc_host->data->pdata->quirks & SDHCI_QUIRK_MISSING_CAPS) { | ||
212 | host->caps = iproc_host->data->caps; | ||
213 | host->caps1 = iproc_host->data->caps1; | ||
214 | } | ||
215 | |||
216 | return sdhci_add_host(host); | ||
217 | |||
218 | err: | ||
219 | sdhci_pltfm_free(pdev); | ||
220 | return ret; | ||
221 | } | ||
222 | |||
223 | static int sdhci_iproc_remove(struct platform_device *pdev) | ||
224 | { | ||
225 | return sdhci_pltfm_unregister(pdev); | ||
226 | } | ||
227 | |||
228 | static struct platform_driver sdhci_iproc_driver = { | ||
229 | .driver = { | ||
230 | .name = "sdhci-iproc", | ||
231 | .of_match_table = sdhci_iproc_of_match, | ||
232 | .pm = SDHCI_PLTFM_PMOPS, | ||
233 | }, | ||
234 | .probe = sdhci_iproc_probe, | ||
235 | .remove = sdhci_iproc_remove, | ||
236 | }; | ||
237 | module_platform_driver(sdhci_iproc_driver); | ||
238 | |||
239 | MODULE_AUTHOR("Broadcom"); | ||
240 | MODULE_DESCRIPTION("IPROC SDHCI driver"); | ||
241 | MODULE_LICENSE("GPL v2"); | ||