mmc: core: Fixup broken suspend and eMMC4.5 power off notify
This patch fixes up the broken suspend sequence for eMMC with sleep
support. Additionally it reworks the eMMC4.5 Power Off Notification
feature so it fits together with the existing sleep feature.
The CMD0 based re-initialization of the eMMC at resume is re-introduced
to maintain compatiblity for devices using sleep.
A host shall use MMC_CAP2_POWEROFF_NOTIFY to enable the Power Off
Notification feature. We might be able to remove this cap later on,
if we think that Power Off Notification always is preferred over
sleep, even if the host is not able to cut the eMMC VCCQ power.
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
Signed-off-by: Saugata Das <saugata.das@linaro.org>
Acked-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Chris Ball <cjb@laptop.org>
[merez@codeaurora.org: resolved various merge conflicts.
Fix in mmc_resume already exists]
Change-Id: I93be6ff51c3050c54ccc025414dcd4ca916d6a46
Signed-off-by: Tatyana Brokhman <tlinder@codeaurora.org>
Signed-off-by: Maya Erez <merez@codeaurora.org>
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index b91f3d1..3aa38d2 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -1502,48 +1502,6 @@
mmc_host_clk_release(host);
}
-static void mmc_poweroff_notify(struct mmc_host *host)
-{
- struct mmc_card *card;
- unsigned int timeout;
- unsigned int notify_type = EXT_CSD_NO_POWER_NOTIFICATION;
- int err = 0;
-
- card = host->card;
- mmc_claim_host(host);
-
- /*
- * Send power notify command only if card
- * is mmc and notify state is powered ON
- */
- if (card && mmc_card_mmc(card) &&
- (card->poweroff_notify_state == MMC_POWERED_ON)) {
-
- if (host->power_notify_type == MMC_HOST_PW_NOTIFY_SHORT) {
- notify_type = EXT_CSD_POWER_OFF_SHORT;
- timeout = card->ext_csd.generic_cmd6_time;
- card->poweroff_notify_state = MMC_POWEROFF_SHORT;
- } else {
- notify_type = EXT_CSD_POWER_OFF_LONG;
- timeout = card->ext_csd.power_off_longtime;
- card->poweroff_notify_state = MMC_POWEROFF_LONG;
- }
-
- err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
- EXT_CSD_POWER_OFF_NOTIFICATION,
- notify_type, timeout);
-
- if (err && err != -EBADMSG)
- pr_err("Device failed to respond within %d poweroff time.",
- timeout);
- pr_err("Forcefully powering down the device\n");
-
- /* Set the card state to no notification after the poweroff */
- card->poweroff_notify_state = MMC_NO_POWER_NOTIFICATION;
- }
- mmc_release_host(host);
-}
-
/*
* Apply power to the MMC stack. This is a two-stage process.
* First, we enable power to the card without the clock running.
@@ -1601,30 +1559,11 @@
void mmc_power_off(struct mmc_host *host)
{
- int err = 0;
mmc_host_clk_hold(host);
host->ios.clock = 0;
host->ios.vdd = 0;
- mmc_poweroff_notify(host);
- /*
- * For eMMC 4.5 device send AWAKE command before
- * POWER_OFF_NOTIFY command, because in sleep state
- * eMMC 4.5 devices respond to only RESET and AWAKE cmd
- */
- if (host->card && mmc_card_is_sleep(host->card) &&
- host->bus_ops->resume) {
- err = host->bus_ops->resume(host);
-
- if (!err)
- mmc_poweroff_notify(host);
- else
- pr_warning("%s: error %d during resume",
- mmc_hostname(host), err);
- pr_warning(" (continue with poweroff sequence)\n");
- }
-
/*
* Reset ocr mask to be the highest possible voltage supported for
* this mmc host. This value will be used at next power up.
@@ -2888,7 +2827,6 @@
break;
}
host->rescan_disable = 1;
- host->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT;
spin_unlock_irqrestore(&host->lock, flags);
if (cancel_delayed_work_sync(&host->detect))
wake_unlock(&host->detect_wake_lock);
@@ -2917,7 +2855,6 @@
break;
}
host->rescan_disable = 0;
- host->power_notify_type = MMC_HOST_PW_NOTIFY_LONG;
spin_unlock_irqrestore(&host->lock, flags);
mmc_detect_change(host, 0);
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 50af7fa..47fd9b9 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -1028,7 +1028,7 @@
* so check for success and update the flag
*/
if (!err)
- card->poweroff_notify_state = MMC_POWERED_ON;
+ card->ext_csd.power_off_notification = EXT_CSD_POWER_ON;
}
/*
@@ -1356,6 +1356,35 @@
return err;
}
+static int mmc_can_poweroff_notify(const struct mmc_card *card)
+{
+ return card &&
+ mmc_card_mmc(card) &&
+ (card->ext_csd.power_off_notification == EXT_CSD_POWER_ON);
+}
+
+static int mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type)
+{
+ unsigned int timeout = card->ext_csd.generic_cmd6_time;
+ int err;
+
+ /* Use EXT_CSD_POWER_OFF_SHORT as default notification type. */
+ if (notify_type == EXT_CSD_POWER_OFF_LONG)
+ timeout = card->ext_csd.power_off_longtime;
+
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_POWER_OFF_NOTIFICATION,
+ notify_type, timeout);
+ if (err)
+ pr_err("%s: Power Off Notification timed out, %u\n",
+ mmc_hostname(card->host), timeout);
+
+ /* Disable the power off notification after the switch operation. */
+ card->ext_csd.power_off_notification = EXT_CSD_NO_POWER_NOTIFICATION;
+
+ return err;
+}
+
/*
* Host is being removed. Free up the current card.
*/
@@ -1419,11 +1448,11 @@
BUG_ON(!host->card);
mmc_claim_host(host);
- if (mmc_card_can_sleep(host)) {
+ if (mmc_can_poweroff_notify(host->card))
+ err = mmc_poweroff_notify(host->card, EXT_CSD_POWER_OFF_SHORT);
+ else if (mmc_card_can_sleep(host))
err = mmc_card_sleep(host);
- if (!err)
- mmc_card_set_sleep(host->card);
- } else if (!mmc_host_is_spi(host))
+ else if (!mmc_host_is_spi(host))
mmc_deselect_cards(host);
host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_200);
mmc_release_host(host);
@@ -1456,7 +1485,6 @@
int ret;
host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_200);
- mmc_card_clr_sleep(host->card);
mmc_claim_host(host);
ret = mmc_init_card(host, host->ocr, host->card);
mmc_release_host(host);
diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
index ab3fc46..bd374f6 100644
--- a/drivers/mmc/host/dw_mmc.c
+++ b/drivers/mmc/host/dw_mmc.c
@@ -1790,11 +1790,6 @@
if (host->pdata->quirks & DW_MCI_QUIRK_HIGHSPEED)
mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED;
- if (mmc->caps2 & MMC_CAP2_POWEROFF_NOTIFY)
- mmc->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT;
- else
- mmc->power_notify_type = MMC_HOST_PW_NOTIFY_NONE;
-
if (host->pdata->blk_settings) {
mmc->max_segs = host->pdata->blk_settings->max_segs;
mmc->max_blk_size = host->pdata->blk_settings->max_blk_size;
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 2f7a2f3..43f7e77 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -2581,15 +2581,6 @@
if (caps[1] & SDHCI_DRIVER_TYPE_D)
mmc->caps |= MMC_CAP_DRIVER_TYPE_D;
- /*
- * If Power Off Notify capability is enabled by the host,
- * set notify to short power off notify timeout value.
- */
- if (mmc->caps2 & MMC_CAP2_POWEROFF_NOTIFY)
- mmc->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT;
- else
- mmc->power_notify_type = MMC_HOST_PW_NOTIFY_NONE;
-
/* Initial value for re-tuning timer count */
host->tuning_count = (caps[1] & SDHCI_RETUNING_TIMER_COUNT_MASK) >>
SDHCI_RETUNING_TIMER_COUNT_SHIFT;