1bb5f8ea4Sludovic.desroches@atmel.com /* 2bb5f8ea4Sludovic.desroches@atmel.com * Atmel SDMMC controller driver. 3bb5f8ea4Sludovic.desroches@atmel.com * 4bb5f8ea4Sludovic.desroches@atmel.com * Copyright (C) 2015 Atmel, 5bb5f8ea4Sludovic.desroches@atmel.com * 2015 Ludovic Desroches <ludovic.desroches@atmel.com> 6bb5f8ea4Sludovic.desroches@atmel.com * 7bb5f8ea4Sludovic.desroches@atmel.com * This software is licensed under the terms of the GNU General Public 8bb5f8ea4Sludovic.desroches@atmel.com * License version 2, as published by the Free Software Foundation, and 9bb5f8ea4Sludovic.desroches@atmel.com * may be copied, distributed, and modified under those terms. 10bb5f8ea4Sludovic.desroches@atmel.com * 11bb5f8ea4Sludovic.desroches@atmel.com * This program is distributed in the hope that it will be useful, 12bb5f8ea4Sludovic.desroches@atmel.com * but WITHOUT ANY WARRANTY; without even the implied warranty of 13bb5f8ea4Sludovic.desroches@atmel.com * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14bb5f8ea4Sludovic.desroches@atmel.com * GNU General Public License for more details. 15bb5f8ea4Sludovic.desroches@atmel.com */ 16bb5f8ea4Sludovic.desroches@atmel.com 17bb5f8ea4Sludovic.desroches@atmel.com #include <linux/clk.h> 18*4e289a7dSLudovic Desroches #include <linux/delay.h> 19bb5f8ea4Sludovic.desroches@atmel.com #include <linux/err.h> 20bb5f8ea4Sludovic.desroches@atmel.com #include <linux/io.h> 21bb5f8ea4Sludovic.desroches@atmel.com #include <linux/mmc/host.h> 2264e5cd72Sludovic.desroches@atmel.com #include <linux/mmc/slot-gpio.h> 23bb5f8ea4Sludovic.desroches@atmel.com #include <linux/module.h> 24bb5f8ea4Sludovic.desroches@atmel.com #include <linux/of.h> 25bb5f8ea4Sludovic.desroches@atmel.com #include <linux/of_device.h> 26f5f17813Sludovic.desroches@atmel.com #include <linux/pm.h> 27f5f17813Sludovic.desroches@atmel.com #include <linux/pm_runtime.h> 28bb5f8ea4Sludovic.desroches@atmel.com 29bb5f8ea4Sludovic.desroches@atmel.com #include "sdhci-pltfm.h" 30bb5f8ea4Sludovic.desroches@atmel.com 31bb5f8ea4Sludovic.desroches@atmel.com #define SDMMC_CACR 0x230 32bb5f8ea4Sludovic.desroches@atmel.com #define SDMMC_CACR_CAPWREN BIT(0) 33bb5f8ea4Sludovic.desroches@atmel.com #define SDMMC_CACR_KEY (0x46 << 8) 34bb5f8ea4Sludovic.desroches@atmel.com 35bb5f8ea4Sludovic.desroches@atmel.com struct sdhci_at91_priv { 36bb5f8ea4Sludovic.desroches@atmel.com struct clk *hclock; 37bb5f8ea4Sludovic.desroches@atmel.com struct clk *gck; 38bb5f8ea4Sludovic.desroches@atmel.com struct clk *mainck; 39bb5f8ea4Sludovic.desroches@atmel.com }; 40bb5f8ea4Sludovic.desroches@atmel.com 41*4e289a7dSLudovic Desroches static void sdhci_at91_set_clock(struct sdhci_host *host, unsigned int clock) 42*4e289a7dSLudovic Desroches { 43*4e289a7dSLudovic Desroches u16 clk; 44*4e289a7dSLudovic Desroches unsigned long timeout; 45*4e289a7dSLudovic Desroches 46*4e289a7dSLudovic Desroches host->mmc->actual_clock = 0; 47*4e289a7dSLudovic Desroches 48*4e289a7dSLudovic Desroches /* 49*4e289a7dSLudovic Desroches * There is no requirement to disable the internal clock before 50*4e289a7dSLudovic Desroches * changing the SD clock configuration. Moreover, disabling the 51*4e289a7dSLudovic Desroches * internal clock, changing the configuration and re-enabling the 52*4e289a7dSLudovic Desroches * internal clock causes some bugs. It can prevent to get the internal 53*4e289a7dSLudovic Desroches * clock stable flag ready and an unexpected switch to the base clock 54*4e289a7dSLudovic Desroches * when using presets. 55*4e289a7dSLudovic Desroches */ 56*4e289a7dSLudovic Desroches clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); 57*4e289a7dSLudovic Desroches clk &= SDHCI_CLOCK_INT_EN; 58*4e289a7dSLudovic Desroches sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); 59*4e289a7dSLudovic Desroches 60*4e289a7dSLudovic Desroches if (clock == 0) 61*4e289a7dSLudovic Desroches return; 62*4e289a7dSLudovic Desroches 63*4e289a7dSLudovic Desroches clk = sdhci_calc_clk(host, clock, &host->mmc->actual_clock); 64*4e289a7dSLudovic Desroches 65*4e289a7dSLudovic Desroches clk |= SDHCI_CLOCK_INT_EN; 66*4e289a7dSLudovic Desroches sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); 67*4e289a7dSLudovic Desroches 68*4e289a7dSLudovic Desroches /* Wait max 20 ms */ 69*4e289a7dSLudovic Desroches timeout = 20; 70*4e289a7dSLudovic Desroches while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) 71*4e289a7dSLudovic Desroches & SDHCI_CLOCK_INT_STABLE)) { 72*4e289a7dSLudovic Desroches if (timeout == 0) { 73*4e289a7dSLudovic Desroches pr_err("%s: Internal clock never stabilised.\n", 74*4e289a7dSLudovic Desroches mmc_hostname(host->mmc)); 75*4e289a7dSLudovic Desroches return; 76*4e289a7dSLudovic Desroches } 77*4e289a7dSLudovic Desroches timeout--; 78*4e289a7dSLudovic Desroches mdelay(1); 79*4e289a7dSLudovic Desroches } 80*4e289a7dSLudovic Desroches 81*4e289a7dSLudovic Desroches clk |= SDHCI_CLOCK_CARD_EN; 82*4e289a7dSLudovic Desroches sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); 83*4e289a7dSLudovic Desroches } 84*4e289a7dSLudovic Desroches 85bb5f8ea4Sludovic.desroches@atmel.com static const struct sdhci_ops sdhci_at91_sama5d2_ops = { 86*4e289a7dSLudovic Desroches .set_clock = sdhci_at91_set_clock, 87bb5f8ea4Sludovic.desroches@atmel.com .set_bus_width = sdhci_set_bus_width, 88bb5f8ea4Sludovic.desroches@atmel.com .reset = sdhci_reset, 89bb5f8ea4Sludovic.desroches@atmel.com .set_uhs_signaling = sdhci_set_uhs_signaling, 90bb5f8ea4Sludovic.desroches@atmel.com }; 91bb5f8ea4Sludovic.desroches@atmel.com 92bb5f8ea4Sludovic.desroches@atmel.com static const struct sdhci_pltfm_data soc_data_sama5d2 = { 93bb5f8ea4Sludovic.desroches@atmel.com .ops = &sdhci_at91_sama5d2_ops, 94bb5f8ea4Sludovic.desroches@atmel.com }; 95bb5f8ea4Sludovic.desroches@atmel.com 96bb5f8ea4Sludovic.desroches@atmel.com static const struct of_device_id sdhci_at91_dt_match[] = { 97bb5f8ea4Sludovic.desroches@atmel.com { .compatible = "atmel,sama5d2-sdhci", .data = &soc_data_sama5d2 }, 98bb5f8ea4Sludovic.desroches@atmel.com {} 99bb5f8ea4Sludovic.desroches@atmel.com }; 100bb5f8ea4Sludovic.desroches@atmel.com 101f5f17813Sludovic.desroches@atmel.com #ifdef CONFIG_PM 102f5f17813Sludovic.desroches@atmel.com static int sdhci_at91_runtime_suspend(struct device *dev) 103f5f17813Sludovic.desroches@atmel.com { 104f5f17813Sludovic.desroches@atmel.com struct sdhci_host *host = dev_get_drvdata(dev); 105f5f17813Sludovic.desroches@atmel.com struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 10610f1c135SJisheng Zhang struct sdhci_at91_priv *priv = sdhci_pltfm_priv(pltfm_host); 107f5f17813Sludovic.desroches@atmel.com int ret; 108f5f17813Sludovic.desroches@atmel.com 109f5f17813Sludovic.desroches@atmel.com ret = sdhci_runtime_suspend_host(host); 110f5f17813Sludovic.desroches@atmel.com 111f5f17813Sludovic.desroches@atmel.com clk_disable_unprepare(priv->gck); 112f5f17813Sludovic.desroches@atmel.com clk_disable_unprepare(priv->hclock); 113f5f17813Sludovic.desroches@atmel.com clk_disable_unprepare(priv->mainck); 114f5f17813Sludovic.desroches@atmel.com 115f5f17813Sludovic.desroches@atmel.com return ret; 116f5f17813Sludovic.desroches@atmel.com } 117f5f17813Sludovic.desroches@atmel.com 118f5f17813Sludovic.desroches@atmel.com static int sdhci_at91_runtime_resume(struct device *dev) 119f5f17813Sludovic.desroches@atmel.com { 120f5f17813Sludovic.desroches@atmel.com struct sdhci_host *host = dev_get_drvdata(dev); 121f5f17813Sludovic.desroches@atmel.com struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 12210f1c135SJisheng Zhang struct sdhci_at91_priv *priv = sdhci_pltfm_priv(pltfm_host); 123f5f17813Sludovic.desroches@atmel.com int ret; 124f5f17813Sludovic.desroches@atmel.com 125f5f17813Sludovic.desroches@atmel.com ret = clk_prepare_enable(priv->mainck); 126f5f17813Sludovic.desroches@atmel.com if (ret) { 127f5f17813Sludovic.desroches@atmel.com dev_err(dev, "can't enable mainck\n"); 128f5f17813Sludovic.desroches@atmel.com return ret; 129f5f17813Sludovic.desroches@atmel.com } 130f5f17813Sludovic.desroches@atmel.com 131f5f17813Sludovic.desroches@atmel.com ret = clk_prepare_enable(priv->hclock); 132f5f17813Sludovic.desroches@atmel.com if (ret) { 133f5f17813Sludovic.desroches@atmel.com dev_err(dev, "can't enable hclock\n"); 134f5f17813Sludovic.desroches@atmel.com return ret; 135f5f17813Sludovic.desroches@atmel.com } 136f5f17813Sludovic.desroches@atmel.com 137f5f17813Sludovic.desroches@atmel.com ret = clk_prepare_enable(priv->gck); 138f5f17813Sludovic.desroches@atmel.com if (ret) { 139f5f17813Sludovic.desroches@atmel.com dev_err(dev, "can't enable gck\n"); 140f5f17813Sludovic.desroches@atmel.com return ret; 141f5f17813Sludovic.desroches@atmel.com } 142f5f17813Sludovic.desroches@atmel.com 143f5f17813Sludovic.desroches@atmel.com return sdhci_runtime_resume_host(host); 144f5f17813Sludovic.desroches@atmel.com } 145f5f17813Sludovic.desroches@atmel.com #endif /* CONFIG_PM */ 146f5f17813Sludovic.desroches@atmel.com 147f5f17813Sludovic.desroches@atmel.com static const struct dev_pm_ops sdhci_at91_dev_pm_ops = { 148f5f17813Sludovic.desroches@atmel.com SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, 149f5f17813Sludovic.desroches@atmel.com pm_runtime_force_resume) 150f5f17813Sludovic.desroches@atmel.com SET_RUNTIME_PM_OPS(sdhci_at91_runtime_suspend, 151f5f17813Sludovic.desroches@atmel.com sdhci_at91_runtime_resume, 152f5f17813Sludovic.desroches@atmel.com NULL) 153f5f17813Sludovic.desroches@atmel.com }; 154f5f17813Sludovic.desroches@atmel.com 155bb5f8ea4Sludovic.desroches@atmel.com static int sdhci_at91_probe(struct platform_device *pdev) 156bb5f8ea4Sludovic.desroches@atmel.com { 157bb5f8ea4Sludovic.desroches@atmel.com const struct of_device_id *match; 158bb5f8ea4Sludovic.desroches@atmel.com const struct sdhci_pltfm_data *soc_data; 159bb5f8ea4Sludovic.desroches@atmel.com struct sdhci_host *host; 160bb5f8ea4Sludovic.desroches@atmel.com struct sdhci_pltfm_host *pltfm_host; 161bb5f8ea4Sludovic.desroches@atmel.com struct sdhci_at91_priv *priv; 162bb5f8ea4Sludovic.desroches@atmel.com unsigned int caps0, caps1; 163bb5f8ea4Sludovic.desroches@atmel.com unsigned int clk_base, clk_mul; 164bb5f8ea4Sludovic.desroches@atmel.com unsigned int gck_rate, real_gck_rate; 165bb5f8ea4Sludovic.desroches@atmel.com int ret; 166bb5f8ea4Sludovic.desroches@atmel.com 167bb5f8ea4Sludovic.desroches@atmel.com match = of_match_device(sdhci_at91_dt_match, &pdev->dev); 168bb5f8ea4Sludovic.desroches@atmel.com if (!match) 169bb5f8ea4Sludovic.desroches@atmel.com return -EINVAL; 170bb5f8ea4Sludovic.desroches@atmel.com soc_data = match->data; 171bb5f8ea4Sludovic.desroches@atmel.com 17210f1c135SJisheng Zhang host = sdhci_pltfm_init(pdev, soc_data, sizeof(*priv)); 17310f1c135SJisheng Zhang if (IS_ERR(host)) 17410f1c135SJisheng Zhang return PTR_ERR(host); 17510f1c135SJisheng Zhang 17610f1c135SJisheng Zhang pltfm_host = sdhci_priv(host); 17710f1c135SJisheng Zhang priv = sdhci_pltfm_priv(pltfm_host); 178bb5f8ea4Sludovic.desroches@atmel.com 179bb5f8ea4Sludovic.desroches@atmel.com priv->mainck = devm_clk_get(&pdev->dev, "baseclk"); 180bb5f8ea4Sludovic.desroches@atmel.com if (IS_ERR(priv->mainck)) { 181bb5f8ea4Sludovic.desroches@atmel.com dev_err(&pdev->dev, "failed to get baseclk\n"); 182bb5f8ea4Sludovic.desroches@atmel.com return PTR_ERR(priv->mainck); 183bb5f8ea4Sludovic.desroches@atmel.com } 184bb5f8ea4Sludovic.desroches@atmel.com 185bb5f8ea4Sludovic.desroches@atmel.com priv->hclock = devm_clk_get(&pdev->dev, "hclock"); 186bb5f8ea4Sludovic.desroches@atmel.com if (IS_ERR(priv->hclock)) { 187bb5f8ea4Sludovic.desroches@atmel.com dev_err(&pdev->dev, "failed to get hclock\n"); 188bb5f8ea4Sludovic.desroches@atmel.com return PTR_ERR(priv->hclock); 189bb5f8ea4Sludovic.desroches@atmel.com } 190bb5f8ea4Sludovic.desroches@atmel.com 191bb5f8ea4Sludovic.desroches@atmel.com priv->gck = devm_clk_get(&pdev->dev, "multclk"); 192bb5f8ea4Sludovic.desroches@atmel.com if (IS_ERR(priv->gck)) { 193bb5f8ea4Sludovic.desroches@atmel.com dev_err(&pdev->dev, "failed to get multclk\n"); 194bb5f8ea4Sludovic.desroches@atmel.com return PTR_ERR(priv->gck); 195bb5f8ea4Sludovic.desroches@atmel.com } 196bb5f8ea4Sludovic.desroches@atmel.com 197bb5f8ea4Sludovic.desroches@atmel.com /* 198bb5f8ea4Sludovic.desroches@atmel.com * The mult clock is provided by as a generated clock by the PMC 199bb5f8ea4Sludovic.desroches@atmel.com * controller. In order to set the rate of gck, we have to get the 200bb5f8ea4Sludovic.desroches@atmel.com * base clock rate and the clock mult from capabilities. 201bb5f8ea4Sludovic.desroches@atmel.com */ 202bb5f8ea4Sludovic.desroches@atmel.com clk_prepare_enable(priv->hclock); 203bb5f8ea4Sludovic.desroches@atmel.com caps0 = readl(host->ioaddr + SDHCI_CAPABILITIES); 204bb5f8ea4Sludovic.desroches@atmel.com caps1 = readl(host->ioaddr + SDHCI_CAPABILITIES_1); 205bb5f8ea4Sludovic.desroches@atmel.com clk_base = (caps0 & SDHCI_CLOCK_V3_BASE_MASK) >> SDHCI_CLOCK_BASE_SHIFT; 206bb5f8ea4Sludovic.desroches@atmel.com clk_mul = (caps1 & SDHCI_CLOCK_MUL_MASK) >> SDHCI_CLOCK_MUL_SHIFT; 207bb5f8ea4Sludovic.desroches@atmel.com gck_rate = clk_base * 1000000 * (clk_mul + 1); 208bb5f8ea4Sludovic.desroches@atmel.com ret = clk_set_rate(priv->gck, gck_rate); 209bb5f8ea4Sludovic.desroches@atmel.com if (ret < 0) { 210bb5f8ea4Sludovic.desroches@atmel.com dev_err(&pdev->dev, "failed to set gck"); 211bb5f8ea4Sludovic.desroches@atmel.com goto hclock_disable_unprepare; 212bb5f8ea4Sludovic.desroches@atmel.com } 213bb5f8ea4Sludovic.desroches@atmel.com /* 214bb5f8ea4Sludovic.desroches@atmel.com * We need to check if we have the requested rate for gck because in 215bb5f8ea4Sludovic.desroches@atmel.com * some cases this rate could be not supported. If it happens, the rate 216bb5f8ea4Sludovic.desroches@atmel.com * is the closest one gck can provide. We have to update the value 217bb5f8ea4Sludovic.desroches@atmel.com * of clk mul. 218bb5f8ea4Sludovic.desroches@atmel.com */ 219bb5f8ea4Sludovic.desroches@atmel.com real_gck_rate = clk_get_rate(priv->gck); 220bb5f8ea4Sludovic.desroches@atmel.com if (real_gck_rate != gck_rate) { 221bb5f8ea4Sludovic.desroches@atmel.com clk_mul = real_gck_rate / (clk_base * 1000000) - 1; 222bb5f8ea4Sludovic.desroches@atmel.com caps1 &= (~SDHCI_CLOCK_MUL_MASK); 223bb5f8ea4Sludovic.desroches@atmel.com caps1 |= ((clk_mul << SDHCI_CLOCK_MUL_SHIFT) & SDHCI_CLOCK_MUL_MASK); 224bb5f8ea4Sludovic.desroches@atmel.com /* Set capabilities in r/w mode. */ 225bb5f8ea4Sludovic.desroches@atmel.com writel(SDMMC_CACR_KEY | SDMMC_CACR_CAPWREN, host->ioaddr + SDMMC_CACR); 226bb5f8ea4Sludovic.desroches@atmel.com writel(caps1, host->ioaddr + SDHCI_CAPABILITIES_1); 227bb5f8ea4Sludovic.desroches@atmel.com /* Set capabilities in ro mode. */ 228bb5f8ea4Sludovic.desroches@atmel.com writel(0, host->ioaddr + SDMMC_CACR); 229bb5f8ea4Sludovic.desroches@atmel.com dev_info(&pdev->dev, "update clk mul to %u as gck rate is %u Hz\n", 230bb5f8ea4Sludovic.desroches@atmel.com clk_mul, real_gck_rate); 231bb5f8ea4Sludovic.desroches@atmel.com } 232bb5f8ea4Sludovic.desroches@atmel.com 233bb5f8ea4Sludovic.desroches@atmel.com clk_prepare_enable(priv->mainck); 234bb5f8ea4Sludovic.desroches@atmel.com clk_prepare_enable(priv->gck); 235bb5f8ea4Sludovic.desroches@atmel.com 236bb5f8ea4Sludovic.desroches@atmel.com ret = mmc_of_parse(host->mmc); 237bb5f8ea4Sludovic.desroches@atmel.com if (ret) 238bb5f8ea4Sludovic.desroches@atmel.com goto clocks_disable_unprepare; 239bb5f8ea4Sludovic.desroches@atmel.com 240bb5f8ea4Sludovic.desroches@atmel.com sdhci_get_of_property(pdev); 241bb5f8ea4Sludovic.desroches@atmel.com 242f5f17813Sludovic.desroches@atmel.com pm_runtime_get_noresume(&pdev->dev); 243f5f17813Sludovic.desroches@atmel.com pm_runtime_set_active(&pdev->dev); 244f5f17813Sludovic.desroches@atmel.com pm_runtime_enable(&pdev->dev); 245f5f17813Sludovic.desroches@atmel.com pm_runtime_set_autosuspend_delay(&pdev->dev, 50); 246f5f17813Sludovic.desroches@atmel.com pm_runtime_use_autosuspend(&pdev->dev); 247f5f17813Sludovic.desroches@atmel.com 248bb5f8ea4Sludovic.desroches@atmel.com ret = sdhci_add_host(host); 249bb5f8ea4Sludovic.desroches@atmel.com if (ret) 250f5f17813Sludovic.desroches@atmel.com goto pm_runtime_disable; 251f5f17813Sludovic.desroches@atmel.com 25264e5cd72Sludovic.desroches@atmel.com /* 25364e5cd72Sludovic.desroches@atmel.com * When calling sdhci_runtime_suspend_host(), the sdhci layer makes 25464e5cd72Sludovic.desroches@atmel.com * the assumption that all the clocks of the controller are disabled. 25564e5cd72Sludovic.desroches@atmel.com * It means we can't get irq from it when it is runtime suspended. 25664e5cd72Sludovic.desroches@atmel.com * For that reason, it is not planned to wake-up on a card detect irq 25764e5cd72Sludovic.desroches@atmel.com * from the controller. 25864e5cd72Sludovic.desroches@atmel.com * If we want to use runtime PM and to be able to wake-up on card 25964e5cd72Sludovic.desroches@atmel.com * insertion, we have to use a GPIO for the card detection or we can 26064e5cd72Sludovic.desroches@atmel.com * use polling. Be aware that using polling will resume/suspend the 26164e5cd72Sludovic.desroches@atmel.com * controller between each attempt. 26264e5cd72Sludovic.desroches@atmel.com * Disable SDHCI_QUIRK_BROKEN_CARD_DETECTION to be sure nobody tries 26364e5cd72Sludovic.desroches@atmel.com * to enable polling via device tree with broken-cd property. 26464e5cd72Sludovic.desroches@atmel.com */ 26564e5cd72Sludovic.desroches@atmel.com if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE) && 26664e5cd72Sludovic.desroches@atmel.com IS_ERR_VALUE(mmc_gpio_get_cd(host->mmc))) { 26764e5cd72Sludovic.desroches@atmel.com host->mmc->caps |= MMC_CAP_NEEDS_POLL; 26864e5cd72Sludovic.desroches@atmel.com host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION; 26964e5cd72Sludovic.desroches@atmel.com } 27064e5cd72Sludovic.desroches@atmel.com 271f5f17813Sludovic.desroches@atmel.com pm_runtime_put_autosuspend(&pdev->dev); 272bb5f8ea4Sludovic.desroches@atmel.com 273bb5f8ea4Sludovic.desroches@atmel.com return 0; 274bb5f8ea4Sludovic.desroches@atmel.com 275f5f17813Sludovic.desroches@atmel.com pm_runtime_disable: 276f5f17813Sludovic.desroches@atmel.com pm_runtime_disable(&pdev->dev); 277f5f17813Sludovic.desroches@atmel.com pm_runtime_set_suspended(&pdev->dev); 2782df9d58fSJisheng Zhang pm_runtime_put_noidle(&pdev->dev); 279bb5f8ea4Sludovic.desroches@atmel.com clocks_disable_unprepare: 280bb5f8ea4Sludovic.desroches@atmel.com clk_disable_unprepare(priv->gck); 281bb5f8ea4Sludovic.desroches@atmel.com clk_disable_unprepare(priv->mainck); 282bb5f8ea4Sludovic.desroches@atmel.com hclock_disable_unprepare: 283bb5f8ea4Sludovic.desroches@atmel.com clk_disable_unprepare(priv->hclock); 284bb5f8ea4Sludovic.desroches@atmel.com sdhci_pltfm_free(pdev); 285bb5f8ea4Sludovic.desroches@atmel.com return ret; 286bb5f8ea4Sludovic.desroches@atmel.com } 287bb5f8ea4Sludovic.desroches@atmel.com 288bb5f8ea4Sludovic.desroches@atmel.com static int sdhci_at91_remove(struct platform_device *pdev) 289bb5f8ea4Sludovic.desroches@atmel.com { 290bb5f8ea4Sludovic.desroches@atmel.com struct sdhci_host *host = platform_get_drvdata(pdev); 291bb5f8ea4Sludovic.desroches@atmel.com struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 29210f1c135SJisheng Zhang struct sdhci_at91_priv *priv = sdhci_pltfm_priv(pltfm_host); 29310f1c135SJisheng Zhang struct clk *gck = priv->gck; 29410f1c135SJisheng Zhang struct clk *hclock = priv->hclock; 29510f1c135SJisheng Zhang struct clk *mainck = priv->mainck; 296bb5f8ea4Sludovic.desroches@atmel.com 297f5f17813Sludovic.desroches@atmel.com pm_runtime_get_sync(&pdev->dev); 298f5f17813Sludovic.desroches@atmel.com pm_runtime_disable(&pdev->dev); 299f5f17813Sludovic.desroches@atmel.com pm_runtime_put_noidle(&pdev->dev); 300f5f17813Sludovic.desroches@atmel.com 301bb5f8ea4Sludovic.desroches@atmel.com sdhci_pltfm_unregister(pdev); 302bb5f8ea4Sludovic.desroches@atmel.com 30310f1c135SJisheng Zhang clk_disable_unprepare(gck); 30410f1c135SJisheng Zhang clk_disable_unprepare(hclock); 30510f1c135SJisheng Zhang clk_disable_unprepare(mainck); 306bb5f8ea4Sludovic.desroches@atmel.com 307bb5f8ea4Sludovic.desroches@atmel.com return 0; 308bb5f8ea4Sludovic.desroches@atmel.com } 309bb5f8ea4Sludovic.desroches@atmel.com 310bb5f8ea4Sludovic.desroches@atmel.com static struct platform_driver sdhci_at91_driver = { 311bb5f8ea4Sludovic.desroches@atmel.com .driver = { 312bb5f8ea4Sludovic.desroches@atmel.com .name = "sdhci-at91", 313bb5f8ea4Sludovic.desroches@atmel.com .of_match_table = sdhci_at91_dt_match, 314f5f17813Sludovic.desroches@atmel.com .pm = &sdhci_at91_dev_pm_ops, 315bb5f8ea4Sludovic.desroches@atmel.com }, 316bb5f8ea4Sludovic.desroches@atmel.com .probe = sdhci_at91_probe, 317bb5f8ea4Sludovic.desroches@atmel.com .remove = sdhci_at91_remove, 318bb5f8ea4Sludovic.desroches@atmel.com }; 319bb5f8ea4Sludovic.desroches@atmel.com 320bb5f8ea4Sludovic.desroches@atmel.com module_platform_driver(sdhci_at91_driver); 321bb5f8ea4Sludovic.desroches@atmel.com 322bb5f8ea4Sludovic.desroches@atmel.com MODULE_DESCRIPTION("SDHCI driver for at91"); 323bb5f8ea4Sludovic.desroches@atmel.com MODULE_AUTHOR("Ludovic Desroches <ludovic.desroches@atmel.com>"); 324bb5f8ea4Sludovic.desroches@atmel.com MODULE_LICENSE("GPL v2"); 325