support setting the OTP enhanced user area parameters

Signed-off-by: Ben Gardiner <bengardiner@nanometrics.ca>
Signed-off-by: Chris Ball <cjb@laptop.org>
diff --git a/mmc.c b/mmc.c
index dcbab18..725b842 100644
--- a/mmc.c
+++ b/mmc.c
@@ -70,6 +70,11 @@
 		"Set the eMMC data sector size to 4KB by disabling emulation on\n<device>.",
 	  NULL
 	},
+	{ do_enh_area_set, -4,
+	  "enh_area set", "<-y|-n> " "<start KiB> " "<length KiB> " "<device>\n"
+		"Enable the enhanced user area for the <device>.\nDry-run only unless -y is passed.\nNOTE!  This is a one-time programmable (unreversible) change.",
+	  NULL
+	},
 	{ do_status_get, -1,
 	  "status get", "<device>\n"
 	  "Print the response to STATUS_SEND (CMD13).",
diff --git a/mmc.h b/mmc.h
index aa67242..ad07b44 100644
--- a/mmc.h
+++ b/mmc.h
@@ -44,12 +44,14 @@
 #define EXT_CSD_PART_SWITCH_TIME	199
 #define EXT_CSD_BOOT_CFG		179
 #define EXT_CSD_PART_CONFIG		179
+#define EXT_CSD_ERASE_GROUP_DEF		175
 #define EXT_CSD_BOOT_WP			173
 #define EXT_CSD_WR_REL_PARAM		166
 #define EXT_CSD_SANITIZE_START		165
 #define EXT_CSD_BKOPS_EN		163	/* R/W */
 #define EXT_CSD_RST_N_FUNCTION		162	/* R/W */
 #define EXT_CSD_PARTITIONING_SUPPORT	160	/* RO */
+#define EXT_CSD_PARTITIONS_ATTRIBUTE	156	/* R/W */
 #define EXT_CSD_PARTITION_SETTING_COMPLETED	155	/* R/W */
 #define EXT_CSD_ENH_SIZE_MULT_2		142
 #define EXT_CSD_ENH_SIZE_MULT_1		141
@@ -99,6 +101,7 @@
 #define EXT_CSD_PART_CONFIG_ACC_ACK	  (0x40)
 #define EXT_CSD_PARTITIONING_EN		(1<<0)
 #define EXT_CSD_ENH_ATTRIBUTE_EN	(1<<1)
+#define EXT_CSD_ENH_USR			(1<<0)
 
 /* From kernel linux/mmc/core.h */
 #define MMC_RSP_PRESENT	(1 << 0)
diff --git a/mmc_cmds.c b/mmc_cmds.c
index 3483c87..ba4f9cf 100644
--- a/mmc_cmds.c
+++ b/mmc_cmds.c
@@ -449,6 +449,172 @@
 	return ext_csd[224];
 }
 
