2020-06-04 13:35:27

by Veerabhadrarao Badiganti

[permalink] [raw]
Subject: [RFC V1] mmc: core: Add partial initialization support

From: Maya Erez <[email protected]>

This change adds the ability to partially initialize
the MMC card by using card Sleep/Awake sequence (CMD5).
Card will be sent to Sleep state during runtime/system suspend
and will be woken up during runtime/system resume.
By using this sequence the card doesn't need full initialization
which gives time reduction in system/runtime resume path.

Signed-off-by: Maya Erez <[email protected]>
Signed-off-by: Veerabhadrarao Badiganti <[email protected]>
---
drivers/mmc/core/mmc.c | 146 ++++++++++++++++++++++++++++++++++++++++++++---
include/linux/mmc/card.h | 4 ++
include/linux/mmc/host.h | 2 +
3 files changed, 143 insertions(+), 9 deletions(-)

diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index de94fbe629bd..5c6444113c92 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -1892,7 +1892,13 @@ static int mmc_can_sleep(struct mmc_card *card)
return (card && card->ext_csd.rev >= 3);
}

-static int mmc_sleep(struct mmc_host *host)
+static int mmc_can_sleepawake(struct mmc_host *host)
+{
+ return host && (host->caps2 & MMC_CAP2_SLEEP_AWAKE) && host->card &&
+ (host->card->ext_csd.rev >= 3);
+}
+
+static int mmc_sleepawake(struct mmc_host *host, bool sleep)
{
struct mmc_command cmd = {};
struct mmc_card *card = host->card;
@@ -1902,13 +1908,16 @@ static int mmc_sleep(struct mmc_host *host)
/* Re-tuning can't be done once the card is deselected */
mmc_retune_hold(host);

- err = mmc_deselect_cards(host);
- if (err)
- goto out_release;
+ if (sleep) {
+ err = mmc_deselect_cards(host);
+ if (err)
+ goto out_release;
+ }

cmd.opcode = MMC_SLEEP_AWAKE;
cmd.arg = card->rca << 16;
- cmd.arg |= 1 << 15;
+ if (sleep)
+ cmd.arg |= 1 << 15;

/*
* If the max_busy_timeout of the host is specified, validate it against
@@ -1939,6 +1948,9 @@ static int mmc_sleep(struct mmc_host *host)
if (!cmd.busy_timeout || !(host->caps & MMC_CAP_WAIT_WHILE_BUSY))
mmc_delay(timeout_ms);

+ if (!sleep)
+ err = mmc_select_card(card);
+
out_release:
mmc_retune_release(host);
return err;
@@ -2016,6 +2028,69 @@ static void mmc_detect(struct mmc_host *host)
}
}

+static int mmc_cache_card_ext_csd(struct mmc_host *host)
+{
+ int err;
+ u8 *ext_csd;
+ struct mmc_card *card = host->card;
+
+ err = mmc_get_ext_csd(card, &ext_csd);
+ if (err || !ext_csd) {
+ pr_err("%s: %s: mmc_get_ext_csd failed (%d)\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
+ /* only cache read/write fields that the sw changes */
+ card->ext_csd.raw_ext_csd_cmdq = ext_csd[EXT_CSD_CMDQ_MODE_EN];
+ card->ext_csd.raw_ext_csd_cache_ctrl = ext_csd[EXT_CSD_CACHE_CTRL];
+ card->ext_csd.raw_ext_csd_bus_width = ext_csd[EXT_CSD_BUS_WIDTH];
+ card->ext_csd.raw_ext_csd_hs_timing = ext_csd[EXT_CSD_HS_TIMING];
+
+ kfree(ext_csd);
+
+ return 0;
+}
+
+static int mmc_test_awake_ext_csd(struct mmc_host *host)
+{
+ int err;
+ u8 *ext_csd;
+ struct mmc_card *card = host->card;
+
+ err = mmc_get_ext_csd(card, &ext_csd);
+ if (err) {
+ pr_err("%s: %s: mmc_get_ext_csd failed (%d)\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
+ /* only compare read/write fields that the sw changes */
+ pr_debug("%s: %s: type(cached:current) cmdq(%d:%d) cache_ctrl(%d:%d) bus_width (%d:%d) timing(%d:%d)\n",
+ mmc_hostname(host), __func__,
+ card->ext_csd.raw_ext_csd_cmdq,
+ ext_csd[EXT_CSD_CMDQ_MODE_EN],
+ card->ext_csd.raw_ext_csd_cache_ctrl,
+ ext_csd[EXT_CSD_CACHE_CTRL],
+ card->ext_csd.raw_ext_csd_bus_width,
+ ext_csd[EXT_CSD_BUS_WIDTH],
+ card->ext_csd.raw_ext_csd_hs_timing,
+ ext_csd[EXT_CSD_HS_TIMING]);
+
+ err = !((card->ext_csd.raw_ext_csd_cmdq ==
+ ext_csd[EXT_CSD_CMDQ_MODE_EN]) &&
+ (card->ext_csd.raw_ext_csd_cache_ctrl ==
+ ext_csd[EXT_CSD_CACHE_CTRL]) &&
+ (card->ext_csd.raw_ext_csd_bus_width ==
+ ext_csd[EXT_CSD_BUS_WIDTH]) &&
+ (card->ext_csd.raw_ext_csd_hs_timing ==
+ ext_csd[EXT_CSD_HS_TIMING]));
+
+ kfree(ext_csd);
+
+ return err;
+}
+
static int _mmc_suspend(struct mmc_host *host, bool is_suspend)
{
int err = 0;
@@ -2034,8 +2109,12 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend)
if (mmc_can_poweroff_notify(host->card) &&
((host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) || !is_suspend))
err = mmc_poweroff_notify(host->card, notify_type);
- else if (mmc_can_sleep(host->card))
- err = mmc_sleep(host);
+ if (mmc_can_sleepawake(host)) {
+ memcpy(&host->cached_ios, &host->ios, sizeof(host->cached_ios));
+ mmc_cache_card_ext_csd(host);
+ }
+ if (mmc_can_sleep(host->card))
+ err = mmc_sleepawake(host, true);
else if (!mmc_host_is_spi(host))
err = mmc_deselect_cards(host);

@@ -2048,6 +2127,43 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend)
return err;
}

