From fe3bd6122588e0f99e1cb7034cfc89fa0faec754 Mon Sep 17 00:00:00 2001
From: hsuan-chih_chen <hsuan-chih_chen@asus.com>
Date: Wed, 29 Apr 2015 20:09:30 +0800
Subject: [PATCH] mmc: add force poweroff notify and sw reset for some card

sending power off notify to eMMC before power off.
trigger sw reset before actual HW reset.

Change-Id: I4e3df098b084cc5f39cfd1c52718ed0833d8ba24
Signed-off-by: hsuan-chih_chen <hsuan-chih_chen@asus.com>
---
 drivers/mmc/core/core.c     | 62 +++++++++++++++++++++++++++++++++++++
 drivers/mmc/core/core.h     |  4 +++
 drivers/mmc/core/host.c     |  4 +++
 drivers/mmc/core/mmc.c      |  4 +++
 drivers/mmc/host/msm_sdcc.c | 17 ++++++++++
 include/linux/mmc/card.h    |  5 +++
 include/linux/mmc/host.h    |  1 +
 7 files changed, 97 insertions(+)

diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index d40f6c852727..d0c8249866ef 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -44,6 +44,11 @@
 #include "sd_ops.h"
 #include "sdio_ops.h"
 
+#include <linux/fs.h>
+#include <linux/delay.h>
+
+extern void kernel_restart(char *cmd);
+
 /*
  * Background operations can take a long time, depending on the housekeeping
  * operations the card has to perform.
@@ -1468,6 +1473,40 @@ void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type)
 	mmc_host_clk_release(host);
 }
 
+void mmc_force_poweroff_notify(struct mmc_host *host)
+{
+	int err = 0;
+	unsigned int timeout;
+
+	mmc_claim_host(host);
+
+	if (mmc_card_is_sleep(host->card)) {
+		BUG_ON(!host->bus_ops->resume);
+		err = host->bus_ops->resume(host);
+	}
+
+	if (err) {
+		pr_err("failed to resume for force poweroff notify\n");
+		return;
+	}
+
+	timeout = host->card->ext_csd.generic_cmd6_time;
+
+	pr_info("sending poweroff notify\n");
+	err = mmc_switch(host->card, EXT_CSD_CMD_SET_NORMAL,
+		EXT_CSD_POWER_OFF_NOTIFICATION,
+		EXT_CSD_POWER_OFF_SHORT, timeout);
+	if (err && err != -EBADMSG) {
+		pr_err("Device failed to respond within %d poweroff time\n",
+			timeout);
+		return;
+	}
+
+	pr_info("poweroff notify sent\n");
+	mmc_release_host(host);
+	return;
+}
+
 /*
  * Apply power to the MMC stack.  This is a two-stage process.
  * First, we enable power to the card without the clock running.
@@ -2868,6 +2907,29 @@ int mmc_pm_notify(struct notifier_block *notify_block,
 }
 #endif
 
+
+/* force send poweroff notify to Kingston eMMC
+ * while long press power key before hw reset
+ */
+int force_poweroff_notify(struct notifier_block *notify_block,
+			unsigned long mode, void *unused)
+{
+	struct mmc_host *host = container_of(
+		notify_block, struct mmc_host, force_poweroff_notifier);
+
+	if (host->card == NULL)
+		return 0;
+	/* only for Kingston card */
+	if (mmc_card_mmc(host->card)
+		&& host->card->cid.manfid == 0x70) {
+		emergency_sync();
+		emergency_remount();
+		msleep(1000);
+		kernel_restart(NULL);
+	}
+	return 0;
+}
+
 #ifdef CONFIG_MMC_EMBEDDED_SDIO
 void mmc_set_embedded_sdio_data(struct mmc_host *host,
 				struct sdio_cis *cis,
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
index 85d273775ac0..2b4c51ff39aa 100644
--- a/drivers/mmc/core/core.h
+++ b/drivers/mmc/core/core.h
@@ -47,6 +47,7 @@ int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage,
 void mmc_set_timing(struct mmc_host *host, unsigned int timing);
 void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type);
 void mmc_power_off(struct mmc_host *host);
