aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorBen Dooks <ben@simtec.co.uk>2009-06-14 08:52:37 -0400
committerPierre Ossman <pierre@ossman.eu>2009-06-21 15:00:57 -0400
commit0d1bb41ad4ebca92fafbab6d6c60438d7efef386 (patch)
tree6b76d5533aae2c78a51a0aab4f0a7e2ff086fbf9 /drivers
parentc277331d5fbaae5772ed19862feefa91f4e477d3 (diff)
sdhci-s3c: Samsung S3C based SDHCI controller glue
Add support for the 'HSMMC' block(s) in the Samsung SoC line. These are compatible with the SDHCI driver so add the necessary setup and driver binding for the platform devices. Signed-off-by: Ben Dooks <ben@simtec.co.uk> Signed-off-by: Pierre Ossman <pierre@ossman.eu>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mmc/host/Kconfig25
-rw-r--r--drivers/mmc/host/Makefile1
-rw-r--r--drivers/mmc/host/sdhci-s3c.c425
3 files changed, 451 insertions, 0 deletions
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
97config 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
112config 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
97config MMC_OMAP 122config 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
15obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o 15obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o
16obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o 16obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o
17obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o 17obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o
18obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o
18obj-$(CONFIG_MMC_WBSD) += wbsd.o 19obj-$(CONFIG_MMC_WBSD) += wbsd.o
19obj-$(CONFIG_MMC_AU1X) += au1xmmc.o 20obj-$(CONFIG_MMC_AU1X) += au1xmmc.o
20obj-$(CONFIG_MMC_OMAP) += omap.o 21obj-$(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 */
40struct 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
51static 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 */
60static 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
68static 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*/
88static 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
112static 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 */
123static 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*/
155static 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
211static 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
217static 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
370static int __devexit sdhci_s3c_remove(struct platform_device *pdev)
371{
372 return 0;
373}
374
375#ifdef CONFIG_PM
376
377static 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
385static 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
398static 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
409static int __init sdhci_s3c_init(void)
410{
411 return platform_driver_register(&sdhci_s3c_driver);
412}
413
414static void __exit sdhci_s3c_exit(void)
415{
416 platform_driver_unregister(&sdhci_s3c_driver);
417}
418
419module_init(sdhci_s3c_init);
420module_exit(sdhci_s3c_exit);
421
422MODULE_DESCRIPTION("Samsung SDHCI (HSMMC) glue");
423MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
424MODULE_LICENSE("GPL v2");
425MODULE_ALIAS("platform:s3c-sdhci");