diff options
Diffstat (limited to 'drivers/mmc/host/sdhci.c')
-rw-r--r-- | drivers/mmc/host/sdhci.c | 97 |
1 files changed, 97 insertions, 0 deletions
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 32aeb42..cd2833f 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -26,6 +26,8 @@ #include <linux/mmc/mmc.h> #include <linux/mmc/host.h> +#include <plat/regs-sdhci.h> + #include "sdhci.h" #define DRIVER_NAME "sdhci" @@ -104,6 +106,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 +950,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 +987,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 +1023,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 +1329,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) @@ -1799,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, @@ -1807,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, }; /*****************************************************************************\ @@ -1852,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. @@ -1865,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. @@ -1890,6 +1960,11 @@ 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) + mod_timer(&host->busy_check_timer, jiffies + msecs_to_jiffies(10)); + else + sdhci_disable_clock_card(host); host->mrq = NULL; host->cmd = NULL; @@ -1950,6 +2025,23 @@ 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)) + 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 * @@ -1976,6 +2068,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; } @@ -2236,6 +2329,8 @@ int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state) if (ret) return ret; + del_timer(&host->busy_check_timer); + free_irq(host->irq, host); if (host->vmmc) @@ -2701,6 +2796,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); @@ -2806,6 +2902,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); |