|
@@ -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>
|
|
@@ -260,6 +261,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. */
|
|
@@ -497,6 +510,277 @@ static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
|
|
|
return nor->write_reg(nor, nor->erase_opcode, buf, nor->addr_width);
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * 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)
|
|
|
+ *
|
|
|
+ * Return: the result of the division
|
|
|
+ */
|
|
|
+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
|
|
|
+ *
|
|
|
+ * Return: 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
|
|
|
+ *
|
|
|
+ * Return: 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
|
|
|
+ *
|
|
|
+ * Return: a 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
|
|
|
+ *
|
|
|
+ * Return: 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.
|
|
|
+ *
|
|
|
+ * Return: 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.
|
|
|
+ *
|
|
|
+ * Return: 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.
|
|
@@ -511,9 +795,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;
|
|
@@ -552,7 +838,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);
|
|
|
|
|
@@ -567,6 +853,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);
|
|
@@ -2165,6 +2457,116 @@ 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.
|
|
|
+ *
|
|
|
+ * Return: the result of @l->size - @r->size
|
|
|
+ */
|
|
|
+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'
|
|
@@ -2199,12 +2601,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)
|
|
@@ -2273,7 +2677,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;
|
|
@@ -2288,18 +2697,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)
|
|
@@ -2455,6 +2871,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));
|
|
|
|
|
@@ -2494,6 +2913,28 @@ static int spi_nor_init_params(struct spi_nor *nor,
|
|
|
spi_nor_set_pp_settings(¶ms->page_programs[SNOR_CMD_PP],
|
|
|
SPINOR_OP_PP, SNOR_PROTO_1_1_1);
|
|
|
|
|
|
+ /*
|
|
|
+ * Sector Erase settings. Sort Erase Types in ascending order, with the
|
|
|
+ * smallest erase size starting at BIT(0).
|
|
|
+ */
|
|
|
+ erase_mask = 0;
|
|
|
+ i = 0;
|
|
|
+ if (info->flags & SECT_4K_PMC) {
|
|
|
+ erase_mask |= BIT(i);
|
|
|
+ spi_nor_set_erase_type(&map->erase_type[i], 4096u,
|
|
|
+ SPINOR_OP_BE_4K_PMC);
|
|
|
+ i++;
|
|
|
+ } else if (info->flags & SECT_4K) {
|
|
|
+ erase_mask |= BIT(i);
|
|
|
+ spi_nor_set_erase_type(&map->erase_type[i], 4096u,
|
|
|
+ SPINOR_OP_BE_4K);
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ erase_mask |= BIT(i);
|
|
|
+ spi_nor_set_erase_type(&map->erase_type[i], info->sector_size,
|
|
|
+ SPINOR_OP_SE);
|
|
|
+ spi_nor_init_uniform_erase_map(map, erase_mask, params->size);
|
|
|
+
|
|
|
/* Select the procedure to set the Quad Enable bit. */
|
|
|
if (params->hwcaps.mask & (SNOR_HWCAPS_READ_QUAD |
|
|
|
SNOR_HWCAPS_PP_QUAD)) {
|
|
@@ -2521,20 +2962,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;
|
|
@@ -2643,29 +3084,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;
|
|
|
}
|
|
|
|
|
@@ -2712,7 +3227,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");
|