[MMC] Provide access to Samsung's e-MMC Smart Report via sysfs

Added CONFIG_MMC_SAMSUNG_SMART which enables code that will
expose Samsung's proprietary e-MMC health information to sysfs.

Example output:
$ cat /sys/devices/platform/*/*/mmc_host/mmc*/mmc*/samsung_smart
version                       : 0
error mode                    : Normal
super block size              : 4194304
super page size               : 32768
optimal write size            : 32768
read reclaim count            : 0
optimal trim size             : 262144
number of banks               : 2
initial bad blocks per bank   : 10,4,0,0
runtime bad blocks per bank   : 0,0,0,0
reserved blocks left per bank : 131,137,0,0
all erase counts (min,avg,max): 0,760,65535
SLC erase counts (min,avg,max): 0,5,74
MLC erase counts (min,avg,max): 0,790,65535

Change-Id: Ib476ea5d35264aa94b378f49121d31b20b751f04
Signed-off-by: Scott Anderson <saa@android.com>
diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig
index 85c2e1a..5e9a948 100644
--- a/drivers/mmc/core/Kconfig
+++ b/drivers/mmc/core/Kconfig
@@ -44,3 +44,9 @@
 	  about re-trying SD init requests. This can be a useful
 	  work-around for buggy controllers and hardware. Enable
 	  if you are experiencing issues with SD detection.
+
+config MMC_SAMSUNG_SMART
+	bool "Make Samsung Smart Report available in sysfs"
+	help
+	  If you say Y here, code will be added to retrieve the Smart
+	  Report from Samsung e-MMC cards and make it available via sysfs.
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 4a4d42c..2c435b1 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -483,6 +483,21 @@
 	return err;
 }
 
+#ifdef CONFIG_MMC_SAMSUNG_SMART
+static ssize_t mmc_samsung_smart(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct mmc_card *card = mmc_dev_to_card(dev);
+
+	if (card->quirks & MMC_QUIRK_SAMSUNG_SMART)
+		return mmc_samsung_smart_handle(card, buf);
+
+	/* There is no information available for this card. */
+	return 0;
+}
+static DEVICE_ATTR(samsung_smart, S_IRUGO, mmc_samsung_smart, NULL);
+#endif /* CONFIG_MMC_SAMSUNG_SMART */
+
 MMC_DEV_ATTR(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
 	card->raw_cid[2], card->raw_cid[3]);
 MMC_DEV_ATTR(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
@@ -514,6 +529,9 @@
 	&dev_attr_serial.attr,
 	&dev_attr_enhanced_area_offset.attr,
 	&dev_attr_enhanced_area_size.attr,
+#ifdef CONFIG_MMC_SAMSUNG_SMART
+	&dev_attr_samsung_smart.attr,
+#endif
 	NULL,
 };
 
@@ -548,6 +566,14 @@
 	MMC_FIXUP_REV("MAG4FA", 0x15, CID_OEMID_ANY,
 		      cid_rev(0, 0x25, 1997, 1), cid_rev(0, 0x25, 2012, 12),
 		      add_quirk_mmc, MMC_QUIRK_SAMSUNG_WL_PATCH),
+#ifdef CONFIG_MMC_SAMSUNG_SMART
+	MMC_FIXUP("VYL00M", 0x15, CID_OEMID_ANY,
+		      add_quirk_mmc, MMC_QUIRK_SAMSUNG_SMART),
+	MMC_FIXUP("KYL00M", 0x15, CID_OEMID_ANY,
+		      add_quirk_mmc, MMC_QUIRK_SAMSUNG_SMART),
+	MMC_FIXUP("MAG4FA", 0x15, CID_OEMID_ANY,
+		      add_quirk_mmc, MMC_QUIRK_SAMSUNG_SMART),
+#endif
 	END_FIXUP
 };
 
diff --git a/drivers/mmc/core/quirks.c b/drivers/mmc/core/quirks.c
index 9df3d97..48fb238 100644
--- a/drivers/mmc/core/quirks.c
+++ b/drivers/mmc/core/quirks.c
@@ -164,7 +164,7 @@
 
 #define TEST_MMC_FW_PATCHING
 
-#ifdef TEST_MMC_FW_PATCHING
+#if defined(CONFIG_MMC_SAMSUNG_SMART) || defined(TEST_MMC_FW_PATCHING)
 static struct mmc_command wcmd;
 static struct mmc_data wdata;
 
@@ -198,7 +198,7 @@
 		return wdata.error;
 	return 0;
 }
