Commit | Line | Data |
---|---|---|
3085e9c1 AV |
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> | |
8226a219 | 24 | #include <asm/machdep.h> |
7657c3a7 | 25 | #include "sdhci-of.h" |
3085e9c1 AV |
26 | #include "sdhci.h" |
27 | ||
7657c3a7 | 28 | #ifdef CONFIG_MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER |
3085e9c1 AV |
29 | |
30 | /* | |
7657c3a7 AH |
31 | * These accessors are designed for big endian hosts doing I/O to |
32 | * little endian controllers incorporating a 32-bit hardware byte swapper. | |
3085e9c1 AV |
33 | */ |
34 | ||
7657c3a7 | 35 | u32 sdhci_be32bs_readl(struct sdhci_host *host, int reg) |
3085e9c1 AV |
36 | { |
37 | return in_be32(host->ioaddr + reg); | |
38 | } | |
39 | ||
7657c3a7 | 40 | u16 sdhci_be32bs_readw(struct sdhci_host *host, int reg) |
3085e9c1 | 41 | { |
7657c3a7 | 42 | return in_be16(host->ioaddr + (reg ^ 0x2)); |
3085e9c1 AV |
43 | } |
44 | ||
7657c3a7 | 45 | u8 sdhci_be32bs_readb(struct sdhci_host *host, int reg) |
3085e9c1 AV |
46 | { |
47 | return in_8(host->ioaddr + (reg ^ 0x3)); | |
48 | } | |
49 | ||
7657c3a7 | 50 | void sdhci_be32bs_writel(struct sdhci_host *host, u32 val, int reg) |
3085e9c1 AV |
51 | { |
52 | out_be32(host->ioaddr + reg, val); | |
53 | } | |
54 | ||
7657c3a7 | 55 | void sdhci_be32bs_writew(struct sdhci_host *host, u16 val, int reg) |
3085e9c1 AV |
56 | { |
57 | struct sdhci_of_host *of_host = sdhci_priv(host); | |
58 | int base = reg & ~0x3; | |
59 | int shift = (reg & 0x2) * 8; | |
60 | ||
61 | switch (reg) { | |
62 | case SDHCI_TRANSFER_MODE: | |
63 | /* | |
64 | * Postpone this write, we must do it together with a | |
65 | * command write that is down below. | |
66 | */ | |
67 | of_host->xfer_mode_shadow = val; | |
68 | return; | |
69 | case SDHCI_COMMAND: | |
7657c3a7 AH |
70 | sdhci_be32bs_writel(host, val << 16 | of_host->xfer_mode_shadow, |
71 | SDHCI_TRANSFER_MODE); | |
3085e9c1 | 72 | return; |
3085e9c1 AV |
73 | } |
74 | clrsetbits_be32(host->ioaddr + base, 0xffff << shift, val << shift); | |
75 | } | |
76 | ||
7657c3a7 | 77 | void sdhci_be32bs_writeb(struct sdhci_host *host, u8 val, int reg) |
3085e9c1 AV |
78 | { |
79 | int base = reg & ~0x3; | |
80 | int shift = (reg & 0x3) * 8; | |
81 | ||
82 | clrsetbits_be32(host->ioaddr + base , 0xff << shift, val << shift); | |
83 | } | |
7657c3a7 | 84 | #endif /* CONFIG_MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER */ |
3085e9c1 AV |
85 | |
86 | #ifdef CONFIG_PM | |
87 | ||
2dc11581 | 88 | static int sdhci_of_suspend(struct platform_device *ofdev, pm_message_t state) |
3085e9c1 AV |
89 | { |
90 | struct sdhci_host *host = dev_get_drvdata(&ofdev->dev); | |
91 | ||
1a13f8fa | 92 | return mmc_suspend_host(host->mmc); |
3085e9c1 AV |
93 | } |
94 | ||
2dc11581 | 95 | static int sdhci_of_resume(struct platform_device *ofdev) |
3085e9c1 AV |
96 | { |
97 | struct sdhci_host *host = dev_get_drvdata(&ofdev->dev); | |
98 | ||
99 | return mmc_resume_host(host->mmc); | |
100 | } | |
101 | ||
102 | #else | |
103 | ||
104 | #define sdhci_of_suspend NULL | |
105 | #define sdhci_of_resume NULL | |
106 | ||
107 | #endif | |
108 | ||
8226a219 AV |
109 | static bool __devinit sdhci_of_wp_inverted(struct device_node *np) |
110 | { | |
111 | if (of_get_property(np, "sdhci,wp-inverted", NULL)) | |
112 | return true; | |
113 | ||
114 | /* Old device trees don't have the wp-inverted property. */ | |
115 | return machine_is(mpc837x_rdb) || machine_is(mpc837x_mds); | |
116 | } | |
117 | ||
2dc11581 | 118 | static int __devinit sdhci_of_probe(struct platform_device *ofdev, |
3085e9c1 AV |
119 | const struct of_device_id *match) |
120 | { | |
61c7a080 | 121 | struct device_node *np = ofdev->dev.of_node; |
3085e9c1 AV |
122 | struct sdhci_of_data *sdhci_of_data = match->data; |
123 | struct sdhci_host *host; | |
124 | struct sdhci_of_host *of_host; | |
da81c3b9 | 125 | const __be32 *clk; |
3085e9c1 AV |
126 | int size; |
127 | int ret; | |
128 | ||
129 | if (!of_device_is_available(np)) | |
130 | return -ENODEV; | |
131 | ||
132 | host = sdhci_alloc_host(&ofdev->dev, sizeof(*of_host)); | |
2198a64a | 133 | if (IS_ERR(host)) |
3085e9c1 AV |
134 | return -ENOMEM; |
135 | ||
136 | of_host = sdhci_priv(host); | |
137 | dev_set_drvdata(&ofdev->dev, host); | |
138 | ||
139 | host->ioaddr = of_iomap(np, 0); | |
140 | if (!host->ioaddr) { | |
141 | ret = -ENOMEM; | |
142 | goto err_addr_map; | |
143 | } | |
144 | ||
145 | host->irq = irq_of_parse_and_map(np, 0); | |
146 | if (!host->irq) { | |
147 | ret = -EINVAL; | |
148 | goto err_no_irq; | |
149 | } | |
150 | ||
151 | host->hw_name = dev_name(&ofdev->dev); | |
152 | if (sdhci_of_data) { | |
153 | host->quirks = sdhci_of_data->quirks; | |
154 | host->ops = &sdhci_of_data->ops; | |
155 | } | |
156 | ||
c4512f79 JH |
157 | if (of_get_property(np, "sdhci,auto-cmd12", NULL)) |
158 | host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12; | |
159 | ||
160 | ||
5fe23c7f AV |
161 | if (of_get_property(np, "sdhci,1-bit-only", NULL)) |
162 | host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA; | |
163 | ||
8226a219 AV |
164 | if (sdhci_of_wp_inverted(np)) |
165 | host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT; | |
166 | ||
3085e9c1 AV |
167 | clk = of_get_property(np, "clock-frequency", &size); |
168 | if (clk && size == sizeof(*clk) && *clk) | |
da81c3b9 | 169 | of_host->clock = be32_to_cpup(clk); |
3085e9c1 AV |
170 | |
171 | ret = sdhci_add_host(host); | |
172 | if (ret) | |
173 | goto err_add_host; | |
174 | ||
175 | return 0; | |
176 | ||
177 | err_add_host: | |
178 | irq_dispose_mapping(host->irq); | |
179 | err_no_irq: | |
180 | iounmap(host->ioaddr); | |
181 | err_addr_map: | |
182 | sdhci_free_host(host); | |
183 | return ret; | |
184 | } | |
185 | ||
2dc11581 | 186 | static int __devexit sdhci_of_remove(struct platform_device *ofdev) |
3085e9c1 AV |
187 | { |
188 | struct sdhci_host *host = dev_get_drvdata(&ofdev->dev); | |
189 | ||
190 | sdhci_remove_host(host, 0); | |
191 | sdhci_free_host(host); | |
192 | irq_dispose_mapping(host->irq); | |
193 | iounmap(host->ioaddr); | |
194 | return 0; | |
195 | } | |
196 | ||
197 | static const struct of_device_id sdhci_of_match[] = { | |
7657c3a7 | 198 | #ifdef CONFIG_MMC_SDHCI_OF_ESDHC |
3085e9c1 AV |
199 | { .compatible = "fsl,mpc8379-esdhc", .data = &sdhci_esdhc, }, |
200 | { .compatible = "fsl,mpc8536-esdhc", .data = &sdhci_esdhc, }, | |
992697e9 | 201 | { .compatible = "fsl,esdhc", .data = &sdhci_esdhc, }, |
1144ab5d AH |
202 | #endif |
203 | #ifdef CONFIG_MMC_SDHCI_OF_HLWD | |
204 | { .compatible = "nintendo,hollywood-sdhci", .data = &sdhci_hlwd, }, | |
7657c3a7 | 205 | #endif |
3085e9c1 AV |
206 | { .compatible = "generic-sdhci", }, |
207 | {}, | |
208 | }; | |
209 | MODULE_DEVICE_TABLE(of, sdhci_of_match); | |
210 | ||
211 | static struct of_platform_driver sdhci_of_driver = { | |
4018294b GL |
212 | .driver = { |
213 | .name = "sdhci-of", | |
214 | .owner = THIS_MODULE, | |
215 | .of_match_table = sdhci_of_match, | |
216 | }, | |
3085e9c1 AV |
217 | .probe = sdhci_of_probe, |
218 | .remove = __devexit_p(sdhci_of_remove), | |
219 | .suspend = sdhci_of_suspend, | |
220 | .resume = sdhci_of_resume, | |
221 | }; | |
222 | ||
223 | static int __init sdhci_of_init(void) | |
224 | { | |
225 | return of_register_platform_driver(&sdhci_of_driver); | |
226 | } | |
227 | module_init(sdhci_of_init); | |
228 | ||
229 | static void __exit sdhci_of_exit(void) | |
230 | { | |
231 | of_unregister_platform_driver(&sdhci_of_driver); | |
232 | } | |
233 | module_exit(sdhci_of_exit); | |
234 | ||
235 | MODULE_DESCRIPTION("Secure Digital Host Controller Interface OF driver"); | |
236 | MODULE_AUTHOR("Xiaobo Xie <X.Xie@freescale.com>, " | |
237 | "Anton Vorontsov <avorontsov@ru.mvista.com>"); | |
238 | MODULE_LICENSE("GPL"); |