diff options
author | Russell King <rmk+kernel@arm.linux.org.uk> | 2012-03-24 00:10:36 +0000 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2012-03-24 00:10:36 +0000 |
commit | bba1594d348b59d6172e02bf74fba837c8273989 (patch) | |
tree | 223e67a4ad043d4ec9361e89c59592ea60e7ddff /drivers/mmc/host/mmci.c | |
parent | 9e5ed094c89e55fbf11d2e81d60be98eb12346c0 (diff) | |
parent | 7437cfa532842ce75189826742bddf1ba137f58e (diff) |
Merge branch 'mmci' into amba
Diffstat (limited to 'drivers/mmc/host/mmci.c')
-rw-r--r-- | drivers/mmc/host/mmci.c | 166 |
1 files changed, 121 insertions, 45 deletions
diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 6692392c05d..deb73e2b746 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -53,6 +53,8 @@ static unsigned int fmax = 515633; * @sdio: variant supports SDIO * @st_clkdiv: true if using a ST-specific clock divider algorithm * @blksz_datactrl16: true if Block size is at b16..b30 position in datactrl register + * @pwrreg_powerup: power up value for MMCIPOWER register + * @signal_direction: input/out direction of bus signals can be indicated */ struct variant_data { unsigned int clkreg; @@ -63,18 +65,22 @@ struct variant_data { bool sdio; bool st_clkdiv; bool blksz_datactrl16; + u32 pwrreg_powerup; + bool signal_direction; }; static struct variant_data variant_arm = { .fifosize = 16 * 4, .fifohalfsize = 8 * 4, .datalength_bits = 16, + .pwrreg_powerup = MCI_PWR_UP, }; static struct variant_data variant_arm_extended_fifo = { .fifosize = 128 * 4, .fifohalfsize = 64 * 4, .datalength_bits = 16, + .pwrreg_powerup = MCI_PWR_UP, }; static struct variant_data variant_u300 = { @@ -83,6 +89,8 @@ static struct variant_data variant_u300 = { .clkreg_enable = MCI_ST_U300_HWFCEN, .datalength_bits = 16, .sdio = true, + .pwrreg_powerup = MCI_PWR_ON, + .signal_direction = true, }; static struct variant_data variant_ux500 = { @@ -93,6 +101,8 @@ static struct variant_data variant_ux500 = { .datalength_bits = 24, .sdio = true, .st_clkdiv = true, + .pwrreg_powerup = MCI_PWR_ON, + .signal_direction = true, }; static struct variant_data variant_ux500v2 = { @@ -104,11 +114,35 @@ static struct variant_data variant_ux500v2 = { .sdio = true, .st_clkdiv = true, .blksz_datactrl16 = true, + .pwrreg_powerup = MCI_PWR_ON, + .signal_direction = true, }; /* * 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; @@ -153,7 +187,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 @@ -166,14 +200,10 @@ mmci_request_end(struct mmci_host *host, struct mmc_request *mrq) host->mrq = NULL; host->cmd = NULL; - /* - * Need to drop the host lock here; mmc_request_done may call - * back into the driver... - */ - spin_unlock(&host->lock); - pm_runtime_put(mmc_dev(host->mmc)); mmc_request_done(host->mmc, mrq); - spin_lock(&host->lock); + + 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) @@ -607,6 +637,11 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) if (data->flags & MMC_DATA_READ) datactrl |= MCI_DPSM_DIRECTION; + /* The ST Micro variants has a special bit to enable SDIO */ + if (variant->sdio && host->mmc->card) + if (mmc_card_sdio(host->mmc->card)) + datactrl |= MCI_ST_DPSM_SDIOEN; + /* * Attempt to use DMA operation mode, if this * should fail, fall back to PIO mode @@ -635,11 +670,6 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) irqmask = MCI_TXFIFOHALFEMPTYMASK; } - /* The ST Micro variants has a special bit to enable SDIO */ - if (variant->sdio && host->mmc->card) - if (mmc_card_sdio(host->mmc->card)) - datactrl |= MCI_ST_DPSM_SDIOEN; - writel(datactrl, base + MMCIDATACTRL); writel(readl(base + MMCIMASK0) & ~MCI_DATAENDMASK, base + MMCIMASK0); mmci_set_mask1(host, irqmask); @@ -786,7 +816,24 @@ static int mmci_pio_read(struct mmci_host *host, char *buffer, unsigned int rema if (count <= 0) break; - readsl(base + MMCIFIFO, ptr, count >> 2); + /* + * SDIO especially may want to send something that is + * not divisible by 4 (as opposed to card sectors + * etc). Therefore make sure to always read the last bytes + * while only doing full 32-bit reads towards the FIFO. + */ + if (unlikely(count & 0x3)) { + if (count < 4) { + unsigned char buf[4]; + readsl(base + MMCIFIFO, buf, 1); + memcpy(ptr, buf, count); + } else { + readsl(base + MMCIFIFO, ptr, count >> 2); + count &= ~0x3; + } + } else { + readsl(base + MMCIFIFO, ptr, count >> 2); + } ptr += count; remain -= count; @@ -821,14 +868,13 @@ static int mmci_pio_write(struct mmci_host *host, char *buffer, unsigned int rem */ if (variant->sdio && mmc_card_sdio(host->mmc->card)) { + u32 clk; if (count < 8) - writel(readl(host->base + MMCICLOCK) & - ~variant->clkreg_enable, - host->base + MMCICLOCK); + 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); } /* @@ -1015,10 +1061,17 @@ static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq) static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct mmci_host *host = mmc_priv(mmc); + struct variant_data *variant = host->variant; u32 pwr = 0; unsigned long flags; int ret; + pm_runtime_get_sync(mmc_dev(mmc)); + + if (host->plat->ios_handler && + host->plat->ios_handler(mmc_dev(mmc), ios)) + dev_err(mmc_dev(mmc), "platform ios_handler failed\n"); + switch (ios->power_mode) { case MMC_POWER_OFF: if (host->vcc) @@ -1035,22 +1088,38 @@ 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; } } - if (host->plat->vdd_handler) - pwr |= host->plat->vdd_handler(mmc_dev(mmc), ios->vdd, - ios->power_mode); - /* The ST version does not have this, fall through to POWER_ON */ - if (host->hw_designer != AMBA_VENDOR_ST) { - pwr |= MCI_PWR_UP; - break; - } + /* + * The ST Micro variant doesn't have the PL180s MCI_PWR_UP + * and instead uses MCI_PWR_ON so apply whatever value is + * configured in the variant data. + */ + pwr |= variant->pwrreg_powerup; + + break; case MMC_POWER_ON: pwr |= MCI_PWR_ON; break; } + if (variant->signal_direction && ios->power_mode != MMC_POWER_OFF) { + /* + * The ST Micro variant has some additional bits + * indicating signal direction for the signals in + * the SD/MMC bus and feedback-clock usage. + */ + pwr |= host->plat->sigdir; + + if (ios->bus_width == MMC_BUS_WIDTH_4) + pwr &= ~MCI_ST_DATA74DIREN; + else if (ios->bus_width == MMC_BUS_WIDTH_1) + pwr &= (~MCI_ST_DATA74DIREN & + ~MCI_ST_DATA31DIREN & + ~MCI_ST_DATA2DIREN); + } + if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) { if (host->hw_designer != AMBA_VENDOR_ST) pwr |= MCI_ROD; @@ -1066,13 +1135,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 != pwr) { - host->pwr = pwr; - writel(pwr, host->base + MMCIPOWER); - } + mmci_write_pwrreg(host, pwr); spin_unlock_irqrestore(&host->lock, flags); + + 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) @@ -1345,6 +1414,8 @@ static int __devinit mmci_probe(struct amba_device *dev, mmci_dma_setup(host); + pm_runtime_set_autosuspend_delay(&dev->dev, 50); + pm_runtime_use_autosuspend(&dev->dev); pm_runtime_put(&dev->dev); mmc_add_host(mmc); @@ -1429,43 +1500,49 @@ static int __devexit mmci_remove(struct amba_device *dev) return 0; } -#ifdef CONFIG_PM -static int mmci_suspend(struct amba_device *dev, pm_message_t state) +#ifdef CONFIG_SUSPEND +static int mmci_suspend(struct device *dev) { - struct mmc_host *mmc = amba_get_drvdata(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 == 0) + if (ret == 0) { + pm_runtime_get_sync(dev); writel(0, host->base + MMCIMASK0); + } } return ret; } -static int mmci_resume(struct amba_device *dev) +static int mmci_resume(struct device *dev) { - struct mmc_host *mmc = amba_get_drvdata(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); writel(MCI_IRQENABLE, host->base + MMCIMASK0); + pm_runtime_put(dev); ret = mmc_resume_host(mmc); } return ret; } -#else -#define mmci_suspend NULL -#define mmci_resume NULL #endif +static const struct dev_pm_ops mmci_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mmci_suspend, mmci_resume) +}; + static struct amba_id mmci_ids[] = { { .id = 0x00041180, @@ -1511,11 +1588,10 @@ MODULE_DEVICE_TABLE(amba, mmci_ids); static struct amba_driver mmci_driver = { .drv = { .name = DRIVER_NAME, + .pm = &mmci_dev_pm_ops, }, .probe = mmci_probe, .remove = __devexit_p(mmci_remove), - .suspend = mmci_suspend, - .resume = mmci_resume, .id_table = mmci_ids, }; |