aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mmc
diff options
context:
space:
mode:
authorAnton Vorontsov <avorontsov@ru.mvista.com>2009-03-16 17:14:05 -0400
committerPierre Ossman <drzeus@drzeus.cx>2009-03-24 16:30:11 -0400
commit3085e9c1b24ab2322230d35efac72147b8213865 (patch)
tree107da7334eac5e1868082fd6cbc5c362d0116306 /drivers/mmc
parent0633f654241483edc8a235ab87264ff6bbcd08d5 (diff)
mmc: Add OpenFirmware bindings for SDHCI driver
This patch adds a new driver: sdhci-of. The driver is similar to the sdhci-pci, it contains common probe code, and controller-specific ops and quirks. So far there are only Freescale eSDHC ops and quirks. Signed-off-by: Anton Vorontsov <avorontsov@ru.mvista.com> Acked-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
Diffstat (limited to 'drivers/mmc')
-rw-r--r--drivers/mmc/host/Kconfig11
-rw-r--r--drivers/mmc/host/Makefile1
-rw-r--r--drivers/mmc/host/sdhci-of.c309
3 files changed, 321 insertions, 0 deletions
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index e8a7b50a01e7..126a0da095e4 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -72,6 +72,17 @@ config MMC_RICOH_MMC
72 72
73 If unsure, say Y. 73 If unsure, say Y.
74 74
75config MMC_SDHCI_OF
76 tristate "SDHCI support on OpenFirmware platforms"
77 depends on MMC_SDHCI && PPC_OF
78 select MMC_SDHCI_IO_ACCESSORS
79 help
80 This selects the OF support for Secure Digital Host Controller
81 Interfaces. So far, only the Freescale eSDHC controller is known
82 to exist on OF platforms.
83
84 If unsure, say N.
85
75config MMC_OMAP 86config MMC_OMAP
76 tristate "TI OMAP Multimedia Card Interface support" 87 tristate "TI OMAP Multimedia Card Interface support"
77 depends on ARCH_OMAP 88 depends on ARCH_OMAP
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index c006eb47e592..970a997620e1 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_MMC_MXC) += mxcmmc.o
13obj-$(CONFIG_MMC_SDHCI) += sdhci.o 13obj-$(CONFIG_MMC_SDHCI) += sdhci.o
14obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o 14obj-$(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_WBSD) += wbsd.o 17obj-$(CONFIG_MMC_WBSD) += wbsd.o
17obj-$(CONFIG_MMC_AU1X) += au1xmmc.o 18obj-$(CONFIG_MMC_AU1X) += au1xmmc.o
18obj-$(CONFIG_MMC_OMAP) += omap.o 19obj-$(CONFIG_MMC_OMAP) += omap.o
diff --git a/drivers/mmc/host/sdhci-of.c b/drivers/mmc/host/sdhci-of.c
new file mode 100644
index 000000000000..3ff4ac3abe8b
--- /dev/null
+++ b/drivers/mmc/host/sdhci-of.c
@@ -0,0 +1,309 @@
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 "sdhci.h"
25
26struct sdhci_of_data {
27 unsigned int quirks;
28 struct sdhci_ops ops;
29};
30
31struct sdhci_of_host {
32 unsigned int clock;
33 u16 xfer_mode_shadow;
34};
35
36/*
37 * Ops and quirks for the Freescale eSDHC controller.
38 */
39
40#define ESDHC_DMA_SYSCTL 0x40c
41#define ESDHC_DMA_SNOOP 0x00000040
42
43#define ESDHC_SYSTEM_CONTROL 0x2c
44#define ESDHC_CLOCK_MASK 0x0000fff0
45#define ESDHC_PREDIV_SHIFT 8
46#define ESDHC_DIVIDER_SHIFT 4
47#define ESDHC_CLOCK_PEREN 0x00000004
48#define ESDHC_CLOCK_HCKEN 0x00000002
49#define ESDHC_CLOCK_IPGEN 0x00000001
50
51static u32 esdhc_readl(struct sdhci_host *host, int reg)
52{
53 return in_be32(host->ioaddr + reg);
54}
55
56static u16 esdhc_readw(struct sdhci_host *host, int reg)
57{
58 return in_be16(host->ioaddr + (reg ^ 0x2));
59}
60
61static u8 esdhc_readb(struct sdhci_host *host, int reg)
62{
63 return in_8(host->ioaddr + (reg ^ 0x3));
64}
65
66static void esdhc_writel(struct sdhci_host *host, u32 val, int reg)
67{
68 out_be32(host->ioaddr + reg, val);
69}
70
71static void esdhc_writew(struct sdhci_host *host, u16 val, int reg)
72{
73 struct sdhci_of_host *of_host = sdhci_priv(host);
74 int base = reg & ~0x3;
75 int shift = (reg & 0x2) * 8;
76
77 switch (reg) {
78 case SDHCI_TRANSFER_MODE:
79 /*
80 * Postpone this write, we must do it together with a
81 * command write that is down below.
82 */
83 of_host->xfer_mode_shadow = val;
84 return;
85 case SDHCI_COMMAND:
86 esdhc_writel(host, val << 16 | of_host->xfer_mode_shadow,
87 SDHCI_TRANSFER_MODE);
88 return;
89 case SDHCI_BLOCK_SIZE:
90 /*
91 * Two last DMA bits are reserved, and first one is used for
92 * non-standard blksz of 4096 bytes that we don't support
93 * yet. So clear the DMA boundary bits.
94 */
95 val &= ~SDHCI_MAKE_BLKSZ(0x7, 0);
96 /* fall through */
97 }
98 clrsetbits_be32(host->ioaddr + base, 0xffff << shift, val << shift);
99}
100
101static void esdhc_writeb(struct sdhci_host *host, u8 val, int reg)
102{
103 int base = reg & ~0x3;
104 int shift = (reg & 0x3) * 8;
105
106 clrsetbits_be32(host->ioaddr + base , 0xff << shift, val << shift);
107}
108
109static void esdhc_set_clock(struct sdhci_host *host, unsigned int clock)
110{
111 int div;
112 int pre_div = 2;
113
114 clrbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, ESDHC_CLOCK_IPGEN |
115 ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN | ESDHC_CLOCK_MASK);
116
117 if (clock == 0)
118 goto out;
119
120 if (host->max_clk / 16 > clock) {
121 for (; pre_div < 256; pre_div *= 2) {
122 if (host->max_clk / pre_div < clock * 16)
123 break;
124 }
125 }
126
127 for (div = 1; div <= 16; div++) {
128 if (host->max_clk / (div * pre_div) <= clock)
129 break;
130 }
131
132 pre_div >>= 1;
133
134 setbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, ESDHC_CLOCK_IPGEN |
135 ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN |
136 div << ESDHC_DIVIDER_SHIFT | pre_div << ESDHC_PREDIV_SHIFT);
137 mdelay(100);
138out:
139 host->clock = clock;
140}
141
142static int esdhc_enable_dma(struct sdhci_host *host)
143{
144 setbits32(host->ioaddr + ESDHC_DMA_SYSCTL, ESDHC_DMA_SNOOP);
145 return 0;
146}
147
148static unsigned int esdhc_get_max_clock(struct sdhci_host *host)
149{
150 struct sdhci_of_host *of_host = sdhci_priv(host);
151
152 return of_host->clock;
153}
154
155static unsigned int esdhc_get_timeout_clock(struct sdhci_host *host)
156{
157 struct sdhci_of_host *of_host = sdhci_priv(host);
158
159 return of_host->clock / 1000;
160}
161
162static struct sdhci_of_data sdhci_esdhc = {
163 .quirks = SDHCI_QUIRK_FORCE_BLK_SZ_2048 |
164 SDHCI_QUIRK_BROKEN_CARD_DETECTION |
165 SDHCI_QUIRK_INVERTED_WRITE_PROTECT |
166 SDHCI_QUIRK_NO_BUSY_IRQ |
167 SDHCI_QUIRK_NONSTANDARD_CLOCK |
168 SDHCI_QUIRK_PIO_NEEDS_DELAY |
169 SDHCI_QUIRK_RESTORE_IRQS_AFTER_RESET |
170 SDHCI_QUIRK_NO_CARD_NO_RESET,
171 .ops = {
172 .readl = esdhc_readl,
173 .readw = esdhc_readw,
174 .readb = esdhc_readb,
175 .writel = esdhc_writel,
176 .writew = esdhc_writew,
177 .writeb = esdhc_writeb,
178 .set_clock = esdhc_set_clock,
179 .enable_dma = esdhc_enable_dma,
180 .get_max_clock = esdhc_get_max_clock,
181 .get_timeout_clock = esdhc_get_timeout_clock,
182 },
183};
184
185#ifdef CONFIG_PM
186
187static int sdhci_of_suspend(struct of_device *ofdev, pm_message_t state)
188{
189 struct sdhci_host *host = dev_get_drvdata(&ofdev->dev);
190
191 return mmc_suspend_host(host->mmc, state);
192}
193
194static int sdhci_of_resume(struct of_device *ofdev)
195{
196 struct sdhci_host *host = dev_get_drvdata(&ofdev->dev);
197
198 return mmc_resume_host(host->mmc);
199}
200
201#else
202
203#define sdhci_of_suspend NULL
204#define sdhci_of_resume NULL
205
206#endif
207
208static int __devinit sdhci_of_probe(struct of_device *ofdev,
209 const struct of_device_id *match)
210{
211 struct device_node *np = ofdev->node;
212 struct sdhci_of_data *sdhci_of_data = match->data;
213 struct sdhci_host *host;
214 struct sdhci_of_host *of_host;
215 const u32 *clk;
216 int size;
217 int ret;
218
219 if (!of_device_is_available(np))
220 return -ENODEV;
221
222 host = sdhci_alloc_host(&ofdev->dev, sizeof(*of_host));
223 if (!host)
224 return -ENOMEM;
225
226 of_host = sdhci_priv(host);
227 dev_set_drvdata(&ofdev->dev, host);
228
229 host->ioaddr = of_iomap(np, 0);
230 if (!host->ioaddr) {
231 ret = -ENOMEM;
232 goto err_addr_map;
233 }
234
235 host->irq = irq_of_parse_and_map(np, 0);
236 if (!host->irq) {
237 ret = -EINVAL;
238 goto err_no_irq;
239 }
240
241 host->hw_name = dev_name(&ofdev->dev);
242 if (sdhci_of_data) {
243 host->quirks = sdhci_of_data->quirks;
244 host->ops = &sdhci_of_data->ops;
245 }
246
247 clk = of_get_property(np, "clock-frequency", &size);
248 if (clk && size == sizeof(*clk) && *clk)
249 of_host->clock = *clk;
250
251 ret = sdhci_add_host(host);
252 if (ret)
253 goto err_add_host;
254
255 return 0;
256
257err_add_host:
258 irq_dispose_mapping(host->irq);
259err_no_irq:
260 iounmap(host->ioaddr);
261err_addr_map:
262 sdhci_free_host(host);
263 return ret;
264}
265
266static int __devexit sdhci_of_remove(struct of_device *ofdev)
267{
268 struct sdhci_host *host = dev_get_drvdata(&ofdev->dev);
269
270 sdhci_remove_host(host, 0);
271 sdhci_free_host(host);
272 irq_dispose_mapping(host->irq);
273 iounmap(host->ioaddr);
274 return 0;
275}
276
277static const struct of_device_id sdhci_of_match[] = {
278 { .compatible = "fsl,mpc8379-esdhc", .data = &sdhci_esdhc, },
279 { .compatible = "fsl,mpc8536-esdhc", .data = &sdhci_esdhc, },
280 { .compatible = "generic-sdhci", },
281 {},
282};
283MODULE_DEVICE_TABLE(of, sdhci_of_match);
284
285static struct of_platform_driver sdhci_of_driver = {
286 .driver.name = "sdhci-of",
287 .match_table = sdhci_of_match,
288 .probe = sdhci_of_probe,
289 .remove = __devexit_p(sdhci_of_remove),
290 .suspend = sdhci_of_suspend,
291 .resume = sdhci_of_resume,
292};
293
294static int __init sdhci_of_init(void)
295{
296 return of_register_platform_driver(&sdhci_of_driver);
297}
298module_init(sdhci_of_init);
299
300static void __exit sdhci_of_exit(void)
301{
302 of_unregister_platform_driver(&sdhci_of_driver);
303}
304module_exit(sdhci_of_exit);
305
306MODULE_DESCRIPTION("Secure Digital Host Controller Interface OF driver");
307MODULE_AUTHOR("Xiaobo Xie <X.Xie@freescale.com>, "
308 "Anton Vorontsov <avorontsov@ru.mvista.com>");
309MODULE_LICENSE("GPL");