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 <vireshk@kernel.org> |
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/highmem.h> |
19 | #include <linux/module.h> |
20 | #include <linux/interrupt.h> |
21 | #include <linux/irq.h> |
22 | #include <linux/of.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/slot-gpio.h> |
28 | #include <linux/io.h> |
29 | #include "sdhci.h" |
30 | |
31 | struct spear_sdhci { |
32 | struct clk *clk; |
33 | }; |
34 | |
35 | /* sdhci ops */ |
36 | static const struct sdhci_ops sdhci_pltfm_ops = { |
37 | .set_clock = sdhci_set_clock, |
38 | .set_bus_width = sdhci_set_bus_width, |
39 | .reset = sdhci_reset, |
40 | .set_uhs_signaling = sdhci_set_uhs_signaling, |
41 | }; |
42 | |
43 | static int sdhci_probe(struct platform_device *pdev) |
44 | { |
45 | struct sdhci_host *host; |
46 | struct spear_sdhci *sdhci; |
47 | struct device *dev; |
48 | int ret; |
49 | |
50 | dev = pdev->dev.parent ? pdev->dev.parent : &pdev->dev; |
51 | host = sdhci_alloc_host(dev, priv_size: sizeof(*sdhci)); |
52 | if (IS_ERR(ptr: host)) { |
53 | ret = PTR_ERR(ptr: host); |
54 | dev_dbg(&pdev->dev, "cannot allocate memory for sdhci\n" ); |
55 | goto err; |
56 | } |
57 | |
58 | host->ioaddr = devm_platform_ioremap_resource(pdev, index: 0); |
59 | if (IS_ERR(ptr: host->ioaddr)) { |
60 | ret = PTR_ERR(ptr: host->ioaddr); |
61 | dev_dbg(&pdev->dev, "unable to map iomem: %d\n" , ret); |
62 | goto err_host; |
63 | } |
64 | |
65 | host->hw_name = "sdhci" ; |
66 | host->ops = &sdhci_pltfm_ops; |
67 | host->irq = platform_get_irq(pdev, 0); |
68 | if (host->irq < 0) { |
69 | ret = host->irq; |
70 | goto err_host; |
71 | } |
72 | host->quirks = SDHCI_QUIRK_BROKEN_ADMA; |
73 | |
74 | sdhci = sdhci_priv(host); |
75 | |
76 | /* clk enable */ |
77 | sdhci->clk = devm_clk_get(dev: &pdev->dev, NULL); |
78 | if (IS_ERR(ptr: sdhci->clk)) { |
79 | ret = PTR_ERR(ptr: sdhci->clk); |
80 | dev_dbg(&pdev->dev, "Error getting clock\n" ); |
81 | goto err_host; |
82 | } |
83 | |
84 | ret = clk_prepare_enable(clk: sdhci->clk); |
85 | if (ret) { |
86 | dev_dbg(&pdev->dev, "Error enabling clock\n" ); |
87 | goto err_host; |
88 | } |
89 | |
90 | ret = clk_set_rate(clk: sdhci->clk, rate: 50000000); |
91 | if (ret) |
92 | dev_dbg(&pdev->dev, "Error setting desired clk, clk=%lu\n" , |
93 | clk_get_rate(sdhci->clk)); |
94 | |
95 | /* |
96 | * It is optional to use GPIOs for sdhci card detection. If we |
97 | * find a descriptor using slot GPIO, we use it. |
98 | */ |
99 | ret = mmc_gpiod_request_cd(host: host->mmc, con_id: "cd" , idx: 0, override_active_level: false, debounce: 0); |
100 | if (ret == -EPROBE_DEFER) |
101 | goto disable_clk; |
102 | |
103 | ret = sdhci_add_host(host); |
104 | if (ret) |
105 | goto disable_clk; |
106 | |
107 | platform_set_drvdata(pdev, data: host); |
108 | |
109 | return 0; |
110 | |
111 | disable_clk: |
112 | clk_disable_unprepare(clk: sdhci->clk); |
113 | err_host: |
114 | sdhci_free_host(host); |
115 | err: |
116 | dev_err(&pdev->dev, "spear-sdhci probe failed: %d\n" , ret); |
117 | return ret; |
118 | } |
119 | |
120 | static void sdhci_remove(struct platform_device *pdev) |
121 | { |
122 | struct sdhci_host *host = platform_get_drvdata(pdev); |
123 | struct spear_sdhci *sdhci = sdhci_priv(host); |
124 | int dead = 0; |
125 | u32 scratch; |
126 | |
127 | scratch = readl(addr: host->ioaddr + SDHCI_INT_STATUS); |
128 | if (scratch == (u32)-1) |
129 | dead = 1; |
130 | |
131 | sdhci_remove_host(host, dead); |
132 | clk_disable_unprepare(clk: sdhci->clk); |
133 | sdhci_free_host(host); |
134 | } |
135 | |
136 | #ifdef CONFIG_PM_SLEEP |
137 | static int sdhci_suspend(struct device *dev) |
138 | { |
139 | struct sdhci_host *host = dev_get_drvdata(dev); |
140 | struct spear_sdhci *sdhci = sdhci_priv(host); |
141 | int ret; |
142 | |
143 | if (host->tuning_mode != SDHCI_TUNING_MODE_3) |
144 | mmc_retune_needed(host: host->mmc); |
145 | |
146 | ret = sdhci_suspend_host(host); |
147 | if (!ret) |
148 | clk_disable(clk: sdhci->clk); |
149 | |
150 | return ret; |
151 | } |
152 | |
153 | static int sdhci_resume(struct device *dev) |
154 | { |
155 | struct sdhci_host *host = dev_get_drvdata(dev); |
156 | struct spear_sdhci *sdhci = sdhci_priv(host); |
157 | int ret; |
158 | |
159 | ret = clk_enable(clk: sdhci->clk); |
160 | if (ret) { |
161 | dev_dbg(dev, "Resume: Error enabling clock\n" ); |
162 | return ret; |
163 | } |
164 | |
165 | return sdhci_resume_host(host); |
166 | } |
167 | #endif |
168 | |
169 | static SIMPLE_DEV_PM_OPS(sdhci_pm_ops, sdhci_suspend, sdhci_resume); |
170 | |
171 | static const struct of_device_id sdhci_spear_id_table[] = { |
172 | { .compatible = "st,spear300-sdhci" }, |
173 | {} |
174 | }; |
175 | MODULE_DEVICE_TABLE(of, sdhci_spear_id_table); |
176 | |
177 | static struct platform_driver sdhci_driver = { |
178 | .driver = { |
179 | .name = "sdhci" , |
180 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
181 | .pm = &sdhci_pm_ops, |
182 | .of_match_table = sdhci_spear_id_table, |
183 | }, |
184 | .probe = sdhci_probe, |
185 | .remove_new = sdhci_remove, |
186 | }; |
187 | |
188 | module_platform_driver(sdhci_driver); |
189 | |
190 | MODULE_DESCRIPTION("SPEAr Secure Digital Host Controller Interface driver" ); |
191 | MODULE_AUTHOR("Viresh Kumar <vireshk@kernel.org>" ); |
192 | MODULE_LICENSE("GPL v2" ); |
193 | |