Hi, Yogesh, On 10/16/2018 12:51 PM, Yogesh Narayan Gaur wrote: > Hi Tudor, > > This patch is breaking the 1-4-4 Read protocol for the spansion flash "s25fl512s". > > Without this patch read request command for Quad mode, 4-byte enable, is coming as 0xEC i.e. SPINOR_OP_READ_1_4_4_4B. > But after applying this patch, read request command for Quad mode is coming as 0x6C i.e. SPINOR_OP_READ_1_1_4_4B. > > This flash also supports non-uniform erase. > Can you please check and provide some suggestion? I don't have this memory to test it, but I'll try to help. Does s25fl512s support non-uniform erase? I'm looking in datasheet[1] at JEDEC BFPT table, dwords 8 and 9, page 132/146 and it looks like it supports just 256KB uniform erase. Thanks, ta [1] http://www.cypress.com/file/177971/download > > -- > Regards > Yogesh Gaur > >> -----Original Message----- >> From: linux-mtd [mailto:linux-mtd-bounces@xxxxxxxxxxxxxxxxxxx] On Behalf Of >> Tudor Ambarus >> Sent: Tuesday, September 11, 2018 9:10 PM >> To: marek.vasut@xxxxxxxxx; dwmw2@xxxxxxxxxxxxx; >> computersforpeace@xxxxxxxxx; boris.brezillon@xxxxxxxxxxx; richard@xxxxxx >> Cc: Tudor Ambarus <tudor.ambarus@xxxxxxxxxxxxx>; linux- >> kernel@xxxxxxxxxxxxxxx; nicolas.ferre@xxxxxxxxxxxxx; >> cyrille.pitchen@xxxxxxxxxxxxx; linux-mtd@xxxxxxxxxxxxxxxxxxx; linux-arm- >> kernel@xxxxxxxxxxxxxxxxxxx; Cristian.Birsan@xxxxxxxxxxxxx >> Subject: [PATCH v3 1/2] mtd: spi-nor: add support to non-uniform SFDP SPI NOR >> flash memories >> >> Based on Cyrille Pitchen's patch >> https://emea01.safelinks.protection.outlook.com/?url=https%3A%2F%2Flkml.or >> g%2Flkml%2F2017%2F3%2F22%2F935&data=02%7C01%7Cyogeshnarayan. >> gaur%40nxp.com%7C3c782e52b7fd4a8b9af008d617fd5154%7C686ea1d3bc2b4 >> c6fa92cd99c5c301635%7C0%7C0%7C636722774108718782&sdata=szyc% >> 2FTumG6eYAmBd0oW3IL7v1yLh9E1SAZqL%2BCWczOA%3D&reserved=0. >> >> This patch is a transitional patch in introducing the support of SFDP SPI >> memories with non-uniform erase sizes like Spansion s25fs512s. >> Non-uniform erase maps will be used later when initialized based on the SFDP >> data. >> >> Introduce the memory erase map which splits the memory array into one or >> many erase regions. Each erase region supports up to 4 erase types, as defined >> by the JEDEC JESD216B (SFDP) specification. >> >> To be backward compatible, the erase map of uniform SPI NOR flash memories >> is initialized so it contains only one erase region and this erase region supports >> only one erase command. Hence a single size is used to erase any sector/block >> of the memory. >> >> Besides, since the algorithm used to erase sectors on non-uniform SPI NOR flash >> memories is quite expensive, when possible, the erase map is tuned to come >> back to the uniform case. >> >> The 'erase with the best command, move forward and repeat' approach was >> suggested by Cristian Birsan in a brainstorm session, so: >> >> Suggested-by: Cristian Birsan <cristian.birsan@xxxxxxxxxxxxx> >> Signed-off-by: Tudor Ambarus <tudor.ambarus@xxxxxxxxxxxxx> >> --- >> drivers/mtd/spi-nor/spi-nor.c | 594 >> +++++++++++++++++++++++++++++++++++++++--- >> include/linux/mtd/spi-nor.h | 107 ++++++++ >> 2 files changed, 659 insertions(+), 42 deletions(-) >> >> diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index >> dc8757e..4687345 100644 >> --- a/drivers/mtd/spi-nor/spi-nor.c >> +++ b/drivers/mtd/spi-nor/spi-nor.c >> @@ -18,6 +18,7 @@ >> #include <linux/math64.h> >> #include <linux/sizes.h> >> #include <linux/slab.h> >> +#include <linux/sort.h> >> >> #include <linux/mtd/mtd.h> >> #include <linux/of_platform.h> >> @@ -261,6 +262,18 @@ static void spi_nor_set_4byte_opcodes(struct spi_nor >> *nor, >> nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode); >> nor->program_opcode = spi_nor_convert_3to4_program(nor- >>> program_opcode); >> nor->erase_opcode = spi_nor_convert_3to4_erase(nor->erase_opcode); >> + >> + if (!spi_nor_has_uniform_erase(nor)) { >> + struct spi_nor_erase_map *map = &nor->erase_map; >> + struct spi_nor_erase_type *erase; >> + int i; >> + >> + for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) { >> + erase = &map->erase_type[i]; >> + erase->opcode = >> + spi_nor_convert_3to4_erase(erase->opcode); >> + } >> + } >> } >> >> /* Enable/disable 4-byte addressing mode. */ @@ -499,6 +512,275 @@ static >> int spi_nor_erase_sector(struct spi_nor *nor, u32 addr) } >> >> /* >> + * spi_nor_div_by_erase_size() - calculate remainder and update new dividend >> + * @erase: pointer to a structure that describes a SPI NOR erase type >> + * @dividend: dividend value >> + * @remainder: pointer to u32 remainder (will be updated) >> + * >> + * Returns two values: remainder and the new dividend */ static u64 >> +spi_nor_div_by_erase_size(const struct spi_nor_erase_type *erase, >> + u64 dividend, u32 *remainder) >> +{ >> + /* JEDEC JESD216B Standard imposes erase sizes to be power of 2. */ >> + *remainder = (u32)dividend & erase->size_mask; >> + return dividend >> erase->size_shift; >> +} >> + >> +/* >> + * spi_nor_find_best_erase_type() - find the best erase type for the >> +given >> + * offset in the serial flash memory and the number of bytes to erase. >> +The >> + * region in which the address fits is expected to be provided. >> + * @map: the erase map of the SPI NOR >> + * @region: pointer to a structure that describes a SPI NOR erase region >> + * @addr: offset in the serial flash memory >> + * @len: number of bytes to erase >> + * >> + * Returns a pointer to the best fitted erase type, NULL otherwise. >> + */ >> +static const struct spi_nor_erase_type * >> +spi_nor_find_best_erase_type(const struct spi_nor_erase_map *map, >> + const struct spi_nor_erase_region *region, >> + u64 addr, u32 len) >> +{ >> + const struct spi_nor_erase_type *erase; >> + u32 rem; >> + int i; >> + u8 erase_mask = region->offset & SNOR_ERASE_TYPE_MASK; >> + >> + /* >> + * Erase types are ordered by size, with the biggest erase type at >> + * index 0. >> + */ >> + for (i = SNOR_ERASE_TYPE_MAX - 1; i >= 0; i--) { >> + /* Does the erase region support the tested erase type? */ >> + if (!(erase_mask & BIT(i))) >> + continue; >> + >> + erase = &map->erase_type[i]; >> + >> + /* Don't erase more than what the user has asked for. */ >> + if (erase->size > len) >> + continue; >> + >> + /* Alignment is not mandatory for overlaid regions */ >> + if (region->offset & SNOR_OVERLAID_REGION) >> + return erase; >> + >> + spi_nor_div_by_erase_size(erase, addr, &rem); >> + if (rem) >> + continue; >> + else >> + return erase; >> + } >> + >> + return NULL; >> +} >> + >> +/* >> + * spi_nor_region_next() - get the next spi nor region >> + * @region: pointer to a structure that describes a SPI NOR erase region >> + * >> + * Returns the next spi nor region or NULL if last region. >> + */ >> +static struct spi_nor_erase_region * >> +spi_nor_region_next(struct spi_nor_erase_region *region) { >> + if (spi_nor_region_is_last(region)) >> + return NULL; >> + region++; >> + return region; >> +} >> + >> +/* >> + * spi_nor_find_erase_region() - find the region of the serial flash >> +memory in >> + * which the offset fits >> + * @map: the erase map of the SPI NOR >> + * @addr: offset in the serial flash memory >> + * >> + * Returns pointer to the spi_nor_erase_region struct, ERR_PTR(-errno) >> + * otherwise. >> + */ >> +static struct spi_nor_erase_region * >> +spi_nor_find_erase_region(const struct spi_nor_erase_map *map, u64 >> +addr) { >> + struct spi_nor_erase_region *region = map->regions; >> + u64 region_start = region->offset & ~SNOR_ERASE_FLAGS_MASK; >> + u64 region_end = region_start + region->size; >> + >> + while (addr < region_start || addr >= region_end) { >> + region = spi_nor_region_next(region); >> + if (!region) >> + return ERR_PTR(-EINVAL); >> + >> + region_start = region->offset & ~SNOR_ERASE_FLAGS_MASK; >> + region_end = region_start + region->size; >> + } >> + >> + return region; >> +} >> + >> +/* >> + * spi_nor_init_erase_cmd() - initialize an erase command >> + * @region: pointer to a structure that describes a SPI NOR erase region >> + * @erase: pointer to a structure that describes a SPI NOR erase type >> + * >> + * Returns the pointer to the allocated erase command, ERR_PTR(-errno) >> + * otherwise. >> + */ >> +static struct spi_nor_erase_command * >> +spi_nor_init_erase_cmd(const struct spi_nor_erase_region *region, >> + const struct spi_nor_erase_type *erase) { >> + struct spi_nor_erase_command *cmd; >> + >> + cmd = kmalloc(sizeof(*cmd), GFP_KERNEL); >> + if (!cmd) >> + return ERR_PTR(-ENOMEM); >> + >> + INIT_LIST_HEAD(&cmd->list); >> + cmd->opcode = erase->opcode; >> + cmd->count = 1; >> + >> + if (region->offset & SNOR_OVERLAID_REGION) >> + cmd->size = region->size; >> + else >> + cmd->size = erase->size; >> + >> + return cmd; >> +} >> + >> +/* >> + * spi_nor_destroy_erase_cmd_list() - destroy erase command list >> + * @erase_list: list of erase commands >> + */ >> +static void spi_nor_destroy_erase_cmd_list(struct list_head >> +*erase_list) { >> + struct spi_nor_erase_command *cmd, *next; >> + >> + list_for_each_entry_safe(cmd, next, erase_list, list) { >> + list_del(&cmd->list); >> + kfree(cmd); >> + } >> +} >> + >> +/* >> + * spi_nor_init_erase_cmd_list() - initialize erase command list >> + * @nor: pointer to a 'struct spi_nor' >> + * @erase_list: list of erase commands to be executed once we validate that >> the >> + * erase can be performed >> + * @addr: offset in the serial flash memory >> + * @len: number of bytes to erase >> + * >> + * Builds the list of best fitted erase commands and verifies if the >> +erase can >> + * be performed. >> + * >> + * Returns 0 on success, -errno otherwise. >> + */ >> +static int spi_nor_init_erase_cmd_list(struct spi_nor *nor, >> + struct list_head *erase_list, >> + u64 addr, u32 len) >> +{ >> + const struct spi_nor_erase_map *map = &nor->erase_map; >> + const struct spi_nor_erase_type *erase, *prev_erase = NULL; >> + struct spi_nor_erase_region *region; >> + struct spi_nor_erase_command *cmd = NULL; >> + u64 region_end; >> + int ret = -EINVAL; >> + >> + region = spi_nor_find_erase_region(map, addr); >> + if (IS_ERR(region)) >> + return PTR_ERR(region); >> + >> + region_end = spi_nor_region_end(region); >> + >> + while (len) { >> + erase = spi_nor_find_best_erase_type(map, region, addr, len); >> + if (!erase) >> + goto destroy_erase_cmd_list; >> + >> + if (prev_erase != erase || >> + region->offset & SNOR_OVERLAID_REGION) { >> + cmd = spi_nor_init_erase_cmd(region, erase); >> + if (IS_ERR(cmd)) { >> + ret = PTR_ERR(cmd); >> + goto destroy_erase_cmd_list; >> + } >> + >> + list_add_tail(&cmd->list, erase_list); >> + } else { >> + cmd->count++; >> + } >> + >> + addr += cmd->size; >> + len -= cmd->size; >> + >> + if (len && addr >= region_end) { >> + region = spi_nor_region_next(region); >> + if (!region) >> + goto destroy_erase_cmd_list; >> + region_end = spi_nor_region_end(region); >> + } >> + >> + prev_erase = erase; >> + } >> + >> + return 0; >> + >> +destroy_erase_cmd_list: >> + spi_nor_destroy_erase_cmd_list(erase_list); >> + return ret; >> +} >> + >> +/* >> + * spi_nor_erase_multi_sectors() - perform a non-uniform erase >> + * @nor: pointer to a 'struct spi_nor' >> + * @addr: offset in the serial flash memory >> + * @len: number of bytes to erase >> + * >> + * Build a list of best fitted erase commands and execute it once we >> + * validate that the erase can be performed. >> + * >> + * Returns 0 on success, -errno otherwise. >> + */ >> +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; >> + >> + ret = spi_nor_init_erase_cmd_list(nor, &erase_list, addr, len); >> + if (ret) >> + return ret; >> + >> + list_for_each_entry_safe(cmd, next, &erase_list, list) { >> + nor->erase_opcode = cmd->opcode; >> + while (cmd->count) { >> + write_enable(nor); >> + >> + ret = spi_nor_erase_sector(nor, addr); >> + if (ret) >> + goto destroy_erase_cmd_list; >> + >> + addr += cmd->size; >> + cmd->count--; >> + >> + ret = spi_nor_wait_till_ready(nor); >> + if (ret) >> + goto destroy_erase_cmd_list; >> + } >> + list_del(&cmd->list); >> + kfree(cmd); >> + } >> + >> + return 0; >> + >> +destroy_erase_cmd_list: >> + spi_nor_destroy_erase_cmd_list(&erase_list); >> + return ret; >> +} >> + >> +/* >> * Erase an address range on the nor chip. The address range may extend >> * one or more erase sectors. Return an error is there is a problem erasing. >> */ >> @@ -512,9 +794,11 @@ static int spi_nor_erase(struct mtd_info *mtd, struct >> erase_info *instr) >> dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr, >> (long long)instr->len); >> >> - div_u64_rem(instr->len, mtd->erasesize, &rem); >> - if (rem) >> - return -EINVAL; >> + if (spi_nor_has_uniform_erase(nor)) { >> + div_u64_rem(instr->len, mtd->erasesize, &rem); >> + if (rem) >> + return -EINVAL; >> + } >> >> addr = instr->addr; >> len = instr->len; >> @@ -553,7 +837,7 @@ static int spi_nor_erase(struct mtd_info *mtd, struct >> erase_info *instr) >> */ >> >> /* "sector"-at-a-time erase */ >> - } else { >> + } else if (spi_nor_has_uniform_erase(nor)) { >> while (len) { >> write_enable(nor); >> >> @@ -568,6 +852,12 @@ static int spi_nor_erase(struct mtd_info *mtd, struct >> erase_info *instr) >> if (ret) >> goto erase_err; >> } >> + >> + /* erase multiple sectors */ >> + } else { >> + ret = spi_nor_erase_multi_sectors(nor, addr, len); >> + if (ret) >> + goto erase_err; >> } >> >> write_disable(nor); >> @@ -2190,6 +2480,113 @@ static const struct sfdp_bfpt_erase >> sfdp_bfpt_erases[] = { >> >> static int spi_nor_hwcaps_read2cmd(u32 hwcaps); >> >> +/* >> + * spi_nor_set_erase_type() - set a SPI NOR erase type >> + * @erase: pointer to a structure that describes a SPI NOR erase type >> + * @size: the size of the sector/block erased by the erase type >> + * @opcode: the SPI command op code to erase the sector/block >> + */ >> +static void spi_nor_set_erase_type(struct spi_nor_erase_type *erase, >> + u32 size, u8 opcode) >> +{ >> + erase->size = size; >> + erase->opcode = opcode; >> + /* JEDEC JESD216B Standard imposes erase sizes to be power of 2. */ >> + erase->size_shift = ffs(erase->size) - 1; >> + erase->size_mask = (1 << erase->size_shift) - 1; } >> + >> +/* >> + * spi_nor_set_erase_settings_from_bfpt() - set erase type settings from BFPT >> + * @erase: pointer to a structure that describes a SPI NOR erase type >> + * @size: the size of the sector/block erased by the erase type >> + * @opcode: the SPI command op code to erase the sector/block >> + * @i: erase type index as sorted in the Basic Flash Parameter Table >> + * >> + * The supported Erase Types will be sorted at init in ascending order, >> +with >> + * the smallest Erase Type size being the first member in the >> +erase_type array >> + * of the spi_nor_erase_map structure. Save the Erase Type index as >> +sorted in >> + * the Basic Flash Parameter Table since it will be used later on to >> + * synchronize with the supported Erase Types defined in SFDP optional tables. >> + */ >> +static void >> +spi_nor_set_erase_settings_from_bfpt(struct spi_nor_erase_type *erase, >> + u32 size, u8 opcode, u8 i) >> +{ >> + erase->idx = i; >> + spi_nor_set_erase_type(erase, size, opcode); } >> + >> +/* spi_nor_map_cmp_erase_type() - compare the map's erase types by size >> + * @l: member in the left half of the map's erase_type array >> + * @r: member in the right half of the map's erase_type array >> + * >> + * Comparison function used in the sort() call to sort in ascending >> +order the >> + * map's erase types, the smallest erase type size being the first >> +member in the >> + * sorted erase_type array. >> + */ >> +static int spi_nor_map_cmp_erase_type(const void *l, const void *r) { >> + const struct spi_nor_erase_type *left = l, *right = r; >> + >> + return left->size - right->size; >> +} >> + >> +/* >> + * spi_nor_regions_sort_erase_types() - sort erase types in each region >> + * @map: the erase map of the SPI NOR >> + * >> + * Function assumes that the erase types defined in the erase map are >> +already >> + * sorted in ascending order, with the smallest erase type size being >> +the first >> + * member in the erase_type array. It replicates the sort done for the >> +map's >> + * erase types. Each region's erase bitmask will indicate which erase >> +types are >> + * supported from the sorted erase types defined in the erase map. >> + * Sort the all region's erase type at init in order to speed up the >> +process of >> + * finding the best erase command at runtime. >> + */ >> +static void spi_nor_regions_sort_erase_types(struct spi_nor_erase_map >> +*map) { >> + struct spi_nor_erase_region *region = map->regions; >> + struct spi_nor_erase_type *erase_type = map->erase_type; >> + int i; >> + u8 region_erase_mask, sorted_erase_mask; >> + >> + while (region) { >> + region_erase_mask = region->offset & >> SNOR_ERASE_TYPE_MASK; >> + >> + /* Replicate the sort done for the map's erase types. */ >> + sorted_erase_mask = 0; >> + for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) >> + if (erase_type[i].size && >> + region_erase_mask & BIT(erase_type[i].idx)) >> + sorted_erase_mask |= BIT(i); >> + >> + /* Overwrite erase mask. */ >> + region->offset = (region->offset & ~SNOR_ERASE_TYPE_MASK) >> | >> + sorted_erase_mask; >> + >> + region = spi_nor_region_next(region); >> + } >> +} >> + >> +/* >> + *spi_nor_init_uniform_erase_map() - Initialize uniform erase map >> + * @map: the erase map of the SPI NOR >> + * @erase_mask: bitmask encoding erase types that can erase >> the entire >> + * flash memory >> + * @flash_size: the spi nor flash memory size >> + */ >> +static void spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map, >> + u8 erase_mask, u64 flash_size) >> +{ >> + /* Offset 0 with erase_mask and SNOR_LAST_REGION bit set */ >> + map->uniform_region.offset = (erase_mask & >> SNOR_ERASE_TYPE_MASK) | >> + SNOR_LAST_REGION; >> + map->uniform_region.size = flash_size; >> + map->regions = &map->uniform_region; >> + map->uniform_erase_type = erase_mask; >> +} >> + >> /** >> * spi_nor_parse_bfpt() - read and parse the Basic Flash Parameter Table. >> * @nor: pointer to a 'struct spi_nor' >> @@ -2224,12 +2621,14 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, >> const struct sfdp_parameter_header *bfpt_header, >> struct spi_nor_flash_parameter *params) { >> - struct mtd_info *mtd = &nor->mtd; >> + struct spi_nor_erase_map *map = &nor->erase_map; >> + struct spi_nor_erase_type *erase_type = map->erase_type; >> struct sfdp_bfpt bfpt; >> size_t len; >> int i, cmd, err; >> u32 addr; >> u16 half; >> + u8 erase_mask; >> >> /* JESD216 Basic Flash Parameter Table length is at least 9 DWORDs. */ >> if (bfpt_header->length < BFPT_DWORD_MAX_JESD216) @@ -2298,7 >> +2697,12 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, >> spi_nor_set_read_settings_from_bfpt(read, half, rd->proto); >> } >> >> - /* Sector Erase settings. */ >> + /* >> + * Sector Erase settings. Reinitialize the uniform erase map using the >> + * Erase Types defined in the bfpt table. >> + */ >> + erase_mask = 0; >> + memset(&nor->erase_map, 0, sizeof(nor->erase_map)); >> for (i = 0; i < ARRAY_SIZE(sfdp_bfpt_erases); i++) { >> const struct sfdp_bfpt_erase *er = &sfdp_bfpt_erases[i]; >> u32 erasesize; >> @@ -2313,18 +2717,25 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, >> >> erasesize = 1U << erasesize; >> opcode = (half >> 8) & 0xff; >> -#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS >> - if (erasesize == SZ_4K) { >> - nor->erase_opcode = opcode; >> - mtd->erasesize = erasesize; >> - break; >> - } >> -#endif >> - if (!mtd->erasesize || mtd->erasesize < erasesize) { >> - nor->erase_opcode = opcode; >> - mtd->erasesize = erasesize; >> - } >> + erase_mask |= BIT(i); >> + spi_nor_set_erase_settings_from_bfpt(&erase_type[i], >> erasesize, >> + opcode, i); >> } >> + spi_nor_init_uniform_erase_map(map, erase_mask, params->size); >> + /* >> + * Sort all the map's Erase Types in ascending order with the smallest >> + * erase size being the first member in the erase_type array. >> + */ >> + sort(erase_type, SNOR_ERASE_TYPE_MAX, sizeof(erase_type[0]), >> + spi_nor_map_cmp_erase_type, NULL); >> + /* >> + * Sort the erase types in the uniform region in order to update the >> + * uniform_erase_type bitmask. The bitmask will be used later on when >> + * selecting the uniform erase. >> + */ >> + spi_nor_regions_sort_erase_types(map); >> + map->uniform_erase_type = map->uniform_region.offset & >> + SNOR_ERASE_TYPE_MASK; >> >> /* Stop here if not JESD216 rev A or later. */ >> if (bfpt_header->length < BFPT_DWORD_MAX) @@ -2480,6 +2891,9 >> @@ static int spi_nor_init_params(struct spi_nor *nor, >> const struct flash_info *info, >> struct spi_nor_flash_parameter *params) { >> + struct spi_nor_erase_map *map = &nor->erase_map; >> + u8 i, erase_mask; >> + >> /* Set legacy flash parameters as default. */ >> memset(params, 0, sizeof(*params)); >> >> @@ -2519,6 +2933,28 @@ static int spi_nor_init_params(struct spi_nor *nor, >> spi_nor_set_pp_settings(¶ms->page_programs[SNOR_CMD_PP], >> SPINOR_OP_PP, SNOR_PROTO_1_1_1); >> >> + /* >> + * Sector Erase settings. Sort Erase Types in ascending order, with the >> + * smallest erase size starting at BIT(0). >> + */ >> + erase_mask = 0; >> + i = 0; >> + if (info->flags & SECT_4K_PMC) { >> + erase_mask |= BIT(i); >> + spi_nor_set_erase_type(&map->erase_type[i], 4096u, >> + SPINOR_OP_BE_4K_PMC); >> + i++; >> + } else if (info->flags & SECT_4K) { >> + erase_mask |= BIT(i); >> + spi_nor_set_erase_type(&map->erase_type[i], 4096u, >> + SPINOR_OP_BE_4K); >> + i++; >> + } >> + erase_mask |= BIT(i); >> + spi_nor_set_erase_type(&map->erase_type[i], info->sector_size, >> + SPINOR_OP_SE); >> + spi_nor_init_uniform_erase_map(map, erase_mask, params->size); >> + >> /* Select the procedure to set the Quad Enable bit. */ >> if (params->hwcaps.mask & (SNOR_HWCAPS_READ_QUAD | >> SNOR_HWCAPS_PP_QUAD)) { >> @@ -2546,20 +2982,20 @@ static int spi_nor_init_params(struct spi_nor *nor, >> params->quad_enable = info->quad_enable; >> } >> >> - /* Override the parameters with data read from SFDP tables. */ >> - nor->addr_width = 0; >> - nor->mtd.erasesize = 0; >> if ((info->flags & (SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)) && >> !(info->flags & SPI_NOR_SKIP_SFDP)) { >> struct spi_nor_flash_parameter sfdp_params; >> + struct spi_nor_erase_map prev_map; >> >> memcpy(&sfdp_params, params, sizeof(sfdp_params)); >> - if (spi_nor_parse_sfdp(nor, &sfdp_params)) { >> - nor->addr_width = 0; >> - nor->mtd.erasesize = 0; >> - } else { >> + memcpy(&prev_map, &nor->erase_map, sizeof(prev_map)); >> + >> + if (spi_nor_parse_sfdp(nor, &sfdp_params)) >> + /* restore previous erase map */ >> + memcpy(&nor->erase_map, &prev_map, >> + sizeof(nor->erase_map)); >> + else >> memcpy(params, &sfdp_params, sizeof(*params)); >> - } >> } >> >> return 0; >> @@ -2668,29 +3104,103 @@ static int spi_nor_select_pp(struct spi_nor *nor, >> return 0; >> } >> >> -static int spi_nor_select_erase(struct spi_nor *nor, >> - const struct flash_info *info) >> +/* >> + * spi_nor_select_uniform_erase() - select optimum uniform erase type >> + * @map: the erase map of the SPI NOR >> + * @wanted_size: the erase type size to search for. Contains the value of >> + * info->sector_size or of the "small sector" size in case >> + * CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is defined. >> + * >> + * Once the optimum uniform sector erase command is found, disable all >> +the >> + * other. >> + * >> + * Return: pointer to erase type on success, NULL otherwise. >> + */ >> +static const struct spi_nor_erase_type * >> +spi_nor_select_uniform_erase(struct spi_nor_erase_map *map, >> + const u32 wanted_size) >> { >> - struct mtd_info *mtd = &nor->mtd; >> + const struct spi_nor_erase_type *tested_erase, *erase = NULL; >> + int i; >> + u8 uniform_erase_type = map->uniform_erase_type; >> >> - /* Do nothing if already configured from SFDP. */ >> - if (mtd->erasesize) >> - return 0; >> + for (i = SNOR_ERASE_TYPE_MAX - 1; i >= 0; i--) { >> + if (!(uniform_erase_type & BIT(i))) >> + continue; >> + >> + tested_erase = &map->erase_type[i]; >> + >> + /* >> + * If the current erase size is the one, stop here: >> + * we have found the right uniform Sector Erase command. >> + */ >> + if (tested_erase->size == wanted_size) { >> + erase = tested_erase; >> + break; >> + } >> >> + /* >> + * Otherwise, the current erase size is still a valid canditate. >> + * Select the biggest valid candidate. >> + */ >> + if (!erase && tested_erase->size) >> + erase = tested_erase; >> + /* keep iterating to find the wanted_size */ >> + } >> + >> + 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); >> + return erase; >> +} >> + >> +static int spi_nor_select_erase(struct spi_nor *nor, u32 wanted_size) { >> + struct spi_nor_erase_map *map = &nor->erase_map; >> + const struct spi_nor_erase_type *erase = NULL; >> + struct mtd_info *mtd = &nor->mtd; >> + int i; >> + >> + /* >> + * The previous implementation handling Sector Erase commands >> assumed >> + * that the SPI flash memory has an uniform layout then used only one >> + * of the supported erase sizes for all Sector Erase commands. >> + * So to be backward compatible, the new implementation also tries to >> + * manage the SPI flash memory as uniform with a single erase sector >> + * size, when possible. >> + */ >> #ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS >> /* prefer "small sector" erase if possible */ >> - if (info->flags & SECT_4K) { >> - nor->erase_opcode = SPINOR_OP_BE_4K; >> - mtd->erasesize = 4096; >> - } else if (info->flags & SECT_4K_PMC) { >> - nor->erase_opcode = SPINOR_OP_BE_4K_PMC; >> - mtd->erasesize = 4096; >> - } else >> + wanted_size = 4096u; >> #endif >> - { >> - nor->erase_opcode = SPINOR_OP_SE; >> - mtd->erasesize = info->sector_size; >> + >> + if (spi_nor_has_uniform_erase(nor)) { >> + erase = spi_nor_select_uniform_erase(map, wanted_size); >> + if (!erase) >> + return -EINVAL; >> + nor->erase_opcode = erase->opcode; >> + mtd->erasesize = erase->size; >> + return 0; >> } >> + >> + /* >> + * For non-uniform SPI flash memory, set mtd->erasesize to the >> + * maximum erase sector size. No need to set nor->erase_opcode. >> + */ >> + for (i = SNOR_ERASE_TYPE_MAX - 1; i >= 0; i--) { >> + if (map->erase_type[i].size) { >> + erase = &map->erase_type[i]; >> + break; >> + } >> + } >> + >> + if (!erase) >> + return -EINVAL; >> + >> + mtd->erasesize = erase->size; >> return 0; >> } >> >> @@ -2737,7 +3247,7 @@ static int spi_nor_setup(struct spi_nor *nor, const >> struct flash_info *info, >> } >> >> /* Select the Sector Erase command. */ >> - err = spi_nor_select_erase(nor, info); >> + err = spi_nor_select_erase(nor, info->sector_size); >> if (err) { >> dev_err(nor->dev, >> "can't select erase settings supported by both the SPI >> controller and memory.\n"); diff --git a/include/linux/mtd/spi-nor.h >> b/include/linux/mtd/spi-nor.h index 09a10fd..a873a0b 100644 >> --- a/include/linux/mtd/spi-nor.h >> +++ b/include/linux/mtd/spi-nor.h >> @@ -240,6 +240,94 @@ enum spi_nor_option_flags { }; >> >> /** >> + * struct spi_nor_erase_type - Structure to describe a SPI NOR erase type >> + * @size: the size of the sector/block erased by the erase type. >> + * JEDEC JESD216B imposes erase sizes to be a power of 2. >> + * @size_shift: @size is a power of 2, the shift is stored in >> + * @size_shift. >> + * @size_mask: the size mask based on @size_shift. >> + * @opcode: the SPI command op code to erase the sector/block. >> + * @idx: Erase Type index as sorted in the Basic Flash Parameter >> + * Table. It will be used to synchronize the supported >> + * Erase Types with the ones identified in the SFDP >> + * optional tables. >> + */ >> +struct spi_nor_erase_type { >> + u32 size; >> + u32 size_shift; >> + u32 size_mask; >> + u8 opcode; >> + u8 idx; >> +}; >> + >> +/** >> + * struct spi_nor_erase_command - Used for non-uniform erases >> + * The structure is used to describe a list of erase commands to be >> +executed >> + * once we validate that the erase can be performed. The elements in >> +the list >> + * are run-length encoded. >> + * @list: for inclusion into the list of erase commands. >> + * @count: how many times the same erase command should be >> + * consecutively used. >> + * @size: the size of the sector/block erased by the command. >> + * @opcode: the SPI command op code to erase the sector/block. >> + */ >> +struct spi_nor_erase_command { >> + struct list_head list; >> + u32 count; >> + u32 size; >> + u8 opcode; >> +}; >> + >> +/** >> + * struct spi_nor_erase_region - Structure to describe a SPI NOR erase region >> + * @offset: the offset in the data array of erase region start. >> + * LSB bits are used as a bitmask encoding flags to >> + * determine if this region is overlaid, if this region is >> + * the last in the SPI NOR flash memory and to indicate >> + * all the supported erase commands inside this region. >> + * The erase types are sorted in ascending order with the >> + * smallest Erase Type size being at BIT(0). >> + * @size: the size of the region in bytes. >> + */ >> +struct spi_nor_erase_region { >> + u64 offset; >> + u64 size; >> +}; >> + >> +#define SNOR_ERASE_TYPE_MAX 4 >> +#define SNOR_ERASE_TYPE_MASK >> GENMASK_ULL(SNOR_ERASE_TYPE_MAX - 1, 0) >> + >> +#define SNOR_LAST_REGION BIT(4) >> +#define SNOR_OVERLAID_REGION BIT(5) >> + >> +#define SNOR_ERASE_FLAGS_MAX 6 >> +#define SNOR_ERASE_FLAGS_MASK >> GENMASK_ULL(SNOR_ERASE_FLAGS_MAX - 1, 0) >> + >> +/** >> + * struct spi_nor_erase_map - Structure to describe the SPI NOR erase map >> + * @regions: array of erase regions. The regions are consecutive in >> + * address space. Walking through the regions is done >> + * incrementally. >> + * @uniform_region: a pre-allocated erase region for SPI NOR with a uniform >> + * sector size (legacy implementation). >> + * @erase_type: an array of erase types shared by all the regions. >> + * The erase types are sorted in ascending order, with the >> + * smallest Erase Type size being the first member in the >> + * erase_type array. >> + * @uniform_erase_type: bitmask encoding erase types that can erase >> the >> + * entire memory. This member is completed at init by >> + * uniform and non-uniform SPI NOR flash memories if >> they >> + * support at least one erase type that can erase the >> + * entire memory. >> + */ >> +struct spi_nor_erase_map { >> + struct spi_nor_erase_region *regions; >> + struct spi_nor_erase_region uniform_region; >> + struct spi_nor_erase_type erase_type[SNOR_ERASE_TYPE_MAX]; >> + u8 uniform_erase_type; >> +}; >> + >> +/** >> * struct flash_info - Forward declaration of a structure used internally by >> * spi_nor_scan() >> */ >> @@ -263,6 +351,7 @@ struct flash_info; >> * @write_proto: the SPI protocol for write operations >> * @reg_proto the SPI protocol for read_reg/write_reg/erase >> operations >> * @cmd_buf: used by the write_reg >> + * @erase_map: the erase map of the SPI NOR >> * @prepare: [OPTIONAL] do some preparations for the >> * read/write/erase/lock/unlock operations >> * @unprepare: [OPTIONAL] do some post work after the >> @@ -298,6 +387,7 @@ struct spi_nor { >> bool sst_write_second; >> u32 flags; >> u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE]; >> + struct spi_nor_erase_map erase_map; >> >> int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops); >> void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops); @@ - >> 318,6 +408,23 @@ struct spi_nor { >> void *priv; >> }; >> >> +static u64 __maybe_unused >> +spi_nor_region_is_last(const struct spi_nor_erase_region *region) { >> + return region->offset & SNOR_LAST_REGION; } >> + >> +static u64 __maybe_unused >> +spi_nor_region_end(const struct spi_nor_erase_region *region) { >> + return (region->offset & ~SNOR_ERASE_FLAGS_MASK) + region->size; } >> + >> +static bool __maybe_unused spi_nor_has_uniform_erase(const struct >> +spi_nor *nor) { >> + return !!nor->erase_map.uniform_erase_type; >> +} >> + >> static inline void spi_nor_set_flash_node(struct spi_nor *nor, >> struct device_node *np) >> { >> -- >> 2.9.4 >> >> >> ______________________________________________________ >> Linux MTD discussion mailing list >> https://emea01.safelinks.protection.outlook.com/?url=http%3A%2F%2Flists.infr >> adead.org%2Fmailman%2Flistinfo%2Flinux- >> mtd%2F&data=02%7C01%7Cyogeshnarayan.gaur%40nxp.com%7C3c782e5 >> 2b7fd4a8b9af008d617fd5154%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0% >> 7C0%7C636722774108718782&sdata=cSpHUDMi0LDV%2FxAYj6i6piSi3gn% >> 2BDGAMWKoOx3%2F5%2BsU%3D&reserved=0 > ______________________________________________________ Linux MTD discussion mailing list http://lists.infradead.org/mailman/listinfo/linux-mtd/