+int do_enh_area_set(int nargs, char **argv)
+{
+	__u8 value;
+	__u8 ext_csd[512];
+	int fd, ret;
+	char *device;
+	int dry_run = 1;
+	unsigned int start_kib, length_kib, enh_start_addr, enh_size_mult;
+	unsigned long align;
+
+	CHECK(nargs != 5, "Usage: mmc enh_area set <-y|-n> <start KiB> <length KiB> "
+			  "</path/to/mmcblkX>\n", exit(1));
+
+	if (!strcmp("-y", argv[1]))
+		dry_run = 0;
+
+	start_kib = strtol(argv[2], NULL, 10);
+	length_kib = strtol(argv[3], NULL, 10);
+	device = argv[4];
+
+	fd = open(device, O_RDWR);
+	if (fd < 0) {
+		perror("open");
+		exit(1);
+	}
+
+	ret = read_extcsd(fd, ext_csd);
+	if (ret) {
+		fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
+		exit(1);
+	}
+
+	/* assert ENH_ATTRIBUTE_EN */
+	if (!(ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & EXT_CSD_ENH_ATTRIBUTE_EN))
+	{
+		printf(" Device cannot have enhanced tech.\n");
+		exit(1);
+	}
+
+	/* assert not PARTITION_SETTING_COMPLETED */
+	if (ext_csd[EXT_CSD_PARTITION_SETTING_COMPLETED])
+	{
+		printf(" Device is already partitioned\n");
+		exit(1);
+	}
+
+	align = 512l * get_hc_wp_grp_size(ext_csd) * get_hc_erase_grp_size(ext_csd);
+
+	enh_size_mult = (length_kib + align/2l) / align;
+
+	enh_start_addr = start_kib * 1024 / (is_blockaddresed(ext_csd) ? 512 : 1);
+	enh_start_addr /= align;
+	enh_start_addr *= align;
+
+	/* set EXT_CSD_ERASE_GROUP_DEF bit 0 */
+	ret = write_extcsd_value(fd, EXT_CSD_ERASE_GROUP_DEF, 0x1);
+	if (ret) {
+		fprintf(stderr, "Could not write 0x1 to "
+			"EXT_CSD[%d] in %s\n",
+			EXT_CSD_ERASE_GROUP_DEF, device);
+		exit(1);
+	}
+
+	/* write to ENH_START_ADDR and ENH_SIZE_MULT and PARTITIONS_ATTRIBUTE's ENH_USR bit */
+	value = (enh_start_addr >> 24) & 0xff;
+	ret = write_extcsd_value(fd, EXT_CSD_ENH_START_ADDR_3, value);
+	if (ret) {
+		fprintf(stderr, "Could not write 0x%02x to "
+			"EXT_CSD[%d] in %s\n", value,
+			EXT_CSD_ENH_START_ADDR_3, device);
+		exit(1);
+	}
+	value = (enh_start_addr >> 16) & 0xff;
+	ret = write_extcsd_value(fd, EXT_CSD_ENH_START_ADDR_2, value);
+	if (ret) {
+		fprintf(stderr, "Could not write 0x%02x to "
+			"EXT_CSD[%d] in %s\n", value,
+			EXT_CSD_ENH_START_ADDR_2, device);
+		exit(1);
+	}
+	value = (enh_start_addr >> 8) & 0xff;
+	ret = write_extcsd_value(fd, EXT_CSD_ENH_START_ADDR_1, value);
+	if (ret) {
+		fprintf(stderr, "Could not write 0x%02x to "
+			"EXT_CSD[%d] in %s\n", value,
+			EXT_CSD_ENH_START_ADDR_1, device);
+		exit(1);
+	}
+	value = enh_start_addr & 0xff;
+	ret = write_extcsd_value(fd, EXT_CSD_ENH_START_ADDR_0, value);
+	if (ret) {
+		fprintf(stderr, "Could not write 0x%02x to "
+			"EXT_CSD[%d] in %s\n", value,
+			EXT_CSD_ENH_START_ADDR_0, device);
+		exit(1);
+	}
+
+	value = (enh_size_mult >> 16) & 0xff;
+	ret = write_extcsd_value(fd, EXT_CSD_ENH_SIZE_MULT_2, value);
+	if (ret) {
+		fprintf(stderr, "Could not write 0x%02x to "
+			"EXT_CSD[%d] in %s\n", value,
+			EXT_CSD_ENH_SIZE_MULT_2, device);
+		exit(1);
+	}
+	value = (enh_size_mult >> 8) & 0xff;
+	ret = write_extcsd_value(fd, EXT_CSD_ENH_SIZE_MULT_1, value);
+	if (ret) {
+		fprintf(stderr, "Could not write 0x%02x to "
+			"EXT_CSD[%d] in %s\n", value,
+			EXT_CSD_ENH_SIZE_MULT_1, device);
+		exit(1);
+	}
+	value = enh_size_mult & 0xff;
+	ret = write_extcsd_value(fd, EXT_CSD_ENH_SIZE_MULT_0, value);
+	if (ret) {
+		fprintf(stderr, "Could not write 0x%02x to "
+			"EXT_CSD[%d] in %s\n", value,
+			EXT_CSD_ENH_SIZE_MULT_0, device);
+		exit(1);
+	}
+
+	ret = write_extcsd_value(fd, EXT_CSD_PARTITIONS_ATTRIBUTE, EXT_CSD_ENH_USR);
+	if (ret) {
+		fprintf(stderr, "Could not write EXT_CSD_ENH_USR to "
+			"EXT_CSD[%d] in %s\n",
+			EXT_CSD_PARTITIONS_ATTRIBUTE, device);
+		exit(1);
+	}
+
+	if (dry_run)
+	{
+		fprintf(stderr, "NOT setting PARTITION_SETTING_COMPLETED\n");
+		exit(1);
+	}
+
+	fprintf(stderr, "setting OTP PARTITION_SETTING_COMPLETED!\n");
+	ret = write_extcsd_value(fd, EXT_CSD_PARTITION_SETTING_COMPLETED, 0x1);
+	if (ret) {
+		fprintf(stderr, "Could not write 0x1 to "
+			"EXT_CSD[%d] in %s\n",
+			EXT_CSD_PARTITION_SETTING_COMPLETED, device);
+		exit(1);
+	}
+
+	__u32 response;
+	ret = send_status(fd, &response);
+	if (ret) {
+		fprintf(stderr, "Could not get response to SEND_STATUS from %s\n", device);
+		exit(1);
+	}
+
+	if (response & R1_SWITCH_ERROR)
+	{
+		fprintf(stderr, "Setting ENH_USR area failed on %s\n", device);
+		exit(1);
+	}
+
+	fprintf(stderr, "Setting ENH_USR area on %s SUCCESS\n", device);
+	fprintf(stderr, "Device power cycle needed for settings to take effect.\n"
+		"Confirm that PARTITION_SETTING_COMPLETED bit is set using 'extcsd read'"
+		"after power cycle\n");
+
+	return 0;
+}
+
 int do_read_extcsd(int nargs, char **argv)
 {
 	__u8 ext_csd[512], ext_csd_rev, reg;
@@ -723,7 +889,7 @@
 	printf("Boot bus Conditions [BOOT_BUS_CONDITIONS: 0x%02x]\n",
 		ext_csd[177]);
 	printf("High-density erase group definition"
-		" [ERASE_GROUP_DEF: 0x%02x]\n", ext_csd[175]);
+		" [ERASE_GROUP_DEF: 0x%02x]\n", ext_csd[EXT_CSD_ERASE_GROUP_DEF]);
 
 	print_writeprotect_status(ext_csd);
 
@@ -766,7 +932,7 @@
 		printf(" i.e. %lu KiB\n", 512l * reg * wp_sz * erase_sz);
 
 		printf("Partitions attribute [PARTITIONS_ATTRIBUTE]: 0x%02x\n",
-			ext_csd[156]);
+			ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE]);
 		reg = ext_csd[EXT_CSD_PARTITION_SETTING_COMPLETED];
 		printf("Partitioning Setting"
 			" [PARTITION_SETTING_COMPLETED]: 0x%02x\n",
diff --git a/mmc_cmds.h b/mmc_cmds.h
index 4fb01c8..fa347f5 100644
--- a/mmc_cmds.h
+++ b/mmc_cmds.h
@@ -26,3 +26,4 @@
 int do_hwreset_dis(int nargs, char **argv);
 int do_sanitize(int nargs, char **argv);
 int do_status_get(int nargs, char **argv);
+int do_enh_area_set(int nargs, char **argv);