diff options
-rw-r--r-- | MAINTAINERS | 7 | ||||
-rw-r--r-- | drivers/mmc/host/Kconfig | 25 | ||||
-rw-r--r-- | drivers/mmc/host/Makefile | 1 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-s3c.c | 425 |
4 files changed, 458 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index dc226e78612c..ba7ee2b872bb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -5094,6 +5094,13 @@ L: sdhci-devel@lists.ossman.eu | |||
5094 | S: Maintained | 5094 | S: Maintained |
5095 | F: drivers/mmc/host/sdhci.* | 5095 | F: drivers/mmc/host/sdhci.* |
5096 | 5096 | ||
5097 | SECURE DIGITAL HOST CONTROLLER INTERFACE (SDHCI) SAMSUNG DRIVER | ||
5098 | P: Ben Dooks | ||
5099 | M: ben-linux@fluff.org | ||
5100 | L: sdhci-devel@lists.ossman.eu | ||
5101 | S: Maintained | ||
5102 | F: drivers/mmc/host/sdhci-s3c.c | ||
5103 | |||
5097 | SECURITY SUBSYSTEM | 5104 | SECURITY SUBSYSTEM |
5098 | P: James Morris | 5105 | P: James Morris |
5099 | M: jmorris@namei.org | 5106 | M: jmorris@namei.org |
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 40111a6d8d5b..760992204a5e 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig | |||
@@ -94,6 +94,31 @@ config MMC_SDHCI_PLTFM | |||
94 | 94 | ||
95 | If unsure, say N. | 95 | If unsure, say N. |
96 | 96 | ||
97 | config MMC_SDHCI_S3C | ||
98 | tristate "SDHCI support on Samsung S3C SoC" | ||
99 | depends on MMC_SDHCI && (PLAT_S3C24XX || PLAT_S3C64XX) | ||
100 | help | ||
101 | This selects the Secure Digital Host Controller Interface (SDHCI) | ||
102 | often referrered to as the HSMMC block in some of the Samsung S3C | ||
103 | range of SoC. | ||
104 | |||
105 | Note, due to the problems with DMA, the DMA support is only | ||
106 | available with CONFIG_EXPERIMENTAL is selected. | ||
107 | |||
108 | If you have a controller with this interface, say Y or M here. | ||
109 | |||
110 | If unsure, say N. | ||
111 | |||
112 | config MMC_SDHCI_S3C_DMA | ||
113 | bool "DMA support on S3C SDHCI" | ||
114 | depends on MMC_SDHCI_S3C && EXPERIMENTAL | ||
115 | help | ||
116 | Enable DMA support on the Samsung S3C SDHCI glue. The DMA | ||
117 | has proved to be problematic if the controller encounters | ||
118 | certain errors, and thus should be treated with care. | ||
119 | |||
120 | YMMV. | ||
121 | |||
97 | config MMC_OMAP | 122 | config MMC_OMAP |
98 | tristate "TI OMAP Multimedia Card Interface support" | 123 | tristate "TI OMAP Multimedia Card Interface support" |
99 | depends on ARCH_OMAP | 124 | depends on ARCH_OMAP |
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 79da397c5fea..e637f2fa9bbd 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile | |||
@@ -15,6 +15,7 @@ obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o | |||
15 | obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o | 15 | obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o |
16 | obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o | 16 | obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o |
17 | obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o | 17 | obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o |
18 | obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o | ||
18 | obj-$(CONFIG_MMC_WBSD) += wbsd.o | 19 | obj-$(CONFIG_MMC_WBSD) += wbsd.o |
19 | obj-$(CONFIG_MMC_AU1X) += au1xmmc.o | 20 | obj-$(CONFIG_MMC_AU1X) += au1xmmc.o |
20 | obj-$(CONFIG_MMC_OMAP) += omap.o | 21 | obj-$(CONFIG_MMC_OMAP) += omap.o |
diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c new file mode 100644 index 000000000000..19246fe3d3e2 --- /dev/null +++ b/drivers/mmc/host/sdhci-s3c.c | |||
@@ -0,0 +1,425 @@ | |||
1 | /* linux/drivers/mmc/host/sdhci-s3c.c | ||
2 | * | ||
3 | * Copyright 2008 Openmoko Inc. | ||
4 | * Copyright 2008 Simtec Electronics | ||
5 | * Ben Dooks <ben@simtec.co.uk> | ||
6 | * http://armlinux.simtec.co.uk/ | ||
7 | * | ||
8 | * SDHCI (HSMMC) support for Samsung SoC | ||
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 version 2 as | ||
12 | * published by the Free Software Foundation. | ||
13 | */ | ||
14 | |||
15 | #include <linux/delay.h> | ||
16 | #include <linux/dma-mapping.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/clk.h> | ||
19 | #include <linux/io.h> | ||
20 | |||
21 | #include <linux/mmc/host.h> | ||
22 | |||
23 | #include <plat/sdhci.h> | ||
24 | #include <plat/regs-sdhci.h> | ||
25 | |||
26 | #include "sdhci.h" | ||
27 | |||
28 | #define MAX_BUS_CLK (4) | ||
29 | |||
30 | /** | ||
31 | * struct sdhci_s3c - S3C SDHCI instance | ||
32 | * @host: The SDHCI host created | ||
33 | * @pdev: The platform device we where created from. | ||
34 | * @ioarea: The resource created when we claimed the IO area. | ||
35 | * @pdata: The platform data for this controller. | ||
36 | * @cur_clk: The index of the current bus clock. | ||
37 | * @clk_io: The clock for the internal bus interface. | ||
38 | * @clk_bus: The clocks that are available for the SD/MMC bus clock. | ||
39 | */ | ||
40 | struct sdhci_s3c { | ||
41 | struct sdhci_host *host; | ||
42 | struct platform_device *pdev; | ||
43 | struct resource *ioarea; | ||
44 | struct s3c_sdhci_platdata *pdata; | ||
45 | unsigned int cur_clk; | ||
46 | |||
47 | struct clk *clk_io; | ||
48 | struct clk *clk_bus[MAX_BUS_CLK]; | ||
49 | }; | ||
50 | |||
51 | static inline struct sdhci_s3c *to_s3c(struct sdhci_host *host) | ||
52 | { | ||
53 | return sdhci_priv(host); | ||
54 | } | ||
55 | |||
56 | /** | ||
57 | * get_curclk - convert ctrl2 register to clock source number | ||
58 | * @ctrl2: Control2 register value. | ||
59 | */ | ||
60 | static u32 get_curclk(u32 ctrl2) | ||
61 | { | ||
62 | ctrl2 &= S3C_SDHCI_CTRL2_SELBASECLK_MASK; | ||
63 | ctrl2 >>= S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; | ||
64 | |||
65 | return ctrl2; | ||
66 | } | ||
67 | |||
68 | static void sdhci_s3c_check_sclk(struct sdhci_host *host) | ||
69 | { | ||
70 | struct sdhci_s3c *ourhost = to_s3c(host); | ||
71 | u32 tmp = readl(host->ioaddr + S3C_SDHCI_CONTROL2); | ||
72 | |||
73 | if (get_curclk(tmp) != ourhost->cur_clk) { | ||
74 | dev_dbg(&ourhost->pdev->dev, "restored ctrl2 clock setting\n"); | ||
75 | |||
76 | tmp &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK; | ||
77 | tmp |= ourhost->cur_clk << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; | ||
78 | writel(tmp, host->ioaddr + 0x80); | ||
79 | } | ||
80 | } | ||
81 | |||
82 | /** | ||
83 | * sdhci_s3c_get_max_clk - callback to get maximum clock frequency. | ||
84 | * @host: The SDHCI host instance. | ||
85 | * | ||
86 | * Callback to return the maximum clock rate acheivable by the controller. | ||
87 | */ | ||
88 | static unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host) | ||
89 | { | ||
90 | struct sdhci_s3c *ourhost = to_s3c(host); | ||
91 | struct clk *busclk; | ||
92 | unsigned int rate, max; | ||
93 | int clk; | ||
94 | |||
95 | /* note, a reset will reset the clock source */ | ||
96 | |||
97 | sdhci_s3c_check_sclk(host); | ||
98 | |||
99 | for (max = 0, clk = 0; clk < MAX_BUS_CLK; clk++) { | ||
100 | busclk = ourhost->clk_bus[clk]; | ||
101 | if (!busclk) | ||
102 | continue; | ||
103 | |||
104 | rate = clk_get_rate(busclk); | ||
105 | if (rate > max) | ||
106 | max = rate; | ||
107 | } | ||
108 | |||
109 | return max; | ||
110 | } | ||
111 | |||
112 | static unsigned int sdhci_s3c_get_timeout_clk(struct sdhci_host *host) | ||
113 | { | ||
114 | return sdhci_s3c_get_max_clk(host) / 1000000; | ||
115 | } | ||
116 | |||
117 | /** | ||
118 | * sdhci_s3c_consider_clock - consider one the bus clocks for current setting | ||
119 | * @ourhost: Our SDHCI instance. | ||
120 | * @src: The source clock index. | ||
121 | * @wanted: The clock frequency wanted. | ||
122 | */ | ||
123 | static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost, | ||
124 | unsigned int src, | ||
125 | unsigned int wanted) | ||
126 | { | ||
127 | unsigned long rate; | ||
128 | struct clk *clksrc = ourhost->clk_bus[src]; | ||
129 | int div; | ||
130 | |||
131 | if (!clksrc) | ||
132 | return UINT_MAX; | ||
133 | |||
134 | rate = clk_get_rate(clksrc); | ||
135 | |||
136 | for (div = 1; div < 256; div *= 2) { | ||
137 | if ((rate / div) <= wanted) | ||
138 | break; | ||
139 | } | ||
140 | |||
141 | dev_dbg(&ourhost->pdev->dev, "clk %d: rate %ld, want %d, got %ld\n", | ||
142 | src, rate, wanted, rate / div); | ||
143 | |||
144 | return (wanted - (rate / div)); | ||
145 | } | ||
146 | |||
147 | /** | ||
148 | * sdhci_s3c_set_clock - callback on clock change | ||
149 | * @host: The SDHCI host being changed | ||
150 | * @clock: The clock rate being requested. | ||
151 | * | ||
152 | * When the card's clock is going to be changed, look at the new frequency | ||
153 | * and find the best clock source to go with it. | ||
154 | */ | ||
155 | static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock) | ||
156 | { | ||
157 | struct sdhci_s3c *ourhost = to_s3c(host); | ||
158 | unsigned int best = UINT_MAX; | ||
159 | unsigned int delta; | ||
160 | int best_src = 0; | ||
161 | int src; | ||
162 | u32 ctrl; | ||
163 | |||
164 | /* don't bother if the clock is going off. */ | ||
165 | if (clock == 0) | ||
166 | return; | ||
167 | |||
168 | for (src = 0; src < MAX_BUS_CLK; src++) { | ||
169 | delta = sdhci_s3c_consider_clock(ourhost, src, clock); | ||
170 | if (delta < best) { | ||
171 | best = delta; | ||
172 | best_src = src; | ||
173 | } | ||
174 | } | ||
175 | |||
176 | dev_dbg(&ourhost->pdev->dev, | ||
177 | "selected source %d, clock %d, delta %d\n", | ||
178 | best_src, clock, best); | ||
179 | |||
180 | /* select the new clock source */ | ||
181 | |||
182 | if (ourhost->cur_clk != best_src) { | ||
183 | struct clk *clk = ourhost->clk_bus[best_src]; | ||
184 | |||
185 | /* turn clock off to card before changing clock source */ | ||
186 | writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL); | ||
187 | |||
188 | ourhost->cur_clk = best_src; | ||
189 | host->max_clk = clk_get_rate(clk); | ||
190 | host->timeout_clk = sdhci_s3c_get_timeout_clk(host); | ||
191 | |||
192 | ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2); | ||
193 | ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK; | ||
194 | ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; | ||
195 | writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2); | ||
196 | } | ||
197 | |||
198 | /* reconfigure the hardware for new clock rate */ | ||
199 | |||
200 | { | ||
201 | struct mmc_ios ios; | ||
202 | |||
203 | ios.clock = clock; | ||
204 | |||
205 | if (ourhost->pdata->cfg_card) | ||
206 | (ourhost->pdata->cfg_card)(ourhost->pdev, host->ioaddr, | ||
207 | &ios, NULL); | ||
208 | } | ||
209 | } | ||
210 | |||
211 | static struct sdhci_ops sdhci_s3c_ops = { | ||
212 | .get_max_clock = sdhci_s3c_get_max_clk, | ||
213 | .get_timeout_clock = sdhci_s3c_get_timeout_clk, | ||
214 | .set_clock = sdhci_s3c_set_clock, | ||
215 | }; | ||
216 | |||
217 | static int __devinit sdhci_s3c_probe(struct platform_device *pdev) | ||
218 | { | ||
219 | struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data; | ||
220 | struct device *dev = &pdev->dev; | ||
221 | struct sdhci_host *host; | ||
222 | struct sdhci_s3c *sc; | ||
223 | struct resource *res; | ||
224 | int ret, irq, ptr, clks; | ||
225 | |||
226 | if (!pdata) { | ||
227 | dev_err(dev, "no device data specified\n"); | ||
228 | return -ENOENT; | ||
229 | } | ||
230 | |||
231 | irq = platform_get_irq(pdev, 0); | ||
232 | if (irq < 0) { | ||
233 | dev_err(dev, "no irq specified\n"); | ||
234 | return irq; | ||
235 | } | ||
236 | |||
237 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
238 | if (!res) { | ||
239 | dev_err(dev, "no memory specified\n"); | ||
240 | return -ENOENT; | ||
241 | } | ||
242 | |||
243 | host = sdhci_alloc_host(dev, sizeof(struct sdhci_s3c)); | ||
244 | if (IS_ERR(host)) { | ||
245 | dev_err(dev, "sdhci_alloc_host() failed\n"); | ||
246 | return PTR_ERR(host); | ||
247 | } | ||
248 | |||
249 | sc = sdhci_priv(host); | ||
250 | |||
251 | sc->host = host; | ||
252 | sc->pdev = pdev; | ||
253 | sc->pdata = pdata; | ||
254 | |||
255 | platform_set_drvdata(pdev, host); | ||
256 | |||
257 | sc->clk_io = clk_get(dev, "hsmmc"); | ||
258 | if (IS_ERR(sc->clk_io)) { | ||
259 | dev_err(dev, "failed to get io clock\n"); | ||
260 | ret = PTR_ERR(sc->clk_io); | ||
261 | goto err_io_clk; | ||
262 | } | ||
263 | |||
264 | /* enable the local io clock and keep it running for the moment. */ | ||
265 | clk_enable(sc->clk_io); | ||
266 | |||
267 | for (clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) { | ||
268 | struct clk *clk; | ||
269 | char *name = pdata->clocks[ptr]; | ||
270 | |||
271 | if (name == NULL) | ||
272 | continue; | ||
273 | |||
274 | clk = clk_get(dev, name); | ||
275 | if (IS_ERR(clk)) { | ||
276 | dev_err(dev, "failed to get clock %s\n", name); | ||
277 | continue; | ||
278 | } | ||
279 | |||
280 | clks++; | ||
281 | sc->clk_bus[ptr] = clk; | ||
282 | clk_enable(clk); | ||
283 | |||
284 | dev_info(dev, "clock source %d: %s (%ld Hz)\n", | ||
285 | ptr, name, clk_get_rate(clk)); | ||
286 | } | ||
287 | |||
288 | if (clks == 0) { | ||
289 | dev_err(dev, "failed to find any bus clocks\n"); | ||
290 | ret = -ENOENT; | ||
291 | goto err_no_busclks; | ||
292 | } | ||
293 | |||
294 | sc->ioarea = request_mem_region(res->start, resource_size(res), | ||
295 | mmc_hostname(host->mmc)); | ||
296 | if (!sc->ioarea) { | ||
297 | dev_err(dev, "failed to reserve register area\n"); | ||
298 | ret = -ENXIO; | ||
299 | goto err_req_regs; | ||
300 | } | ||
301 | |||
302 | host->ioaddr = ioremap_nocache(res->start, resource_size(res)); | ||
303 | if (!host->ioaddr) { | ||
304 | dev_err(dev, "failed to map registers\n"); | ||
305 | ret = -ENXIO; | ||
306 | goto err_req_regs; | ||
307 | } | ||
308 | |||
309 | /* Ensure we have minimal gpio selected CMD/CLK/Detect */ | ||
310 | if (pdata->cfg_gpio) | ||
311 | pdata->cfg_gpio(pdev, pdata->max_width); | ||
312 | |||
313 | host->hw_name = "samsung-hsmmc"; | ||
314 | host->ops = &sdhci_s3c_ops; | ||
315 | host->quirks = 0; | ||
316 | host->irq = irq; | ||
317 | |||
318 | /* Setup quirks for the controller */ | ||
319 | |||
320 | /* Currently with ADMA enabled we are getting some length | ||
321 | * interrupts that are not being dealt with, do disable | ||
322 | * ADMA until this is sorted out. */ | ||
323 | host->quirks |= SDHCI_QUIRK_BROKEN_ADMA; | ||
324 | host->quirks |= SDHCI_QUIRK_32BIT_ADMA_SIZE; | ||
325 | |||
326 | #ifndef CONFIG_MMC_SDHCI_S3C_DMA | ||
327 | |||
328 | /* we currently see overruns on errors, so disable the SDMA | ||
329 | * support as well. */ | ||
330 | host->quirks |= SDHCI_QUIRK_BROKEN_DMA; | ||
331 | |||
332 | #endif /* CONFIG_MMC_SDHCI_S3C_DMA */ | ||
333 | |||
334 | /* It seems we do not get an DATA transfer complete on non-busy | ||
335 | * transfers, not sure if this is a problem with this specific | ||
336 | * SDHCI block, or a missing configuration that needs to be set. */ | ||
337 | host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ; | ||
338 | |||
339 | host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR | | ||
340 | SDHCI_QUIRK_32BIT_DMA_SIZE); | ||
341 | |||
342 | ret = sdhci_add_host(host); | ||
343 | if (ret) { | ||
344 | dev_err(dev, "sdhci_add_host() failed\n"); | ||
345 | goto err_add_host; | ||
346 | } | ||
347 | |||
348 | return 0; | ||
349 | |||
350 | err_add_host: | ||
351 | release_resource(sc->ioarea); | ||
352 | kfree(sc->ioarea); | ||
353 | |||
354 | err_req_regs: | ||
355 | for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) { | ||
356 | clk_disable(sc->clk_bus[ptr]); | ||
357 | clk_put(sc->clk_bus[ptr]); | ||
358 | } | ||
359 | |||
360 | err_no_busclks: | ||
361 | clk_disable(sc->clk_io); | ||
362 | clk_put(sc->clk_io); | ||
363 | |||
364 | err_io_clk: | ||
365 | sdhci_free_host(host); | ||
366 | |||
367 | return ret; | ||
368 | } | ||
369 | |||
370 | static int __devexit sdhci_s3c_remove(struct platform_device *pdev) | ||
371 | { | ||
372 | return 0; | ||
373 | } | ||
374 | |||
375 | #ifdef CONFIG_PM | ||
376 | |||
377 | static int sdhci_s3c_suspend(struct platform_device *dev, pm_message_t pm) | ||
378 | { | ||
379 | struct sdhci_host *host = platform_get_drvdata(dev); | ||
380 | |||
381 | sdhci_suspend_host(host, pm); | ||
382 | return 0; | ||
383 | } | ||
384 | |||
385 | static int sdhci_s3c_resume(struct platform_device *dev) | ||
386 | { | ||
387 | struct sdhci_host *host = platform_get_drvdata(dev); | ||
388 | |||
389 | sdhci_resume_host(host); | ||
390 | return 0; | ||
391 | } | ||
392 | |||
393 | #else | ||
394 | #define sdhci_s3c_suspend NULL | ||
395 | #define sdhci_s3c_resume NULL | ||
396 | #endif | ||
397 | |||
398 | static struct platform_driver sdhci_s3c_driver = { | ||
399 | .probe = sdhci_s3c_probe, | ||
400 | .remove = __devexit_p(sdhci_s3c_remove), | ||
401 | .suspend = sdhci_s3c_suspend, | ||
402 | .resume = sdhci_s3c_resume, | ||
403 | .driver = { | ||
404 | .owner = THIS_MODULE, | ||
405 | .name = "s3c-sdhci", | ||
406 | }, | ||
407 | }; | ||
408 | |||
409 | static int __init sdhci_s3c_init(void) | ||
410 | { | ||
411 | return platform_driver_register(&sdhci_s3c_driver); | ||
412 | } | ||
413 | |||
414 | static void __exit sdhci_s3c_exit(void) | ||
415 | { | ||
416 | platform_driver_unregister(&sdhci_s3c_driver); | ||
417 | } | ||
418 | |||
419 | module_init(sdhci_s3c_init); | ||
420 | module_exit(sdhci_s3c_exit); | ||
421 | |||
422 | MODULE_DESCRIPTION("Samsung SDHCI (HSMMC) glue"); | ||
423 | MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); | ||
424 | MODULE_LICENSE("GPL v2"); | ||
425 | MODULE_ALIAS("platform:s3c-sdhci"); | ||