1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * sdhci-pltfm.c Support for SDHCI platform devices |
4 | * Copyright (c) 2009 Intel Corporation |
5 | * |
6 | * Copyright (c) 2007, 2011 Freescale Semiconductor, Inc. |
7 | * Copyright (c) 2009 MontaVista Software, Inc. |
8 | * |
9 | * Authors: Xiaobo Xie <X.Xie@freescale.com> |
10 | * Anton Vorontsov <avorontsov@ru.mvista.com> |
11 | */ |
12 | |
13 | /* Supports: |
14 | * SDHCI platform devices |
15 | * |
16 | * Inspired by sdhci-pci.c, by Pierre Ossman |
17 | */ |
18 | |
19 | #include <linux/err.h> |
20 | #include <linux/module.h> |
21 | #include <linux/property.h> |
22 | #ifdef CONFIG_PPC |
23 | #include <asm/machdep.h> |
24 | #endif |
25 | #include "sdhci-pltfm.h" |
26 | |
27 | unsigned int sdhci_pltfm_clk_get_max_clock(struct sdhci_host *host) |
28 | { |
29 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
30 | |
31 | return clk_get_rate(clk: pltfm_host->clk); |
32 | } |
33 | EXPORT_SYMBOL_GPL(sdhci_pltfm_clk_get_max_clock); |
34 | |
35 | static const struct sdhci_ops sdhci_pltfm_ops = { |
36 | .set_clock = sdhci_set_clock, |
37 | .set_bus_width = sdhci_set_bus_width, |
38 | .reset = sdhci_reset, |
39 | .set_uhs_signaling = sdhci_set_uhs_signaling, |
40 | }; |
41 | |
42 | static bool sdhci_wp_inverted(struct device *dev) |
43 | { |
44 | if (device_property_present(dev, propname: "sdhci,wp-inverted" ) || |
45 | device_property_present(dev, propname: "wp-inverted" )) |
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 | static void sdhci_get_compatibility(struct platform_device *pdev) |
57 | { |
58 | struct device *dev = &pdev->dev; |
59 | struct sdhci_host *host = platform_get_drvdata(pdev); |
60 | |
61 | if (device_is_compatible(dev, compat: "fsl,p2020-rev1-esdhc" )) |
62 | host->quirks |= SDHCI_QUIRK_BROKEN_DMA; |
63 | |
64 | if (device_is_compatible(dev, compat: "fsl,p2020-esdhc" ) || |
65 | device_is_compatible(dev, compat: "fsl,p1010-esdhc" ) || |
66 | device_is_compatible(dev, compat: "fsl,t4240-esdhc" ) || |
67 | device_is_compatible(dev, compat: "fsl,mpc8536-esdhc" )) |
68 | host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; |
69 | } |
70 | |
71 | void sdhci_get_property(struct platform_device *pdev) |
72 | { |
73 | struct device *dev = &pdev->dev; |
74 | struct sdhci_host *host = platform_get_drvdata(pdev); |
75 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
76 | u32 bus_width; |
77 | |
78 | if (device_property_present(dev, propname: "sdhci,auto-cmd12" )) |
79 | host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12; |
80 | |
81 | if (device_property_present(dev, propname: "sdhci,1-bit-only" ) || |
82 | (device_property_read_u32(dev, propname: "bus-width" , val: &bus_width) == 0 && |
83 | bus_width == 1)) |
84 | host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA; |
85 | |
86 | if (sdhci_wp_inverted(dev)) |
87 | host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT; |
88 | |
89 | if (device_property_present(dev, propname: "broken-cd" )) |
90 | host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; |
91 | |
92 | if (device_property_present(dev, propname: "no-1-8-v" )) |
93 | host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V; |
94 | |
95 | sdhci_get_compatibility(pdev); |
96 | |
97 | device_property_read_u32(dev, propname: "clock-frequency" , val: &pltfm_host->clock); |
98 | |
99 | if (device_property_present(dev, propname: "keep-power-in-suspend" )) |
100 | host->mmc->pm_caps |= MMC_PM_KEEP_POWER; |
101 | |
102 | if (device_property_read_bool(dev, propname: "wakeup-source" ) || |
103 | device_property_read_bool(dev, propname: "enable-sdio-wakeup" )) /* legacy */ |
104 | host->mmc->pm_caps |= MMC_PM_WAKE_SDIO_IRQ; |
105 | } |
106 | EXPORT_SYMBOL_GPL(sdhci_get_property); |
107 | |
108 | struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev, |
109 | const struct sdhci_pltfm_data *pdata, |
110 | size_t priv_size) |
111 | { |
112 | struct sdhci_host *host; |
113 | void __iomem *ioaddr; |
114 | int irq; |
115 | |
116 | ioaddr = devm_platform_ioremap_resource(pdev, index: 0); |
117 | if (IS_ERR(ptr: ioaddr)) |
118 | return ERR_CAST(ptr: ioaddr); |
119 | |
120 | irq = platform_get_irq(pdev, 0); |
121 | if (irq < 0) |
122 | return ERR_PTR(error: irq); |
123 | |
124 | host = sdhci_alloc_host(dev: &pdev->dev, |
125 | priv_size: sizeof(struct sdhci_pltfm_host) + priv_size); |
126 | if (IS_ERR(ptr: host)) { |
127 | dev_err(&pdev->dev, "%s failed %pe\n" , __func__, host); |
128 | return ERR_CAST(ptr: host); |
129 | } |
130 | |
131 | host->ioaddr = ioaddr; |
132 | host->irq = irq; |
133 | host->hw_name = dev_name(dev: &pdev->dev); |
134 | if (pdata && pdata->ops) |
135 | host->ops = pdata->ops; |
136 | else |
137 | host->ops = &sdhci_pltfm_ops; |
138 | if (pdata) { |
139 | host->quirks = pdata->quirks; |
140 | host->quirks2 = pdata->quirks2; |
141 | } |
142 | |
143 | platform_set_drvdata(pdev, data: host); |
144 | |
145 | return host; |
146 | } |
147 | EXPORT_SYMBOL_GPL(sdhci_pltfm_init); |
148 | |
149 | void sdhci_pltfm_free(struct platform_device *pdev) |
150 | { |
151 | struct sdhci_host *host = platform_get_drvdata(pdev); |
152 | |
153 | sdhci_free_host(host); |
154 | } |
155 | EXPORT_SYMBOL_GPL(sdhci_pltfm_free); |
156 | |
157 | int sdhci_pltfm_init_and_add_host(struct platform_device *pdev, |
158 | const struct sdhci_pltfm_data *pdata, |
159 | size_t priv_size) |
160 | { |
161 | struct sdhci_host *host; |
162 | int ret = 0; |
163 | |
164 | host = sdhci_pltfm_init(pdev, pdata, priv_size); |
165 | if (IS_ERR(ptr: host)) |
166 | return PTR_ERR(ptr: host); |
167 | |
168 | sdhci_get_property(pdev); |
169 | |
170 | ret = sdhci_add_host(host); |
171 | if (ret) |
172 | sdhci_pltfm_free(pdev); |
173 | |
174 | return ret; |
175 | } |
176 | EXPORT_SYMBOL_GPL(sdhci_pltfm_init_and_add_host); |
177 | |
178 | void sdhci_pltfm_remove(struct platform_device *pdev) |
179 | { |
180 | struct sdhci_host *host = platform_get_drvdata(pdev); |
181 | int dead = (readl(addr: host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); |
182 | |
183 | sdhci_remove_host(host, dead); |
184 | sdhci_pltfm_free(pdev); |
185 | } |
186 | EXPORT_SYMBOL_GPL(sdhci_pltfm_remove); |
187 | |
188 | #ifdef CONFIG_PM_SLEEP |
189 | int sdhci_pltfm_suspend(struct device *dev) |
190 | { |
191 | struct sdhci_host *host = dev_get_drvdata(dev); |
192 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
193 | int ret; |
194 | |
195 | if (host->tuning_mode != SDHCI_TUNING_MODE_3) |
196 | mmc_retune_needed(host: host->mmc); |
197 | |
198 | ret = sdhci_suspend_host(host); |
199 | if (ret) |
200 | return ret; |
201 | |
202 | clk_disable_unprepare(clk: pltfm_host->clk); |
203 | |
204 | return 0; |
205 | } |
206 | EXPORT_SYMBOL_GPL(sdhci_pltfm_suspend); |
207 | |
208 | int sdhci_pltfm_resume(struct device *dev) |
209 | { |
210 | struct sdhci_host *host = dev_get_drvdata(dev); |
211 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
212 | int ret; |
213 | |
214 | ret = clk_prepare_enable(clk: pltfm_host->clk); |
215 | if (ret) |
216 | return ret; |
217 | |
218 | ret = sdhci_resume_host(host); |
219 | if (ret) |
220 | clk_disable_unprepare(clk: pltfm_host->clk); |
221 | |
222 | return ret; |
223 | } |
224 | EXPORT_SYMBOL_GPL(sdhci_pltfm_resume); |
225 | #endif |
226 | |
227 | const struct dev_pm_ops sdhci_pltfm_pmops = { |
228 | SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_suspend, sdhci_pltfm_resume) |
229 | }; |
230 | EXPORT_SYMBOL_GPL(sdhci_pltfm_pmops); |
231 | |
232 | static int __init sdhci_pltfm_drv_init(void) |
233 | { |
234 | pr_info("sdhci-pltfm: SDHCI platform and OF driver helper\n" ); |
235 | |
236 | return 0; |
237 | } |
238 | module_init(sdhci_pltfm_drv_init); |
239 | |
240 | static void __exit sdhci_pltfm_drv_exit(void) |
241 | { |
242 | } |
243 | module_exit(sdhci_pltfm_drv_exit); |
244 | |
245 | MODULE_DESCRIPTION("SDHCI platform and OF driver helper" ); |
246 | MODULE_AUTHOR("Intel Corporation" ); |
247 | MODULE_LICENSE("GPL v2" ); |
248 | |