diff options
Diffstat (limited to 'drivers/mmc/host/sdhci-of-core.c')
-rw-r--r-- | drivers/mmc/host/sdhci-of-core.c | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/drivers/mmc/host/sdhci-of-core.c b/drivers/mmc/host/sdhci-of-core.c new file mode 100644 index 00000000000..01ab916c280 --- /dev/null +++ b/drivers/mmc/host/sdhci-of-core.c | |||
@@ -0,0 +1,336 @@ | |||
1 | /* | ||
2 | * OpenFirmware bindings for Secure Digital Host Controller Interface. | ||
3 | * | ||
4 | * Copyright (c) 2007 Freescale Semiconductor, Inc. | ||
5 | * Copyright (c) 2009 MontaVista Software, Inc. | ||
6 | * | ||
7 | * Authors: Xiaobo Xie <X.Xie@freescale.com> | ||
8 | * Anton Vorontsov <avorontsov@ru.mvista.com> | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License as published by | ||
12 | * the Free Software Foundation; either version 2 of the License, or (at | ||
13 | * your option) any later version. | ||
14 | */ | ||
15 | |||
16 | #include <linux/module.h> | ||
17 | #include <linux/init.h> | ||
18 | #include <linux/io.h> | ||
19 | #include <linux/interrupt.h> | ||
20 | #include <linux/delay.h> | ||
21 | #include <linux/of.h> | ||
22 | #include <linux/of_platform.h> | ||
23 | #include <linux/mmc/host.h> | ||
24 | #include <asm/machdep.h> | ||
25 | #include "sdhci.h" | ||
26 | |||
27 | struct sdhci_of_data { | ||
28 | unsigned int quirks; | ||
29 | struct sdhci_ops ops; | ||
30 | }; | ||
31 | |||
32 | struct sdhci_of_host { | ||
33 | unsigned int clock; | ||
34 | u16 xfer_mode_shadow; | ||
35 | }; | ||
36 | |||
37 | /* | ||
38 | * Ops and quirks for the Freescale eSDHC controller. | ||
39 | */ | ||
40 | |||
41 | #define ESDHC_DMA_SYSCTL 0x40c | ||
42 | #define ESDHC_DMA_SNOOP 0x00000040 | ||
43 | |||
44 | #define ESDHC_SYSTEM_CONTROL 0x2c | ||
45 | #define ESDHC_CLOCK_MASK 0x0000fff0 | ||
46 | #define ESDHC_PREDIV_SHIFT 8 | ||
47 | #define ESDHC_DIVIDER_SHIFT 4 | ||
48 | #define ESDHC_CLOCK_PEREN 0x00000004 | ||
49 | #define ESDHC_CLOCK_HCKEN 0x00000002 | ||
50 | #define ESDHC_CLOCK_IPGEN 0x00000001 | ||
51 | |||
52 | #define ESDHC_HOST_CONTROL_RES 0x05 | ||
53 | |||
54 | static u32 esdhc_readl(struct sdhci_host *host, int reg) | ||
55 | { | ||
56 | return in_be32(host->ioaddr + reg); | ||
57 | } | ||
58 | |||
59 | static u16 esdhc_readw(struct sdhci_host *host, int reg) | ||
60 | { | ||
61 | u16 ret; | ||
62 | |||
63 | if (unlikely(reg == SDHCI_HOST_VERSION)) | ||
64 | ret = in_be16(host->ioaddr + reg); | ||
65 | else | ||
66 | ret = in_be16(host->ioaddr + (reg ^ 0x2)); | ||
67 | return ret; | ||
68 | } | ||
69 | |||
70 | static u8 esdhc_readb(struct sdhci_host *host, int reg) | ||
71 | { | ||
72 | return in_8(host->ioaddr + (reg ^ 0x3)); | ||
73 | } | ||
74 | |||
75 | static void esdhc_writel(struct sdhci_host *host, u32 val, int reg) | ||
76 | { | ||
77 | out_be32(host->ioaddr + reg, val); | ||
78 | } | ||
79 | |||
80 | static void esdhc_writew(struct sdhci_host *host, u16 val, int reg) | ||
81 | { | ||
82 | struct sdhci_of_host *of_host = sdhci_priv(host); | ||
83 | int base = reg & ~0x3; | ||
84 | int shift = (reg & 0x2) * 8; | ||
85 | |||
86 | switch (reg) { | ||
87 | case SDHCI_TRANSFER_MODE: | ||
88 | /* | ||
89 | * Postpone this write, we must do it together with a | ||
90 | * command write that is down below. | ||
91 | */ | ||
92 | of_host->xfer_mode_shadow = val; | ||
93 | return; | ||
94 | case SDHCI_COMMAND: | ||
95 | esdhc_writel(host, val << 16 | of_host->xfer_mode_shadow, | ||
96 | SDHCI_TRANSFER_MODE); | ||
97 | return; | ||
98 | case SDHCI_BLOCK_SIZE: | ||
99 | /* | ||
100 | * Two last DMA bits are reserved, and first one is used for | ||
101 | * non-standard blksz of 4096 bytes that we don't support | ||
102 | * yet. So clear the DMA boundary bits. | ||
103 | */ | ||
104 | val &= ~SDHCI_MAKE_BLKSZ(0x7, 0); | ||
105 | /* fall through */ | ||
106 | } | ||
107 | clrsetbits_be32(host->ioaddr + base, 0xffff << shift, val << shift); | ||
108 | } | ||
109 | |||
110 | static void esdhc_writeb(struct sdhci_host *host, u8 val, int reg) | ||
111 | { | ||
112 | int base = reg & ~0x3; | ||
113 | int shift = (reg & 0x3) * 8; | ||
114 | |||
115 | /* Prevent SDHCI core from writing reserved bits (e.g. HISPD). */ | ||
116 | if (reg == SDHCI_HOST_CONTROL) | ||
117 | val &= ~ESDHC_HOST_CONTROL_RES; | ||
118 | |||
119 | clrsetbits_be32(host->ioaddr + base , 0xff << shift, val << shift); | ||
120 | } | ||
121 | |||
122 | static void esdhc_set_clock(struct sdhci_host *host, unsigned int clock) | ||
123 | { | ||
124 | int pre_div = 2; | ||
125 | int div = 1; | ||
126 | |||
127 | clrbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, ESDHC_CLOCK_IPGEN | | ||
128 | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN | ESDHC_CLOCK_MASK); | ||
129 | |||
130 | if (clock == 0) | ||
131 | goto out; | ||
132 | |||
133 | while (host->max_clk / pre_div / 16 > clock && pre_div < 256) | ||
134 | pre_div *= 2; | ||
135 | |||
136 | while (host->max_clk / pre_div / div > clock && div < 16) | ||
137 | div++; | ||
138 | |||
139 | dev_dbg(mmc_dev(host->mmc), "desired SD clock: %d, actual: %d\n", | ||
140 | clock, host->max_clk / pre_div / div); | ||
141 | |||
142 | pre_div >>= 1; | ||
143 | div--; | ||
144 | |||
145 | setbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, ESDHC_CLOCK_IPGEN | | ||
146 | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN | | ||
147 | div << ESDHC_DIVIDER_SHIFT | pre_div << ESDHC_PREDIV_SHIFT); | ||
148 | mdelay(100); | ||
149 | out: | ||
150 | host->clock = clock; | ||
151 | } | ||
152 | |||
153 | static int esdhc_enable_dma(struct sdhci_host *host) | ||
154 | { | ||
155 | setbits32(host->ioaddr + ESDHC_DMA_SYSCTL, ESDHC_DMA_SNOOP); | ||
156 | return 0; | ||
157 | } | ||
158 | |||
159 | static unsigned int esdhc_get_max_clock(struct sdhci_host *host) | ||
160 | { | ||
161 | struct sdhci_of_host *of_host = sdhci_priv(host); | ||
162 | |||
163 | return of_host->clock; | ||
164 | } | ||
165 | |||
166 | static unsigned int esdhc_get_min_clock(struct sdhci_host *host) | ||
167 | { | ||
168 | struct sdhci_of_host *of_host = sdhci_priv(host); | ||
169 | |||
170 | return of_host->clock / 256 / 16; | ||
171 | } | ||
172 | |||
173 | static struct sdhci_of_data sdhci_esdhc = { | ||
174 | .quirks = SDHCI_QUIRK_FORCE_BLK_SZ_2048 | | ||
175 | SDHCI_QUIRK_BROKEN_CARD_DETECTION | | ||
176 | SDHCI_QUIRK_NO_BUSY_IRQ | | ||
177 | SDHCI_QUIRK_NONSTANDARD_CLOCK | | ||
178 | SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | | ||
179 | SDHCI_QUIRK_PIO_NEEDS_DELAY | | ||
180 | SDHCI_QUIRK_RESTORE_IRQS_AFTER_RESET | | ||
181 | SDHCI_QUIRK_NO_CARD_NO_RESET, | ||
182 | .ops = { | ||
183 | .readl = esdhc_readl, | ||
184 | .readw = esdhc_readw, | ||
185 | .readb = esdhc_readb, | ||
186 | .writel = esdhc_writel, | ||
187 | .writew = esdhc_writew, | ||
188 | .writeb = esdhc_writeb, | ||
189 | .set_clock = esdhc_set_clock, | ||
190 | .enable_dma = esdhc_enable_dma, | ||
191 | .get_max_clock = esdhc_get_max_clock, | ||
192 | .get_min_clock = esdhc_get_min_clock, | ||
193 | }, | ||
194 | }; | ||
195 | |||
196 | #ifdef CONFIG_PM | ||
197 | |||
198 | static int sdhci_of_suspend(struct of_device *ofdev, pm_message_t state) | ||
199 | { | ||
200 | struct sdhci_host *host = dev_get_drvdata(&ofdev->dev); | ||
201 | |||
202 | return mmc_suspend_host(host->mmc, state); | ||
203 | } | ||
204 | |||
205 | static int sdhci_of_resume(struct of_device *ofdev) | ||
206 | { | ||
207 | struct sdhci_host *host = dev_get_drvdata(&ofdev->dev); | ||
208 | |||
209 | return mmc_resume_host(host->mmc); | ||
210 | } | ||
211 | |||
212 | #else | ||
213 | |||
214 | #define sdhci_of_suspend NULL | ||
215 | #define sdhci_of_resume NULL | ||
216 | |||
217 | #endif | ||
218 | |||
219 | static bool __devinit sdhci_of_wp_inverted(struct device_node *np) | ||
220 | { | ||
221 | if (of_get_property(np, "sdhci,wp-inverted", NULL)) | ||
222 | return true; | ||
223 | |||
224 | /* Old device trees don't have the wp-inverted property. */ | ||
225 | return machine_is(mpc837x_rdb) || machine_is(mpc837x_mds); | ||
226 | } | ||
227 | |||
228 | static int __devinit sdhci_of_probe(struct of_device *ofdev, | ||
229 | const struct of_device_id *match) | ||
230 | { | ||
231 | struct device_node *np = ofdev->node; | ||
232 | struct sdhci_of_data *sdhci_of_data = match->data; | ||
233 | struct sdhci_host *host; | ||
234 | struct sdhci_of_host *of_host; | ||
235 | const u32 *clk; | ||
236 | int size; | ||
237 | int ret; | ||
238 | |||
239 | if (!of_device_is_available(np)) | ||
240 | return -ENODEV; | ||
241 | |||
242 | host = sdhci_alloc_host(&ofdev->dev, sizeof(*of_host)); | ||
243 | if (IS_ERR(host)) | ||
244 | return -ENOMEM; | ||
245 | |||
246 | of_host = sdhci_priv(host); | ||
247 | dev_set_drvdata(&ofdev->dev, host); | ||
248 | |||
249 | host->ioaddr = of_iomap(np, 0); | ||
250 | if (!host->ioaddr) { | ||
251 | ret = -ENOMEM; | ||
252 | goto err_addr_map; | ||
253 | } | ||
254 | |||
255 | host->irq = irq_of_parse_and_map(np, 0); | ||
256 | if (!host->irq) { | ||
257 | ret = -EINVAL; | ||
258 | goto err_no_irq; | ||
259 | } | ||
260 | |||
261 | host->hw_name = dev_name(&ofdev->dev); | ||
262 | if (sdhci_of_data) { | ||
263 | host->quirks = sdhci_of_data->quirks; | ||
264 | host->ops = &sdhci_of_data->ops; | ||
265 | } | ||
266 | |||
267 | if (of_get_property(np, "sdhci,1-bit-only", NULL)) | ||
268 | host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA; | ||
269 | |||
270 | if (sdhci_of_wp_inverted(np)) | ||
271 | host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT; | ||
272 | |||
273 | clk = of_get_property(np, "clock-frequency", &size); | ||
274 | if (clk && size == sizeof(*clk) && *clk) | ||
275 | of_host->clock = *clk; | ||
276 | |||
277 | ret = sdhci_add_host(host); | ||
278 | if (ret) | ||
279 | goto err_add_host; | ||
280 | |||
281 | return 0; | ||
282 | |||
283 | err_add_host: | ||
284 | irq_dispose_mapping(host->irq); | ||
285 | err_no_irq: | ||
286 | iounmap(host->ioaddr); | ||
287 | err_addr_map: | ||
288 | sdhci_free_host(host); | ||
289 | return ret; | ||
290 | } | ||
291 | |||
292 | static int __devexit sdhci_of_remove(struct of_device *ofdev) | ||
293 | { | ||
294 | struct sdhci_host *host = dev_get_drvdata(&ofdev->dev); | ||
295 | |||
296 | sdhci_remove_host(host, 0); | ||
297 | sdhci_free_host(host); | ||
298 | irq_dispose_mapping(host->irq); | ||
299 | iounmap(host->ioaddr); | ||
300 | return 0; | ||
301 | } | ||
302 | |||
303 | static const struct of_device_id sdhci_of_match[] = { | ||
304 | { .compatible = "fsl,mpc8379-esdhc", .data = &sdhci_esdhc, }, | ||
305 | { .compatible = "fsl,mpc8536-esdhc", .data = &sdhci_esdhc, }, | ||
306 | { .compatible = "fsl,esdhc", .data = &sdhci_esdhc, }, | ||
307 | { .compatible = "generic-sdhci", }, | ||
308 | {}, | ||
309 | }; | ||
310 | MODULE_DEVICE_TABLE(of, sdhci_of_match); | ||
311 | |||
312 | static struct of_platform_driver sdhci_of_driver = { | ||
313 | .driver.name = "sdhci-of", | ||
314 | .match_table = sdhci_of_match, | ||
315 | .probe = sdhci_of_probe, | ||
316 | .remove = __devexit_p(sdhci_of_remove), | ||
317 | .suspend = sdhci_of_suspend, | ||
318 | .resume = sdhci_of_resume, | ||
319 | }; | ||
320 | |||
321 | static int __init sdhci_of_init(void) | ||
322 | { | ||
323 | return of_register_platform_driver(&sdhci_of_driver); | ||
324 | } | ||
325 | module_init(sdhci_of_init); | ||
326 | |||
327 | static void __exit sdhci_of_exit(void) | ||
328 | { | ||
329 | of_unregister_platform_driver(&sdhci_of_driver); | ||
330 | } | ||
331 | module_exit(sdhci_of_exit); | ||
332 | |||
333 | MODULE_DESCRIPTION("Secure Digital Host Controller Interface OF driver"); | ||
334 | MODULE_AUTHOR("Xiaobo Xie <X.Xie@freescale.com>, " | ||
335 | "Anton Vorontsov <avorontsov@ru.mvista.com>"); | ||
336 | MODULE_LICENSE("GPL"); | ||