diff mbox

mmc: core: add core-level function for sending tuning commands

Message ID 1416581201-9851-1-git-send-email-21cnbao@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Barry Song Nov. 21, 2014, 2:46 p.m. UTC
From: Minda Chen <Minda.Chen@csr.com>

According to the SD card spec, Add a manual tuning command function
for SDR104/HS200 by sending command 19 or command 21 to read data
and compare with the tuning block pattern.

this patch will help to decrease some platform private codes in
SDHCI platform_execute_tuning() callbacks.

Signed-off-by: Minda Chen <Minda.Chen@csr.com>
Signed-off-by: Barry Song <Baohua.Song@csr.com>
---
 drivers/mmc/core/mmc_ops.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/mmc/core.h   |  1 +
 2 files changed, 66 insertions(+)

Comments

Ulf Hansson Nov. 24, 2014, 11:44 a.m. UTC | #1
On 21 November 2014 at 15:46, Barry Song <21cnbao@gmail.com> wrote:
> From: Minda Chen <Minda.Chen@csr.com>
>
> According to the SD card spec, Add a manual tuning command function
> for SDR104/HS200 by sending command 19 or command 21 to read data
> and compare with the tuning block pattern.
>
> this patch will help to decrease some platform private codes in
> SDHCI platform_execute_tuning() callbacks.
>
> Signed-off-by: Minda Chen <Minda.Chen@csr.com>
> Signed-off-by: Barry Song <Baohua.Song@csr.com>
> ---
>  drivers/mmc/core/mmc_ops.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/mmc/core.h   |  1 +
>  2 files changed, 66 insertions(+)
>
> diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
> index 7911e05..ecc7789 100644
> --- a/drivers/mmc/core/mmc_ops.c
> +++ b/drivers/mmc/core/mmc_ops.c
> @@ -543,6 +543,71 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
>  }
>  EXPORT_SYMBOL_GPL(mmc_switch);
>
> +int mmc_send_tuning(struct mmc_card *card, u32 opcode)
> +{
> +       struct mmc_request mrq = {NULL};
> +       struct mmc_command cmd = {0};
> +       struct mmc_data data = {0};
> +       struct scatterlist sg;
> +       struct mmc_host *mmc = card->host;
> +       struct mmc_ios *ios = &mmc->ios;
> +       const u8 *tuning_block_pattern;
> +       int size, err = 0;
> +       u8 *data_buf;
> +
> +       if (opcode == MMC_SEND_TUNING_BLOCK_HS200) {

I don't think we need to care about the opcode. Let's just check the bus_width.

> +               if (ios->bus_width == MMC_BUS_WIDTH_8) {
> +                       tuning_block_pattern = tuning_blk_pattern_8bit;
> +                       size = sizeof(tuning_blk_pattern_8bit);
> +               } else if (ios->bus_width == MMC_BUS_WIDTH_4) {
> +                       tuning_block_pattern = tuning_blk_pattern_4bit;
> +                       size = sizeof(tuning_blk_pattern_4bit);
> +               } else
> +                       return -EINVAL;
> +       } else if (opcode == MMC_SEND_TUNING_BLOCK) {
> +               tuning_block_pattern = tuning_blk_pattern_4bit;
> +               size = sizeof(tuning_blk_pattern_4bit);
> +       } else
> +               return -EINVAL;
> +
> +       data_buf = kmalloc(size, GFP_KERNEL);

You should use kzalloc() to get the zeroed buffer you want.

> +       if (!data_buf)
> +               return -ENOMEM;
> +
> +       mrq.cmd = &cmd;
> +       mrq.data = &data;
> +
> +       cmd.opcode = opcode;
> +       cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
> +
> +       data.blksz = size;
> +       data.blocks = 1;
> +       data.flags = MMC_DATA_READ;

These needs to be assigned as well:

data.sg = &sg;
data.sg_len = 1;

> +
> +       mmc_set_data_timeout(&data, card);

mmc_set_data_timeout() doesn't handle CMD21/19.

The specs tells us about 40 commands should be executed within 150ms.
I would pick a value of 150ms, just to be sure we are inside that
range. Also, assign "data->timeout_ns" here, instead of relying on
mmc_set_data_timeout().

> +       sg_init_one(&sg, data_buf, size);
> +       memset(data_buf, 0, size);
> +       mmc_wait_for_req(mmc, &mrq);
> +
> +       if (cmd.error) {
> +               err = cmd.error;
> +               goto out;
> +       }
> +
> +       if (data.error) {
> +               err = data.error;
> +               goto out;
> +       }
> +
> +       if (memcmp(data_buf, tuning_block_pattern, size))
> +               err = -EIO;
> +
> +out:
> +       kfree(data_buf);
> +       return err;
> +}
> +EXPORT_SYMBOL_GPL(mmc_send_tuning);
> +
>  static int
>  mmc_send_bus_test(struct mmc_card *card, struct mmc_host *host, u8 opcode,
>                   u8 len)
> diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h
> index f206e29..82a0119 100644
> --- a/include/linux/mmc/core.h
> +++ b/include/linux/mmc/core.h
> @@ -154,6 +154,7 @@ extern void mmc_start_bkops(struct mmc_card *card, bool from_exception);
>  extern int __mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int, bool,
>                         bool, bool);
>  extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int);
> +extern int mmc_send_tuning(struct mmc_card *, u32);
>  extern int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd);
>
>  #define MMC_ERASE_ARG          0x00000000
> --
> 2.1.1
>

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
Barry Song Nov. 25, 2014, 3:19 a.m. UTC | #2
2014-11-24 19:44 GMT+08:00 Ulf Hansson <ulf.hansson@linaro.org>:
> On 21 November 2014 at 15:46, Barry Song <21cnbao@gmail.com> wrote:
>> From: Minda Chen <Minda.Chen@csr.com>
>>
>> According to the SD card spec, Add a manual tuning command function
>> for SDR104/HS200 by sending command 19 or command 21 to read data
>> and compare with the tuning block pattern.
>>
>> this patch will help to decrease some platform private codes in
>> SDHCI platform_execute_tuning() callbacks.
>>
>> Signed-off-by: Minda Chen <Minda.Chen@csr.com>
>> Signed-off-by: Barry Song <Baohua.Song@csr.com>
>> ---
>>  drivers/mmc/core/mmc_ops.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++
>>  include/linux/mmc/core.h   |  1 +
>>  2 files changed, 66 insertions(+)
>>
>> diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
>> index 7911e05..ecc7789 100644
>> --- a/drivers/mmc/core/mmc_ops.c
>> +++ b/drivers/mmc/core/mmc_ops.c
>> @@ -543,6 +543,71 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
>>  }
>>  EXPORT_SYMBOL_GPL(mmc_switch);
>>
>> +int mmc_send_tuning(struct mmc_card *card, u32 opcode)
>> +{
>> +       struct mmc_request mrq = {NULL};
>> +       struct mmc_command cmd = {0};
>> +       struct mmc_data data = {0};
>> +       struct scatterlist sg;
>> +       struct mmc_host *mmc = card->host;
>> +       struct mmc_ios *ios = &mmc->ios;
>> +       const u8 *tuning_block_pattern;
>> +       int size, err = 0;
>> +       u8 *data_buf;
>> +
>> +       if (opcode == MMC_SEND_TUNING_BLOCK_HS200) {
>
> I don't think we need to care about the opcode. Let's just check the bus_width.
>
>> +               if (ios->bus_width == MMC_BUS_WIDTH_8) {
>> +                       tuning_block_pattern = tuning_blk_pattern_8bit;
>> +                       size = sizeof(tuning_blk_pattern_8bit);
>> +               } else if (ios->bus_width == MMC_BUS_WIDTH_4) {
>> +                       tuning_block_pattern = tuning_blk_pattern_4bit;
>> +                       size = sizeof(tuning_blk_pattern_4bit);
>> +               } else
>> +                       return -EINVAL;
>> +       } else if (opcode == MMC_SEND_TUNING_BLOCK) {
>> +               tuning_block_pattern = tuning_blk_pattern_4bit;
>> +               size = sizeof(tuning_blk_pattern_4bit);
>> +       } else
>> +               return -EINVAL;
>> +
>> +       data_buf = kmalloc(size, GFP_KERNEL);
>
> You should use kzalloc() to get the zeroed buffer you want.
>
>> +       if (!data_buf)
>> +               return -ENOMEM;
>> +
>> +       mrq.cmd = &cmd;
>> +       mrq.data = &data;
>> +
>> +       cmd.opcode = opcode;
>> +       cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
>> +
>> +       data.blksz = size;
>> +       data.blocks = 1;
>> +       data.flags = MMC_DATA_READ;
>
> These needs to be assigned as well:
>
> data.sg = &sg;
> data.sg_len = 1;
>
>> +
>> +       mmc_set_data_timeout(&data, card);
>
> mmc_set_data_timeout() doesn't handle CMD21/19.
>
> The specs tells us about 40 commands should be executed within 150ms.
> I would pick a value of 150ms, just to be sure we are inside that
> range. Also, assign "data->timeout_ns" here, instead of relying on
> mmc_set_data_timeout().
>
>> +       sg_init_one(&sg, data_buf, size);
>> +       memset(data_buf, 0, size);
>> +       mmc_wait_for_req(mmc, &mrq);
>> +
>> +       if (cmd.error) {
>> +               err = cmd.error;
>> +               goto out;
>> +       }
>> +
>> +       if (data.error) {
>> +               err = data.error;
>> +               goto out;
>> +       }
>> +
>> +       if (memcmp(data_buf, tuning_block_pattern, size))
>> +               err = -EIO;
>> +
>> +out:
>> +       kfree(data_buf);
>> +       return err;
>> +}
>> +EXPORT_SYMBOL_GPL(mmc_send_tuning);
>> +
>>  static int
>>  mmc_send_bus_test(struct mmc_card *card, struct mmc_host *host, u8 opcode,
>>                   u8 len)
>> diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h
>> index f206e29..82a0119 100644
>> --- a/include/linux/mmc/core.h
>> +++ b/include/linux/mmc/core.h
>> @@ -154,6 +154,7 @@ extern void mmc_start_bkops(struct mmc_card *card, bool from_exception);
>>  extern int __mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int, bool,
>>                         bool, bool);
>>  extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int);
>> +extern int mmc_send_tuning(struct mmc_card *, u32);
>>  extern int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd);
>>
>>  #define MMC_ERASE_ARG          0x00000000
>> --
>> 2.1.1
>>

