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> 270aacd23fSJoseph Lo #include <linux/mmc/slot-gpio.h> 2803d2bfc8SOlof Johansson 2903d2bfc8SOlof Johansson #include "sdhci-pltfm.h" 3003d2bfc8SOlof Johansson 31ca5879d3SPavan Kunapuli /* Tegra SDHOST controller vendor register definitions */ 32ca5879d3SPavan Kunapuli #define SDHCI_TEGRA_VENDOR_MISC_CTRL 0x120 333145351aSAndrew Bresticker #define SDHCI_MISC_CTRL_ENABLE_SDR104 0x8 343145351aSAndrew Bresticker #define SDHCI_MISC_CTRL_ENABLE_SDR50 0x10 35ca5879d3SPavan Kunapuli #define SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300 0x20 363145351aSAndrew Bresticker #define SDHCI_MISC_CTRL_ENABLE_DDR50 0x200 37ca5879d3SPavan Kunapuli 383e44a1a7SStephen Warren #define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0) 393e44a1a7SStephen Warren #define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1) 40ca5879d3SPavan Kunapuli #define NVQUIRK_ENABLE_SDHCI_SPEC_300 BIT(2) 413145351aSAndrew Bresticker #define NVQUIRK_DISABLE_SDR50 BIT(3) 423145351aSAndrew Bresticker #define NVQUIRK_DISABLE_SDR104 BIT(4) 433145351aSAndrew Bresticker #define NVQUIRK_DISABLE_DDR50 BIT(5) 44352ee868SPavan Kunapuli #define NVQUIRK_SHADOW_XFER_MODE_REG BIT(6) 453e44a1a7SStephen Warren 463e44a1a7SStephen Warren struct sdhci_tegra_soc_data { 471db5eebfSLars-Peter Clausen const struct sdhci_pltfm_data *pdata; 483e44a1a7SStephen Warren u32 nvquirks; 493e44a1a7SStephen Warren }; 503e44a1a7SStephen Warren 513e44a1a7SStephen Warren struct sdhci_tegra { 523e44a1a7SStephen Warren const struct sdhci_tegra_soc_data *soc_data; 530e786102SStephen Warren int power_gpio; 543e44a1a7SStephen Warren }; 553e44a1a7SStephen Warren 5603d2bfc8SOlof Johansson static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) 5703d2bfc8SOlof Johansson { 583e44a1a7SStephen Warren struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 593e44a1a7SStephen Warren struct sdhci_tegra *tegra_host = pltfm_host->priv; 603e44a1a7SStephen Warren const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; 613e44a1a7SStephen Warren 623e44a1a7SStephen Warren if (unlikely((soc_data->nvquirks & NVQUIRK_FORCE_SDHCI_SPEC_200) && 633e44a1a7SStephen Warren (reg == SDHCI_HOST_VERSION))) { 6403d2bfc8SOlof Johansson /* Erratum: Version register is invalid in HW. */ 6503d2bfc8SOlof Johansson return SDHCI_SPEC_200; 6603d2bfc8SOlof Johansson } 6703d2bfc8SOlof Johansson 6803d2bfc8SOlof Johansson return readw(host->ioaddr + reg); 6903d2bfc8SOlof Johansson } 7003d2bfc8SOlof Johansson 71352ee868SPavan Kunapuli static void tegra_sdhci_writew(struct sdhci_host *host, u16 val, int reg) 72352ee868SPavan Kunapuli { 73352ee868SPavan Kunapuli struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 74352ee868SPavan Kunapuli struct sdhci_tegra *tegra_host = pltfm_host->priv; 75352ee868SPavan Kunapuli const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; 76352ee868SPavan Kunapuli 77352ee868SPavan Kunapuli if (soc_data->nvquirks & NVQUIRK_SHADOW_XFER_MODE_REG) { 78352ee868SPavan Kunapuli switch (reg) { 79352ee868SPavan Kunapuli case SDHCI_TRANSFER_MODE: 80352ee868SPavan Kunapuli /* 81352ee868SPavan Kunapuli * Postpone this write, we must do it together with a 82352ee868SPavan Kunapuli * command write that is down below. 83352ee868SPavan Kunapuli */ 84352ee868SPavan Kunapuli pltfm_host->xfer_mode_shadow = val; 85352ee868SPavan Kunapuli return; 86352ee868SPavan Kunapuli case SDHCI_COMMAND: 87352ee868SPavan Kunapuli writel((val << 16) | pltfm_host->xfer_mode_shadow, 88352ee868SPavan Kunapuli host->ioaddr + SDHCI_TRANSFER_MODE); 89352ee868SPavan Kunapuli return; 90352ee868SPavan Kunapuli } 91352ee868SPavan Kunapuli } 92352ee868SPavan Kunapuli 93352ee868SPavan Kunapuli writew(val, host->ioaddr + reg); 94352ee868SPavan Kunapuli } 95352ee868SPavan Kunapuli 9603d2bfc8SOlof Johansson static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) 9703d2bfc8SOlof Johansson { 983e44a1a7SStephen Warren struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 993e44a1a7SStephen Warren struct sdhci_tegra *tegra_host = pltfm_host->priv; 1003e44a1a7SStephen Warren const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; 1013e44a1a7SStephen Warren 10203d2bfc8SOlof Johansson /* Seems like we're getting spurious timeout and crc errors, so 10303d2bfc8SOlof Johansson * disable signalling of them. In case of real errors software 10403d2bfc8SOlof Johansson * timers should take care of eventually detecting them. 10503d2bfc8SOlof Johansson */ 10603d2bfc8SOlof Johansson if (unlikely(reg == SDHCI_SIGNAL_ENABLE)) 10703d2bfc8SOlof Johansson val &= ~(SDHCI_INT_TIMEOUT|SDHCI_INT_CRC); 10803d2bfc8SOlof Johansson 10903d2bfc8SOlof Johansson writel(val, host->ioaddr + reg); 11003d2bfc8SOlof Johansson 1113e44a1a7SStephen Warren if (unlikely((soc_data->nvquirks & NVQUIRK_ENABLE_BLOCK_GAP_DET) && 1123e44a1a7SStephen Warren (reg == SDHCI_INT_ENABLE))) { 11303d2bfc8SOlof Johansson /* Erratum: Must enable block gap interrupt detection */ 11403d2bfc8SOlof Johansson u8 gap_ctrl = readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); 11503d2bfc8SOlof Johansson if (val & SDHCI_INT_CARD_INT) 11603d2bfc8SOlof Johansson gap_ctrl |= 0x8; 11703d2bfc8SOlof Johansson else 11803d2bfc8SOlof Johansson gap_ctrl &= ~0x8; 11903d2bfc8SOlof Johansson writeb(gap_ctrl, host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); 12003d2bfc8SOlof Johansson } 12103d2bfc8SOlof Johansson } 12203d2bfc8SOlof Johansson 1233e44a1a7SStephen Warren static unsigned int tegra_sdhci_get_ro(struct sdhci_host *host) 12403d2bfc8SOlof Johansson { 1250aacd23fSJoseph Lo return mmc_gpio_get_ro(host->mmc); 12603d2bfc8SOlof Johansson } 12703d2bfc8SOlof Johansson 12803231f9bSRussell King static void tegra_sdhci_reset(struct sdhci_host *host, u8 mask) 129ca5879d3SPavan Kunapuli { 130ca5879d3SPavan Kunapuli struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 131ca5879d3SPavan Kunapuli struct sdhci_tegra *tegra_host = pltfm_host->priv; 132ca5879d3SPavan Kunapuli const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; 1333145351aSAndrew Bresticker u32 misc_ctrl; 134ca5879d3SPavan Kunapuli 13503231f9bSRussell King sdhci_reset(host, mask); 13603231f9bSRussell King 137ca5879d3SPavan Kunapuli if (!(mask & SDHCI_RESET_ALL)) 138ca5879d3SPavan Kunapuli return; 139ca5879d3SPavan Kunapuli 1403145351aSAndrew Bresticker misc_ctrl = sdhci_readw(host, SDHCI_TEGRA_VENDOR_MISC_CTRL); 141ca5879d3SPavan Kunapuli /* Erratum: Enable SDHCI spec v3.00 support */ 1423145351aSAndrew Bresticker if (soc_data->nvquirks & NVQUIRK_ENABLE_SDHCI_SPEC_300) 143ca5879d3SPavan Kunapuli misc_ctrl |= SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300; 1443145351aSAndrew Bresticker /* Don't advertise UHS modes which aren't supported yet */ 1453145351aSAndrew Bresticker if (soc_data->nvquirks & NVQUIRK_DISABLE_SDR50) 1463145351aSAndrew Bresticker misc_ctrl &= ~SDHCI_MISC_CTRL_ENABLE_SDR50; 1473145351aSAndrew Bresticker if (soc_data->nvquirks & NVQUIRK_DISABLE_DDR50) 1483145351aSAndrew Bresticker misc_ctrl &= ~SDHCI_MISC_CTRL_ENABLE_DDR50; 1493145351aSAndrew Bresticker if (soc_data->nvquirks & NVQUIRK_DISABLE_SDR104) 1503145351aSAndrew Bresticker misc_ctrl &= ~SDHCI_MISC_CTRL_ENABLE_SDR104; 1513145351aSAndrew Bresticker sdhci_writew(host, misc_ctrl, SDHCI_TEGRA_VENDOR_MISC_CTRL); 152ca5879d3SPavan Kunapuli } 153ca5879d3SPavan Kunapuli 1542317f56cSRussell King static void tegra_sdhci_set_bus_width(struct sdhci_host *host, int bus_width) 15503d2bfc8SOlof Johansson { 15603d2bfc8SOlof Johansson u32 ctrl; 15703d2bfc8SOlof Johansson 15803d2bfc8SOlof Johansson ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); 1590aacd23fSJoseph Lo if ((host->mmc->caps & MMC_CAP_8_BIT_DATA) && 1600aacd23fSJoseph Lo (bus_width == MMC_BUS_WIDTH_8)) { 16103d2bfc8SOlof Johansson ctrl &= ~SDHCI_CTRL_4BITBUS; 16203d2bfc8SOlof Johansson ctrl |= SDHCI_CTRL_8BITBUS; 16303d2bfc8SOlof Johansson } else { 16403d2bfc8SOlof Johansson ctrl &= ~SDHCI_CTRL_8BITBUS; 16503d2bfc8SOlof Johansson if (bus_width == MMC_BUS_WIDTH_4) 16603d2bfc8SOlof Johansson ctrl |= SDHCI_CTRL_4BITBUS; 16703d2bfc8SOlof Johansson else 16803d2bfc8SOlof Johansson ctrl &= ~SDHCI_CTRL_4BITBUS; 16903d2bfc8SOlof Johansson } 17003d2bfc8SOlof Johansson sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); 17103d2bfc8SOlof Johansson } 17203d2bfc8SOlof Johansson 173c915568dSLars-Peter Clausen static const struct sdhci_ops tegra_sdhci_ops = { 17485d6509dSShawn Guo .get_ro = tegra_sdhci_get_ro, 17585d6509dSShawn Guo .read_w = tegra_sdhci_readw, 176352ee868SPavan Kunapuli .write_w = tegra_sdhci_writew, 17785d6509dSShawn Guo .write_l = tegra_sdhci_writel, 1781771059cSRussell King .set_clock = sdhci_set_clock, 1792317f56cSRussell King .set_bus_width = tegra_sdhci_set_bus_width, 18003231f9bSRussell King .reset = tegra_sdhci_reset, 18196d7b78cSRussell King .set_uhs_signaling = sdhci_set_uhs_signaling, 182f9260355SAndrew Bresticker .get_max_clock = sdhci_pltfm_clk_get_max_clock, 18385d6509dSShawn Guo }; 18403d2bfc8SOlof Johansson 1851db5eebfSLars-Peter Clausen static const struct sdhci_pltfm_data sdhci_tegra20_pdata = { 18685d6509dSShawn Guo .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | 18785d6509dSShawn Guo SDHCI_QUIRK_SINGLE_POWER_WRITE | 18885d6509dSShawn Guo SDHCI_QUIRK_NO_HISPD_BIT | 189f9260355SAndrew Bresticker SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC | 190f9260355SAndrew Bresticker SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, 19185d6509dSShawn Guo .ops = &tegra_sdhci_ops, 19285d6509dSShawn Guo }; 19385d6509dSShawn Guo 1943e44a1a7SStephen Warren static struct sdhci_tegra_soc_data soc_data_tegra20 = { 1953e44a1a7SStephen Warren .pdata = &sdhci_tegra20_pdata, 1963e44a1a7SStephen Warren .nvquirks = NVQUIRK_FORCE_SDHCI_SPEC_200 | 1973e44a1a7SStephen Warren NVQUIRK_ENABLE_BLOCK_GAP_DET, 1983e44a1a7SStephen Warren }; 1993e44a1a7SStephen Warren 2001db5eebfSLars-Peter Clausen static const struct sdhci_pltfm_data sdhci_tegra30_pdata = { 2013e44a1a7SStephen Warren .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | 2023e44a1a7SStephen Warren SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | 2033e44a1a7SStephen Warren SDHCI_QUIRK_SINGLE_POWER_WRITE | 2043e44a1a7SStephen Warren SDHCI_QUIRK_NO_HISPD_BIT | 205f9260355SAndrew Bresticker SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC | 206f9260355SAndrew Bresticker SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, 2073e44a1a7SStephen Warren .ops = &tegra_sdhci_ops, 2083e44a1a7SStephen Warren }; 2093e44a1a7SStephen Warren 2103e44a1a7SStephen Warren static struct sdhci_tegra_soc_data soc_data_tegra30 = { 2113e44a1a7SStephen Warren .pdata = &sdhci_tegra30_pdata, 2123145351aSAndrew Bresticker .nvquirks = NVQUIRK_ENABLE_SDHCI_SPEC_300 | 2133145351aSAndrew Bresticker NVQUIRK_DISABLE_SDR50 | 2143145351aSAndrew Bresticker NVQUIRK_DISABLE_SDR104, 2153e44a1a7SStephen Warren }; 2163e44a1a7SStephen Warren 2171db5eebfSLars-Peter Clausen static const struct sdhci_pltfm_data sdhci_tegra114_pdata = { 2185ebf2552SRhyland Klein .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | 2195ebf2552SRhyland Klein SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | 2205ebf2552SRhyland Klein SDHCI_QUIRK_SINGLE_POWER_WRITE | 2215ebf2552SRhyland Klein SDHCI_QUIRK_NO_HISPD_BIT | 222f9260355SAndrew Bresticker SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC | 223f9260355SAndrew Bresticker SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, 2245ebf2552SRhyland Klein .ops = &tegra_sdhci_ops, 2255ebf2552SRhyland Klein }; 2265ebf2552SRhyland Klein 2275ebf2552SRhyland Klein static struct sdhci_tegra_soc_data soc_data_tegra114 = { 2285ebf2552SRhyland Klein .pdata = &sdhci_tegra114_pdata, 2293145351aSAndrew Bresticker .nvquirks = NVQUIRK_DISABLE_SDR50 | 2303145351aSAndrew Bresticker NVQUIRK_DISABLE_DDR50 | 231352ee868SPavan Kunapuli NVQUIRK_DISABLE_SDR104 | 232352ee868SPavan Kunapuli NVQUIRK_SHADOW_XFER_MODE_REG, 2335ebf2552SRhyland Klein }; 2345ebf2552SRhyland Klein 235498d83e7SBill Pemberton static const struct of_device_id sdhci_tegra_dt_match[] = { 23667debea3SStephen Warren { .compatible = "nvidia,tegra124-sdhci", .data = &soc_data_tegra114 }, 2375ebf2552SRhyland Klein { .compatible = "nvidia,tegra114-sdhci", .data = &soc_data_tegra114 }, 2383e44a1a7SStephen Warren { .compatible = "nvidia,tegra30-sdhci", .data = &soc_data_tegra30 }, 2393e44a1a7SStephen Warren { .compatible = "nvidia,tegra20-sdhci", .data = &soc_data_tegra20 }, 240275173b2SGrant Likely {} 241275173b2SGrant Likely }; 242e4404fabSArnd Bergmann MODULE_DEVICE_TABLE(of, sdhci_tegra_dt_match); 243275173b2SGrant Likely 24447caa84fSSimon Baatz static int sdhci_tegra_parse_dt(struct device *dev) 245275173b2SGrant Likely { 2460e786102SStephen Warren struct device_node *np = dev->of_node; 2470aacd23fSJoseph Lo struct sdhci_host *host = dev_get_drvdata(dev); 2480aacd23fSJoseph Lo struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 2490aacd23fSJoseph Lo struct sdhci_tegra *tegra_host = pltfm_host->priv; 250275173b2SGrant Likely 2510e786102SStephen Warren tegra_host->power_gpio = of_get_named_gpio(np, "power-gpios", 0); 25247caa84fSSimon Baatz return mmc_of_parse(host->mmc); 253275173b2SGrant Likely } 254275173b2SGrant Likely 255c3be1efdSBill Pemberton static int sdhci_tegra_probe(struct platform_device *pdev) 25603d2bfc8SOlof Johansson { 2573e44a1a7SStephen Warren const struct of_device_id *match; 2583e44a1a7SStephen Warren const struct sdhci_tegra_soc_data *soc_data; 2593e44a1a7SStephen Warren struct sdhci_host *host; 26085d6509dSShawn Guo struct sdhci_pltfm_host *pltfm_host; 2613e44a1a7SStephen Warren struct sdhci_tegra *tegra_host; 26203d2bfc8SOlof Johansson struct clk *clk; 26303d2bfc8SOlof Johansson int rc; 26403d2bfc8SOlof Johansson 2653e44a1a7SStephen Warren match = of_match_device(sdhci_tegra_dt_match, &pdev->dev); 266b37f9d98SJoseph Lo if (!match) 267b37f9d98SJoseph Lo return -EINVAL; 2683e44a1a7SStephen Warren soc_data = match->data; 2693e44a1a7SStephen Warren 2700e748234SChristian Daudt host = sdhci_pltfm_init(pdev, soc_data->pdata, 0); 27185d6509dSShawn Guo if (IS_ERR(host)) 27285d6509dSShawn Guo return PTR_ERR(host); 27385d6509dSShawn Guo pltfm_host = sdhci_priv(host); 27485d6509dSShawn Guo 2753e44a1a7SStephen Warren tegra_host = devm_kzalloc(&pdev->dev, sizeof(*tegra_host), GFP_KERNEL); 2763e44a1a7SStephen Warren if (!tegra_host) { 2773e44a1a7SStephen Warren dev_err(mmc_dev(host->mmc), "failed to allocate tegra_host\n"); 2783e44a1a7SStephen Warren rc = -ENOMEM; 2790e786102SStephen Warren goto err_alloc_tegra_host; 2803e44a1a7SStephen Warren } 2813e44a1a7SStephen Warren tegra_host->soc_data = soc_data; 2823e44a1a7SStephen Warren pltfm_host->priv = tegra_host; 283275173b2SGrant Likely 28447caa84fSSimon Baatz rc = sdhci_tegra_parse_dt(&pdev->dev); 28547caa84fSSimon Baatz if (rc) 28647caa84fSSimon Baatz goto err_parse_dt; 2870e786102SStephen Warren 2880e786102SStephen Warren if (gpio_is_valid(tegra_host->power_gpio)) { 2890e786102SStephen Warren rc = gpio_request(tegra_host->power_gpio, "sdhci_power"); 29003d2bfc8SOlof Johansson if (rc) { 29103d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), 29203d2bfc8SOlof Johansson "failed to allocate power gpio\n"); 29385d6509dSShawn Guo goto err_power_req; 29403d2bfc8SOlof Johansson } 2950e786102SStephen Warren gpio_direction_output(tegra_host->power_gpio, 1); 29603d2bfc8SOlof Johansson } 29703d2bfc8SOlof Johansson 29803d2bfc8SOlof Johansson clk = clk_get(mmc_dev(host->mmc), NULL); 29903d2bfc8SOlof Johansson if (IS_ERR(clk)) { 30003d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), "clk err\n"); 30103d2bfc8SOlof Johansson rc = PTR_ERR(clk); 30285d6509dSShawn Guo goto err_clk_get; 30303d2bfc8SOlof Johansson } 3041e674bc6SPrashant Gaikwad clk_prepare_enable(clk); 30503d2bfc8SOlof Johansson pltfm_host->clk = clk; 30603d2bfc8SOlof Johansson 30785d6509dSShawn Guo rc = sdhci_add_host(host); 30885d6509dSShawn Guo if (rc) 30985d6509dSShawn Guo goto err_add_host; 31085d6509dSShawn Guo 31103d2bfc8SOlof Johansson return 0; 31203d2bfc8SOlof Johansson 31385d6509dSShawn Guo err_add_host: 3141e674bc6SPrashant Gaikwad clk_disable_unprepare(pltfm_host->clk); 31585d6509dSShawn Guo clk_put(pltfm_host->clk); 31685d6509dSShawn Guo err_clk_get: 3170e786102SStephen Warren if (gpio_is_valid(tegra_host->power_gpio)) 3180e786102SStephen Warren gpio_free(tegra_host->power_gpio); 31985d6509dSShawn Guo err_power_req: 32047caa84fSSimon Baatz err_parse_dt: 3210e786102SStephen Warren err_alloc_tegra_host: 32285d6509dSShawn Guo sdhci_pltfm_free(pdev); 32303d2bfc8SOlof Johansson return rc; 32403d2bfc8SOlof Johansson } 32503d2bfc8SOlof Johansson 3266e0ee714SBill Pemberton static int sdhci_tegra_remove(struct platform_device *pdev) 32703d2bfc8SOlof Johansson { 32885d6509dSShawn Guo struct sdhci_host *host = platform_get_drvdata(pdev); 32903d2bfc8SOlof Johansson struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 3303e44a1a7SStephen Warren struct sdhci_tegra *tegra_host = pltfm_host->priv; 33185d6509dSShawn Guo int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); 33285d6509dSShawn Guo 33385d6509dSShawn Guo sdhci_remove_host(host, dead); 33403d2bfc8SOlof Johansson 3350e786102SStephen Warren if (gpio_is_valid(tegra_host->power_gpio)) 3360e786102SStephen Warren gpio_free(tegra_host->power_gpio); 33703d2bfc8SOlof Johansson 3381e674bc6SPrashant Gaikwad clk_disable_unprepare(pltfm_host->clk); 33903d2bfc8SOlof Johansson clk_put(pltfm_host->clk); 34085d6509dSShawn Guo 34185d6509dSShawn Guo sdhci_pltfm_free(pdev); 34285d6509dSShawn Guo 34385d6509dSShawn Guo return 0; 34403d2bfc8SOlof Johansson } 34503d2bfc8SOlof Johansson 34685d6509dSShawn Guo static struct platform_driver sdhci_tegra_driver = { 34785d6509dSShawn Guo .driver = { 34885d6509dSShawn Guo .name = "sdhci-tegra", 349275173b2SGrant Likely .of_match_table = sdhci_tegra_dt_match, 35029495aa0SManuel Lauss .pm = SDHCI_PLTFM_PMOPS, 35185d6509dSShawn Guo }, 35285d6509dSShawn Guo .probe = sdhci_tegra_probe, 3530433c143SBill Pemberton .remove = sdhci_tegra_remove, 35403d2bfc8SOlof Johansson }; 35503d2bfc8SOlof Johansson 356d1f81a64SAxel Lin module_platform_driver(sdhci_tegra_driver); 35785d6509dSShawn Guo 35885d6509dSShawn Guo MODULE_DESCRIPTION("SDHCI driver for Tegra"); 35985d6509dSShawn Guo MODULE_AUTHOR("Google, Inc."); 36085d6509dSShawn Guo MODULE_LICENSE("GPL v2"); 361