diff options
author | Stephen Warren <swarren@nvidia.com> | 2012-02-01 18:30:55 -0500 |
---|---|---|
committer | Chris Ball <cjb@laptop.org> | 2012-03-25 19:33:45 -0400 |
commit | 3e44a1a7d22cdc44c98569fedbd993985bfd64d3 (patch) | |
tree | d6dac36ff0d819e5b2b436acb804f320f8e995aa /drivers | |
parent | 192b5372421766f62fce20d2db2deb19a58e2cfc (diff) |
mmc: sdhci-tegra: Explicitly support Tegra30
Tegra30 differs from Tegra20 in a number of ways. This patch implements a
minimal set of differences in order to get the Cardhu board's SD slot and
eMMC working. Given the diffs between the mainline sdhci-tegra.c and our
downstream versions, I expect we'll eventually need to add many more
differences, hence the seemingly heavy-weight addition of the soc_data
structure.
* Tegra30 doesn't need to override register access to SDHCI_HOST_VERSION or
SDHCI_INT_ENABLE.
* Tegra30 needs quirk SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK.
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Signed-off-by: Chris Ball <cjb@laptop.org>
Diffstat (limited to 'drivers')
-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 78a36eba4df0..ccbca0ba8c39 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> |
@@ -32,6 +33,19 @@ | |||
32 | 33 | ||
33 | #include "sdhci-pltfm.h" | 34 | #include "sdhci-pltfm.h" |
34 | 35 | ||
36 | #define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0) | ||
37 | #define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1) | ||
38 | |||
39 | struct sdhci_tegra_soc_data { | ||
40 | struct sdhci_pltfm_data *pdata; | ||
41 | u32 nvquirks; | ||
42 | }; | ||
43 | |||
44 | struct sdhci_tegra { | ||
45 | const struct tegra_sdhci_platform_data *plat; | ||
46 | const struct sdhci_tegra_soc_data *soc_data; | ||
47 | }; | ||
48 | |||
35 | static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) | 49 | static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) |
36 | { | 50 | { |
37 | u32 val; | 51 | u32 val; |
@@ -47,7 +61,12 @@ static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) | |||
47 | 61 | ||
48 | static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) | 62 | static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) |
49 | { | 63 | { |
50 | if (unlikely(reg == SDHCI_HOST_VERSION)) { | 64 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
65 | struct sdhci_tegra *tegra_host = pltfm_host->priv; | ||
66 | const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; | ||
67 | |||
68 | if (unlikely((soc_data->nvquirks & NVQUIRK_FORCE_SDHCI_SPEC_200) && | ||
69 | (reg == SDHCI_HOST_VERSION))) { | ||
51 | /* Erratum: Version register is invalid in HW. */ | 70 | /* Erratum: Version register is invalid in HW. */ |
52 | return SDHCI_SPEC_200; | 71 | return SDHCI_SPEC_200; |
53 | } | 72 | } |
@@ -57,6 +76,10 @@ static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) | |||
57 | 76 | ||
58 | static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) | 77 | static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) |
59 | { | 78 | { |
79 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | ||
80 | struct sdhci_tegra *tegra_host = pltfm_host->priv; | ||
81 | const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; | ||
82 | |||
60 | /* Seems like we're getting spurious timeout and crc errors, so | 83 | /* Seems like we're getting spurious timeout and crc errors, so |
61 | * disable signalling of them. In case of real errors software | 84 | * disable signalling of them. In case of real errors software |
62 | * timers should take care of eventually detecting them. | 85 | * timers should take care of eventually detecting them. |
@@ -66,7 +89,8 @@ static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) | |||
66 | 89 | ||
67 | writel(val, host->ioaddr + reg); | 90 | writel(val, host->ioaddr + reg); |
68 | 91 | ||
69 | if (unlikely(reg == SDHCI_INT_ENABLE)) { | 92 | if (unlikely((soc_data->nvquirks & NVQUIRK_ENABLE_BLOCK_GAP_DET) && |
93 | (reg == SDHCI_INT_ENABLE))) { | ||
70 | /* Erratum: Must enable block gap interrupt detection */ | 94 | /* Erratum: Must enable block gap interrupt detection */ |
71 | u8 gap_ctrl = readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); | 95 | u8 gap_ctrl = readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); |
72 | if (val & SDHCI_INT_CARD_INT) | 96 | if (val & SDHCI_INT_CARD_INT) |
@@ -77,10 +101,11 @@ static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) | |||
77 | } | 101 | } |
78 | } | 102 | } |
79 | 103 | ||
80 | static unsigned int tegra_sdhci_get_ro(struct sdhci_host *sdhci) | 104 | static unsigned int tegra_sdhci_get_ro(struct sdhci_host *host) |
81 | { | 105 | { |
82 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); | 106 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
83 | struct tegra_sdhci_platform_data *plat = pltfm_host->priv; | 107 | struct sdhci_tegra *tegra_host = pltfm_host->priv; |
108 | const struct tegra_sdhci_platform_data *plat = tegra_host->plat; | ||
84 | 109 | ||
85 | if (!gpio_is_valid(plat->wp_gpio)) | 110 | if (!gpio_is_valid(plat->wp_gpio)) |
86 | return -1; | 111 | return -1; |
@@ -99,7 +124,8 @@ static irqreturn_t carddetect_irq(int irq, void *data) | |||
99 | static int tegra_sdhci_8bit(struct sdhci_host *host, int bus_width) | 124 | static int tegra_sdhci_8bit(struct sdhci_host *host, int bus_width) |
100 | { | 125 | { |
101 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | 126 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
102 | struct tegra_sdhci_platform_data *plat = pltfm_host->priv; | 127 | struct sdhci_tegra *tegra_host = pltfm_host->priv; |
128 | const struct tegra_sdhci_platform_data *plat = tegra_host->plat; | ||
103 | u32 ctrl; | 129 | u32 ctrl; |
104 | 130 | ||
105 | ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); | 131 | ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); |
@@ -125,16 +151,44 @@ static struct sdhci_ops tegra_sdhci_ops = { | |||
125 | .platform_8bit_width = tegra_sdhci_8bit, | 151 | .platform_8bit_width = tegra_sdhci_8bit, |
126 | }; | 152 | }; |
127 | 153 | ||
128 | static struct sdhci_pltfm_data sdhci_tegra_pdata = { | 154 | #ifdef CONFIG_ARCH_TEGRA_2x_SOC |
155 | static struct sdhci_pltfm_data sdhci_tegra20_pdata = { | ||
156 | .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | | ||
157 | SDHCI_QUIRK_SINGLE_POWER_WRITE | | ||
158 | SDHCI_QUIRK_NO_HISPD_BIT | | ||
159 | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, | ||
160 | .ops = &tegra_sdhci_ops, | ||
161 | }; | ||
162 | |||
163 | static struct sdhci_tegra_soc_data soc_data_tegra20 = { | ||
164 | .pdata = &sdhci_tegra20_pdata, | ||
165 | .nvquirks = NVQUIRK_FORCE_SDHCI_SPEC_200 | | ||
166 | NVQUIRK_ENABLE_BLOCK_GAP_DET, | ||
167 | }; | ||
168 | #endif | ||
169 | |||
170 | #ifdef CONFIG_ARCH_TEGRA_3x_SOC | ||
171 | static struct sdhci_pltfm_data sdhci_tegra30_pdata = { | ||
129 | .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | | 172 | .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | |
173 | SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | | ||
130 | SDHCI_QUIRK_SINGLE_POWER_WRITE | | 174 | SDHCI_QUIRK_SINGLE_POWER_WRITE | |
131 | SDHCI_QUIRK_NO_HISPD_BIT | | 175 | SDHCI_QUIRK_NO_HISPD_BIT | |
132 | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, | 176 | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, |
133 | .ops = &tegra_sdhci_ops, | 177 | .ops = &tegra_sdhci_ops, |
134 | }; | 178 | }; |
135 | 179 | ||
180 | static struct sdhci_tegra_soc_data soc_data_tegra30 = { | ||
181 | .pdata = &sdhci_tegra30_pdata, | ||
182 | }; | ||
183 | #endif | ||
184 | |||
136 | static const struct of_device_id sdhci_tegra_dt_match[] __devinitdata = { | 185 | static const struct of_device_id sdhci_tegra_dt_match[] __devinitdata = { |
137 | { .compatible = "nvidia,tegra20-sdhci", }, | 186 | #ifdef CONFIG_ARCH_TEGRA_3x_SOC |
187 | { .compatible = "nvidia,tegra30-sdhci", .data = &soc_data_tegra30 }, | ||
188 | #endif | ||
189 | #ifdef CONFIG_ARCH_TEGRA_2x_SOC | ||
190 | { .compatible = "nvidia,tegra20-sdhci", .data = &soc_data_tegra20 }, | ||
191 | #endif | ||
138 | {} | 192 | {} |
139 | }; | 193 | }; |
140 | MODULE_DEVICE_TABLE(of, sdhci_dt_ids); | 194 | MODULE_DEVICE_TABLE(of, sdhci_dt_ids); |
@@ -165,13 +219,22 @@ static struct tegra_sdhci_platform_data * __devinit sdhci_tegra_dt_parse_pdata( | |||
165 | 219 | ||
166 | static int __devinit sdhci_tegra_probe(struct platform_device *pdev) | 220 | static int __devinit sdhci_tegra_probe(struct platform_device *pdev) |
167 | { | 221 | { |
222 | const struct of_device_id *match; | ||
223 | const struct sdhci_tegra_soc_data *soc_data; | ||
224 | struct sdhci_host *host; | ||
168 | struct sdhci_pltfm_host *pltfm_host; | 225 | struct sdhci_pltfm_host *pltfm_host; |
169 | struct tegra_sdhci_platform_data *plat; | 226 | struct tegra_sdhci_platform_data *plat; |
170 | struct sdhci_host *host; | 227 | struct sdhci_tegra *tegra_host; |
171 | struct clk *clk; | 228 | struct clk *clk; |
172 | int rc; | 229 | int rc; |
173 | 230 | ||
174 | host = sdhci_pltfm_init(pdev, &sdhci_tegra_pdata); | 231 | match = of_match_device(sdhci_tegra_dt_match, &pdev->dev); |
232 | if (match) | ||
233 | soc_data = match->data; | ||
234 | else | ||
235 | soc_data = &soc_data_tegra20; | ||
236 | |||
237 | host = sdhci_pltfm_init(pdev, soc_data->pdata); | ||
175 | if (IS_ERR(host)) | 238 | if (IS_ERR(host)) |
176 | return PTR_ERR(host); | 239 | return PTR_ERR(host); |
177 | 240 | ||
@@ -188,7 +251,17 @@ static int __devinit sdhci_tegra_probe(struct platform_device *pdev) | |||
188 | goto err_no_plat; | 251 | goto err_no_plat; |
189 | } | 252 | } |
190 | 253 | ||
191 | pltfm_host->priv = plat; | 254 | tegra_host = devm_kzalloc(&pdev->dev, sizeof(*tegra_host), GFP_KERNEL); |
255 | if (!tegra_host) { | ||
256 | dev_err(mmc_dev(host->mmc), "failed to allocate tegra_host\n"); | ||
257 | rc = -ENOMEM; | ||
258 | goto err_no_plat; | ||
259 | } | ||
260 | |||
261 | tegra_host->plat = plat; | ||
262 | tegra_host->soc_data = soc_data; | ||
263 | |||
264 | pltfm_host->priv = tegra_host; | ||
192 | 265 | ||
193 | if (gpio_is_valid(plat->power_gpio)) { | 266 | if (gpio_is_valid(plat->power_gpio)) { |
194 | rc = gpio_request(plat->power_gpio, "sdhci_power"); | 267 | rc = gpio_request(plat->power_gpio, "sdhci_power"); |
@@ -284,7 +357,8 @@ static int __devexit sdhci_tegra_remove(struct platform_device *pdev) | |||
284 | { | 357 | { |
285 | struct sdhci_host *host = platform_get_drvdata(pdev); | 358 | struct sdhci_host *host = platform_get_drvdata(pdev); |
286 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | 359 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
287 | struct tegra_sdhci_platform_data *plat = pltfm_host->priv; | 360 | struct sdhci_tegra *tegra_host = pltfm_host->priv; |
361 | const struct tegra_sdhci_platform_data *plat = tegra_host->plat; | ||
288 | int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); | 362 | int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); |
289 | 363 | ||
290 | sdhci_remove_host(host, dead); | 364 | sdhci_remove_host(host, dead); |
@@ -327,5 +401,5 @@ static struct platform_driver sdhci_tegra_driver = { | |||
327 | module_platform_driver(sdhci_tegra_driver); | 401 | module_platform_driver(sdhci_tegra_driver); |
328 | 402 | ||
329 | MODULE_DESCRIPTION("SDHCI driver for Tegra"); | 403 | MODULE_DESCRIPTION("SDHCI driver for Tegra"); |
330 | MODULE_AUTHOR(" Google, Inc."); | 404 | MODULE_AUTHOR("Google, Inc."); |
331 | MODULE_LICENSE("GPL v2"); | 405 | MODULE_LICENSE("GPL v2"); |