What about?

546 int mmc_send_tuning(struct mmc_card *card)
547 {
548         struct mmc_request mrq = {NULL};
549         struct mmc_command cmd = {0};
550         struct mmc_data data = {0};
551         struct scatterlist sg;
552         struct mmc_host *mmc = card->host;
553         struct mmc_ios *ios = &mmc->ios;
554         const u8 *tuning_block_pattern;
555         int size, err = 0;
556         u8 *data_buf;
557         u32 opcode;
558
559         if (ios->bus_width == MMC_BUS_WIDTH_8) {
560                 tuning_block_pattern = tuning_blk_pattern_8bit;
561                 size = sizeof(tuning_blk_pattern_8bit);
562                 opcode = MMC_SEND_TUNING_BLOCK_HS200;
563         } else if (ios->bus_width == MMC_BUS_WIDTH_4) {
564                 tuning_block_pattern = tuning_blk_pattern_4bit;
565                 size = sizeof(tuning_blk_pattern_4bit);
566                 opcode = MMC_SEND_TUNING_BLOCK;
567         } else
568                 return -EINVAL;
569
570         data_buf = kzalloc(size, GFP_KERNEL);
571         if (!data_buf)
572                 return -ENOMEM;
573
574         mrq.cmd = &cmd;
575         mrq.data = &data;
576
577         cmd.opcode = opcode;
578         cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
579
580         data.blksz = size;
581         data.blocks = 1;
582         data.flags = MMC_DATA_READ;
583
584         /*
585          * According to the tuning specs, Tuning process
586          * is normally shorter 40 executions of CMD19,
587          * and timeout value should be shorter than 150 ms
588          */
589         data.timeout_ns = 150 * NSEC_PER_MSEC;
590
591         data.sg = &sg;
592         data.sg_len = 1;
593         sg_init_one(&sg, data_buf, size);
594
595         mmc_wait_for_req(mmc, &mrq);
596
597         if (cmd.error) {
598                 err = cmd.error;
599                 goto out;
600         }
601
602         if (data.error) {
603                 err = data.error;
604                 goto out;
605         }
606
607         if (memcmp(data_buf, tuning_block_pattern, size))
608                 err = -EIO;
609
610 out:
611         kfree(data_buf);
612         return err;
613 }
614 EXPORT_SYMBOL_GPL(mmc_send_tuning);

