Commit | Line | Data |
---|---|---|
a87d5638 MD |
1 | /* |
2 | * SuperH Mobile SDHI | |
3 | * | |
4 | * Copyright (C) 2009 Magnus Damm | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | * | |
10 | * Based on "Compaq ASIC3 support": | |
11 | * | |
12 | * Copyright 2001 Compaq Computer Corporation. | |
13 | * Copyright 2004-2005 Phil Blundell | |
14 | * Copyright 2007-2008 OpenedHand Ltd. | |
15 | * | |
16 | * Authors: Phil Blundell <pb@handhelds.org>, | |
17 | * Samuel Ortiz <sameo@openedhand.com> | |
18 | * | |
19 | */ | |
20 | ||
21 | #include <linux/kernel.h> | |
22 | #include <linux/clk.h> | |
5a0e3ad6 | 23 | #include <linux/slab.h> |
c7bb4487 | 24 | #include <linux/mod_devicetable.h> |
88b47679 | 25 | #include <linux/module.h> |
5a00a971 | 26 | #include <linux/of_device.h> |
a87d5638 | 27 | #include <linux/platform_device.h> |
3c49e810 | 28 | #include <linux/mmc/host.h> |
42051e8a | 29 | #include <linux/mmc/sh_mobile_sdhi.h> |
a87d5638 | 30 | #include <linux/mfd/tmio.h> |
056676da | 31 | #include <linux/sh_dma.h> |
973ed3af | 32 | #include <linux/delay.h> |
a87d5638 | 33 | |
42051e8a GL |
34 | #include "tmio_mmc.h" |
35 | ||
e3c418f1 KM |
36 | #define EXT_ACC 0xe4 |
37 | ||
5a00a971 GL |
38 | struct sh_mobile_sdhi_of_data { |
39 | unsigned long tmio_flags; | |
40 | }; | |
41 | ||
42 | static const struct sh_mobile_sdhi_of_data sh_mobile_sdhi_of_cfg[] = { | |
43 | { | |
44 | .tmio_flags = TMIO_MMC_HAS_IDLE_WAIT, | |
45 | }, | |
46 | }; | |
47 | ||
a87d5638 MD |
48 | struct sh_mobile_sdhi { |
49 | struct clk *clk; | |
50 | struct tmio_mmc_data mmc_data; | |
056676da | 51 | struct tmio_mmc_dma dma_priv; |
a87d5638 MD |
52 | }; |
53 | ||
56c49287 GL |
54 | static int sh_mobile_sdhi_clk_enable(struct platform_device *pdev, unsigned int *f) |
55 | { | |
6e2c0f3f | 56 | struct mmc_host *mmc = platform_get_drvdata(pdev); |
56c49287 GL |
57 | struct tmio_mmc_host *host = mmc_priv(mmc); |
58 | struct sh_mobile_sdhi *priv = container_of(host->pdata, struct sh_mobile_sdhi, mmc_data); | |
00fb3d2a | 59 | int ret = clk_prepare_enable(priv->clk); |
56c49287 GL |
60 | if (ret < 0) |
61 | return ret; | |
62 | ||
63 | *f = clk_get_rate(priv->clk); | |
64 | return 0; | |
65 | } | |
66 | ||
67 | static void sh_mobile_sdhi_clk_disable(struct platform_device *pdev) | |
68 | { | |
6e2c0f3f | 69 | struct mmc_host *mmc = platform_get_drvdata(pdev); |
56c49287 GL |
70 | struct tmio_mmc_host *host = mmc_priv(mmc); |
71 | struct sh_mobile_sdhi *priv = container_of(host->pdata, struct sh_mobile_sdhi, mmc_data); | |
00fb3d2a | 72 | clk_disable_unprepare(priv->clk); |
56c49287 GL |
73 | } |
74 | ||
973ed3af SH |
75 | static int sh_mobile_sdhi_wait_idle(struct tmio_mmc_host *host) |
76 | { | |
77 | int timeout = 1000; | |
78 | ||
79 | while (--timeout && !(sd_ctrl_read16(host, CTL_STATUS2) & (1 << 13))) | |
80 | udelay(1); | |
81 | ||
82 | if (!timeout) { | |
83 | dev_warn(host->pdata->dev, "timeout waiting for SD bus idle\n"); | |
84 | return -EBUSY; | |
85 | } | |
86 | ||
87 | return 0; | |
88 | } | |
89 | ||
90 | static int sh_mobile_sdhi_write16_hook(struct tmio_mmc_host *host, int addr) | |
91 | { | |
92 | switch (addr) | |
93 | { | |
94 | case CTL_SD_CMD: | |
95 | case CTL_STOP_INTERNAL_ACTION: | |
96 | case CTL_XFER_BLK_COUNT: | |
97 | case CTL_SD_CARD_CLK_CTL: | |
98 | case CTL_SD_XFER_LEN: | |
99 | case CTL_SD_MEM_CARD_OPT: | |
100 | case CTL_TRANSACTION_CTL: | |
101 | case CTL_DMA_ENABLE: | |
102 | return sh_mobile_sdhi_wait_idle(host); | |
103 | } | |
104 | ||
105 | return 0; | |
106 | } | |
107 | ||
7f524217 GL |
108 | static void sh_mobile_sdhi_cd_wakeup(const struct platform_device *pdev) |
109 | { | |
6e2c0f3f | 110 | mmc_detect_change(platform_get_drvdata(pdev), msecs_to_jiffies(100)); |
7f524217 GL |
111 | } |
112 | ||
113 | static const struct sh_mobile_sdhi_ops sdhi_ops = { | |
114 | .cd_wakeup = sh_mobile_sdhi_cd_wakeup, | |
115 | }; | |
116 | ||
5a00a971 | 117 | static const struct of_device_id sh_mobile_sdhi_of_match[] = { |
df1d0584 GL |
118 | { .compatible = "renesas,sdhi-shmobile" }, |
119 | { .compatible = "renesas,sdhi-sh7372" }, | |
120 | { .compatible = "renesas,sdhi-sh73a0", .data = &sh_mobile_sdhi_of_cfg[0], }, | |
121 | { .compatible = "renesas,sdhi-r8a73a4", .data = &sh_mobile_sdhi_of_cfg[0], }, | |
122 | { .compatible = "renesas,sdhi-r8a7740", .data = &sh_mobile_sdhi_of_cfg[0], }, | |
123 | { .compatible = "renesas,sdhi-r8a7778", .data = &sh_mobile_sdhi_of_cfg[0], }, | |
124 | { .compatible = "renesas,sdhi-r8a7779", .data = &sh_mobile_sdhi_of_cfg[0], }, | |
125 | { .compatible = "renesas,sdhi-r8a7790", .data = &sh_mobile_sdhi_of_cfg[0], }, | |
5a00a971 GL |
126 | {}, |
127 | }; | |
128 | MODULE_DEVICE_TABLE(of, sh_mobile_sdhi_of_match); | |
129 | ||
c3be1efd | 130 | static int sh_mobile_sdhi_probe(struct platform_device *pdev) |
a87d5638 | 131 | { |
5a00a971 GL |
132 | const struct of_device_id *of_id = |
133 | of_match_device(sh_mobile_sdhi_of_match, &pdev->dev); | |
a87d5638 | 134 | struct sh_mobile_sdhi *priv; |
056676da GL |
135 | struct tmio_mmc_data *mmc_data; |
136 | struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; | |
42051e8a | 137 | struct tmio_mmc_host *host; |
3b159a6e | 138 | struct resource *res; |
d5098cb6 SH |
139 | int irq, ret, i = 0; |
140 | bool multiplexed_isr = true; | |
87ae7bbe | 141 | struct tmio_mmc_dma *dma_priv; |
e3c418f1 | 142 | u16 ver; |
a87d5638 | 143 | |
3b159a6e KM |
144 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
145 | if (!res) | |
146 | return -EINVAL; | |
147 | ||
ac51b961 | 148 | priv = devm_kzalloc(&pdev->dev, sizeof(struct sh_mobile_sdhi), GFP_KERNEL); |
a87d5638 MD |
149 | if (priv == NULL) { |
150 | dev_err(&pdev->dev, "kzalloc failed\n"); | |
151 | return -ENOMEM; | |
152 | } | |
153 | ||
056676da | 154 | mmc_data = &priv->mmc_data; |
87ae7bbe | 155 | dma_priv = &priv->dma_priv; |
056676da | 156 | |
c7bb4487 | 157 | if (p) { |
c7bb4487 GL |
158 | if (p->init) { |
159 | ret = p->init(pdev, &sdhi_ops); | |
160 | if (ret) | |
ac51b961 | 161 | return ret; |
c7bb4487 | 162 | } |
e82b4ac9 BH |
163 | } |
164 | ||
ac51b961 | 165 | priv->clk = devm_clk_get(&pdev->dev, NULL); |
a87d5638 | 166 | if (IS_ERR(priv->clk)) { |
a87d5638 | 167 | ret = PTR_ERR(priv->clk); |
56ae1adc | 168 | dev_err(&pdev->dev, "cannot get clock: %d\n", ret); |
42051e8a | 169 | goto eclkget; |
a87d5638 MD |
170 | } |
171 | ||
56c49287 GL |
172 | mmc_data->clk_enable = sh_mobile_sdhi_clk_enable; |
173 | mmc_data->clk_disable = sh_mobile_sdhi_clk_disable; | |
056676da | 174 | mmc_data->capabilities = MMC_CAP_MMC_HIGHSPEED; |
7b952137 | 175 | mmc_data->write16_hook = sh_mobile_sdhi_write16_hook; |
bb0fe533 | 176 | if (p) { |
f87c20a9 | 177 | mmc_data->flags = p->tmio_flags; |
bb0fe533 | 178 | mmc_data->ocr_mask = p->tmio_ocr_mask; |
998283e2 | 179 | mmc_data->capabilities |= p->tmio_caps; |
d7d8d500 | 180 | mmc_data->capabilities2 |= p->tmio_caps2; |
58126c87 | 181 | mmc_data->cd_gpio = p->cd_gpio; |
42051e8a | 182 | |
3e713373 | 183 | if (p->dma_slave_tx > 0 && p->dma_slave_rx > 0) { |
eec95ee2 GL |
184 | /* |
185 | * Yes, we have to provide slave IDs twice to TMIO: | |
186 | * once as a filter parameter and once for channel | |
187 | * configuration as an explicit slave ID | |
188 | */ | |
189 | dma_priv->chan_priv_tx = (void *)p->dma_slave_tx; | |
190 | dma_priv->chan_priv_rx = (void *)p->dma_slave_rx; | |
191 | dma_priv->slave_id_tx = p->dma_slave_tx; | |
192 | dma_priv->slave_id_rx = p->dma_slave_rx; | |
42051e8a | 193 | } |
bb0fe533 | 194 | } |
056676da | 195 | |
87ae7bbe GL |
196 | dma_priv->alignment_shift = 1; /* 2-byte alignment */ |
197 | dma_priv->filter = shdma_chan_filter; | |
198 | ||
199 | mmc_data->dma = dma_priv; | |
200 | ||
f1334fb3 YG |
201 | /* |
202 | * All SDHI blocks support 2-byte and larger block sizes in 4-bit | |
203 | * bus width mode. | |
204 | */ | |
205 | mmc_data->flags |= TMIO_MMC_BLKSZ_2BYTES; | |
206 | ||
23b66071 AH |
207 | /* |
208 | * All SDHI blocks support SDIO IRQ signalling. | |
209 | */ | |
210 | mmc_data->flags |= TMIO_MMC_SDIO_IRQ; | |
211 | ||
5a00a971 GL |
212 | if (of_id && of_id->data) { |
213 | const struct sh_mobile_sdhi_of_data *of_data = of_id->data; | |
214 | mmc_data->flags |= of_data->tmio_flags; | |
215 | } | |
216 | ||
3b159a6e KM |
217 | /* SD control register space size is 0x100, 0x200 for bus_shift=1 */ |
218 | mmc_data->bus_shift = resource_size(res) >> 9; | |
219 | ||
42051e8a GL |
220 | ret = tmio_mmc_host_probe(&host, pdev, mmc_data); |
221 | if (ret < 0) | |
222 | goto eprobe; | |
a87d5638 | 223 | |
e3c418f1 KM |
224 | /* |
225 | * FIXME: | |
226 | * this Workaround can be more clever method | |
227 | */ | |
228 | ver = sd_ctrl_read16(host, CTL_VERSION); | |
229 | if (ver == 0xCB0D) | |
230 | sd_ctrl_write16(host, EXT_ACC, 1); | |
231 | ||
d5098cb6 SH |
232 | /* |
233 | * Allow one or more specific (named) ISRs or | |
234 | * one or more multiplexed (un-named) ISRs. | |
235 | */ | |
236 | ||
237 | irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_CARD_DETECT); | |
238 | if (irq >= 0) { | |
239 | multiplexed_isr = false; | |
ac51b961 | 240 | ret = devm_request_irq(&pdev->dev, irq, tmio_mmc_card_detect_irq, 0, |
d5098cb6 SH |
241 | dev_name(&pdev->dev), host); |
242 | if (ret) | |
ac51b961 | 243 | goto eirq; |
d5098cb6 SH |
244 | } |
245 | ||
246 | irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_SDIO); | |
247 | if (irq >= 0) { | |
248 | multiplexed_isr = false; | |
ac51b961 | 249 | ret = devm_request_irq(&pdev->dev, irq, tmio_mmc_sdio_irq, 0, |
d5098cb6 SH |
250 | dev_name(&pdev->dev), host); |
251 | if (ret) | |
ac51b961 | 252 | goto eirq; |
d5098cb6 SH |
253 | } |
254 | ||
255 | irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_SDCARD); | |
256 | if (irq >= 0) { | |
257 | multiplexed_isr = false; | |
ac51b961 | 258 | ret = devm_request_irq(&pdev->dev, irq, tmio_mmc_sdcard_irq, 0, |
d6a1f863 | 259 | dev_name(&pdev->dev), host); |
d5098cb6 | 260 | if (ret) |
ac51b961 | 261 | goto eirq; |
d5098cb6 SH |
262 | } else if (!multiplexed_isr) { |
263 | dev_err(&pdev->dev, | |
264 | "Principal SD-card IRQ is missing among named interrupts\n"); | |
265 | ret = irq; | |
ac51b961 | 266 | goto eirq; |
d5098cb6 SH |
267 | } |
268 | ||
269 | if (multiplexed_isr) { | |
270 | while (1) { | |
271 | irq = platform_get_irq(pdev, i); | |
272 | if (irq < 0) | |
273 | break; | |
274 | i++; | |
ac51b961 | 275 | ret = devm_request_irq(&pdev->dev, irq, tmio_mmc_irq, 0, |
d5098cb6 SH |
276 | dev_name(&pdev->dev), host); |
277 | if (ret) | |
ac51b961 | 278 | goto eirq; |
d6a1f863 | 279 | } |
d5098cb6 SH |
280 | |
281 | /* There must be at least one IRQ source */ | |
7913ae7d WY |
282 | if (!i) { |
283 | ret = irq; | |
ac51b961 | 284 | goto eirq; |
7913ae7d | 285 | } |
8e7bfdb3 | 286 | } |
d5098cb6 | 287 | |
1f7d6819 MD |
288 | dev_info(&pdev->dev, "%s base at 0x%08lx clock rate %u MHz\n", |
289 | mmc_hostname(host->mmc), (unsigned long) | |
58126c87 | 290 | (platform_get_resource(pdev, IORESOURCE_MEM, 0)->start), |
369213bd | 291 | host->mmc->f_max / 1000000); |
a87d5638 | 292 | |
42051e8a | 293 | return ret; |
a87d5638 | 294 | |
ac51b961 | 295 | eirq: |
8e7bfdb3 | 296 | tmio_mmc_host_remove(host); |
42051e8a | 297 | eprobe: |
42051e8a | 298 | eclkget: |
c7bb4487 | 299 | if (p && p->cleanup) |
e82b4ac9 | 300 | p->cleanup(pdev); |
a87d5638 MD |
301 | return ret; |
302 | } | |
303 | ||
304 | static int sh_mobile_sdhi_remove(struct platform_device *pdev) | |
305 | { | |
42051e8a GL |
306 | struct mmc_host *mmc = platform_get_drvdata(pdev); |
307 | struct tmio_mmc_host *host = mmc_priv(mmc); | |
25958804 | 308 | struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; |
d6a1f863 | 309 | |
742a0c7c GL |
310 | tmio_mmc_host_remove(host); |
311 | ||
c7bb4487 | 312 | if (p && p->cleanup) |
e82b4ac9 BH |
313 | p->cleanup(pdev); |
314 | ||
a87d5638 MD |
315 | return 0; |
316 | } | |
317 | ||
e6ee7182 GL |
318 | static const struct dev_pm_ops tmio_mmc_dev_pm_ops = { |
319 | .suspend = tmio_mmc_host_suspend, | |
320 | .resume = tmio_mmc_host_resume, | |
25958804 GL |
321 | .runtime_suspend = tmio_mmc_host_runtime_suspend, |
322 | .runtime_resume = tmio_mmc_host_runtime_resume, | |
e6ee7182 GL |
323 | }; |
324 | ||
a87d5638 MD |
325 | static struct platform_driver sh_mobile_sdhi_driver = { |
326 | .driver = { | |
327 | .name = "sh_mobile_sdhi", | |
328 | .owner = THIS_MODULE, | |
e6ee7182 | 329 | .pm = &tmio_mmc_dev_pm_ops, |
c7bb4487 | 330 | .of_match_table = sh_mobile_sdhi_of_match, |
a87d5638 MD |
331 | }, |
332 | .probe = sh_mobile_sdhi_probe, | |
0433c143 | 333 | .remove = sh_mobile_sdhi_remove, |
a87d5638 MD |
334 | }; |
335 | ||
d1f81a64 | 336 | module_platform_driver(sh_mobile_sdhi_driver); |
a87d5638 MD |
337 | |
338 | MODULE_DESCRIPTION("SuperH Mobile SDHI driver"); | |
339 | MODULE_AUTHOR("Magnus Damm"); | |
340 | MODULE_LICENSE("GPL v2"); | |
42051e8a | 341 | MODULE_ALIAS("platform:sh_mobile_sdhi"); |