diff mbox

[v2,07/11] mmc: host: omap_hsmmc: add tuning support

Message ID 1440493542-26150-8-git-send-email-kishon@ti.com (mailing list archive)
State New, archived
Headers show

Commit Message

Kishon Vijay Abraham I Aug. 25, 2015, 9:05 a.m. UTC
From: Balaji T K <balajitk@ti.com>

MMC tuning procedure is required to support SD card
UHS1-SDR104 mode and EMMC HS200 mode.

The tuning function omap_execute_tuning() will only
be called by the MMC/SD core if the corresponding
speed modes are supported by the OMAP silicon which
is set in the mmc host "caps" field.

Signed-off-by: Balaji T K <balajitk@ti.com>
Signed-off-by: Viswanath Puttagunta <vishp@ti.com>
Signed-off-by: Sourav Poddar <sourav.poddar@ti.com>
[kishon@ti.com : cleanup the tuning sequence]
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
---
 drivers/mmc/host/omap_hsmmc.c |  234 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 232 insertions(+), 2 deletions(-)

Comments

Ulf Hansson Aug. 25, 2015, 2:12 p.m. UTC | #1
On 25 August 2015 at 11:05, Kishon Vijay Abraham I <kishon@ti.com> wrote:
> From: Balaji T K <balajitk@ti.com>
>
> MMC tuning procedure is required to support SD card
> UHS1-SDR104 mode and EMMC HS200 mode.
>
> The tuning function omap_execute_tuning() will only
> be called by the MMC/SD core if the corresponding
> speed modes are supported by the OMAP silicon which
> is set in the mmc host "caps" field.
>
> Signed-off-by: Balaji T K <balajitk@ti.com>
> Signed-off-by: Viswanath Puttagunta <vishp@ti.com>
> Signed-off-by: Sourav Poddar <sourav.poddar@ti.com>
> [kishon@ti.com : cleanup the tuning sequence]
> Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
> ---
>  drivers/mmc/host/omap_hsmmc.c |  234 ++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 232 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
> index c042b91..43485c3 100644
> --- a/drivers/mmc/host/omap_hsmmc.c
> +++ b/drivers/mmc/host/omap_hsmmc.c
> @@ -18,6 +18,7 @@
>  #include <linux/module.h>
>  #include <linux/init.h>
>  #include <linux/kernel.h>
> +#include <linux/slab.h>
>  #include <linux/debugfs.h>
>  #include <linux/dmaengine.h>
>  #include <linux/seq_file.h>
> @@ -49,6 +50,7 @@
>  /* OMAP HSMMC Host Controller Registers */
>  #define OMAP_HSMMC_SYSSTATUS   0x0014
>  #define OMAP_HSMMC_CON         0x002C
> +#define OMAP_HSMMC_DLL         0x0034
>  #define OMAP_HSMMC_SDMASA      0x0100
>  #define OMAP_HSMMC_BLK         0x0104
>  #define OMAP_HSMMC_ARG         0x0108
> @@ -66,6 +68,7 @@
>  #define OMAP_HSMMC_ISE         0x0138
>  #define OMAP_HSMMC_AC12                0x013C
>  #define OMAP_HSMMC_CAPA                0x0140
> +#define OMAP_HSMMC_CAPA2       0x0144
>
>  #define VS18                   (1 << 26)
>  #define VS30                   (1 << 25)
> @@ -114,6 +117,7 @@
>
>  /* AC12 */
>  #define AC12_V1V8_SIGEN                (1 << 19)
> +#define AC12_SCLK_SEL          (1 << 23)
>  #define AC12_UHSMC_MASK                (7 << 16)
>  #define AC12_UHSMC_SDR12       (0 << 16)
>  #define AC12_UHSMC_SDR25       (1 << 16)
> @@ -122,6 +126,18 @@
>  #define AC12_UHSMC_DDR50       (4 << 16)
>  #define AC12_UHSMC_RES         (0x7 << 16)
>
> +/* DLL */
> +#define DLL_SWT                        (1 << 20)
> +#define DLL_FORCE_SR_C_SHIFT   13
> +#define DLL_FORCE_SR_C_MASK    0x7f
> +#define DLL_FORCE_VALUE                (1 << 12)
> +#define DLL_CALIB              (1 << 1)
> +
> +#define MAX_PHASE_DELAY                0x7c
> +
> +/* CAPA2 */
> +#define CAPA2_TSDR50           (1 << 13)
> +
>  /* Interrupt masks for IE and ISE register */
>  #define CC_EN                  (1 << 0)
>  #define TC_EN                  (1 << 1)
> @@ -202,6 +218,7 @@ struct omap_hsmmc_host {
>         int                     vqmmc_enabled;
>         resource_size_t         mapbase;
>         spinlock_t              irq_lock; /* Prevent races with irq handler */
> +       struct completion       buf_ready;
>         unsigned int            dma_len;
>         unsigned int            dma_sg_idx;
>         unsigned char           bus_mode;
> @@ -229,6 +246,9 @@ struct omap_hsmmc_host {
>         struct omap_hsmmc_next  next_data;
>         struct  omap_hsmmc_platform_data        *pdata;
>
> +       u32                     *tuning_data;
> +       int                     tuning_size;
> +
>         /* return MMC cover switch state, can be NULL if not supported.
>          *
>          * possible return values:
> @@ -245,8 +265,39 @@ struct omap_mmc_of_data {
>         u8 controller_flags;
>  };
>
> +static const u8 ref_tuning_4bits[] = {
> +       0xff, 0x0f, 0xff, 0x00, 0xff, 0xcc, 0xc3, 0xcc,
> +       0xc3, 0x3c, 0xcc, 0xff, 0xfe, 0xff, 0xfe, 0xef,
> +       0xff, 0xdf, 0xff, 0xdd, 0xff, 0xfb, 0xff, 0xfb,
> +       0xbf, 0xff, 0x7f, 0xff, 0x77, 0xf7, 0xbd, 0xef,
> +       0xff, 0xf0, 0xff, 0xf0, 0x0f, 0xfc, 0xcc, 0x3c,
> +       0xcc, 0x33, 0xcc, 0xcf, 0xff, 0xef, 0xff, 0xee,
> +       0xff, 0xfd, 0xff, 0xfd, 0xdf, 0xff, 0xbf, 0xff,
> +       0xbb, 0xff, 0xf7, 0xff, 0xf7, 0x7f, 0x7b, 0xde,
> +};
> +
> +static const u8 ref_tuning_8bits[] = {
> +       0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00,
> +       0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, 0xcc,
> +       0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, 0xff,
> +       0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, 0xff,
> +       0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, 0xdd,
> +       0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb,
> +       0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, 0xff,
> +       0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, 0xff,
> +       0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00,
> +       0x00, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc,
> +       0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff,
> +       0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee,
> +       0xff, 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd,
> +       0xdd, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff,
> +       0xbb, 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff,
> +       0xff, 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee,
> +};

These bit patterns already exists in the mmc core as a part of the
mmc_send_tuning() API.

> +
>  static void omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host);
>  static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host, int iov);
> +static void omap_hsmmc_disable_tuning(struct omap_hsmmc_host *host);
>
>  static int omap_hsmmc_card_detect(struct device *dev)
>  {
> @@ -609,8 +660,12 @@ static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host,
>  {
>         u32 irq_mask = INT_EN_MASK;
>         unsigned long flags;
> +       bool is_tuning;
>
> -       if (host->use_dma)
> +       is_tuning = (cmd->opcode == MMC_SEND_TUNING_BLOCK) ||
> +                   (cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200);
> +
> +       if (!is_tuning && host->use_dma)
>                 irq_mask &= ~(BRR_EN | BWR_EN);
>
>         /* Disable timeout for erases */
> @@ -956,7 +1011,11 @@ omap_hsmmc_start_command(struct omap_hsmmc_host *host, struct mmc_command *cmd,
>                         cmdreg &= ~(DDIR);
>         }
>
> -       if (host->use_dma)
> +       /* Tuning command is special. Data Present Select should be set */
> +       if ((cmd->opcode == MMC_SEND_TUNING_BLOCK) ||
> +           (cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200))
> +               cmdreg |= DP_SELECT | DDIR;
> +       else if (host->use_dma)
>                 cmdreg |= DMAE;
>
>         host->req_in_progress = 1;
> @@ -1187,6 +1246,7 @@ static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status)
>         struct mmc_data *data;
>         int end_cmd = 0, end_trans = 0;
>         int error = 0;
> +       int i;
>
>         data = host->data;
>         dev_vdbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status);
> @@ -1222,6 +1282,13 @@ static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status)
>                 }
>         }
>
> +       if (status & BRR_EN) {
> +               for (i = 0; i < host->tuning_size / 4; i++)
> +                       host->tuning_data[i] =
> +                               OMAP_HSMMC_READ(host->base, DATA);
> +               complete(&host->buf_ready);
> +       }
> +
>         OMAP_HSMMC_WRITE(host->base, STAT, status);
>         if (end_cmd || ((status & CC_EN) && host->cmd))
>                 omap_hsmmc_cmd_done(host, host->cmd);
> @@ -1721,6 +1788,7 @@ static void omap_hsmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>         if (ios->power_mode != host->power_mode) {
>                 switch (ios->power_mode) {
>                 case MMC_POWER_OFF:
> +                       omap_hsmmc_disable_tuning(host);
>                         mmc_pdata(host)->set_power(host->dev, 0, 0);
>                         break;
>                 case MMC_POWER_UP:
> @@ -2035,6 +2103,166 @@ static int omap_hsmmc_card_busy(struct mmc_host *mmc)
>         return ret;
>  }
>
> +static inline void omap_hsmmc_set_dll(struct omap_hsmmc_host *host, int count)
> +{
> +       int i;
> +       u32 dll;
> +
> +       dll = OMAP_HSMMC_READ(host->base, DLL);
> +       dll |= DLL_FORCE_VALUE;
> +       dll &= ~(DLL_FORCE_SR_C_MASK << DLL_FORCE_SR_C_SHIFT);
> +       dll |= (count << DLL_FORCE_SR_C_SHIFT);
> +       OMAP_HSMMC_WRITE(host->base, DLL, dll);
> +
> +       dll |= DLL_CALIB;
> +       OMAP_HSMMC_WRITE(host->base, DLL, dll);
> +       for (i = 0; i < 1000; i++) {
> +               if (OMAP_HSMMC_READ(host->base, DLL) & DLL_CALIB)
> +                       break;
> +       }
> +       dll &= ~DLL_CALIB;
> +       OMAP_HSMMC_WRITE(host->base, DLL, dll);
> +}
> +
> +static void omap_hsmmc_disable_tuning(struct omap_hsmmc_host *host)
> +{
> +       int val;
> +
> +       val = OMAP_HSMMC_READ(host->base, AC12);
> +       val &= ~(AC12_SCLK_SEL);
> +       OMAP_HSMMC_WRITE(host->base, AC12, val);
> +
> +       val = OMAP_HSMMC_READ(host->base, DLL);
> +       val &= ~(DLL_FORCE_VALUE | DLL_SWT);
> +       OMAP_HSMMC_WRITE(host->base, DLL, val);
> +}
> +
> +static int omap_hsmmc_execute_tuning(struct mmc_host *mmc, u32 opcode)
> +{
> +       u32 val;
> +       u8 cur_match, prev_match = 0;
> +       int ret;
> +       u32 phase_delay = 0;
> +       u32 start_window = 0, max_window = 0;
> +       u32 length = 0, max_len = 0;
> +       const u8 *tuning_ref;
> +       struct mmc_ios *ios = &mmc->ios;
> +       struct omap_hsmmc_host *host;
> +       struct mmc_command cmd = {0};
> +       struct mmc_request mrq = {NULL};
> +
> +       /* clock tuning is not needed for upto 52MHz */
> +       if (ios->clock <= OMAP_MMC_MAX_CLOCK)
> +               return 0;
> +
> +       host = mmc_priv(mmc);
> +
> +       val = OMAP_HSMMC_READ(host->base, CAPA2);
> +       if (ios->timing == MMC_TIMING_UHS_SDR50 && !(val & CAPA2_TSDR50))
> +               return 0;
> +
> +       switch (ios->bus_width) {
> +       case MMC_BUS_WIDTH_8:
> +               tuning_ref = ref_tuning_8bits;
> +               host->tuning_size = sizeof(ref_tuning_8bits);
> +               break;
> +       case MMC_BUS_WIDTH_4:
> +               tuning_ref = ref_tuning_4bits;
> +               host->tuning_size = sizeof(ref_tuning_4bits);
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       host->tuning_data = kzalloc(host->tuning_size, GFP_KERNEL);
> +       if (!host->tuning_data)
> +               return -ENOMEM;
> +
> +       val = OMAP_HSMMC_READ(host->base, DLL);
> +       val |= DLL_SWT;
> +       OMAP_HSMMC_WRITE(host->base, DLL, val);
> +
> +       while (phase_delay <= MAX_PHASE_DELAY) {
> +               omap_hsmmc_set_dll(host, phase_delay);
> +
> +               cmd.opcode = opcode;
> +               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;
> +               OMAP_HSMMC_WRITE(host->base, BLK, host->tuning_size);
> +               set_data_timeout(host, 150000000, 0);
> +               omap_hsmmc_start_command(host, &cmd, NULL);
> +               host->cmd = NULL;
> +               host->mrq = NULL;
> +
> +               ret = wait_for_completion_timeout(&host->buf_ready,
> +                                                 msecs_to_jiffies(5000));
> +               if (ret == 0) {
> +                       dev_err(mmc_dev(host->mmc), "Tuning BRR timeout");
> +                       ret = -ETIMEDOUT;
> +                       goto tuning_error;
> +               }
> +

Please make use of mmc_send_tuning() which should simplfy some parts
of this code.

> +               host->req_in_progress = false;
> +
> +               cur_match = !memcmp(host->tuning_data, tuning_ref,
> +                                   host->tuning_size);
> +               if (cur_match) {
> +                       if (prev_match) {
> +                               length++;
> +                       } else {
> +                               start_window = phase_delay;
> +                               length = 1;
> +                       }
> +               }
> +
> +               if (length > max_len) {
> +                       max_window = start_window;
> +                       max_len = length;
> +               }
> +
> +               prev_match = cur_match;
> +               phase_delay += 4;
> +       }
> +
> +       if (!max_len) {
> +               dev_err(mmc_dev(host->mmc), "Unable to find match\n");
> +               ret = -EIO;
> +               goto tuning_error;
> +       }
> +
> +       val = OMAP_HSMMC_READ(host->base, AC12);
> +       if (!(val & AC12_SCLK_SEL)) {
> +               ret = -EIO;
> +               goto tuning_error;
> +       }
> +
> +       phase_delay = max_window + 4 * (max_len >> 1);
> +       omap_hsmmc_set_dll(host, phase_delay);
> +
> +       omap_hsmmc_reset_controller_fsm(host, SRD);
> +       omap_hsmmc_reset_controller_fsm(host, SRC);
> +
> +       kfree(host->tuning_data);
> +       return 0;
> +
> +tuning_error:
> +       dev_err(mmc_dev(host->mmc),
> +               "Tuning failed. Using fixed sampling clock\n");
> +
> +       omap_hsmmc_disable_tuning(host);
> +       omap_hsmmc_reset_controller_fsm(host, SRD);
> +       omap_hsmmc_reset_controller_fsm(host, SRC);
> +
> +       kfree(host->tuning_data);
> +       return ret;
> +}
> +
>  static struct mmc_host_ops omap_hsmmc_ops = {
>         .post_req = omap_hsmmc_post_req,
>         .pre_req = omap_hsmmc_pre_req,
> @@ -2046,6 +2274,7 @@ static struct mmc_host_ops omap_hsmmc_ops = {
>         .enable_sdio_irq = omap_hsmmc_enable_sdio_irq,
>         .start_signal_voltage_switch = omap_hsmmc_start_signal_voltage_switch,
>         .card_busy = omap_hsmmc_card_busy,
> +       .execute_tuning = omap_hsmmc_execute_tuning,
>  };
>
>  #ifdef CONFIG_DEBUG_FS
> @@ -2275,6 +2504,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>                 mmc->f_max = OMAP_MMC_MAX_CLOCK;
>
>         spin_lock_init(&host->irq_lock);
> +       init_completion(&host->buf_ready);
>
>         host->fclk = devm_clk_get(&pdev->dev, "fck");
>         if (IS_ERR(host->fclk)) {
> --
> 1.7.9.5
>

Kind regards
Uffe
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index c042b91..43485c3 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -18,6 +18,7 @@ 
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/kernel.h>
+#include <linux/slab.h>
 #include <linux/debugfs.h>
 #include <linux/dmaengine.h>
 #include <linux/seq_file.h>
@@ -49,6 +50,7 @@ 
 /* OMAP HSMMC Host Controller Registers */
 #define OMAP_HSMMC_SYSSTATUS	0x0014
 #define OMAP_HSMMC_CON		0x002C
+#define OMAP_HSMMC_DLL		0x0034
 #define OMAP_HSMMC_SDMASA	0x0100
 #define OMAP_HSMMC_BLK		0x0104
 #define OMAP_HSMMC_ARG		0x0108
@@ -66,6 +68,7 @@ 
 #define OMAP_HSMMC_ISE		0x0138
 #define OMAP_HSMMC_AC12		0x013C
 #define OMAP_HSMMC_CAPA		0x0140
+#define OMAP_HSMMC_CAPA2	0x0144
 
 #define VS18			(1 << 26)
 #define VS30			(1 << 25)
@@ -114,6 +117,7 @@ 
 
 /* AC12 */
 #define AC12_V1V8_SIGEN		(1 << 19)
+#define AC12_SCLK_SEL		(1 << 23)
 #define AC12_UHSMC_MASK		(7 << 16)
 #define AC12_UHSMC_SDR12	(0 << 16)
 #define AC12_UHSMC_SDR25	(1 << 16)
@@ -122,6 +126,18 @@ 
 #define AC12_UHSMC_DDR50	(4 << 16)
 #define AC12_UHSMC_RES		(0x7 << 16)
 
+/* DLL */
+#define DLL_SWT			(1 << 20)
+#define DLL_FORCE_SR_C_SHIFT	13
+#define DLL_FORCE_SR_C_MASK	0x7f
+#define DLL_FORCE_VALUE		(1 << 12)
+#define DLL_CALIB		(1 << 1)
+
+#define MAX_PHASE_DELAY		0x7c
+
+/* CAPA2 */
+#define CAPA2_TSDR50		(1 << 13)
+
 /* Interrupt masks for IE and ISE register */
 #define CC_EN			(1 << 0)
 #define TC_EN			(1 << 1)
@@ -202,6 +218,7 @@  struct omap_hsmmc_host {
 	int			vqmmc_enabled;
 	resource_size_t		mapbase;
 	spinlock_t		irq_lock; /* Prevent races with irq handler */
+	struct completion	buf_ready;
 	unsigned int		dma_len;
 	unsigned int		dma_sg_idx;
 	unsigned char		bus_mode;
@@ -229,6 +246,9 @@  struct omap_hsmmc_host {
 	struct omap_hsmmc_next	next_data;
 	struct	omap_hsmmc_platform_data	*pdata;
 
+	u32			*tuning_data;
+	int			tuning_size;
+
 	/* return MMC cover switch state, can be NULL if not supported.
 	 *
 	 * possible return values:
@@ -245,8 +265,39 @@  struct omap_mmc_of_data {
 	u8 controller_flags;
 };
 
+static const u8 ref_tuning_4bits[] = {
+	0xff, 0x0f, 0xff, 0x00, 0xff, 0xcc, 0xc3, 0xcc,
+	0xc3, 0x3c, 0xcc, 0xff, 0xfe, 0xff, 0xfe, 0xef,
+	0xff, 0xdf, 0xff, 0xdd, 0xff, 0xfb, 0xff, 0xfb,
+	0xbf, 0xff, 0x7f, 0xff, 0x77, 0xf7, 0xbd, 0xef,
+	0xff, 0xf0, 0xff, 0xf0, 0x0f, 0xfc, 0xcc, 0x3c,
+	0xcc, 0x33, 0xcc, 0xcf, 0xff, 0xef, 0xff, 0xee,
+	0xff, 0xfd, 0xff, 0xfd, 0xdf, 0xff, 0xbf, 0xff,
+	0xbb, 0xff, 0xf7, 0xff, 0xf7, 0x7f, 0x7b, 0xde,
+};
+
+static const u8 ref_tuning_8bits[] = {
+	0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, 0xcc,
+	0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, 0xff,
+	0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, 0xff,
+	0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, 0xdd,
+	0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb,
+	0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, 0xff,
+	0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, 0xff,
+	0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00,
+	0x00, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc,
+	0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff,
+	0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee,
+	0xff, 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd,
+	0xdd, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff,
+	0xbb, 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff,
+	0xff, 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee,
+};
+
 static void omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host);
 static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host, int iov);
+static void omap_hsmmc_disable_tuning(struct omap_hsmmc_host *host);
 
 static int omap_hsmmc_card_detect(struct device *dev)
 {
@@ -609,8 +660,12 @@  static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host,
 {
 	u32 irq_mask = INT_EN_MASK;
 	unsigned long flags;
+	bool is_tuning;
 
-	if (host->use_dma)
+	is_tuning = (cmd->opcode == MMC_SEND_TUNING_BLOCK) ||
+		    (cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200);
+
+	if (!is_tuning && host->use_dma)
 		irq_mask &= ~(BRR_EN | BWR_EN);
 
 	/* Disable timeout for erases */
@@ -956,7 +1011,11 @@  omap_hsmmc_start_command(struct omap_hsmmc_host *host, struct mmc_command *cmd,
 			cmdreg &= ~(DDIR);
 	}
 
-	if (host->use_dma)
+	/* Tuning command is special. Data Present Select should be set */
+	if ((cmd->opcode == MMC_SEND_TUNING_BLOCK) ||
+	    (cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200))
+		cmdreg |= DP_SELECT | DDIR;
+	else if (host->use_dma)
 		cmdreg |= DMAE;
 
 	host->req_in_progress = 1;
@@ -1187,6 +1246,7 @@  static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status)
 	struct mmc_data *data;
 	int end_cmd = 0, end_trans = 0;
 	int error = 0;
+	int i;
 
 	data = host->data;
 	dev_vdbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status);
@@ -1222,6 +1282,13 @@  static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status)
 		}
 	}
 
+	if (status & BRR_EN) {
+		for (i = 0; i < host->tuning_size / 4; i++)
+			host->tuning_data[i] =
+				OMAP_HSMMC_READ(host->base, DATA);
+		complete(&host->buf_ready);
+	}
+
 	OMAP_HSMMC_WRITE(host->base, STAT, status);
 	if (end_cmd || ((status & CC_EN) && host->cmd))
 		omap_hsmmc_cmd_done(host, host->cmd);
@@ -1721,6 +1788,7 @@  static void omap_hsmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 	if (ios->power_mode != host->power_mode) {
 		switch (ios->power_mode) {
 		case MMC_POWER_OFF:
+			omap_hsmmc_disable_tuning(host);
 			mmc_pdata(host)->set_power(host->dev, 0, 0);
 			break;
 		case MMC_POWER_UP:
@@ -2035,6 +2103,166 @@  static int omap_hsmmc_card_busy(struct mmc_host *mmc)
 	return ret;
 }
 
+static inline void omap_hsmmc_set_dll(struct omap_hsmmc_host *host, int count)
+{
+	int i;
+	u32 dll;
+
+	dll = OMAP_HSMMC_READ(host->base, DLL);
+	dll |= DLL_FORCE_VALUE;
+	dll &= ~(DLL_FORCE_SR_C_MASK << DLL_FORCE_SR_C_SHIFT);
+	dll |= (count << DLL_FORCE_SR_C_SHIFT);
+	OMAP_HSMMC_WRITE(host->base, DLL, dll);
+
+	dll |= DLL_CALIB;
+	OMAP_HSMMC_WRITE(host->base, DLL, dll);
+	for (i = 0; i < 1000; i++) {
+		if (OMAP_HSMMC_READ(host->base, DLL) & DLL_CALIB)
+			break;
+	}
+	dll &= ~DLL_CALIB;
+	OMAP_HSMMC_WRITE(host->base, DLL, dll);
+}
+
+static void omap_hsmmc_disable_tuning(struct omap_hsmmc_host *host)
+{
+	int val;
+
+	val = OMAP_HSMMC_READ(host->base, AC12);
+	val &= ~(AC12_SCLK_SEL);
+	OMAP_HSMMC_WRITE(host->base, AC12, val);
+
+	val = OMAP_HSMMC_READ(host->base, DLL);
+	val &= ~(DLL_FORCE_VALUE | DLL_SWT);
+	OMAP_HSMMC_WRITE(host->base, DLL, val);
+}
+
+static int omap_hsmmc_execute_tuning(struct mmc_host *mmc, u32 opcode)
+{
+	u32 val;
+	u8 cur_match, prev_match = 0;
+	int ret;
+	u32 phase_delay = 0;
+	u32 start_window = 0, max_window = 0;
+	u32 length = 0, max_len = 0;
+	const u8 *tuning_ref;
+	struct mmc_ios *ios = &mmc->ios;
+	struct omap_hsmmc_host *host;
+	struct mmc_command cmd = {0};
+	struct mmc_request mrq = {NULL};
+
+	/* clock tuning is not needed for upto 52MHz */
+	if (ios->clock <= OMAP_MMC_MAX_CLOCK)
+		return 0;
+
+	host = mmc_priv(mmc);
+
+	val = OMAP_HSMMC_READ(host->base, CAPA2);
+	if (ios->timing == MMC_TIMING_UHS_SDR50 && !(val & CAPA2_TSDR50))
+		return 0;
+
+	switch (ios->bus_width) {
+	case MMC_BUS_WIDTH_8:
+		tuning_ref = ref_tuning_8bits;
+		host->tuning_size = sizeof(ref_tuning_8bits);
+		break;
+	case MMC_BUS_WIDTH_4:
+		tuning_ref = ref_tuning_4bits;
+		host->tuning_size = sizeof(ref_tuning_4bits);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	host->tuning_data = kzalloc(host->tuning_size, GFP_KERNEL);
+	if (!host->tuning_data)
+		return -ENOMEM;
+
+	val = OMAP_HSMMC_READ(host->base, DLL);
+	val |= DLL_SWT;
+	OMAP_HSMMC_WRITE(host->base, DLL, val);
+
+	while (phase_delay <= MAX_PHASE_DELAY) {
+		omap_hsmmc_set_dll(host, phase_delay);
+
+		cmd.opcode = opcode;
+		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;
+		OMAP_HSMMC_WRITE(host->base, BLK, host->tuning_size);
+		set_data_timeout(host, 150000000, 0);
+		omap_hsmmc_start_command(host, &cmd, NULL);
+		host->cmd = NULL;
+		host->mrq = NULL;
+
+		ret = wait_for_completion_timeout(&host->buf_ready,
+						  msecs_to_jiffies(5000));
+		if (ret == 0) {
+			dev_err(mmc_dev(host->mmc), "Tuning BRR timeout");
+			ret = -ETIMEDOUT;
+			goto tuning_error;
+		}
+
+		host->req_in_progress = false;
+
+		cur_match = !memcmp(host->tuning_data, tuning_ref,
+				    host->tuning_size);
+		if (cur_match) {
+			if (prev_match) {
+				length++;
+			} else {
+				start_window = phase_delay;
+				length = 1;
+			}
+		}
+
+		if (length > max_len) {
+			max_window = start_window;
+			max_len = length;
+		}
+
+		prev_match = cur_match;
+		phase_delay += 4;
+	}
+
+	if (!max_len) {
+		dev_err(mmc_dev(host->mmc), "Unable to find match\n");
+		ret = -EIO;
+		goto tuning_error;
+	}
+
+	val = OMAP_HSMMC_READ(host->base, AC12);
+	if (!(val & AC12_SCLK_SEL)) {
+		ret = -EIO;
+		goto tuning_error;
+	}
+
+	phase_delay = max_window + 4 * (max_len >> 1);
+	omap_hsmmc_set_dll(host, phase_delay);
+
+	omap_hsmmc_reset_controller_fsm(host, SRD);
+	omap_hsmmc_reset_controller_fsm(host, SRC);
+
+	kfree(host->tuning_data);
+	return 0;
+
+tuning_error:
+	dev_err(mmc_dev(host->mmc),
+		"Tuning failed. Using fixed sampling clock\n");
+
+	omap_hsmmc_disable_tuning(host);
+	omap_hsmmc_reset_controller_fsm(host, SRD);
+	omap_hsmmc_reset_controller_fsm(host, SRC);
+
+	kfree(host->tuning_data);
+	return ret;
+}
+
 static struct mmc_host_ops omap_hsmmc_ops = {
 	.post_req = omap_hsmmc_post_req,
 	.pre_req = omap_hsmmc_pre_req,
@@ -2046,6 +2274,7 @@  static struct mmc_host_ops omap_hsmmc_ops = {
 	.enable_sdio_irq = omap_hsmmc_enable_sdio_irq,
 	.start_signal_voltage_switch = omap_hsmmc_start_signal_voltage_switch,
 	.card_busy = omap_hsmmc_card_busy,
+	.execute_tuning = omap_hsmmc_execute_tuning,
 };
 
 #ifdef CONFIG_DEBUG_FS
@@ -2275,6 +2504,7 @@  static int omap_hsmmc_probe(struct platform_device *pdev)
 		mmc->f_max = OMAP_MMC_MAX_CLOCK;
 
 	spin_lock_init(&host->irq_lock);
+	init_completion(&host->buf_ready);
 
 	host->fclk = devm_clk_get(&pdev->dev, "fck");
 	if (IS_ERR(host->fclk)) {