[PATCH] mmc: core: Send SLEEP_NOTIFICATION for eMMC 5.x device

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



eMMC 5.0 spec introduces a new power off notification, SLEEP_NOTIFICATION,
the host may send before issuing the sleep commamd.

Some eMMC expect the SLEEP_NOTIFICATION for optimal performance:
It is recommended by Sandisk for its iNAND 7232 to empty the iNAND
SmartTLC buffer, See "Emptying the iNAND SmartSLC buffer" of datasheet.
Hynix eMMC also resume faster when SLEEP_NOTIFICATION is sent before
going to sleep.

SLEEP_NOTIFICATION PON can theoretically take a long time, iNAND sleep
notification timeout is set to 83.88s. But it has to be sent just before
CMD5, otherwise newer commands may make it moot.

Signed-off-by: Gwendal Grignou <gwendal@xxxxxxxxxxxx>
---
There were several attempts to add SLEEP_NOTIFICATION, here is another try.
For refrence:
 http://thread.gmane.org/gmane.linux.kernel.mmc/23471 [1/2034]
 http://www.spinics.net/lists/linux-mmc/msg31344.html [6/2015]
 https://patchwork.kernel.org/patch/9360633/ [10/2016]


 drivers/mmc/core/mmc.c   | 46 +++++++++++++++++++++++++++++++++++++---------
 include/linux/mmc/card.h |  3 ++-
 include/linux/mmc/mmc.h  |  2 ++
 3 files changed, 41 insertions(+), 10 deletions(-)

diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 36217ad5e9b1..00ee7dd5ba07 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -633,6 +633,13 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd)
 			ext_csd[EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A];
 		card->ext_csd.device_life_time_est_typ_b =
 			ext_csd[EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_B];
+
+		if (ext_csd[EXT_CSD_SLEEP_NOTIFICATION_TIME] == 0 ||
+		    ext_csd[EXT_CSD_SLEEP_NOTIFICATION_TIME] >= 0x18)
+			card->ext_csd.sleep_notification_time = 0;
+		else
+			card->ext_csd.sleep_notification_time =
+				max((10 << ext_csd[EXT_CSD_SLEEP_NOTIFICATION_TIME]) / 1000, 1);
 	}
 
 	/* eMMC v5.1 or later */
@@ -652,6 +659,7 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd)
 				 card->ext_csd.cmdq_depth);
 		}
 	}
+
 out:
 	return err;
 }
@@ -1865,18 +1873,34 @@ static int mmc_can_poweroff_notify(const struct mmc_card *card)
 		(card->ext_csd.power_off_notification == EXT_CSD_POWER_ON);
 }
 
+static int mmc_can_sleep_notify(const struct mmc_card *card)
+{
+	return mmc_can_poweroff_notify(card) &&
+		(card->ext_csd.sleep_notification_time > 0);
+}
+
 static int mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type)
 {
-	unsigned int timeout = card->ext_csd.generic_cmd6_time;
+	unsigned int timeout;
+	bool use_busy_signal = true;
 	int err;
 
-	/* Use EXT_CSD_POWER_OFF_SHORT as default notification type. */
-	if (notify_type == EXT_CSD_POWER_OFF_LONG)
+	switch (notify_type) {
+	case EXT_CSD_POWER_OFF_LONG:
 		timeout = card->ext_csd.power_off_longtime;
+		break;
+	case EXT_CSD_SLEEP_NOTIFICATION:
+		timeout = card->ext_csd.sleep_notification_time;
+		use_busy_signal = false;
+		break;
+	default:
+		/* Use EXT_CSD_POWER_OFF_SHORT as default notification type. */
+		timeout = card->ext_csd.generic_cmd6_time;
+	}
 
 	err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
 			EXT_CSD_POWER_OFF_NOTIFICATION,
-			notify_type, timeout, 0, true, false, false);
+			notify_type, timeout, 0, use_busy_signal, false, false);
 	if (err)
 		pr_err("%s: Power Off Notification timed out, %u\n",
 		       mmc_hostname(card->host), timeout);
@@ -1952,13 +1976,17 @@ 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))
+		((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);
-	else if (!mmc_host_is_spi(host))
+	} else if (mmc_can_sleep(host->card)) {
+		if (mmc_can_sleep_notify(host->card))
+			err = mmc_poweroff_notify(host->card,
+					EXT_CSD_SLEEP_NOTIFICATION);
+		if (!err)
+			err = mmc_sleep(host);
+	} else if (!mmc_host_is_spi(host)) {
 		err = mmc_deselect_cards(host);
-
+	}
 	if (!err) {
 		mmc_power_off(host);
 		mmc_card_set_suspended(host->card);
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 279b39008a33..5af3661bd98a 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -59,8 +59,9 @@ struct mmc_ext_csd {
 	u8			packed_event_en;
 	unsigned int		part_time;		/* Units: ms */
 	unsigned int		sa_timeout;		/* Units: 100ns */
-	unsigned int		generic_cmd6_time;	/* Units: 10ms */
+	unsigned int		generic_cmd6_time;	/* Units: ms */
 	unsigned int            power_off_longtime;     /* Units: ms */
+	unsigned int            sleep_notification_time; /* Units: ms */
 	u8			power_off_notification;	/* state */
 	unsigned int		hs_max_dtr;
 	unsigned int		hs200_max_dtr;
diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h
index 3ffc27aaeeaf..901e124dee72 100644
--- a/include/linux/mmc/mmc.h
+++ b/include/linux/mmc/mmc.h
@@ -277,6 +277,7 @@ static inline bool mmc_op_multi(u32 opcode)
 #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 */
@@ -379,6 +380,7 @@ static inline bool mmc_op_multi(u32 opcode)
 #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 */
-- 
2.15.0.448.gf294e3d99a-goog

--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux