diff mbox

[RFC,v2] mmc: sleep notification

Message ID FDD07FEB422EF948A392FDC201AEEAE64FA9EC74@SACMBXIP02.sdcorp.global.sandisk.com (mailing list archive)
State New, archived
Headers show

Commit Message

Avi Shchislowski May 26, 2015, 1:44 p.m. UTC
This patch is implements the new additional state of
Power_Off_Notification - SLEEP_NOTIFICATION.
Until now, the implementation of Power_Off_Notification
supported only three modes - POWERED_ON (0x01),
POWER_OFF_SHORT (0x02) and POWER_OFF_LONG (0x03).

As part of eMMC5.0 before moving to Sleep state hosts may set the
POWER_OFF_NOTIFICATION byte to SLEEP_NOTIFICATION (0x04).
After setting SLEEP_NOTIFICATION, host should wait for
the busy line to be de-asserted.
The max timeout allowed for busy line de-assertion defined
in SLEEP_NOTIFICATION_TIME byte in EXT_CSD [216].
HPI may interrupt the SLEEP_NOTIFICATION operation.
In that case POWER_OFF_NOTIFICATION byte will restore to POWERED_ON.

Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>
Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
---------
v2:
- remove calling mmc_interrupt_hpi in case the device is doing
  sleep notification from block.c
- remove call of "mmc_card_doing_sleep_notify" from core.c
- mmc_sleep_notify changed to static function

Comments

Ulf Hansson May 26, 2015, 2:22 p.m. UTC | #1
On 26 May 2015 at 15:44, Avi Shchislowski <Avi.Shchislowski@sandisk.com> wrote:
> This patch is implements the new additional state of
> Power_Off_Notification - SLEEP_NOTIFICATION.
> Until now, the implementation of Power_Off_Notification
> supported only three modes - POWERED_ON (0x01),
> POWER_OFF_SHORT (0x02) and POWER_OFF_LONG (0x03).
>
> As part of eMMC5.0 before moving to Sleep state hosts may set the
> POWER_OFF_NOTIFICATION byte to SLEEP_NOTIFICATION (0x04).
> After setting SLEEP_NOTIFICATION, host should wait for
> the busy line to be de-asserted.
> The max timeout allowed for busy line de-assertion defined
> in SLEEP_NOTIFICATION_TIME byte in EXT_CSD [216].
> HPI may interrupt the SLEEP_NOTIFICATION operation.
> In that case POWER_OFF_NOTIFICATION byte will restore to POWERED_ON.
>
> Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>
> Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>

This is a great improvement according to earlier version.

Still you have some things to do before I am satisfied...

> ---------
> v2:
> - remove calling mmc_interrupt_hpi in case the device is doing
>   sleep notification from block.c
> - remove call of "mmc_card_doing_sleep_notify" from core.c
> - mmc_sleep_notify changed to static function
>
> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
> index a802863..2bf2b2c 100644
> --- a/drivers/mmc/core/mmc.c
> +++ b/drivers/mmc/core/mmc.c
> @@ -59,6 +59,12 @@ static const unsigned int tacc_mant[] = {
>                 __res & __mask;                                         \
>         })
>
> +#define MMC_SLEEP_NOTIFY_MAX_TIME      0x17

This doesn't tell me much. Could we have the value in ms, or whatever
that makes sense?

> +#define GET_SLEEP_NOTIFY_TIME(value) \
> +       (10 * (1 << (unsigned int)(value)))
> +#define GET_SLEEP_NOTIFY_TIME_MSEC(value) \
> +       (DIV_ROUND_UP(GET_SLEEP_NOTIFY_TIME(value), 1000))

Please remove these two macros. Do the calculations where needed instead.

> +
>  /*
>   * Given the decoded CSD structure, decode the raw CID to our CID structure.
>   */
> @@ -582,6 +588,8 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd)
>                 card->ext_csd.ffu_capable =
>                         (ext_csd[EXT_CSD_SUPPORTED_MODE] & 0x1) &&
>                         !(ext_csd[EXT_CSD_FW_CONFIG] & 0x1);
> +               card->ext_csd.sleep_notify_time =
> +                       ext_csd[EXT_CSD_SLEEP_NOTIFICATION_TIME];
>         }
>  out:
>         return err;
> @@ -1529,6 +1537,15 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
>                         card->ext_csd.hpi_en = 1;
>         }
>
> +       /* sleep notify enable/disable for eMMC 5.0 and above */
> +       if ((card->ext_csd.rev >= 7) &&  card->ext_csd.hpi_en &&

Why depend on hpi_en here? Is that according to the specification?

> +                       (card->host->caps2 & MMC_CAP2_SLEEP_NOTIFY) &&

We don't need a new MMC_CAP2 for this, please remove it.

> +                       card->ext_csd.sleep_notify_time > 0 &&
> +                       card->ext_csd.sleep_notify_time <=
> +                               MMC_SLEEP_NOTIFY_MAX_TIME) {
> +               card->can_sleep_notify = 1;

Finally, I don't think the above calculations and if statements is
that much of an issue to do at each suspend. So please move this
entire thing into a helper function instead and thus remove the new
member "can_sleep_notify".

Similar as mmc_can_poweroff_notify().

> +       }
> +
>         /*
>          * If cache size is higher than 0, this indicates
>          * the existence of cache and it can be turned on.
> @@ -1642,6 +1659,33 @@ out_release:
>         return err;
>  }
>
> +/*
> + * check if device is in program state (busy)
> + */
> +static bool mmc_device_prg_state(struct mmc_card *card)
> +{
> +       int rc;
> +       u32 status;
> +       bool state;
> +
> +       mmc_get_card(card);
> +       rc = mmc_send_status(card, &status);
> +       if (rc) {
> +               pr_err("%s: Get card status fail. rc=%d\n",
> +                       mmc_hostname(card->host), rc);
> +               state = false;
> +               goto out;
> +       }
> +
> +       if (R1_CURRENT_STATE(status) == R1_STATE_PRG)
> +               state =  true;
> +       else
> +               state =  false;
> +out:
> +       mmc_put_card(card);
> +       return state;
> +}
> +
>  static int mmc_can_poweroff_notify(const struct mmc_card *card)
>  {
>         return card &&
> @@ -1653,20 +1697,106 @@ static int mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type)
>  {
>         unsigned int timeout = card->ext_csd.generic_cmd6_time;
>         int err;
> +       bool use_busy_signal;
>
>         /* 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;
> +       else if (notify_type == EXT_CSD_SLEEP_NOTIFICATION) {
> +               /* calculate the maximum timeout for the
> +                * switch command when notifying the device
> +                * that it is about to move to sleep */
> +               timeout = GET_SLEEP_NOTIFY_TIME_MSEC(
> +                       card->ext_csd.sleep_notify_time);
> +       }
>
> +       /* do not wait on busy signal in case of
> +        * Sleep Notification - to let host get
> +        * another requests
> +        */

There will be no other requests executed in this path, so the upper
comment just doesn't make sense.

I guess you are trying to prepare for using HPI to interrupt sleep
notification from runtime PM resume? If that is the case, please don't
make that code a part of this patch. Instead add that functionality on
top of this patch.

