MMC: add erase function to both mmc and sd

Erase is a very basic function since the begin of sd specification is
announced. Although we could write a bulk of full 0xff memory to the
range to take place of erase, it is more convenient and safe to
implement the erase function itself.

Signed-off-by: Lei Wen <leiwen@marvell.com>
Signed-off-by: Andy Fleming <afleming@freescale.com>
Acked-by: Mike Frysinger <vapier@gentoo.org>
diff --git a/common/cmd_mmc.c b/common/cmd_mmc.c
index a645803..7335cdc 100644
--- a/common/cmd_mmc.c
+++ b/common/cmd_mmc.c
@@ -91,6 +91,7 @@
 	MMC_INVALID,
 	MMC_READ,
 	MMC_WRITE,
+	MMC_ERASE,
 };
 static void print_mmcinfo(struct mmc *mmc)
 {
@@ -252,15 +253,24 @@
 		state = MMC_READ;
 	else if (strcmp(argv[1], "write") == 0)
 		state = MMC_WRITE;
+	else if (strcmp(argv[1], "erase") == 0)
+		state = MMC_ERASE;
 	else
 		state = MMC_INVALID;
 
 	if (state != MMC_INVALID) {
 		struct mmc *mmc = find_mmc_device(curr_device);
-		void *addr = (void *)simple_strtoul(argv[2], NULL, 16);
-		u32 blk = simple_strtoul(argv[3], NULL, 16);
-		u32 cnt = simple_strtoul(argv[4], NULL, 16);
-		u32 n;
+		int idx = 2;
+		u32 blk, cnt, n;
+		void *addr;
+
+		if (state != MMC_ERASE) {
+			addr = (void *)simple_strtoul(argv[idx], NULL, 16);
+			++idx;
+		} else
+			addr = 0;
+		blk = simple_strtoul(argv[idx], NULL, 16);
+		cnt = simple_strtoul(argv[idx + 1], NULL, 16);
 
 		if (!mmc) {
 			printf("no mmc device at slot %x\n", curr_device);
@@ -283,6 +293,9 @@
 			n = mmc->block_dev.block_write(curr_device, blk,
 						      cnt, addr);
 			break;
+		case MMC_ERASE:
+			n = mmc->block_dev.block_erase(curr_device, blk, cnt);
+			break;
 		default:
 			BUG();
 		}
@@ -300,6 +313,7 @@
 	"MMC sub system",
 	"read addr blk# cnt\n"
 	"mmc write addr blk# cnt\n"
+	"mmc erase blk# cnt\n"
 	"mmc rescan\n"
 	"mmc part - lists available partition on current mmc device\n"
 	"mmc dev [dev] [part] - show or set current mmc device [partition]\n"
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c
index 21aedba..9a1ee3d 100644
--- a/drivers/mmc/mmc.c
+++ b/drivers/mmc/mmc.c
@@ -174,6 +174,88 @@
 	return NULL;
 }
 
+static ulong mmc_erase_t(struct mmc *mmc, ulong start, lbaint_t blkcnt)
+{
+	struct mmc_cmd cmd;
+	ulong end;
+	int err, start_cmd, end_cmd;
+
+	if (mmc->high_capacity)
+		end = start + blkcnt - 1;
+	else {
+		end = (start + blkcnt - 1) * mmc->write_bl_len;
+		start *= mmc->write_bl_len;
+	}
+
+	if (IS_SD(mmc)) {
+		start_cmd = SD_CMD_ERASE_WR_BLK_START;
+		end_cmd = SD_CMD_ERASE_WR_BLK_END;
+	} else {
+		start_cmd = MMC_CMD_ERASE_GROUP_START;
+		end_cmd = MMC_CMD_ERASE_GROUP_END;
+	}
+
+	cmd.cmdidx = start_cmd;
+	cmd.cmdarg = start;
+	cmd.resp_type = MMC_RSP_R1;
+	cmd.flags = 0;
+
+	err = mmc_send_cmd(mmc, &cmd, NULL);
+	if (err)
+		goto err_out;
+
+	cmd.cmdidx = end_cmd;
+	cmd.cmdarg = end;
+
+	err = mmc_send_cmd(mmc, &cmd, NULL);
+	if (err)
+		goto err_out;
+
+	cmd.cmdidx = MMC_CMD_ERASE;
+	cmd.cmdarg = SECURE_ERASE;
+	cmd.resp_type = MMC_RSP_R1b;
+
+	err = mmc_send_cmd(mmc, &cmd, NULL);
+	if (err)
+		goto err_out;
+
+	return 0;
+
+err_out:
+	puts("mmc erase failed\n");
+	return err;
+}
+
+static unsigned long
+mmc_berase(int dev_num, unsigned long start, lbaint_t blkcnt)
+{
+	int err = 0;
+	struct mmc *mmc = find_mmc_device(dev_num);
+	lbaint_t blk = 0, blk_r = 0;
+
+	if (!mmc)
+		return -1;
+
+	if ((start % mmc->erase_grp_size) || (blkcnt % mmc->erase_grp_size))
+		printf("\n\nCaution! Your devices Erase group is 0x%x\n"
+			"The erase range would be change to 0x%lx~0x%lx\n\n",
+		       mmc->erase_grp_size, start & ~(mmc->erase_grp_size - 1),
+		       ((start + blkcnt + mmc->erase_grp_size)
+		       & ~(mmc->erase_grp_size - 1)) - 1);
+
+	while (blk < blkcnt) {
+		blk_r = ((blkcnt - blk) > mmc->erase_grp_size) ?
+			mmc->erase_grp_size : (blkcnt - blk);
+		err = mmc_erase_t(mmc, start + blk, blk_r);
+		if (err)
+			break;
+
+		blk += blk_r;
+	}
+
+	return blk;
+}
+
 static ulong
 mmc_write_blocks(struct mmc *mmc, ulong start, lbaint_t blkcnt, const void*src)
 {
@@ -911,6 +993,10 @@
 			return err;
 	}
 
+	/*
+	 * For SD, its erase group is always one sector
+	 */
+	mmc->erase_grp_size = 1;
 	mmc->part_config = MMCPART_NOAVAILABLE;
 	if (!IS_SD(mmc) && (mmc->version >= MMC_VERSION_4)) {
 		/* check  ext_csd version and capacity */
@@ -921,6 +1007,21 @@
 			mmc->capacity *= 512;
 		}
 
+		/*
+		 * Check whether GROUP_DEF is set, if yes, read out
+		 * group size from ext_csd directly, or calculate
+		 * the group size from the csd value.
+		 */
+		if (ext_csd[175])
+			mmc->erase_grp_size = ext_csd[224] * 512 * 1024;
+		else {
+			int erase_gsz, erase_gmul;
+			erase_gsz = (mmc->csd[2] & 0x00007c00) >> 10;
+			erase_gmul = (mmc->csd[2] & 0x000003e0) >> 5;
+			mmc->erase_grp_size = (erase_gsz + 1)
+				* (erase_gmul + 1);
+		}
+
 		/* store the partition info of emmc */
 		if (ext_csd[160] & PART_SUPPORT)
 			mmc->part_config = ext_csd[179];
@@ -1044,6 +1145,7 @@
 	mmc->block_dev.removable = 1;
 	mmc->block_dev.block_read = mmc_bread;
 	mmc->block_dev.block_write = mmc_bwrite;
+	mmc->block_dev.block_erase = mmc_berase;
 	if (!mmc->b_max)
 		mmc->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
 
diff --git a/include/mmc.h b/include/mmc.h
index aeacdee..1c8a360 100644
--- a/include/mmc.h
+++ b/include/mmc.h
@@ -75,6 +75,9 @@
 #define MMC_CMD_READ_MULTIPLE_BLOCK	18
 #define MMC_CMD_WRITE_SINGLE_BLOCK	24
 #define MMC_CMD_WRITE_MULTIPLE_BLOCK	25
+#define MMC_CMD_ERASE_GROUP_START	35
+#define MMC_CMD_ERASE_GROUP_END		36
+#define MMC_CMD_ERASE			38
 #define MMC_CMD_APP_CMD			55
 #define MMC_CMD_SPI_READ_OCR		58
 #define MMC_CMD_SPI_CRC_ON_OFF		59
@@ -84,6 +87,8 @@
 #define SD_CMD_SEND_IF_COND		8
 
 #define SD_CMD_APP_SET_BUS_WIDTH	6
+#define SD_CMD_ERASE_WR_BLK_START	32
+#define SD_CMD_ERASE_WR_BLK_END		33
 #define SD_CMD_APP_SEND_OP_COND		41
 #define SD_CMD_APP_SEND_SCR		51
 
@@ -99,6 +104,8 @@
 #define OCR_VOLTAGE_MASK	0x007FFF80
 #define OCR_ACCESS_MODE		0x60000000
 
+#define SECURE_ERASE		0x80000000
+
 #define MMC_STATUS_MASK		(~0x0206BF7F)
 #define MMC_STATUS_RDY_FOR_DATA (1 << 8)
 #define MMC_STATUS_CURR_STATE	(0xf << 9)
@@ -285,6 +292,7 @@
 	uint tran_speed;
 	uint read_bl_len;
 	uint write_bl_len;
+	uint erase_grp_size;
 	u64 capacity;
 	block_dev_desc_t block_dev;
 	int (*send_cmd)(struct mmc *mmc,
diff --git a/include/part.h b/include/part.h
index 3cdae02..5243511 100644
--- a/include/part.h
+++ b/include/part.h
@@ -49,6 +49,9 @@
 				       unsigned long start,
 				       lbaint_t blkcnt,
 				       const void *buffer);
+	unsigned long   (*block_erase)(int dev,
+				       unsigned long start,
+				       lbaint_t blkcnt);
 	void		*priv;		/* driver private struct pointer */
 }block_dev_desc_t;