diff options
Diffstat (limited to 'drivers/mmc/host/sdhci-tegra.c')
-rw-r--r-- | drivers/mmc/host/sdhci-tegra.c | 100 |
1 files changed, 87 insertions, 13 deletions
diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c index cb348569454b..53b26502f6e2 100644 --- a/drivers/mmc/host/sdhci-tegra.c +++ b/drivers/mmc/host/sdhci-tegra.c | |||
@@ -19,6 +19,7 @@ | |||
19 | #include <linux/clk.h> | 19 | #include <linux/clk.h> |
20 | #include <linux/io.h> | 20 | #include <linux/io.h> |
21 | #include <linux/of.h> | 21 | #include <linux/of.h> |
22 | #include <linux/of_device.h> | ||
22 | #include <linux/of_gpio.h> | 23 | #include <linux/of_gpio.h> |
23 | #include <linux/gpio.h> | 24 | #include <linux/gpio.h> |
24 | #include <linux/mmc/card.h> | 25 | #include <linux/mmc/card.h> |
@@ -31,6 +32,19 @@ | |||
31 | 32 | ||
32 | #include "sdhci-pltfm.h" | 33 | #include "sdhci-pltfm.h" |
33 | 34 | ||
35 | #define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0) | ||
36 | #define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1) | ||
37 | |||
38 | struct sdhci_tegra_soc_data { | ||
39 | struct sdhci_pltfm_data *pdata; | ||
40 | u32 nvquirks; | ||
41 | }; | ||
42 | |||
43 | struct sdhci_tegra { | ||
44 | const struct tegra_sdhci_platform_data *plat; | ||
45 | const struct sdhci_tegra_soc_data *soc_data; | ||
46 | }; | ||
47 | |||
34 | static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) | 48 | static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) |
35 | { | 49 | { |
36 | u32 val; | 50 | u32 val; |
@@ -46,7 +60,12 @@ static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) | |||
46 | 60 | ||
47 | static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) | 61 | static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) |
48 | { | 62 | { |
49 | if (unlikely(reg == SDHCI_HOST_VERSION)) { | 63 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
64 | struct sdhci_tegra *tegra_host = pltfm_host->priv; | ||
65 | const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; | ||
66 | |||
67 | if (unlikely((soc_data->nvquirks & NVQUIRK_FORCE_SDHCI_SPEC_200) && | ||
68 | (reg == SDHCI_HOST_VERSION))) { | ||
50 | /* Erratum: Version register is invalid in HW. */ | 69 | /* Erratum: Version register is invalid in HW. */ |
51 | return SDHCI_SPEC_200; | 70 | return SDHCI_SPEC_200; |
52 | } | 71 | } |
@@ -56,6 +75,10 @@ static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) | |||
56 | 75 | ||
57 | static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) | 76 | static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) |
58 | { | 77 | { |
78 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | ||
79 | struct sdhci_tegra *tegra_host = pltfm_host->priv; | ||
80 | const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; | ||
81 | |||
59 | /* Seems like we're getting spurious timeout and crc errors, so | 82 | /* Seems like we're getting spurious timeout and crc errors, so |
60 | * disable signalling of them. In case of real errors software | 83 | * disable signalling of them. In case of real errors software |
61 | * timers should take care of eventually detecting them. | 84 | * timers should take care of eventually detecting them. |
@@ -65,7 +88,8 @@ static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) | |||
65 | 88 | ||
66 | writel(val, host->ioaddr + reg); | 89 | writel(val, host->ioaddr + reg); |
67 | 90 | ||
68 | if (unlikely(reg == SDHCI_INT_ENABLE)) { | 91 | if (unlikely((soc_data->nvquirks & NVQUIRK_ENABLE_BLOCK_GAP_DET) && |
92 | (reg == SDHCI_INT_ENABLE))) { | ||
69 | /* Erratum: Must enable block gap interrupt detection */ | 93 | /* Erratum: Must enable block gap interrupt detection */ |
70 | u8 gap_ctrl = readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); | 94 | u8 gap_ctrl = readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); |
71 | if (val & SDHCI_INT_CARD_INT) | 95 | if (val & SDHCI_INT_CARD_INT) |
@@ -76,10 +100,11 @@ static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) | |||
76 | } | 100 | } |
77 | } | 101 | } |
78 | 102 | ||
79 | static unsigned int tegra_sdhci_get_ro(struct sdhci_host *sdhci) | 103 | static unsigned int tegra_sdhci_get_ro(struct sdhci_host *host) |
80 | { | 104 | { |
81 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); | 105 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
82 | struct tegra_sdhci_platform_data *plat = pltfm_host->priv; | 106 | struct sdhci_tegra *tegra_host = pltfm_host->priv; |
107 | const struct tegra_sdhci_platform_data *plat = tegra_host->plat; | ||
83 | 108 | ||
84 | if (!gpio_is_valid(plat->wp_gpio)) | 109 | if (!gpio_is_valid(plat->wp_gpio)) |
85 | return -1; | 110 | return -1; |
@@ -98,7 +123,8 @@ static irqreturn_t carddetect_irq(int irq, void *data) | |||
98 | static int tegra_sdhci_8bit(struct sdhci_host *host, int bus_width) | 123 | static int tegra_sdhci_8bit(struct sdhci_host *host, int bus_width) |
99 | { | 124 | { |
100 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | 125 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
101 | struct tegra_sdhci_platform_data *plat = pltfm_host->priv; | 126 | struct sdhci_tegra *tegra_host = pltfm_host->priv; |
127 | const struct tegra_sdhci_platform_data *plat = tegra_host->plat; | ||
102 | u32 ctrl; | 128 | u32 ctrl; |
103 | 129 | ||
104 | ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); | 130 | ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); |
@@ -124,16 +150,44 @@ static struct sdhci_ops tegra_sdhci_ops = { | |||
124 | .platform_8bit_width = tegra_sdhci_8bit, | 150 | .platform_8bit_width = tegra_sdhci_8bit, |
125 | }; | 151 | }; |
126 | 152 | ||
127 | static struct sdhci_pltfm_data sdhci_tegra_pdata = { | 153 | #ifdef CONFIG_ARCH_TEGRA_2x_SOC |
154 | static struct sdhci_pltfm_data sdhci_tegra20_pdata = { | ||
155 | .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | | ||
156 | SDHCI_QUIRK_SINGLE_POWER_WRITE | | ||
157 | SDHCI_QUIRK_NO_HISPD_BIT | | ||
158 | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, | ||
159 | .ops = &tegra_sdhci_ops, | ||
160 | }; | ||
161 | |||
162 | static struct sdhci_tegra_soc_data soc_data_tegra20 = { | ||
163 | .pdata = &sdhci_tegra20_pdata, | ||
164 | .nvquirks = NVQUIRK_FORCE_SDHCI_SPEC_200 | | ||
165 | NVQUIRK_ENABLE_BLOCK_GAP_DET, | ||
166 | }; | ||
167 | #endif | ||
168 | |||
169 | #ifdef CONFIG_ARCH_TEGRA_3x_SOC | ||
170 | static struct sdhci_pltfm_data sdhci_tegra30_pdata = { | ||
128 | .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | | 171 | .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | |
172 | SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | | ||
129 | SDHCI_QUIRK_SINGLE_POWER_WRITE | | 173 | SDHCI_QUIRK_SINGLE_POWER_WRITE | |
130 | SDHCI_QUIRK_NO_HISPD_BIT | | 174 | SDHCI_QUIRK_NO_HISPD_BIT | |
131 | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, | 175 | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, |
132 | .ops = &tegra_sdhci_ops, | 176 | .ops = &tegra_sdhci_ops, |
133 | }; | 177 | }; |
134 | 178 | ||
179 | static struct sdhci_tegra_soc_data soc_data_tegra30 = { | ||
180 | .pdata = &sdhci_tegra30_pdata, | ||
181 | }; | ||
182 | #endif | ||
183 | |||
135 | static const struct of_device_id sdhci_tegra_dt_match[] __devinitdata = { | 184 | static const struct of_device_id sdhci_tegra_dt_match[] __devinitdata = { |
136 | { .compatible = "nvidia,tegra20-sdhci", }, | 185 | #ifdef CONFIG_ARCH_TEGRA_3x_SOC |
186 | { .compatible = "nvidia,tegra30-sdhci", .data = &soc_data_tegra30 }, | ||
187 | #endif | ||
188 | #ifdef CONFIG_ARCH_TEGRA_2x_SOC | ||
189 | { .compatible = "nvidia,tegra20-sdhci", .data = &soc_data_tegra20 }, | ||
190 | #endif | ||
137 | {} | 191 | {} |
138 | }; | 192 | }; |
139 | MODULE_DEVICE_TABLE(of, sdhci_dt_ids); | 193 | MODULE_DEVICE_TABLE(of, sdhci_dt_ids); |
@@ -164,13 +218,22 @@ static struct tegra_sdhci_platform_data * __devinit sdhci_tegra_dt_parse_pdata( | |||
164 | 218 | ||
165 | static int __devinit sdhci_tegra_probe(struct platform_device *pdev) | 219 | static int __devinit sdhci_tegra_probe(struct platform_device *pdev) |
166 | { | 220 | { |
221 | const struct of_device_id *match; | ||
222 | const struct sdhci_tegra_soc_data *soc_data; | ||
223 | struct sdhci_host *host; | ||
167 | struct sdhci_pltfm_host *pltfm_host; | 224 | struct sdhci_pltfm_host *pltfm_host; |
168 | struct tegra_sdhci_platform_data *plat; | 225 | struct tegra_sdhci_platform_data *plat; |
169 | struct sdhci_host *host; | 226 | struct sdhci_tegra *tegra_host; |
170 | struct clk *clk; | 227 | struct clk *clk; |
171 | int rc; | 228 | int rc; |
172 | 229 | ||
173 | host = sdhci_pltfm_init(pdev, &sdhci_tegra_pdata); | 230 | match = of_match_device(sdhci_tegra_dt_match, &pdev->dev); |
231 | if (match) | ||
232 | soc_data = match->data; | ||
233 | else | ||
234 | soc_data = &soc_data_tegra20; | ||
235 | |||
236 | host = sdhci_pltfm_init(pdev, soc_data->pdata); | ||
174 | if (IS_ERR(host)) | 237 | if (IS_ERR(host)) |
175 | return PTR_ERR(host); | 238 | return PTR_ERR(host); |
176 | 239 | ||
@@ -187,7 +250,17 @@ static int __devinit sdhci_tegra_probe(struct platform_device *pdev) | |||
187 | goto err_no_plat; | 250 | goto err_no_plat; |
188 | } | 251 | } |
189 | 252 | ||
190 | pltfm_host->priv = plat; | 253 | tegra_host = devm_kzalloc(&pdev->dev, sizeof(*tegra_host), GFP_KERNEL); |
254 | if (!tegra_host) { | ||
255 | dev_err(mmc_dev(host->mmc), "failed to allocate tegra_host\n"); | ||
256 | rc = -ENOMEM; | ||
257 | goto err_no_plat; | ||
258 | } | ||
259 | |||
260 | tegra_host->plat = plat; | ||
261 | tegra_host->soc_data = soc_data; | ||
262 | |||
263 | pltfm_host->priv = tegra_host; | ||
191 | 264 | ||
192 | if (gpio_is_valid(plat->power_gpio)) { | 265 | if (gpio_is_valid(plat->power_gpio)) { |
193 | rc = gpio_request(plat->power_gpio, "sdhci_power"); | 266 | rc = gpio_request(plat->power_gpio, "sdhci_power"); |
@@ -283,7 +356,8 @@ static int __devexit sdhci_tegra_remove(struct platform_device *pdev) | |||
283 | { | 356 | { |
284 | struct sdhci_host *host = platform_get_drvdata(pdev); | 357 | struct sdhci_host *host = platform_get_drvdata(pdev); |
285 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | 358 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
286 | struct tegra_sdhci_platform_data *plat = pltfm_host->priv; | 359 | struct sdhci_tegra *tegra_host = pltfm_host->priv; |
360 | const struct tegra_sdhci_platform_data *plat = tegra_host->plat; | ||
287 | int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); | 361 | int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); |
288 | 362 | ||
289 | sdhci_remove_host(host, dead); | 363 | sdhci_remove_host(host, dead); |
@@ -326,5 +400,5 @@ static struct platform_driver sdhci_tegra_driver = { | |||
326 | module_platform_driver(sdhci_tegra_driver); | 400 | module_platform_driver(sdhci_tegra_driver); |
327 | 401 | ||
328 | MODULE_DESCRIPTION("SDHCI driver for Tegra"); | 402 | MODULE_DESCRIPTION("SDHCI driver for Tegra"); |
329 | MODULE_AUTHOR(" Google, Inc."); | 403 | MODULE_AUTHOR("Google, Inc."); |
330 | MODULE_LICENSE("GPL v2"); | 404 | MODULE_LICENSE("GPL v2"); |