1c63b3cbaSViresh KUMAR /* 2c63b3cbaSViresh KUMAR * drivers/mmc/host/sdhci-spear.c 3c63b3cbaSViresh KUMAR * 4c63b3cbaSViresh KUMAR * Support of SDHCI platform devices for spear soc family 5c63b3cbaSViresh KUMAR * 6c63b3cbaSViresh KUMAR * Copyright (C) 2010 ST Microelectronics 710d8935fSViresh Kumar * Viresh Kumar <viresh.linux@gmail.com> 8c63b3cbaSViresh KUMAR * 9c63b3cbaSViresh KUMAR * Inspired by sdhci-pltfm.c 10c63b3cbaSViresh KUMAR * 11c63b3cbaSViresh KUMAR * This file is licensed under the terms of the GNU General Public 12c63b3cbaSViresh KUMAR * License version 2. This program is licensed "as is" without any 13c63b3cbaSViresh KUMAR * warranty of any kind, whether express or implied. 14c63b3cbaSViresh KUMAR */ 15c63b3cbaSViresh KUMAR 16c63b3cbaSViresh KUMAR #include <linux/clk.h> 17c63b3cbaSViresh KUMAR #include <linux/delay.h> 18c63b3cbaSViresh KUMAR #include <linux/gpio.h> 19c63b3cbaSViresh KUMAR #include <linux/highmem.h> 2088b47679SPaul Gortmaker #include <linux/module.h> 21c63b3cbaSViresh KUMAR #include <linux/interrupt.h> 22c63b3cbaSViresh KUMAR #include <linux/irq.h> 23067bf748SViresh Kumar #include <linux/of.h> 24067bf748SViresh Kumar #include <linux/of_gpio.h> 25c63b3cbaSViresh KUMAR #include <linux/platform_device.h> 26b70a7fabSViresh Kumar #include <linux/pm.h> 27c63b3cbaSViresh KUMAR #include <linux/slab.h> 28c63b3cbaSViresh KUMAR #include <linux/mmc/host.h> 29c63b3cbaSViresh KUMAR #include <linux/mmc/sdhci-spear.h> 30c63b3cbaSViresh KUMAR #include <linux/io.h> 31c63b3cbaSViresh KUMAR #include "sdhci.h" 32c63b3cbaSViresh KUMAR 33c63b3cbaSViresh KUMAR struct spear_sdhci { 34c63b3cbaSViresh KUMAR struct clk *clk; 35c63b3cbaSViresh KUMAR struct sdhci_plat_data *data; 36c63b3cbaSViresh KUMAR }; 37c63b3cbaSViresh KUMAR 38c63b3cbaSViresh KUMAR /* sdhci ops */ 39c915568dSLars-Peter Clausen static const struct sdhci_ops sdhci_pltfm_ops = { 40c63b3cbaSViresh KUMAR /* Nothing to do for now. */ 41c63b3cbaSViresh KUMAR }; 42c63b3cbaSViresh KUMAR 43c63b3cbaSViresh KUMAR /* gpio card detection interrupt handler */ 44c63b3cbaSViresh KUMAR static irqreturn_t sdhci_gpio_irq(int irq, void *dev_id) 45c63b3cbaSViresh KUMAR { 46c63b3cbaSViresh KUMAR struct platform_device *pdev = dev_id; 47c63b3cbaSViresh KUMAR struct sdhci_host *host = platform_get_drvdata(pdev); 48c63b3cbaSViresh KUMAR struct spear_sdhci *sdhci = dev_get_platdata(&pdev->dev); 49c63b3cbaSViresh KUMAR unsigned long gpio_irq_type; 50c63b3cbaSViresh KUMAR int val; 51c63b3cbaSViresh KUMAR 52c63b3cbaSViresh KUMAR val = gpio_get_value(sdhci->data->card_int_gpio); 53c63b3cbaSViresh KUMAR 54c63b3cbaSViresh KUMAR /* val == 1 -> card removed, val == 0 -> card inserted */ 55c63b3cbaSViresh KUMAR /* if card removed - set irq for low level, else vice versa */ 56c63b3cbaSViresh KUMAR gpio_irq_type = val ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH; 57dced35aeSThomas Gleixner irq_set_irq_type(irq, gpio_irq_type); 58c63b3cbaSViresh KUMAR 59c63b3cbaSViresh KUMAR if (sdhci->data->card_power_gpio >= 0) { 60c63b3cbaSViresh KUMAR if (!sdhci->data->power_always_enb) { 61c63b3cbaSViresh KUMAR /* if card inserted, give power, otherwise remove it */ 62c63b3cbaSViresh KUMAR val = sdhci->data->power_active_high ? !val : val ; 63c63b3cbaSViresh KUMAR gpio_set_value(sdhci->data->card_power_gpio, val); 64c63b3cbaSViresh KUMAR } 65c63b3cbaSViresh KUMAR } 66c63b3cbaSViresh KUMAR 67c63b3cbaSViresh KUMAR /* inform sdhci driver about card insertion/removal */ 68c63b3cbaSViresh KUMAR tasklet_schedule(&host->card_tasklet); 69c63b3cbaSViresh KUMAR 70c63b3cbaSViresh KUMAR return IRQ_HANDLED; 71c63b3cbaSViresh KUMAR } 72c63b3cbaSViresh KUMAR 73067bf748SViresh Kumar #ifdef CONFIG_OF 74c3be1efdSBill Pemberton static struct sdhci_plat_data *sdhci_probe_config_dt(struct platform_device *pdev) 75067bf748SViresh Kumar { 76067bf748SViresh Kumar struct device_node *np = pdev->dev.of_node; 77067bf748SViresh Kumar struct sdhci_plat_data *pdata = NULL; 78067bf748SViresh Kumar int cd_gpio; 79067bf748SViresh Kumar 80067bf748SViresh Kumar cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); 81067bf748SViresh Kumar if (!gpio_is_valid(cd_gpio)) 82067bf748SViresh Kumar cd_gpio = -1; 83067bf748SViresh Kumar 84067bf748SViresh Kumar /* If pdata is required */ 85067bf748SViresh Kumar if (cd_gpio != -1) { 86067bf748SViresh Kumar pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); 87067bf748SViresh Kumar if (!pdata) { 88067bf748SViresh Kumar dev_err(&pdev->dev, "DT: kzalloc failed\n"); 89067bf748SViresh Kumar return ERR_PTR(-ENOMEM); 90067bf748SViresh Kumar } 91067bf748SViresh Kumar } 92067bf748SViresh Kumar 93067bf748SViresh Kumar pdata->card_int_gpio = cd_gpio; 94067bf748SViresh Kumar 95067bf748SViresh Kumar return pdata; 96067bf748SViresh Kumar } 97067bf748SViresh Kumar #else 98c3be1efdSBill Pemberton static struct sdhci_plat_data *sdhci_probe_config_dt(struct platform_device *pdev) 99067bf748SViresh Kumar { 100067bf748SViresh Kumar return ERR_PTR(-ENOSYS); 101067bf748SViresh Kumar } 102067bf748SViresh Kumar #endif 103067bf748SViresh Kumar 104c3be1efdSBill Pemberton static int sdhci_probe(struct platform_device *pdev) 105c63b3cbaSViresh KUMAR { 106067bf748SViresh Kumar struct device_node *np = pdev->dev.of_node; 107c63b3cbaSViresh KUMAR struct sdhci_host *host; 108c63b3cbaSViresh KUMAR struct resource *iomem; 109c63b3cbaSViresh KUMAR struct spear_sdhci *sdhci; 110c63b3cbaSViresh KUMAR int ret; 111c63b3cbaSViresh KUMAR 112c63b3cbaSViresh KUMAR iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 113c63b3cbaSViresh KUMAR if (!iomem) { 114c63b3cbaSViresh KUMAR ret = -ENOMEM; 115c63b3cbaSViresh KUMAR dev_dbg(&pdev->dev, "memory resource not defined\n"); 116c63b3cbaSViresh KUMAR goto err; 117c63b3cbaSViresh KUMAR } 118c63b3cbaSViresh KUMAR 1196ebaf8f2SViresh Kumar if (!devm_request_mem_region(&pdev->dev, iomem->start, 1206ebaf8f2SViresh Kumar resource_size(iomem), "spear-sdhci")) { 121c63b3cbaSViresh KUMAR ret = -EBUSY; 122c63b3cbaSViresh KUMAR dev_dbg(&pdev->dev, "cannot request region\n"); 123c63b3cbaSViresh KUMAR goto err; 124c63b3cbaSViresh KUMAR } 125c63b3cbaSViresh KUMAR 1266ebaf8f2SViresh Kumar sdhci = devm_kzalloc(&pdev->dev, sizeof(*sdhci), GFP_KERNEL); 127c63b3cbaSViresh KUMAR if (!sdhci) { 128c63b3cbaSViresh KUMAR ret = -ENOMEM; 129c63b3cbaSViresh KUMAR dev_dbg(&pdev->dev, "cannot allocate memory for sdhci\n"); 1306ebaf8f2SViresh Kumar goto err; 131c63b3cbaSViresh KUMAR } 132c63b3cbaSViresh KUMAR 133c63b3cbaSViresh KUMAR /* clk enable */ 134c63b3cbaSViresh KUMAR sdhci->clk = clk_get(&pdev->dev, NULL); 135c63b3cbaSViresh KUMAR if (IS_ERR(sdhci->clk)) { 136c63b3cbaSViresh KUMAR ret = PTR_ERR(sdhci->clk); 137c63b3cbaSViresh KUMAR dev_dbg(&pdev->dev, "Error getting clock\n"); 1386ebaf8f2SViresh Kumar goto err; 139c63b3cbaSViresh KUMAR } 140c63b3cbaSViresh KUMAR 141da764f97SViresh Kumar ret = clk_prepare_enable(sdhci->clk); 142c63b3cbaSViresh KUMAR if (ret) { 143c63b3cbaSViresh KUMAR dev_dbg(&pdev->dev, "Error enabling clock\n"); 1446ebaf8f2SViresh Kumar goto put_clk; 145c63b3cbaSViresh KUMAR } 146c63b3cbaSViresh KUMAR 147257f9df1SVipul Kumar Samar ret = clk_set_rate(sdhci->clk, 50000000); 148257f9df1SVipul Kumar Samar if (ret) 149257f9df1SVipul Kumar Samar dev_dbg(&pdev->dev, "Error setting desired clk, clk=%lu\n", 150257f9df1SVipul Kumar Samar clk_get_rate(sdhci->clk)); 151257f9df1SVipul Kumar Samar 152067bf748SViresh Kumar if (np) { 153067bf748SViresh Kumar sdhci->data = sdhci_probe_config_dt(pdev); 154067bf748SViresh Kumar if (IS_ERR(sdhci->data)) { 155067bf748SViresh Kumar dev_err(&pdev->dev, "DT: Failed to get pdata\n"); 156067bf748SViresh Kumar return -ENODEV; 157067bf748SViresh Kumar } 158067bf748SViresh Kumar } else { 159c63b3cbaSViresh KUMAR sdhci->data = dev_get_platdata(&pdev->dev); 160067bf748SViresh Kumar } 161067bf748SViresh Kumar 162c63b3cbaSViresh KUMAR pdev->dev.platform_data = sdhci; 163c63b3cbaSViresh KUMAR 164c63b3cbaSViresh KUMAR if (pdev->dev.parent) 165c63b3cbaSViresh KUMAR host = sdhci_alloc_host(pdev->dev.parent, 0); 166c63b3cbaSViresh KUMAR else 167c63b3cbaSViresh KUMAR host = sdhci_alloc_host(&pdev->dev, 0); 168c63b3cbaSViresh KUMAR 169c63b3cbaSViresh KUMAR if (IS_ERR(host)) { 170c63b3cbaSViresh KUMAR ret = PTR_ERR(host); 171c63b3cbaSViresh KUMAR dev_dbg(&pdev->dev, "error allocating host\n"); 1726ebaf8f2SViresh Kumar goto disable_clk; 173c63b3cbaSViresh KUMAR } 174c63b3cbaSViresh KUMAR 175c63b3cbaSViresh KUMAR host->hw_name = "sdhci"; 176c63b3cbaSViresh KUMAR host->ops = &sdhci_pltfm_ops; 177c63b3cbaSViresh KUMAR host->irq = platform_get_irq(pdev, 0); 178c63b3cbaSViresh KUMAR host->quirks = SDHCI_QUIRK_BROKEN_ADMA; 179c63b3cbaSViresh KUMAR 1806ebaf8f2SViresh Kumar host->ioaddr = devm_ioremap(&pdev->dev, iomem->start, 1816ebaf8f2SViresh Kumar resource_size(iomem)); 182c63b3cbaSViresh KUMAR if (!host->ioaddr) { 183c63b3cbaSViresh KUMAR ret = -ENOMEM; 184c63b3cbaSViresh KUMAR dev_dbg(&pdev->dev, "failed to remap registers\n"); 1856ebaf8f2SViresh Kumar goto free_host; 186c63b3cbaSViresh KUMAR } 187c63b3cbaSViresh KUMAR 188c63b3cbaSViresh KUMAR ret = sdhci_add_host(host); 189c63b3cbaSViresh KUMAR if (ret) { 190c63b3cbaSViresh KUMAR dev_dbg(&pdev->dev, "error adding host\n"); 1916ebaf8f2SViresh Kumar goto free_host; 192c63b3cbaSViresh KUMAR } 193c63b3cbaSViresh KUMAR 194c63b3cbaSViresh KUMAR platform_set_drvdata(pdev, host); 195c63b3cbaSViresh KUMAR 196c63b3cbaSViresh KUMAR /* 197c63b3cbaSViresh KUMAR * It is optional to use GPIOs for sdhci Power control & sdhci card 198c63b3cbaSViresh KUMAR * interrupt detection. If sdhci->data is NULL, then use original sdhci 199c63b3cbaSViresh KUMAR * lines otherwise GPIO lines. 200c63b3cbaSViresh KUMAR * If GPIO is selected for power control, then power should be disabled 201c63b3cbaSViresh KUMAR * after card removal and should be enabled when card insertion 202c63b3cbaSViresh KUMAR * interrupt occurs 203c63b3cbaSViresh KUMAR */ 204c63b3cbaSViresh KUMAR if (!sdhci->data) 205c63b3cbaSViresh KUMAR return 0; 206c63b3cbaSViresh KUMAR 207c63b3cbaSViresh KUMAR if (sdhci->data->card_power_gpio >= 0) { 208c63b3cbaSViresh KUMAR int val = 0; 209c63b3cbaSViresh KUMAR 2106ebaf8f2SViresh Kumar ret = devm_gpio_request(&pdev->dev, 2116ebaf8f2SViresh Kumar sdhci->data->card_power_gpio, "sdhci"); 212c63b3cbaSViresh KUMAR if (ret < 0) { 213c63b3cbaSViresh KUMAR dev_dbg(&pdev->dev, "gpio request fail: %d\n", 214c63b3cbaSViresh KUMAR sdhci->data->card_power_gpio); 2156ebaf8f2SViresh Kumar goto set_drvdata; 216c63b3cbaSViresh KUMAR } 217c63b3cbaSViresh KUMAR 218c63b3cbaSViresh KUMAR if (sdhci->data->power_always_enb) 219c63b3cbaSViresh KUMAR val = sdhci->data->power_active_high; 220c63b3cbaSViresh KUMAR else 221c63b3cbaSViresh KUMAR val = !sdhci->data->power_active_high; 222c63b3cbaSViresh KUMAR 223c63b3cbaSViresh KUMAR ret = gpio_direction_output(sdhci->data->card_power_gpio, val); 224c63b3cbaSViresh KUMAR if (ret) { 225c63b3cbaSViresh KUMAR dev_dbg(&pdev->dev, "gpio set direction fail: %d\n", 226c63b3cbaSViresh KUMAR sdhci->data->card_power_gpio); 2276ebaf8f2SViresh Kumar goto set_drvdata; 228c63b3cbaSViresh KUMAR } 229c63b3cbaSViresh KUMAR } 230c63b3cbaSViresh KUMAR 231c63b3cbaSViresh KUMAR if (sdhci->data->card_int_gpio >= 0) { 2326ebaf8f2SViresh Kumar ret = devm_gpio_request(&pdev->dev, sdhci->data->card_int_gpio, 2336ebaf8f2SViresh Kumar "sdhci"); 234c63b3cbaSViresh KUMAR if (ret < 0) { 235c63b3cbaSViresh KUMAR dev_dbg(&pdev->dev, "gpio request fail: %d\n", 236c63b3cbaSViresh KUMAR sdhci->data->card_int_gpio); 2376ebaf8f2SViresh Kumar goto set_drvdata; 238c63b3cbaSViresh KUMAR } 239c63b3cbaSViresh KUMAR 240c63b3cbaSViresh KUMAR ret = gpio_direction_input(sdhci->data->card_int_gpio); 241c63b3cbaSViresh KUMAR if (ret) { 242c63b3cbaSViresh KUMAR dev_dbg(&pdev->dev, "gpio set direction fail: %d\n", 243c63b3cbaSViresh KUMAR sdhci->data->card_int_gpio); 2446ebaf8f2SViresh Kumar goto set_drvdata; 245c63b3cbaSViresh KUMAR } 2466ebaf8f2SViresh Kumar ret = devm_request_irq(&pdev->dev, 2476ebaf8f2SViresh Kumar gpio_to_irq(sdhci->data->card_int_gpio), 248c63b3cbaSViresh KUMAR sdhci_gpio_irq, IRQF_TRIGGER_LOW, 249c63b3cbaSViresh KUMAR mmc_hostname(host->mmc), pdev); 250c63b3cbaSViresh KUMAR if (ret) { 251c63b3cbaSViresh KUMAR dev_dbg(&pdev->dev, "gpio request irq fail: %d\n", 252c63b3cbaSViresh KUMAR sdhci->data->card_int_gpio); 2536ebaf8f2SViresh Kumar goto set_drvdata; 254c63b3cbaSViresh KUMAR } 255c63b3cbaSViresh KUMAR 256c63b3cbaSViresh KUMAR } 257c63b3cbaSViresh KUMAR 258c63b3cbaSViresh KUMAR return 0; 259c63b3cbaSViresh KUMAR 2606ebaf8f2SViresh Kumar set_drvdata: 261c63b3cbaSViresh KUMAR platform_set_drvdata(pdev, NULL); 262c63b3cbaSViresh KUMAR sdhci_remove_host(host, 1); 2636ebaf8f2SViresh Kumar free_host: 264c63b3cbaSViresh KUMAR sdhci_free_host(host); 2656ebaf8f2SViresh Kumar disable_clk: 266da764f97SViresh Kumar clk_disable_unprepare(sdhci->clk); 2676ebaf8f2SViresh Kumar put_clk: 268c63b3cbaSViresh KUMAR clk_put(sdhci->clk); 269c63b3cbaSViresh KUMAR err: 270c63b3cbaSViresh KUMAR dev_err(&pdev->dev, "spear-sdhci probe failed: %d\n", ret); 271c63b3cbaSViresh KUMAR return ret; 272c63b3cbaSViresh KUMAR } 273c63b3cbaSViresh KUMAR 2746e0ee714SBill Pemberton static int sdhci_remove(struct platform_device *pdev) 275c63b3cbaSViresh KUMAR { 276c63b3cbaSViresh KUMAR struct sdhci_host *host = platform_get_drvdata(pdev); 277c63b3cbaSViresh KUMAR struct spear_sdhci *sdhci = dev_get_platdata(&pdev->dev); 2786ebaf8f2SViresh Kumar int dead = 0; 279c63b3cbaSViresh KUMAR u32 scratch; 280c63b3cbaSViresh KUMAR 281c63b3cbaSViresh KUMAR platform_set_drvdata(pdev, NULL); 282c63b3cbaSViresh KUMAR scratch = readl(host->ioaddr + SDHCI_INT_STATUS); 283c63b3cbaSViresh KUMAR if (scratch == (u32)-1) 284c63b3cbaSViresh KUMAR dead = 1; 285c63b3cbaSViresh KUMAR 286c63b3cbaSViresh KUMAR sdhci_remove_host(host, dead); 287c63b3cbaSViresh KUMAR sdhci_free_host(host); 288da764f97SViresh Kumar clk_disable_unprepare(sdhci->clk); 289c63b3cbaSViresh KUMAR clk_put(sdhci->clk); 290c63b3cbaSViresh KUMAR 291c63b3cbaSViresh KUMAR return 0; 292c63b3cbaSViresh KUMAR } 293c63b3cbaSViresh KUMAR 294b0dd099cSJingoo Han #ifdef CONFIG_PM_SLEEP 295b70a7fabSViresh Kumar static int sdhci_suspend(struct device *dev) 296b70a7fabSViresh Kumar { 297b70a7fabSViresh Kumar struct sdhci_host *host = dev_get_drvdata(dev); 298b70a7fabSViresh Kumar struct spear_sdhci *sdhci = dev_get_platdata(dev); 299b70a7fabSViresh Kumar int ret; 300b70a7fabSViresh Kumar 301984589e5SViresh Kumar ret = sdhci_suspend_host(host); 302b70a7fabSViresh Kumar if (!ret) 30306960a1bSViresh Kumar clk_disable(sdhci->clk); 304b70a7fabSViresh Kumar 305b70a7fabSViresh Kumar return ret; 306b70a7fabSViresh Kumar } 307b70a7fabSViresh Kumar 308b70a7fabSViresh Kumar static int sdhci_resume(struct device *dev) 309b70a7fabSViresh Kumar { 310b70a7fabSViresh Kumar struct sdhci_host *host = dev_get_drvdata(dev); 311b70a7fabSViresh Kumar struct spear_sdhci *sdhci = dev_get_platdata(dev); 312b70a7fabSViresh Kumar int ret; 313b70a7fabSViresh Kumar 31406960a1bSViresh Kumar ret = clk_enable(sdhci->clk); 315b70a7fabSViresh Kumar if (ret) { 316b70a7fabSViresh Kumar dev_dbg(dev, "Resume: Error enabling clock\n"); 317b70a7fabSViresh Kumar return ret; 318b70a7fabSViresh Kumar } 319b70a7fabSViresh Kumar 320b70a7fabSViresh Kumar return sdhci_resume_host(host); 321b70a7fabSViresh Kumar } 322b70a7fabSViresh Kumar #endif 323b70a7fabSViresh Kumar 3244b1a6170SShiraz Hashim static SIMPLE_DEV_PM_OPS(sdhci_pm_ops, sdhci_suspend, sdhci_resume); 3254b1a6170SShiraz Hashim 326067bf748SViresh Kumar #ifdef CONFIG_OF 327067bf748SViresh Kumar static const struct of_device_id sdhci_spear_id_table[] = { 328067bf748SViresh Kumar { .compatible = "st,spear300-sdhci" }, 329067bf748SViresh Kumar {} 330067bf748SViresh Kumar }; 331067bf748SViresh Kumar MODULE_DEVICE_TABLE(of, sdhci_spear_id_table); 332067bf748SViresh Kumar #endif 333067bf748SViresh Kumar 334c63b3cbaSViresh KUMAR static struct platform_driver sdhci_driver = { 335c63b3cbaSViresh KUMAR .driver = { 336c63b3cbaSViresh KUMAR .name = "sdhci", 337c63b3cbaSViresh KUMAR .owner = THIS_MODULE, 338b70a7fabSViresh Kumar .pm = &sdhci_pm_ops, 339067bf748SViresh Kumar .of_match_table = of_match_ptr(sdhci_spear_id_table), 340c63b3cbaSViresh KUMAR }, 341c63b3cbaSViresh KUMAR .probe = sdhci_probe, 3420433c143SBill Pemberton .remove = sdhci_remove, 343c63b3cbaSViresh KUMAR }; 344c63b3cbaSViresh KUMAR 345d1f81a64SAxel Lin module_platform_driver(sdhci_driver); 346c63b3cbaSViresh KUMAR 347c63b3cbaSViresh KUMAR MODULE_DESCRIPTION("SPEAr Secure Digital Host Controller Interface driver"); 34810d8935fSViresh Kumar MODULE_AUTHOR("Viresh Kumar <viresh.linux@gmail.com>"); 349c63b3cbaSViresh KUMAR MODULE_LICENSE("GPL v2"); 350