diff options
Diffstat (limited to 'drivers/mmc/host/mmci.c')
-rw-r--r-- | drivers/mmc/host/mmci.c | 503 |
1 files changed, 287 insertions, 216 deletions
diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index ac988d7c3e0..28b335fbc8c 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -19,7 +19,6 @@ #include <linux/err.h> #include <linux/highmem.h> #include <linux/log2.h> -#include <linux/pm_runtime.h> #include <linux/mmc/pm.h> #include <linux/mmc/host.h> #include <linux/mmc/card.h> @@ -59,6 +58,7 @@ static unsigned int fmax = 515633; * @non_power_of_2_blksize: true if block sizes can be other than power of two * @pwrreg_powerup: power up value for MMCIPOWER register * @signal_direction: input/out direction of bus signals can be indicated + * @pwrreg_ctrl_power: bits in MMCIPOWER register controls ext. power supply */ struct variant_data { unsigned int clkreg; @@ -73,6 +73,7 @@ struct variant_data { bool non_power_of_2_blksize; u32 pwrreg_powerup; bool signal_direction; + bool pwrreg_ctrl_power; }; static struct variant_data variant_arm = { @@ -80,6 +81,7 @@ static struct variant_data variant_arm = { .fifohalfsize = 8 * 4, .datalength_bits = 16, .pwrreg_powerup = MCI_PWR_UP, + .pwrreg_ctrl_power = true, }; static struct variant_data variant_arm_extended_fifo = { @@ -87,6 +89,7 @@ static struct variant_data variant_arm_extended_fifo = { .fifohalfsize = 64 * 4, .datalength_bits = 16, .pwrreg_powerup = MCI_PWR_UP, + .pwrreg_ctrl_power = true, }; static struct variant_data variant_u300 = { @@ -155,6 +158,28 @@ static int mmci_validate_data(struct mmci_host *host, /* * This must be called with host->lock held */ +static void mmci_write_clkreg(struct mmci_host *host, u32 clk) +{ + if (host->clk_reg != clk) { + host->clk_reg = clk; + writel(clk, host->base + MMCICLOCK); + } +} + +/* + * This must be called with host->lock held + */ +static void mmci_write_pwrreg(struct mmci_host *host, u32 pwr) +{ + if (host->pwr_reg != pwr) { + host->pwr_reg = pwr; + writel(pwr, host->base + MMCIPOWER); + } +} + +/* + * This must be called with host->lock held + */ static void mmci_set_clkreg(struct mmci_host *host, unsigned int desired) { struct variant_data *variant = host->variant; @@ -199,7 +224,7 @@ static void mmci_set_clkreg(struct mmci_host *host, unsigned int desired) if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_8) clk |= MCI_ST_8BIT_BUS; - writel(clk, host->base + MMCICLOCK); + mmci_write_clkreg(host, clk); } static void @@ -214,8 +239,8 @@ mmci_request_end(struct mmci_host *host, struct mmc_request *mrq) mmc_request_done(host->mmc, mrq); - pm_runtime_mark_last_busy(host->mmc->parent); - pm_runtime_put_autosuspend(host->mmc->parent); + pm_runtime_mark_last_busy(mmc_dev(host->mmc)); + pm_runtime_put_autosuspend(mmc_dev(host->mmc)); } static void mmci_set_mask1(struct mmci_host *host, unsigned int mask) @@ -239,6 +264,7 @@ static void mmci_stop_data(struct mmci_host *host) writel(0, host->base + MMCIDATACTRL); mmci_set_mask1(host, 0); host->data = NULL; + host->datactrl_reg = 0; } static void mmci_init_sg(struct mmci_host *host, struct mmc_data *data) @@ -349,10 +375,33 @@ static inline void mmci_dma_release(struct mmci_host *host) host->dma_rx_channel = host->dma_tx_channel = NULL; } +static void mmci_dma_data_error(struct mmci_host *host, struct mmc_data *data) +{ + dev_err(mmc_dev(host->mmc), "error during DMA transfer!\n"); + dmaengine_terminate_all(host->dma_current); + host->dma_current = NULL; + host->dma_desc_current = NULL; + data->host_cookie = 0; +} + static void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data) { - struct dma_chan *chan = host->dma_current; + struct dma_chan *chan; enum dma_data_direction dir; + + if (data->flags & MMC_DATA_READ) { + dir = DMA_FROM_DEVICE; + chan = host->dma_rx_channel; + } else { + dir = DMA_TO_DEVICE; + chan = host->dma_tx_channel; + } + + dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, dir); +} + +static void mmci_dma_finalize(struct mmci_host *host, struct mmc_data *data) +{ u32 status; int i; @@ -371,19 +420,12 @@ static void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data) * contiguous buffers. On TX, we'll get a FIFO underrun error. */ if (status & MCI_RXDATAAVLBLMASK) { - dmaengine_terminate_all(chan); - if (!data->error) - data->error = -EIO; - } - - if (data->flags & MMC_DATA_WRITE) { - dir = DMA_TO_DEVICE; - } else { - dir = DMA_FROM_DEVICE; + data->error = -EIO; + mmci_dma_data_error(host, data); } if (!data->host_cookie) - dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, dir); + mmci_dma_unmap(host, data); /* * Use of DMA with scatter-gather is impossible. @@ -393,16 +435,15 @@ static void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data) dev_err(mmc_dev(host->mmc), "buggy DMA detected. Taking evasive action.\n"); mmci_dma_release(host); } -} -static void mmci_dma_data_error(struct mmci_host *host) -{ - dev_err(mmc_dev(host->mmc), "error during DMA transfer!\n"); - dmaengine_terminate_all(host->dma_current); + host->dma_current = NULL; + host->dma_desc_current = NULL; } -static int mmci_dma_prep_data(struct mmci_host *host, struct mmc_data *data, - struct mmci_host_next *next) +/* prepares DMA channel and DMA descriptor, returns non-zero on failure */ +static int __mmci_dma_prep_data(struct mmci_host *host, struct mmc_data *data, + struct dma_chan **dma_chan, + struct dma_async_tx_descriptor **dma_desc) { struct variant_data *variant = host->variant; struct dma_slave_config conf = { @@ -419,16 +460,6 @@ static int mmci_dma_prep_data(struct mmci_host *host, struct mmc_data *data, enum dma_data_direction buffer_dirn; int nr_sg; - /* Check if next job is already prepared */ - if (data->host_cookie && !next && - host->dma_current && host->dma_desc_current) - return 0; - - if (!next) { - host->dma_current = NULL; - host->dma_desc_current = NULL; - } - if (data->flags & MMC_DATA_READ) { conf.direction = DMA_DEV_TO_MEM; buffer_dirn = DMA_FROM_DEVICE; @@ -462,30 +493,42 @@ static int mmci_dma_prep_data(struct mmci_host *host, struct mmc_data *data, if (!desc) goto unmap_exit; - if (next) { - next->dma_chan = chan; - next->dma_desc = desc; - } else { - host->dma_current = chan; - host->dma_desc_current = desc; - } + *dma_chan = chan; + *dma_desc = desc; return 0; unmap_exit: - if (!next) - dmaengine_terminate_all(chan); dma_unmap_sg(device->dev, data->sg, data->sg_len, buffer_dirn); return -ENOMEM; } -static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) +static int inline mmci_dma_prep_data(struct mmci_host *host, + struct mmc_data *data) +{ + /* Check if next job is already prepared. */ + if (host->dma_current && host->dma_desc_current) + return 0; + + /* No job were prepared thus do it now. */ + return __mmci_dma_prep_data(host, data, &host->dma_current, + &host->dma_desc_current); +} + +static inline int mmci_dma_prep_next(struct mmci_host *host, + struct mmc_data *data) +{ + struct mmci_host_next *nd = &host->next_data; + return __mmci_dma_prep_data(host, data, &nd->dma_chan, &nd->dma_desc); +} + +static int mmci_dma_start_data(struct mmci_host *host) { int ret; struct mmc_data *data = host->data; struct variant_data *variant = host->variant; - ret = mmci_dma_prep_data(host, host->data, NULL); + ret = mmci_dma_prep_data(host, host->data); if (ret) return ret; @@ -496,15 +539,15 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) dmaengine_submit(host->dma_desc_current); dma_async_issue_pending(host->dma_current); - datactrl |= MCI_DPSM_DMAENABLE; + host->datactrl_reg |= MCI_DPSM_DMAENABLE; /* Some hardware versions need special flags for SDIO DMA write */ if (variant->sdio && host->mmc->card && mmc_card_sdio(host->mmc->card) && (data->flags & MMC_DATA_WRITE)) - datactrl |= variant->dma_sdio_req_ctrl; + host->datactrl_reg |= variant->dma_sdio_req_ctrl; /* Trigger the DMA transfer */ - writel(datactrl, host->base + MMCIDATACTRL); + writel(host->datactrl_reg, host->base + MMCIDATACTRL); /* * Let the MMCI say when the data is ended and it's time @@ -521,18 +564,14 @@ static void mmci_get_next_data(struct mmci_host *host, struct mmc_data *data) struct mmci_host_next *next = &host->next_data; if (data->host_cookie && data->host_cookie != next->cookie) { - pr_warning("[%s] invalid cookie: data->host_cookie %d" + pr_err("[%s] invalid cookie: data->host_cookie %d" " host->next_data.cookie %d\n", __func__, data->host_cookie, host->next_data.cookie); - data->host_cookie = 0; + BUG(); } - if (!data->host_cookie) - return; - host->dma_desc_current = next->dma_desc; host->dma_current = next->dma_chan; - next->dma_desc = NULL; next->dma_chan = NULL; } @@ -547,22 +586,18 @@ static void mmci_pre_request(struct mmc_host *mmc, struct mmc_request *mrq, if (!data) return; - if (mmci_validate_data(host, mrq->data)) - return; + BUG_ON(data->host_cookie); - if (data->host_cookie) { - data->host_cookie = 0; + if (mmci_validate_data(host, data)) return; - } - /* if config for dma */ - if (((data->flags & MMC_DATA_WRITE) && host->dma_tx_channel) || - ((data->flags & MMC_DATA_READ) && host->dma_rx_channel)) { - if (mmci_dma_prep_data(host, data, nd)) - data->host_cookie = 0; - else - data->host_cookie = ++nd->cookie < 0 ? 1 : nd->cookie; - } + /* + * Don't prepare DMA if there is no previous request, + * is_first_req is set. Instead, prepare DMA while + * start command is being issued. + */ + if (!is_first_req && !mmci_dma_prep_next(host, data)) + data->host_cookie = ++nd->cookie < 0 ? 1 : nd->cookie; } static void mmci_post_request(struct mmc_host *mmc, struct mmc_request *mrq, @@ -570,29 +605,23 @@ static void mmci_post_request(struct mmc_host *mmc, struct mmc_request *mrq, { struct mmci_host *host = mmc_priv(mmc); struct mmc_data *data = mrq->data; - struct dma_chan *chan; - enum dma_data_direction dir; - if (!data) + if (!data || !data->host_cookie) return; - if (data->flags & MMC_DATA_READ) { - dir = DMA_FROM_DEVICE; - chan = host->dma_rx_channel; - } else { - dir = DMA_TO_DEVICE; - chan = host->dma_tx_channel; - } + mmci_dma_unmap(host, data); + if (err) { + struct mmci_host_next *next = &host->next_data; + struct dma_chan *chan; + if (data->flags & MMC_DATA_READ) + chan = host->dma_rx_channel; + else + chan = host->dma_tx_channel; + dmaengine_terminate_all(chan); - /* if config for dma */ - if (chan) { - if (err) - dmaengine_terminate_all(chan); - if (data->host_cookie) - dma_unmap_sg(mmc_dev(host->mmc), data->sg, - data->sg_len, dir); - mrq->data->host_cookie = 0; + next->dma_desc = NULL; + next->dma_chan = NULL; } } @@ -613,11 +642,20 @@ static inline void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data) { } -static inline void mmci_dma_data_error(struct mmci_host *host) +static inline void mmci_dma_finalize(struct mmci_host *host, struct mmc_data *data) +{ +} + +static inline void mmci_dma_data_error(struct mmci_host *host, struct mmc_data *data) +{ +} + +static inline int mmci_dma_start_data(struct mmci_host *host) { + return -ENOSYS; } -static inline int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) +static inline int mmci_dma_prep_data(struct mmci_host *host, struct mmc_data *data) { return -ENOSYS; } @@ -627,10 +665,10 @@ static inline int mmci_dma_start_data(struct mmci_host *host, unsigned int datac #endif -static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) +static void mmci_setup_datactrl(struct mmci_host *host, struct mmc_data *data) { struct variant_data *variant = host->variant; - unsigned int datactrl, timeout, irqmask; + unsigned int datactrl, timeout; unsigned long long clks; void __iomem *base; int blksz_bits; @@ -664,33 +702,41 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) /* The ST Micro variants has a special bit to enable SDIO */ if (variant->sdio && host->mmc->card) if (mmc_card_sdio(host->mmc->card)) { - /* - * The ST Micro variants has a special bit - * to enable SDIO. - */ - datactrl |= MCI_ST_DPSM_SDIOEN; /* * The ST Micro variant for SDIO write transfer sizes * less then 8 bytes needs to have clock H/W flow * control disabled. */ - if ((host->size < 8) && - (data->flags & MMC_DATA_WRITE)) - writel(readl(host->base + MMCICLOCK) & - ~variant->clkreg_enable, - host->base + MMCICLOCK); + u32 clk; + if ((host->size < 8) && (data->flags & MMC_DATA_WRITE)) + clk = host->clk_reg & ~variant->clkreg_enable; else - writel(readl(host->base + MMCICLOCK) | - variant->clkreg_enable, - host->base + MMCICLOCK); + clk = host->clk_reg | variant->clkreg_enable; + + mmci_write_clkreg(host, clk); + + /* + * The ST Micro variants has a special bit + * to enable SDIO. + */ + datactrl |= MCI_ST_DPSM_SDIOEN; } + host->datactrl_reg = datactrl; + writel(datactrl, base + MMCIDATACTRL); +} + +static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) +{ + unsigned int irqmask; + struct variant_data *variant = host->variant; + void __iomem *base = host->base; /* * Attempt to use DMA operation mode, if this * should fail, fall back to PIO mode */ - if (!mmci_dma_start_data(host, datactrl)) + if (!mmci_dma_start_data(host)) return; /* IRQ mode, map the SG list for CPU reading/writing */ @@ -714,7 +760,6 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) irqmask = MCI_TXFIFOHALFEMPTYMASK; } - writel(datactrl, base + MMCIDATACTRL); writel(readl(base + MMCIMASK0) & ~MCI_DATAENDMASK, base + MMCIMASK0); mmci_set_mask1(host, irqmask); } @@ -757,8 +802,10 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, u32 remain, success; /* Terminate the DMA transfer */ - if (dma_inprogress(host)) - mmci_dma_data_error(host); + if (dma_inprogress(host)) { + mmci_dma_data_error(host, data); + mmci_dma_unmap(host, data); + } /* * Calculate how far we are into the transfer. Note that @@ -797,7 +844,7 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, if (status & MCI_DATAEND || data->error) { if (dma_inprogress(host)) - mmci_dma_unmap(host, data); + mmci_dma_finalize(host, data); mmci_stop_data(host); if (!data->error) @@ -832,14 +879,16 @@ mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd, } if (!cmd->data || cmd->error) { - if (host->data) { - /* Terminate the DMA transfer */ - if (dma_inprogress(host)) - mmci_dma_data_error(host); - mmci_stop_data(host); + /* Terminate the DMA transfer */ + if (dma_inprogress(host)) { + mmci_dma_data_error(host, host->mrq->data); + mmci_dma_unmap(host, host->mrq->data); } + if (host->data) + mmci_stop_data(host); mmci_request_end(host, cmd->mrq); } else if (!(cmd->data->flags & MMC_DATA_READ)) { + mmci_setup_datactrl(host, cmd->data); mmci_start_data(host, cmd->data); } } @@ -1058,6 +1107,7 @@ static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct mmci_host *host = mmc_priv(mmc); unsigned long flags; + bool dmaprep_after_cmd = false; WARN_ON(host->mrq != NULL); @@ -1073,14 +1123,28 @@ static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq) host->mrq = mrq; - if (mrq->data) + if (mrq->data) { + dmaprep_after_cmd = + (host->variant->clkreg_enable && + (mrq->data->flags & MMC_DATA_READ)) || + !(mrq->data->flags & MMC_DATA_READ); mmci_get_next_data(host, mrq->data); - - if (mrq->data && mrq->data->flags & MMC_DATA_READ) - mmci_start_data(host, mrq->data); + if (mrq->data->flags & MMC_DATA_READ) { + mmci_setup_datactrl(host, mrq->data); + if (!dmaprep_after_cmd) + mmci_start_data(host, mrq->data); + } + } mmci_start_command(host, mrq->cmd, 0); + if (mrq->data && dmaprep_after_cmd) { + mmci_dma_prep_data(host, mrq->data); + + if (mrq->data->flags & MMC_DATA_READ) + mmci_start_data(host, mrq->data); + } + spin_unlock_irqrestore(&host->lock, flags); } @@ -1092,7 +1156,7 @@ static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) unsigned long flags; int ret; - pm_runtime_get_sync(mmc->parent); + pm_runtime_get_sync(mmc_dev(mmc)); if (host->plat->ios_handler && host->plat->ios_handler(mmc_dev(mmc), ios)) @@ -1114,7 +1178,7 @@ static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) * power should be rare so we print an error * and return here. */ - return; + goto out; } } /* @@ -1161,16 +1225,13 @@ static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) spin_lock_irqsave(&host->lock, flags); mmci_set_clkreg(host, ios->clock); - - if (host->pwr_reg != pwr) { - host->pwr_reg = pwr; - writel(pwr, host->base + MMCIPOWER); - } + mmci_write_pwrreg(host, pwr); spin_unlock_irqrestore(&host->lock, flags); - pm_runtime_mark_last_busy(mmc->parent); - pm_runtime_put_autosuspend(mmc->parent); + out: + pm_runtime_mark_last_busy(mmc_dev(mmc)); + pm_runtime_put_autosuspend(mmc_dev(mmc)); } static int mmci_get_ro(struct mmc_host *mmc) @@ -1205,6 +1266,21 @@ static int mmci_get_cd(struct mmc_host *mmc) return status; } +static int mmci_sig_volt_switch(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct mmci_host *host = mmc_priv(mmc); + int ret = 0; + + if (host->plat->ios_handler) { + pm_runtime_get_sync(mmc_dev(mmc)); + ret = host->plat->ios_handler(mmc_dev(mmc), ios); + pm_runtime_mark_last_busy(mmc_dev(mmc)); + pm_runtime_put_autosuspend(mmc_dev(mmc)); + } + + return ret; +} + static irqreturn_t mmci_cd_irq(int irq, void *dev_id) { struct mmci_host *host = dev_id; @@ -1221,6 +1297,7 @@ static const struct mmc_host_ops mmci_ops = { .set_ios = mmci_set_ios, .get_ro = mmci_get_ro, .get_cd = mmci_get_cd, + .start_signal_voltage_switch = mmci_sig_volt_switch, }; static int __devinit mmci_probe(struct amba_device *dev, @@ -1254,9 +1331,6 @@ static int __devinit mmci_probe(struct amba_device *dev, host->gpio_wp = -ENOSYS; host->gpio_cd = -ENOSYS; host->gpio_cd_irq = -1; - host->irqmask0_reg = 0; - host->pwr_reg = 0; - host->clk_reg = 0; host->hw_designer = amba_manf(dev); host->hw_revision = amba_rev(dev); @@ -1438,7 +1512,6 @@ static int __devinit mmci_probe(struct amba_device *dev, goto irq0_free; } - host->irqmask0_reg = MCI_IRQENABLE; writel(MCI_IRQENABLE, host->base + MMCIMASK0); amba_set_drvdata(dev, mmc); @@ -1450,8 +1523,8 @@ static int __devinit mmci_probe(struct amba_device *dev, mmci_dma_setup(host); - pm_runtime_set_autosuspend_delay(mmc->parent, 50); - pm_runtime_use_autosuspend(mmc->parent); + pm_runtime_set_autosuspend_delay(&dev->dev, 50); + pm_runtime_use_autosuspend(&dev->dev); pm_runtime_put(&dev->dev); mmc_add_host(mmc); @@ -1494,11 +1567,10 @@ static int __devexit mmci_remove(struct amba_device *dev) struct mmci_host *host = mmc_priv(mmc); /* - * Make sure the host is resumed and undo the - * pm_runtime_put in probe. + * Undo pm_runtime_put() in probe. We use the _sync + * version here so that we can access the primecell. */ - pm_runtime_resume(mmc->parent); - pm_runtime_get_noresume(mmc->parent); + pm_runtime_get_sync(&dev->dev); mmc_remove_host(mmc); @@ -1521,7 +1593,6 @@ static int __devexit mmci_remove(struct amba_device *dev) gpio_free(host->gpio_cd); iounmap(host->base); - clk_disable(host->clk); clk_unprepare(host->clk); clk_put(host->clk); @@ -1538,72 +1609,18 @@ static int __devexit mmci_remove(struct amba_device *dev) return 0; } -#ifdef CONFIG_SUSPEND - -#ifdef CONFIG_PM_RUNTIME -static void mmci_disable_irq(struct mmci_host *host) {} -static void mmci_enable_irq(struct mmci_host *host) {} -#else -static void mmci_disable_irq(struct mmci_host *host) -{ - writel(0, host->base + MMCIMASK0); -} -static void mmci_enable_irq(struct mmci_host *host) -{ - writel(host->irqmask0_reg, host->base + MMCIMASK0); -} -#endif - -static int mmci_suspend(struct device *dev) +#if defined(CONFIG_SUSPEND) || defined(CONFIG_PM_RUNTIME) +static int mmci_save(struct amba_device *dev) { - struct amba_device *adev = to_amba_device(dev); - struct mmc_host *mmc = amba_get_drvdata(adev); - int ret = 0; - - if (mmc) { - struct mmci_host *host = mmc_priv(mmc); - - ret = mmc_suspend_host(mmc); - if (!ret) - mmci_disable_irq(host); - } - - return ret; -} - -static int mmci_resume(struct device *dev) -{ - struct amba_device *adev = to_amba_device(dev); - struct mmc_host *mmc = amba_get_drvdata(adev); - int ret = 0; - - if (mmc) { - struct mmci_host *host = mmc_priv(mmc); - - mmci_enable_irq(host); - ret = mmc_resume_host(mmc); - } - - return ret; -} -#endif - -#ifdef CONFIG_PM_RUNTIME -static int mmci_runtime_suspend(struct device *dev) -{ - struct amba_device *adev = to_amba_device(dev); - struct mmc_host *mmc = amba_get_drvdata(adev); + struct mmc_host *mmc = amba_get_drvdata(dev); unsigned long flags; - int ret; struct mmc_ios ios; + int ret = 0; if (mmc) { struct mmci_host *host = mmc_priv(mmc); - /* - * Let the ios_handler act on a POWER_OFF to potentially do some - * power save actions. - */ + /* Let the ios_handler act on a POWER_OFF to save power. */ if (host->plat->ios_handler) { memcpy(&ios, &mmc->ios, sizeof(struct mmc_ios)); ios.power_mode = MMC_POWER_OFF; @@ -1615,11 +1632,6 @@ static int mmci_runtime_suspend(struct device *dev) spin_lock_irqsave(&host->lock, flags); - /* Save registers for POWER, CLOCK and IRQMASK0 */ - host->irqmask0_reg = readl(host->base + MMCIMASK0); - host->pwr_reg = readl(host->base + MMCIPOWER); - host->clk_reg = readl(host->base + MMCICLOCK); - /* * Make sure we do not get any interrupts when we disabled the * clock and the regulator and as well make sure to clear the @@ -1632,37 +1644,33 @@ static int mmci_runtime_suspend(struct device *dev) spin_unlock_irqrestore(&host->lock, flags); clk_disable(host->clk); - amba_vcore_disable(adev); + amba_vcore_disable(dev); } - return 0; + return ret; } -static int mmci_runtime_resume(struct device *dev) +static int mmci_restore(struct amba_device *dev) { - struct amba_device *adev = to_amba_device(dev); - struct mmc_host *mmc = amba_get_drvdata(adev); + struct mmc_host *mmc = amba_get_drvdata(dev); unsigned long flags; if (mmc) { struct mmci_host *host = mmc_priv(mmc); - amba_vcore_enable(adev); + amba_vcore_enable(dev); clk_enable(host->clk); spin_lock_irqsave(&host->lock, flags); - /* Restore registers for POWER, CLOCK and IRQMASK0 */ + /* Restore registers and re-enable interrupts. */ writel(host->clk_reg, host->base + MMCICLOCK); writel(host->pwr_reg, host->base + MMCIPOWER); - writel(host->irqmask0_reg, host->base + MMCIMASK0); + writel(MCI_IRQENABLE, host->base + MMCIMASK0); spin_unlock_irqrestore(&host->lock, flags); - /* - * Restore settings done by the ios_handler. This shall be done - * quickly to keep request latency low. - */ + /* Restore settings done by the ios_handler. */ if (host->plat->ios_handler) host->plat->ios_handler(mmc_dev(mmc), &mmc->ios); @@ -1670,19 +1678,82 @@ static int mmci_runtime_resume(struct device *dev) return 0; } +#endif -static int mmci_runtime_idle(struct device *dev) +#ifdef CONFIG_SUSPEND +static int mmci_suspend(struct device *dev) { - pm_runtime_suspend(dev); - return 0; + struct amba_device *adev = to_amba_device(dev); + struct mmc_host *mmc = amba_get_drvdata(adev); + int ret = 0; + + if (mmc) { + ret = mmc_suspend_host(mmc); + if (ret == 0) { + pm_runtime_get_sync(dev); + mmci_save(adev); + amba_pclk_disable(adev); + } + } + + return ret; +} + +static int mmci_resume(struct device *dev) +{ + struct amba_device *adev = to_amba_device(dev); + struct mmc_host *mmc = amba_get_drvdata(adev); + int ret = 0; + + if (mmc) { + amba_pclk_enable(adev); + mmci_restore(adev); + pm_runtime_put(dev); + + ret = mmc_resume_host(mmc); + } + + return ret; +} +#endif + +#ifdef CONFIG_PM_RUNTIME +static int mmci_runtime_suspend(struct device *dev) +{ + struct amba_device *adev = to_amba_device(dev); + struct mmc_host *mmc = amba_get_drvdata(adev); + int ret = 0; + + if (mmc) { + struct mmci_host *host = mmc_priv(mmc); + struct variant_data *variant = host->variant; + if (!variant->pwrreg_ctrl_power) + ret = mmci_save(adev); + } + + return ret; +} + +static int mmci_runtime_resume(struct device *dev) +{ + struct amba_device *adev = to_amba_device(dev); + struct mmc_host *mmc = amba_get_drvdata(adev); + int ret = 0; + + if (mmc) { + struct mmci_host *host = mmc_priv(mmc); + struct variant_data *variant = host->variant; + if (!variant->pwrreg_ctrl_power) + ret = mmci_restore(adev); + } + + return ret; } #endif static const struct dev_pm_ops mmci_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(mmci_suspend, mmci_resume) - SET_RUNTIME_PM_OPS(mmci_runtime_suspend, - mmci_runtime_resume, - mmci_runtime_idle) + SET_RUNTIME_PM_OPS(mmci_runtime_suspend, mmci_runtime_resume, NULL) }; static struct amba_id mmci_ids[] = { |