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?

--
Regards
Yogesh Gaur

> -----Original Message-----
> From: linux-mtd [mailto:linux-mtd-boun...@lists.infradead.org] On Behalf Of
> Tudor Ambarus
> Sent: Tuesday, September 11, 2018 9:10 PM
> To: marek.va...@gmail.com; dw...@infradead.org;
> computersforpe...@gmail.com; boris.brezil...@bootlin.com; rich...@nod.at
> Cc: Tudor Ambarus <tudor.amba...@microchip.com>; linux-
> ker...@vger.kernel.org; nicolas.fe...@microchip.com;
> cyrille.pitc...@microchip.com; linux-...@lists.infradead.org; linux-arm-
> ker...@lists.infradead.org; cristian.bir...@microchip.com
> 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&amp;data=02%7C01%7Cyogeshnarayan.
> gaur%40nxp.com%7C3c782e52b7fd4a8b9af008d617fd5154%7C686ea1d3bc2b4
> c6fa92cd99c5c301635%7C0%7C0%7C636722774108718782&amp;sdata=szyc%
> 2FTumG6eYAmBd0oW3IL7v1yLh9E1SAZqL%2BCWczOA%3D&amp;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.bir...@microchip.com>
> Signed-off-by: Tudor Ambarus <tudor.amba...@microchip.com>
> ---
>  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(&params->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&amp;data=02%7C01%7Cyogeshnarayan.gaur%40nxp.com%7C3c782e5
> 2b7fd4a8b9af008d617fd5154%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%
> 7C0%7C636722774108718782&amp;sdata=cSpHUDMi0LDV%2FxAYj6i6piSi3gn%
> 2BDGAMWKoOx3%2F5%2BsU%3D&amp;reserved=0

Reply via email to