diff mbox series

[RFC] 5.4 spi-nor: allow uniform eraseregion device to try smallest erase

Message ID 20200812023442.8748-1-git@johnthomson.fastmail.com.au
State Not Applicable
Headers show
Series [RFC] 5.4 spi-nor: allow uniform eraseregion device to try smallest erase | expand

Commit Message

John Thomson Aug. 12, 2020, 2:34 a.m. UTC
Allow a uniform erase region spi-nor device to test a requested erase
length, which is not cleanly divisible by the mtd device erasesize,
against the smallest supported erasesize of the device.
If this test is successful, use the multi_sectors path to calculate
and run a list of erase operations.

Signed-off-by: John Thomson <git@johnthomson.fastmail.com.au>

--

Hi linux-mtd

I am trying to get a 4K erase to work on a 64K erasesize
(CONFIG_MTD_SPI_NOR_USE_4K_SECTORS unset) spi-nor device,
which has a uniform erase region.
I am using Linux 5.4 in OpenWrt. I am not sure how I would test
my changes against current Linux.
Is there common hardware, or software that would allow me to do this?

I do seem to get the result I want with this (5.4) patch,
but want to check how valid my changes are,
or if there is a better approach?

Any suggestions would be greatly appreciated!
Cheers
---
 drivers/mtd/spi-nor/spi-nor.c | 52 +++++++++++++++++++++++++++++------
 1 file changed, 44 insertions(+), 8 deletions(-)
diff mbox series

Patch

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index f417fb680cd..480bf95d36e 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -1200,11 +1200,14 @@  static int spi_nor_erase_multi_sectors(struct spi_nor *nor, u64 addr, u32 len)
 	LIST_HEAD(erase_list);
 	struct spi_nor_erase_command *cmd, *next;
 	int ret;
+	u8 previous_erase_opcode;
 
 	ret = spi_nor_init_erase_cmd_list(nor, &erase_list, addr, len);
 	if (ret)
 		return ret;
 
+	if (spi_nor_has_uniform_erase(nor))
+		previous_erase_opcode = nor->erase_opcode;
 	list_for_each_entry_safe(cmd, next, &erase_list, list) {
 		nor->erase_opcode = cmd->opcode;
 		while (cmd->count) {
@@ -1225,10 +1228,14 @@  static int spi_nor_erase_multi_sectors(struct spi_nor *nor, u64 addr, u32 len)
 		kfree(cmd);
 	}
 
+	if (spi_nor_has_uniform_erase(nor))
+		nor->erase_opcode = previous_erase_opcode;
 	return 0;
 
 destroy_erase_cmd_list:
 	spi_nor_destroy_erase_cmd_list(&erase_list);
+	if (spi_nor_has_uniform_erase(nor))
+		nor->erase_opcode = previous_erase_opcode;
 	return ret;
 }
 
@@ -1239,17 +1246,37 @@  static int spi_nor_erase_multi_sectors(struct spi_nor *nor, u64 addr, u32 len)
 static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
 {
 	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+	struct spi_nor_erase_map *map = &nor->params.erase_map;
 	u32 addr, len;
 	uint32_t rem;
 	int ret;
+	uint32_t rem_minimum;
+	u8 i;
 
 	dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr,
 			(long long)instr->len);
 
-	if (spi_nor_has_uniform_erase(nor)) {
+	if (spi_nor_has_uniform_erase(nor))
 		div_u64_rem(instr->len, mtd->erasesize, &rem);
-		if (rem)
-			return -EINVAL;
+
+	/* Find the smallest set bit of uniform_erase_type, and
+	 * find out the erase size it uses.
+	 * Test the erase length is cleanly divisible by it.
+	 */
+	if (spi_nor_has_uniform_erase(nor) && rem) {
+		for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) {
+			if (map->erase_type[i].size == 0)
+				continue;
+			if (map->uniform_erase_type & BIT(i)) {
+				div_u64_rem(instr->len,
+					    map->erase_type[i].size,
+					    &rem_minimum);
+				if (rem_minimum)
+					return -EINVAL;
+				break;
+			} else if (i == SNOR_ERASE_TYPE_MAX - 1)
+				return -EINVAL;
+		}
 	}
 
 	addr = instr->addr;
@@ -1260,7 +1287,9 @@  static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
 		return ret;
 
 	/* whole-chip erase? */
-	if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
+	if (len == mtd->size &&
+	    !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE) &&
+	    !rem) {
 		unsigned long timeout;
 
 		write_enable(nor);
@@ -1289,7 +1318,7 @@  static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
 	 */
 
 	/* "sector"-at-a-time erase */
-	} else if (spi_nor_has_uniform_erase(nor)) {
+	} else if (spi_nor_has_uniform_erase(nor) && !rem) {
 		while (len) {
 			write_enable(nor);
 
@@ -4258,9 +4287,10 @@  spi_nor_select_uniform_erase(struct spi_nor_erase_map *map,
 	if (!erase)
 		return NULL;
 
-	/* Disable all other Sector Erase commands. */
-	map->uniform_erase_type &= ~SNOR_ERASE_TYPE_MASK;
-	map->uniform_erase_type |= BIT(erase - map->erase_type);
+	/* Disable all other Sector Erase commands.
+	 * map->uniform_erase_type &= ~SNOR_ERASE_TYPE_MASK;
+	 * map->uniform_erase_type |= BIT(erase - map->erase_type);
+	 */
 	return erase;
 }
 
@@ -4443,12 +4473,18 @@  static void spi_nor_sfdp_init_params(struct spi_nor *nor)
 	struct spi_nor_flash_parameter sfdp_params;
 
 	memcpy(&sfdp_params, &nor->params, sizeof(sfdp_params));
+	if (spi_nor_has_uniform_erase(nor))
+		nor->params.erase_map.regions = (
+			&nor->params.erase_map.uniform_region);
 
 	if (spi_nor_parse_sfdp(nor, &sfdp_params)) {
 		nor->addr_width = 0;
 		nor->flags &= ~SNOR_F_4B_OPCODES;
 	} else {
 		memcpy(&nor->params, &sfdp_params, sizeof(nor->params));
+		if (spi_nor_has_uniform_erase(nor))
+			nor->params.erase_map.regions = (
+				&nor->params.erase_map.uniform_region);
 	}
 }