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