Hi Tudor,

> -----Original Message-----
> From: Cyrille Pitchen [mailto:cyrille.pitc...@wedev4u.fr]
> Sent: Tuesday, October 16, 2018 10:04 PM
> To: Tudor Ambarus <tudor.amba...@microchip.com>; Yogesh Narayan Gaur
> <yogeshnarayan.g...@nxp.com>; marek.va...@gmail.com;
> dw...@infradead.org; computersforpe...@gmail.com;
> boris.brezil...@bootlin.com; rich...@nod.at
> Cc: linux-kernel@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: Re: [PATCH v3 1/2] mtd: spi-nor: add support to non-uniform SFDP SPI
> NOR flash memories
> 
> Hi Tudor,
> 
> Le 16/10/2018 à 17:14, Tudor Ambarus a écrit :
> > 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.
> >
> 
Actually there is no entry of s25fs512s in current spi-nor.c file.
For my connected flash part, jedec ID read points to s25fl512s. I have asked my 
board team to confirm the name of exact connected flash part.
When I check the data sheet of s25fs512s, it also points to the same Jedec ID 
information.
      { "s25fl512s",  INFO(0x010220, 0x4d00, 256 * 1024, 256, ....}

But as stated earlier, if I skip reading SFDP or read using 1-1-1 protocol then 
read are always correct.
For 1-4-4 protocol read are wrong and on further debugging found that Read code 
of 0x6C is being send as opcode instead of 0xEC.

If I revert this patch, reads are working fine.

--
Regards
Yogesh Gaur

> s25fS512s supports both uniform and non uniform erase options but s25fL512s is
> always uniform. L is an old memory part, S is newer.
>
> Also, the 8th and 9th WORDs of the Basic Flash Parameter Table alone can't 
> tell
> you whether or not the memory part can be non uniform.
> If the memory can be non uniform then the sector erase map table is mandatory,
> hence when the table is missing you know that your memory part is always
> uniform.
> 
> Best regards,
> 
> Cyrille
> 
> > Thanks,
> > ta
> >
> > [1]
> > https://emea01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwww.
> >
> cypress.com%2Ffile%2F177971%2Fdownload&amp;data=02%7C01%7Cyogeshn
> araya
> >
> n.gaur%40nxp.com%7C76e7e1555f4a4cda378008d63385480b%7C686ea1d3bc2
> b4c6f
> >
> a92cd99c5c301635%7C0%7C0%7C636753044876199155&amp;sdata=cioC98EH
> OGlFbg
> > XPhoIIJ72K3JrNUnzA1pYhSB9jDwg%3D&amp;reserved=0
> >
> >>
> >> --
> >> 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%2Fl
> >>> kml.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%2Fli
> >>> sts.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
> >>
> >
> > ______________________________________________________
> > Linux MTD discussion mailing list
> > https://emea01.safelinks.protection.outlook.com/?url=http%3A%2F%2Flist
> > s.infradead.org%2Fmailman%2Flistinfo%2Flinux-
> mtd%2F&amp;data=02%7C01%7
> >
> Cyogeshnarayan.gaur%40nxp.com%7C76e7e1555f4a4cda378008d63385480b%
> 7C686
> >
> ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C636753044876199155&amp;s
> data=0
> >
> vdEcONHlufYQW%2BD7K6lVaPByXMuDH5YAyx%2FE%2FC3eno%3D&amp;reserv
> ed=0
> >

Reply via email to