-#endif /* TEST_MMC_FW_PATCHING */
+#endif /* CONFIG_MMC_SAMSUNG_SMART || TEST_MMC_FW_PATCHING */
 
 /*
  * Copy entire page when wear leveling is happened
@@ -321,3 +321,150 @@
 		pr_err("%s : Failed to fixup Samsung emmc firmware(%d)\n",
 			mmc_hostname(card->host), err);
 }
+
+#ifdef CONFIG_MMC_SAMSUNG_SMART
+static int mmc_samsung_smart_read(struct mmc_card *card, u8 *rdblock)
+{
+	int err, errx;
+
+	/* enter vendor Smart Report mode */
+	err = mmc_movi_vendor_cmd(card, 0xEFAC62EC);
+	if (err) {
+		pr_err("Failed entering Smart Report mode(1, %d)\n", err);
+		return err;
+	}
+	err = mmc_movi_vendor_cmd(card, 0x0000CCEE);
+	if (err) {
+		pr_err("Failed entering Smart Report mode(2, %d)\n", err);
+		return err;
+	}
+
+	/* read Smart Report */
+	err = mmc_movi_read_cmd(card, rdblock);
+	if (err)
+		pr_err("Failed reading Smart Report(%d)\n", err);
+		/* Do NOT return yet; we must leave Smart Report mode.*/
+
+	/* exit vendor Smart Report mode */
+	errx = mmc_movi_vendor_cmd(card, 0xEFAC62EC);
+	if (errx)
+		pr_err("Failed exiting Smart Report mode(1, %d)\n", errx);
+	else {
+		errx = mmc_movi_vendor_cmd(card, 0x00DECCEE);
+		if (errx)
+			pr_err("Failed exiting Smart Report mode(2, %d)\n",
+									errx);
+	}
+	if (err)
+		return err;
+	return errx;
+}
+ssize_t mmc_samsung_smart_parse(u32 *report, char *for_sysfs)
+{
+	unsigned size = PAGE_SIZE;
+	unsigned wrote;
+	unsigned i;
+	u32 val;
+	char *str;
+	static const struct {
+		char *fmt;
+		unsigned val_index;
+	} to_output[] = {
+		{ "super block size              : %u\n", 1 },
+		{ "super page size               : %u\n", 2 },
+		{ "optimal write size            : %u\n", 3 },
+		{ "read reclaim count            : %u\n", 20 },
+		{ "optimal trim size             : %u\n", 21 },
+		{ "number of banks               : %u\n", 4 },
+		{ "initial bad blocks per bank   : %u",	  5 },
+		{ ",%u",				  8 },
+		{ ",%u",				  11 },
+		{ ",%u\n",				  14 },
+		{ "runtime bad blocks per bank   : %u",	  6 },
+		{ ",%u",				  9 },
+		{ ",%u",				  12 },
+		{ ",%u\n",				  15 },
+		{ "reserved blocks left per bank : %u",	  7 },
+		{ ",%u",				  10 },
+		{ ",%u",				  13 },
+		{ ",%u\n",				  16 },
+		{ "all erase counts (min,avg,max): %u",	  18 },
+		{ ",%u",				  19 },
+		{ ",%u\n",				  17 },
+		{ "SLC erase counts (min,avg,max): %u",	  31 },
+		{ ",%u",				  32 },
+		{ ",%u\n",				  30 },
+		{ "MLC erase counts (min,avg,max): %u",	  34 },
+		{ ",%u",				  35 },
+		{ ",%u\n",				  33 },
+	};
+
+	/* A version field just in case things change. */
+	wrote = scnprintf(for_sysfs, size,
+				"version                       : %u\n", 0);
+	size -= wrote;
+	for_sysfs += wrote;
+
+	/* The error mode. */
+	val = le32_to_cpu(report[0]);
+	switch (val) {
+	case 0xD2D2D2D2:
+		str = "Normal";
+		break;
+	case 0x5C5C5C5C:
+		str = "RuntimeFatalError";
+		break;
+	case 0xE1E1E1E1:
+		str = "MetaBrokenError";
+		break;
+	case 0x37373737:
+		str = "OpenFatalError";
+		val = 0; /* Remaining data is unreliable. */
+		break;
+	default:
+		str = "Invalid";
+		val = 0; /* Remaining data is unreliable. */
+		break;
+	}
+	wrote = scnprintf(for_sysfs, size,
+				"error mode                    : %s\n", str);
+	size -= wrote;
+	for_sysfs += wrote;
+	/* Exit if we can't rely on the remaining data. */
+	if (!val)
+		return PAGE_SIZE - size;
+
+	for (i = 0; i < ARRAY_SIZE(to_output); i++) {
+		wrote = scnprintf(for_sysfs, size, to_output[i].fmt,
+				  le32_to_cpu(report[to_output[i].val_index]));
+		size -= wrote;
+		for_sysfs += wrote;
+	}
+
+	return PAGE_SIZE - size;
+}
+ssize_t mmc_samsung_smart_handle(struct mmc_card *card, char *buf)
+{
+	int err;
+	u32 *buffer;
+	ssize_t len;
+
+	buffer = kmalloc(512, GFP_KERNEL);
+	if (!buffer) {
+		pr_err("Failed to alloc memory for Smart Report\n");
+		return 0;
+	}
+
+	mmc_claim_host(card->host);
+	err = mmc_samsung_smart_read(card, (u8 *)buffer);
+	mmc_release_host(card->host);
+
+	if (err)
+		len = 0;
+	else
+		len = mmc_samsung_smart_parse(buffer, buf);
+
+	kfree(buffer);
+	return len;
+}
+#endif /* CONFIG_MMC_SAMSUNG_SMART */
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 197178a..df4219d 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -189,6 +189,7 @@
 #define MMC_QUIRK_INAND_CMD38	(1<<6)		/* iNAND devices have broken CMD38 */
 #define MMC_QUIRK_BLK_NO_CMD23	(1<<7)		/* Avoid CMD23 for regular multiblock */
 #define MMC_QUIRK_SAMSUNG_WL_PATCH	(1<<8)	/* Patch Samsung FW to fix wear leveling bug */
+#define MMC_QUIRK_SAMSUNG_SMART	(1<<9)		/* Samsung SMART is available */
 
 	unsigned int		erase_size;	/* erase size in sectors */
  	unsigned int		erase_shift;	/* if erase unit is power 2 */
@@ -404,5 +405,6 @@
 extern void mmc_fixup_device(struct mmc_card *card,
 			     const struct mmc_fixup *table);
 extern void mmc_fixup_samsung_fw(struct mmc_card *card);
+extern ssize_t mmc_samsung_smart_handle(struct mmc_card *card, char *buf);
 
 #endif