>
> Kind regards
> Uffe

-barry
--
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
Ulf Hansson Nov. 25, 2014, 10:32 a.m. UTC | #3
On 25 November 2014 at 04:19, Barry Song <21cnbao@gmail.com> wrote:
> 2014-11-24 19:44 GMT+08:00 Ulf Hansson <ulf.hansson@linaro.org>:
>> On 21 November 2014 at 15:46, Barry Song <21cnbao@gmail.com> wrote:
>>> From: Minda Chen <Minda.Chen@csr.com>
>>>
>>> According to the SD card spec, Add a manual tuning command function
>>> for SDR104/HS200 by sending command 19 or command 21 to read data
>>> and compare with the tuning block pattern.
>>>
>>> this patch will help to decrease some platform private codes in
>>> SDHCI platform_execute_tuning() callbacks.
>>>
>>> Signed-off-by: Minda Chen <Minda.Chen@csr.com>
>>> Signed-off-by: Barry Song <Baohua.Song@csr.com>
>>> ---
>>>  drivers/mmc/core/mmc_ops.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++
>>>  include/linux/mmc/core.h   |  1 +
>>>  2 files changed, 66 insertions(+)
>>>
>>> diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
>>> index 7911e05..ecc7789 100644
>>> --- a/drivers/mmc/core/mmc_ops.c
>>> +++ b/drivers/mmc/core/mmc_ops.c
>>> @@ -543,6 +543,71 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
>>>  }
>>>  EXPORT_SYMBOL_GPL(mmc_switch);
>>>
>>> +int mmc_send_tuning(struct mmc_card *card, u32 opcode)
>>> +{
>>> +       struct mmc_request mrq = {NULL};
>>> +       struct mmc_command cmd = {0};
>>> +       struct mmc_data data = {0};
>>> +       struct scatterlist sg;
>>> +       struct mmc_host *mmc = card->host;
>>> +       struct mmc_ios *ios = &mmc->ios;
>>> +       const u8 *tuning_block_pattern;
>>> +       int size, err = 0;
>>> +       u8 *data_buf;
>>> +
>>> +       if (opcode == MMC_SEND_TUNING_BLOCK_HS200) {
>>
>> I don't think we need to care about the opcode. Let's just check the bus_width.
>>
>>> +               if (ios->bus_width == MMC_BUS_WIDTH_8) {
>>> +                       tuning_block_pattern = tuning_blk_pattern_8bit;
>>> +                       size = sizeof(tuning_blk_pattern_8bit);
>>> +               } else if (ios->bus_width == MMC_BUS_WIDTH_4) {
>>> +                       tuning_block_pattern = tuning_blk_pattern_4bit;
>>> +                       size = sizeof(tuning_blk_pattern_4bit);
>>> +               } else
>>> +                       return -EINVAL;
>>> +       } else if (opcode == MMC_SEND_TUNING_BLOCK) {
>>> +               tuning_block_pattern = tuning_blk_pattern_4bit;
>>> +               size = sizeof(tuning_blk_pattern_4bit);
>>> +       } else
>>> +               return -EINVAL;
>>> +
>>> +       data_buf = kmalloc(size, GFP_KERNEL);
>>
>> You should use kzalloc() to get the zeroed buffer you want.
>>
>>> +       if (!data_buf)
>>> +               return -ENOMEM;
>>> +
>>> +       mrq.cmd = &cmd;
>>> +       mrq.data = &data;
>>> +
>>> +       cmd.opcode = opcode;
>>> +       cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
>>> +
>>> +       data.blksz = size;
>>> +       data.blocks = 1;
>>> +       data.flags = MMC_DATA_READ;
>>
>> These needs to be assigned as well:
>>
>> data.sg = &sg;
>> data.sg_len = 1;
>>
>>> +
>>> +       mmc_set_data_timeout(&data, card);
>>
>> mmc_set_data_timeout() doesn't handle CMD21/19.
>>
>> The specs tells us about 40 commands should be executed within 150ms.
>> I would pick a value of 150ms, just to be sure we are inside that
>> range. Also, assign "data->timeout_ns" here, instead of relying on
>> mmc_set_data_timeout().
>>
>>> +       sg_init_one(&sg, data_buf, size);
>>> +       memset(data_buf, 0, size);
>>> +       mmc_wait_for_req(mmc, &mrq);
>>> +
>>> +       if (cmd.error) {
>>> +               err = cmd.error;
>>> +               goto out;
>>> +       }
>>> +
>>> +       if (data.error) {
>>> +               err = data.error;
>>> +               goto out;
>>> +       }
>>> +
>>> +       if (memcmp(data_buf, tuning_block_pattern, size))
>>> +               err = -EIO;
>>> +
>>> +out:
>>> +       kfree(data_buf);
>>> +       return err;
>>> +}
>>> +EXPORT_SYMBOL_GPL(mmc_send_tuning);
>>> +
>>>  static int
>>>  mmc_send_bus_test(struct mmc_card *card, struct mmc_host *host, u8 opcode,
>>>                   u8 len)
>>> diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h
>>> index f206e29..82a0119 100644
>>> --- a/include/linux/mmc/core.h
>>> +++ b/include/linux/mmc/core.h
>>> @@ -154,6 +154,7 @@ extern void mmc_start_bkops(struct mmc_card *card, bool from_exception);
>>>  extern int __mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int, bool,
>>>                         bool, bool);
>>>  extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int);
>>> +extern int mmc_send_tuning(struct mmc_card *, u32);
>>>  extern int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd);
>>>
>>>  #define MMC_ERASE_ARG          0x00000000
>>> --
>>> 2.1.1
>>>
>
> What about?
>
> 546 int mmc_send_tuning(struct mmc_card *card)
> 547 {
> 548         struct mmc_request mrq = {NULL};
> 549         struct mmc_command cmd = {0};
> 550         struct mmc_data data = {0};
> 551         struct scatterlist sg;
> 552         struct mmc_host *mmc = card->host;
> 553         struct mmc_ios *ios = &mmc->ios;
> 554         const u8 *tuning_block_pattern;
> 555         int size, err = 0;
> 556         u8 *data_buf;
> 557         u32 opcode;
> 558
> 559         if (ios->bus_width == MMC_BUS_WIDTH_8) {
> 560                 tuning_block_pattern = tuning_blk_pattern_8bit;
> 561                 size = sizeof(tuning_blk_pattern_8bit);
> 562                 opcode = MMC_SEND_TUNING_BLOCK_HS200;
> 563         } else if (ios->bus_width == MMC_BUS_WIDTH_4) {
> 564                 tuning_block_pattern = tuning_blk_pattern_4bit;
> 565                 size = sizeof(tuning_blk_pattern_4bit);
> 566                 opcode = MMC_SEND_TUNING_BLOCK;
> 567         } else
> 568                 return -EINVAL;
> 569
> 570         data_buf = kzalloc(size, GFP_KERNEL);
> 571         if (!data_buf)
> 572                 return -ENOMEM;
> 573
> 574         mrq.cmd = &cmd;
> 575         mrq.data = &data;
> 576
> 577         cmd.opcode = opcode;
> 578         cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
> 579
> 580         data.blksz = size;
> 581         data.blocks = 1;
> 582         data.flags = MMC_DATA_READ;
> 583
> 584         /*
> 585          * According to the tuning specs, Tuning process
> 586          * is normally shorter 40 executions of CMD19,
> 587          * and timeout value should be shorter than 150 ms
> 588          */
> 589         data.timeout_ns = 150 * NSEC_PER_MSEC;
> 590
> 591         data.sg = &sg;
> 592         data.sg_len = 1;
> 593         sg_init_one(&sg, data_buf, size);
> 594
> 595         mmc_wait_for_req(mmc, &mrq);
> 596
> 597         if (cmd.error) {
> 598                 err = cmd.error;
> 599                 goto out;
> 600         }
> 601
> 602         if (data.error) {
> 603                 err = data.error;
> 604                 goto out;
> 605         }
> 606
> 607         if (memcmp(data_buf, tuning_block_pattern, size))
> 608                 err = -EIO;
> 609
> 610 out:
> 611         kfree(data_buf);
> 612         return err;
> 613 }
> 614 EXPORT_SYMBOL_GPL(mmc_send_tuning);
>

