diff options
-rw-r--r-- | arch/arm/mach-tegra/include/mach/sdhci.h | 29 | ||||
-rw-r--r-- | drivers/mmc/host/Kconfig | 10 | ||||
-rw-r--r-- | drivers/mmc/host/Makefile | 1 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-pltfm.c | 3 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-pltfm.h | 1 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-tegra.c | 257 |
6 files changed, 301 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/include/mach/sdhci.h b/arch/arm/mach-tegra/include/mach/sdhci.h new file mode 100644 index 000000000000..3ad086e859c3 --- /dev/null +++ b/arch/arm/mach-tegra/include/mach/sdhci.h | |||
@@ -0,0 +1,29 @@ | |||
1 | /* | ||
2 | * include/asm-arm/arch-tegra/include/mach/sdhci.h | ||
3 | * | ||
4 | * Copyright (C) 2009 Palm, Inc. | ||
5 | * Author: Yvonne Yip <y@palm.com> | ||
6 | * | ||
7 | * This software is licensed under the terms of the GNU General Public | ||
8 | * License version 2, as published by the Free Software Foundation, and | ||
9 | * may be copied, distributed, and modified under those terms. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | */ | ||
17 | #ifndef __ASM_ARM_ARCH_TEGRA_SDHCI_H | ||
18 | #define __ASM_ARM_ARCH_TEGRA_SDHCI_H | ||
19 | |||
20 | #include <linux/mmc/host.h> | ||
21 | |||
22 | struct tegra_sdhci_platform_data { | ||
23 | int cd_gpio; | ||
24 | int wp_gpio; | ||
25 | int power_gpio; | ||
26 | int is_8bit; | ||
27 | }; | ||
28 | |||
29 | #endif | ||
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index f8fa9efca8ee..9f47d38dcc7f 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig | |||
@@ -151,6 +151,16 @@ config MMC_SDHCI_DOVE | |||
151 | 151 | ||
152 | If unsure, say N. | 152 | If unsure, say N. |
153 | 153 | ||
154 | config MMC_SDHCI_TEGRA | ||
155 | tristate "SDHCI platform support for the Tegra SD/MMC Controller" | ||
156 | depends on MMC_SDHCI_PLTFM && ARCH_TEGRA | ||
157 | select MMC_SDHCI_IO_ACCESSORS | ||
158 | help | ||
159 | This selects the Tegra SD/MMC controller. If you have a Tegra | ||
160 | platform with SD or MMC devices, say Y or M here. | ||
161 | |||
162 | If unsure, say N. | ||
163 | |||
154 | config MMC_SDHCI_S3C | 164 | config MMC_SDHCI_S3C |
155 | tristate "SDHCI support on Samsung S3C SoC" | 165 | tristate "SDHCI support on Samsung S3C SoC" |
156 | depends on MMC_SDHCI && PLAT_SAMSUNG | 166 | depends on MMC_SDHCI && PLAT_SAMSUNG |
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index d91364d4bf39..6d1ff9e27368 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile | |||
@@ -40,6 +40,7 @@ sdhci-platform-y := sdhci-pltfm.o | |||
40 | sdhci-platform-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o | 40 | sdhci-platform-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o |
41 | sdhci-platform-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o | 41 | sdhci-platform-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o |
42 | sdhci-platform-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o | 42 | sdhci-platform-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o |
43 | sdhci-platform-$(CONFIG_MMC_SDHCI_TEGRA) += sdhci-tegra.o | ||
43 | 44 | ||
44 | obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o | 45 | obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o |
45 | sdhci-of-y := sdhci-of-core.o | 46 | sdhci-of-y := sdhci-of-core.o |
diff --git a/drivers/mmc/host/sdhci-pltfm.c b/drivers/mmc/host/sdhci-pltfm.c index 91c6766c660c..dbab0407f4b6 100644 --- a/drivers/mmc/host/sdhci-pltfm.c +++ b/drivers/mmc/host/sdhci-pltfm.c | |||
@@ -173,6 +173,9 @@ static const struct platform_device_id sdhci_pltfm_ids[] = { | |||
173 | #ifdef CONFIG_MMC_SDHCI_DOVE | 173 | #ifdef CONFIG_MMC_SDHCI_DOVE |
174 | { "sdhci-dove", (kernel_ulong_t)&sdhci_dove_pdata }, | 174 | { "sdhci-dove", (kernel_ulong_t)&sdhci_dove_pdata }, |
175 | #endif | 175 | #endif |
176 | #ifdef CONFIG_MMC_SDHCI_TEGRA | ||
177 | { "sdhci-tegra", (kernel_ulong_t)&sdhci_tegra_pdata }, | ||
178 | #endif | ||
176 | { }, | 179 | { }, |
177 | }; | 180 | }; |
178 | MODULE_DEVICE_TABLE(platform, sdhci_pltfm_ids); | 181 | MODULE_DEVICE_TABLE(platform, sdhci_pltfm_ids); |
diff --git a/drivers/mmc/host/sdhci-pltfm.h b/drivers/mmc/host/sdhci-pltfm.h index 62118b9f9d0a..ea2e44d9be5e 100644 --- a/drivers/mmc/host/sdhci-pltfm.h +++ b/drivers/mmc/host/sdhci-pltfm.h | |||
@@ -23,5 +23,6 @@ struct sdhci_pltfm_host { | |||
23 | extern struct sdhci_pltfm_data sdhci_cns3xxx_pdata; | 23 | extern struct sdhci_pltfm_data sdhci_cns3xxx_pdata; |
24 | extern struct sdhci_pltfm_data sdhci_esdhc_imx_pdata; | 24 | extern struct sdhci_pltfm_data sdhci_esdhc_imx_pdata; |
25 | extern struct sdhci_pltfm_data sdhci_dove_pdata; | 25 | extern struct sdhci_pltfm_data sdhci_dove_pdata; |
26 | extern struct sdhci_pltfm_data sdhci_tegra_pdata; | ||
26 | 27 | ||
27 | #endif /* _DRIVERS_MMC_SDHCI_PLTFM_H */ | 28 | #endif /* _DRIVERS_MMC_SDHCI_PLTFM_H */ |
diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c new file mode 100644 index 000000000000..4823ee94a63f --- /dev/null +++ b/drivers/mmc/host/sdhci-tegra.c | |||
@@ -0,0 +1,257 @@ | |||
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> | ||
16 | #include <linux/init.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/clk.h> | ||
19 | #include <linux/io.h> | ||
20 | #include <linux/gpio.h> | ||
21 | #include <linux/mmc/card.h> | ||
22 | #include <linux/mmc/host.h> | ||
23 | |||
24 | #include <mach/gpio.h> | ||
25 | #include <mach/sdhci.h> | ||
26 | |||
27 | #include "sdhci.h" | ||
28 | #include "sdhci-pltfm.h" | ||
29 | |||
30 | static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) | ||
31 | { | ||
32 | u32 val; | ||
33 | |||
34 | if (unlikely(reg == SDHCI_PRESENT_STATE)) { | ||
35 | /* Use wp_gpio here instead? */ | ||
36 | val = readl(host->ioaddr + reg); | ||
37 | return val | SDHCI_WRITE_PROTECT; | ||
38 | } | ||
39 | |||
40 | return readl(host->ioaddr + reg); | ||
41 | } | ||
42 | |||
43 | static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) | ||
44 | { | ||
45 | if (unlikely(reg == SDHCI_HOST_VERSION)) { | ||
46 | /* Erratum: Version register is invalid in HW. */ | ||
47 | return SDHCI_SPEC_200; | ||
48 | } | ||
49 | |||
50 | return readw(host->ioaddr + reg); | ||
51 | } | ||
52 | |||
53 | static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) | ||
54 | { | ||
55 | /* Seems like we're getting spurious timeout and crc errors, so | ||
56 | * disable signalling of them. In case of real errors software | ||
57 | * timers should take care of eventually detecting them. | ||
58 | */ | ||
59 | if (unlikely(reg == SDHCI_SIGNAL_ENABLE)) | ||
60 | val &= ~(SDHCI_INT_TIMEOUT|SDHCI_INT_CRC); | ||
61 | |||
62 | writel(val, host->ioaddr + reg); | ||
63 | |||
64 | if (unlikely(reg == SDHCI_INT_ENABLE)) { | ||
65 | /* Erratum: Must enable block gap interrupt detection */ | ||
66 | u8 gap_ctrl = readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); | ||
67 | if (val & SDHCI_INT_CARD_INT) | ||
68 | gap_ctrl |= 0x8; | ||
69 | else | ||
70 | gap_ctrl &= ~0x8; | ||
71 | writeb(gap_ctrl, host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); | ||
72 | } | ||
73 | } | ||
74 | |||
75 | static unsigned int tegra_sdhci_get_ro(struct sdhci_host *sdhci) | ||
76 | { | ||
77 | struct platform_device *pdev = to_platform_device(mmc_dev(sdhci->mmc)); | ||
78 | struct tegra_sdhci_platform_data *plat; | ||
79 | |||
80 | plat = pdev->dev.platform_data; | ||
81 | |||
82 | if (!gpio_is_valid(plat->wp_gpio)) | ||
83 | return -1; | ||
84 | |||
85 | return gpio_get_value(plat->wp_gpio); | ||
86 | } | ||
87 | |||
88 | static irqreturn_t carddetect_irq(int irq, void *data) | ||
89 | { | ||
90 | struct sdhci_host *sdhost = (struct sdhci_host *)data; | ||
91 | |||
92 | tasklet_schedule(&sdhost->card_tasklet); | ||
93 | return IRQ_HANDLED; | ||
94 | }; | ||
95 | |||
96 | static int tegra_sdhci_8bit(struct sdhci_host *host, int bus_width) | ||
97 | { | ||
98 | struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc)); | ||
99 | struct tegra_sdhci_platform_data *plat; | ||
100 | u32 ctrl; | ||
101 | |||
102 | plat = pdev->dev.platform_data; | ||
103 | |||
104 | ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); | ||
105 | if (plat->is_8bit && bus_width == MMC_BUS_WIDTH_8) { | ||
106 | ctrl &= ~SDHCI_CTRL_4BITBUS; | ||
107 | ctrl |= SDHCI_CTRL_8BITBUS; | ||
108 | } else { | ||
109 | ctrl &= ~SDHCI_CTRL_8BITBUS; | ||
110 | if (bus_width == MMC_BUS_WIDTH_4) | ||
111 | ctrl |= SDHCI_CTRL_4BITBUS; | ||
112 | else | ||
113 | ctrl &= ~SDHCI_CTRL_4BITBUS; | ||
114 | } | ||
115 | sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); | ||
116 | return 0; | ||
117 | } | ||
118 | |||
119 | |||
120 | static int tegra_sdhci_pltfm_init(struct sdhci_host *host, | ||
121 | struct sdhci_pltfm_data *pdata) | ||
122 | { | ||
123 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | ||
124 | struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc)); | ||
125 | struct tegra_sdhci_platform_data *plat; | ||
126 | struct clk *clk; | ||
127 | int rc; | ||
128 | |||
129 | plat = pdev->dev.platform_data; | ||
130 | if (plat == NULL) { | ||
131 | dev_err(mmc_dev(host->mmc), "missing platform data\n"); | ||
132 | return -ENXIO; | ||
133 | } | ||
134 | |||
135 | if (gpio_is_valid(plat->power_gpio)) { | ||
136 | rc = gpio_request(plat->power_gpio, "sdhci_power"); | ||
137 | if (rc) { | ||
138 | dev_err(mmc_dev(host->mmc), | ||
139 | "failed to allocate power gpio\n"); | ||
140 | goto out; | ||
141 | } | ||
142 | tegra_gpio_enable(plat->power_gpio); | ||
143 | gpio_direction_output(plat->power_gpio, 1); | ||
144 | } | ||
145 | |||
146 | if (gpio_is_valid(plat->cd_gpio)) { | ||
147 | rc = gpio_request(plat->cd_gpio, "sdhci_cd"); | ||
148 | if (rc) { | ||
149 | dev_err(mmc_dev(host->mmc), | ||
150 | "failed to allocate cd gpio\n"); | ||
151 | goto out_power; | ||
152 | } | ||
153 | tegra_gpio_enable(plat->cd_gpio); | ||
154 | gpio_direction_input(plat->cd_gpio); | ||
155 | |||
156 | rc = request_irq(gpio_to_irq(plat->cd_gpio), carddetect_irq, | ||
157 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | ||
158 | mmc_hostname(host->mmc), host); | ||
159 | |||
160 | if (rc) { | ||
161 | dev_err(mmc_dev(host->mmc), "request irq error\n"); | ||
162 | goto out_cd; | ||
163 | } | ||
164 | |||
165 | } | ||
166 | |||
167 | if (gpio_is_valid(plat->wp_gpio)) { | ||
168 | rc = gpio_request(plat->wp_gpio, "sdhci_wp"); | ||
169 | if (rc) { | ||
170 | dev_err(mmc_dev(host->mmc), | ||
171 | "failed to allocate wp gpio\n"); | ||
172 | goto out_cd; | ||
173 | } | ||
174 | tegra_gpio_enable(plat->wp_gpio); | ||
175 | gpio_direction_input(plat->wp_gpio); | ||
176 | } | ||
177 | |||
178 | clk = clk_get(mmc_dev(host->mmc), NULL); | ||
179 | if (IS_ERR(clk)) { | ||
180 | dev_err(mmc_dev(host->mmc), "clk err\n"); | ||
181 | rc = PTR_ERR(clk); | ||
182 | goto out_wp; | ||
183 | } | ||
184 | clk_enable(clk); | ||
185 | pltfm_host->clk = clk; | ||
186 | |||
187 | if (plat->is_8bit) | ||
188 | host->mmc->caps |= MMC_CAP_8_BIT_DATA; | ||
189 | |||
190 | return 0; | ||
191 | |||
192 | out_wp: | ||
193 | if (gpio_is_valid(plat->wp_gpio)) { | ||
194 | tegra_gpio_disable(plat->wp_gpio); | ||
195 | gpio_free(plat->wp_gpio); | ||
196 | } | ||
197 | |||
198 | out_cd: | ||
199 | if (gpio_is_valid(plat->cd_gpio)) { | ||
200 | tegra_gpio_disable(plat->cd_gpio); | ||
201 | gpio_free(plat->cd_gpio); | ||
202 | } | ||
203 | |||
204 | out_power: | ||
205 | if (gpio_is_valid(plat->power_gpio)) { | ||
206 | tegra_gpio_disable(plat->power_gpio); | ||
207 | gpio_free(plat->power_gpio); | ||
208 | } | ||
209 | |||
210 | out: | ||
211 | return rc; | ||
212 | } | ||
213 | |||
214 | static void tegra_sdhci_pltfm_exit(struct sdhci_host *host) | ||
215 | { | ||
216 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | ||
217 | struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc)); | ||
218 | struct tegra_sdhci_platform_data *plat; | ||
219 | |||
220 | plat = pdev->dev.platform_data; | ||
221 | |||
222 | if (gpio_is_valid(plat->wp_gpio)) { | ||
223 | tegra_gpio_disable(plat->wp_gpio); | ||
224 | gpio_free(plat->wp_gpio); | ||
225 | } | ||
226 | |||
227 | if (gpio_is_valid(plat->cd_gpio)) { | ||
228 | tegra_gpio_disable(plat->cd_gpio); | ||
229 | gpio_free(plat->cd_gpio); | ||
230 | } | ||
231 | |||
232 | if (gpio_is_valid(plat->power_gpio)) { | ||
233 | tegra_gpio_disable(plat->power_gpio); | ||
234 | gpio_free(plat->power_gpio); | ||
235 | } | ||
236 | |||
237 | clk_disable(pltfm_host->clk); | ||
238 | clk_put(pltfm_host->clk); | ||
239 | } | ||
240 | |||
241 | static struct sdhci_ops tegra_sdhci_ops = { | ||
242 | .get_ro = tegra_sdhci_get_ro, | ||
243 | .read_l = tegra_sdhci_readl, | ||
244 | .read_w = tegra_sdhci_readw, | ||
245 | .write_l = tegra_sdhci_writel, | ||
246 | .platform_8bit_width = tegra_sdhci_8bit, | ||
247 | }; | ||
248 | |||
249 | struct sdhci_pltfm_data sdhci_tegra_pdata = { | ||
250 | .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | | ||
251 | SDHCI_QUIRK_SINGLE_POWER_WRITE | | ||
252 | SDHCI_QUIRK_NO_HISPD_BIT | | ||
253 | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, | ||
254 | .ops = &tegra_sdhci_ops, | ||
255 | .init = tegra_sdhci_pltfm_init, | ||
256 | .exit = tegra_sdhci_pltfm_exit, | ||
257 | }; | ||