mmc: core: Add Power Off Notify Feature eMMC 4.5
This patch adds support for the power off notify feature, available in
eMMC 4.5 devices. If the host has support for this feature, then the
mmc core will notify the device by setting the POWER_OFF_NOTIFICATION
byte in the extended csd register with a value of 1 (POWER_ON).
For suspend mode short timeout is used, whereas for the normal poweroff
long timeout is used.
Signed-off-by: Girish K S <girish.shivananjappa@linaro.org>
Signed-off-by: Jaehoon Chung <jh80.chung@samsung.com>
Signed-off-by: Chris Ball <cjb@laptop.org>
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 61d7730..a3c4e0f 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -1212,11 +1212,43 @@
void mmc_power_off(struct mmc_host *host)
{
+ struct mmc_card *card;
+ unsigned int notify_type;
+ unsigned int timeout;
+ int err;
+
mmc_host_clk_hold(host);
+ card = host->card;
host->ios.clock = 0;
host->ios.vdd = 0;
+ 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. Forcefully powering down the device\n",
+ timeout);
+
+ /* Set the card state to no notification after the poweroff */
+ card->poweroff_notify_state = MMC_NO_POWER_NOTIFICATION;
+ }
+
/*
* Reset ocr mask to be the highest possible voltage supported for
* this mmc host. This value will be used at next power up.
@@ -2208,6 +2240,7 @@
spin_lock_irqsave(&host->lock, flags);
host->rescan_disable = 1;
+ host->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT;
spin_unlock_irqrestore(&host->lock, flags);
cancel_delayed_work_sync(&host->detect);
@@ -2231,6 +2264,7 @@
spin_lock_irqsave(&host->lock, flags);
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 4e869d3..f8ea938 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -458,10 +458,12 @@
else
card->erased_byte = 0x0;
- if (card->ext_csd.rev >= 6)
+ if (card->ext_csd.rev >= 6) {
card->ext_csd.generic_cmd6_time = 10 *
ext_csd[EXT_CSD_GENERIC_CMD6_TIME];
- else
+ card->ext_csd.power_off_longtime = 10 *
+ ext_csd[EXT_CSD_POWER_OFF_LONG_TIME];
+ } else
card->ext_csd.generic_cmd6_time = 0;
out:
@@ -846,6 +848,23 @@
}
/*
+ * If the host supports the power_off_notify capability then
+ * set the notification byte in the ext_csd register of device
+ */
+ if ((host->caps2 & MMC_CAP2_POWEROFF_NOTIFY) &&
+ (card->poweroff_notify_state == MMC_NO_POWER_NOTIFICATION)) {
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_POWER_OFF_NOTIFICATION,
+ EXT_CSD_POWER_ON,
+ card->ext_csd.generic_cmd6_time);
+ if (err && err != -EBADMSG)
+ goto free_card;
+ }
+
+ if (!err)
+ card->poweroff_notify_state = MMC_POWERED_ON;
+
+ /*
* Activate high speed (if supported)
*/
if ((card->ext_csd.hs_max_dtr != 0) &&
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 2cc3ffa..6d8eea3 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -2739,6 +2739,15 @@
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;