> +       use_busy_signal = (notify_type == EXT_CSD_SLEEP_NOTIFICATION) ?
> +                       false : true;
>         err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>                         EXT_CSD_POWER_OFF_NOTIFICATION,
> -                       notify_type, timeout, true, false, false);
> -       if (err)
> +                       notify_type, timeout,
> +                       use_busy_signal, false, false);
> +       if (!err) {
> +               card->ext_csd.power_off_notification = notify_type;
> +       } else {
>                 pr_err("%s: Power Off Notification timed out, %u\n",
> -                      mmc_hostname(card->host), timeout);
> +                       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;
> +}
> +
> +static int mmc_sleep_notify(struct mmc_card *card)
> +{
> +       int err = 0;
> +       bool is_busy = 0;
> +       unsigned long prg_wait = 0;
> +
> +       if (!card->can_sleep_notify || !mmc_can_poweroff_notify(card))
> +               return 0;
> +
> +       if (!mmc_card_doing_sleep_notify(card)) {
> +               mmc_get_card(card);
> +               mmc_card_set_sleep_notify(card);
> +               err = mmc_poweroff_notify(card,
> +                       EXT_CSD_SLEEP_NOTIFICATION);
> +               mmc_put_card(card);
> +               if (err) {
> +                       pr_err("%s: mmc_poweroff_notify failed with %d\n",
> +                              __func__, err);
> +                       goto out;
> +               }
> +
> +               prg_wait = jiffies +
> +                       msecs_to_jiffies(GET_SLEEP_NOTIFY_TIME_MSEC(
> +                       card->ext_csd.sleep_notify_time));
> +               }
> +
> +       /*
> +        * Loop will run until:
> +        * 1. Device is no more in Busy state
> +        * 2. Sleep notification is not interrupted by HPI & IO request
> +        */

As stated earlier. I don't want this patch to consider the above
options at this point. Please remove all related code from this patch.

> +       do {
> +               /* added some delay to avoid sending card status too often */
> +               msleep(20);
> +               err = 0;
> +               /* Stop polling in case sleep notification was HPIed already */
> +               if (!mmc_card_doing_sleep_notify(card)) {
> +                       is_busy = mmc_device_prg_state(card);
> +                       if (is_busy)
> +                               err = -EBUSY;
> +                       break;
> +               }
> +               is_busy = mmc_device_prg_state(card);
> +               if (is_busy && time_after(jiffies, prg_wait)) {
> +                       /*
> +                        * making sure we are still in busy before
> +                        * sending HPI due to timeout error
> +                        */
> +                       is_busy = mmc_device_prg_state(card);
> +                       if (is_busy) {
> +                               if (mmc_card_doing_sleep_notify(card)) {
> +                                       card->ext_csd.power_off_notification =
> +                                               EXT_CSD_POWER_ON;
> +                                       mmc_interrupt_hpi(card);
> +                               }
> +                               err = -ETIMEDOUT;
> +                               break;
> +                       }
> +               }
> +       } while (is_busy);
> +
> +out:
> +       mmc_card_clr_sleep_notify(card);
> +       if (err) {
> +               pr_err("%s: mmc_poweroff_notify for sleep failed with %d\n",
> +                      mmc_hostname(card->host), err);
> +       }
>
>         return err;
>  }
> @@ -1745,8 +1875,16 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend)
>                 goto out;
>
>         if (mmc_can_poweroff_notify(host->card) &&
> -               ((host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) || !is_suspend))
> -               err = mmc_poweroff_notify(host->card, notify_type);
> +               ((host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) || !is_suspend)) {
> +               if (!host->card->can_sleep_notify ||
> +                       !mmc_can_poweroff_notify(host->card)) {

This looks wrong.

The outer "if" already checked "if (mmc_can_poweroff_notify(host->card)".

> +                       err = mmc_poweroff_notify(host->card, notify_type);
> +               } else {
> +                       err = mmc_sleep_notify(host->card);
> +                       if (err != -ETIMEDOUT)
> +                               err = mmc_sleep(host);
> +               }
> +       }
>         else if (mmc_can_sleep(host->card))
>                 err = mmc_sleep(host);
>         else if (!mmc_host_is_spi(host))
> diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
> index 19f0175..ed91e6f 100644
> --- a/include/linux/mmc/card.h
> +++ b/include/linux/mmc/card.h
> @@ -62,6 +62,7 @@ struct mmc_ext_csd {
>         unsigned int            sa_timeout;             /* Units: 100ns */
>         unsigned int            generic_cmd6_time;      /* Units: 10ms */
>         unsigned int            power_off_longtime;     /* Units: ms */
> +       unsigned int            sleep_notify_time;      /* Units: us */
>         u8                      power_off_notification; /* state */
>         unsigned int            hs_max_dtr;
>         unsigned int            hs200_max_dtr;
> @@ -262,6 +263,7 @@ struct mmc_card {
>  #define MMC_CARD_REMOVED       (1<<4)          /* card has been removed */
>  #define MMC_STATE_DOING_BKOPS  (1<<5)          /* card is doing BKOPS */
>  #define MMC_STATE_SUSPENDED    (1<<6)          /* card is suspended */
> +#define MMC_STATE_SLEEP_NOTIFY (1<<7)          /* card in sleep notify */
>         unsigned int            quirks;         /* card quirks */
>  #define MMC_QUIRK_LENIENT_FN0  (1<<0)          /* allow SDIO FN0 writes outside of the VS CCCR range */
>  #define MMC_QUIRK_BLKSZ_FOR_BYTE_MODE (1<<1)   /* use func->cur_blksize */
> @@ -309,6 +311,7 @@ struct mmc_card {
>         struct dentry           *debugfs_root;
>         struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical partitions */
>         unsigned int    nr_parts;
> +       u8 can_sleep_notify;    /* sleep_notify on/off */
>  };
>
>  /*
> @@ -427,6 +430,7 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data)
>  #define mmc_card_removed(c)    ((c) && ((c)->state & MMC_CARD_REMOVED))
>  #define mmc_card_doing_bkops(c)        ((c)->state & MMC_STATE_DOING_BKOPS)
>  #define mmc_card_suspended(c)  ((c)->state & MMC_STATE_SUSPENDED)
> +#define mmc_card_doing_sleep_notify(c) ((c)->state & MMC_STATE_SLEEP_NOTIFY)
>
>  #define mmc_card_set_present(c)        ((c)->state |= MMC_STATE_PRESENT)
>  #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY)
> @@ -437,6 +441,8 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data)
>  #define mmc_card_clr_doing_bkops(c)    ((c)->state &= ~MMC_STATE_DOING_BKOPS)
>  #define mmc_card_set_suspended(c) ((c)->state |= MMC_STATE_SUSPENDED)
>  #define mmc_card_clr_suspended(c) ((c)->state &= ~MMC_STATE_SUSPENDED)
> +#define mmc_card_set_sleep_notify(c)   ((c)->state |= MMC_STATE_SLEEP_NOTIFY)
> +#define mmc_card_clr_sleep_notify(c)   ((c)->state &= ~MMC_STATE_SLEEP_NOTIFY)
>
>  /*
>   * Quirk add/remove for MMC products.
> diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
> index f471193..111e05d 100644
> --- a/include/linux/mmc/host.h
> +++ b/include/linux/mmc/host.h
> @@ -286,6 +286,7 @@ struct mmc_host {
>                                  MMC_CAP2_HS400_1_2V)
>  #define MMC_CAP2_HSX00_1_2V    (MMC_CAP2_HS200_1_2V_SDR | MMC_CAP2_HS400_1_2V)
>  #define MMC_CAP2_SDIO_IRQ_NOTHREAD (1 << 17)
> +#define MMC_CAP2_SLEEP_NOTIFY  (1 << 18)       /* sleep notify supported */
>
>         mmc_pm_flag_t           pm_caps;        /* supported pm features */
>
> diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h
> index 124f562..bbb71ae 100644
> --- a/include/linux/mmc/mmc.h
> +++ b/include/linux/mmc/mmc.h
> @@ -309,6 +309,7 @@ struct _mmc_csd {
>  #define EXT_CSD_PWR_CL_52_360          202     /* RO */
>  #define EXT_CSD_PWR_CL_26_360          203     /* RO */
>  #define EXT_CSD_SEC_CNT                        212     /* RO, 4 bytes */
> +#define EXT_CSD_SLEEP_NOTIFICATION_TIME        216     /* RO */
>  #define EXT_CSD_S_A_TIMEOUT            217     /* RO */
>  #define EXT_CSD_REL_WR_SEC_C           222     /* RO */
>  #define EXT_CSD_HC_WP_GRP_SIZE         221     /* RO */
> @@ -403,6 +404,7 @@ struct _mmc_csd {
>  #define EXT_CSD_POWER_ON               1
>  #define EXT_CSD_POWER_OFF_SHORT                2
>  #define EXT_CSD_POWER_OFF_LONG         3
> +#define EXT_CSD_SLEEP_NOTIFICATION     4
>
>  #define EXT_CSD_PWR_CL_8BIT_MASK       0xF0    /* 8 bit PWR CLS */
>  #define EXT_CSD_PWR_CL_4BIT_MASK       0x0F    /* 8 bit PWR CLS */
> --
> 1.7.9.5
>

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
Alex Lemberg May 26, 2015, 2:33 p.m. UTC | #2
Hi Ulf,

In this version we are calling sleep_notify from suspend(), per your recommendation.
No HPI is needed in this case.
(The commit message of v2 should be changed)

Thanks,
Alex

> -----Original Message-----
> From: Avi Shchislowski
> Sent: Tuesday, May 26, 2015 4:44 PM
> To: Ulf Hansson
> Cc: Alex Lemberg; linux-mmc
> Subject: [RFC PATCH v2] mmc: sleep notification
> 
> This patch is implements the new additional state of Power_Off_Notification -
> SLEEP_NOTIFICATION.
> Until now, the implementation of Power_Off_Notification supported only three
> modes - POWERED_ON (0x01), POWER_OFF_SHORT (0x02) and
> POWER_OFF_LONG (0x03).
> 
> As part of eMMC5.0 before moving to Sleep state hosts may set the
> POWER_OFF_NOTIFICATION byte to SLEEP_NOTIFICATION (0x04).
> After setting SLEEP_NOTIFICATION, host should wait for the busy line to be de-
> asserted.
> The max timeout allowed for busy line de-assertion defined in
> SLEEP_NOTIFICATION_TIME byte in EXT_CSD [216].
> HPI may interrupt the SLEEP_NOTIFICATION operation.
> In that case POWER_OFF_NOTIFICATION byte will restore to POWERED_ON.
> 
> Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>
> Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
> ---------
> v2:
> - remove calling mmc_interrupt_hpi in case the device is doing
>   sleep notification from block.c
> - remove call of "mmc_card_doing_sleep_notify" from core.c
> - mmc_sleep_notify changed to static function
> 
> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index
> a802863..2bf2b2c 100644
> --- a/drivers/mmc/core/mmc.c
> +++ b/drivers/mmc/core/mmc.c
> @@ -59,6 +59,12 @@ static const unsigned int tacc_mant[] = {
>  		__res & __mask;
> 	\
>  	})
> 
> +#define MMC_SLEEP_NOTIFY_MAX_TIME	0x17
> +#define GET_SLEEP_NOTIFY_TIME(value) \
> +	(10 * (1 << (unsigned int)(value)))
> +#define GET_SLEEP_NOTIFY_TIME_MSEC(value) \
> +	(DIV_ROUND_UP(GET_SLEEP_NOTIFY_TIME(value), 1000))
> +
>  /*
>   * Given the decoded CSD structure, decode the raw CID to our CID structure.
>   */
> @@ -582,6 +588,8 @@ static int mmc_decode_ext_csd(struct mmc_card
> *card, u8 *ext_csd)
>  		card->ext_csd.ffu_capable =
>  			(ext_csd[EXT_CSD_SUPPORTED_MODE] & 0x1) &&
>  			!(ext_csd[EXT_CSD_FW_CONFIG] & 0x1);
> +		card->ext_csd.sleep_notify_time =
> +			ext_csd[EXT_CSD_SLEEP_NOTIFICATION_TIME];
>  	}
>  out:
>  	return err;
> @@ -1529,6 +1537,15 @@ static int mmc_init_card(struct mmc_host *host,
> u32 ocr,
>  			card->ext_csd.hpi_en = 1;
>  	}
> 
> +	/* sleep notify enable/disable for eMMC 5.0 and above */
> +	if ((card->ext_csd.rev >= 7) &&  card->ext_csd.hpi_en &&
> +			(card->host->caps2 & MMC_CAP2_SLEEP_NOTIFY) &&
> +			card->ext_csd.sleep_notify_time > 0 &&
> +			card->ext_csd.sleep_notify_time <=
> +				MMC_SLEEP_NOTIFY_MAX_TIME) {
> +		card->can_sleep_notify = 1;
> +	}
> +
>  	/*
>  	 * If cache size is higher than 0, this indicates
>  	 * the existence of cache and it can be turned on.
> @@ -1642,6 +1659,33 @@ out_release:
>  	return err;
>  }
> 
> +/*
> + * check if device is in program state (busy)  */ static bool
> +mmc_device_prg_state(struct mmc_card *card) {
> +	int rc;
> +	u32 status;
> +	bool state;
> +
> +	mmc_get_card(card);
> +	rc = mmc_send_status(card, &status);
> +	if (rc) {
> +		pr_err("%s: Get card status fail. rc=%d\n",
> +			mmc_hostname(card->host), rc);
> +		state = false;
> +		goto out;
> +	}
> +
> +	if (R1_CURRENT_STATE(status) == R1_STATE_PRG)
> +		state =  true;
> +	else
> +		state =  false;
> +out:
> +	mmc_put_card(card);
> +	return state;
> +}
> +
>  static int mmc_can_poweroff_notify(const struct mmc_card *card)  {
>  	return card &&
> @@ -1653,20 +1697,106 @@ static int mmc_poweroff_notify(struct mmc_card
> *card, unsigned int notify_type)  {
>  	unsigned int timeout = card->ext_csd.generic_cmd6_time;
>  	int err;
> +	bool use_busy_signal;
> 
>  	/* 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;
> +	else if (notify_type == EXT_CSD_SLEEP_NOTIFICATION) {
> +		/* calculate the maximum timeout for the
> +		 * switch command when notifying the device
> +		 * that it is about to move to sleep */
> +		timeout = GET_SLEEP_NOTIFY_TIME_MSEC(
> +			card->ext_csd.sleep_notify_time);
> +	}
> 
> +	/* do not wait on busy signal in case of
> +	 * Sleep Notification - to let host get
> +	 * another requests
> +	 */
> +	use_busy_signal = (notify_type == EXT_CSD_SLEEP_NOTIFICATION) ?
> +			false : true;
>  	err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>  			EXT_CSD_POWER_OFF_NOTIFICATION,
> -			notify_type, timeout, true, false, false);
> -	if (err)
> +			notify_type, timeout,
> +			use_busy_signal, false, false);
> +	if (!err) {
> +		card->ext_csd.power_off_notification = notify_type;
> +	} else {
>  		pr_err("%s: Power Off Notification timed out, %u\n",
> -		       mmc_hostname(card->host), timeout);
> +			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;
> +}
> +
> +static int mmc_sleep_notify(struct mmc_card *card) {
> +	int err = 0;
> +	bool is_busy = 0;
> +	unsigned long prg_wait = 0;
> +
> +	if (!card->can_sleep_notify || !mmc_can_poweroff_notify(card))
> +		return 0;
> +
> +	if (!mmc_card_doing_sleep_notify(card)) {
> +		mmc_get_card(card);
> +		mmc_card_set_sleep_notify(card);
> +		err = mmc_poweroff_notify(card,
> +			EXT_CSD_SLEEP_NOTIFICATION);
> +		mmc_put_card(card);
> +		if (err) {
> +			pr_err("%s: mmc_poweroff_notify failed with %d\n",
> +			       __func__, err);
> +			goto out;
> +		}
> +
> +		prg_wait = jiffies +
> +			msecs_to_jiffies(GET_SLEEP_NOTIFY_TIME_MSEC(
> +			card->ext_csd.sleep_notify_time));
> +		}
> +
> +	/*
> +	 * Loop will run until:
> +	 * 1. Device is no more in Busy state
> +	 * 2. Sleep notification is not interrupted by HPI & IO request
> +	 */
> +	do {
> +		/* added some delay to avoid sending card status too often */
> +		msleep(20);
> +		err = 0;
> +		/* Stop polling in case sleep notification was HPIed already */
> +		if (!mmc_card_doing_sleep_notify(card)) {
> +			is_busy = mmc_device_prg_state(card);
> +			if (is_busy)
> +				err = -EBUSY;
> +			break;
> +		}
> +		is_busy = mmc_device_prg_state(card);
> +		if (is_busy && time_after(jiffies, prg_wait)) {
> +			/*
> +			 * making sure we are still in busy before
> +			 * sending HPI due to timeout error
> +			 */
> +			is_busy = mmc_device_prg_state(card);
> +			if (is_busy) {
> +				if (mmc_card_doing_sleep_notify(card)) {
> +					card->ext_csd.power_off_notification
> =
> +						EXT_CSD_POWER_ON;
> +					mmc_interrupt_hpi(card);
> +				}
> +				err = -ETIMEDOUT;
> +				break;
> +			}
> +		}
> +	} while (is_busy);
> +
> +out:
> +	mmc_card_clr_sleep_notify(card);
> +	if (err) {
> +		pr_err("%s: mmc_poweroff_notify for sleep failed with %d\n",
> +		       mmc_hostname(card->host), err);
> +	}
> 
>  	return err;
>  }
> @@ -1745,8 +1875,16 @@ static int _mmc_suspend(struct mmc_host *host,
> bool is_suspend)
>  		goto out;
> 
>  	if (mmc_can_poweroff_notify(host->card) &&
> -		((host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) ||
> !is_suspend))
> -		err = mmc_poweroff_notify(host->card, notify_type);
> +		((host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) ||
> !is_suspend)) {
> +		if (!host->card->can_sleep_notify ||
> +			!mmc_can_poweroff_notify(host->card)) {
> +			err = mmc_poweroff_notify(host->card, notify_type);
> +		} else {
> +			err = mmc_sleep_notify(host->card);
> +			if (err != -ETIMEDOUT)
> +				err = mmc_sleep(host);
> +		}
> +	}
>  	else if (mmc_can_sleep(host->card))
>  		err = mmc_sleep(host);
>  	else if (!mmc_host_is_spi(host))
> diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index
> 19f0175..ed91e6f 100644
> --- a/include/linux/mmc/card.h
> +++ b/include/linux/mmc/card.h
> @@ -62,6 +62,7 @@ struct mmc_ext_csd {
>  	unsigned int		sa_timeout;		/* Units: 100ns */
>  	unsigned int		generic_cmd6_time;	/* Units: 10ms */
>  	unsigned int            power_off_longtime;     /* Units: ms */
> +	unsigned int		sleep_notify_time;	/* Units: us */
>  	u8			power_off_notification;	/* state */
>  	unsigned int		hs_max_dtr;
>  	unsigned int		hs200_max_dtr;
> @@ -262,6 +263,7 @@ struct mmc_card {
>  #define MMC_CARD_REMOVED	(1<<4)		/* card has been
> removed */
>  #define MMC_STATE_DOING_BKOPS	(1<<5)		/* card is doing BKOPS
> */
>  #define MMC_STATE_SUSPENDED	(1<<6)		/* card is suspended
> */
> +#define MMC_STATE_SLEEP_NOTIFY (1<<7)		/* card in sleep notify
> */
>  	unsigned int		quirks; 	/* card quirks */
>  #define MMC_QUIRK_LENIENT_FN0	(1<<0)		/* allow SDIO FN0
> writes outside of the VS CCCR range */
>  #define MMC_QUIRK_BLKSZ_FOR_BYTE_MODE (1<<1)	/* use func-
> >cur_blksize */
> @@ -309,6 +311,7 @@ struct mmc_card {
>  	struct dentry		*debugfs_root;
>  	struct mmc_part	part[MMC_NUM_PHY_PARTITION]; /* physical
> partitions */
>  	unsigned int    nr_parts;
> +	u8 can_sleep_notify;	/* sleep_notify on/off */
>  };
> 
>  /*
> @@ -427,6 +430,7 @@ static inline void __maybe_unused remove_quirk(struct
> mmc_card *card, int data)
>  #define mmc_card_removed(c)	((c) && ((c)->state & MMC_CARD_REMOVED))
>  #define mmc_card_doing_bkops(c)	((c)->state &
> MMC_STATE_DOING_BKOPS)
>  #define mmc_card_suspended(c)	((c)->state &
> MMC_STATE_SUSPENDED)
> +#define mmc_card_doing_sleep_notify(c) ((c)->state &
> +MMC_STATE_SLEEP_NOTIFY)
> 
>  #define mmc_card_set_present(c)	((c)->state |= MMC_STATE_PRESENT)
>  #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY)
> @@ -437,6 +441,8 @@ static inline void __maybe_unused remove_quirk(struct
> mmc_card *card, int data)
>  #define mmc_card_clr_doing_bkops(c)	((c)->state &=
> ~MMC_STATE_DOING_BKOPS)
>  #define mmc_card_set_suspended(c) ((c)->state |= MMC_STATE_SUSPENDED)
> #define mmc_card_clr_suspended(c) ((c)->state &=
> ~MMC_STATE_SUSPENDED)
> +#define mmc_card_set_sleep_notify(c)	((c)->state |=
> MMC_STATE_SLEEP_NOTIFY)
> +#define mmc_card_clr_sleep_notify(c)	((c)->state &=
> ~MMC_STATE_SLEEP_NOTIFY)
> 
>  /*
>   * Quirk add/remove for MMC products.
> diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index
> f471193..111e05d 100644
> --- a/include/linux/mmc/host.h
> +++ b/include/linux/mmc/host.h
> @@ -286,6 +286,7 @@ struct mmc_host {
>  				 MMC_CAP2_HS400_1_2V)
>  #define MMC_CAP2_HSX00_1_2V	(MMC_CAP2_HS200_1_2V_SDR |
> MMC_CAP2_HS400_1_2V)
>  #define MMC_CAP2_SDIO_IRQ_NOTHREAD (1 << 17)
> +#define MMC_CAP2_SLEEP_NOTIFY	(1 << 18)	/* sleep notify
> supported */
> 
>  	mmc_pm_flag_t		pm_caps;	/* supported pm
> features */
> 
> diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index
> 124f562..bbb71ae 100644
> --- a/include/linux/mmc/mmc.h
> +++ b/include/linux/mmc/mmc.h
> @@ -309,6 +309,7 @@ struct _mmc_csd {
>  #define EXT_CSD_PWR_CL_52_360		202	/* RO */
>  #define EXT_CSD_PWR_CL_26_360		203	/* RO */
>  #define EXT_CSD_SEC_CNT			212	/* RO, 4 bytes */
> +#define EXT_CSD_SLEEP_NOTIFICATION_TIME	216	/* RO */
>  #define EXT_CSD_S_A_TIMEOUT		217	/* RO */
>  #define EXT_CSD_REL_WR_SEC_C		222	/* RO */
>  #define EXT_CSD_HC_WP_GRP_SIZE		221	/* RO */
> @@ -403,6 +404,7 @@ struct _mmc_csd {
>  #define EXT_CSD_POWER_ON		1
>  #define EXT_CSD_POWER_OFF_SHORT		2
>  #define EXT_CSD_POWER_OFF_LONG		3
> +#define EXT_CSD_SLEEP_NOTIFICATION	4
> 
>  #define EXT_CSD_PWR_CL_8BIT_MASK	0xF0	/* 8 bit PWR CLS */
>  #define EXT_CSD_PWR_CL_4BIT_MASK	0x0F	/* 8 bit PWR CLS */
> --
> 1.7.9.5

--
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
Avi Shchislowski May 27, 2015, 8:44 a.m. UTC | #3
> -----Original Message-----

> From: Ulf Hansson [mailto:ulf.hansson@linaro.org]

> Sent: Tuesday, May 26, 2015 5:22 PM

> To: Avi Shchislowski

> Cc: Alex Lemberg; linux-mmc

> Subject: Re: [RFC PATCH v2] mmc: sleep notification

> 

> On 26 May 2015 at 15:44, Avi Shchislowski <Avi.Shchislowski@sandisk.com>

> wrote:

> > This patch is implements the new additional state of

> > Power_Off_Notification - SLEEP_NOTIFICATION.

> > Until now, the implementation of Power_Off_Notification supported only

> > three modes - POWERED_ON (0x01), POWER_OFF_SHORT (0x02) and

> > POWER_OFF_LONG (0x03).

> >

> > As part of eMMC5.0 before moving to Sleep state hosts may set the

> > POWER_OFF_NOTIFICATION byte to SLEEP_NOTIFICATION (0x04).

> > After setting SLEEP_NOTIFICATION, host should wait for the busy line

> > to be de-asserted.

> > The max timeout allowed for busy line de-assertion defined in

> > SLEEP_NOTIFICATION_TIME byte in EXT_CSD [216].

> > HPI may interrupt the SLEEP_NOTIFICATION operation.

> > In that case POWER_OFF_NOTIFICATION byte will restore to POWERED_ON.

> >

> > Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>

> > Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>

> 

> This is a great improvement according to earlier version.

> 

> Still you have some things to do before I am satisfied...

> 

> > ---------

> > v2:

> > - remove calling mmc_interrupt_hpi in case the device is doing

> >   sleep notification from block.c

> > - remove call of "mmc_card_doing_sleep_notify" from core.c

> > - mmc_sleep_notify changed to static function

> >

> > diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index

> > a802863..2bf2b2c 100644

> > --- a/drivers/mmc/core/mmc.c

> > +++ b/drivers/mmc/core/mmc.c

> > @@ -59,6 +59,12 @@ static const unsigned int tacc_mant[] = {

> >                 __res & __mask;                                         \

> >         })

> >

> > +#define MMC_SLEEP_NOTIFY_MAX_TIME      0x17

> 

> This doesn't tell me much. Could we have the value in ms, or whatever that

> makes sense?

>

It is a value taken from the spec, I will add a commit to describe the value
 
> > +#define GET_SLEEP_NOTIFY_TIME(value) \

> > +       (10 * (1 << (unsigned int)(value))) #define

> > +GET_SLEEP_NOTIFY_TIME_MSEC(value) \

> > +       (DIV_ROUND_UP(GET_SLEEP_NOTIFY_TIME(value), 1000))

> 

> Please remove these two macros. Do the calculations where needed instead.

>

Will do
> > +

> >  /*

> >   * Given the decoded CSD structure, decode the raw CID to our CID structure.

> >   */

> > @@ -582,6 +588,8 @@ static int mmc_decode_ext_csd(struct mmc_card

> *card, u8 *ext_csd)

> >                 card->ext_csd.ffu_capable =

> >                         (ext_csd[EXT_CSD_SUPPORTED_MODE] & 0x1) &&

> >                         !(ext_csd[EXT_CSD_FW_CONFIG] & 0x1);

> > +               card->ext_csd.sleep_notify_time =

> > +                       ext_csd[EXT_CSD_SLEEP_NOTIFICATION_TIME];

> >         }

> >  out:

> >         return err;

> > @@ -1529,6 +1537,15 @@ static int mmc_init_card(struct mmc_host *host,

> u32 ocr,

> >                         card->ext_csd.hpi_en = 1;

> >         }

> >

> > +       /* sleep notify enable/disable for eMMC 5.0 and above */

> > +       if ((card->ext_csd.rev >= 7) &&  card->ext_csd.hpi_en &&

> 

> Why depend on hpi_en here? Is that according to the specification?

> 

> > +                       (card->host->caps2 & MMC_CAP2_SLEEP_NOTIFY) &&

> 

> We don't need a new MMC_CAP2 for this, please remove it.

>

We prefer leaving it as is and give the vendors the ability to control if
The host will support sleep notification
 
> > +                       card->ext_csd.sleep_notify_time > 0 &&

> > +                       card->ext_csd.sleep_notify_time <=

> > +                               MMC_SLEEP_NOTIFY_MAX_TIME) {

> > +               card->can_sleep_notify = 1;

> 

> Finally, I don't think the above calculations and if statements is that much of an

> issue to do at each suspend. So please move this entire thing into a helper

> function instead and thus remove the new member "can_sleep_notify".

> 

> Similar as mmc_can_poweroff_notify().

> 

Will do
> > +       }

> > +

> >         /*

> >          * If cache size is higher than 0, this indicates

> >          * the existence of cache and it can be turned on.

> > @@ -1642,6 +1659,33 @@ out_release:

> >         return err;

> >  }

> >

> > +/*

> > + * check if device is in program state (busy)  */ static bool

> > +mmc_device_prg_state(struct mmc_card *card) {

> > +       int rc;

> > +       u32 status;

> > +       bool state;

> > +

> > +       mmc_get_card(card);

> > +       rc = mmc_send_status(card, &status);

> > +       if (rc) {

> > +               pr_err("%s: Get card status fail. rc=%d\n",

> > +                       mmc_hostname(card->host), rc);

> > +               state = false;

> > +               goto out;

> > +       }

> > +

> > +       if (R1_CURRENT_STATE(status) == R1_STATE_PRG)

> > +               state =  true;

> > +       else

> > +               state =  false;

> > +out:

> > +       mmc_put_card(card);

> > +       return state;

> > +}

> > +

> >  static int mmc_can_poweroff_notify(const struct mmc_card *card)  {

> >         return card &&

> > @@ -1653,20 +1697,106 @@ static int mmc_poweroff_notify(struct

> > mmc_card *card, unsigned int notify_type)  {

> >         unsigned int timeout = card->ext_csd.generic_cmd6_time;

> >         int err;

> > +       bool use_busy_signal;

> >

> >         /* 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;

> > +       else if (notify_type == EXT_CSD_SLEEP_NOTIFICATION) {

> > +               /* calculate the maximum timeout for the

> > +                * switch command when notifying the device

> > +                * that it is about to move to sleep */

> > +               timeout = GET_SLEEP_NOTIFY_TIME_MSEC(

> > +                       card->ext_csd.sleep_notify_time);

> > +       }

> >

> > +       /* do not wait on busy signal in case of

> > +        * Sleep Notification - to let host get

> > +        * another requests

> > +        */

> 

> There will be no other requests executed in this path, so the upper comment

> just doesn't make sense.

You are right I will remove 
> 

> I guess you are trying to prepare for using HPI to interrupt sleep notification

> from runtime PM resume? If that is the case, please don't make that code a

> part of this patch. Instead add that functionality on top of this patch.

> 

We prefer sending this as non-blocking switch command in case of sleep_notification
> > +       use_busy_signal = (notify_type == EXT_CSD_SLEEP_NOTIFICATION) ?

> > +                       false : true;

> >         err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,

> >                         EXT_CSD_POWER_OFF_NOTIFICATION,

> > -                       notify_type, timeout, true, false, false);

> > -       if (err)

> > +                       notify_type, timeout,

> > +                       use_busy_signal, false, false);

> > +       if (!err) {

> > +               card->ext_csd.power_off_notification = notify_type;

> > +       } else {

> >                 pr_err("%s: Power Off Notification timed out, %u\n",

> > -                      mmc_hostname(card->host), timeout);

> > +                       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;

> > +}

> > +

> > +static int mmc_sleep_notify(struct mmc_card *card) {

> > +       int err = 0;

> > +       bool is_busy = 0;

> > +       unsigned long prg_wait = 0;

> > +

> > +       if (!card->can_sleep_notify || !mmc_can_poweroff_notify(card))

> > +               return 0;

> > +

> > +       if (!mmc_card_doing_sleep_notify(card)) {

> > +               mmc_get_card(card);

> > +               mmc_card_set_sleep_notify(card);

> > +               err = mmc_poweroff_notify(card,

> > +                       EXT_CSD_SLEEP_NOTIFICATION);

> > +               mmc_put_card(card);

> > +               if (err) {

> > +                       pr_err("%s: mmc_poweroff_notify failed with %d\n",

> > +                              __func__, err);

> > +                       goto out;

> > +               }

> > +

> > +               prg_wait = jiffies +

> > +                       msecs_to_jiffies(GET_SLEEP_NOTIFY_TIME_MSEC(

> > +                       card->ext_csd.sleep_notify_time));

> > +               }

> > +

> > +       /*

> > +        * Loop will run until:

> > +        * 1. Device is no more in Busy state

> > +        * 2. Sleep notification is not interrupted by HPI & IO request

> > +        */

> 

> As stated earlier. I don't want this patch to consider the above options at this

> point. Please remove all related code from this patch.

> 

I will remove all code related to HPI 
> > +       do {

> > +               /* added some delay to avoid sending card status too often */

> > +               msleep(20);

> > +               err = 0;

> > +               /* Stop polling in case sleep notification was HPIed already */

> > +               if (!mmc_card_doing_sleep_notify(card)) {

> > +                       is_busy = mmc_device_prg_state(card);

> > +                       if (is_busy)

> > +                               err = -EBUSY;

> > +                       break;

> > +               }

> > +               is_busy = mmc_device_prg_state(card);

> > +               if (is_busy && time_after(jiffies, prg_wait)) {

> > +                       /*

> > +                        * making sure we are still in busy before

> > +                        * sending HPI due to timeout error

> > +                        */

> > +                       is_busy = mmc_device_prg_state(card);

> > +                       if (is_busy) {

> > +                               if (mmc_card_doing_sleep_notify(card)) {

> > +                                       card->ext_csd.power_off_notification =

> > +                                               EXT_CSD_POWER_ON;

> > +                                       mmc_interrupt_hpi(card);

> > +                               }

> > +                               err = -ETIMEDOUT;

> > +                               break;

> > +                       }

> > +               }

> > +       } while (is_busy);

> > +

> > +out:

> > +       mmc_card_clr_sleep_notify(card);

> > +       if (err) {

> > +               pr_err("%s: mmc_poweroff_notify for sleep failed with %d\n",

> > +                      mmc_hostname(card->host), err);

> > +       }

> >

> >         return err;

> >  }

> > @@ -1745,8 +1875,16 @@ static int _mmc_suspend(struct mmc_host *host,

> bool is_suspend)

> >                 goto out;

> >

> >         if (mmc_can_poweroff_notify(host->card) &&

> > -               ((host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) || !is_suspend))

> > -               err = mmc_poweroff_notify(host->card, notify_type);

> > +               ((host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) || !is_suspend)) {

> > +               if (!host->card->can_sleep_notify ||

> > +                       !mmc_can_poweroff_notify(host->card)) {

> 

> This looks wrong.

> 

> The outer "if" already checked "if (mmc_can_poweroff_notify(host->card)".

Correct, will remove
> 

> > +                       err = mmc_poweroff_notify(host->card, notify_type);

> > +               } else {

> > +                       err = mmc_sleep_notify(host->card);

> > +                       if (err != -ETIMEDOUT)

> > +                               err = mmc_sleep(host);

> > +               }

> > +       }

