Commit | Line | Data |
---|---|---|
536ac998 ZG |
1 | /* linux/drivers/mmc/host/sdhci-pxa.c |
2 | * | |
3 | * Copyright (C) 2010 Marvell International Ltd. | |
4 | * Zhangfei Gao <zhangfei.gao@marvell.com> | |
5 | * Kevin Wang <dwang4@marvell.com> | |
6 | * Mingwei Wang <mwwang@marvell.com> | |
7 | * Philip Rakity <prakity@marvell.com> | |
8 | * Mark Brown <markb@marvell.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 version 2 as | |
12 | * published by the Free Software Foundation. | |
13 | */ | |
14 | ||
15 | /* Supports: | |
16 | * SDHCI support for MMP2/PXA910/PXA168 | |
17 | * | |
18 | * Refer to sdhci-s3c.c. | |
19 | */ | |
20 | ||
21 | #include <linux/delay.h> | |
22 | #include <linux/platform_device.h> | |
23 | #include <linux/mmc/host.h> | |
24 | #include <linux/clk.h> | |
25 | #include <linux/io.h> | |
26 | #include <linux/err.h> | |
27 | #include <plat/sdhci.h> | |
28 | #include "sdhci.h" | |
29 | ||
30 | #define DRIVER_NAME "sdhci-pxa" | |
31 | ||
32 | #define SD_FIFO_PARAM 0x104 | |
33 | #define DIS_PAD_SD_CLK_GATE 0x400 | |
34 | ||
35 | struct sdhci_pxa { | |
36 | struct sdhci_host *host; | |
37 | struct sdhci_pxa_platdata *pdata; | |
38 | struct clk *clk; | |
39 | struct resource *res; | |
40 | ||
41 | u8 clk_enable; | |
42 | }; | |
43 | ||
44 | /*****************************************************************************\ | |
45 | * * | |
46 | * SDHCI core callbacks * | |
47 | * * | |
48 | \*****************************************************************************/ | |
49 | static void set_clock(struct sdhci_host *host, unsigned int clock) | |
50 | { | |
51 | struct sdhci_pxa *pxa = sdhci_priv(host); | |
52 | u32 tmp = 0; | |
53 | ||
54 | if (clock == 0) { | |
55 | if (pxa->clk_enable) { | |
56 | clk_disable(pxa->clk); | |
57 | pxa->clk_enable = 0; | |
58 | } | |
59 | } else { | |
60 | if (0 == pxa->clk_enable) { | |
61 | if (pxa->pdata->flags & PXA_FLAG_DISABLE_CLOCK_GATING) { | |
62 | tmp = readl(host->ioaddr + SD_FIFO_PARAM); | |
63 | tmp |= DIS_PAD_SD_CLK_GATE; | |
64 | writel(tmp, host->ioaddr + SD_FIFO_PARAM); | |
65 | } | |
66 | clk_enable(pxa->clk); | |
67 | pxa->clk_enable = 1; | |
68 | } | |
69 | } | |
70 | } | |
71 | ||
72 | static struct sdhci_ops sdhci_pxa_ops = { | |
73 | .set_clock = set_clock, | |
74 | }; | |
75 | ||
76 | /*****************************************************************************\ | |
77 | * * | |
78 | * Device probing/removal * | |
79 | * * | |
80 | \*****************************************************************************/ | |
81 | ||
82 | static int __devinit sdhci_pxa_probe(struct platform_device *pdev) | |
83 | { | |
84 | struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data; | |
85 | struct device *dev = &pdev->dev; | |
86 | struct sdhci_host *host = NULL; | |
87 | struct resource *iomem = NULL; | |
88 | struct sdhci_pxa *pxa = NULL; | |
89 | int ret, irq; | |
90 | ||
91 | irq = platform_get_irq(pdev, 0); | |
92 | if (irq < 0) { | |
93 | dev_err(dev, "no irq specified\n"); | |
94 | return irq; | |
95 | } | |
96 | ||
97 | iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
98 | if (!iomem) { | |
99 | dev_err(dev, "no memory specified\n"); | |
100 | return -ENOENT; | |
101 | } | |
102 | ||
103 | host = sdhci_alloc_host(&pdev->dev, sizeof(struct sdhci_pxa)); | |
104 | if (IS_ERR(host)) { | |
105 | dev_err(dev, "failed to alloc host\n"); | |
106 | return PTR_ERR(host); | |
107 | } | |
108 | ||
109 | pxa = sdhci_priv(host); | |
110 | pxa->host = host; | |
111 | pxa->pdata = pdata; | |
112 | pxa->clk_enable = 0; | |
113 | ||
114 | pxa->clk = clk_get(dev, "PXA-SDHCLK"); | |
115 | if (IS_ERR(pxa->clk)) { | |
116 | dev_err(dev, "failed to get io clock\n"); | |
117 | ret = PTR_ERR(pxa->clk); | |
118 | goto out; | |
119 | } | |
120 | ||
121 | pxa->res = request_mem_region(iomem->start, resource_size(iomem), | |
122 | mmc_hostname(host->mmc)); | |
123 | if (!pxa->res) { | |
124 | dev_err(&pdev->dev, "cannot request region\n"); | |
125 | ret = -EBUSY; | |
126 | goto out; | |
127 | } | |
128 | ||
129 | host->ioaddr = ioremap(iomem->start, resource_size(iomem)); | |
130 | if (!host->ioaddr) { | |
131 | dev_err(&pdev->dev, "failed to remap registers\n"); | |
132 | ret = -ENOMEM; | |
133 | goto out; | |
134 | } | |
135 | ||
136 | host->hw_name = "MMC"; | |
137 | host->ops = &sdhci_pxa_ops; | |
138 | host->irq = irq; | |
139 | host->quirks = SDHCI_QUIRK_BROKEN_ADMA | SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; | |
140 | ||
141 | if (pdata->quirks) | |
142 | host->quirks |= pdata->quirks; | |
143 | ||
15ec4461 PR |
144 | /* If slot design supports 8 bit data, indicate this to MMC. */ |
145 | if (pdata->flags & PXA_FLAG_SD_8_BIT_CAPABLE_SLOT) | |
146 | host->mmc->caps |= MMC_CAP_8_BIT_DATA; | |
147 | ||
536ac998 ZG |
148 | ret = sdhci_add_host(host); |
149 | if (ret) { | |
150 | dev_err(&pdev->dev, "failed to add host\n"); | |
151 | goto out; | |
152 | } | |
153 | ||
154 | if (pxa->pdata->max_speed) | |
155 | host->mmc->f_max = pxa->pdata->max_speed; | |
156 | ||
157 | platform_set_drvdata(pdev, host); | |
158 | ||
159 | return 0; | |
160 | out: | |
161 | if (host) { | |
162 | clk_put(pxa->clk); | |
163 | if (host->ioaddr) | |
164 | iounmap(host->ioaddr); | |
165 | if (pxa->res) | |
166 | release_mem_region(pxa->res->start, | |
167 | resource_size(pxa->res)); | |
168 | sdhci_free_host(host); | |
169 | } | |
170 | ||
171 | return ret; | |
172 | } | |
173 | ||
174 | static int __devexit sdhci_pxa_remove(struct platform_device *pdev) | |
175 | { | |
176 | struct sdhci_host *host = platform_get_drvdata(pdev); | |
177 | struct sdhci_pxa *pxa = sdhci_priv(host); | |
178 | int dead = 0; | |
179 | u32 scratch; | |
180 | ||
181 | if (host) { | |
182 | scratch = readl(host->ioaddr + SDHCI_INT_STATUS); | |
183 | if (scratch == (u32)-1) | |
184 | dead = 1; | |
185 | ||
186 | sdhci_remove_host(host, dead); | |
187 | ||
188 | if (host->ioaddr) | |
189 | iounmap(host->ioaddr); | |
190 | if (pxa->res) | |
191 | release_mem_region(pxa->res->start, | |
192 | resource_size(pxa->res)); | |
193 | if (pxa->clk_enable) { | |
194 | clk_disable(pxa->clk); | |
195 | pxa->clk_enable = 0; | |
196 | } | |
197 | clk_put(pxa->clk); | |
198 | ||
199 | sdhci_free_host(host); | |
200 | platform_set_drvdata(pdev, NULL); | |
201 | } | |
202 | ||
203 | return 0; | |
204 | } | |
205 | ||
206 | #ifdef CONFIG_PM | |
207 | static int sdhci_pxa_suspend(struct platform_device *dev, pm_message_t state) | |
208 | { | |
209 | struct sdhci_host *host = platform_get_drvdata(dev); | |
210 | ||
211 | return sdhci_suspend_host(host, state); | |
212 | } | |
213 | ||
214 | static int sdhci_pxa_resume(struct platform_device *dev) | |
215 | { | |
216 | struct sdhci_host *host = platform_get_drvdata(dev); | |
217 | ||
218 | return sdhci_resume_host(host); | |
219 | } | |
220 | #else | |
221 | #define sdhci_pxa_suspend NULL | |
222 | #define sdhci_pxa_resume NULL | |
223 | #endif | |
224 | ||
225 | static struct platform_driver sdhci_pxa_driver = { | |
226 | .probe = sdhci_pxa_probe, | |
227 | .remove = __devexit_p(sdhci_pxa_remove), | |
228 | .suspend = sdhci_pxa_suspend, | |
229 | .resume = sdhci_pxa_resume, | |
230 | .driver = { | |
231 | .name = DRIVER_NAME, | |
232 | .owner = THIS_MODULE, | |
233 | }, | |
234 | }; | |
235 | ||
236 | /*****************************************************************************\ | |
237 | * * | |
238 | * Driver init/exit * | |
239 | * * | |
240 | \*****************************************************************************/ | |
241 | ||
242 | static int __init sdhci_pxa_init(void) | |
243 | { | |
244 | return platform_driver_register(&sdhci_pxa_driver); | |
245 | } | |
246 | ||
247 | static void __exit sdhci_pxa_exit(void) | |
248 | { | |
249 | platform_driver_unregister(&sdhci_pxa_driver); | |
250 | } | |
251 | ||
252 | module_init(sdhci_pxa_init); | |
253 | module_exit(sdhci_pxa_exit); | |
254 | ||
255 | MODULE_DESCRIPTION("SDH controller driver for PXA168/PXA910/MMP2"); | |
256 | MODULE_AUTHOR("Zhangfei Gao <zhangfei.gao@marvell.com>"); | |
257 | MODULE_LICENSE("GPL v2"); |