+void mmc_force_poweroff_notify(struct mmc_host *host);
 
 static inline void mmc_delay(unsigned int ms)
 {
@@ -70,6 +71,9 @@ int mmc_attach_mmc(struct mmc_host *host);
 int mmc_attach_sd(struct mmc_host *host);
 int mmc_attach_sdio(struct mmc_host *host);
 
+int force_poweroff_notify(struct notifier_block *notify_block,
+			unsigned long mode, void *unused);
+
 /* Module parameters */
 extern bool use_spi_crc;
 
diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
index 29b64f0b7c86..e249788661a9 100644
--- a/drivers/mmc/core/host.c
+++ b/drivers/mmc/core/host.c
@@ -27,6 +27,8 @@
 #include "core.h"
 #include "host.h"
 
+#include <linux/gpio_keys.h>
+
 #define cls_dev_to_mmc_host(d)	container_of(d, struct mmc_host, class_dev)
 
 static void mmc_host_classdev_release(struct device *dev)
@@ -335,6 +337,8 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
 #ifdef CONFIG_PM
 	host->pm_notify.notifier_call = mmc_pm_notify;
 #endif
+	host->force_poweroff_notifier.notifier_call = force_poweroff_notify;
+	register_resetkey_notifier(&host->force_poweroff_notifier);
 
 	/*
 	 * By default, hosts do not support SGIO or large requests.
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index ea1eca731ae5..e56d8479b945 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -1464,6 +1464,8 @@ static int mmc_suspend(struct mmc_host *host)
 		err = mmc_card_sleep(host);
 	else if (!mmc_host_is_spi(host))
 		mmc_deselect_cards(host);
+
+	mmc_card_set_sleep(host->card);
 	host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_200);
 
 out:
@@ -1484,6 +1486,7 @@ static int mmc_resume(struct mmc_host *host)
 	BUG_ON(!host);
 	BUG_ON(!host->card);
 
+	mmc_card_clr_sleep(host->card);
 	mmc_claim_host(host);
 	err = mmc_init_card(host, host->ocr, host->card);
 	mmc_release_host(host);
@@ -1496,6 +1499,7 @@ 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);
diff --git a/drivers/mmc/host/msm_sdcc.c b/drivers/mmc/host/msm_sdcc.c
index 91aa05f94bb5..8781bfeb114a 100644
--- a/drivers/mmc/host/msm_sdcc.c
+++ b/drivers/mmc/host/msm_sdcc.c
@@ -60,6 +60,7 @@
 
 #include "msm_sdcc.h"
 #include "msm_sdcc_dml.h"
+#include "../core/core.h"
 
 #define DRIVER_NAME "msm-sdcc"
 
@@ -6256,6 +6257,21 @@ static int msmsdcc_remove(struct platform_device *pdev)
 	return 0;
 }
 
+static void msmsdcc_shutdown(struct platform_device *pdev)
+{
+	struct mmc_host *mmc = mmc_get_drvdata(pdev);
+
+	if (!mmc || !mmc->card)
+		return;
+
+	/* only for Kingston eMMC */
+	if (mmc_card_mmc(mmc->card)
+		&& mmc->card->cid.manfid == 0x70)
+		mmc_force_poweroff_notify(mmc);
+
+	return;
+}
+
 #ifdef CONFIG_MSM_SDIO_AL
 int msmsdcc_sdio_al_lpm(struct mmc_host *mmc, bool enable)
 {
@@ -6616,6 +6632,7 @@ MODULE_DEVICE_TABLE(of, msmsdcc_dt_match);
 static struct platform_driver msmsdcc_driver = {
 	.probe		= msmsdcc_probe,
 	.remove		= msmsdcc_remove,
+	.shutdown	= msmsdcc_shutdown,
 	.driver		= {
 		.name	= "msm_sdcc",
 		.pm	= &msmsdcc_dev_pm_ops,
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 6aade3e4d34f..9d698ed331a9 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -300,6 +300,7 @@ struct mmc_card {
 #define MMC_STATE_HIGHSPEED_200	(1<<8)		/* card is in HS200 mode */
 #define MMC_STATE_DOING_BKOPS	(1<<10)		/* card is doing BKOPS */
 #define MMC_STATE_NEED_BKOPS	(1<<11)		/* card needs to do BKOPS */
+#define MMC_STATE_SLEEP		(1<<12)		/* card is in sleep/deselect state */
 	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 */
@@ -472,6 +473,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_need_bkops(c)	((c)->state & MMC_STATE_NEED_BKOPS)
+#define mmc_card_is_sleep(c)	((c)->state & MMC_STATE_SLEEP)
 
 #define mmc_card_set_present(c)	((c)->state |= MMC_STATE_PRESENT)
 #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY)
@@ -487,6 +489,9 @@ 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_need_bkops(c)	((c)->state |= MMC_STATE_NEED_BKOPS)
 #define mmc_card_clr_need_bkops(c)	((c)->state &= ~MMC_STATE_NEED_BKOPS)
+#define mmc_card_set_sleep(c)	((c)->state |= MMC_STATE_SLEEP)
+#define mmc_card_clr_sleep(c)	((c)->state &= ~MMC_STATE_SLEEP)
+
 /*
  * Quirk add/remove for MMC products.
  */
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index e7d34fc16839..df526d6d12e9 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -169,6 +169,7 @@ struct mmc_host {
 	u32			ocr_avail_sd;	/* SD-specific OCR */
 	u32			ocr_avail_mmc;	/* MMC-specific OCR */
 	struct notifier_block	pm_notify;
+	struct notifier_block	force_poweroff_notifier;
 
 #define MMC_VDD_165_195		0x00000080	/* VDD voltage 1.65 - 1.95 */
 #define MMC_VDD_20_21		0x00000100	/* VDD voltage 2.0 ~ 2.1 */
-- 
GitLab