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