diff options
Diffstat (limited to 'drivers/mmc/host/sunxi-mmc.c')
-rw-r--r-- | drivers/mmc/host/sunxi-mmc.c | 187 |
1 files changed, 124 insertions, 63 deletions
diff --git a/drivers/mmc/host/sunxi-mmc.c b/drivers/mmc/host/sunxi-mmc.c index 20cfb20418f3..e7472590f2ed 100644 --- a/drivers/mmc/host/sunxi-mmc.c +++ b/drivers/mmc/host/sunxi-mmc.c @@ -13,36 +13,34 @@ * the License, or (at your option) any later version. */ -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/io.h> -#include <linux/device.h> -#include <linux/interrupt.h> -#include <linux/delay.h> -#include <linux/err.h> - #include <linux/clk.h> #include <linux/clk/sunxi-ng.h> -#include <linux/gpio.h> -#include <linux/platform_device.h> -#include <linux/spinlock.h> -#include <linux/scatterlist.h> +#include <linux/delay.h> +#include <linux/device.h> #include <linux/dma-mapping.h> -#include <linux/slab.h> -#include <linux/reset.h> -#include <linux/regulator/consumer.h> - -#include <linux/of_address.h> -#include <linux/of_gpio.h> -#include <linux/of_platform.h> - +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mmc/card.h> +#include <linux/mmc/core.h> #include <linux/mmc/host.h> +#include <linux/mmc/mmc.h> #include <linux/mmc/sd.h> #include <linux/mmc/sdio.h> -#include <linux/mmc/mmc.h> -#include <linux/mmc/core.h> -#include <linux/mmc/card.h> #include <linux/mmc/slot-gpio.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_gpio.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/spinlock.h> /* register offset definitions */ #define SDXC_REG_GCTRL (0x00) /* SMC Global Control Register */ @@ -322,10 +320,9 @@ static int sunxi_mmc_reset_host(struct sunxi_mmc_host *host) return 0; } -static int sunxi_mmc_init_host(struct mmc_host *mmc) +static int sunxi_mmc_init_host(struct sunxi_mmc_host *host) { u32 rval; - struct sunxi_mmc_host *host = mmc_priv(mmc); if (sunxi_mmc_reset_host(host)) return -EIO; @@ -859,17 +856,48 @@ static int sunxi_mmc_clk_set_rate(struct sunxi_mmc_host *host, return 0; } -static void sunxi_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +static void sunxi_mmc_set_bus_width(struct sunxi_mmc_host *host, + unsigned char width) +{ + switch (width) { + case MMC_BUS_WIDTH_1: + mmc_writel(host, REG_WIDTH, SDXC_WIDTH1); + break; + case MMC_BUS_WIDTH_4: + mmc_writel(host, REG_WIDTH, SDXC_WIDTH4); + break; + case MMC_BUS_WIDTH_8: + mmc_writel(host, REG_WIDTH, SDXC_WIDTH8); + break; + } +} + +static void sunxi_mmc_set_clk(struct sunxi_mmc_host *host, struct mmc_ios *ios) { - struct sunxi_mmc_host *host = mmc_priv(mmc); u32 rval; - /* Set the power state */ - switch (ios->power_mode) { - case MMC_POWER_ON: - break; + /* set ddr mode */ + rval = mmc_readl(host, REG_GCTRL); + if (ios->timing == MMC_TIMING_UHS_DDR50 || + ios->timing == MMC_TIMING_MMC_DDR52) + rval |= SDXC_DDR_MODE; + else + rval &= ~SDXC_DDR_MODE; + mmc_writel(host, REG_GCTRL, rval); + + host->ferror = sunxi_mmc_clk_set_rate(host, ios); + /* Android code had a usleep_range(50000, 55000); here */ +} +static void sunxi_mmc_card_power(struct sunxi_mmc_host *host, + struct mmc_ios *ios) +{ + struct mmc_host *mmc = host->mmc; + + switch (ios->power_mode) { case MMC_POWER_UP: + dev_dbg(mmc_dev(mmc), "Powering card up\n"); + if (!IS_ERR(mmc->supply.vmmc)) { host->ferror = mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, @@ -887,53 +915,33 @@ static void sunxi_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) } host->vqmmc_enabled = true; } - - host->ferror = sunxi_mmc_init_host(mmc); - if (host->ferror) - return; - - dev_dbg(mmc_dev(mmc), "power on!\n"); break; case MMC_POWER_OFF: - dev_dbg(mmc_dev(mmc), "power off!\n"); - sunxi_mmc_reset_host(host); + dev_dbg(mmc_dev(mmc), "Powering card off\n"); + if (!IS_ERR(mmc->supply.vmmc)) mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0); if (!IS_ERR(mmc->supply.vqmmc) && host->vqmmc_enabled) regulator_disable(mmc->supply.vqmmc); + host->vqmmc_enabled = false; break; - } - /* set bus width */ - switch (ios->bus_width) { - case MMC_BUS_WIDTH_1: - mmc_writel(host, REG_WIDTH, SDXC_WIDTH1); - break; - case MMC_BUS_WIDTH_4: - mmc_writel(host, REG_WIDTH, SDXC_WIDTH4); - break; - case MMC_BUS_WIDTH_8: - mmc_writel(host, REG_WIDTH, SDXC_WIDTH8); + default: + dev_dbg(mmc_dev(mmc), "Ignoring unknown card power state\n"); break; } +} - /* set ddr mode */ - rval = mmc_readl(host, REG_GCTRL); - if (ios->timing == MMC_TIMING_UHS_DDR50 || - ios->timing == MMC_TIMING_MMC_DDR52) - rval |= SDXC_DDR_MODE; - else - rval &= ~SDXC_DDR_MODE; - mmc_writel(host, REG_GCTRL, rval); +static void sunxi_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct sunxi_mmc_host *host = mmc_priv(mmc); - /* set up clock */ - if (ios->power_mode) { - host->ferror = sunxi_mmc_clk_set_rate(host, ios); - /* Android code had a usleep_range(50000, 55000); here */ - } + sunxi_mmc_card_power(host, ios); + sunxi_mmc_set_bus_width(host, ios->bus_width); + sunxi_mmc_set_clk(host, ios); } static int sunxi_mmc_volt_switch(struct mmc_host *mmc, struct mmc_ios *ios) @@ -955,6 +963,9 @@ static void sunxi_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) unsigned long flags; u32 imask; + if (enable) + pm_runtime_get_noresume(host->dev); + spin_lock_irqsave(&host->lock, flags); imask = mmc_readl(host, REG_IMASK); @@ -967,6 +978,9 @@ static void sunxi_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) } mmc_writel(host, REG_IMASK, imask); spin_unlock_irqrestore(&host->lock, flags); + + if (!enable) + pm_runtime_put_noidle(host->mmc->parent); } static void sunxi_mmc_hw_reset(struct mmc_host *mmc) @@ -1380,6 +1394,15 @@ static int sunxi_mmc_probe(struct platform_device *pdev) if (ret) goto error_free_dma; + ret = sunxi_mmc_init_host(host); + if (ret) + goto error_free_dma; + + pm_runtime_set_active(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, 50); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_enable(&pdev->dev); + ret = mmc_add_host(mmc); if (ret) goto error_free_dma; @@ -1400,6 +1423,7 @@ static int sunxi_mmc_remove(struct platform_device *pdev) struct sunxi_mmc_host *host = mmc_priv(mmc); mmc_remove_host(mmc); + pm_runtime_force_suspend(&pdev->dev); disable_irq(host->irq); sunxi_mmc_disable(host); dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); @@ -1408,10 +1432,47 @@ static int sunxi_mmc_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int sunxi_mmc_runtime_resume(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct sunxi_mmc_host *host = mmc_priv(mmc); + int ret; + + ret = sunxi_mmc_enable(host); + if (ret) + return ret; + + sunxi_mmc_init_host(host); + sunxi_mmc_set_bus_width(host, mmc->ios.bus_width); + sunxi_mmc_set_clk(host, &mmc->ios); + + return 0; +} + +static int sunxi_mmc_runtime_suspend(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct sunxi_mmc_host *host = mmc_priv(mmc); + + sunxi_mmc_reset_host(host); + sunxi_mmc_disable(host); + + return 0; +} +#endif + +static const struct dev_pm_ops sunxi_mmc_pm_ops = { + SET_RUNTIME_PM_OPS(sunxi_mmc_runtime_suspend, + sunxi_mmc_runtime_resume, + NULL) +}; + static struct platform_driver sunxi_mmc_driver = { .driver = { .name = "sunxi-mmc", .of_match_table = of_match_ptr(sunxi_mmc_of_match), + .pm = &sunxi_mmc_pm_ops, }, .probe = sunxi_mmc_probe, .remove = sunxi_mmc_remove, |