diff options
Diffstat (limited to 'drivers/mmc/host/atmel-mci.c')
-rw-r--r-- | drivers/mmc/host/atmel-mci.c | 207 |
1 files changed, 113 insertions, 94 deletions
diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c index 77250d4b1979..62aba9af19f4 100644 --- a/drivers/mmc/host/atmel-mci.c +++ b/drivers/mmc/host/atmel-mci.c @@ -30,13 +30,16 @@ #include <linux/stat.h> #include <linux/types.h> #include <linux/platform_data/atmel.h> +#include <linux/platform_data/mmc-atmel-mci.h> #include <linux/mmc/host.h> #include <linux/mmc/sdio.h> -#include <mach/atmel-mci.h> #include <linux/atmel-mci.h> #include <linux/atmel_pdc.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/pinctrl/consumer.h> #include <asm/cacheflush.h> #include <asm/io.h> @@ -44,6 +47,8 @@ #include "atmel-mci-regs.h" +#define AUTOSUSPEND_DELAY 50 + #define ATMCI_DATA_ERROR_FLAGS (ATMCI_DCRCE | ATMCI_DTOE | ATMCI_OVRE | ATMCI_UNRE) #define ATMCI_DMA_THRESHOLD 16 @@ -386,20 +391,19 @@ static int atmci_regs_show(struct seq_file *s, void *v) if (!buf) return -ENOMEM; + pm_runtime_get_sync(&host->pdev->dev); + /* * Grab a more or less consistent snapshot. Note that we're * not disabling interrupts, so IMR and SR may not be * consistent. */ - ret = clk_prepare_enable(host->mck); - if (ret) - goto out; - spin_lock_bh(&host->lock); memcpy_fromio(buf, host->regs, ATMCI_REGS_SIZE); spin_unlock_bh(&host->lock); - clk_disable_unprepare(host->mck); + pm_runtime_mark_last_busy(&host->pdev->dev); + pm_runtime_put_autosuspend(&host->pdev->dev); seq_printf(s, "MR:\t0x%08x%s%s ", buf[ATMCI_MR / 4], @@ -449,7 +453,6 @@ static int atmci_regs_show(struct seq_file *s, void *v) val & ATMCI_CFG_LSYNC ? " LSYNC" : ""); } -out: kfree(buf); return ret; @@ -560,6 +563,9 @@ atmci_of_init(struct platform_device *pdev) pdata->slot[slot_id].detect_is_active_high = of_property_read_bool(cnp, "cd-inverted"); + pdata->slot[slot_id].non_removable = + of_property_read_bool(cnp, "non-removable"); + pdata->slot[slot_id].wp_pin = of_get_named_gpio(cnp, "wp-gpios", 0); } @@ -1252,6 +1258,8 @@ static void atmci_request(struct mmc_host *mmc, struct mmc_request *mrq) WARN_ON(slot->mrq); dev_dbg(&host->pdev->dev, "MRQ: cmd %u\n", mrq->cmd->opcode); + pm_runtime_get_sync(&host->pdev->dev); + /* * We may "know" the card is gone even though there's still an * electrical connection. If so, we really need to communicate @@ -1281,7 +1289,8 @@ static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) struct atmel_mci_slot *slot = mmc_priv(mmc); struct atmel_mci *host = slot->host; unsigned int i; - bool unprepare_clk; + + pm_runtime_get_sync(&host->pdev->dev); slot->sdc_reg &= ~ATMCI_SDCBUS_MASK; switch (ios->bus_width) { @@ -1297,13 +1306,8 @@ static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) unsigned int clock_min = ~0U; u32 clkdiv; - clk_prepare(host->mck); - unprepare_clk = true; - spin_lock_bh(&host->lock); if (!host->mode_reg) { - clk_enable(host->mck); - unprepare_clk = false; atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST); atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIEN); if (host->caps.has_cfg_reg) @@ -1371,8 +1375,6 @@ static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) } else { bool any_slot_active = false; - unprepare_clk = false; - spin_lock_bh(&host->lock); slot->clock = 0; for (i = 0; i < ATMCI_MAX_NR_SLOTS; i++) { @@ -1385,17 +1387,12 @@ static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIDIS); if (host->mode_reg) { atmci_readl(host, ATMCI_MR); - clk_disable(host->mck); - unprepare_clk = true; } host->mode_reg = 0; } spin_unlock_bh(&host->lock); } - if (unprepare_clk) - clk_unprepare(host->mck); - switch (ios->power_mode) { case MMC_POWER_OFF: if (!IS_ERR(mmc->supply.vmmc)) @@ -1421,6 +1418,9 @@ static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) */ break; } + + pm_runtime_mark_last_busy(&host->pdev->dev); + pm_runtime_put_autosuspend(&host->pdev->dev); } static int atmci_get_ro(struct mmc_host *mmc) @@ -1512,6 +1512,9 @@ static void atmci_request_end(struct atmel_mci *host, struct mmc_request *mrq) spin_unlock(&host->lock); mmc_request_done(prev_mmc, mrq); spin_lock(&host->lock); + + pm_runtime_mark_last_busy(&host->pdev->dev); + pm_runtime_put_autosuspend(&host->pdev->dev); } static void atmci_command_complete(struct atmel_mci *host, @@ -2137,7 +2140,7 @@ static irqreturn_t atmci_detect_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } -static int __init atmci_init_slot(struct atmel_mci *host, +static int atmci_init_slot(struct atmel_mci *host, struct mci_slot_pdata *slot_data, unsigned int id, u32 sdc_reg, u32 sdio_irq) { @@ -2206,8 +2209,12 @@ static int __init atmci_init_slot(struct atmel_mci *host, } } - if (!gpio_is_valid(slot->detect_pin)) - mmc->caps |= MMC_CAP_NEEDS_POLL; + if (!gpio_is_valid(slot->detect_pin)) { + if (slot_data->non_removable) + mmc->caps |= MMC_CAP_NONREMOVABLE; + else + mmc->caps |= MMC_CAP_NEEDS_POLL; + } if (gpio_is_valid(slot->wp_pin)) { if (devm_gpio_request(&host->pdev->dev, slot->wp_pin, @@ -2265,55 +2272,25 @@ static void atmci_cleanup_slot(struct atmel_mci_slot *slot, mmc_free_host(slot->mmc); } -static bool atmci_filter(struct dma_chan *chan, void *pdata) +static int atmci_configure_dma(struct atmel_mci *host) { - struct mci_platform_data *sl_pdata = pdata; - struct mci_dma_data *sl; - - if (!sl_pdata) - return false; + host->dma.chan = dma_request_slave_channel_reason(&host->pdev->dev, + "rxtx"); + if (IS_ERR(host->dma.chan)) + return PTR_ERR(host->dma.chan); + + dev_info(&host->pdev->dev, "using %s for DMA transfers\n", + dma_chan_name(host->dma.chan)); + + host->dma_conf.src_addr = host->mapbase + ATMCI_RDR; + host->dma_conf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_conf.src_maxburst = 1; + host->dma_conf.dst_addr = host->mapbase + ATMCI_TDR; + host->dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_conf.dst_maxburst = 1; + host->dma_conf.device_fc = false; - sl = sl_pdata->dma_slave; - if (sl && find_slave_dev(sl) == chan->device->dev) { - chan->private = slave_data_ptr(sl); - return true; - } else { - return false; - } -} - -static bool atmci_configure_dma(struct atmel_mci *host) -{ - struct mci_platform_data *pdata; - dma_cap_mask_t mask; - - if (host == NULL) - return false; - - pdata = host->pdev->dev.platform_data; - - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - - host->dma.chan = dma_request_slave_channel_compat(mask, atmci_filter, pdata, - &host->pdev->dev, "rxtx"); - if (!host->dma.chan) { - dev_warn(&host->pdev->dev, "no DMA channel available\n"); - return false; - } else { - dev_info(&host->pdev->dev, - "using %s for DMA transfers\n", - dma_chan_name(host->dma.chan)); - - host->dma_conf.src_addr = host->mapbase + ATMCI_RDR; - host->dma_conf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - host->dma_conf.src_maxburst = 1; - host->dma_conf.dst_addr = host->mapbase + ATMCI_TDR; - host->dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - host->dma_conf.dst_maxburst = 1; - host->dma_conf.device_fc = false; - return true; - } + return 0; } /* @@ -2321,7 +2298,7 @@ static bool atmci_configure_dma(struct atmel_mci *host) * HSMCI provides DMA support and a new config register but no more supports * PDC. */ -static void __init atmci_get_cap(struct atmel_mci *host) +static void atmci_get_cap(struct atmel_mci *host) { unsigned int version; @@ -2370,7 +2347,7 @@ static void __init atmci_get_cap(struct atmel_mci *host) } } -static int __init atmci_probe(struct platform_device *pdev) +static int atmci_probe(struct platform_device *pdev) { struct mci_platform_data *pdata; struct atmel_mci *host; @@ -2417,19 +2394,23 @@ static int __init atmci_probe(struct platform_device *pdev) atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST); host->bus_hz = clk_get_rate(host->mck); - clk_disable_unprepare(host->mck); host->mapbase = regs->start; tasklet_init(&host->tasklet, atmci_tasklet_func, (unsigned long)host); ret = request_irq(irq, atmci_interrupt, 0, dev_name(&pdev->dev), host); - if (ret) + if (ret) { + clk_disable_unprepare(host->mck); return ret; + } /* Get MCI capabilities and set operations according to it */ atmci_get_cap(host); - if (atmci_configure_dma(host)) { + ret = atmci_configure_dma(host); + if (ret == -EPROBE_DEFER) + goto err_dma_probe_defer; + if (ret == 0) { host->prepare_data = &atmci_prepare_data_dma; host->submit_data = &atmci_submit_data_dma; host->stop_transfer = &atmci_stop_transfer_dma; @@ -2449,6 +2430,12 @@ static int __init atmci_probe(struct platform_device *pdev) setup_timer(&host->timer, atmci_timeout_timer, (unsigned long)host); + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_enable(&pdev->dev); + /* We need at least one slot to succeed */ nr_slots = 0; ret = -ENODEV; @@ -2491,6 +2478,9 @@ static int __init atmci_probe(struct platform_device *pdev) "Atmel MCI controller at 0x%08lx irq %d, %u slots\n", host->mapbase, irq, nr_slots); + pm_runtime_mark_last_busy(&host->pdev->dev); + pm_runtime_put_autosuspend(&pdev->dev); + return 0; err_dma_alloc: @@ -2499,18 +2489,26 @@ err_dma_alloc: atmci_cleanup_slot(host->slot[i], i); } err_init_slot: + clk_disable_unprepare(host->mck); + + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + del_timer_sync(&host->timer); - if (host->dma.chan) + if (!IS_ERR(host->dma.chan)) dma_release_channel(host->dma.chan); +err_dma_probe_defer: free_irq(irq, host); return ret; } -static int __exit atmci_remove(struct platform_device *pdev) +static int atmci_remove(struct platform_device *pdev) { struct atmel_mci *host = platform_get_drvdata(pdev); unsigned int i; + pm_runtime_get_sync(&pdev->dev); + if (host->buffer) dma_free_coherent(&pdev->dev, host->buf_size, host->buffer, host->buf_phys_addr); @@ -2520,41 +2518,62 @@ static int __exit atmci_remove(struct platform_device *pdev) atmci_cleanup_slot(host->slot[i], i); } - clk_prepare_enable(host->mck); atmci_writel(host, ATMCI_IDR, ~0UL); atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIDIS); atmci_readl(host, ATMCI_SR); - clk_disable_unprepare(host->mck); del_timer_sync(&host->timer); - if (host->dma.chan) + if (!IS_ERR(host->dma.chan)) dma_release_channel(host->dma.chan); free_irq(platform_get_irq(pdev, 0), host); + clk_disable_unprepare(host->mck); + + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + return 0; } -static struct platform_driver atmci_driver = { - .remove = __exit_p(atmci_remove), - .driver = { - .name = "atmel_mci", - .of_match_table = of_match_ptr(atmci_dt_ids), - }, -}; - -static int __init atmci_init(void) +#ifdef CONFIG_PM +static int atmci_runtime_suspend(struct device *dev) { - return platform_driver_probe(&atmci_driver, atmci_probe); + struct atmel_mci *host = dev_get_drvdata(dev); + + clk_disable_unprepare(host->mck); + + pinctrl_pm_select_sleep_state(dev); + + return 0; } -static void __exit atmci_exit(void) +static int atmci_runtime_resume(struct device *dev) { - platform_driver_unregister(&atmci_driver); + struct atmel_mci *host = dev_get_drvdata(dev); + + pinctrl_pm_select_default_state(dev); + + return clk_prepare_enable(host->mck); } +#endif -late_initcall(atmci_init); /* try to load after dma driver when built-in */ -module_exit(atmci_exit); +static const struct dev_pm_ops atmci_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_PM_RUNTIME_PM_OPS(atmci_runtime_suspend, atmci_runtime_resume, NULL) +}; + +static struct platform_driver atmci_driver = { + .probe = atmci_probe, + .remove = atmci_remove, + .driver = { + .name = "atmel_mci", + .of_match_table = of_match_ptr(atmci_dt_ids), + .pm = &atmci_dev_pm_ops, + }, +}; +module_platform_driver(atmci_driver); MODULE_DESCRIPTION("Atmel Multimedia Card Interface driver"); MODULE_AUTHOR("Haavard Skinnemoen (Atmel)"); |