xref: /openbmc/linux/drivers/mmc/host/sdhci-spear.c (revision d1f81a64)
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>
2088b47679SPaul Gortmaker #include <linux/module.h>
21c63b3cbaSViresh KUMAR #include <linux/interrupt.h>
22c63b3cbaSViresh KUMAR #include <linux/irq.h>
23c63b3cbaSViresh KUMAR #include <linux/platform_device.h>
24b70a7fabSViresh Kumar #include <linux/pm.h>
25c63b3cbaSViresh KUMAR #include <linux/slab.h>
26c63b3cbaSViresh KUMAR #include <linux/mmc/host.h>
27c63b3cbaSViresh KUMAR #include <linux/mmc/sdhci-spear.h>
28c63b3cbaSViresh KUMAR #include <linux/io.h>
29c63b3cbaSViresh KUMAR #include "sdhci.h"
30c63b3cbaSViresh KUMAR 
31c63b3cbaSViresh KUMAR struct spear_sdhci {
32c63b3cbaSViresh KUMAR 	struct clk *clk;
33c63b3cbaSViresh KUMAR 	struct sdhci_plat_data *data;
34c63b3cbaSViresh KUMAR };
35c63b3cbaSViresh KUMAR 
36c63b3cbaSViresh KUMAR /* sdhci ops */
37c63b3cbaSViresh KUMAR static struct sdhci_ops sdhci_pltfm_ops = {
38c63b3cbaSViresh KUMAR 	/* Nothing to do for now. */
39c63b3cbaSViresh KUMAR };
40c63b3cbaSViresh KUMAR 
41c63b3cbaSViresh KUMAR /* gpio card detection interrupt handler */
42c63b3cbaSViresh KUMAR static irqreturn_t sdhci_gpio_irq(int irq, void *dev_id)
43c63b3cbaSViresh KUMAR {
44c63b3cbaSViresh KUMAR 	struct platform_device *pdev = dev_id;
45c63b3cbaSViresh KUMAR 	struct sdhci_host *host = platform_get_drvdata(pdev);
46c63b3cbaSViresh KUMAR 	struct spear_sdhci *sdhci = dev_get_platdata(&pdev->dev);
47c63b3cbaSViresh KUMAR 	unsigned long gpio_irq_type;
48c63b3cbaSViresh KUMAR 	int val;
49c63b3cbaSViresh KUMAR 
50c63b3cbaSViresh KUMAR 	val = gpio_get_value(sdhci->data->card_int_gpio);
51c63b3cbaSViresh KUMAR 
52c63b3cbaSViresh KUMAR 	/* val == 1 -> card removed, val == 0 -> card inserted */
53c63b3cbaSViresh KUMAR 	/* if card removed - set irq for low level, else vice versa */
54c63b3cbaSViresh KUMAR 	gpio_irq_type = val ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH;
55dced35aeSThomas Gleixner 	irq_set_irq_type(irq, gpio_irq_type);
56c63b3cbaSViresh KUMAR 
57c63b3cbaSViresh KUMAR 	if (sdhci->data->card_power_gpio >= 0) {
58c63b3cbaSViresh KUMAR 		if (!sdhci->data->power_always_enb) {
59c63b3cbaSViresh KUMAR 			/* if card inserted, give power, otherwise remove it */
60c63b3cbaSViresh KUMAR 			val = sdhci->data->power_active_high ? !val : val ;
61c63b3cbaSViresh KUMAR 			gpio_set_value(sdhci->data->card_power_gpio, val);
62c63b3cbaSViresh KUMAR 		}
63c63b3cbaSViresh KUMAR 	}
64c63b3cbaSViresh KUMAR 
65c63b3cbaSViresh KUMAR 	/* inform sdhci driver about card insertion/removal */
66c63b3cbaSViresh KUMAR 	tasklet_schedule(&host->card_tasklet);
67c63b3cbaSViresh KUMAR 
68c63b3cbaSViresh KUMAR 	return IRQ_HANDLED;
69c63b3cbaSViresh KUMAR }
70c63b3cbaSViresh KUMAR 
71c63b3cbaSViresh KUMAR static int __devinit sdhci_probe(struct platform_device *pdev)
72c63b3cbaSViresh KUMAR {
73c63b3cbaSViresh KUMAR 	struct sdhci_host *host;
74c63b3cbaSViresh KUMAR 	struct resource *iomem;
75c63b3cbaSViresh KUMAR 	struct spear_sdhci *sdhci;
76c63b3cbaSViresh KUMAR 	int ret;
77c63b3cbaSViresh KUMAR 
78c63b3cbaSViresh KUMAR 	BUG_ON(pdev == NULL);
79c63b3cbaSViresh KUMAR 
80c63b3cbaSViresh KUMAR 	iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
81c63b3cbaSViresh KUMAR 	if (!iomem) {
82c63b3cbaSViresh KUMAR 		ret = -ENOMEM;
83c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "memory resource not defined\n");
84c63b3cbaSViresh KUMAR 		goto err;
85c63b3cbaSViresh KUMAR 	}
86c63b3cbaSViresh KUMAR 
87c63b3cbaSViresh KUMAR 	if (!request_mem_region(iomem->start, resource_size(iomem),
88c63b3cbaSViresh KUMAR 				"spear-sdhci")) {
89c63b3cbaSViresh KUMAR 		ret = -EBUSY;
90c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "cannot request region\n");
91c63b3cbaSViresh KUMAR 		goto err;
92c63b3cbaSViresh KUMAR 	}
93c63b3cbaSViresh KUMAR 
94c63b3cbaSViresh KUMAR 	sdhci = kzalloc(sizeof(*sdhci), GFP_KERNEL);
95c63b3cbaSViresh KUMAR 	if (!sdhci) {
96c63b3cbaSViresh KUMAR 		ret = -ENOMEM;
97c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "cannot allocate memory for sdhci\n");
98c63b3cbaSViresh KUMAR 		goto err_kzalloc;
99c63b3cbaSViresh KUMAR 	}
100c63b3cbaSViresh KUMAR 
101c63b3cbaSViresh KUMAR 	/* clk enable */
102c63b3cbaSViresh KUMAR 	sdhci->clk = clk_get(&pdev->dev, NULL);
103c63b3cbaSViresh KUMAR 	if (IS_ERR(sdhci->clk)) {
104c63b3cbaSViresh KUMAR 		ret = PTR_ERR(sdhci->clk);
105c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "Error getting clock\n");
106c63b3cbaSViresh KUMAR 		goto err_clk_get;
107c63b3cbaSViresh KUMAR 	}
108c63b3cbaSViresh KUMAR 
109c63b3cbaSViresh KUMAR 	ret = clk_enable(sdhci->clk);
110c63b3cbaSViresh KUMAR 	if (ret) {
111c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "Error enabling clock\n");
112c63b3cbaSViresh KUMAR 		goto err_clk_enb;
113c63b3cbaSViresh KUMAR 	}
114c63b3cbaSViresh KUMAR 
115c63b3cbaSViresh KUMAR 	/* overwrite platform_data */
116c63b3cbaSViresh KUMAR 	sdhci->data = dev_get_platdata(&pdev->dev);
117c63b3cbaSViresh KUMAR 	pdev->dev.platform_data = sdhci;
118c63b3cbaSViresh KUMAR 
119c63b3cbaSViresh KUMAR 	if (pdev->dev.parent)
120c63b3cbaSViresh KUMAR 		host = sdhci_alloc_host(pdev->dev.parent, 0);
121c63b3cbaSViresh KUMAR 	else
122c63b3cbaSViresh KUMAR 		host = sdhci_alloc_host(&pdev->dev, 0);
123c63b3cbaSViresh KUMAR 
124c63b3cbaSViresh KUMAR 	if (IS_ERR(host)) {
125c63b3cbaSViresh KUMAR 		ret = PTR_ERR(host);
126c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "error allocating host\n");
127c63b3cbaSViresh KUMAR 		goto err_alloc_host;
128c63b3cbaSViresh KUMAR 	}
129c63b3cbaSViresh KUMAR 
130c63b3cbaSViresh KUMAR 	host->hw_name = "sdhci";
131c63b3cbaSViresh KUMAR 	host->ops = &sdhci_pltfm_ops;
132c63b3cbaSViresh KUMAR 	host->irq = platform_get_irq(pdev, 0);
133c63b3cbaSViresh KUMAR 	host->quirks = SDHCI_QUIRK_BROKEN_ADMA;
134c63b3cbaSViresh KUMAR 
135c63b3cbaSViresh KUMAR 	host->ioaddr = ioremap(iomem->start, resource_size(iomem));
136c63b3cbaSViresh KUMAR 	if (!host->ioaddr) {
137c63b3cbaSViresh KUMAR 		ret = -ENOMEM;
138c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "failed to remap registers\n");
139c63b3cbaSViresh KUMAR 		goto err_ioremap;
140c63b3cbaSViresh KUMAR 	}
141c63b3cbaSViresh KUMAR 
142c63b3cbaSViresh KUMAR 	ret = sdhci_add_host(host);
143c63b3cbaSViresh KUMAR 	if (ret) {
144c63b3cbaSViresh KUMAR 		dev_dbg(&pdev->dev, "error adding host\n");
145c63b3cbaSViresh KUMAR 		goto err_add_host;
146c63b3cbaSViresh KUMAR 	}
147c63b3cbaSViresh KUMAR 
148c63b3cbaSViresh KUMAR 	platform_set_drvdata(pdev, host);
149c63b3cbaSViresh KUMAR 
150c63b3cbaSViresh KUMAR 	/*
151c63b3cbaSViresh KUMAR 	 * It is optional to use GPIOs for sdhci Power control & sdhci card
152c63b3cbaSViresh KUMAR 	 * interrupt detection. If sdhci->data is NULL, then use original sdhci
153c63b3cbaSViresh KUMAR 	 * lines otherwise GPIO lines.
154c63b3cbaSViresh KUMAR 	 * If GPIO is selected for power control, then power should be disabled
155c63b3cbaSViresh KUMAR 	 * after card removal and should be enabled when card insertion
156c63b3cbaSViresh KUMAR 	 * interrupt occurs
157c63b3cbaSViresh KUMAR 	 */
158c63b3cbaSViresh KUMAR 	if (!sdhci->data)
159c63b3cbaSViresh KUMAR 		return 0;
160c63b3cbaSViresh KUMAR 
161c63b3cbaSViresh KUMAR 	if (sdhci->data->card_power_gpio >= 0) {
162c63b3cbaSViresh KUMAR 		int val = 0;
163c63b3cbaSViresh KUMAR 
164c63b3cbaSViresh KUMAR 		ret = gpio_request(sdhci->data->card_power_gpio, "sdhci");
165c63b3cbaSViresh KUMAR 		if (ret < 0) {
166c63b3cbaSViresh KUMAR 			dev_dbg(&pdev->dev, "gpio request fail: %d\n",
167c63b3cbaSViresh KUMAR 					sdhci->data->card_power_gpio);
168c63b3cbaSViresh KUMAR 			goto err_pgpio_request;
169c63b3cbaSViresh KUMAR 		}
170c63b3cbaSViresh KUMAR 
171c63b3cbaSViresh KUMAR 		if (sdhci->data->power_always_enb)
172c63b3cbaSViresh KUMAR 			val = sdhci->data->power_active_high;
173c63b3cbaSViresh KUMAR 		else
174c63b3cbaSViresh KUMAR 			val = !sdhci->data->power_active_high;
175c63b3cbaSViresh KUMAR 
176c63b3cbaSViresh KUMAR 		ret = gpio_direction_output(sdhci->data->card_power_gpio, val);
177c63b3cbaSViresh KUMAR 		if (ret) {
178c63b3cbaSViresh KUMAR 			dev_dbg(&pdev->dev, "gpio set direction fail: %d\n",
179c63b3cbaSViresh KUMAR 					sdhci->data->card_power_gpio);
180c63b3cbaSViresh KUMAR 			goto err_pgpio_direction;
181c63b3cbaSViresh KUMAR 		}
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 
275b70a7fabSViresh Kumar #ifdef CONFIG_PM
276b70a7fabSViresh Kumar static int sdhci_suspend(struct device *dev)
277b70a7fabSViresh Kumar {
278b70a7fabSViresh Kumar 	struct sdhci_host *host = dev_get_drvdata(dev);
279b70a7fabSViresh Kumar 	struct spear_sdhci *sdhci = dev_get_platdata(dev);
280b70a7fabSViresh Kumar 	pm_message_t state = {.event = 0};
281b70a7fabSViresh Kumar 	int ret;
282b70a7fabSViresh Kumar 
283b70a7fabSViresh Kumar 	ret = sdhci_suspend_host(host, state);
284b70a7fabSViresh Kumar 	if (!ret)
285b70a7fabSViresh Kumar 		clk_disable(sdhci->clk);
286b70a7fabSViresh Kumar 
287b70a7fabSViresh Kumar 	return ret;
288b70a7fabSViresh Kumar }
289b70a7fabSViresh Kumar 
290b70a7fabSViresh Kumar static int sdhci_resume(struct device *dev)
291b70a7fabSViresh Kumar {
292b70a7fabSViresh Kumar 	struct sdhci_host *host = dev_get_drvdata(dev);
293b70a7fabSViresh Kumar 	struct spear_sdhci *sdhci = dev_get_platdata(dev);
294b70a7fabSViresh Kumar 	int ret;
295b70a7fabSViresh Kumar 
296b70a7fabSViresh Kumar 	ret = clk_enable(sdhci->clk);
297b70a7fabSViresh Kumar 	if (ret) {
298b70a7fabSViresh Kumar 		dev_dbg(dev, "Resume: Error enabling clock\n");
299b70a7fabSViresh Kumar 		return ret;
300b70a7fabSViresh Kumar 	}
301b70a7fabSViresh Kumar 
302b70a7fabSViresh Kumar 	return sdhci_resume_host(host);
303b70a7fabSViresh Kumar }
304b70a7fabSViresh Kumar 
305b70a7fabSViresh Kumar const struct dev_pm_ops sdhci_pm_ops = {
306b70a7fabSViresh Kumar 	.suspend	= sdhci_suspend,
307b70a7fabSViresh Kumar 	.resume		= sdhci_resume,
308b70a7fabSViresh Kumar };
309b70a7fabSViresh Kumar #endif
310b70a7fabSViresh Kumar 
311c63b3cbaSViresh KUMAR static struct platform_driver sdhci_driver = {
312c63b3cbaSViresh KUMAR 	.driver = {
313c63b3cbaSViresh KUMAR 		.name	= "sdhci",
314c63b3cbaSViresh KUMAR 		.owner	= THIS_MODULE,
315b70a7fabSViresh Kumar #ifdef CONFIG_PM
316b70a7fabSViresh Kumar 		.pm	= &sdhci_pm_ops,
317b70a7fabSViresh Kumar #endif
318c63b3cbaSViresh KUMAR 	},
319c63b3cbaSViresh KUMAR 	.probe		= sdhci_probe,
320c63b3cbaSViresh KUMAR 	.remove		= __devexit_p(sdhci_remove),
321c63b3cbaSViresh KUMAR };
322c63b3cbaSViresh KUMAR 
323d1f81a64SAxel Lin module_platform_driver(sdhci_driver);
324c63b3cbaSViresh KUMAR 
325c63b3cbaSViresh KUMAR MODULE_DESCRIPTION("SPEAr Secure Digital Host Controller Interface driver");
326c63b3cbaSViresh KUMAR MODULE_AUTHOR("Viresh Kumar <viresh.kumar@st.com>");
327c63b3cbaSViresh KUMAR MODULE_LICENSE("GPL v2");
328