Looks good!

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/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
index 7911e05..ecc7789 100644
--- a/drivers/mmc/core/mmc_ops.c
+++ b/drivers/mmc/core/mmc_ops.c
@@ -543,6 +543,71 @@  int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
 }
 EXPORT_SYMBOL_GPL(mmc_switch);
 
+int mmc_send_tuning(struct mmc_card *card, u32 opcode)
+{
+	struct mmc_request mrq = {NULL};
+	struct mmc_command cmd = {0};
+	struct mmc_data data = {0};
+	struct scatterlist sg;
+	struct mmc_host *mmc = card->host;
+	struct mmc_ios *ios = &mmc->ios;
+	const u8 *tuning_block_pattern;
+	int size, err = 0;
+	u8 *data_buf;
+
+	if (opcode == MMC_SEND_TUNING_BLOCK_HS200) {
+		if (ios->bus_width == MMC_BUS_WIDTH_8) {
+			tuning_block_pattern = tuning_blk_pattern_8bit;
+			size = sizeof(tuning_blk_pattern_8bit);
+		} else if (ios->bus_width == MMC_BUS_WIDTH_4) {
+			tuning_block_pattern = tuning_blk_pattern_4bit;
+			size = sizeof(tuning_blk_pattern_4bit);
+		} else
+			return -EINVAL;
+	} else if (opcode == MMC_SEND_TUNING_BLOCK) {
+		tuning_block_pattern = tuning_blk_pattern_4bit;
+		size = sizeof(tuning_blk_pattern_4bit);
+	} else
+		return -EINVAL;
+
+	data_buf = kmalloc(size, GFP_KERNEL);
+	if (!data_buf)
+		return -ENOMEM;
+
+	mrq.cmd = &cmd;
+	mrq.data = &data;
+
+	cmd.opcode = opcode;
+	cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+
+	data.blksz = size;
+	data.blocks = 1;
+	data.flags = MMC_DATA_READ;
+
+	mmc_set_data_timeout(&data, card);
+	sg_init_one(&sg, data_buf, size);
+	memset(data_buf, 0, size);
+	mmc_wait_for_req(mmc, &mrq);
+
+	if (cmd.error) {
+		err = cmd.error;
+		goto out;
+	}
+
+	if (data.error) {
+		err = data.error;
+		goto out;
+	}
+
+	if (memcmp(data_buf, tuning_block_pattern, size))
+		err = -EIO;
+
+out:
+	kfree(data_buf);
+	return err;
+}
+EXPORT_SYMBOL_GPL(mmc_send_tuning);
+
 static int
 mmc_send_bus_test(struct mmc_card *card, struct mmc_host *host, u8 opcode,
 		  u8 len)
diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h
index f206e29..82a0119 100644
--- a/include/linux/mmc/core.h
+++ b/include/linux/mmc/core.h
@@ -154,6 +154,7 @@  extern void mmc_start_bkops(struct mmc_card *card, bool from_exception);
 extern int __mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int, bool,
 			bool, bool);
 extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int);
+extern int mmc_send_tuning(struct mmc_card *, u32);
 extern int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd);
 
 #define MMC_ERASE_ARG		0x00000000