diff options
Diffstat (limited to 'drivers/mmc')
-rw-r--r-- | drivers/mmc/host/Kconfig | 2 | ||||
-rwxr-xr-x[-rw-r--r--] | drivers/mmc/host/sdhci-s3c.c | 107 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.c | 120 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.h | 3 |
4 files changed, 207 insertions, 25 deletions
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 56dbf3f..0ff3b56 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -204,7 +204,7 @@ config MMC_SDHCI_SPEAR config MMC_SDHCI_S3C_DMA bool "DMA support on S3C SDHCI" - depends on MMC_SDHCI_S3C && EXPERIMENTAL + depends on MMC_SDHCI_S3C help Enable DMA support on the Samsung S3C SDHCI glue. The DMA has proved to be problematic if the controller encounters diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index 8cd999f..fb3eba6 100644..100755 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -21,6 +21,7 @@ #include <linux/gpio.h> #include <linux/mmc/host.h> +#include <linux/mmc/card.h> #include <plat/sdhci.h> #include <plat/regs-sdhci.h> @@ -45,6 +46,7 @@ struct sdhci_s3c { struct resource *ioarea; struct s3c_sdhci_platdata *pdata; unsigned int cur_clk; + bool cur_clk_set; int ext_cd_irq; int ext_cd_gpio; @@ -79,7 +81,7 @@ static void sdhci_s3c_check_sclk(struct sdhci_host *host) tmp &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK; tmp |= ourhost->cur_clk << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; - writel(tmp, host->ioaddr + 0x80); + writel(tmp, host->ioaddr + S3C_SDHCI_CONTROL2); } } @@ -113,6 +115,46 @@ static unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host) return max; } +static void sdhci_s3c_set_ios(struct sdhci_host *host, + struct mmc_ios *ios) +{ + struct sdhci_s3c *ourhost = to_s3c(host); + struct s3c_sdhci_platdata *pdata = ourhost->pdata; + int width; + u8 tmp; + + sdhci_s3c_check_sclk(host); + + if (ios->power_mode != MMC_POWER_OFF) { + switch (ios->bus_width) { + case MMC_BUS_WIDTH_8: + width = 8; + tmp = readb(host->ioaddr + SDHCI_HOST_CONTROL); + writeb(tmp | SDHCI_S3C_CTRL_8BITBUS, + host->ioaddr + SDHCI_HOST_CONTROL); + break; + case MMC_BUS_WIDTH_4: + width = 4; + break; + case MMC_BUS_WIDTH_1: + width = 1; + break; + default: + BUG(); + } + + if (pdata->cfg_gpio) + pdata->cfg_gpio(ourhost->pdev, width); + } + + if (pdata->cfg_card) { + pdata->cfg_card(ourhost->pdev, host->ioaddr, + ios, host->mmc->card); + pdata->rx_cfg = 0; + pdata->tx_cfg = 0; + } +} + /** * sdhci_s3c_consider_clock - consider one the bus clocks for current setting * @ourhost: Our SDHCI instance. @@ -187,13 +229,14 @@ static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock) /* select the new clock source */ - if (ourhost->cur_clk != best_src) { + if (ourhost->cur_clk != best_src || !ourhost->cur_clk_set) { struct clk *clk = ourhost->clk_bus[best_src]; /* turn clock off to card before changing clock source */ writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL); ourhost->cur_clk = best_src; + ourhost->cur_clk_set = true; host->max_clk = clk_get_rate(clk); ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2); @@ -201,18 +244,15 @@ static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock) ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2); } +} - /* reconfigure the hardware for new clock rate */ - - { - struct mmc_ios ios; - - ios.clock = clock; +static void sdhci_s3c_adjust_cfg(struct sdhci_host *host, int rw) +{ + struct sdhci_s3c *ourhost = to_s3c(host); + struct s3c_sdhci_platdata *pdata = ourhost->pdata; - if (ourhost->pdata->cfg_card) - (ourhost->pdata->cfg_card)(ourhost->pdev, host->ioaddr, - &ios, NULL); - } + if(pdata->adjust_cfg_card) + pdata->adjust_cfg_card(pdata, host->ioaddr, rw); } /** @@ -316,6 +356,8 @@ static struct sdhci_ops sdhci_s3c_ops = { .set_clock = sdhci_s3c_set_clock, .get_min_clock = sdhci_s3c_get_min_clock, .platform_8bit_width = sdhci_s3c_platform_8bit_width, + .set_ios = sdhci_s3c_set_ios, + .adjust_cfg = sdhci_s3c_adjust_cfg, }; static void sdhci_s3c_notify_change(struct platform_device *dev, int state) @@ -487,6 +529,9 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) /* Setup quirks for the controller */ host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC; host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT; + host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; + if (pdata->must_maintain_clock) + host->quirks |= SDHCI_QUIRK_MUST_MAINTAIN_CLOCK; #ifndef CONFIG_MMC_SDHCI_S3C_DMA @@ -494,6 +539,9 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) * support as well. */ host->quirks |= SDHCI_QUIRK_BROKEN_DMA; + /* PIO currently has problems with multi-block IO */ + host->quirks |= SDHCI_QUIRK_NO_MULTIBLOCK; + #endif /* CONFIG_MMC_SDHCI_S3C_DMA */ /* It seems we do not get an DATA transfer complete on non-busy @@ -534,6 +582,11 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) if (pdata->host_caps) host->mmc->caps |= pdata->host_caps; + /* Set pm_flags for built_in device */ + host->mmc->pm_caps = MMC_PM_KEEP_POWER | MMC_PM_IGNORE_PM_NOTIFY; + if (pdata->built_in) + host->mmc->pm_flags = MMC_PM_KEEP_POWER | MMC_PM_IGNORE_PM_NOTIFY; + ret = sdhci_add_host(host); if (ret) { dev_err(dev, "sdhci_add_host() failed\n"); @@ -557,8 +610,10 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) err_req_regs: for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) { - clk_disable(sc->clk_bus[ptr]); - clk_put(sc->clk_bus[ptr]); + if (sc->clk_bus[ptr]) { + clk_disable(sc->clk_bus[ptr]); + clk_put(sc->clk_bus[ptr]); + } } err_no_busclks: @@ -576,11 +631,16 @@ static int __devexit sdhci_s3c_remove(struct platform_device *pdev) struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data; struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_s3c *sc = sdhci_priv(host); - int ptr; + int ptr, dead = 0; + u32 scratch; if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_cleanup) pdata->ext_cd_cleanup(&sdhci_s3c_notify_change); + scratch = readl(host->ioaddr + SDHCI_INT_STATUS); + if (scratch == (u32)-1) + dead = 1; + if (sc->ext_cd_irq) free_irq(sc->ext_cd_irq, sc); @@ -589,7 +649,7 @@ static int __devexit sdhci_s3c_remove(struct platform_device *pdev) sdhci_remove_host(host, 1); - for (ptr = 0; ptr < 3; ptr++) { + for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) { if (sc->clk_bus[ptr]) { clk_disable(sc->clk_bus[ptr]); clk_put(sc->clk_bus[ptr]); @@ -614,6 +674,11 @@ static int sdhci_s3c_suspend(struct platform_device *dev, pm_message_t pm) { struct sdhci_host *host = platform_get_drvdata(dev); + struct mmc_host *mmc = host->mmc; + + if (mmc->card && (mmc->card->type == MMC_TYPE_SDIO)) + mmc->pm_flags |= MMC_PM_KEEP_POWER; + sdhci_suspend_host(host, pm); return 0; } @@ -621,8 +686,18 @@ static int sdhci_s3c_suspend(struct platform_device *dev, pm_message_t pm) static int sdhci_s3c_resume(struct platform_device *dev) { struct sdhci_host *host = platform_get_drvdata(dev); + struct s3c_sdhci_platdata *pdata = dev->dev.platform_data; + u32 ier; sdhci_resume_host(host); + + if (pdata->enable_intr_on_resume) { + ier = sdhci_readl(host, SDHCI_INT_ENABLE); + ier |= SDHCI_INT_CARD_INT; + sdhci_writel(host, ier, SDHCI_INT_ENABLE); + sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE); + } + return 0; } diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index d517a21..862ad52 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -25,6 +25,9 @@ #include <linux/mmc/mmc.h> #include <linux/mmc/host.h> +#include <linux/mmc/card.h> + +#include <plat/regs-sdhci.h> #include "sdhci.h" @@ -104,6 +107,24 @@ static void sdhci_dumpregs(struct sdhci_host *host) * * \*****************************************************************************/ +static void sdhci_enable_clock_card(struct sdhci_host *host) +{ + u16 clk; + + clk = readw(host->ioaddr + SDHCI_CLOCK_CONTROL); + clk |= SDHCI_CLOCK_CARD_EN; + writew(clk, host->ioaddr + SDHCI_CLOCK_CONTROL); +} + +static void sdhci_disable_clock_card(struct sdhci_host *host) +{ + u16 clk; + + clk = readw(host->ioaddr + SDHCI_CLOCK_CONTROL); + clk &= ~SDHCI_CLOCK_CARD_EN; + writew(clk, host->ioaddr + SDHCI_CLOCK_CONTROL); +} + static void sdhci_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set) { u32 ier; @@ -930,9 +951,14 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) int flags; u32 mask; unsigned long timeout; +#if defined(CONFIG_MMC_SDHCI_S3C) || defined(CONFIG_MMC_SDHCI_MODULE) + int i; +#endif WARN_ON(host->cmd); + del_timer(&host->busy_check_timer); + /* Wait max 10 ms */ timeout = 10; @@ -962,8 +988,12 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) host->cmd = cmd; + sdhci_enable_clock_card(host); sdhci_prepare_data(host, cmd); + if(cmd->flags & MMC_RSP_BUSY) + sdhci_writeb(host, 0xE, SDHCI_TIMEOUT_CONTROL); + sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT); sdhci_set_transfer_mode(host, cmd); @@ -994,6 +1024,17 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) if (cmd->data || (cmd->opcode == MMC_SEND_TUNING_BLOCK)) flags |= SDHCI_CMD_DATA; +#if defined(CONFIG_MMC_SDHCI_S3C) || defined(CONFIG_MMC_SDHCI_MODULE) + mask = readl(host->ioaddr + SDHCI_INT_STATUS); + writel(mask & SDHCI_INT_DATA_MASK & SDHCI_INT_CMD_MASK, host->ioaddr + SDHCI_INT_STATUS); + + for(i=0; i<0x1000000; i++) { + mask = readl(host->ioaddr + S3C64XX_SDHCI_CONTROL4); + if(!(mask & S3C64XX_SDHCI_CONTROL4_BUSY)) + break; + } +#endif + sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); } @@ -1289,6 +1330,9 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) sdhci_reinit(host); } + if (host->ops->set_ios) + host->ops->set_ios(host, ios); + sdhci_set_clock(host, ios->clock); if (ios->power_mode == MMC_POWER_OFF) @@ -1798,6 +1842,26 @@ static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable) spin_unlock_irqrestore(&host->lock, flags); } +void sdhci_adjust_cfg(struct mmc_host *mmc, int rw) +{ + struct sdhci_host *host; + unsigned long flags; + struct mmc_ios *ios = &mmc->ios; + unsigned int clock; + + host = mmc_priv(mmc); + + spin_lock_irqsave(&host->lock, flags); + + if(host->ops->adjust_cfg) + host->ops->adjust_cfg(host, rw); + + sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + + mmiowb(); + spin_unlock_irqrestore(&host->lock, flags); +} + static const struct mmc_host_ops sdhci_ops = { .request = sdhci_request, .set_ios = sdhci_set_ios, @@ -1806,6 +1870,7 @@ static const struct mmc_host_ops sdhci_ops = { .start_signal_voltage_switch = sdhci_start_signal_voltage_switch, .execute_tuning = sdhci_execute_tuning, .enable_preset_value = sdhci_enable_preset_value, + .adjust_cfg = sdhci_adjust_cfg, }; /*****************************************************************************\ @@ -1851,6 +1916,9 @@ static void sdhci_tasklet_finish(unsigned long param) host = (struct sdhci_host*)param; + if(host == NULL) + return; + /* * If this tasklet gets rescheduled while running, it will * be run again afterwards but without any active request. @@ -1864,6 +1932,9 @@ static void sdhci_tasklet_finish(unsigned long param) mrq = host->mrq; + if(mrq == NULL || mrq->cmd == NULL) + goto out; + /* * The controller needs a reset of internal state machines * upon error conditions. @@ -1889,6 +1960,12 @@ static void sdhci_tasklet_finish(unsigned long param) sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); } +out: + if((readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_DATA_INHIBIT) || + (host->quirks & SDHCI_QUIRK_MUST_MAINTAIN_CLOCK)) + mod_timer(&host->busy_check_timer, jiffies + msecs_to_jiffies(10)); + else + sdhci_disable_clock_card(host); host->mrq = NULL; host->cmd = NULL; @@ -1949,6 +2026,24 @@ static void sdhci_tuning_timer(unsigned long data) spin_unlock_irqrestore(&host->lock, flags); } +static void sdhci_busy_check_timer(unsigned long data) +{ + struct sdhci_host *host; + unsigned long flags; + + host = (struct sdhci_host*)data; + + spin_lock_irqsave(&host->lock, flags); + + if((readl(host->ioaddr + SDHCI_PRESENT_STATE) & (SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT)) || + (host->quirks & SDHCI_QUIRK_MUST_MAINTAIN_CLOCK)) + mod_timer(&host->busy_check_timer, jiffies + msecs_to_jiffies(10)); + else + sdhci_disable_clock_card(host); + + spin_unlock_irqrestore(&host->lock, flags); +} + /*****************************************************************************\ * * * Interrupt handling * @@ -1975,6 +2070,7 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask) if (host->cmd->error) { tasklet_schedule(&host->finish_tasklet); + host->cmd = NULL; return; } @@ -2219,7 +2315,7 @@ out: int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state) { - int ret; + int ret = 0; sdhci_disable_card_detection(host); @@ -2231,10 +2327,17 @@ int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state) } ret = mmc_suspend_host(host->mmc); - if (ret) + if (ret) { + sdhci_enable_card_detection(host); return ret; + } - free_irq(host->irq, host); + sdhci_mask_irqs(host, SDHCI_INT_ALL_MASK); + + del_timer(&host->busy_check_timer); + + if (host->irq) + disable_irq(host->irq); if (host->vmmc) ret = regulator_disable(host->vmmc); @@ -2246,7 +2349,7 @@ EXPORT_SYMBOL_GPL(sdhci_suspend_host); int sdhci_resume_host(struct sdhci_host *host) { - int ret; + int ret = 0; if (host->vmmc) { int ret = regulator_enable(host->vmmc); @@ -2260,15 +2363,14 @@ int sdhci_resume_host(struct sdhci_host *host) host->ops->enable_dma(host); } - ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED, - mmc_hostname(host->mmc), host); - if (ret) - return ret; + if (host->irq) + enable_irq(host->irq); sdhci_init(host, (host->mmc->pm_flags & MMC_PM_KEEP_POWER)); mmiowb(); ret = mmc_resume_host(host->mmc); + sdhci_enable_card_detection(host); /* Set the re-tuning expiration flag */ @@ -2699,6 +2801,7 @@ int sdhci_add_host(struct sdhci_host *host) sdhci_tasklet_finish, (unsigned long)host); setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host); + setup_timer(&host->busy_check_timer, sdhci_busy_check_timer, (unsigned long)host); if (host->version >= SDHCI_SPEC_300) { init_waitqueue_head(&host->buf_ready_int); @@ -2804,6 +2907,7 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) del_timer_sync(&host->timer); if (host->version >= SDHCI_SPEC_300) del_timer_sync(&host->tuning_timer); + del_timer_sync(&host->busy_check_timer); tasklet_kill(&host->card_tasklet); tasklet_kill(&host->finish_tasklet); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 745c42f..98d0bc2 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -274,6 +274,9 @@ struct sdhci_ops { void (*platform_reset_exit)(struct sdhci_host *host, u8 mask); int (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs); + void (*set_ios)(struct sdhci_host *host, + struct mmc_ios *ios); + void (*adjust_cfg)(struct sdhci_host *host, int rw); }; #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS |