lkml.org 
[lkml]   [2018]   [Oct]   [16]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    /
    From
    SubjectRE: [PATCH v3 1/2] mtd: spi-nor: add support to non-uniform SFDP SPI NOR flash memories
    Date
    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-bounces@lists.infradead.org] On Behalf Of
    > Tudor Ambarus
    > Sent: Tuesday, September 11, 2018 9:10 PM
    > To: marek.vasut@gmail.com; dwmw2@infradead.org;
    > computersforpeace@gmail.com; boris.brezillon@bootlin.com; richard@nod.at
    > Cc: Tudor Ambarus <tudor.ambarus@microchip.com>; linux-
    > kernel@vger.kernel.org; nicolas.ferre@microchip.com;
    > cyrille.pitchen@microchip.com; linux-mtd@lists.infradead.org; linux-arm-
    > kernel@lists.infradead.org; Cristian.Birsan@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.birsan@microchip.com>
    > Signed-off-by: Tudor Ambarus <tudor.ambarus@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

    \
     
     \ /
      Last update: 2018-10-16 11:53    [W:4.691 / U:0.028 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site