xref: /openbmc/linux/drivers/mmc/host/sdhci-spear.c (revision c63b3cba)
1c63b3cbaSViresh KUMAR /*
2c63b3cbaSViresh KUMAR  * drivers/mmc/host/sdhci-spear.c
3c63b3cbaSViresh KUMAR  *
4c63b3cbaSViresh KUMAR  * Support of SDHCI platform devices for spear soc family
5c63b3cbaSViresh KUMAR  *
6c63b3cbaSViresh KUMAR  * Copyright (C) 2010 ST Microelectronics
7c63b3cbaSViresh KUMAR  * Viresh Kumar<viresh.kumar@st.com>
8c63b3cbaSViresh KUMAR  *
9c63b3cbaSViresh KUMAR  * Inspired by sdhci-pltfm.c
10c63b3cbaSViresh KUMAR  *
11c63b3cbaSViresh KUMAR  * This file is licensed under the terms of the GNU General Public
12c63b3cbaSViresh KUMAR  * License version 2. This program is licensed "as is" without any
13c63b3cbaSViresh KUMAR  * warranty of any kind, whether express or implied.
14c63b3cbaSViresh KUMAR  */
15c63b3cbaSViresh KUMAR 
16c63b3cbaSViresh KUMAR #include <linux/clk.h>
17c63b3cbaSViresh KUMAR #include <linux/delay.h>
18c63b3cbaSViresh KUMAR #include <linux/gpio.h>
19c63b3cbaSViresh KUMAR #include <linux/highmem.h>
20c63b3cbaSViresh KUMAR #include <linux/interrupt.h>
21c63b3cbaSViresh KUMAR #include <linux/irq.h>
22c63b3cbaSViresh KUMAR #include <linux/platform_device.h>
23c63b3cbaSViresh KUMAR #include <linux/slab.h>
24c63b3cbaSViresh KUMAR #include <linux/mmc/host.h>
25c63b3cbaSViresh KUMAR #include <linux/mmc/sdhci-spear.h>
26c63b3cbaSViresh KUMAR #include <linux/io.h>
27c63b3cbaSViresh KUMAR #include "sdhci.h"
28c63b3cbaSViresh KUMAR 
29c63b3cbaSViresh KUMAR struct spear_sdhci {
30c63b3cbaSViresh KUMAR 	struct clk *clk;
31c63b3cbaSViresh KUMAR 	struct sdhci_plat_data *data;
32c63b3cbaSViresh KUMAR };
33c63b3cbaSViresh KUMAR 
34c63b3cbaSViresh KUMAR /* sdhci ops */
35c63b3cbaSViresh KUMAR static struct sdhci_ops sdhci_pltfm_ops = {
36c63b3cbaSViresh KUMAR 	/* Nothing to do for now. */
37c63b3cbaSViresh KUMAR };
38c63b3cbaSViresh KUMAR 
39c63b3cbaSViresh KUMAR /* gpio card detection interrupt handler */
40c63b3cbaSViresh KUMAR static irqreturn_t sdhci_gpio_irq(int irq, void *dev_id)
41c63b3cbaSViresh KUMAR {
42c63b3cbaSViresh KUMAR 	struct platform_device *pdev = dev_id;
43c63b3cbaSViresh KUMAR 	struct sdhci_host *host = platform_get_drvdata(pdev);
44c63b3cbaSViresh KUMAR 	struct spear_sdhci *sdhci = dev_get_platdata(&pdev->dev);
45c63b3cbaSViresh KUMAR 	unsigned long gpio_irq_type;
46c63b3cbaSViresh KUMAR 	int val;
47c63b3cbaSViresh KUMAR 
48c63b3cbaSViresh KUMAR 	val = gpio_get_value(sdhci->data->card_int_gpio);
49c63b3cbaSViresh KUMAR 
50c63b3cbaSViresh KUMAR 	/* val == 1 -> card removed, val == 0 -> card inserted */
51c63b3cbaSViresh KUMAR 	/* if card removed - set irq for low level, else vice versa */
52c63b3cbaSViresh KUMAR 	gpio_irq_type = val ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH;
53c63b3cbaSViresh KUMAR 	set_irq_type(irq, gpio_irq_type);
54c63b3cbaSViresh KUMAR 
55c63b3cbaSViresh KUMAR 	if (sdhci->data->card_power_gpio >= 0) {
56c63b3cbaSViresh KUMAR 		if (!sdhci->data->power_always_enb) {
57c63b3cbaSViresh KUMAR 			/* if card inserted, give power, otherwise remove it */
58c63b3cbaSViresh KUMAR 			val = sdhci->data->power_active_high ? !val : val ;
59c63b3cbaSViresh KUMAR 			gpio_set_value(sdhci->data->card_power_gpio, val);
60c63b3cbaSViresh KUMAR 		}
61c63b3cbaSViresh KUMAR 	}
62c63b3cbaSViresh KUMAR 
63c63b3cbaSViresh KUMAR 	/* inform sdhci driver about card insertion/removal */
64c63b3cbaSViresh KUMAR 	tasklet_schedule(&host->card_tasklet);
65c63b3cbaSViresh KUMAR 
66c63b3cbaSViresh KUMAR 	return IRQ_HANDLED;
67c63b3cbaSViresh KUMAR }
68c63b3cbaSViresh KUMAR 
69c63b3cbaSViresh KUMAR static int __devinit sdhci_probe(struct platform_device *pdev)
70c63b3cbaSViresh KUMAR {
71c63b3cbaSViresh KUMAR 	struct sdhci_host *host;
72c63b3cbaSViresh KUMAR 	struct resource *iomem;
73c63b3cbaSViresh KUMAR 	struct spear_sdhci *sdhci;
74c63b3cbaSViresh KUMAR 	int ret;
75c63b3cbaSViresh KUMAR 
76c63b3cbaSViresh KUMAR 	BUG_ON(pdev == NULL);
77c63b3cbaSViresh KUMAR 
78c63b3cbaSViresh KUMAR 	iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
79c63b3cbaSViresh KUMAR 	if (!iomem) {
80c63b3cbaSViresh KUMAR 		ret = -ENOMEM;
81c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "memory resource not defined\n");
82c63b3cbaSViresh KUMAR 		goto err;
83c63b3cbaSViresh KUMAR 	}
84c63b3cbaSViresh KUMAR 
85c63b3cbaSViresh KUMAR 	if (!request_mem_region(iomem->start, resource_size(iomem),
86c63b3cbaSViresh KUMAR 				"spear-sdhci")) {
87c63b3cbaSViresh KUMAR 		ret = -EBUSY;
88c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "cannot request region\n");
89c63b3cbaSViresh KUMAR 		goto err;
90c63b3cbaSViresh KUMAR 	}
91c63b3cbaSViresh KUMAR 
92c63b3cbaSViresh KUMAR 	sdhci = kzalloc(sizeof(*sdhci), GFP_KERNEL);
93c63b3cbaSViresh KUMAR 	if (!sdhci) {
94c63b3cbaSViresh KUMAR 		ret = -ENOMEM;
95c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "cannot allocate memory for sdhci\n");
96c63b3cbaSViresh KUMAR 		goto err_kzalloc;
97c63b3cbaSViresh KUMAR 	}
98c63b3cbaSViresh KUMAR 
99c63b3cbaSViresh KUMAR 	/* clk enable */
100c63b3cbaSViresh KUMAR 	sdhci->clk = clk_get(&pdev->dev, NULL);
101c63b3cbaSViresh KUMAR 	if (IS_ERR(sdhci->clk)) {
102c63b3cbaSViresh KUMAR 		ret = PTR_ERR(sdhci->clk);
103c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "Error getting clock\n");
104c63b3cbaSViresh KUMAR 		goto err_clk_get;
105c63b3cbaSViresh KUMAR 	}
106c63b3cbaSViresh KUMAR 
107c63b3cbaSViresh KUMAR 	ret = clk_enable(sdhci->clk);
108c63b3cbaSViresh KUMAR 	if (ret) {
109c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "Error enabling clock\n");
110c63b3cbaSViresh KUMAR 		goto err_clk_enb;
111c63b3cbaSViresh KUMAR 	}
112c63b3cbaSViresh KUMAR 
113c63b3cbaSViresh KUMAR 	/* overwrite platform_data */
114c63b3cbaSViresh KUMAR 	sdhci->data = dev_get_platdata(&pdev->dev);
115c63b3cbaSViresh KUMAR 	pdev->dev.platform_data = sdhci;
116c63b3cbaSViresh KUMAR 
117c63b3cbaSViresh KUMAR 	if (pdev->dev.parent)
118c63b3cbaSViresh KUMAR 		host = sdhci_alloc_host(pdev->dev.parent, 0);
119c63b3cbaSViresh KUMAR 	else
120c63b3cbaSViresh KUMAR 		host = sdhci_alloc_host(&pdev->dev, 0);
121c63b3cbaSViresh KUMAR 
122c63b3cbaSViresh KUMAR 	if (IS_ERR(host)) {
123c63b3cbaSViresh KUMAR 		ret = PTR_ERR(host);
124c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "error allocating host\n");
125c63b3cbaSViresh KUMAR 		goto err_alloc_host;
126c63b3cbaSViresh KUMAR 	}
127c63b3cbaSViresh KUMAR 
128c63b3cbaSViresh KUMAR 	host->hw_name = "sdhci";
129c63b3cbaSViresh KUMAR 	host->ops = &sdhci_pltfm_ops;
130c63b3cbaSViresh KUMAR 	host->irq = platform_get_irq(pdev, 0);
131c63b3cbaSViresh KUMAR 	host->quirks = SDHCI_QUIRK_BROKEN_ADMA;
132c63b3cbaSViresh KUMAR 
133c63b3cbaSViresh KUMAR 	host->ioaddr = ioremap(iomem->start, resource_size(iomem));
134c63b3cbaSViresh KUMAR 	if (!host->ioaddr) {
135c63b3cbaSViresh KUMAR 		ret = -ENOMEM;
136c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "failed to remap registers\n");
137c63b3cbaSViresh KUMAR 		goto err_ioremap;
138c63b3cbaSViresh KUMAR 	}
139c63b3cbaSViresh KUMAR 
140c63b3cbaSViresh KUMAR 	ret = sdhci_add_host(host);
141c63b3cbaSViresh KUMAR 	if (ret) {
142c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "error adding host\n");
143c63b3cbaSViresh KUMAR 		goto err_add_host;
144c63b3cbaSViresh KUMAR 	}
145c63b3cbaSViresh KUMAR 
146c63b3cbaSViresh KUMAR 	platform_set_drvdata(pdev, host);
147c63b3cbaSViresh KUMAR 
148c63b3cbaSViresh KUMAR 	/*
149c63b3cbaSViresh KUMAR 	 * It is optional to use GPIOs for sdhci Power control & sdhci card
150c63b3cbaSViresh KUMAR 	 * interrupt detection. If sdhci->data is NULL, then use original sdhci
151c63b3cbaSViresh KUMAR 	 * lines otherwise GPIO lines.
152c63b3cbaSViresh KUMAR 	 * If GPIO is selected for power control, then power should be disabled
153c63b3cbaSViresh KUMAR 	 * after card removal and should be enabled when card insertion
154c63b3cbaSViresh KUMAR 	 * interrupt occurs
155c63b3cbaSViresh KUMAR 	 */
156c63b3cbaSViresh KUMAR 	if (!sdhci->data)
157c63b3cbaSViresh KUMAR 		return 0;
158c63b3cbaSViresh KUMAR 
159c63b3cbaSViresh KUMAR 	if (sdhci->data->card_power_gpio >= 0) {
160c63b3cbaSViresh KUMAR 		int val = 0;
161c63b3cbaSViresh KUMAR 
162c63b3cbaSViresh KUMAR 		ret = gpio_request(sdhci->data->card_power_gpio, "sdhci");
163c63b3cbaSViresh KUMAR 		if (ret < 0) {
164c63b3cbaSViresh KUMAR 			dev_dbg(&pdev->dev, "gpio request fail: %d\n",
165c63b3cbaSViresh KUMAR 					sdhci->data->card_power_gpio);
166c63b3cbaSViresh KUMAR 			goto err_pgpio_request;
167c63b3cbaSViresh KUMAR 		}
168c63b3cbaSViresh KUMAR 
169c63b3cbaSViresh KUMAR 		if (sdhci->data->power_always_enb)
170c63b3cbaSViresh KUMAR 			val = sdhci->data->power_active_high;
171c63b3cbaSViresh KUMAR 		else
172c63b3cbaSViresh KUMAR 			val = !sdhci->data->power_active_high;
173c63b3cbaSViresh KUMAR 
174c63b3cbaSViresh KUMAR 		ret = gpio_direction_output(sdhci->data->card_power_gpio, val);
175c63b3cbaSViresh KUMAR 		if (ret) {
176c63b3cbaSViresh KUMAR 			dev_dbg(&pdev->dev, "gpio set direction fail: %d\n",
177c63b3cbaSViresh KUMAR 					sdhci->data->card_power_gpio);
178c63b3cbaSViresh KUMAR 			goto err_pgpio_direction;
179c63b3cbaSViresh KUMAR 		}
180c63b3cbaSViresh KUMAR 
181c63b3cbaSViresh KUMAR 		gpio_set_value(sdhci->data->card_power_gpio, 1);
182c63b3cbaSViresh KUMAR 	}
183c63b3cbaSViresh KUMAR 
184c63b3cbaSViresh KUMAR 	if (sdhci->data->card_int_gpio >= 0) {
185c63b3cbaSViresh KUMAR 		ret = gpio_request(sdhci->data->card_int_gpio, "sdhci");
186c63b3cbaSViresh KUMAR 		if (ret < 0) {
187c63b3cbaSViresh KUMAR 			dev_dbg(&pdev->dev, "gpio request fail: %d\n",
188c63b3cbaSViresh KUMAR 					sdhci->data->card_int_gpio);
189c63b3cbaSViresh KUMAR 			goto err_igpio_request;
190c63b3cbaSViresh KUMAR 		}
191c63b3cbaSViresh KUMAR 
192c63b3cbaSViresh KUMAR 		ret = gpio_direction_input(sdhci->data->card_int_gpio);
193c63b3cbaSViresh KUMAR 		if (ret) {
194c63b3cbaSViresh KUMAR 			dev_dbg(&pdev->dev, "gpio set direction fail: %d\n",
195c63b3cbaSViresh KUMAR 					sdhci->data->card_int_gpio);
196c63b3cbaSViresh KUMAR 			goto err_igpio_direction;
197c63b3cbaSViresh KUMAR 		}
198c63b3cbaSViresh KUMAR 		ret = request_irq(gpio_to_irq(sdhci->data->card_int_gpio),
199c63b3cbaSViresh KUMAR 				sdhci_gpio_irq, IRQF_TRIGGER_LOW,
200c63b3cbaSViresh KUMAR 				mmc_hostname(host->mmc), pdev);
201c63b3cbaSViresh KUMAR 		if (ret) {
202c63b3cbaSViresh KUMAR 			dev_dbg(&pdev->dev, "gpio request irq fail: %d\n",
203c63b3cbaSViresh KUMAR 					sdhci->data->card_int_gpio);
204c63b3cbaSViresh KUMAR 			goto err_igpio_request_irq;
205c63b3cbaSViresh KUMAR 		}
206c63b3cbaSViresh KUMAR 
207c63b3cbaSViresh KUMAR 	}
208c63b3cbaSViresh KUMAR 
209c63b3cbaSViresh KUMAR 	return 0;
210c63b3cbaSViresh KUMAR 
211c63b3cbaSViresh KUMAR err_igpio_request_irq:
212c63b3cbaSViresh KUMAR err_igpio_direction:
213c63b3cbaSViresh KUMAR 	if (sdhci->data->card_int_gpio >= 0)
214c63b3cbaSViresh KUMAR 		gpio_free(sdhci->data->card_int_gpio);
215c63b3cbaSViresh KUMAR err_igpio_request:
216c63b3cbaSViresh KUMAR err_pgpio_direction:
217c63b3cbaSViresh KUMAR 	if (sdhci->data->card_power_gpio >= 0)
218c63b3cbaSViresh KUMAR 		gpio_free(sdhci->data->card_power_gpio);
219c63b3cbaSViresh KUMAR err_pgpio_request:
220c63b3cbaSViresh KUMAR 	platform_set_drvdata(pdev, NULL);
221c63b3cbaSViresh KUMAR 	sdhci_remove_host(host, 1);
222c63b3cbaSViresh KUMAR err_add_host:
223c63b3cbaSViresh KUMAR 	iounmap(host->ioaddr);
224c63b3cbaSViresh KUMAR err_ioremap:
225c63b3cbaSViresh KUMAR 	sdhci_free_host(host);
226c63b3cbaSViresh KUMAR err_alloc_host:
227c63b3cbaSViresh KUMAR 	clk_disable(sdhci->clk);
228c63b3cbaSViresh KUMAR err_clk_enb:
229c63b3cbaSViresh KUMAR 	clk_put(sdhci->clk);
230c63b3cbaSViresh KUMAR err_clk_get:
231c63b3cbaSViresh KUMAR 	kfree(sdhci);
232c63b3cbaSViresh KUMAR err_kzalloc:
233c63b3cbaSViresh KUMAR 	release_mem_region(iomem->start, resource_size(iomem));
234c63b3cbaSViresh KUMAR err:
235c63b3cbaSViresh KUMAR 	dev_err(&pdev->dev, "spear-sdhci probe failed: %d\n", ret);
236c63b3cbaSViresh KUMAR 	return ret;
237c63b3cbaSViresh KUMAR }
238c63b3cbaSViresh KUMAR 
239c63b3cbaSViresh KUMAR static int __devexit sdhci_remove(struct platform_device *pdev)
240c63b3cbaSViresh KUMAR {
241c63b3cbaSViresh KUMAR 	struct sdhci_host *host = platform_get_drvdata(pdev);
242c63b3cbaSViresh KUMAR 	struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
243c63b3cbaSViresh KUMAR 	struct spear_sdhci *sdhci = dev_get_platdata(&pdev->dev);
244c63b3cbaSViresh KUMAR 	int dead;
245c63b3cbaSViresh KUMAR 	u32 scratch;
246c63b3cbaSViresh KUMAR 
247c63b3cbaSViresh KUMAR 	if (sdhci->data) {
248c63b3cbaSViresh KUMAR 		if (sdhci->data->card_int_gpio >= 0) {
249c63b3cbaSViresh KUMAR 			free_irq(gpio_to_irq(sdhci->data->card_int_gpio), pdev);
250c63b3cbaSViresh KUMAR 			gpio_free(sdhci->data->card_int_gpio);
251c63b3cbaSViresh KUMAR 		}
252c63b3cbaSViresh KUMAR 
253c63b3cbaSViresh KUMAR 		if (sdhci->data->card_power_gpio >= 0)
254c63b3cbaSViresh KUMAR 			gpio_free(sdhci->data->card_power_gpio);
255c63b3cbaSViresh KUMAR 	}
256c63b3cbaSViresh KUMAR 
257c63b3cbaSViresh KUMAR 	platform_set_drvdata(pdev, NULL);
258c63b3cbaSViresh KUMAR 	dead = 0;
259c63b3cbaSViresh KUMAR 	scratch = readl(host->ioaddr + SDHCI_INT_STATUS);
260c63b3cbaSViresh KUMAR 	if (scratch == (u32)-1)
261c63b3cbaSViresh KUMAR 		dead = 1;
262c63b3cbaSViresh KUMAR 
263c63b3cbaSViresh KUMAR 	sdhci_remove_host(host, dead);
264c63b3cbaSViresh KUMAR 	iounmap(host->ioaddr);
265c63b3cbaSViresh KUMAR 	sdhci_free_host(host);
266c63b3cbaSViresh KUMAR 	clk_disable(sdhci->clk);
267c63b3cbaSViresh KUMAR 	clk_put(sdhci->clk);
268c63b3cbaSViresh KUMAR 	kfree(sdhci);
269c63b3cbaSViresh KUMAR 	if (iomem)
270c63b3cbaSViresh KUMAR 		release_mem_region(iomem->start, resource_size(iomem));
271c63b3cbaSViresh KUMAR 
272c63b3cbaSViresh KUMAR 	return 0;
273c63b3cbaSViresh KUMAR }
274c63b3cbaSViresh KUMAR 
275c63b3cbaSViresh KUMAR static struct platform_driver sdhci_driver = {
276c63b3cbaSViresh KUMAR 	.driver = {
277c63b3cbaSViresh KUMAR 		.name	= "sdhci",
278c63b3cbaSViresh KUMAR 		.owner	= THIS_MODULE,
279c63b3cbaSViresh KUMAR 	},
280c63b3cbaSViresh KUMAR 	.probe		= sdhci_probe,
281c63b3cbaSViresh KUMAR 	.remove		= __devexit_p(sdhci_remove),
282c63b3cbaSViresh KUMAR };
283c63b3cbaSViresh KUMAR 
284c63b3cbaSViresh KUMAR static int __init sdhci_init(void)
285c63b3cbaSViresh KUMAR {
286c63b3cbaSViresh KUMAR 	return platform_driver_register(&sdhci_driver);
287c63b3cbaSViresh KUMAR }
288c63b3cbaSViresh KUMAR module_init(sdhci_init);
289c63b3cbaSViresh KUMAR 
290c63b3cbaSViresh KUMAR static void __exit sdhci_exit(void)
291c63b3cbaSViresh KUMAR {
292c63b3cbaSViresh KUMAR 	platform_driver_unregister(&sdhci_driver);
293c63b3cbaSViresh KUMAR }
294c63b3cbaSViresh KUMAR module_exit(sdhci_exit);
295c63b3cbaSViresh KUMAR 
296c63b3cbaSViresh KUMAR MODULE_DESCRIPTION("SPEAr Secure Digital Host Controller Interface driver");
297c63b3cbaSViresh KUMAR MODULE_AUTHOR("Viresh Kumar <viresh.kumar@st.com>");
298c63b3cbaSViresh KUMAR MODULE_LICENSE("GPL v2");
299