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 <[email protected]>
Signed-off-by: Saugata Das <[email protected]>
Acked-by: Linus Walleij <[email protected]>
Signed-off-by: Chris Ball <[email protected]>
This commit is contained in:
Ulf Hansson 2012-10-05 12:45:39 -04:00 • committed by Chris Ball
commit e6c085863f
6 changed files with 37 additions and 101 deletions

View file

@ -1273,48 +1273,6 @@ void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type)
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. 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;
}
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.
@ -1377,8 +1335,6 @@ static void mmc_power_up(struct mmc_host *host)
void mmc_power_off(struct mmc_host *host)
{
int err = 0;
if (host->ios.power_mode == MMC_POWER_OFF)
return;
@ -1387,22 +1343,6 @@ void mmc_power_off(struct mmc_host *host)
host->ios.clock = 0;
host->ios.vdd = 0;
/*
* 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 "
"(continue with poweroff sequence)\n",
mmc_hostname(host), err);
}
/*
* Reset ocr mask to be the highest possible voltage supported for
@ -2579,7 +2519,6 @@ int mmc_pm_notify(struct notifier_block *notify_block,
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);
@ -2603,7 +2542,6 @@ int mmc_pm_notify(struct notifier_block *notify_block,
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);

View file

@ -1007,7 +1007,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
* 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;
}
/*
@ -1273,6 +1273,35 @@ err:
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.
*/
@ -1333,11 +1362,11 @@ static int mmc_suspend(struct mmc_host *host)
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))
err = mmc_deselect_cards(host);
host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_200);
mmc_release_host(host);
@ -1359,11 +1388,7 @@ static int mmc_resume(struct mmc_host *host)
BUG_ON(!host->card);
mmc_claim_host(host);
if (mmc_card_is_sleep(host->card)) {
err = mmc_card_awake(host);
mmc_card_clr_sleep(host->card);
} else
err = mmc_init_card(host, host->ocr, host->card);
err = mmc_init_card(host, host->ocr, host->card);
mmc_release_host(host);
return err;
@ -1374,7 +1399,6 @@ static int mmc_power_restore(struct mmc_host *host)
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);

View file

@ -1885,11 +1885,6 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id)
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;

View file

@ -2886,15 +2886,6 @@ int sdhci_add_host(struct sdhci_host *host)
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;