xref: /linux/drivers/mmc/host/sdhci-pltfm.c (revision f2ee442115c9b6219083c019939a9cc0c9abb2f8)
1 /*
2  * sdhci-pltfm.c Support for SDHCI platform devices
3  * Copyright (c) 2009 Intel Corporation
4  *
5  * Copyright (c) 2007 Freescale Semiconductor, Inc.
6  * Copyright (c) 2009 MontaVista Software, Inc.
7  *
8  * Authors: Xiaobo Xie <X.Xie@freescale.com>
9  *	    Anton Vorontsov <avorontsov@ru.mvista.com>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License version 2 as
13  * published by the Free Software Foundation.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23  */
24 
25 /* Supports:
26  * SDHCI platform devices
27  *
28  * Inspired by sdhci-pci.c, by Pierre Ossman
29  */
30 
31 #include <linux/err.h>
32 #include <linux/module.h>
33 #include <linux/of.h>
34 #ifdef CONFIG_PPC
35 #include <asm/machdep.h>
36 #endif
37 #include "sdhci-pltfm.h"
38 
39 static struct sdhci_ops sdhci_pltfm_ops = {
40 };
41 
42 #ifdef CONFIG_OF
43 static bool sdhci_of_wp_inverted(struct device_node *np)
44 {
45 	if (of_get_property(np, "sdhci,wp-inverted", NULL))
46 		return true;
47 
48 	/* Old device trees don't have the wp-inverted property. */
49 #ifdef CONFIG_PPC
50 	return machine_is(mpc837x_rdb) || machine_is(mpc837x_mds);
51 #else
52 	return false;
53 #endif /* CONFIG_PPC */
54 }
55 
56 void sdhci_get_of_property(struct platform_device *pdev)
57 {
58 	struct device_node *np = pdev->dev.of_node;
59 	struct sdhci_host *host = platform_get_drvdata(pdev);
60 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
61 	const __be32 *clk;
62 	int size;
63 
64 	if (of_device_is_available(np)) {
65 		if (of_get_property(np, "sdhci,auto-cmd12", NULL))
66 			host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
67 
68 		if (of_get_property(np, "sdhci,1-bit-only", NULL))
69 			host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;
70 
71 		if (sdhci_of_wp_inverted(np))
72 			host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT;
73 
74 		clk = of_get_property(np, "clock-frequency", &size);
75 		if (clk && size == sizeof(*clk) && *clk)
76 			pltfm_host->clock = be32_to_cpup(clk);
77 	}
78 }
79 #else
80 void sdhci_get_of_property(struct platform_device *pdev) {}
81 #endif /* CONFIG_OF */
82 EXPORT_SYMBOL_GPL(sdhci_get_of_property);
83 
84 struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev,
85 				    struct sdhci_pltfm_data *pdata)
86 {
87 	struct sdhci_host *host;
88 	struct sdhci_pltfm_host *pltfm_host;
89 	struct device_node *np = pdev->dev.of_node;
90 	struct resource *iomem;
91 	int ret;
92 
93 	iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
94 	if (!iomem) {
95 		ret = -ENOMEM;
96 		goto err;
97 	}
98 
99 	if (resource_size(iomem) < 0x100)
100 		dev_err(&pdev->dev, "Invalid iomem size!\n");
101 
102 	/* Some PCI-based MFD need the parent here */
103 	if (pdev->dev.parent != &platform_bus && !np)
104 		host = sdhci_alloc_host(pdev->dev.parent, sizeof(*pltfm_host));
105 	else
106 		host = sdhci_alloc_host(&pdev->dev, sizeof(*pltfm_host));
107 
108 	if (IS_ERR(host)) {
109 		ret = PTR_ERR(host);
110 		goto err;
111 	}
112 
113 	pltfm_host = sdhci_priv(host);
114 
115 	host->hw_name = dev_name(&pdev->dev);
116 	if (pdata && pdata->ops)
117 		host->ops = pdata->ops;
118 	else
119 		host->ops = &sdhci_pltfm_ops;
120 	if (pdata)
121 		host->quirks = pdata->quirks;
122 	host->irq = platform_get_irq(pdev, 0);
123 
124 	if (!request_mem_region(iomem->start, resource_size(iomem),
125 		mmc_hostname(host->mmc))) {
126 		dev_err(&pdev->dev, "cannot request region\n");
127 		ret = -EBUSY;
128 		goto err_request;
129 	}
130 
131 	host->ioaddr = ioremap(iomem->start, resource_size(iomem));
132 	if (!host->ioaddr) {
133 		dev_err(&pdev->dev, "failed to remap registers\n");
134 		ret = -ENOMEM;
135 		goto err_remap;
136 	}
137 
138 	platform_set_drvdata(pdev, host);
139 
140 	return host;
141 
142 err_remap:
143 	release_mem_region(iomem->start, resource_size(iomem));
144 err_request:
145 	sdhci_free_host(host);
146 err:
147 	dev_err(&pdev->dev, "%s failed %d\n", __func__, ret);
148 	return ERR_PTR(ret);
149 }
150 EXPORT_SYMBOL_GPL(sdhci_pltfm_init);
151 
152 void sdhci_pltfm_free(struct platform_device *pdev)
153 {
154 	struct sdhci_host *host = platform_get_drvdata(pdev);
155 	struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
156 
157 	iounmap(host->ioaddr);
158 	release_mem_region(iomem->start, resource_size(iomem));
159 	sdhci_free_host(host);
160 	platform_set_drvdata(pdev, NULL);
161 }
162 EXPORT_SYMBOL_GPL(sdhci_pltfm_free);
163 
164 int sdhci_pltfm_register(struct platform_device *pdev,
165 			 struct sdhci_pltfm_data *pdata)
166 {
167 	struct sdhci_host *host;
168 	int ret = 0;
169 
170 	host = sdhci_pltfm_init(pdev, pdata);
171 	if (IS_ERR(host))
172 		return PTR_ERR(host);
173 
174 	sdhci_get_of_property(pdev);
175 
176 	ret = sdhci_add_host(host);
177 	if (ret)
178 		sdhci_pltfm_free(pdev);
179 
180 	return ret;
181 }
182 EXPORT_SYMBOL_GPL(sdhci_pltfm_register);
183 
184 int sdhci_pltfm_unregister(struct platform_device *pdev)
185 {
186 	struct sdhci_host *host = platform_get_drvdata(pdev);
187 	int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff);
188 
189 	sdhci_remove_host(host, dead);
190 	sdhci_pltfm_free(pdev);
191 
192 	return 0;
193 }
194 EXPORT_SYMBOL_GPL(sdhci_pltfm_unregister);
195 
196 #ifdef CONFIG_PM
197 int sdhci_pltfm_suspend(struct platform_device *dev, pm_message_t state)
198 {
199 	struct sdhci_host *host = platform_get_drvdata(dev);
200 
201 	return sdhci_suspend_host(host, state);
202 }
203 EXPORT_SYMBOL_GPL(sdhci_pltfm_suspend);
204 
205 int sdhci_pltfm_resume(struct platform_device *dev)
206 {
207 	struct sdhci_host *host = platform_get_drvdata(dev);
208 
209 	return sdhci_resume_host(host);
210 }
211 EXPORT_SYMBOL_GPL(sdhci_pltfm_resume);
212 #endif	/* CONFIG_PM */
213 
214 static int __init sdhci_pltfm_drv_init(void)
215 {
216 	pr_info("sdhci-pltfm: SDHCI platform and OF driver helper\n");
217 
218 	return 0;
219 }
220 module_init(sdhci_pltfm_drv_init);
221 
222 static void __exit sdhci_pltfm_drv_exit(void)
223 {
224 }
225 module_exit(sdhci_pltfm_drv_exit);
226 
227 MODULE_DESCRIPTION("SDHCI platform and OF driver helper");
228 MODULE_AUTHOR("Intel Corporation");
229 MODULE_LICENSE("GPL v2");
230