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