+static int mmc_partial_init(struct mmc_host *host)
+{
+ int err = 0;
+ struct mmc_card *card = host->card;
+
+ mmc_set_bus_width(host, host->cached_ios.bus_width);
+ mmc_set_timing(host, host->cached_ios.timing);
+ if (host->cached_ios.enhanced_strobe) {
+ host->ios.enhanced_strobe = true;
+ if (host->ops->hs400_enhanced_strobe)
+ host->ops->hs400_enhanced_strobe(host, &host->ios);
+ }
+ mmc_set_clock(host, host->cached_ios.clock);
+ mmc_set_bus_mode(host, host->cached_ios.bus_mode);
+
+ if (!mmc_card_hs400es(card) &&
+ (mmc_card_hs200(card) || mmc_card_hs400(card)))
+ err = mmc_execute_tuning(card);
+
+ /*
+ * The ext_csd is read to make sure the card did not went through
+ * Power-failure during sleep period.
+ * A subset of the W/E_P, W/C_P register will be tested. In case
+ * these registers values are different from the values that were
+ * cached during suspend, we will conclude that a Power-failure occurred
+ * and will do full initialization sequence.
+ */
+ err = mmc_test_awake_ext_csd(host);
+ if (err) {
+ pr_debug("%s: %s: fail on ext_csd read (%d)\n",
+ mmc_hostname(host), __func__, err);
+ goto out;
+ }
+out:
+ return err;
+}
+
/*
* Suspend callback
*/
@@ -2070,7 +2186,7 @@ static int mmc_suspend(struct mmc_host *host)
*/
static int _mmc_resume(struct mmc_host *host)
{
- int err = 0;
+ int err = -EINVAL;

mmc_claim_host(host);

@@ -2078,7 +2194,19 @@ static int _mmc_resume(struct mmc_host *host)
goto out;

mmc_power_up(host, host->card->ocr);
- err = mmc_init_card(host, host->card->ocr, host->card);
+
+ if (mmc_can_sleepawake(host)) {
+ err = mmc_sleepawake(host, false);
+ if (!err)
+ err = mmc_partial_init(host);
+ else
+ pr_err("%s: %s: awake failed (%d), fallback to full init\n",
+ mmc_hostname(host), __func__, err);
+ }
+
+ if (err)
+ err = mmc_init_card(host, host->card->ocr, host->card);
+
mmc_card_clr_suspended(host->card);

out:
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index cf3780a6ccc4..2f4c8d3d5763 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -85,6 +85,8 @@ struct mmc_ext_csd {
unsigned int data_tag_unit_size; /* DATA TAG UNIT size */
unsigned int boot_ro_lock; /* ro lock support */
bool boot_ro_lockable;
+ u8 raw_ext_csd_cmdq; /* 15 */
+ u8 raw_ext_csd_cache_ctrl; /* 33 */
bool ffu_capable; /* Firmware upgrade support */
bool cmdq_en; /* Command Queue enabled */
bool cmdq_support; /* Command Queue supported */
@@ -95,7 +97,9 @@ struct mmc_ext_csd {
u8 raw_partition_support; /* 160 */
u8 raw_rpmb_size_mult; /* 168 */
u8 raw_erased_mem_count; /* 181 */
+ u8 raw_ext_csd_bus_width; /* 183 */
u8 strobe_support; /* 184 */
+ u8 raw_ext_csd_hs_timing; /* 185 */
u8 raw_ext_csd_structure; /* 194 */
u8 raw_card_type; /* 196 */
u8 raw_driver_strength; /* 197 */
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index d4a50e5dc111..a69abe9f032b 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -375,6 +375,7 @@ struct mmc_host {
#define MMC_CAP2_CQE_DCMD (1 << 24) /* CQE can issue a direct command */
#define MMC_CAP2_AVOID_3_3V (1 << 25) /* Host must negotiate down from 3.3V */
#define MMC_CAP2_MERGE_CAPABLE (1 << 26) /* Host can merge a segment over the segment size */
+#define MMC_CAP2_SLEEP_AWAKE (1 << 27) /* Use Sleep/Awake (CMD5) */

int fixed_drv_type; /* fixed driver type for non-removable media */

@@ -393,6 +394,7 @@ struct mmc_host {
spinlock_t lock; /* lock for claim and bus ops */

struct mmc_ios ios; /* current io bus settings */
+ struct mmc_ios cached_ios;

/* group bitfields together to minimize padding */
unsigned int use_spi_crc:1;
--
Qualcomm India Private Limited, on behalf of Qualcomm Innovation Center, Inc., is a member of Code Aurora Forum, a Linux Foundation Collaborative Project


2020-06-08 07:31:57

by Ulf Hansson

[permalink] [raw]
Subject: Re: [RFC V1] mmc: core: Add partial initialization support

On Thu, 4 Jun 2020 at 15:30, Veerabhadrarao Badiganti
<[email protected]> wrote:
>
> From: Maya Erez <[email protected]>
>
> This change adds the ability to partially initialize
> the MMC card by using card Sleep/Awake sequence (CMD5).
> Card will be sent to Sleep state during runtime/system suspend
> and will be woken up during runtime/system resume.
> By using this sequence the card doesn't need full initialization
> which gives time reduction in system/runtime resume path.

It would be nice if you could provide some more exact numbers of what
the gain is for a couple of different eMMCs, as to justify the change
a bit more.

Kind regards
Uffe

>
> Signed-off-by: Maya Erez <[email protected]>
> Signed-off-by: Veerabhadrarao Badiganti <[email protected]>
> ---
> drivers/mmc/core/mmc.c | 146 ++++++++++++++++++++++++++++++++++++++++++++---
> include/linux/mmc/card.h | 4 ++
> include/linux/mmc/host.h | 2 +
> 3 files changed, 143 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
> index de94fbe629bd..5c6444113c92 100644
> --- a/drivers/mmc/core/mmc.c
> +++ b/drivers/mmc/core/mmc.c
> @@ -1892,7 +1892,13 @@ static int mmc_can_sleep(struct mmc_card *card)
> return (card && card->ext_csd.rev >= 3);
> }
>
> -static int mmc_sleep(struct mmc_host *host)
> +static int mmc_can_sleepawake(struct mmc_host *host)
> +{
> + return host && (host->caps2 & MMC_CAP2_SLEEP_AWAKE) && host->card &&
> + (host->card->ext_csd.rev >= 3);
> +}
> +
> +static int mmc_sleepawake(struct mmc_host *host, bool sleep)
> {
> struct mmc_command cmd = {};
> struct mmc_card *card = host->card;
> @@ -1902,13 +1908,16 @@ static int mmc_sleep(struct mmc_host *host)
> /* Re-tuning can't be done once the card is deselected */
> mmc_retune_hold(host);
>
> - err = mmc_deselect_cards(host);
> - if (err)
> - goto out_release;
> + if (sleep) {
> + err = mmc_deselect_cards(host);
> + if (err)
> + goto out_release;
> + }
>
> cmd.opcode = MMC_SLEEP_AWAKE;
> cmd.arg = card->rca << 16;
> - cmd.arg |= 1 << 15;
> + if (sleep)
> + cmd.arg |= 1 << 15;
>
> /*
> * If the max_busy_timeout of the host is specified, validate it against
> @@ -1939,6 +1948,9 @@ static int mmc_sleep(struct mmc_host *host)
> if (!cmd.busy_timeout || !(host->caps & MMC_CAP_WAIT_WHILE_BUSY))
> mmc_delay(timeout_ms);
>
> + if (!sleep)
> + err = mmc_select_card(card);
> +
> out_release:
> mmc_retune_release(host);
> return err;
> @@ -2016,6 +2028,69 @@ static void mmc_detect(struct mmc_host *host)
> }
> }
>
> +static int mmc_cache_card_ext_csd(struct mmc_host *host)
> +{
> + int err;
> + u8 *ext_csd;
> + struct mmc_card *card = host->card;
> +
> + err = mmc_get_ext_csd(card, &ext_csd);
> + if (err || !ext_csd) {
> + pr_err("%s: %s: mmc_get_ext_csd failed (%d)\n",
> + mmc_hostname(host), __func__, err);
> + return err;
> + }
> +
> + /* only cache read/write fields that the sw changes */
> + card->ext_csd.raw_ext_csd_cmdq = ext_csd[EXT_CSD_CMDQ_MODE_EN];
> + card->ext_csd.raw_ext_csd_cache_ctrl = ext_csd[EXT_CSD_CACHE_CTRL];
> + card->ext_csd.raw_ext_csd_bus_width = ext_csd[EXT_CSD_BUS_WIDTH];
> + card->ext_csd.raw_ext_csd_hs_timing = ext_csd[EXT_CSD_HS_TIMING];
> +
> + kfree(ext_csd);
> +
> + return 0;
> +}
> +
> +static int mmc_test_awake_ext_csd(struct mmc_host *host)
> +{
> + int err;
> + u8 *ext_csd;
> + struct mmc_card *card = host->card;
> +
> + err = mmc_get_ext_csd(card, &ext_csd);
> + if (err) {
> + pr_err("%s: %s: mmc_get_ext_csd failed (%d)\n",
> + mmc_hostname(host), __func__, err);
> + return err;
> + }
> +
> + /* only compare read/write fields that the sw changes */
> + pr_debug("%s: %s: type(cached:current) cmdq(%d:%d) cache_ctrl(%d:%d) bus_width (%d:%d) timing(%d:%d)\n",
> + mmc_hostname(host), __func__,
> + card->ext_csd.raw_ext_csd_cmdq,
> + ext_csd[EXT_CSD_CMDQ_MODE_EN],
> + card->ext_csd.raw_ext_csd_cache_ctrl,
> + ext_csd[EXT_CSD_CACHE_CTRL],
> + card->ext_csd.raw_ext_csd_bus_width,
> + ext_csd[EXT_CSD_BUS_WIDTH],
> + card->ext_csd.raw_ext_csd_hs_timing,
> + ext_csd[EXT_CSD_HS_TIMING]);
> +
> + err = !((card->ext_csd.raw_ext_csd_cmdq ==
> + ext_csd[EXT_CSD_CMDQ_MODE_EN]) &&
> + (card->ext_csd.raw_ext_csd_cache_ctrl ==
> + ext_csd[EXT_CSD_CACHE_CTRL]) &&
> + (card->ext_csd.raw_ext_csd_bus_width ==
> + ext_csd[EXT_CSD_BUS_WIDTH]) &&
> + (card->ext_csd.raw_ext_csd_hs_timing ==
> + ext_csd[EXT_CSD_HS_TIMING]));
> +
> + kfree(ext_csd);
> +
> + return err;
> +}
> +
> static int _mmc_suspend(struct mmc_host *host, bool is_suspend)
> {
> int err = 0;
> @@ -2034,8 +2109,12 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend)
> if (mmc_can_poweroff_notify(host->card) &&
> ((host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) || !is_suspend))
> err = mmc_poweroff_notify(host->card, notify_type);
> - else if (mmc_can_sleep(host->card))
> - err = mmc_sleep(host);
> + if (mmc_can_sleepawake(host)) {
> + memcpy(&host->cached_ios, &host->ios, sizeof(host->cached_ios));
> + mmc_cache_card_ext_csd(host);
> + }
> + if (mmc_can_sleep(host->card))
> + err = mmc_sleepawake(host, true);
> else if (!mmc_host_is_spi(host))
> err = mmc_deselect_cards(host);
>
> @@ -2048,6 +2127,43 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend)
> return err;
> }
>
> +static int mmc_partial_init(struct mmc_host *host)
> +{
> + int err = 0;
> + struct mmc_card *card = host->card;
> +
> + mmc_set_bus_width(host, host->cached_ios.bus_width);
> + mmc_set_timing(host, host->cached_ios.timing);
> + if (host->cached_ios.enhanced_strobe) {
> + host->ios.enhanced_strobe = true;
> + if (host->ops->hs400_enhanced_strobe)
> + host->ops->hs400_enhanced_strobe(host, &host->ios);
> + }
> + mmc_set_clock(host, host->cached_ios.clock);
> + mmc_set_bus_mode(host, host->cached_ios.bus_mode);
> +
> + if (!mmc_card_hs400es(card) &&
> + (mmc_card_hs200(card) || mmc_card_hs400(card)))
> + err = mmc_execute_tuning(card);
> +
> + /*
> + * The ext_csd is read to make sure the card did not went through
> + * Power-failure during sleep period.
> + * A subset of the W/E_P, W/C_P register will be tested. In case
> + * these registers values are different from the values that were
> + * cached during suspend, we will conclude that a Power-failure occurred
> + * and will do full initialization sequence.
> + */
> + err = mmc_test_awake_ext_csd(host);
> + if (err) {
> + pr_debug("%s: %s: fail on ext_csd read (%d)\n",
> + mmc_hostname(host), __func__, err);
> + goto out;
> + }
> +out:
> + return err;
> +}
> +
> /*
> * Suspend callback
> */
> @@ -2070,7 +2186,7 @@ static int mmc_suspend(struct mmc_host *host)
> */
> static int _mmc_resume(struct mmc_host *host)
> {
> - int err = 0;
> + int err = -EINVAL;
>
> mmc_claim_host(host);
>
> @@ -2078,7 +2194,19 @@ static int _mmc_resume(struct mmc_host *host)
> goto out;
>
> mmc_power_up(host, host->card->ocr);
> - err = mmc_init_card(host, host->card->ocr, host->card);
> +
> + if (mmc_can_sleepawake(host)) {
> + err = mmc_sleepawake(host, false);
> + if (!err)
> + err = mmc_partial_init(host);
> + else
> + pr_err("%s: %s: awake failed (%d), fallback to full init\n",
> + mmc_hostname(host), __func__, err);
> + }
> +
> + if (err)
> + err = mmc_init_card(host, host->card->ocr, host->card);
> +
> mmc_card_clr_suspended(host->card);
>
> out:
> diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
> index cf3780a6ccc4..2f4c8d3d5763 100644
> --- a/include/linux/mmc/card.h
> +++ b/include/linux/mmc/card.h
> @@ -85,6 +85,8 @@ struct mmc_ext_csd {
> unsigned int data_tag_unit_size; /* DATA TAG UNIT size */
> unsigned int boot_ro_lock; /* ro lock support */
> bool boot_ro_lockable;
> + u8 raw_ext_csd_cmdq; /* 15 */
> + u8 raw_ext_csd_cache_ctrl; /* 33 */
> bool ffu_capable; /* Firmware upgrade support */
> bool cmdq_en; /* Command Queue enabled */
> bool cmdq_support; /* Command Queue supported */
> @@ -95,7 +97,9 @@ struct mmc_ext_csd {
> u8 raw_partition_support; /* 160 */
> u8 raw_rpmb_size_mult; /* 168 */
> u8 raw_erased_mem_count; /* 181 */
> + u8 raw_ext_csd_bus_width; /* 183 */
> u8 strobe_support; /* 184 */
> + u8 raw_ext_csd_hs_timing; /* 185 */
> u8 raw_ext_csd_structure; /* 194 */
> u8 raw_card_type; /* 196 */
> u8 raw_driver_strength; /* 197 */
> diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
> index d4a50e5dc111..a69abe9f032b 100644
> --- a/include/linux/mmc/host.h
> +++ b/include/linux/mmc/host.h
> @@ -375,6 +375,7 @@ struct mmc_host {
> #define MMC_CAP2_CQE_DCMD (1 << 24) /* CQE can issue a direct command */
> #define MMC_CAP2_AVOID_3_3V (1 << 25) /* Host must negotiate down from 3.3V */
> #define MMC_CAP2_MERGE_CAPABLE (1 << 26) /* Host can merge a segment over the segment size */
> +#define MMC_CAP2_SLEEP_AWAKE (1 << 27) /* Use Sleep/Awake (CMD5) */
>
> int fixed_drv_type; /* fixed driver type for non-removable media */
>
> @@ -393,6 +394,7 @@ struct mmc_host {
> spinlock_t lock; /* lock for claim and bus ops */
>
> struct mmc_ios ios; /* current io bus settings */
> + struct mmc_ios cached_ios;
>
> /* group bitfields together to minimize padding */
> unsigned int use_spi_crc:1;
> --
> Qualcomm India Private Limited, on behalf of Qualcomm Innovation Center, Inc., is a member of Code Aurora Forum, a Linux Foundation Collaborative Project
>