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> 1603d2bfc8SOlof Johansson #include <linux/init.h> 1703d2bfc8SOlof Johansson #include <linux/platform_device.h> 1803d2bfc8SOlof Johansson #include <linux/clk.h> 1903d2bfc8SOlof Johansson #include <linux/io.h> 20275173b2SGrant Likely #include <linux/of_gpio.h> 2103d2bfc8SOlof Johansson #include <linux/gpio.h> 2203d2bfc8SOlof Johansson #include <linux/mmc/card.h> 2303d2bfc8SOlof Johansson #include <linux/mmc/host.h> 2403d2bfc8SOlof Johansson 2503d2bfc8SOlof Johansson #include <mach/gpio.h> 2603d2bfc8SOlof Johansson #include <mach/sdhci.h> 2703d2bfc8SOlof Johansson 2803d2bfc8SOlof Johansson #include "sdhci-pltfm.h" 2903d2bfc8SOlof Johansson 3003d2bfc8SOlof Johansson static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg) 3103d2bfc8SOlof Johansson { 3203d2bfc8SOlof Johansson u32 val; 3303d2bfc8SOlof Johansson 3403d2bfc8SOlof Johansson if (unlikely(reg == SDHCI_PRESENT_STATE)) { 3503d2bfc8SOlof Johansson /* Use wp_gpio here instead? */ 3603d2bfc8SOlof Johansson val = readl(host->ioaddr + reg); 3703d2bfc8SOlof Johansson return val | SDHCI_WRITE_PROTECT; 3803d2bfc8SOlof Johansson } 3903d2bfc8SOlof Johansson 4003d2bfc8SOlof Johansson return readl(host->ioaddr + reg); 4103d2bfc8SOlof Johansson } 4203d2bfc8SOlof Johansson 4303d2bfc8SOlof Johansson static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) 4403d2bfc8SOlof Johansson { 4503d2bfc8SOlof Johansson if (unlikely(reg == SDHCI_HOST_VERSION)) { 4603d2bfc8SOlof Johansson /* Erratum: Version register is invalid in HW. */ 4703d2bfc8SOlof Johansson return SDHCI_SPEC_200; 4803d2bfc8SOlof Johansson } 4903d2bfc8SOlof Johansson 5003d2bfc8SOlof Johansson return readw(host->ioaddr + reg); 5103d2bfc8SOlof Johansson } 5203d2bfc8SOlof Johansson 5303d2bfc8SOlof Johansson static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) 5403d2bfc8SOlof Johansson { 5503d2bfc8SOlof Johansson /* Seems like we're getting spurious timeout and crc errors, so 5603d2bfc8SOlof Johansson * disable signalling of them. In case of real errors software 5703d2bfc8SOlof Johansson * timers should take care of eventually detecting them. 5803d2bfc8SOlof Johansson */ 5903d2bfc8SOlof Johansson if (unlikely(reg == SDHCI_SIGNAL_ENABLE)) 6003d2bfc8SOlof Johansson val &= ~(SDHCI_INT_TIMEOUT|SDHCI_INT_CRC); 6103d2bfc8SOlof Johansson 6203d2bfc8SOlof Johansson writel(val, host->ioaddr + reg); 6303d2bfc8SOlof Johansson 6403d2bfc8SOlof Johansson if (unlikely(reg == SDHCI_INT_ENABLE)) { 6503d2bfc8SOlof Johansson /* Erratum: Must enable block gap interrupt detection */ 6603d2bfc8SOlof Johansson u8 gap_ctrl = readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); 6703d2bfc8SOlof Johansson if (val & SDHCI_INT_CARD_INT) 6803d2bfc8SOlof Johansson gap_ctrl |= 0x8; 6903d2bfc8SOlof Johansson else 7003d2bfc8SOlof Johansson gap_ctrl &= ~0x8; 7103d2bfc8SOlof Johansson writeb(gap_ctrl, host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); 7203d2bfc8SOlof Johansson } 7303d2bfc8SOlof Johansson } 7403d2bfc8SOlof Johansson 7503d2bfc8SOlof Johansson static unsigned int tegra_sdhci_get_ro(struct sdhci_host *sdhci) 7603d2bfc8SOlof Johansson { 77275173b2SGrant Likely struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); 78275173b2SGrant Likely struct tegra_sdhci_platform_data *plat = pltfm_host->priv; 7903d2bfc8SOlof Johansson 8003d2bfc8SOlof Johansson if (!gpio_is_valid(plat->wp_gpio)) 8103d2bfc8SOlof Johansson return -1; 8203d2bfc8SOlof Johansson 8303d2bfc8SOlof Johansson return gpio_get_value(plat->wp_gpio); 8403d2bfc8SOlof Johansson } 8503d2bfc8SOlof Johansson 8603d2bfc8SOlof Johansson static irqreturn_t carddetect_irq(int irq, void *data) 8703d2bfc8SOlof Johansson { 8803d2bfc8SOlof Johansson struct sdhci_host *sdhost = (struct sdhci_host *)data; 8903d2bfc8SOlof Johansson 9003d2bfc8SOlof Johansson tasklet_schedule(&sdhost->card_tasklet); 9103d2bfc8SOlof Johansson return IRQ_HANDLED; 9203d2bfc8SOlof Johansson }; 9303d2bfc8SOlof Johansson 9403d2bfc8SOlof Johansson static int tegra_sdhci_8bit(struct sdhci_host *host, int bus_width) 9503d2bfc8SOlof Johansson { 96275173b2SGrant Likely struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 97275173b2SGrant Likely struct tegra_sdhci_platform_data *plat = pltfm_host->priv; 9803d2bfc8SOlof Johansson u32 ctrl; 9903d2bfc8SOlof Johansson 10003d2bfc8SOlof Johansson ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); 10103d2bfc8SOlof Johansson if (plat->is_8bit && bus_width == MMC_BUS_WIDTH_8) { 10203d2bfc8SOlof Johansson ctrl &= ~SDHCI_CTRL_4BITBUS; 10303d2bfc8SOlof Johansson ctrl |= SDHCI_CTRL_8BITBUS; 10403d2bfc8SOlof Johansson } else { 10503d2bfc8SOlof Johansson ctrl &= ~SDHCI_CTRL_8BITBUS; 10603d2bfc8SOlof Johansson if (bus_width == MMC_BUS_WIDTH_4) 10703d2bfc8SOlof Johansson ctrl |= SDHCI_CTRL_4BITBUS; 10803d2bfc8SOlof Johansson else 10903d2bfc8SOlof Johansson ctrl &= ~SDHCI_CTRL_4BITBUS; 11003d2bfc8SOlof Johansson } 11103d2bfc8SOlof Johansson sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); 11203d2bfc8SOlof Johansson return 0; 11303d2bfc8SOlof Johansson } 11403d2bfc8SOlof Johansson 11585d6509dSShawn Guo static struct sdhci_ops tegra_sdhci_ops = { 11685d6509dSShawn Guo .get_ro = tegra_sdhci_get_ro, 11785d6509dSShawn Guo .read_l = tegra_sdhci_readl, 11885d6509dSShawn Guo .read_w = tegra_sdhci_readw, 11985d6509dSShawn Guo .write_l = tegra_sdhci_writel, 12085d6509dSShawn Guo .platform_8bit_width = tegra_sdhci_8bit, 12185d6509dSShawn Guo }; 12203d2bfc8SOlof Johansson 12385d6509dSShawn Guo static struct sdhci_pltfm_data sdhci_tegra_pdata = { 12485d6509dSShawn Guo .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | 12585d6509dSShawn Guo SDHCI_QUIRK_SINGLE_POWER_WRITE | 12685d6509dSShawn Guo SDHCI_QUIRK_NO_HISPD_BIT | 12785d6509dSShawn Guo SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, 12885d6509dSShawn Guo .ops = &tegra_sdhci_ops, 12985d6509dSShawn Guo }; 13085d6509dSShawn Guo 131275173b2SGrant Likely static const struct of_device_id sdhci_tegra_dt_match[] __devinitdata = { 132275173b2SGrant Likely { .compatible = "nvidia,tegra20-sdhci", }, 133275173b2SGrant Likely {} 134275173b2SGrant Likely }; 135275173b2SGrant Likely MODULE_DEVICE_TABLE(of, sdhci_dt_ids); 136275173b2SGrant Likely 137275173b2SGrant Likely static struct tegra_sdhci_platform_data * __devinit sdhci_tegra_dt_parse_pdata( 138275173b2SGrant Likely struct platform_device *pdev) 139275173b2SGrant Likely { 140275173b2SGrant Likely struct tegra_sdhci_platform_data *plat; 141275173b2SGrant Likely struct device_node *np = pdev->dev.of_node; 142275173b2SGrant Likely 143275173b2SGrant Likely if (!np) 144275173b2SGrant Likely return NULL; 145275173b2SGrant Likely 146275173b2SGrant Likely plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL); 147275173b2SGrant Likely if (!plat) { 148275173b2SGrant Likely dev_err(&pdev->dev, "Can't allocate platform data\n"); 149275173b2SGrant Likely return NULL; 150275173b2SGrant Likely } 151275173b2SGrant Likely 152275173b2SGrant Likely plat->cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); 153275173b2SGrant Likely plat->wp_gpio = of_get_named_gpio(np, "wp-gpios", 0); 154275173b2SGrant Likely plat->power_gpio = of_get_named_gpio(np, "power-gpios", 0); 155275173b2SGrant Likely 156275173b2SGrant Likely return plat; 157275173b2SGrant Likely } 158275173b2SGrant Likely 15985d6509dSShawn Guo static int __devinit sdhci_tegra_probe(struct platform_device *pdev) 16003d2bfc8SOlof Johansson { 16185d6509dSShawn Guo struct sdhci_pltfm_host *pltfm_host; 16203d2bfc8SOlof Johansson struct tegra_sdhci_platform_data *plat; 16385d6509dSShawn Guo struct sdhci_host *host; 16403d2bfc8SOlof Johansson struct clk *clk; 16503d2bfc8SOlof Johansson int rc; 16603d2bfc8SOlof Johansson 16785d6509dSShawn Guo host = sdhci_pltfm_init(pdev, &sdhci_tegra_pdata); 16885d6509dSShawn Guo if (IS_ERR(host)) 16985d6509dSShawn Guo return PTR_ERR(host); 17085d6509dSShawn Guo 17185d6509dSShawn Guo pltfm_host = sdhci_priv(host); 17285d6509dSShawn Guo 17303d2bfc8SOlof Johansson plat = pdev->dev.platform_data; 17485d6509dSShawn Guo 175275173b2SGrant Likely if (plat == NULL) 176275173b2SGrant Likely plat = sdhci_tegra_dt_parse_pdata(pdev); 177275173b2SGrant Likely 17803d2bfc8SOlof Johansson if (plat == NULL) { 17903d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), "missing platform data\n"); 18085d6509dSShawn Guo rc = -ENXIO; 18185d6509dSShawn Guo goto err_no_plat; 18203d2bfc8SOlof Johansson } 18303d2bfc8SOlof Johansson 184275173b2SGrant Likely pltfm_host->priv = plat; 185275173b2SGrant Likely 18603d2bfc8SOlof Johansson if (gpio_is_valid(plat->power_gpio)) { 18703d2bfc8SOlof Johansson rc = gpio_request(plat->power_gpio, "sdhci_power"); 18803d2bfc8SOlof Johansson if (rc) { 18903d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), 19003d2bfc8SOlof Johansson "failed to allocate power gpio\n"); 19185d6509dSShawn Guo goto err_power_req; 19203d2bfc8SOlof Johansson } 19303d2bfc8SOlof Johansson tegra_gpio_enable(plat->power_gpio); 19403d2bfc8SOlof Johansson gpio_direction_output(plat->power_gpio, 1); 19503d2bfc8SOlof Johansson } 19603d2bfc8SOlof Johansson 19703d2bfc8SOlof Johansson if (gpio_is_valid(plat->cd_gpio)) { 19803d2bfc8SOlof Johansson rc = gpio_request(plat->cd_gpio, "sdhci_cd"); 19903d2bfc8SOlof Johansson if (rc) { 20003d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), 20103d2bfc8SOlof Johansson "failed to allocate cd gpio\n"); 20285d6509dSShawn Guo goto err_cd_req; 20303d2bfc8SOlof Johansson } 20403d2bfc8SOlof Johansson tegra_gpio_enable(plat->cd_gpio); 20503d2bfc8SOlof Johansson gpio_direction_input(plat->cd_gpio); 20603d2bfc8SOlof Johansson 20703d2bfc8SOlof Johansson rc = request_irq(gpio_to_irq(plat->cd_gpio), carddetect_irq, 20803d2bfc8SOlof Johansson IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 20903d2bfc8SOlof Johansson mmc_hostname(host->mmc), host); 21003d2bfc8SOlof Johansson 21103d2bfc8SOlof Johansson if (rc) { 21203d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), "request irq error\n"); 21385d6509dSShawn Guo goto err_cd_irq_req; 21403d2bfc8SOlof Johansson } 21503d2bfc8SOlof Johansson 21603d2bfc8SOlof Johansson } 21703d2bfc8SOlof Johansson 21803d2bfc8SOlof Johansson if (gpio_is_valid(plat->wp_gpio)) { 21903d2bfc8SOlof Johansson rc = gpio_request(plat->wp_gpio, "sdhci_wp"); 22003d2bfc8SOlof Johansson if (rc) { 22103d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), 22203d2bfc8SOlof Johansson "failed to allocate wp gpio\n"); 22385d6509dSShawn Guo goto err_wp_req; 22403d2bfc8SOlof Johansson } 22503d2bfc8SOlof Johansson tegra_gpio_enable(plat->wp_gpio); 22603d2bfc8SOlof Johansson gpio_direction_input(plat->wp_gpio); 22703d2bfc8SOlof Johansson } 22803d2bfc8SOlof Johansson 22903d2bfc8SOlof Johansson clk = clk_get(mmc_dev(host->mmc), NULL); 23003d2bfc8SOlof Johansson if (IS_ERR(clk)) { 23103d2bfc8SOlof Johansson dev_err(mmc_dev(host->mmc), "clk err\n"); 23203d2bfc8SOlof Johansson rc = PTR_ERR(clk); 23385d6509dSShawn Guo goto err_clk_get; 23403d2bfc8SOlof Johansson } 23503d2bfc8SOlof Johansson clk_enable(clk); 23603d2bfc8SOlof Johansson pltfm_host->clk = clk; 23703d2bfc8SOlof Johansson 238c7f409e3SVenkat Rao host->mmc->pm_caps = plat->pm_flags; 239c7f409e3SVenkat Rao 24003d2bfc8SOlof Johansson if (plat->is_8bit) 24103d2bfc8SOlof Johansson host->mmc->caps |= MMC_CAP_8_BIT_DATA; 24203d2bfc8SOlof Johansson 24385d6509dSShawn Guo rc = sdhci_add_host(host); 24485d6509dSShawn Guo if (rc) 24585d6509dSShawn Guo goto err_add_host; 24685d6509dSShawn Guo 24703d2bfc8SOlof Johansson return 0; 24803d2bfc8SOlof Johansson 24985d6509dSShawn Guo err_add_host: 25085d6509dSShawn Guo clk_disable(pltfm_host->clk); 25185d6509dSShawn Guo clk_put(pltfm_host->clk); 25285d6509dSShawn Guo err_clk_get: 25303d2bfc8SOlof Johansson if (gpio_is_valid(plat->wp_gpio)) { 25403d2bfc8SOlof Johansson tegra_gpio_disable(plat->wp_gpio); 25503d2bfc8SOlof Johansson gpio_free(plat->wp_gpio); 25603d2bfc8SOlof Johansson } 25785d6509dSShawn Guo err_wp_req: 2588154b575SWolfram Sang if (gpio_is_valid(plat->cd_gpio)) 2598154b575SWolfram Sang free_irq(gpio_to_irq(plat->cd_gpio), host); 26085d6509dSShawn Guo err_cd_irq_req: 26103d2bfc8SOlof Johansson if (gpio_is_valid(plat->cd_gpio)) { 26203d2bfc8SOlof Johansson tegra_gpio_disable(plat->cd_gpio); 26303d2bfc8SOlof Johansson gpio_free(plat->cd_gpio); 26403d2bfc8SOlof Johansson } 26585d6509dSShawn Guo err_cd_req: 26603d2bfc8SOlof Johansson if (gpio_is_valid(plat->power_gpio)) { 26703d2bfc8SOlof Johansson tegra_gpio_disable(plat->power_gpio); 26803d2bfc8SOlof Johansson gpio_free(plat->power_gpio); 26903d2bfc8SOlof Johansson } 27085d6509dSShawn Guo err_power_req: 27185d6509dSShawn Guo err_no_plat: 27285d6509dSShawn Guo sdhci_pltfm_free(pdev); 27303d2bfc8SOlof Johansson return rc; 27403d2bfc8SOlof Johansson } 27503d2bfc8SOlof Johansson 27685d6509dSShawn Guo static int __devexit sdhci_tegra_remove(struct platform_device *pdev) 27703d2bfc8SOlof Johansson { 27885d6509dSShawn Guo struct sdhci_host *host = platform_get_drvdata(pdev); 27903d2bfc8SOlof Johansson struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 280275173b2SGrant Likely struct tegra_sdhci_platform_data *plat = pltfm_host->priv; 28185d6509dSShawn Guo int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); 28285d6509dSShawn Guo 28385d6509dSShawn Guo sdhci_remove_host(host, dead); 28403d2bfc8SOlof Johansson 28503d2bfc8SOlof Johansson if (gpio_is_valid(plat->wp_gpio)) { 28603d2bfc8SOlof Johansson tegra_gpio_disable(plat->wp_gpio); 28703d2bfc8SOlof Johansson gpio_free(plat->wp_gpio); 28803d2bfc8SOlof Johansson } 28903d2bfc8SOlof Johansson 29003d2bfc8SOlof Johansson if (gpio_is_valid(plat->cd_gpio)) { 2918154b575SWolfram Sang free_irq(gpio_to_irq(plat->cd_gpio), host); 29203d2bfc8SOlof Johansson tegra_gpio_disable(plat->cd_gpio); 29303d2bfc8SOlof Johansson gpio_free(plat->cd_gpio); 29403d2bfc8SOlof Johansson } 29503d2bfc8SOlof Johansson 29603d2bfc8SOlof Johansson if (gpio_is_valid(plat->power_gpio)) { 29703d2bfc8SOlof Johansson tegra_gpio_disable(plat->power_gpio); 29803d2bfc8SOlof Johansson gpio_free(plat->power_gpio); 29903d2bfc8SOlof Johansson } 30003d2bfc8SOlof Johansson 30103d2bfc8SOlof Johansson clk_disable(pltfm_host->clk); 30203d2bfc8SOlof Johansson clk_put(pltfm_host->clk); 30385d6509dSShawn Guo 30485d6509dSShawn Guo sdhci_pltfm_free(pdev); 30585d6509dSShawn Guo 30685d6509dSShawn Guo return 0; 30703d2bfc8SOlof Johansson } 30803d2bfc8SOlof Johansson 30985d6509dSShawn Guo static struct platform_driver sdhci_tegra_driver = { 31085d6509dSShawn Guo .driver = { 31185d6509dSShawn Guo .name = "sdhci-tegra", 31285d6509dSShawn Guo .owner = THIS_MODULE, 313275173b2SGrant Likely .of_match_table = sdhci_tegra_dt_match, 31485d6509dSShawn Guo }, 31585d6509dSShawn Guo .probe = sdhci_tegra_probe, 31685d6509dSShawn Guo .remove = __devexit_p(sdhci_tegra_remove), 31785d6509dSShawn Guo #ifdef CONFIG_PM 31885d6509dSShawn Guo .suspend = sdhci_pltfm_suspend, 31985d6509dSShawn Guo .resume = sdhci_pltfm_resume, 32085d6509dSShawn Guo #endif 32103d2bfc8SOlof Johansson }; 32203d2bfc8SOlof Johansson 32385d6509dSShawn Guo static int __init sdhci_tegra_init(void) 32485d6509dSShawn Guo { 32585d6509dSShawn Guo return platform_driver_register(&sdhci_tegra_driver); 32685d6509dSShawn Guo } 32785d6509dSShawn Guo module_init(sdhci_tegra_init); 32885d6509dSShawn Guo 32985d6509dSShawn Guo static void __exit sdhci_tegra_exit(void) 33085d6509dSShawn Guo { 33185d6509dSShawn Guo platform_driver_unregister(&sdhci_tegra_driver); 33285d6509dSShawn Guo } 33385d6509dSShawn Guo module_exit(sdhci_tegra_exit); 33485d6509dSShawn Guo 33585d6509dSShawn Guo MODULE_DESCRIPTION("SDHCI driver for Tegra"); 33685d6509dSShawn Guo MODULE_AUTHOR(" Google, Inc."); 33785d6509dSShawn Guo MODULE_LICENSE("GPL v2"); 338