103d2bfc8SOlof Johansson /* 203d2bfc8SOlof Johansson * Copyright (C) 2010 Google, Inc. 303d2bfc8SOlof Johansson * 403d2bfc8SOlof Johansson * This software is licensed under the terms of the GNU General Public 503d2bfc8SOlof Johansson * License version 2, as published by the Free Software Foundation, and 603d2bfc8SOlof Johansson * may be copied, distributed, and modified under those terms. 703d2bfc8SOlof Johansson * 803d2bfc8SOlof Johansson * This program is distributed in the hope that it will be useful, 903d2bfc8SOlof Johansson * but WITHOUT ANY WARRANTY; without even the implied warranty of 1003d2bfc8SOlof Johansson * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1103d2bfc8SOlof Johansson * GNU General Public License for more details. 1203d2bfc8SOlof Johansson * 1303d2bfc8SOlof Johansson */ 1403d2bfc8SOlof Johansson 1503d2bfc8SOlof Johansson #include <linux/err.h> 1696547f5dSPaul Gortmaker #include <linux/module.h> 1703d2bfc8SOlof Johansson #include <linux/init.h> 1803d2bfc8SOlof Johansson #include <linux/platform_device.h> 1903d2bfc8SOlof Johansson #include <linux/clk.h> 2003d2bfc8SOlof Johansson #include <linux/io.h> 2155cd65e4SStephen Warren #include <linux/of.h> 223e44a1a7SStephen Warren #include <linux/of_device.h> 23275173b2SGrant Likely #include <linux/of_gpio.h> 2403d2bfc8SOlof Johansson #include <linux/gpio.h> 2503d2bfc8SOlof Johansson #include <linux/mmc/card.h> 2603d2bfc8SOlof Johansson #include <linux/mmc/host.h> 2788b47679SPaul Gortmaker #include <linux/module.h> 2803d2bfc8SOlof Johansson 29e6b750d4SRussell King #include <asm/gpio.h> 30ea5abbd2SStephen Warren 31ea5abbd2SStephen Warren #include <mach/gpio-tegra.h> 3203d2bfc8SOlof Johansson #include <mach/sdhci.h> 3303d2bfc8SOlof Johansson 3403d2bfc8SOlof Johansson #include "sdhci-pltfm.h" 3503d2bfc8SOlof Johansson 363e44a1a7SStephen Warren #define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0) 373e44a1a7SStephen Warren #define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1) 383e44a1a7SStephen Warren 393e44a1a7SStephen Warren struct sdhci_tegra_soc_data { 403e44a1a7SStephen Warren struct sdhci_pltfm_data *pdata; 413e44a1a7SStephen Warren u32 nvquirks; 423e44a1a7SStephen Warren }; 433e44a1a7SStephen Warren 443e44a1a7SStephen Warren struct sdhci_tegra { 453e44a1a7SStephen Warren const struct tegra_sdhci_platform_data *plat; 463e44a1a7SStephen Warren const struct sdhci_tegra_soc_data *soc_data; 473e44a1a7SStephen Warren }; 483e44a1a7SStephen Warren 4903d2bfc8SOlof Johansson static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) 5003d2bfc8SOlof Johansson { 5103d2bfc8SOlof Johansson u32 val; 5203d2bfc8SOlof Johansson 5303d2bfc8SOlof Johansson if (unlikely(reg == SDHCI_PRESENT_STATE)) { 5403d2bfc8SOlof Johansson /* Use wp_gpio here instead? */ 5503d2bfc8SOlof Johansson val = readl(host->ioaddr + reg); 5603d2bfc8SOlof Johansson return val | SDHCI_WRITE_PROTECT; 5703d2bfc8SOlof Johansson } 5803d2bfc8SOlof Johansson 5903d2bfc8SOlof Johansson return readl(host->ioaddr + reg); 6003d2bfc8SOlof Johansson } 6103d2bfc8SOlof Johansson 6203d2bfc8SOlof Johansson static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) 6303d2bfc8SOlof Johansson { 643e44a1a7SStephen Warren struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 653e44a1a7SStephen Warren struct sdhci_tegra *tegra_host = pltfm_host->priv; 663e44a1a7SStephen Warren const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; 673e44a1a7SStephen Warren 683e44a1a7SStephen Warren if (unlikely((soc_data->nvquirks & NVQUIRK_FORCE_SDHCI_SPEC_200) && 693e44a1a7SStephen Warren (reg == SDHCI_HOST_VERSION))) { 7003d2bfc8SOlof Johansson /* Erratum: Version register is invalid in HW. */ 7103d2bfc8SOlof Johansson return SDHCI_SPEC_200; 7203d2bfc8SOlof Johansson } 7303d2bfc8SOlof Johansson 7403d2bfc8SOlof Johansson return readw(host->ioaddr + reg); 7503d2bfc8SOlof Johansson } 7603d2bfc8SOlof Johansson 7703d2bfc8SOlof Johansson static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) 7803d2bfc8SOlof Johansson { 793e44a1a7SStephen Warren struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 803e44a1a7SStephen Warren struct sdhci_tegra *tegra_host = pltfm_host->priv; 813e44a1a7SStephen Warren const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; 823e44a1a7SStephen Warren 8303d2bfc8SOlof Johansson /* Seems like we're getting spurious timeout and crc errors, so 8403d2bfc8SOlof Johansson * disable signalling of them. In case of real errors software 8503d2bfc8SOlof Johansson * timers should take care of eventually detecting them. 8603d2bfc8SOlof Johansson */ 8703d2bfc8SOlof Johansson if (unlikely(reg == SDHCI_SIGNAL_ENABLE)) 8803d2bfc8SOlof Johansson val &= ~(SDHCI_INT_TIMEOUT|SDHCI_INT_CRC); 8903d2bfc8SOlof Johansson 9003d2bfc8SOlof Johansson writel(val, host->ioaddr + reg); 9103d2bfc8SOlof Johansson 923e44a1a7SStephen Warren if (unlikely((soc_data->nvquirks & NVQUIRK_ENABLE_BLOCK_GAP_DET) && 933e44a1a7SStephen Warren (reg == SDHCI_INT_ENABLE))) { 9403d2bfc8SOlof Johansson /* Erratum: Must enable block gap interrupt detection */ 9503d2bfc8SOlof Johansson u8 gap_ctrl = readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); 9603d2bfc8SOlof Johansson if (val & SDHCI_INT_CARD_INT) 9703d2bfc8SOlof Johansson gap_ctrl |= 0x8; 9803d2bfc8SOlof Johansson else 9903d2bfc8SOlof Johansson gap_ctrl &= ~0x8; 10003d2bfc8SOlof Johansson writeb(gap_ctrl, host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); 10103d2bfc8SOlof Johansson } 10203d2bfc8SOlof Johansson } 10303d2bfc8SOlof Johansson 1043e44a1a7SStephen Warren static unsigned int tegra_sdhci_get_ro(struct sdhci_host *host) 10503d2bfc8SOlof Johansson { 1063e44a1a7SStephen Warren struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 1073e44a1a7SStephen Warren struct sdhci_tegra *tegra_host = pltfm_host->priv; 1083e44a1a7SStephen Warren const struct tegra_sdhci_platform_data *plat = tegra_host->plat; 10903d2bfc8SOlof Johansson 11003d2bfc8SOlof Johansson if (!gpio_is_valid(plat->wp_gpio)) 11103d2bfc8SOlof Johansson return -1; 11203d2bfc8SOlof Johansson 11303d2bfc8SOlof Johansson return gpio_get_value(plat->wp_gpio); 11403d2bfc8SOlof Johansson } 11503d2bfc8SOlof Johansson 11603d2bfc8SOlof Johansson static irqreturn_t carddetect_irq(int irq, void *data) 11703d2bfc8SOlof Johansson { 11803d2bfc8SOlof Johansson struct sdhci_host *sdhost = (struct sdhci_host *)data; 11903d2bfc8SOlof Johansson 12003d2bfc8SOlof Johansson tasklet_schedule(&sdhost->card_tasklet); 12103d2bfc8SOlof Johansson return IRQ_HANDLED; 12203d2bfc8SOlof Johansson }; 12303d2bfc8SOlof Johansson 12403d2bfc8SOlof Johansson static int tegra_sdhci_8bit(struct sdhci_host *host, int bus_width) 12503d2bfc8SOlof Johansson { 126275173b2SGrant Likely struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 1273e44a1a7SStephen Warren struct sdhci_tegra *tegra_host = pltfm_host->priv; 1283e44a1a7SStephen Warren const struct tegra_sdhci_platform_data *plat = tegra_host->plat; 12903d2bfc8SOlof Johansson u32 ctrl; 13003d2bfc8SOlof Johansson 13103d2bfc8SOlof Johansson ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); 13203d2bfc8SOlof Johansson if (plat->is_8bit && bus_width == MMC_BUS_WIDTH_8) { 13303d2bfc8SOlof Johansson ctrl &= ~SDHCI_CTRL_4BITBUS; 13403d2bfc8SOlof Johansson ctrl |= SDHCI_CTRL_8BITBUS; 13503d2bfc8SOlof Johansson } else { 13603d2bfc8SOlof Johansson ctrl &= ~SDHCI_CTRL_8BITBUS; 13703d2bfc8SOlof Johansson if (bus_width == MMC_BUS_WIDTH_4) 13803d2bfc8SOlof Johansson ctrl |= SDHCI_CTRL_4BITBUS; 13903d2bfc8SOlof Johansson else 14003d2bfc8SOlof Johansson ctrl &= ~SDHCI_CTRL_4BITBUS; 14103d2bfc8SOlof Johansson } 14203d2bfc8SOlof Johansson sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); 14303d2bfc8SOlof Johansson return 0; 14403d2bfc8SOlof Johansson } 14503d2bfc8SOlof Johansson 14685d6509dSShawn Guo static struct sdhci_ops tegra_sdhci_ops = { 14785d6509dSShawn Guo .get_ro = tegra_sdhci_get_ro, 14885d6509dSShawn Guo .read_l = tegra_sdhci_readl, 14985d6509dSShawn Guo .read_w = tegra_sdhci_readw, 15085d6509dSShawn Guo .write_l = tegra_sdhci_writel, 15185d6509dSShawn Guo .platform_8bit_width = tegra_sdhci_8bit, 15285d6509dSShawn Guo }; 15303d2bfc8SOlof Johansson 1543e44a1a7SStephen Warren #ifdef CONFIG_ARCH_TEGRA_2x_SOC 1553e44a1a7SStephen Warren static struct sdhci_pltfm_data sdhci_tegra20_pdata = { 15685d6509dSShawn Guo .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | 15785d6509dSShawn Guo SDHCI_QUIRK_SINGLE_POWER_WRITE | 15885d6509dSShawn Guo SDHCI_QUIRK_NO_HISPD_BIT | 15985d6509dSShawn Guo SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, 16085d6509dSShawn Guo .ops = &tegra_sdhci_ops, 16185d6509dSShawn Guo }; 16285d6509dSShawn Guo 1633e44a1a7SStephen Warren static struct sdhci_tegra_soc_data soc_data_tegra20 = { 1643e44a1a7SStephen Warren .pdata = &sdhci_tegra20_pdata, 1653e44a1a7SStephen Warren .nvquirks = NVQUIRK_FORCE_SDHCI_SPEC_200 | 1663e44a1a7SStephen Warren NVQUIRK_ENABLE_BLOCK_GAP_DET, 1673e44a1a7SStephen Warren }; 1683e44a1a7SStephen Warren #endif 1693e44a1a7SStephen Warren 1703e44a1a7SStephen Warren #ifdef CONFIG_ARCH_TEGRA_3x_SOC 1713e44a1a7SStephen Warren static struct sdhci_pltfm_data sdhci_tegra30_pdata = { 1723e44a1a7SStephen Warren .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | 1733e44a1a7SStephen Warren SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | 1743e44a1a7SStephen Warren SDHCI_QUIRK_SINGLE_POWER_WRITE | 1753e44a1a7SStephen Warren SDHCI_QUIRK_NO_HISPD_BIT | 1763e44a1a7SStephen Warren SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, 1773e44a1a7SStephen Warren .ops = &tegra_sdhci_ops, 1783e44a1a7SStephen Warren }; 1793e44a1a7SStephen Warren 1803e44a1a7SStephen Warren static struct sdhci_tegra_soc_data soc_data_tegra30 = { 1813e44a1a7SStephen Warren .pdata = &sdhci_tegra30_pdata, 1823e44a1a7SStephen Warren }; 1833e44a1a7SStephen Warren #endif 1843e44a1a7SStephen Warren 185275173b2SGrant Likely static const struct of_device_id sdhci_tegra_dt_match[] __devinitdata = { 1863e44a1a7SStephen Warren #ifdef CONFIG_ARCH_TEGRA_3x_SOC 1873e44a1a7SStephen Warren { .compatible = "nvidia,tegra30-sdhci", .data = &soc_data_tegra30 }, 1883e44a1a7SStephen Warren #endif 1893e44a1a7SStephen Warren #ifdef CONFIG_ARCH_TEGRA_2x_SOC 1903e44a1a7SStephen Warren { .compatible = "nvidia,tegra20-sdhci", .data = &soc_data_tegra20 }, 1913e44a1a7SStephen Warren #endif 192275173b2SGrant Likely {} 193275173b2SGrant Likely }; 194275173b2SGrant Likely MODULE_DEVICE_TABLE(of, sdhci_dt_ids); 195275173b2SGrant Likely 196275173b2SGrant Likely static struct tegra_sdhci_platform_data * __devinit sdhci_tegra_dt_parse_pdata( 197275173b2SGrant Likely struct platform_device *pdev) 198275173b2SGrant Likely { 199275173b2SGrant Likely struct tegra_sdhci_platform_data *plat; 200275173b2SGrant Likely struct device_node *np = pdev->dev.of_node; 201275173b2SGrant Likely 202275173b2SGrant Likely if (!np) 203275173b2SGrant Likely return NULL; 204275173b2SGrant Likely 205275173b2SGrant Likely plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL); 206275173b2SGrant Likely if (!plat) { 207275173b2SGrant Likely dev_err(&pdev->dev, "Can't allocate platform data\n"); 208275173b2SGrant Likely return NULL; 209275173b2SGrant Likely } 210275173b2SGrant Likely 211275173b2SGrant Likely plat->cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); 212275173b2SGrant Likely plat->wp_gpio = of_get_named_gpio(np, "wp-gpios", 0); 213275173b2SGrant Likely plat->power_gpio = of_get_named_gpio(np, "power-gpios", 0); 21455cd65e4SStephen Warren if (of_find_property(np, "support-8bit", NULL)) 21555cd65e4SStephen Warren plat->is_8bit = 1; 216275173b2SGrant Likely 217275173b2SGrant Likely return plat; 218275173b2SGrant Likely } 219275173b2SGrant Likely 22085d6509dSShawn Guo static int __devinit sdhci_tegra_probe(struct platform_device *pdev) 22103d2bfc8SOlof Johansson { 2223e44a1a7SStephen Warren const struct of_device_id *match; 2233e44a1a7SStephen Warren const struct sdhci_tegra_soc_data *soc_data; 2243e44a1a7SStephen Warren struct sdhci_host *host; 22585d6509dSShawn Guo struct sdhci_pltfm_host *pltfm_host; 22603d2bfc8SOlof Johansson struct tegra_sdhci_platform_data *plat; 2273e44a1a7SStephen Warren struct sdhci_tegra *tegra_host; 22803d2bfc8SOlof Johansson struct clk *clk; 22903d2bfc8SOlof Johansson int rc; 23003d2bfc8SOlof Johansson 2313e44a1a7SStephen Warren match = of_match_device(sdhci_tegra_dt_match, &pdev->dev); 2323e44a1a7SStephen Warren if (match) 2333e44a1a7SStephen Warren soc_data = match->data; 2343e44a1a7SStephen Warren else 2353e44a1a7SStephen Warren soc_data = &soc_data_tegra20; 2363e44a1a7SStephen Warren 2373e44a1a7SStephen Warren host = sdhci_pltfm_init(pdev, soc_data->pdata); 23885d6509dSShawn Guo if (IS_ERR(host)) 23985d6509dSShawn Guo return PTR_ERR(host); 24085d6509dSShawn Guo 24185d6509dSShawn Guo pltfm_host = sdhci_priv(host); 24285d6509dSShawn Guo 24303d2bfc8SOlof Johansson plat = pdev->dev.platform_data; 24485d6509dSShawn Guo 245275173b2SGrant Likely if (plat == NULL) 246275173b2SGrant Likely plat = sdhci_tegra_dt_parse_pdata(pdev); 247275173b2SGrant Likely 24803d2bfc8SOlof Johansson if (plat == NULL) { 24903d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), "missing platform data\n"); 25085d6509dSShawn Guo rc = -ENXIO; 25185d6509dSShawn Guo goto err_no_plat; 25203d2bfc8SOlof Johansson } 25303d2bfc8SOlof Johansson 2543e44a1a7SStephen Warren tegra_host = devm_kzalloc(&pdev->dev, sizeof(*tegra_host), GFP_KERNEL); 2553e44a1a7SStephen Warren if (!tegra_host) { 2563e44a1a7SStephen Warren dev_err(mmc_dev(host->mmc), "failed to allocate tegra_host\n"); 2573e44a1a7SStephen Warren rc = -ENOMEM; 2583e44a1a7SStephen Warren goto err_no_plat; 2593e44a1a7SStephen Warren } 2603e44a1a7SStephen Warren 2613e44a1a7SStephen Warren tegra_host->plat = plat; 2623e44a1a7SStephen Warren tegra_host->soc_data = soc_data; 2633e44a1a7SStephen Warren 2643e44a1a7SStephen Warren pltfm_host->priv = tegra_host; 265275173b2SGrant Likely 26603d2bfc8SOlof Johansson if (gpio_is_valid(plat->power_gpio)) { 26703d2bfc8SOlof Johansson rc = gpio_request(plat->power_gpio, "sdhci_power"); 26803d2bfc8SOlof Johansson if (rc) { 26903d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), 27003d2bfc8SOlof Johansson "failed to allocate power gpio\n"); 27185d6509dSShawn Guo goto err_power_req; 27203d2bfc8SOlof Johansson } 27303d2bfc8SOlof Johansson tegra_gpio_enable(plat->power_gpio); 27403d2bfc8SOlof Johansson gpio_direction_output(plat->power_gpio, 1); 27503d2bfc8SOlof Johansson } 27603d2bfc8SOlof Johansson 27703d2bfc8SOlof Johansson if (gpio_is_valid(plat->cd_gpio)) { 27803d2bfc8SOlof Johansson rc = gpio_request(plat->cd_gpio, "sdhci_cd"); 27903d2bfc8SOlof Johansson if (rc) { 28003d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), 28103d2bfc8SOlof Johansson "failed to allocate cd gpio\n"); 28285d6509dSShawn Guo goto err_cd_req; 28303d2bfc8SOlof Johansson } 28403d2bfc8SOlof Johansson tegra_gpio_enable(plat->cd_gpio); 28503d2bfc8SOlof Johansson gpio_direction_input(plat->cd_gpio); 28603d2bfc8SOlof Johansson 28703d2bfc8SOlof Johansson rc = request_irq(gpio_to_irq(plat->cd_gpio), carddetect_irq, 28803d2bfc8SOlof Johansson IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 28903d2bfc8SOlof Johansson mmc_hostname(host->mmc), host); 29003d2bfc8SOlof Johansson 29103d2bfc8SOlof Johansson if (rc) { 29203d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), "request irq error\n"); 29385d6509dSShawn Guo goto err_cd_irq_req; 29403d2bfc8SOlof Johansson } 29503d2bfc8SOlof Johansson 29603d2bfc8SOlof Johansson } 29703d2bfc8SOlof Johansson 29803d2bfc8SOlof Johansson if (gpio_is_valid(plat->wp_gpio)) { 29903d2bfc8SOlof Johansson rc = gpio_request(plat->wp_gpio, "sdhci_wp"); 30003d2bfc8SOlof Johansson if (rc) { 30103d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), 30203d2bfc8SOlof Johansson "failed to allocate wp gpio\n"); 30385d6509dSShawn Guo goto err_wp_req; 30403d2bfc8SOlof Johansson } 30503d2bfc8SOlof Johansson tegra_gpio_enable(plat->wp_gpio); 30603d2bfc8SOlof Johansson gpio_direction_input(plat->wp_gpio); 30703d2bfc8SOlof Johansson } 30803d2bfc8SOlof Johansson 30903d2bfc8SOlof Johansson clk = clk_get(mmc_dev(host->mmc), NULL); 31003d2bfc8SOlof Johansson if (IS_ERR(clk)) { 31103d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), "clk err\n"); 31203d2bfc8SOlof Johansson rc = PTR_ERR(clk); 31385d6509dSShawn Guo goto err_clk_get; 31403d2bfc8SOlof Johansson } 31503d2bfc8SOlof Johansson clk_enable(clk); 31603d2bfc8SOlof Johansson pltfm_host->clk = clk; 31703d2bfc8SOlof Johansson 318c7f409e3SVenkat Rao host->mmc->pm_caps = plat->pm_flags; 319c7f409e3SVenkat Rao 32003d2bfc8SOlof Johansson if (plat->is_8bit) 32103d2bfc8SOlof Johansson host->mmc->caps |= MMC_CAP_8_BIT_DATA; 32203d2bfc8SOlof Johansson 32385d6509dSShawn Guo rc = sdhci_add_host(host); 32485d6509dSShawn Guo if (rc) 32585d6509dSShawn Guo goto err_add_host; 32685d6509dSShawn Guo 32703d2bfc8SOlof Johansson return 0; 32803d2bfc8SOlof Johansson 32985d6509dSShawn Guo err_add_host: 33085d6509dSShawn Guo clk_disable(pltfm_host->clk); 33185d6509dSShawn Guo clk_put(pltfm_host->clk); 33285d6509dSShawn Guo err_clk_get: 33303d2bfc8SOlof Johansson if (gpio_is_valid(plat->wp_gpio)) { 33403d2bfc8SOlof Johansson tegra_gpio_disable(plat->wp_gpio); 33503d2bfc8SOlof Johansson gpio_free(plat->wp_gpio); 33603d2bfc8SOlof Johansson } 33785d6509dSShawn Guo err_wp_req: 3388154b575SWolfram Sang if (gpio_is_valid(plat->cd_gpio)) 3398154b575SWolfram Sang free_irq(gpio_to_irq(plat->cd_gpio), host); 34085d6509dSShawn Guo err_cd_irq_req: 34103d2bfc8SOlof Johansson if (gpio_is_valid(plat->cd_gpio)) { 34203d2bfc8SOlof Johansson tegra_gpio_disable(plat->cd_gpio); 34303d2bfc8SOlof Johansson gpio_free(plat->cd_gpio); 34403d2bfc8SOlof Johansson } 34585d6509dSShawn Guo err_cd_req: 34603d2bfc8SOlof Johansson if (gpio_is_valid(plat->power_gpio)) { 34703d2bfc8SOlof Johansson tegra_gpio_disable(plat->power_gpio); 34803d2bfc8SOlof Johansson gpio_free(plat->power_gpio); 34903d2bfc8SOlof Johansson } 35085d6509dSShawn Guo err_power_req: 35185d6509dSShawn Guo err_no_plat: 35285d6509dSShawn Guo sdhci_pltfm_free(pdev); 35303d2bfc8SOlof Johansson return rc; 35403d2bfc8SOlof Johansson } 35503d2bfc8SOlof Johansson 35685d6509dSShawn Guo static int __devexit sdhci_tegra_remove(struct platform_device *pdev) 35703d2bfc8SOlof Johansson { 35885d6509dSShawn Guo struct sdhci_host *host = platform_get_drvdata(pdev); 35903d2bfc8SOlof Johansson struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 3603e44a1a7SStephen Warren struct sdhci_tegra *tegra_host = pltfm_host->priv; 3613e44a1a7SStephen Warren const struct tegra_sdhci_platform_data *plat = tegra_host->plat; 36285d6509dSShawn Guo int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); 36385d6509dSShawn Guo 36485d6509dSShawn Guo sdhci_remove_host(host, dead); 36503d2bfc8SOlof Johansson 36603d2bfc8SOlof Johansson if (gpio_is_valid(plat->wp_gpio)) { 36703d2bfc8SOlof Johansson tegra_gpio_disable(plat->wp_gpio); 36803d2bfc8SOlof Johansson gpio_free(plat->wp_gpio); 36903d2bfc8SOlof Johansson } 37003d2bfc8SOlof Johansson 37103d2bfc8SOlof Johansson if (gpio_is_valid(plat->cd_gpio)) { 3728154b575SWolfram Sang free_irq(gpio_to_irq(plat->cd_gpio), host); 37303d2bfc8SOlof Johansson tegra_gpio_disable(plat->cd_gpio); 37403d2bfc8SOlof Johansson gpio_free(plat->cd_gpio); 37503d2bfc8SOlof Johansson } 37603d2bfc8SOlof Johansson 37703d2bfc8SOlof Johansson if (gpio_is_valid(plat->power_gpio)) { 37803d2bfc8SOlof Johansson tegra_gpio_disable(plat->power_gpio); 37903d2bfc8SOlof Johansson gpio_free(plat->power_gpio); 38003d2bfc8SOlof Johansson } 38103d2bfc8SOlof Johansson 38203d2bfc8SOlof Johansson clk_disable(pltfm_host->clk); 38303d2bfc8SOlof Johansson clk_put(pltfm_host->clk); 38485d6509dSShawn Guo 38585d6509dSShawn Guo sdhci_pltfm_free(pdev); 38685d6509dSShawn Guo 38785d6509dSShawn Guo return 0; 38803d2bfc8SOlof Johansson } 38903d2bfc8SOlof Johansson 39085d6509dSShawn Guo static struct platform_driver sdhci_tegra_driver = { 39185d6509dSShawn Guo .driver = { 39285d6509dSShawn Guo .name = "sdhci-tegra", 39385d6509dSShawn Guo .owner = THIS_MODULE, 394275173b2SGrant Likely .of_match_table = sdhci_tegra_dt_match, 39529495aa0SManuel Lauss .pm = SDHCI_PLTFM_PMOPS, 39685d6509dSShawn Guo }, 39785d6509dSShawn Guo .probe = sdhci_tegra_probe, 39885d6509dSShawn Guo .remove = __devexit_p(sdhci_tegra_remove), 39903d2bfc8SOlof Johansson }; 40003d2bfc8SOlof Johansson 401d1f81a64SAxel Lin module_platform_driver(sdhci_tegra_driver); 40285d6509dSShawn Guo 40385d6509dSShawn Guo MODULE_DESCRIPTION("SDHCI driver for Tegra"); 40485d6509dSShawn Guo MODULE_AUTHOR("Google, Inc."); 40585d6509dSShawn Guo MODULE_LICENSE("GPL v2"); 406