[PATCH RFC] 5.4 spi-nor: allow uniform eraseregion device to try smallest erase
John Thomson
git at johnthomson.fastmail.com.au
Tue Aug 11 22:34:42 EDT 2020
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 at 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 --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);
}
}
--
2.28.0
More information about the linux-mtd
mailing list