> >         else if (mmc_can_sleep(host->card))

> >                 err = mmc_sleep(host);

> >         else if (!mmc_host_is_spi(host)) diff --git

> > a/include/linux/mmc/card.h b/include/linux/mmc/card.h index

> > 19f0175..ed91e6f 100644

> > --- a/include/linux/mmc/card.h

> > +++ b/include/linux/mmc/card.h

> > @@ -62,6 +62,7 @@ struct mmc_ext_csd {

> >         unsigned int            sa_timeout;             /* Units: 100ns */

> >         unsigned int            generic_cmd6_time;      /* Units: 10ms */

> >         unsigned int            power_off_longtime;     /* Units: ms */

> > +       unsigned int            sleep_notify_time;      /* Units: us */

> >         u8                      power_off_notification; /* state */

> >         unsigned int            hs_max_dtr;

> >         unsigned int            hs200_max_dtr;

> > @@ -262,6 +263,7 @@ struct mmc_card {

> >  #define MMC_CARD_REMOVED       (1<<4)          /* card has been removed

> */

> >  #define MMC_STATE_DOING_BKOPS  (1<<5)          /* card is doing BKOPS */

> >  #define MMC_STATE_SUSPENDED    (1<<6)          /* card is suspended */

> > +#define MMC_STATE_SLEEP_NOTIFY (1<<7)          /* card in sleep notify */

> >         unsigned int            quirks;         /* card quirks */

> >  #define MMC_QUIRK_LENIENT_FN0  (1<<0)          /* allow SDIO FN0 writes

> outside of the VS CCCR range */

> >  #define MMC_QUIRK_BLKSZ_FOR_BYTE_MODE (1<<1)   /* use func-

> >cur_blksize */

> > @@ -309,6 +311,7 @@ struct mmc_card {

> >         struct dentry           *debugfs_root;

> >         struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical

> partitions */

> >         unsigned int    nr_parts;

> > +       u8 can_sleep_notify;    /* sleep_notify on/off */

> >  };

> >

> >  /*

> > @@ -427,6 +430,7 @@ static inline void __maybe_unused

> remove_quirk(struct mmc_card *card, int data)

> >  #define mmc_card_removed(c)    ((c) && ((c)->state &

> MMC_CARD_REMOVED))

> >  #define mmc_card_doing_bkops(c)        ((c)->state &

> MMC_STATE_DOING_BKOPS)

> >  #define mmc_card_suspended(c)  ((c)->state & MMC_STATE_SUSPENDED)

> > +#define mmc_card_doing_sleep_notify(c) ((c)->state &

> > +MMC_STATE_SLEEP_NOTIFY)

> >

> >  #define mmc_card_set_present(c)        ((c)->state |= MMC_STATE_PRESENT)

> >  #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY)

> > @@ -437,6 +441,8 @@ static inline void __maybe_unused

> remove_quirk(struct mmc_card *card, int data)

> >  #define mmc_card_clr_doing_bkops(c)    ((c)->state &=

> ~MMC_STATE_DOING_BKOPS)

> >  #define mmc_card_set_suspended(c) ((c)->state |=

> MMC_STATE_SUSPENDED)

> > #define mmc_card_clr_suspended(c) ((c)->state &=

> ~MMC_STATE_SUSPENDED)

> > +#define mmc_card_set_sleep_notify(c)   ((c)->state |=

> MMC_STATE_SLEEP_NOTIFY)

> > +#define mmc_card_clr_sleep_notify(c)   ((c)->state &=

> ~MMC_STATE_SLEEP_NOTIFY)

> >

> >  /*

> >   * Quirk add/remove for MMC products.

> > diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index

> > f471193..111e05d 100644

> > --- a/include/linux/mmc/host.h

> > +++ b/include/linux/mmc/host.h

> > @@ -286,6 +286,7 @@ struct mmc_host {

> >                                  MMC_CAP2_HS400_1_2V)

> >  #define MMC_CAP2_HSX00_1_2V    (MMC_CAP2_HS200_1_2V_SDR |

> MMC_CAP2_HS400_1_2V)

> >  #define MMC_CAP2_SDIO_IRQ_NOTHREAD (1 << 17)

> > +#define MMC_CAP2_SLEEP_NOTIFY  (1 << 18)       /* sleep notify supported

> */

> >

> >         mmc_pm_flag_t           pm_caps;        /* supported pm features */

> >

> > diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index

> > 124f562..bbb71ae 100644

> > --- a/include/linux/mmc/mmc.h

> > +++ b/include/linux/mmc/mmc.h

> > @@ -309,6 +309,7 @@ struct _mmc_csd {

> >  #define EXT_CSD_PWR_CL_52_360          202     /* RO */

> >  #define EXT_CSD_PWR_CL_26_360          203     /* RO */

> >  #define EXT_CSD_SEC_CNT                        212     /* RO, 4 bytes */

> > +#define EXT_CSD_SLEEP_NOTIFICATION_TIME        216     /* RO */

> >  #define EXT_CSD_S_A_TIMEOUT            217     /* RO */

> >  #define EXT_CSD_REL_WR_SEC_C           222     /* RO */

> >  #define EXT_CSD_HC_WP_GRP_SIZE         221     /* RO */

> > @@ -403,6 +404,7 @@ struct _mmc_csd {

> >  #define EXT_CSD_POWER_ON               1

> >  #define EXT_CSD_POWER_OFF_SHORT                2

> >  #define EXT_CSD_POWER_OFF_LONG         3

> > +#define EXT_CSD_SLEEP_NOTIFICATION     4

> >

> >  #define EXT_CSD_PWR_CL_8BIT_MASK       0xF0    /* 8 bit PWR CLS */

> >  #define EXT_CSD_PWR_CL_4BIT_MASK       0x0F    /* 8 bit PWR CLS */

> > --

> > 1.7.9.5

> >

> 

> Kind regards

> Uffe
diff mbox

Patch

diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index a802863..2bf2b2c 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -59,6 +59,12 @@  static const unsigned int tacc_mant[] = {
 		__res & __mask;						\
 	})
 
+#define MMC_SLEEP_NOTIFY_MAX_TIME	0x17
+#define GET_SLEEP_NOTIFY_TIME(value) \
+	(10 * (1 << (unsigned int)(value)))
+#define GET_SLEEP_NOTIFY_TIME_MSEC(value) \
+	(DIV_ROUND_UP(GET_SLEEP_NOTIFY_TIME(value), 1000))
+
 /*
  * Given the decoded CSD structure, decode the raw CID to our CID structure.
  */
@@ -582,6 +588,8 @@  static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd)
 		card->ext_csd.ffu_capable =
 			(ext_csd[EXT_CSD_SUPPORTED_MODE] & 0x1) &&
 			!(ext_csd[EXT_CSD_FW_CONFIG] & 0x1);
+		card->ext_csd.sleep_notify_time =
+			ext_csd[EXT_CSD_SLEEP_NOTIFICATION_TIME];
 	}
 out:
 	return err;
@@ -1529,6 +1537,15 @@  static int mmc_init_card(struct mmc_host *host, u32 ocr,
 			card->ext_csd.hpi_en = 1;
 	}
 
+	/* sleep notify enable/disable for eMMC 5.0 and above */
+	if ((card->ext_csd.rev >= 7) &&  card->ext_csd.hpi_en &&
+			(card->host->caps2 & MMC_CAP2_SLEEP_NOTIFY) &&
+			card->ext_csd.sleep_notify_time > 0 &&
+			card->ext_csd.sleep_notify_time <=
+				MMC_SLEEP_NOTIFY_MAX_TIME) {
+		card->can_sleep_notify = 1;
+	}
+
 	/*
 	 * If cache size is higher than 0, this indicates
 	 * the existence of cache and it can be turned on.
@@ -1642,6 +1659,33 @@  out_release:
 	return err;
 }
 
+/*
+ * check if device is in program state (busy)
+ */
+static bool mmc_device_prg_state(struct mmc_card *card)
+{
+	int rc;
+	u32 status;
+	bool state;
+
+	mmc_get_card(card);
+	rc = mmc_send_status(card, &status);
+	if (rc) {
+		pr_err("%s: Get card status fail. rc=%d\n",
+			mmc_hostname(card->host), rc);
+		state = false;
+		goto out;
+	}
+
+	if (R1_CURRENT_STATE(status) == R1_STATE_PRG)
+		state =  true;
+	else
+		state =  false;
+out:
+	mmc_put_card(card);
+	return state;
+}
+
 static int mmc_can_poweroff_notify(const struct mmc_card *card)
 {
 	return card &&
@@ -1653,20 +1697,106 @@  static int mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type)
 {
 	unsigned int timeout = card->ext_csd.generic_cmd6_time;
 	int err;
+	bool use_busy_signal;
 
 	/* 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;
+	else if (notify_type == EXT_CSD_SLEEP_NOTIFICATION) {
+		/* calculate the maximum timeout for the
+		 * switch command when notifying the device
+		 * that it is about to move to sleep */
+		timeout = GET_SLEEP_NOTIFY_TIME_MSEC(
+			card->ext_csd.sleep_notify_time);
+	}
 
+	/* do not wait on busy signal in case of
+	 * Sleep Notification - to let host get
+	 * another requests
+	 */
+	use_busy_signal = (notify_type == EXT_CSD_SLEEP_NOTIFICATION) ?
+			false : true;
 	err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
 			EXT_CSD_POWER_OFF_NOTIFICATION,
-			notify_type, timeout, true, false, false);
-	if (err)
+			notify_type, timeout,
+			use_busy_signal, false, false);
+	if (!err) {
+		card->ext_csd.power_off_notification = notify_type;
+	} else {
 		pr_err("%s: Power Off Notification timed out, %u\n",
-		       mmc_hostname(card->host), timeout);
+			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;
+}
+
+static int mmc_sleep_notify(struct mmc_card *card)
+{
+	int err = 0;
+	bool is_busy = 0;
+	unsigned long prg_wait = 0;
+
+	if (!card->can_sleep_notify || !mmc_can_poweroff_notify(card))
+		return 0;
+
+	if (!mmc_card_doing_sleep_notify(card)) {
+		mmc_get_card(card);
+		mmc_card_set_sleep_notify(card);
+		err = mmc_poweroff_notify(card,
+			EXT_CSD_SLEEP_NOTIFICATION);
+		mmc_put_card(card);
+		if (err) {
+			pr_err("%s: mmc_poweroff_notify failed with %d\n",
+			       __func__, err);
+			goto out;
+		}
+
+		prg_wait = jiffies +
+			msecs_to_jiffies(GET_SLEEP_NOTIFY_TIME_MSEC(
+			card->ext_csd.sleep_notify_time));
+		}
+
+	/*
+	 * Loop will run until:
+	 * 1. Device is no more in Busy state
+	 * 2. Sleep notification is not interrupted by HPI & IO request
+	 */
+	do {
+		/* added some delay to avoid sending card status too often */
+		msleep(20);
+		err = 0;
+		/* Stop polling in case sleep notification was HPIed already */
+		if (!mmc_card_doing_sleep_notify(card)) {
+			is_busy = mmc_device_prg_state(card);
+			if (is_busy)
+				err = -EBUSY;
+			break;
+		}
+		is_busy = mmc_device_prg_state(card);
+		if (is_busy && time_after(jiffies, prg_wait)) {
+			/*
+			 * making sure we are still in busy before
+			 * sending HPI due to timeout error
+			 */
+			is_busy = mmc_device_prg_state(card);
+			if (is_busy) {
+				if (mmc_card_doing_sleep_notify(card)) {
+					card->ext_csd.power_off_notification =
+						EXT_CSD_POWER_ON;
+					mmc_interrupt_hpi(card);
+				}
+				err = -ETIMEDOUT;
+				break;
+			}
+		}
+	} while (is_busy);
+
+out:
+	mmc_card_clr_sleep_notify(card);
+	if (err) {
+		pr_err("%s: mmc_poweroff_notify for sleep failed with %d\n",
+		       mmc_hostname(card->host), err);
+	}
 
 	return err;
 }
