Commit | Line | Data |
---|---|---|
03d2bfc8 OJ |
1 | /* |
2 | * Copyright (C) 2010 Google, Inc. | |
3 | * | |
4 | * This software is licensed under the terms of the GNU General Public | |
5 | * License version 2, as published by the Free Software Foundation, and | |
6 | * may be copied, distributed, and modified under those terms. | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, | |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | * GNU General Public License for more details. | |
12 | * | |
13 | */ | |
14 | ||
15 | #include <linux/err.h> | |
96547f5d | 16 | #include <linux/module.h> |
03d2bfc8 OJ |
17 | #include <linux/init.h> |
18 | #include <linux/platform_device.h> | |
19 | #include <linux/clk.h> | |
20 | #include <linux/io.h> | |
55cd65e4 | 21 | #include <linux/of.h> |
3e44a1a7 | 22 | #include <linux/of_device.h> |
275173b2 | 23 | #include <linux/of_gpio.h> |
03d2bfc8 OJ |
24 | #include <linux/gpio.h> |
25 | #include <linux/mmc/card.h> | |
26 | #include <linux/mmc/host.h> | |
27 | ||
e6b750d4 | 28 | #include <asm/gpio.h> |
ea5abbd2 | 29 | |
e35742b0 | 30 | #include <linux/platform_data/mmc-sdhci-tegra.h> |
03d2bfc8 | 31 | |
03d2bfc8 OJ |
32 | #include "sdhci-pltfm.h" |
33 | ||
ca5879d3 PK |
34 | /* Tegra SDHOST controller vendor register definitions */ |
35 | #define SDHCI_TEGRA_VENDOR_MISC_CTRL 0x120 | |
36 | #define SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300 0x20 | |
37 | ||
3e44a1a7 SW |
38 | #define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0) |
39 | #define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1) | |
ca5879d3 | 40 | #define NVQUIRK_ENABLE_SDHCI_SPEC_300 BIT(2) |
3e44a1a7 SW |
41 | |
42 | struct sdhci_tegra_soc_data { | |
43 | struct sdhci_pltfm_data *pdata; | |
44 | u32 nvquirks; | |
45 | }; | |
46 | ||
47 | struct sdhci_tegra { | |
48 | const struct tegra_sdhci_platform_data *plat; | |
49 | const struct sdhci_tegra_soc_data *soc_data; | |
50 | }; | |
51 | ||
03d2bfc8 OJ |
52 | static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) |
53 | { | |
54 | u32 val; | |
55 | ||
56 | if (unlikely(reg == SDHCI_PRESENT_STATE)) { | |
57 | /* Use wp_gpio here instead? */ | |
58 | val = readl(host->ioaddr + reg); | |
59 | return val | SDHCI_WRITE_PROTECT; | |
60 | } | |
61 | ||
62 | return readl(host->ioaddr + reg); | |
63 | } | |
64 | ||
65 | static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) | |
66 | { | |
3e44a1a7 SW |
67 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
68 | struct sdhci_tegra *tegra_host = pltfm_host->priv; | |
69 | const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; | |
70 | ||
71 | if (unlikely((soc_data->nvquirks & NVQUIRK_FORCE_SDHCI_SPEC_200) && | |
72 | (reg == SDHCI_HOST_VERSION))) { | |
03d2bfc8 OJ |
73 | /* Erratum: Version register is invalid in HW. */ |
74 | return SDHCI_SPEC_200; | |
75 | } | |
76 | ||
77 | return readw(host->ioaddr + reg); | |
78 | } | |
79 | ||
80 | static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) | |
81 | { | |
3e44a1a7 SW |
82 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
83 | struct sdhci_tegra *tegra_host = pltfm_host->priv; | |
84 | const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; | |
85 | ||
03d2bfc8 OJ |
86 | /* Seems like we're getting spurious timeout and crc errors, so |
87 | * disable signalling of them. In case of real errors software | |
88 | * timers should take care of eventually detecting them. | |
89 | */ | |
90 | if (unlikely(reg == SDHCI_SIGNAL_ENABLE)) | |
91 | val &= ~(SDHCI_INT_TIMEOUT|SDHCI_INT_CRC); | |
92 | ||
93 | writel(val, host->ioaddr + reg); | |
94 | ||
3e44a1a7 SW |
95 | if (unlikely((soc_data->nvquirks & NVQUIRK_ENABLE_BLOCK_GAP_DET) && |
96 | (reg == SDHCI_INT_ENABLE))) { | |
03d2bfc8 OJ |
97 | /* Erratum: Must enable block gap interrupt detection */ |
98 | u8 gap_ctrl = readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); | |
99 | if (val & SDHCI_INT_CARD_INT) | |
100 | gap_ctrl |= 0x8; | |
101 | else | |
102 | gap_ctrl &= ~0x8; | |
103 | writeb(gap_ctrl, host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); | |
104 | } | |
105 | } | |
106 | ||
3e44a1a7 | 107 | static unsigned int tegra_sdhci_get_ro(struct sdhci_host *host) |
03d2bfc8 | 108 | { |
3e44a1a7 SW |
109 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
110 | struct sdhci_tegra *tegra_host = pltfm_host->priv; | |
111 | const struct tegra_sdhci_platform_data *plat = tegra_host->plat; | |
03d2bfc8 OJ |
112 | |
113 | if (!gpio_is_valid(plat->wp_gpio)) | |
114 | return -1; | |
115 | ||
116 | return gpio_get_value(plat->wp_gpio); | |
117 | } | |
118 | ||
119 | static irqreturn_t carddetect_irq(int irq, void *data) | |
120 | { | |
121 | struct sdhci_host *sdhost = (struct sdhci_host *)data; | |
122 | ||
123 | tasklet_schedule(&sdhost->card_tasklet); | |
124 | return IRQ_HANDLED; | |
125 | }; | |
126 | ||
ca5879d3 PK |
127 | static void tegra_sdhci_reset_exit(struct sdhci_host *host, u8 mask) |
128 | { | |
129 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | |
130 | struct sdhci_tegra *tegra_host = pltfm_host->priv; | |
131 | const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; | |
132 | ||
133 | if (!(mask & SDHCI_RESET_ALL)) | |
134 | return; | |
135 | ||
136 | /* Erratum: Enable SDHCI spec v3.00 support */ | |
137 | if (soc_data->nvquirks & NVQUIRK_ENABLE_SDHCI_SPEC_300) { | |
138 | u32 misc_ctrl; | |
139 | ||
140 | misc_ctrl = sdhci_readb(host, SDHCI_TEGRA_VENDOR_MISC_CTRL); | |
141 | misc_ctrl |= SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300; | |
142 | sdhci_writeb(host, misc_ctrl, SDHCI_TEGRA_VENDOR_MISC_CTRL); | |
143 | } | |
144 | } | |
145 | ||
03d2bfc8 OJ |
146 | static int tegra_sdhci_8bit(struct sdhci_host *host, int bus_width) |
147 | { | |
275173b2 | 148 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
3e44a1a7 SW |
149 | struct sdhci_tegra *tegra_host = pltfm_host->priv; |
150 | const struct tegra_sdhci_platform_data *plat = tegra_host->plat; | |
03d2bfc8 OJ |
151 | u32 ctrl; |
152 | ||
03d2bfc8 OJ |
153 | ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); |
154 | if (plat->is_8bit && bus_width == MMC_BUS_WIDTH_8) { | |
155 | ctrl &= ~SDHCI_CTRL_4BITBUS; | |
156 | ctrl |= SDHCI_CTRL_8BITBUS; | |
157 | } else { | |
158 | ctrl &= ~SDHCI_CTRL_8BITBUS; | |
159 | if (bus_width == MMC_BUS_WIDTH_4) | |
160 | ctrl |= SDHCI_CTRL_4BITBUS; | |
161 | else | |
162 | ctrl &= ~SDHCI_CTRL_4BITBUS; | |
163 | } | |
164 | sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); | |
165 | return 0; | |
166 | } | |
167 | ||
85d6509d SG |
168 | static struct sdhci_ops tegra_sdhci_ops = { |
169 | .get_ro = tegra_sdhci_get_ro, | |
170 | .read_l = tegra_sdhci_readl, | |
171 | .read_w = tegra_sdhci_readw, | |
172 | .write_l = tegra_sdhci_writel, | |
173 | .platform_8bit_width = tegra_sdhci_8bit, | |
ca5879d3 | 174 | .platform_reset_exit = tegra_sdhci_reset_exit, |
85d6509d SG |
175 | }; |
176 | ||
3e44a1a7 SW |
177 | #ifdef CONFIG_ARCH_TEGRA_2x_SOC |
178 | static struct sdhci_pltfm_data sdhci_tegra20_pdata = { | |
179 | .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | | |
180 | SDHCI_QUIRK_SINGLE_POWER_WRITE | | |
181 | SDHCI_QUIRK_NO_HISPD_BIT | | |
182 | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, | |
183 | .ops = &tegra_sdhci_ops, | |
184 | }; | |
185 | ||
186 | static struct sdhci_tegra_soc_data soc_data_tegra20 = { | |
187 | .pdata = &sdhci_tegra20_pdata, | |
188 | .nvquirks = NVQUIRK_FORCE_SDHCI_SPEC_200 | | |
189 | NVQUIRK_ENABLE_BLOCK_GAP_DET, | |
190 | }; | |
191 | #endif | |
192 | ||
193 | #ifdef CONFIG_ARCH_TEGRA_3x_SOC | |
194 | static struct sdhci_pltfm_data sdhci_tegra30_pdata = { | |
85d6509d | 195 | .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | |
3e44a1a7 | 196 | SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | |
85d6509d SG |
197 | SDHCI_QUIRK_SINGLE_POWER_WRITE | |
198 | SDHCI_QUIRK_NO_HISPD_BIT | | |
199 | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, | |
200 | .ops = &tegra_sdhci_ops, | |
201 | }; | |
03d2bfc8 | 202 | |
3e44a1a7 SW |
203 | static struct sdhci_tegra_soc_data soc_data_tegra30 = { |
204 | .pdata = &sdhci_tegra30_pdata, | |
ca5879d3 | 205 | .nvquirks = NVQUIRK_ENABLE_SDHCI_SPEC_300, |
3e44a1a7 SW |
206 | }; |
207 | #endif | |
208 | ||
498d83e7 | 209 | static const struct of_device_id sdhci_tegra_dt_match[] = { |
3e44a1a7 SW |
210 | #ifdef CONFIG_ARCH_TEGRA_3x_SOC |
211 | { .compatible = "nvidia,tegra30-sdhci", .data = &soc_data_tegra30 }, | |
212 | #endif | |
213 | #ifdef CONFIG_ARCH_TEGRA_2x_SOC | |
214 | { .compatible = "nvidia,tegra20-sdhci", .data = &soc_data_tegra20 }, | |
215 | #endif | |
275173b2 GL |
216 | {} |
217 | }; | |
218 | MODULE_DEVICE_TABLE(of, sdhci_dt_ids); | |
219 | ||
c3be1efd | 220 | static struct tegra_sdhci_platform_data *sdhci_tegra_dt_parse_pdata( |
275173b2 GL |
221 | struct platform_device *pdev) |
222 | { | |
223 | struct tegra_sdhci_platform_data *plat; | |
224 | struct device_node *np = pdev->dev.of_node; | |
c11bd557 | 225 | u32 bus_width; |
275173b2 GL |
226 | |
227 | if (!np) | |
228 | return NULL; | |
229 | ||
230 | plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL); | |
231 | if (!plat) { | |
232 | dev_err(&pdev->dev, "Can't allocate platform data\n"); | |
233 | return NULL; | |
234 | } | |
235 | ||
236 | plat->cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); | |
237 | plat->wp_gpio = of_get_named_gpio(np, "wp-gpios", 0); | |
238 | plat->power_gpio = of_get_named_gpio(np, "power-gpios", 0); | |
c11bd557 SW |
239 | |
240 | if (of_property_read_u32(np, "bus-width", &bus_width) == 0 && | |
241 | bus_width == 8) | |
55cd65e4 | 242 | plat->is_8bit = 1; |
275173b2 GL |
243 | |
244 | return plat; | |
245 | } | |
246 | ||
c3be1efd | 247 | static int sdhci_tegra_probe(struct platform_device *pdev) |
03d2bfc8 | 248 | { |
3e44a1a7 SW |
249 | const struct of_device_id *match; |
250 | const struct sdhci_tegra_soc_data *soc_data; | |
251 | struct sdhci_host *host; | |
85d6509d | 252 | struct sdhci_pltfm_host *pltfm_host; |
03d2bfc8 | 253 | struct tegra_sdhci_platform_data *plat; |
3e44a1a7 | 254 | struct sdhci_tegra *tegra_host; |
03d2bfc8 OJ |
255 | struct clk *clk; |
256 | int rc; | |
257 | ||
3e44a1a7 | 258 | match = of_match_device(sdhci_tegra_dt_match, &pdev->dev); |
b37f9d98 JL |
259 | if (!match) |
260 | return -EINVAL; | |
261 | soc_data = match->data; | |
3e44a1a7 SW |
262 | |
263 | host = sdhci_pltfm_init(pdev, soc_data->pdata); | |
85d6509d SG |
264 | if (IS_ERR(host)) |
265 | return PTR_ERR(host); | |
266 | ||
267 | pltfm_host = sdhci_priv(host); | |
268 | ||
03d2bfc8 | 269 | plat = pdev->dev.platform_data; |
85d6509d | 270 | |
275173b2 GL |
271 | if (plat == NULL) |
272 | plat = sdhci_tegra_dt_parse_pdata(pdev); | |
273 | ||
03d2bfc8 OJ |
274 | if (plat == NULL) { |
275 | dev_err(mmc_dev(host->mmc), "missing platform data\n"); | |
85d6509d SG |
276 | rc = -ENXIO; |
277 | goto err_no_plat; | |
03d2bfc8 OJ |
278 | } |
279 | ||
3e44a1a7 SW |
280 | tegra_host = devm_kzalloc(&pdev->dev, sizeof(*tegra_host), GFP_KERNEL); |
281 | if (!tegra_host) { | |
282 | dev_err(mmc_dev(host->mmc), "failed to allocate tegra_host\n"); | |
283 | rc = -ENOMEM; | |
284 | goto err_no_plat; | |
285 | } | |
286 | ||
287 | tegra_host->plat = plat; | |
288 | tegra_host->soc_data = soc_data; | |
289 | ||
290 | pltfm_host->priv = tegra_host; | |
275173b2 | 291 | |
03d2bfc8 OJ |
292 | if (gpio_is_valid(plat->power_gpio)) { |
293 | rc = gpio_request(plat->power_gpio, "sdhci_power"); | |
294 | if (rc) { | |
295 | dev_err(mmc_dev(host->mmc), | |
296 | "failed to allocate power gpio\n"); | |
85d6509d | 297 | goto err_power_req; |
03d2bfc8 | 298 | } |
03d2bfc8 OJ |
299 | gpio_direction_output(plat->power_gpio, 1); |
300 | } | |
301 | ||
302 | if (gpio_is_valid(plat->cd_gpio)) { | |
303 | rc = gpio_request(plat->cd_gpio, "sdhci_cd"); | |
304 | if (rc) { | |
305 | dev_err(mmc_dev(host->mmc), | |
306 | "failed to allocate cd gpio\n"); | |
85d6509d | 307 | goto err_cd_req; |
03d2bfc8 | 308 | } |
03d2bfc8 OJ |
309 | gpio_direction_input(plat->cd_gpio); |
310 | ||
311 | rc = request_irq(gpio_to_irq(plat->cd_gpio), carddetect_irq, | |
312 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | |
313 | mmc_hostname(host->mmc), host); | |
314 | ||
315 | if (rc) { | |
316 | dev_err(mmc_dev(host->mmc), "request irq error\n"); | |
85d6509d | 317 | goto err_cd_irq_req; |
03d2bfc8 OJ |
318 | } |
319 | ||
320 | } | |
321 | ||
322 | if (gpio_is_valid(plat->wp_gpio)) { | |
323 | rc = gpio_request(plat->wp_gpio, "sdhci_wp"); | |
324 | if (rc) { | |
325 | dev_err(mmc_dev(host->mmc), | |
326 | "failed to allocate wp gpio\n"); | |
85d6509d | 327 | goto err_wp_req; |
03d2bfc8 | 328 | } |
03d2bfc8 OJ |
329 | gpio_direction_input(plat->wp_gpio); |
330 | } | |
331 | ||
332 | clk = clk_get(mmc_dev(host->mmc), NULL); | |
333 | if (IS_ERR(clk)) { | |
334 | dev_err(mmc_dev(host->mmc), "clk err\n"); | |
335 | rc = PTR_ERR(clk); | |
85d6509d | 336 | goto err_clk_get; |
03d2bfc8 | 337 | } |
1e674bc6 | 338 | clk_prepare_enable(clk); |
03d2bfc8 OJ |
339 | pltfm_host->clk = clk; |
340 | ||
c7f409e3 VR |
341 | host->mmc->pm_caps = plat->pm_flags; |
342 | ||
03d2bfc8 OJ |
343 | if (plat->is_8bit) |
344 | host->mmc->caps |= MMC_CAP_8_BIT_DATA; | |
345 | ||
85d6509d SG |
346 | rc = sdhci_add_host(host); |
347 | if (rc) | |
348 | goto err_add_host; | |
349 | ||
03d2bfc8 OJ |
350 | return 0; |
351 | ||
85d6509d | 352 | err_add_host: |
1e674bc6 | 353 | clk_disable_unprepare(pltfm_host->clk); |
85d6509d SG |
354 | clk_put(pltfm_host->clk); |
355 | err_clk_get: | |
3e215d0a | 356 | if (gpio_is_valid(plat->wp_gpio)) |
03d2bfc8 | 357 | gpio_free(plat->wp_gpio); |
85d6509d | 358 | err_wp_req: |
8154b575 WS |
359 | if (gpio_is_valid(plat->cd_gpio)) |
360 | free_irq(gpio_to_irq(plat->cd_gpio), host); | |
85d6509d | 361 | err_cd_irq_req: |
3e215d0a | 362 | if (gpio_is_valid(plat->cd_gpio)) |
03d2bfc8 | 363 | gpio_free(plat->cd_gpio); |
85d6509d | 364 | err_cd_req: |
3e215d0a | 365 | if (gpio_is_valid(plat->power_gpio)) |
03d2bfc8 | 366 | gpio_free(plat->power_gpio); |
85d6509d SG |
367 | err_power_req: |
368 | err_no_plat: | |
369 | sdhci_pltfm_free(pdev); | |
03d2bfc8 OJ |
370 | return rc; |
371 | } | |
372 | ||
6e0ee714 | 373 | static int sdhci_tegra_remove(struct platform_device *pdev) |
03d2bfc8 | 374 | { |
85d6509d | 375 | struct sdhci_host *host = platform_get_drvdata(pdev); |
03d2bfc8 | 376 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
3e44a1a7 SW |
377 | struct sdhci_tegra *tegra_host = pltfm_host->priv; |
378 | const struct tegra_sdhci_platform_data *plat = tegra_host->plat; | |
85d6509d SG |
379 | int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); |
380 | ||
381 | sdhci_remove_host(host, dead); | |
03d2bfc8 | 382 | |
3e215d0a | 383 | if (gpio_is_valid(plat->wp_gpio)) |
03d2bfc8 | 384 | gpio_free(plat->wp_gpio); |
03d2bfc8 OJ |
385 | |
386 | if (gpio_is_valid(plat->cd_gpio)) { | |
8154b575 | 387 | free_irq(gpio_to_irq(plat->cd_gpio), host); |
03d2bfc8 OJ |
388 | gpio_free(plat->cd_gpio); |
389 | } | |
390 | ||
3e215d0a | 391 | if (gpio_is_valid(plat->power_gpio)) |
03d2bfc8 | 392 | gpio_free(plat->power_gpio); |
03d2bfc8 | 393 | |
1e674bc6 | 394 | clk_disable_unprepare(pltfm_host->clk); |
03d2bfc8 | 395 | clk_put(pltfm_host->clk); |
85d6509d SG |
396 | |
397 | sdhci_pltfm_free(pdev); | |
398 | ||
399 | return 0; | |
03d2bfc8 OJ |
400 | } |
401 | ||
85d6509d SG |
402 | static struct platform_driver sdhci_tegra_driver = { |
403 | .driver = { | |
404 | .name = "sdhci-tegra", | |
405 | .owner = THIS_MODULE, | |
275173b2 | 406 | .of_match_table = sdhci_tegra_dt_match, |
29495aa0 | 407 | .pm = SDHCI_PLTFM_PMOPS, |
85d6509d SG |
408 | }, |
409 | .probe = sdhci_tegra_probe, | |
0433c143 | 410 | .remove = sdhci_tegra_remove, |
03d2bfc8 OJ |
411 | }; |
412 | ||
d1f81a64 | 413 | module_platform_driver(sdhci_tegra_driver); |
85d6509d SG |
414 | |
415 | MODULE_DESCRIPTION("SDHCI driver for Tegra"); | |
3e44a1a7 | 416 | MODULE_AUTHOR("Google, Inc."); |
85d6509d | 417 | MODULE_LICENSE("GPL v2"); |