Commit | Line | Data |
---|---|---|
b3b665b0 BS |
1 | /* |
2 | * SDHCI support for SiRF primaII and marco SoCs | |
3 | * | |
4 | * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. | |
5 | * | |
6 | * Licensed under GPLv2 or later. | |
7 | */ | |
8 | ||
9 | #include <linux/delay.h> | |
10 | #include <linux/device.h> | |
11 | #include <linux/mmc/host.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/of.h> | |
14 | #include <linux/of_gpio.h> | |
15 | #include <linux/mmc/slot-gpio.h> | |
b3b665b0 BS |
16 | #include "sdhci-pltfm.h" |
17 | ||
fc0b638a | 18 | #define SDHCI_CLK_DELAY_SETTING 0x4C |
1ba4c322 | 19 | #define SDHCI_SIRF_8BITBUS BIT(3) |
d1ba44a4 | 20 | #define SIRF_TUNING_COUNT 16384 |
1ba4c322 | 21 | |
b3b665b0 | 22 | struct sdhci_sirf_priv { |
b3b665b0 BS |
23 | int gpio_cd; |
24 | }; | |
25 | ||
1ba4c322 MC |
26 | static void sdhci_sirf_set_bus_width(struct sdhci_host *host, int width) |
27 | { | |
28 | u8 ctrl; | |
29 | ||
30 | ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); | |
31 | ctrl &= ~(SDHCI_CTRL_4BITBUS | SDHCI_SIRF_8BITBUS); | |
32 | ||
33 | /* | |
34 | * CSR atlas7 and prima2 SD host version is not 3.0 | |
35 | * 8bit-width enable bit of CSR SD hosts is 3, | |
36 | * while stardard hosts use bit 5 | |
37 | */ | |
38 | if (width == MMC_BUS_WIDTH_8) | |
39 | ctrl |= SDHCI_SIRF_8BITBUS; | |
40 | else if (width == MMC_BUS_WIDTH_4) | |
41 | ctrl |= SDHCI_CTRL_4BITBUS; | |
42 | ||
43 | sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); | |
44 | } | |
45 | ||
a1b0b977 WY |
46 | static u32 sdhci_sirf_readl_le(struct sdhci_host *host, int reg) |
47 | { | |
48 | u32 val = readl(host->ioaddr + reg); | |
49 | ||
50 | if (unlikely((reg == SDHCI_CAPABILITIES_1) && | |
51 | (host->mmc->caps & MMC_CAP_UHS_SDR50))) { | |
52 | /* fake CAP_1 register */ | |
53 | val = SDHCI_SUPPORT_SDR50 | SDHCI_USE_SDR50_TUNING; | |
54 | } | |
55 | ||
56 | if (unlikely(reg == SDHCI_SLOT_INT_STATUS)) { | |
57 | u32 prss = val; | |
58 | /* fake chips as V3.0 host conreoller */ | |
59 | prss &= ~(0xFF << 16); | |
60 | val = prss | (SDHCI_SPEC_300 << 16); | |
61 | } | |
62 | return val; | |
63 | } | |
64 | ||
65 | static u16 sdhci_sirf_readw_le(struct sdhci_host *host, int reg) | |
66 | { | |
67 | u16 ret = 0; | |
68 | ||
69 | ret = readw(host->ioaddr + reg); | |
70 | ||
71 | if (unlikely(reg == SDHCI_HOST_VERSION)) { | |
72 | ret = readw(host->ioaddr + SDHCI_HOST_VERSION); | |
73 | ret |= SDHCI_SPEC_300; | |
74 | } | |
75 | ||
76 | return ret; | |
77 | } | |
78 | ||
fc0b638a MC |
79 | static int sdhci_sirf_execute_tuning(struct sdhci_host *host, u32 opcode) |
80 | { | |
81 | int tuning_seq_cnt = 3; | |
d1ba44a4 | 82 | int phase; |
fc0b638a | 83 | u8 tuned_phase_cnt = 0; |
b36ac1b4 | 84 | int rc = 0, longest_range = 0; |
fc0b638a MC |
85 | int start = -1, end = 0, tuning_value = -1, range = 0; |
86 | u16 clock_setting; | |
87 | struct mmc_host *mmc = host->mmc; | |
88 | ||
89 | clock_setting = sdhci_readw(host, SDHCI_CLK_DELAY_SETTING); | |
90 | clock_setting &= ~0x3fff; | |
91 | ||
92 | retry: | |
93 | phase = 0; | |
d1ba44a4 | 94 | tuned_phase_cnt = 0; |
fc0b638a MC |
95 | do { |
96 | sdhci_writel(host, | |
b36ac1b4 | 97 | clock_setting | phase, |
fc0b638a MC |
98 | SDHCI_CLK_DELAY_SETTING); |
99 | ||
100 | if (!mmc_send_tuning(mmc)) { | |
101 | /* Tuning is successful at this tuning point */ | |
d1ba44a4 | 102 | tuned_phase_cnt++; |
fc0b638a MC |
103 | dev_dbg(mmc_dev(mmc), "%s: Found good phase = %d\n", |
104 | mmc_hostname(mmc), phase); | |
105 | if (start == -1) | |
106 | start = phase; | |
107 | end = phase; | |
108 | range++; | |
109 | if (phase == (SIRF_TUNING_COUNT - 1) | |
110 | && range > longest_range) | |
111 | tuning_value = (start + end) / 2; | |
112 | } else { | |
113 | dev_dbg(mmc_dev(mmc), "%s: Found bad phase = %d\n", | |
114 | mmc_hostname(mmc), phase); | |
115 | if (range > longest_range) { | |
116 | tuning_value = (start + end) / 2; | |
117 | longest_range = range; | |
118 | } | |
119 | start = -1; | |
120 | end = range = 0; | |
121 | } | |
d1ba44a4 | 122 | } while (++phase < SIRF_TUNING_COUNT); |
fc0b638a MC |
123 | |
124 | if (tuned_phase_cnt && tuning_value > 0) { | |
125 | /* | |
126 | * Finally set the selected phase in delay | |
127 | * line hw block. | |
128 | */ | |
129 | phase = tuning_value; | |
130 | sdhci_writel(host, | |
b36ac1b4 | 131 | clock_setting | phase, |
fc0b638a MC |
132 | SDHCI_CLK_DELAY_SETTING); |
133 | ||
134 | dev_dbg(mmc_dev(mmc), "%s: Setting the tuning phase to %d\n", | |
135 | mmc_hostname(mmc), phase); | |
136 | } else { | |
137 | if (--tuning_seq_cnt) | |
138 | goto retry; | |
139 | /* Tuning failed */ | |
140 | dev_dbg(mmc_dev(mmc), "%s: No tuning point found\n", | |
141 | mmc_hostname(mmc)); | |
142 | rc = -EIO; | |
143 | } | |
144 | ||
145 | return rc; | |
146 | } | |
147 | ||
b3b665b0 | 148 | static struct sdhci_ops sdhci_sirf_ops = { |
a1b0b977 WY |
149 | .read_l = sdhci_sirf_readl_le, |
150 | .read_w = sdhci_sirf_readw_le, | |
fc0b638a | 151 | .platform_execute_tuning = sdhci_sirf_execute_tuning, |
1771059c | 152 | .set_clock = sdhci_set_clock, |
e46af298 | 153 | .get_max_clock = sdhci_pltfm_clk_get_max_clock, |
1ba4c322 | 154 | .set_bus_width = sdhci_sirf_set_bus_width, |
03231f9b | 155 | .reset = sdhci_reset, |
96d7b78c | 156 | .set_uhs_signaling = sdhci_set_uhs_signaling, |
b3b665b0 BS |
157 | }; |
158 | ||
159 | static struct sdhci_pltfm_data sdhci_sirf_pdata = { | |
160 | .ops = &sdhci_sirf_ops, | |
161 | .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | | |
162 | SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | | |
163 | SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | | |
164 | SDHCI_QUIRK_INVERTED_WRITE_PROTECT | | |
165 | SDHCI_QUIRK_DELAY_AFTER_POWER, | |
166 | }; | |
167 | ||
168 | static int sdhci_sirf_probe(struct platform_device *pdev) | |
169 | { | |
170 | struct sdhci_host *host; | |
171 | struct sdhci_pltfm_host *pltfm_host; | |
172 | struct sdhci_sirf_priv *priv; | |
e2f6aac6 AB |
173 | struct clk *clk; |
174 | int gpio_cd; | |
b3b665b0 | 175 | int ret; |
b3b665b0 | 176 | |
e2f6aac6 AB |
177 | clk = devm_clk_get(&pdev->dev, NULL); |
178 | if (IS_ERR(clk)) { | |
b3b665b0 | 179 | dev_err(&pdev->dev, "unable to get clock"); |
e2f6aac6 | 180 | return PTR_ERR(clk); |
b3b665b0 BS |
181 | } |
182 | ||
e2f6aac6 AB |
183 | if (pdev->dev.of_node) |
184 | gpio_cd = of_get_named_gpio(pdev->dev.of_node, "cd-gpios", 0); | |
185 | else | |
186 | gpio_cd = -EINVAL; | |
b3b665b0 | 187 | |
e2f6aac6 AB |
188 | host = sdhci_pltfm_init(pdev, &sdhci_sirf_pdata, sizeof(struct sdhci_sirf_priv)); |
189 | if (IS_ERR(host)) | |
190 | return PTR_ERR(host); | |
b3b665b0 BS |
191 | |
192 | pltfm_host = sdhci_priv(host); | |
e46af298 | 193 | pltfm_host->clk = clk; |
e2f6aac6 | 194 | priv = sdhci_pltfm_priv(pltfm_host); |
e2f6aac6 | 195 | priv->gpio_cd = gpio_cd; |
b3b665b0 BS |
196 | |
197 | sdhci_get_of_property(pdev); | |
198 | ||
e46af298 | 199 | ret = clk_prepare_enable(pltfm_host->clk); |
e2f6aac6 AB |
200 | if (ret) |
201 | goto err_clk_prepare; | |
b3b665b0 BS |
202 | |
203 | ret = sdhci_add_host(host); | |
204 | if (ret) | |
205 | goto err_sdhci_add; | |
206 | ||
207 | /* | |
208 | * We must request the IRQ after sdhci_add_host(), as the tasklet only | |
209 | * gets setup in sdhci_add_host() and we oops. | |
210 | */ | |
211 | if (gpio_is_valid(priv->gpio_cd)) { | |
214fc309 | 212 | ret = mmc_gpio_request_cd(host->mmc, priv->gpio_cd, 0); |
b3b665b0 BS |
213 | if (ret) { |
214 | dev_err(&pdev->dev, "card detect irq request failed: %d\n", | |
215 | ret); | |
216 | goto err_request_cd; | |
217 | } | |
d4d11449 | 218 | mmc_gpiod_request_cd_irq(host->mmc); |
b3b665b0 BS |
219 | } |
220 | ||
221 | return 0; | |
222 | ||
223 | err_request_cd: | |
224 | sdhci_remove_host(host, 0); | |
225 | err_sdhci_add: | |
e46af298 | 226 | clk_disable_unprepare(pltfm_host->clk); |
e2f6aac6 | 227 | err_clk_prepare: |
b3b665b0 | 228 | sdhci_pltfm_free(pdev); |
b3b665b0 BS |
229 | return ret; |
230 | } | |
231 | ||
b3b665b0 BS |
232 | #ifdef CONFIG_PM_SLEEP |
233 | static int sdhci_sirf_suspend(struct device *dev) | |
234 | { | |
235 | struct sdhci_host *host = dev_get_drvdata(dev); | |
236 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | |
b3b665b0 BS |
237 | int ret; |
238 | ||
239 | ret = sdhci_suspend_host(host); | |
240 | if (ret) | |
241 | return ret; | |
242 | ||
e46af298 | 243 | clk_disable(pltfm_host->clk); |
b3b665b0 BS |
244 | |
245 | return 0; | |
246 | } | |
247 | ||
248 | static int sdhci_sirf_resume(struct device *dev) | |
249 | { | |
250 | struct sdhci_host *host = dev_get_drvdata(dev); | |
251 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | |
b3b665b0 BS |
252 | int ret; |
253 | ||
e46af298 | 254 | ret = clk_enable(pltfm_host->clk); |
b3b665b0 BS |
255 | if (ret) { |
256 | dev_dbg(dev, "Resume: Error enabling clock\n"); | |
257 | return ret; | |
258 | } | |
259 | ||
260 | return sdhci_resume_host(host); | |
261 | } | |
262 | ||
263 | static SIMPLE_DEV_PM_OPS(sdhci_sirf_pm_ops, sdhci_sirf_suspend, sdhci_sirf_resume); | |
264 | #endif | |
265 | ||
266 | static const struct of_device_id sdhci_sirf_of_match[] = { | |
267 | { .compatible = "sirf,prima2-sdhc" }, | |
268 | { } | |
269 | }; | |
270 | MODULE_DEVICE_TABLE(of, sdhci_sirf_of_match); | |
271 | ||
272 | static struct platform_driver sdhci_sirf_driver = { | |
273 | .driver = { | |
274 | .name = "sdhci-sirf", | |
b3b665b0 BS |
275 | .of_match_table = sdhci_sirf_of_match, |
276 | #ifdef CONFIG_PM_SLEEP | |
277 | .pm = &sdhci_sirf_pm_ops, | |
278 | #endif | |
279 | }, | |
280 | .probe = sdhci_sirf_probe, | |
caebcae9 | 281 | .remove = sdhci_pltfm_unregister, |
b3b665b0 BS |
282 | }; |
283 | ||
284 | module_platform_driver(sdhci_sirf_driver); | |
285 | ||
286 | MODULE_DESCRIPTION("SDHCI driver for SiRFprimaII/SiRFmarco"); | |
287 | MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); | |
288 | MODULE_LICENSE("GPL v2"); |