@@ -1745,8 +1875,16 @@  static int _mmc_suspend(struct mmc_host *host, bool is_suspend)
 		goto out;
 
 	if (mmc_can_poweroff_notify(host->card) &&
-		((host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) || !is_suspend))
-		err = mmc_poweroff_notify(host->card, notify_type);
+		((host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) || !is_suspend)) {
+		if (!host->card->can_sleep_notify ||
+			!mmc_can_poweroff_notify(host->card)) {
+			err = mmc_poweroff_notify(host->card, notify_type);
+		} else {
+			err = mmc_sleep_notify(host->card);
+			if (err != -ETIMEDOUT)
+				err = mmc_sleep(host);
+		}
+	}
 	else if (mmc_can_sleep(host->card))
 		err = mmc_sleep(host);
 	else if (!mmc_host_is_spi(host))
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 19f0175..ed91e6f 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -62,6 +62,7 @@  struct mmc_ext_csd {
 	unsigned int		sa_timeout;		/* Units: 100ns */
 	unsigned int		generic_cmd6_time;	/* Units: 10ms */
 	unsigned int            power_off_longtime;     /* Units: ms */
+	unsigned int		sleep_notify_time;	/* Units: us */
 	u8			power_off_notification;	/* state */
 	unsigned int		hs_max_dtr;
 	unsigned int		hs200_max_dtr;
@@ -262,6 +263,7 @@  struct mmc_card {
 #define MMC_CARD_REMOVED	(1<<4)		/* card has been removed */
 #define MMC_STATE_DOING_BKOPS	(1<<5)		/* card is doing BKOPS */
 #define MMC_STATE_SUSPENDED	(1<<6)		/* card is suspended */
+#define MMC_STATE_SLEEP_NOTIFY (1<<7)		/* card in sleep notify */
 	unsigned int		quirks; 	/* card quirks */
 #define MMC_QUIRK_LENIENT_FN0	(1<<0)		/* allow SDIO FN0 writes outside of the VS CCCR range */
 #define MMC_QUIRK_BLKSZ_FOR_BYTE_MODE (1<<1)	/* use func->cur_blksize */
@@ -309,6 +311,7 @@  struct mmc_card {
 	struct dentry		*debugfs_root;
 	struct mmc_part	part[MMC_NUM_PHY_PARTITION]; /* physical partitions */
 	unsigned int    nr_parts;
+	u8 can_sleep_notify;	/* sleep_notify on/off */
 };
 
 /*
@@ -427,6 +430,7 @@  static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data)
 #define mmc_card_removed(c)	((c) && ((c)->state & MMC_CARD_REMOVED))
 #define mmc_card_doing_bkops(c)	((c)->state & MMC_STATE_DOING_BKOPS)
 #define mmc_card_suspended(c)	((c)->state & MMC_STATE_SUSPENDED)
+#define mmc_card_doing_sleep_notify(c) ((c)->state & MMC_STATE_SLEEP_NOTIFY)
 
 #define mmc_card_set_present(c)	((c)->state |= MMC_STATE_PRESENT)
 #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY)
@@ -437,6 +441,8 @@  static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data)
 #define mmc_card_clr_doing_bkops(c)	((c)->state &= ~MMC_STATE_DOING_BKOPS)
 #define mmc_card_set_suspended(c) ((c)->state |= MMC_STATE_SUSPENDED)
 #define mmc_card_clr_suspended(c) ((c)->state &= ~MMC_STATE_SUSPENDED)
+#define mmc_card_set_sleep_notify(c)	((c)->state |= MMC_STATE_SLEEP_NOTIFY)
+#define mmc_card_clr_sleep_notify(c)	((c)->state &= ~MMC_STATE_SLEEP_NOTIFY)
 
 /*
  * Quirk add/remove for MMC products.
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index f471193..111e05d 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -286,6 +286,7 @@  struct mmc_host {
 				 MMC_CAP2_HS400_1_2V)
 #define MMC_CAP2_HSX00_1_2V	(MMC_CAP2_HS200_1_2V_SDR | MMC_CAP2_HS400_1_2V)
 #define MMC_CAP2_SDIO_IRQ_NOTHREAD (1 << 17)
+#define MMC_CAP2_SLEEP_NOTIFY	(1 << 18)	/* sleep notify supported */
 
 	mmc_pm_flag_t		pm_caps;	/* supported pm features */
 
diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h
index 124f562..bbb71ae 100644
--- a/include/linux/mmc/mmc.h
+++ b/include/linux/mmc/mmc.h
@@ -309,6 +309,7 @@  struct _mmc_csd {
 #define EXT_CSD_PWR_CL_52_360		202	/* RO */
 #define EXT_CSD_PWR_CL_26_360		203	/* RO */
 #define EXT_CSD_SEC_CNT			212	/* RO, 4 bytes */
+#define EXT_CSD_SLEEP_NOTIFICATION_TIME	216	/* RO */
 #define EXT_CSD_S_A_TIMEOUT		217	/* RO */
 #define EXT_CSD_REL_WR_SEC_C		222	/* RO */
 #define EXT_CSD_HC_WP_GRP_SIZE		221	/* RO */
@@ -403,6 +404,7 @@  struct _mmc_csd {
 #define EXT_CSD_POWER_ON		1
 #define EXT_CSD_POWER_OFF_SHORT		2
 #define EXT_CSD_POWER_OFF_LONG		3
+#define EXT_CSD_SLEEP_NOTIFICATION	4
 
 #define EXT_CSD_PWR_CL_8BIT_MASK	0xF0	/* 8 bit PWR CLS */
 #define EXT_CSD_PWR_CL_4BIT_MASK	0x0F	/* 8 bit PWR CLS */