Commit | Line | Data |
---|---|---|
95f25efe WS |
1 | /* |
2 | * Freescale eSDHC i.MX controller driver for the platform bus. | |
3 | * | |
4 | * derived from the OF-version. | |
5 | * | |
6 | * Copyright (c) 2010 Pengutronix e.K. | |
7 | * Author: Wolfram Sang <w.sang@pengutronix.de> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2 of the License. | |
12 | */ | |
13 | ||
14 | #include <linux/io.h> | |
15 | #include <linux/delay.h> | |
16 | #include <linux/err.h> | |
17 | #include <linux/clk.h> | |
0c6d49ce | 18 | #include <linux/gpio.h> |
e149860d | 19 | #include <linux/slab.h> |
95f25efe | 20 | #include <linux/mmc/host.h> |
58ac8177 RZ |
21 | #include <linux/mmc/mmc.h> |
22 | #include <linux/mmc/sdio.h> | |
37865fe9 | 23 | #include <mach/hardware.h> |
0c6d49ce | 24 | #include <mach/esdhc.h> |
95f25efe WS |
25 | #include "sdhci-pltfm.h" |
26 | #include "sdhci-esdhc.h" | |
27 | ||
58ac8177 RZ |
28 | /* VENDOR SPEC register */ |
29 | #define SDHCI_VENDOR_SPEC 0xC0 | |
30 | #define SDHCI_VENDOR_SPEC_SDIO_QUIRK 0x00000002 | |
31 | ||
58ac8177 RZ |
32 | /* |
33 | * The CMDTYPE of the CMD register (offset 0xE) should be set to | |
34 | * "11" when the STOP CMD12 is issued on imx53 to abort one | |
35 | * open ended multi-blk IO. Otherwise the TC INT wouldn't | |
36 | * be generated. | |
37 | * In exact block transfer, the controller doesn't complete the | |
38 | * operations automatically as required at the end of the | |
39 | * transfer and remains on hold if the abort command is not sent. | |
40 | * As a result, the TC flag is not asserted and SW received timeout | |
41 | * exeception. Bit1 of Vendor Spec registor is used to fix it. | |
42 | */ | |
43 | #define ESDHC_FLAG_MULTIBLK_NO_INT (1 << 1) | |
e149860d RZ |
44 | |
45 | struct pltfm_imx_data { | |
46 | int flags; | |
47 | u32 scratchpad; | |
842afc02 | 48 | struct esdhc_platform_data boarddata; |
e149860d RZ |
49 | }; |
50 | ||
95f25efe WS |
51 | static inline void esdhc_clrset_le(struct sdhci_host *host, u32 mask, u32 val, int reg) |
52 | { | |
53 | void __iomem *base = host->ioaddr + (reg & ~0x3); | |
54 | u32 shift = (reg & 0x3) * 8; | |
55 | ||
56 | writel(((readl(base) & ~(mask << shift)) | (val << shift)), base); | |
57 | } | |
58 | ||
7e29c306 WS |
59 | static u32 esdhc_readl_le(struct sdhci_host *host, int reg) |
60 | { | |
842afc02 SG |
61 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
62 | struct pltfm_imx_data *imx_data = pltfm_host->priv; | |
63 | struct esdhc_platform_data *boarddata = &imx_data->boarddata; | |
e149860d | 64 | |
913413c3 | 65 | /* fake CARD_PRESENT flag */ |
7e29c306 WS |
66 | u32 val = readl(host->ioaddr + reg); |
67 | ||
e149860d | 68 | if (unlikely((reg == SDHCI_PRESENT_STATE) |
913413c3 SG |
69 | && gpio_is_valid(boarddata->cd_gpio))) { |
70 | if (gpio_get_value(boarddata->cd_gpio)) | |
7e29c306 | 71 | /* no card, if a valid gpio says so... */ |
803862a6 | 72 | val &= ~SDHCI_CARD_PRESENT; |
7e29c306 WS |
73 | else |
74 | /* ... in all other cases assume card is present */ | |
75 | val |= SDHCI_CARD_PRESENT; | |
76 | } | |
77 | ||
78 | return val; | |
79 | } | |
80 | ||
81 | static void esdhc_writel_le(struct sdhci_host *host, u32 val, int reg) | |
82 | { | |
e149860d RZ |
83 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
84 | struct pltfm_imx_data *imx_data = pltfm_host->priv; | |
842afc02 | 85 | struct esdhc_platform_data *boarddata = &imx_data->boarddata; |
e149860d RZ |
86 | |
87 | if (unlikely((reg == SDHCI_INT_ENABLE || reg == SDHCI_SIGNAL_ENABLE) | |
913413c3 | 88 | && (boarddata->cd_type == ESDHC_CD_GPIO))) |
7e29c306 WS |
89 | /* |
90 | * these interrupts won't work with a custom card_detect gpio | |
7e29c306 WS |
91 | */ |
92 | val &= ~(SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT); | |
93 | ||
58ac8177 RZ |
94 | if (unlikely((imx_data->flags & ESDHC_FLAG_MULTIBLK_NO_INT) |
95 | && (reg == SDHCI_INT_STATUS) | |
96 | && (val & SDHCI_INT_DATA_END))) { | |
97 | u32 v; | |
98 | v = readl(host->ioaddr + SDHCI_VENDOR_SPEC); | |
99 | v &= ~SDHCI_VENDOR_SPEC_SDIO_QUIRK; | |
100 | writel(v, host->ioaddr + SDHCI_VENDOR_SPEC); | |
101 | } | |
102 | ||
7e29c306 WS |
103 | writel(val, host->ioaddr + reg); |
104 | } | |
105 | ||
95f25efe WS |
106 | static u16 esdhc_readw_le(struct sdhci_host *host, int reg) |
107 | { | |
108 | if (unlikely(reg == SDHCI_HOST_VERSION)) | |
109 | reg ^= 2; | |
110 | ||
111 | return readw(host->ioaddr + reg); | |
112 | } | |
113 | ||
114 | static void esdhc_writew_le(struct sdhci_host *host, u16 val, int reg) | |
115 | { | |
116 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | |
e149860d | 117 | struct pltfm_imx_data *imx_data = pltfm_host->priv; |
95f25efe WS |
118 | |
119 | switch (reg) { | |
120 | case SDHCI_TRANSFER_MODE: | |
121 | /* | |
122 | * Postpone this write, we must do it together with a | |
123 | * command write that is down below. | |
124 | */ | |
58ac8177 RZ |
125 | if ((imx_data->flags & ESDHC_FLAG_MULTIBLK_NO_INT) |
126 | && (host->cmd->opcode == SD_IO_RW_EXTENDED) | |
127 | && (host->cmd->data->blocks > 1) | |
128 | && (host->cmd->data->flags & MMC_DATA_READ)) { | |
129 | u32 v; | |
130 | v = readl(host->ioaddr + SDHCI_VENDOR_SPEC); | |
131 | v |= SDHCI_VENDOR_SPEC_SDIO_QUIRK; | |
132 | writel(v, host->ioaddr + SDHCI_VENDOR_SPEC); | |
133 | } | |
e149860d | 134 | imx_data->scratchpad = val; |
95f25efe WS |
135 | return; |
136 | case SDHCI_COMMAND: | |
58ac8177 RZ |
137 | if ((host->cmd->opcode == MMC_STOP_TRANSMISSION) |
138 | && (imx_data->flags & ESDHC_FLAG_MULTIBLK_NO_INT)) | |
139 | val |= SDHCI_CMD_ABORTCMD; | |
e149860d | 140 | writel(val << 16 | imx_data->scratchpad, |
95f25efe WS |
141 | host->ioaddr + SDHCI_TRANSFER_MODE); |
142 | return; | |
143 | case SDHCI_BLOCK_SIZE: | |
144 | val &= ~SDHCI_MAKE_BLKSZ(0x7, 0); | |
145 | break; | |
146 | } | |
147 | esdhc_clrset_le(host, 0xffff, val, reg); | |
148 | } | |
149 | ||
150 | static void esdhc_writeb_le(struct sdhci_host *host, u8 val, int reg) | |
151 | { | |
152 | u32 new_val; | |
153 | ||
154 | switch (reg) { | |
155 | case SDHCI_POWER_CONTROL: | |
156 | /* | |
157 | * FSL put some DMA bits here | |
158 | * If your board has a regulator, code should be here | |
159 | */ | |
160 | return; | |
161 | case SDHCI_HOST_CONTROL: | |
162 | /* FSL messed up here, so we can just keep those two */ | |
163 | new_val = val & (SDHCI_CTRL_LED | SDHCI_CTRL_4BITBUS); | |
164 | /* ensure the endianess */ | |
165 | new_val |= ESDHC_HOST_CONTROL_LE; | |
166 | /* DMA mode bits are shifted */ | |
167 | new_val |= (val & SDHCI_CTRL_DMA_MASK) << 5; | |
168 | ||
169 | esdhc_clrset_le(host, 0xffff, new_val, reg); | |
170 | return; | |
171 | } | |
172 | esdhc_clrset_le(host, 0xff, val, reg); | |
913413c3 SG |
173 | |
174 | /* | |
175 | * The esdhc has a design violation to SDHC spec which tells | |
176 | * that software reset should not affect card detection circuit. | |
177 | * But esdhc clears its SYSCTL register bits [0..2] during the | |
178 | * software reset. This will stop those clocks that card detection | |
179 | * circuit relies on. To work around it, we turn the clocks on back | |
180 | * to keep card detection circuit functional. | |
181 | */ | |
182 | if ((reg == SDHCI_SOFTWARE_RESET) && (val & 1)) | |
183 | esdhc_clrset_le(host, 0x7, 0x7, ESDHC_SYSTEM_CONTROL); | |
95f25efe WS |
184 | } |
185 | ||
186 | static unsigned int esdhc_pltfm_get_max_clock(struct sdhci_host *host) | |
187 | { | |
188 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | |
189 | ||
190 | return clk_get_rate(pltfm_host->clk); | |
191 | } | |
192 | ||
193 | static unsigned int esdhc_pltfm_get_min_clock(struct sdhci_host *host) | |
194 | { | |
195 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | |
196 | ||
197 | return clk_get_rate(pltfm_host->clk) / 256 / 16; | |
198 | } | |
199 | ||
913413c3 SG |
200 | static unsigned int esdhc_pltfm_get_ro(struct sdhci_host *host) |
201 | { | |
842afc02 SG |
202 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
203 | struct pltfm_imx_data *imx_data = pltfm_host->priv; | |
204 | struct esdhc_platform_data *boarddata = &imx_data->boarddata; | |
913413c3 SG |
205 | |
206 | switch (boarddata->wp_type) { | |
207 | case ESDHC_WP_GPIO: | |
208 | if (gpio_is_valid(boarddata->wp_gpio)) | |
209 | return gpio_get_value(boarddata->wp_gpio); | |
210 | case ESDHC_WP_CONTROLLER: | |
211 | return !(readl(host->ioaddr + SDHCI_PRESENT_STATE) & | |
212 | SDHCI_WRITE_PROTECT); | |
213 | case ESDHC_WP_NONE: | |
214 | break; | |
215 | } | |
216 | ||
217 | return -ENOSYS; | |
218 | } | |
219 | ||
0c6d49ce | 220 | static struct sdhci_ops sdhci_esdhc_ops = { |
e149860d | 221 | .read_l = esdhc_readl_le, |
0c6d49ce | 222 | .read_w = esdhc_readw_le, |
e149860d | 223 | .write_l = esdhc_writel_le, |
0c6d49ce WS |
224 | .write_w = esdhc_writew_le, |
225 | .write_b = esdhc_writeb_le, | |
226 | .set_clock = esdhc_set_clock, | |
227 | .get_max_clock = esdhc_pltfm_get_max_clock, | |
228 | .get_min_clock = esdhc_pltfm_get_min_clock, | |
913413c3 | 229 | .get_ro = esdhc_pltfm_get_ro, |
0c6d49ce WS |
230 | }; |
231 | ||
85d6509d SG |
232 | static struct sdhci_pltfm_data sdhci_esdhc_imx_pdata = { |
233 | .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_BROKEN_ADMA | |
234 | | SDHCI_QUIRK_BROKEN_CARD_DETECTION, | |
235 | /* ADMA has issues. Might be fixable */ | |
236 | .ops = &sdhci_esdhc_ops, | |
237 | }; | |
238 | ||
7e29c306 WS |
239 | static irqreturn_t cd_irq(int irq, void *data) |
240 | { | |
241 | struct sdhci_host *sdhost = (struct sdhci_host *)data; | |
242 | ||
243 | tasklet_schedule(&sdhost->card_tasklet); | |
244 | return IRQ_HANDLED; | |
245 | }; | |
246 | ||
85d6509d | 247 | static int __devinit sdhci_esdhc_imx_probe(struct platform_device *pdev) |
95f25efe | 248 | { |
85d6509d SG |
249 | struct sdhci_pltfm_host *pltfm_host; |
250 | struct sdhci_host *host; | |
251 | struct esdhc_platform_data *boarddata; | |
95f25efe | 252 | struct clk *clk; |
0c6d49ce | 253 | int err; |
e149860d | 254 | struct pltfm_imx_data *imx_data; |
95f25efe | 255 | |
85d6509d SG |
256 | host = sdhci_pltfm_init(pdev, &sdhci_esdhc_imx_pdata); |
257 | if (IS_ERR(host)) | |
258 | return PTR_ERR(host); | |
259 | ||
260 | pltfm_host = sdhci_priv(host); | |
261 | ||
262 | imx_data = kzalloc(sizeof(struct pltfm_imx_data), GFP_KERNEL); | |
263 | if (!imx_data) | |
264 | return -ENOMEM; | |
265 | pltfm_host->priv = imx_data; | |
266 | ||
95f25efe WS |
267 | clk = clk_get(mmc_dev(host->mmc), NULL); |
268 | if (IS_ERR(clk)) { | |
269 | dev_err(mmc_dev(host->mmc), "clk err\n"); | |
85d6509d SG |
270 | err = PTR_ERR(clk); |
271 | goto err_clk_get; | |
95f25efe WS |
272 | } |
273 | clk_enable(clk); | |
274 | pltfm_host->clk = clk; | |
275 | ||
e149860d | 276 | if (!cpu_is_mx25()) |
37865fe9 EB |
277 | host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; |
278 | ||
913413c3 | 279 | if (cpu_is_mx25() || cpu_is_mx35()) |
0c6d49ce | 280 | /* Fix errata ENGcm07207 present on i.MX25 and i.MX35 */ |
16a790bc | 281 | host->quirks |= SDHCI_QUIRK_NO_MULTIBLOCK; |
0c6d49ce | 282 | |
58ac8177 RZ |
283 | if (!(cpu_is_mx25() || cpu_is_mx35() || cpu_is_mx51())) |
284 | imx_data->flags |= ESDHC_FLAG_MULTIBLK_NO_INT; | |
285 | ||
842afc02 | 286 | if (!host->mmc->parent->platform_data) { |
913413c3 SG |
287 | dev_err(mmc_dev(host->mmc), "no board data!\n"); |
288 | err = -EINVAL; | |
289 | goto no_board_data; | |
290 | } | |
842afc02 SG |
291 | imx_data->boarddata = *((struct esdhc_platform_data *) |
292 | host->mmc->parent->platform_data); | |
293 | boarddata = &imx_data->boarddata; | |
913413c3 SG |
294 | |
295 | /* write_protect */ | |
296 | if (boarddata->wp_type == ESDHC_WP_GPIO) { | |
0c6d49ce WS |
297 | err = gpio_request_one(boarddata->wp_gpio, GPIOF_IN, "ESDHC_WP"); |
298 | if (err) { | |
299 | dev_warn(mmc_dev(host->mmc), | |
913413c3 SG |
300 | "no write-protect pin available!\n"); |
301 | boarddata->wp_gpio = -EINVAL; | |
0c6d49ce | 302 | } |
913413c3 SG |
303 | } else { |
304 | boarddata->wp_gpio = -EINVAL; | |
305 | } | |
306 | ||
307 | /* card_detect */ | |
308 | if (boarddata->cd_type != ESDHC_CD_GPIO) | |
309 | boarddata->cd_gpio = -EINVAL; | |
7e29c306 | 310 | |
913413c3 SG |
311 | switch (boarddata->cd_type) { |
312 | case ESDHC_CD_GPIO: | |
7e29c306 WS |
313 | err = gpio_request_one(boarddata->cd_gpio, GPIOF_IN, "ESDHC_CD"); |
314 | if (err) { | |
913413c3 | 315 | dev_err(mmc_dev(host->mmc), |
7e29c306 WS |
316 | "no card-detect pin available!\n"); |
317 | goto no_card_detect_pin; | |
318 | } | |
319 | ||
7e29c306 WS |
320 | err = request_irq(gpio_to_irq(boarddata->cd_gpio), cd_irq, |
321 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | |
322 | mmc_hostname(host->mmc), host); | |
323 | if (err) { | |
913413c3 | 324 | dev_err(mmc_dev(host->mmc), "request irq error\n"); |
7e29c306 WS |
325 | goto no_card_detect_irq; |
326 | } | |
913413c3 | 327 | /* fall through */ |
7e29c306 | 328 | |
913413c3 SG |
329 | case ESDHC_CD_CONTROLLER: |
330 | /* we have a working card_detect back */ | |
7e29c306 | 331 | host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION; |
913413c3 SG |
332 | break; |
333 | ||
334 | case ESDHC_CD_PERMANENT: | |
335 | host->mmc->caps = MMC_CAP_NONREMOVABLE; | |
336 | break; | |
337 | ||
338 | case ESDHC_CD_NONE: | |
339 | break; | |
0c6d49ce | 340 | } |
16a790bc | 341 | |
85d6509d SG |
342 | err = sdhci_add_host(host); |
343 | if (err) | |
344 | goto err_add_host; | |
345 | ||
95f25efe | 346 | return 0; |
7e29c306 | 347 | |
913413c3 SG |
348 | err_add_host: |
349 | if (gpio_is_valid(boarddata->cd_gpio)) | |
350 | free_irq(gpio_to_irq(boarddata->cd_gpio), host); | |
351 | no_card_detect_irq: | |
352 | if (gpio_is_valid(boarddata->cd_gpio)) | |
353 | gpio_free(boarddata->cd_gpio); | |
354 | if (gpio_is_valid(boarddata->wp_gpio)) | |
355 | gpio_free(boarddata->wp_gpio); | |
356 | no_card_detect_pin: | |
357 | no_board_data: | |
85d6509d SG |
358 | clk_disable(pltfm_host->clk); |
359 | clk_put(pltfm_host->clk); | |
913413c3 SG |
360 | err_clk_get: |
361 | kfree(imx_data); | |
85d6509d SG |
362 | sdhci_pltfm_free(pdev); |
363 | return err; | |
95f25efe WS |
364 | } |
365 | ||
85d6509d | 366 | static int __devexit sdhci_esdhc_imx_remove(struct platform_device *pdev) |
95f25efe | 367 | { |
85d6509d | 368 | struct sdhci_host *host = platform_get_drvdata(pdev); |
95f25efe | 369 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
e149860d | 370 | struct pltfm_imx_data *imx_data = pltfm_host->priv; |
842afc02 | 371 | struct esdhc_platform_data *boarddata = &imx_data->boarddata; |
85d6509d SG |
372 | int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); |
373 | ||
374 | sdhci_remove_host(host, dead); | |
0c6d49ce | 375 | |
913413c3 | 376 | if (gpio_is_valid(boarddata->wp_gpio)) |
0c6d49ce | 377 | gpio_free(boarddata->wp_gpio); |
95f25efe | 378 | |
913413c3 SG |
379 | if (gpio_is_valid(boarddata->cd_gpio)) { |
380 | free_irq(gpio_to_irq(boarddata->cd_gpio), host); | |
7e29c306 | 381 | gpio_free(boarddata->cd_gpio); |
7e29c306 WS |
382 | } |
383 | ||
95f25efe WS |
384 | clk_disable(pltfm_host->clk); |
385 | clk_put(pltfm_host->clk); | |
e149860d | 386 | kfree(imx_data); |
85d6509d SG |
387 | |
388 | sdhci_pltfm_free(pdev); | |
389 | ||
390 | return 0; | |
95f25efe WS |
391 | } |
392 | ||
85d6509d SG |
393 | static struct platform_driver sdhci_esdhc_imx_driver = { |
394 | .driver = { | |
395 | .name = "sdhci-esdhc-imx", | |
396 | .owner = THIS_MODULE, | |
397 | }, | |
398 | .probe = sdhci_esdhc_imx_probe, | |
399 | .remove = __devexit_p(sdhci_esdhc_imx_remove), | |
400 | #ifdef CONFIG_PM | |
401 | .suspend = sdhci_pltfm_suspend, | |
402 | .resume = sdhci_pltfm_resume, | |
403 | #endif | |
95f25efe | 404 | }; |
85d6509d SG |
405 | |
406 | static int __init sdhci_esdhc_imx_init(void) | |
407 | { | |
408 | return platform_driver_register(&sdhci_esdhc_imx_driver); | |
409 | } | |
410 | module_init(sdhci_esdhc_imx_init); | |
411 | ||
412 | static void __exit sdhci_esdhc_imx_exit(void) | |
413 | { | |
414 | platform_driver_unregister(&sdhci_esdhc_imx_driver); | |
415 | } | |
416 | module_exit(sdhci_esdhc_imx_exit); | |
417 | ||
418 | MODULE_DESCRIPTION("SDHCI driver for Freescale i.MX eSDHC"); | |
419 | MODULE_AUTHOR("Wolfram Sang <w.sang@pengutronix.de>"); | |
420 | MODULE_LICENSE("GPL v2"); |