mmc: sd: add support for tuning during uhs initialization
authorArindam Nath <arindam.nath@amd.com>
Thu, 5 May 2011 06:49:04 +0000 (12:19 +0530)
committerChris Ball <cjb@laptop.org>
Wed, 25 May 2011 03:53:46 +0000 (23:53 -0400)
Host Controller needs tuning during initialization to operate SDR50
and SDR104 UHS-I cards. Whether SDR50 mode actually needs tuning is
indicated by bit 45 of the Host Controller Capabilities register.
A new command CMD19 has been defined in the Physical Layer spec
v3.01 to request the card to send tuning pattern.

We enable Buffer Read Ready interrupt at the very begining of tuning
procedure, because that is the only interrupt generated by the Host
Controller during tuning. We program the block size to 64 in the
Block Size register. We make sure that DMA Enable and Multi Block
Select in the Transfer Mode register are set to 0 before actually
sending CMD19. The tuning block is sent by the card to the Host
Controller using DAT lines, so we set Data Present Select (bit 5) in
the Command register. The Host Controller is responsible for doing
the verfication of tuning block sent by the card at the hardware
level. After sending CMD19, we wait for Buffer Read Ready interrupt.
In case we don't receive an interrupt after the specified timeout
value, we fall back on fixed sampling clock by setting Execute
Tuning (bit 6) and Sampling Clock Select (bit 7) of Host Control2
register to 0. Before exiting the tuning procedure, we disable Buffer
Read Ready interrupt and re-enable other interrupts.

Tested by Zhangfei Gao with a Toshiba uhs card and general hs card,
on mmp2 in SDMA mode.

Signed-off-by: Arindam Nath <arindam.nath@amd.com>
Reviewed-by: Philip Rakity <prakity@marvell.com>
Tested-by: Philip Rakity <prakity@marvell.com>
Acked-by: Zhangfei Gao <zhangfei.gao@marvell.com>
Signed-off-by: Chris Ball <cjb@laptop.org>
drivers/mmc/core/sd.c
drivers/mmc/host/sdhci.c
drivers/mmc/host/sdhci.h
include/linux/mmc/host.h
include/linux/mmc/mmc.h
include/linux/mmc/sdhci.h

index 732c3171ceca986b03e589afd63d1bf1adcb0dd9..fc65475a26ee615fc92cba3786cf40507d13f754 100644 (file)
@@ -618,6 +618,12 @@ static int mmc_sd_init_uhs_card(struct mmc_card *card)
 
        /* Set current limit for the card */
        err = sd_set_current_limit(card, status);
+       if (err)
+               goto out;
+
+       /* SPI mode doesn't define CMD19 */
+       if (!mmc_host_is_spi(card->host) && card->host->ops->execute_tuning)
+               err = card->host->ops->execute_tuning(card->host);
 
 out:
        kfree(status);
index 2a15aad2eba58418b4e5bca55e9432d407fba013..8a56eacea34d45c5d576cf5be92b77acdc382b7a 100644 (file)
@@ -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)
index d96f6afcca1fa84ab51ab3c8c947b9cc2e2d0e32..e62367491eeec286a095126ec0f72710d45c48e6 100644 (file)
 #define   SDHCI_CTRL_DRV_TYPE_A                0x0010
 #define   SDHCI_CTRL_DRV_TYPE_C                0x0020
 #define   SDHCI_CTRL_DRV_TYPE_D                0x0030
+#define  SDHCI_CTRL_EXEC_TUNING                0x0040
+#define  SDHCI_CTRL_TUNED_CLK          0x0080
 #define  SDHCI_CTRL_PRESET_VAL_ENABLE  0x8000
 
 #define SDHCI_CAPABILITIES     0x40
 #define  SDHCI_DRIVER_TYPE_A   0x00000010
 #define  SDHCI_DRIVER_TYPE_C   0x00000020
 #define  SDHCI_DRIVER_TYPE_D   0x00000040
+#define  SDHCI_USE_SDR50_TUNING        0x00002000
 
 #define SDHCI_CAPABILITIES_1   0x44
 
index 52b5dc914a8cf9ffafda1dee2ae3f384007d0465..ca7007fdb3994470176d397af999cebf462b3126 100644 (file)
@@ -136,6 +136,7 @@ struct mmc_host_ops {
        void    (*init_card)(struct mmc_host *host, struct mmc_card *card);
 
        int     (*start_signal_voltage_switch)(struct mmc_host *host, struct mmc_ios *ios);
+       int     (*execute_tuning)(struct mmc_host *host);
 };
 
 struct mmc_card;
index 373b2bf5e5b5d04a505f4d7a93f337c3f0e25d9a..9fa5a73f393de052a88343f5fd9816839d8ebc00 100644 (file)
@@ -50,6 +50,7 @@
 #define MMC_SET_BLOCKLEN         16   /* ac   [31:0] block len   R1  */
 #define MMC_READ_SINGLE_BLOCK    17   /* adtc [31:0] data addr   R1  */
 #define MMC_READ_MULTIPLE_BLOCK  18   /* adtc [31:0] data addr   R1  */
+#define MMC_SEND_TUNING_BLOCK    19   /* adtc                    R1  */
 
   /* class 3 */
 #define MMC_WRITE_DAT_UNTIL_STOP 20   /* adtc [31:0] data addr   R1  */
index 92e1c9ad126cbba5e55bf0189512d6a657d6e7da..b74c8530e95921c1cc56997efcf3721356c2264b 100644 (file)
@@ -111,6 +111,7 @@ struct sdhci_host {
 #define SDHCI_USE_ADMA         (1<<1)  /* Host is ADMA capable */
 #define SDHCI_REQ_USE_DMA      (1<<2)  /* Use DMA for this req. */
 #define SDHCI_DEVICE_DEAD      (1<<3)  /* Device unresponsive */
+#define SDHCI_SDR50_NEEDS_TUNING (1<<4)        /* SDR50 needs tuning */
 
        unsigned int version;   /* SDHCI spec. version */
 
@@ -147,6 +148,9 @@ struct sdhci_host {
        unsigned int            ocr_avail_sd;
        unsigned int            ocr_avail_mmc;
 
+       wait_queue_head_t       buf_ready_int;  /* Waitqueue for Buffer Read Ready interrupt */
+       unsigned int            tuning_done;    /* Condition flag set when CMD19 succeeds */
+
        unsigned long private[0] ____cacheline_aligned;
 };
 #endif /* __SDHCI_H */