diff options
Diffstat (limited to 'drivers/mmc/host/sdhci.c')
-rw-r--r-- | drivers/mmc/host/sdhci.c | 168 |
1 files changed, 167 insertions, 1 deletions
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 2a15aad2eba5..8a56eacea34d 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -38,6 +38,8 @@ #define SDHCI_USE_LEDS_CLASS #endif +#define MAX_TUNING_LOOP 40 + static unsigned int debug_quirks = 0; static void sdhci_finish_data(struct sdhci_host *); @@ -968,7 +970,9 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) flags |= SDHCI_CMD_CRC; if (cmd->flags & MMC_RSP_OPCODE) flags |= SDHCI_CMD_INDEX; - if (cmd->data) + + /* CMD19 is special in that the Data Present Select should be set */ + if (cmd->data || (cmd->opcode == MMC_SEND_TUNING_BLOCK)) flags |= SDHCI_CMD_DATA; sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); @@ -1501,12 +1505,157 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, return 0; } +static int sdhci_execute_tuning(struct mmc_host *mmc) +{ + struct sdhci_host *host; + u16 ctrl; + u32 ier; + int tuning_loop_counter = MAX_TUNING_LOOP; + unsigned long timeout; + int err = 0; + + host = mmc_priv(mmc); + + disable_irq(host->irq); + spin_lock(&host->lock); + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + + /* + * Host Controller needs tuning only in case of SDR104 mode + * and for SDR50 mode when Use Tuning for SDR50 is set in + * Capabilities register. + */ + if (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR104) || + (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR50) && + (host->flags & SDHCI_SDR50_NEEDS_TUNING))) + ctrl |= SDHCI_CTRL_EXEC_TUNING; + else { + spin_unlock(&host->lock); + enable_irq(host->irq); + return 0; + } + + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + + /* + * As per the Host Controller spec v3.00, tuning command + * generates Buffer Read Ready interrupt, so enable that. + * + * Note: The spec clearly says that when tuning sequence + * is being performed, the controller does not generate + * interrupts other than Buffer Read Ready interrupt. But + * to make sure we don't hit a controller bug, we _only_ + * enable Buffer Read Ready interrupt here. + */ + ier = sdhci_readl(host, SDHCI_INT_ENABLE); + sdhci_clear_set_irqs(host, ier, SDHCI_INT_DATA_AVAIL); + + /* + * Issue CMD19 repeatedly till Execute Tuning is set to 0 or the number + * of loops reaches 40 times or a timeout of 150ms occurs. + */ + timeout = 150; + do { + struct mmc_command cmd = {0}; + struct mmc_request mrq = {0}; + + if (!tuning_loop_counter && !timeout) + break; + + cmd.opcode = MMC_SEND_TUNING_BLOCK; + cmd.arg = 0; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.retries = 0; + cmd.data = NULL; + cmd.error = 0; + + mrq.cmd = &cmd; + host->mrq = &mrq; + + /* + * In response to CMD19, the card sends 64 bytes of tuning + * block to the Host Controller. So we set the block size + * to 64 here. + */ + sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64), SDHCI_BLOCK_SIZE); + + /* + * The tuning block is sent by the card to the host controller. + * So we set the TRNS_READ bit in the Transfer Mode register. + * This also takes care of setting DMA Enable and Multi Block + * Select in the same register to 0. + */ + sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE); + + sdhci_send_command(host, &cmd); + + host->cmd = NULL; + host->mrq = NULL; + + spin_unlock(&host->lock); + enable_irq(host->irq); + + /* Wait for Buffer Read Ready interrupt */ + wait_event_interruptible_timeout(host->buf_ready_int, + (host->tuning_done == 1), + msecs_to_jiffies(50)); + disable_irq(host->irq); + spin_lock(&host->lock); + + if (!host->tuning_done) { + printk(KERN_INFO DRIVER_NAME ": Timeout waiting for " + "Buffer Read Ready interrupt during tuning " + "procedure, falling back to fixed sampling " + "clock\n"); + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + ctrl &= ~SDHCI_CTRL_TUNED_CLK; + ctrl &= ~SDHCI_CTRL_EXEC_TUNING; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + + err = -EIO; + goto out; + } + + host->tuning_done = 0; + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + tuning_loop_counter--; + timeout--; + mdelay(1); + } while (ctrl & SDHCI_CTRL_EXEC_TUNING); + + /* + * The Host Driver has exhausted the maximum number of loops allowed, + * so use fixed sampling frequency. + */ + if (!tuning_loop_counter || !timeout) { + ctrl &= ~SDHCI_CTRL_TUNED_CLK; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + } else { + if (!(ctrl & SDHCI_CTRL_TUNED_CLK)) { + printk(KERN_INFO DRIVER_NAME ": Tuning procedure" + " failed, falling back to fixed sampling" + " clock\n"); + err = -EIO; + } + } + +out: + sdhci_clear_set_irqs(host, SDHCI_INT_DATA_AVAIL, ier); + spin_unlock(&host->lock); + enable_irq(host->irq); + + return err; +} + static const struct mmc_host_ops sdhci_ops = { .request = sdhci_request, .set_ios = sdhci_set_ios, .get_ro = sdhci_get_ro, .enable_sdio_irq = sdhci_enable_sdio_irq, .start_signal_voltage_switch = sdhci_start_signal_voltage_switch, + .execute_tuning = sdhci_execute_tuning, }; /*****************************************************************************\ @@ -1724,6 +1873,16 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) { BUG_ON(intmask == 0); + /* CMD19 generates _only_ Buffer Read Ready interrupt */ + if (intmask & SDHCI_INT_DATA_AVAIL) { + if (SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)) == + MMC_SEND_TUNING_BLOCK) { + host->tuning_done = 1; + wake_up(&host->buf_ready_int); + return; + } + } + if (!host->data) { /* * The "data complete" interrupt is also used to @@ -2160,6 +2319,10 @@ int sdhci_add_host(struct sdhci_host *host) if (caps[1] & SDHCI_SUPPORT_DDR50) mmc->caps |= MMC_CAP_UHS_DDR50; + /* Does the host needs tuning for SDR50? */ + if (caps[1] & SDHCI_USE_SDR50_TUNING) + host->flags |= SDHCI_SDR50_NEEDS_TUNING; + /* Driver Type(s) (A, C, D) supported by the host */ if (caps[1] & SDHCI_DRIVER_TYPE_A) mmc->caps |= MMC_CAP_DRIVER_TYPE_A; @@ -2313,6 +2476,9 @@ int sdhci_add_host(struct sdhci_host *host) setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host); + if (host->version >= SDHCI_SPEC_300) + init_waitqueue_head(&host->buf_ready_int); + ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED, mmc_hostname(mmc), host); if (ret) |