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 29e6b750d4SRussell King #include <asm/gpio.h> 30ea5abbd2SStephen Warren 3103d2bfc8SOlof Johansson #include "sdhci-pltfm.h" 3203d2bfc8SOlof Johansson 33ca5879d3SPavan Kunapuli /* Tegra SDHOST controller vendor register definitions */ 34ca5879d3SPavan Kunapuli #define SDHCI_TEGRA_VENDOR_MISC_CTRL 0x120 35ca5879d3SPavan Kunapuli #define SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300 0x20 36ca5879d3SPavan Kunapuli 373e44a1a7SStephen Warren #define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0) 383e44a1a7SStephen Warren #define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1) 39ca5879d3SPavan Kunapuli #define NVQUIRK_ENABLE_SDHCI_SPEC_300 BIT(2) 403e44a1a7SStephen Warren 413e44a1a7SStephen Warren struct sdhci_tegra_soc_data { 423e44a1a7SStephen Warren struct sdhci_pltfm_data *pdata; 433e44a1a7SStephen Warren u32 nvquirks; 443e44a1a7SStephen Warren }; 453e44a1a7SStephen Warren 463e44a1a7SStephen Warren struct sdhci_tegra { 473e44a1a7SStephen Warren const struct sdhci_tegra_soc_data *soc_data; 480e786102SStephen Warren int power_gpio; 493e44a1a7SStephen Warren }; 503e44a1a7SStephen Warren 5103d2bfc8SOlof Johansson static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) 5203d2bfc8SOlof Johansson { 5303d2bfc8SOlof Johansson u32 val; 5403d2bfc8SOlof Johansson 5503d2bfc8SOlof Johansson if (unlikely(reg == SDHCI_PRESENT_STATE)) { 5603d2bfc8SOlof Johansson /* Use wp_gpio here instead? */ 5703d2bfc8SOlof Johansson val = readl(host->ioaddr + reg); 5803d2bfc8SOlof Johansson return val | SDHCI_WRITE_PROTECT; 5903d2bfc8SOlof Johansson } 6003d2bfc8SOlof Johansson 6103d2bfc8SOlof Johansson return readl(host->ioaddr + reg); 6203d2bfc8SOlof Johansson } 6303d2bfc8SOlof Johansson 6403d2bfc8SOlof Johansson static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) 6503d2bfc8SOlof Johansson { 663e44a1a7SStephen Warren struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 673e44a1a7SStephen Warren struct sdhci_tegra *tegra_host = pltfm_host->priv; 683e44a1a7SStephen Warren const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; 693e44a1a7SStephen Warren 703e44a1a7SStephen Warren if (unlikely((soc_data->nvquirks & NVQUIRK_FORCE_SDHCI_SPEC_200) && 713e44a1a7SStephen Warren (reg == SDHCI_HOST_VERSION))) { 7203d2bfc8SOlof Johansson /* Erratum: Version register is invalid in HW. */ 7303d2bfc8SOlof Johansson return SDHCI_SPEC_200; 7403d2bfc8SOlof Johansson } 7503d2bfc8SOlof Johansson 7603d2bfc8SOlof Johansson return readw(host->ioaddr + reg); 7703d2bfc8SOlof Johansson } 7803d2bfc8SOlof Johansson 7903d2bfc8SOlof Johansson static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) 8003d2bfc8SOlof Johansson { 813e44a1a7SStephen Warren struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 823e44a1a7SStephen Warren struct sdhci_tegra *tegra_host = pltfm_host->priv; 833e44a1a7SStephen Warren const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; 843e44a1a7SStephen Warren 8503d2bfc8SOlof Johansson /* Seems like we're getting spurious timeout and crc errors, so 8603d2bfc8SOlof Johansson * disable signalling of them. In case of real errors software 8703d2bfc8SOlof Johansson * timers should take care of eventually detecting them. 8803d2bfc8SOlof Johansson */ 8903d2bfc8SOlof Johansson if (unlikely(reg == SDHCI_SIGNAL_ENABLE)) 9003d2bfc8SOlof Johansson val &= ~(SDHCI_INT_TIMEOUT|SDHCI_INT_CRC); 9103d2bfc8SOlof Johansson 9203d2bfc8SOlof Johansson writel(val, host->ioaddr + reg); 9303d2bfc8SOlof Johansson 943e44a1a7SStephen Warren if (unlikely((soc_data->nvquirks & NVQUIRK_ENABLE_BLOCK_GAP_DET) && 953e44a1a7SStephen Warren (reg == SDHCI_INT_ENABLE))) { 9603d2bfc8SOlof Johansson /* Erratum: Must enable block gap interrupt detection */ 9703d2bfc8SOlof Johansson u8 gap_ctrl = readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); 9803d2bfc8SOlof Johansson if (val & SDHCI_INT_CARD_INT) 9903d2bfc8SOlof Johansson gap_ctrl |= 0x8; 10003d2bfc8SOlof Johansson else 10103d2bfc8SOlof Johansson gap_ctrl &= ~0x8; 10203d2bfc8SOlof Johansson writeb(gap_ctrl, host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); 10303d2bfc8SOlof Johansson } 10403d2bfc8SOlof Johansson } 10503d2bfc8SOlof Johansson 1063e44a1a7SStephen Warren static unsigned int tegra_sdhci_get_ro(struct sdhci_host *host) 10703d2bfc8SOlof Johansson { 1080aacd23fSJoseph Lo return mmc_gpio_get_ro(host->mmc); 10903d2bfc8SOlof Johansson } 11003d2bfc8SOlof Johansson 111ca5879d3SPavan Kunapuli static void tegra_sdhci_reset_exit(struct sdhci_host *host, u8 mask) 112ca5879d3SPavan Kunapuli { 113ca5879d3SPavan Kunapuli struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 114ca5879d3SPavan Kunapuli struct sdhci_tegra *tegra_host = pltfm_host->priv; 115ca5879d3SPavan Kunapuli const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; 116ca5879d3SPavan Kunapuli 117ca5879d3SPavan Kunapuli if (!(mask & SDHCI_RESET_ALL)) 118ca5879d3SPavan Kunapuli return; 119ca5879d3SPavan Kunapuli 120ca5879d3SPavan Kunapuli /* Erratum: Enable SDHCI spec v3.00 support */ 121ca5879d3SPavan Kunapuli if (soc_data->nvquirks & NVQUIRK_ENABLE_SDHCI_SPEC_300) { 122ca5879d3SPavan Kunapuli u32 misc_ctrl; 123ca5879d3SPavan Kunapuli 124ca5879d3SPavan Kunapuli misc_ctrl = sdhci_readb(host, SDHCI_TEGRA_VENDOR_MISC_CTRL); 125ca5879d3SPavan Kunapuli misc_ctrl |= SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300; 126ca5879d3SPavan Kunapuli sdhci_writeb(host, misc_ctrl, SDHCI_TEGRA_VENDOR_MISC_CTRL); 127ca5879d3SPavan Kunapuli } 128ca5879d3SPavan Kunapuli } 129ca5879d3SPavan Kunapuli 1307bc088d3SSascha Hauer static int tegra_sdhci_buswidth(struct sdhci_host *host, int bus_width) 13103d2bfc8SOlof Johansson { 13203d2bfc8SOlof Johansson u32 ctrl; 13303d2bfc8SOlof Johansson 13403d2bfc8SOlof Johansson ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); 1350aacd23fSJoseph Lo if ((host->mmc->caps & MMC_CAP_8_BIT_DATA) && 1360aacd23fSJoseph Lo (bus_width == MMC_BUS_WIDTH_8)) { 13703d2bfc8SOlof Johansson ctrl &= ~SDHCI_CTRL_4BITBUS; 13803d2bfc8SOlof Johansson ctrl |= SDHCI_CTRL_8BITBUS; 13903d2bfc8SOlof Johansson } else { 14003d2bfc8SOlof Johansson ctrl &= ~SDHCI_CTRL_8BITBUS; 14103d2bfc8SOlof Johansson if (bus_width == MMC_BUS_WIDTH_4) 14203d2bfc8SOlof Johansson ctrl |= SDHCI_CTRL_4BITBUS; 14303d2bfc8SOlof Johansson else 14403d2bfc8SOlof Johansson ctrl &= ~SDHCI_CTRL_4BITBUS; 14503d2bfc8SOlof Johansson } 14603d2bfc8SOlof Johansson sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); 14703d2bfc8SOlof Johansson return 0; 14803d2bfc8SOlof Johansson } 14903d2bfc8SOlof Johansson 15085d6509dSShawn Guo static struct sdhci_ops tegra_sdhci_ops = { 15185d6509dSShawn Guo .get_ro = tegra_sdhci_get_ro, 15285d6509dSShawn Guo .read_l = tegra_sdhci_readl, 15385d6509dSShawn Guo .read_w = tegra_sdhci_readw, 15485d6509dSShawn Guo .write_l = tegra_sdhci_writel, 1557bc088d3SSascha Hauer .platform_bus_width = tegra_sdhci_buswidth, 156ca5879d3SPavan Kunapuli .platform_reset_exit = tegra_sdhci_reset_exit, 15785d6509dSShawn Guo }; 15803d2bfc8SOlof Johansson 1593e44a1a7SStephen Warren static struct sdhci_pltfm_data sdhci_tegra20_pdata = { 16085d6509dSShawn Guo .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | 16185d6509dSShawn Guo SDHCI_QUIRK_SINGLE_POWER_WRITE | 16285d6509dSShawn Guo SDHCI_QUIRK_NO_HISPD_BIT | 16385d6509dSShawn Guo SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, 16485d6509dSShawn Guo .ops = &tegra_sdhci_ops, 16585d6509dSShawn Guo }; 16685d6509dSShawn Guo 1673e44a1a7SStephen Warren static struct sdhci_tegra_soc_data soc_data_tegra20 = { 1683e44a1a7SStephen Warren .pdata = &sdhci_tegra20_pdata, 1693e44a1a7SStephen Warren .nvquirks = NVQUIRK_FORCE_SDHCI_SPEC_200 | 1703e44a1a7SStephen Warren NVQUIRK_ENABLE_BLOCK_GAP_DET, 1713e44a1a7SStephen Warren }; 1723e44a1a7SStephen Warren 1733e44a1a7SStephen Warren static struct sdhci_pltfm_data sdhci_tegra30_pdata = { 1743e44a1a7SStephen Warren .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | 1753e44a1a7SStephen Warren SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | 1763e44a1a7SStephen Warren SDHCI_QUIRK_SINGLE_POWER_WRITE | 1773e44a1a7SStephen Warren SDHCI_QUIRK_NO_HISPD_BIT | 1783e44a1a7SStephen Warren SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, 1793e44a1a7SStephen Warren .ops = &tegra_sdhci_ops, 1803e44a1a7SStephen Warren }; 1813e44a1a7SStephen Warren 1823e44a1a7SStephen Warren static struct sdhci_tegra_soc_data soc_data_tegra30 = { 1833e44a1a7SStephen Warren .pdata = &sdhci_tegra30_pdata, 184ca5879d3SPavan Kunapuli .nvquirks = NVQUIRK_ENABLE_SDHCI_SPEC_300, 1853e44a1a7SStephen Warren }; 1863e44a1a7SStephen Warren 1875ebf2552SRhyland Klein static struct sdhci_pltfm_data sdhci_tegra114_pdata = { 1885ebf2552SRhyland Klein .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | 1895ebf2552SRhyland Klein SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | 1905ebf2552SRhyland Klein SDHCI_QUIRK_SINGLE_POWER_WRITE | 1915ebf2552SRhyland Klein SDHCI_QUIRK_NO_HISPD_BIT | 1925ebf2552SRhyland Klein SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, 1935ebf2552SRhyland Klein .ops = &tegra_sdhci_ops, 1945ebf2552SRhyland Klein }; 1955ebf2552SRhyland Klein 1965ebf2552SRhyland Klein static struct sdhci_tegra_soc_data soc_data_tegra114 = { 1975ebf2552SRhyland Klein .pdata = &sdhci_tegra114_pdata, 1985ebf2552SRhyland Klein }; 1995ebf2552SRhyland Klein 200498d83e7SBill Pemberton static const struct of_device_id sdhci_tegra_dt_match[] = { 2015ebf2552SRhyland Klein { .compatible = "nvidia,tegra114-sdhci", .data = &soc_data_tegra114 }, 2023e44a1a7SStephen Warren { .compatible = "nvidia,tegra30-sdhci", .data = &soc_data_tegra30 }, 2033e44a1a7SStephen Warren { .compatible = "nvidia,tegra20-sdhci", .data = &soc_data_tegra20 }, 204275173b2SGrant Likely {} 205275173b2SGrant Likely }; 206275173b2SGrant Likely MODULE_DEVICE_TABLE(of, sdhci_dt_ids); 207275173b2SGrant Likely 2080aacd23fSJoseph Lo static void sdhci_tegra_parse_dt(struct device *dev) 209275173b2SGrant Likely { 2100e786102SStephen Warren struct device_node *np = dev->of_node; 2110aacd23fSJoseph Lo struct sdhci_host *host = dev_get_drvdata(dev); 2120aacd23fSJoseph Lo struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 2130aacd23fSJoseph Lo struct sdhci_tegra *tegra_host = pltfm_host->priv; 214275173b2SGrant Likely 2150e786102SStephen Warren tegra_host->power_gpio = of_get_named_gpio(np, "power-gpios", 0); 2160aacd23fSJoseph Lo mmc_of_parse(host->mmc); 217275173b2SGrant Likely } 218275173b2SGrant Likely 219c3be1efdSBill Pemberton static int sdhci_tegra_probe(struct platform_device *pdev) 22003d2bfc8SOlof Johansson { 2213e44a1a7SStephen Warren const struct of_device_id *match; 2223e44a1a7SStephen Warren const struct sdhci_tegra_soc_data *soc_data; 2233e44a1a7SStephen Warren struct sdhci_host *host; 22485d6509dSShawn Guo struct sdhci_pltfm_host *pltfm_host; 2253e44a1a7SStephen Warren struct sdhci_tegra *tegra_host; 22603d2bfc8SOlof Johansson struct clk *clk; 22703d2bfc8SOlof Johansson int rc; 22803d2bfc8SOlof Johansson 2293e44a1a7SStephen Warren match = of_match_device(sdhci_tegra_dt_match, &pdev->dev); 230b37f9d98SJoseph Lo if (!match) 231b37f9d98SJoseph Lo return -EINVAL; 2323e44a1a7SStephen Warren soc_data = match->data; 2333e44a1a7SStephen Warren 2343e44a1a7SStephen Warren host = sdhci_pltfm_init(pdev, soc_data->pdata); 23585d6509dSShawn Guo if (IS_ERR(host)) 23685d6509dSShawn Guo return PTR_ERR(host); 23785d6509dSShawn Guo pltfm_host = sdhci_priv(host); 23885d6509dSShawn Guo 2393e44a1a7SStephen Warren tegra_host = devm_kzalloc(&pdev->dev, sizeof(*tegra_host), GFP_KERNEL); 2403e44a1a7SStephen Warren if (!tegra_host) { 2413e44a1a7SStephen Warren dev_err(mmc_dev(host->mmc), "failed to allocate tegra_host\n"); 2423e44a1a7SStephen Warren rc = -ENOMEM; 2430e786102SStephen Warren goto err_alloc_tegra_host; 2443e44a1a7SStephen Warren } 2453e44a1a7SStephen Warren tegra_host->soc_data = soc_data; 2463e44a1a7SStephen Warren pltfm_host->priv = tegra_host; 247275173b2SGrant Likely 2480aacd23fSJoseph Lo sdhci_tegra_parse_dt(&pdev->dev); 2490e786102SStephen Warren 2500e786102SStephen Warren if (gpio_is_valid(tegra_host->power_gpio)) { 2510e786102SStephen Warren rc = gpio_request(tegra_host->power_gpio, "sdhci_power"); 25203d2bfc8SOlof Johansson if (rc) { 25303d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), 25403d2bfc8SOlof Johansson "failed to allocate power gpio\n"); 25585d6509dSShawn Guo goto err_power_req; 25603d2bfc8SOlof Johansson } 2570e786102SStephen Warren gpio_direction_output(tegra_host->power_gpio, 1); 25803d2bfc8SOlof Johansson } 25903d2bfc8SOlof Johansson 26003d2bfc8SOlof Johansson clk = clk_get(mmc_dev(host->mmc), NULL); 26103d2bfc8SOlof Johansson if (IS_ERR(clk)) { 26203d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), "clk err\n"); 26303d2bfc8SOlof Johansson rc = PTR_ERR(clk); 26485d6509dSShawn Guo goto err_clk_get; 26503d2bfc8SOlof Johansson } 2661e674bc6SPrashant Gaikwad clk_prepare_enable(clk); 26703d2bfc8SOlof Johansson pltfm_host->clk = clk; 26803d2bfc8SOlof Johansson 26985d6509dSShawn Guo rc = sdhci_add_host(host); 27085d6509dSShawn Guo if (rc) 27185d6509dSShawn Guo goto err_add_host; 27285d6509dSShawn Guo 27303d2bfc8SOlof Johansson return 0; 27403d2bfc8SOlof Johansson 27585d6509dSShawn Guo err_add_host: 2761e674bc6SPrashant Gaikwad clk_disable_unprepare(pltfm_host->clk); 27785d6509dSShawn Guo clk_put(pltfm_host->clk); 27885d6509dSShawn Guo err_clk_get: 2790e786102SStephen Warren if (gpio_is_valid(tegra_host->power_gpio)) 2800e786102SStephen Warren gpio_free(tegra_host->power_gpio); 28185d6509dSShawn Guo err_power_req: 2820e786102SStephen Warren err_alloc_tegra_host: 28385d6509dSShawn Guo sdhci_pltfm_free(pdev); 28403d2bfc8SOlof Johansson return rc; 28503d2bfc8SOlof Johansson } 28603d2bfc8SOlof Johansson 2876e0ee714SBill Pemberton static int sdhci_tegra_remove(struct platform_device *pdev) 28803d2bfc8SOlof Johansson { 28985d6509dSShawn Guo struct sdhci_host *host = platform_get_drvdata(pdev); 29003d2bfc8SOlof Johansson struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 2913e44a1a7SStephen Warren struct sdhci_tegra *tegra_host = pltfm_host->priv; 29285d6509dSShawn Guo int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); 29385d6509dSShawn Guo 29485d6509dSShawn Guo sdhci_remove_host(host, dead); 29503d2bfc8SOlof Johansson 2960e786102SStephen Warren if (gpio_is_valid(tegra_host->power_gpio)) 2970e786102SStephen Warren gpio_free(tegra_host->power_gpio); 29803d2bfc8SOlof Johansson 2991e674bc6SPrashant Gaikwad clk_disable_unprepare(pltfm_host->clk); 30003d2bfc8SOlof Johansson clk_put(pltfm_host->clk); 30185d6509dSShawn Guo 30285d6509dSShawn Guo sdhci_pltfm_free(pdev); 30385d6509dSShawn Guo 30485d6509dSShawn Guo return 0; 30503d2bfc8SOlof Johansson } 30603d2bfc8SOlof Johansson 30785d6509dSShawn Guo static struct platform_driver sdhci_tegra_driver = { 30885d6509dSShawn Guo .driver = { 30985d6509dSShawn Guo .name = "sdhci-tegra", 31085d6509dSShawn Guo .owner = THIS_MODULE, 311275173b2SGrant Likely .of_match_table = sdhci_tegra_dt_match, 31229495aa0SManuel Lauss .pm = SDHCI_PLTFM_PMOPS, 31385d6509dSShawn Guo }, 31485d6509dSShawn Guo .probe = sdhci_tegra_probe, 3150433c143SBill Pemberton .remove = sdhci_tegra_remove, 31603d2bfc8SOlof Johansson }; 31703d2bfc8SOlof Johansson 318d1f81a64SAxel Lin module_platform_driver(sdhci_tegra_driver); 31985d6509dSShawn Guo 32085d6509dSShawn Guo MODULE_DESCRIPTION("SDHCI driver for Tegra"); 32185d6509dSShawn Guo MODULE_AUTHOR("Google, Inc."); 32285d6509dSShawn Guo MODULE_LICENSE("GPL v2"); 323