Skip to content

Commit d2de66f

Browse files
committed
mtd: spi-nor: parse SFDP Sector Map Parameter Table
Add support for the SFDP (JESD216B) Sector Map Parameter Table. This table is optional, but when available, we parse it to identify the location and size of sectors within the main data array of the flash memory device and to identify which Erase Types are supported by each sector. Signed-off-by: Tudor Ambarus <tudor.ambarus@microchip.com>
1 parent 79ac594 commit d2de66f

2 files changed

Lines changed: 264 additions & 16 deletions

File tree

drivers/mtd/spi-nor/spi-nor.c

Lines changed: 253 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2000,6 +2000,35 @@ spi_nor_set_pp_settings(struct spi_nor_pp_command *pp,
20002000
* Serial Flash Discoverable Parameters (SFDP) parsing.
20012001
*/
20022002

2003+
/**
2004+
* spi_nor_read_raw() - raw read of serial flash memory. read_opcode,
2005+
* addr_width and read_dummy members of the struct spi_nor should be previously
2006+
* set.
2007+
* @nor: pointer to a 'struct spi_nor'
2008+
* @addr: offset in the serial flash memory
2009+
* @len: number of bytes to read
2010+
* @buf: buffer where the data is copied into
2011+
*
2012+
* Return: 0 on success, -errno otherwise.
2013+
*/
2014+
static int spi_nor_read_raw(struct spi_nor *nor, u32 addr, size_t len, u8 *buf)
2015+
{
2016+
int ret;
2017+
2018+
while (len) {
2019+
ret = nor->read(nor, addr, len, buf);
2020+
if (!ret || ret > len)
2021+
return -EIO;
2022+
if (ret < 0)
2023+
return ret;
2024+
2025+
buf += ret;
2026+
addr += ret;
2027+
len -= ret;
2028+
}
2029+
return 0;
2030+
}
2031+
20032032
/**
20042033
* spi_nor_read_sfdp() - read Serial Flash Discoverable Parameters.
20052034
* @nor: pointer to a 'struct spi_nor'
@@ -2027,22 +2056,8 @@ static int spi_nor_read_sfdp(struct spi_nor *nor, u32 addr,
20272056
nor->addr_width = 3;
20282057
nor->read_dummy = 8;
20292058

2030-
while (len) {
2031-
ret = nor->read(nor, addr, len, (u8 *)buf);
2032-
if (!ret || ret > len) {
2033-
ret = -EIO;
2034-
goto read_err;
2035-
}
2036-
if (ret < 0)
2037-
goto read_err;
2059+
ret = spi_nor_read_raw(nor, addr, len, buf);
20382060

2039-
buf += ret;
2040-
addr += ret;
2041-
len -= ret;
2042-
}
2043-
ret = 0;
2044-
2045-
read_err:
20462061
nor->read_opcode = read_opcode;
20472062
nor->addr_width = addr_width;
20482063
nor->read_dummy = read_dummy;
@@ -2562,6 +2577,228 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
25622577
return 0;
25632578
}
25642579

2580+
#define SMPT_CMD_ADDRESS_LEN_MASK GENMASK(23, 22)
2581+
#define SMPT_CMD_ADDRESS_LEN_0 (0x0UL << 22)
2582+
#define SMPT_CMD_ADDRESS_LEN_3 (0x1UL << 22)
2583+
#define SMPT_CMD_ADDRESS_LEN_4 (0x2UL << 22)
2584+
#define SMPT_CMD_ADDRESS_LEN_USE_CURRENT (0x3UL << 22)
2585+
2586+
#define SMPT_CMD_READ_DUMMY_MASK GENMASK(19, 16)
2587+
#define SMPT_CMD_READ_DUMMY_SHIFT 16
2588+
#define SMPT_CMD_READ_DUMMY(_cmd) \
2589+
((_cmd & SMPT_CMD_READ_DUMMY_MASK) >> SMPT_CMD_READ_DUMMY_SHIFT)
2590+
#define SMPT_CMD_READ_DUMMY_IS_VARIABLE 0xfUL
2591+
2592+
#define SMPT_CMD_READ_DATA_MASK GENMASK(31, 24)
2593+
#define SMPT_CMD_READ_DATA_SHIFT 24
2594+
#define SMPT_CMD_READ_DATA(_cmd) \
2595+
((_cmd & SMPT_CMD_READ_DATA_MASK) >> SMPT_CMD_READ_DATA_SHIFT)
2596+
2597+
#define SMPT_CMD_OPCODE_MASK GENMASK(15, 8)
2598+
#define SMPT_CMD_OPCODE_SHIFT 8
2599+
#define SMPT_CMD_OPCODE(_cmd) \
2600+
((_cmd & SMPT_CMD_OPCODE_MASK) >> SMPT_CMD_OPCODE_SHIFT)
2601+
2602+
#define SMPT_MAP_REGION_COUNT_MASK GENMASK(23, 16)
2603+
#define SMPT_MAP_REGION_COUNT_SHIFT 16
2604+
#define SMPT_MAP_REGION_COUNT(_header) \
2605+
(((_header & SMPT_MAP_REGION_COUNT_MASK) >> \
2606+
SMPT_MAP_REGION_COUNT_SHIFT) + 1)
2607+
2608+
#define SMPT_MAP_ID_MASK GENMASK(15, 8)
2609+
#define SMPT_MAP_ID_SHIFT 8
2610+
#define SMPT_MAP_ID(_header) ((_header & SMPT_MAP_ID_MASK) >> SMPT_MAP_ID_SHIFT)
2611+
2612+
#define SMPT_MAP_REGION_SIZE_MASK GENMASK(31, 8)
2613+
#define SMPT_MAP_REGION_SIZE_SHIFT 8
2614+
#define SMPT_MAP_REGION_SIZE(_region) \
2615+
((((_region & SMPT_MAP_REGION_SIZE_MASK) >> \
2616+
SMPT_MAP_REGION_SIZE_SHIFT) + 1) * 256)
2617+
2618+
#define SMPT_MAP_REGION_ERASE_TYPE_MASK GENMASK(3, 0)
2619+
#define SMPT_MAP_REGION_ERASE_TYPE(_region) \
2620+
(_region & SMPT_MAP_REGION_ERASE_TYPE_MASK)
2621+
2622+
#define SMPT_DESC_TYPE_MAP BIT(1)
2623+
#define SMPT_DESC_END BIT(0)
2624+
2625+
static u8 spi_nor_smpt_addr_width(const struct spi_nor *nor, const u32 settings)
2626+
{
2627+
switch (settings & SMPT_CMD_ADDRESS_LEN_MASK) {
2628+
case SMPT_CMD_ADDRESS_LEN_0:
2629+
return 0;
2630+
case SMPT_CMD_ADDRESS_LEN_3:
2631+
return 3;
2632+
case SMPT_CMD_ADDRESS_LEN_4:
2633+
return 4;
2634+
case SMPT_CMD_ADDRESS_LEN_USE_CURRENT:
2635+
/* fall through */
2636+
default:
2637+
return nor->addr_width;
2638+
}
2639+
}
2640+
2641+
static u8 spi_nor_smpt_read_dummy(const struct spi_nor *nor, const u32 settings)
2642+
{
2643+
u8 read_dummy = SMPT_CMD_READ_DUMMY(settings);
2644+
2645+
if (read_dummy == SMPT_CMD_READ_DUMMY_IS_VARIABLE)
2646+
return nor->read_dummy;
2647+
return read_dummy;
2648+
}
2649+
2650+
static const u32 *spi_nor_get_map_in_use(struct spi_nor *nor, const u32 *smpt)
2651+
{
2652+
const u32 *ret = NULL;
2653+
u32 i, addr;
2654+
int err;
2655+
u8 addr_width, read_opcode, read_dummy;
2656+
u8 read_data_mask, data_byte, map_id;
2657+
2658+
addr_width = nor->addr_width;
2659+
read_dummy = nor->read_dummy;
2660+
read_opcode = nor->read_opcode;
2661+
2662+
map_id = 0;
2663+
i = 0;
2664+
/* Determine if there are any optional Detection Command Descriptors */
2665+
while (!(smpt[i] & SMPT_DESC_TYPE_MAP)) {
2666+
read_data_mask = SMPT_CMD_READ_DATA(smpt[i]);
2667+
nor->addr_width = spi_nor_smpt_addr_width(nor, smpt[i]);
2668+
nor->read_dummy = spi_nor_smpt_read_dummy(nor, smpt[i]);
2669+
nor->read_opcode = SMPT_CMD_OPCODE(smpt[i]);
2670+
addr = smpt[i + 1];
2671+
2672+
err = spi_nor_read_raw(nor, addr, 1, &data_byte);
2673+
if (err)
2674+
goto out;
2675+
2676+
/*
2677+
* Build an index value that is used to select the Sector Map
2678+
* Configuration that is currently in use.
2679+
*/
2680+
map_id = map_id << 1 | (!(data_byte & read_data_mask) ? 0 : 1);
2681+
i = i + 2;
2682+
}
2683+
2684+
/* Find the matching configuration map */
2685+
while (SMPT_MAP_ID(smpt[i]) != map_id) {
2686+
if (smpt[i] & SMPT_DESC_END)
2687+
goto out;
2688+
/* increment the table index to the next map */
2689+
i += SMPT_MAP_REGION_COUNT(smpt[i]) + 1;
2690+
}
2691+
2692+
ret = smpt + i;
2693+
/* fall through */
2694+
out:
2695+
nor->addr_width = addr_width;
2696+
nor->read_dummy = read_dummy;
2697+
nor->read_opcode = read_opcode;
2698+
return ret;
2699+
}
2700+
2701+
static void
2702+
spi_nor_region_check_overlay(struct spi_nor_erase_region *region,
2703+
const struct spi_nor_erase_type *erase,
2704+
const u8 erase_type)
2705+
{
2706+
int i;
2707+
2708+
for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) {
2709+
if (!(erase_type & BIT(i)))
2710+
continue;
2711+
if (region->size & erase[i].size_mask) {
2712+
spi_nor_region_mark_overlay(region);
2713+
return;
2714+
}
2715+
}
2716+
}
2717+
2718+
static int spi_nor_init_non_uniform_erase_map(struct spi_nor *nor,
2719+
const u32 *smpt)
2720+
{
2721+
struct spi_nor_erase_map *map = &nor->erase_map;
2722+
const struct spi_nor_erase_type *erase = map->erase_type;
2723+
struct spi_nor_erase_region *region;
2724+
u64 offset;
2725+
u32 region_count;
2726+
int i, j;
2727+
u8 erase_type;
2728+
2729+
region_count = SMPT_MAP_REGION_COUNT(*smpt);
2730+
region = devm_kcalloc(nor->dev, region_count, sizeof(*region),
2731+
GFP_KERNEL);
2732+
if (!region)
2733+
return -ENOMEM;
2734+
map->regions = region;
2735+
2736+
map->uniform_erase_type = 0xff;
2737+
offset = 0;
2738+
for (i = 0; i < region_count; i++) {
2739+
j = i + 1; /* index for the region dword */
2740+
region[i].size = SMPT_MAP_REGION_SIZE(smpt[j]);
2741+
erase_type = SMPT_MAP_REGION_ERASE_TYPE(smpt[j]);
2742+
region[i].offset = offset | erase_type;
2743+
2744+
spi_nor_region_check_overlay(&region[i], erase, erase_type);
2745+
2746+
/*
2747+
* Save the erase types that are supported in all regions and
2748+
* can erase the entire flash memory.
2749+
*/
2750+
map->uniform_erase_type &= erase_type;
2751+
2752+
offset = (region[i].offset & ~SNOR_ERASE_FLAGS_MASK) +
2753+
region[i].size;
2754+
}
2755+
2756+
spi_nor_region_mark_end(&region[i - 1]);
2757+
2758+
return 0;
2759+
}
2760+
2761+
static int spi_nor_parse_smpt(struct spi_nor *nor,
2762+
const struct sfdp_parameter_header *smpt_header)
2763+
{
2764+
const u32 *sector_map;
2765+
u32 *smpt;
2766+
size_t len;
2767+
u32 addr;
2768+
int i, ret;
2769+
2770+
/* Read the Sector Map Parameter Table. */
2771+
len = smpt_header->length * sizeof(*smpt);
2772+
smpt = kzalloc(len, GFP_KERNEL);
2773+
if (!smpt)
2774+
return -ENOMEM;
2775+
2776+
addr = SFDP_PARAM_HEADER_PTP(smpt_header);
2777+
ret = spi_nor_read_sfdp(nor, addr, len, smpt);
2778+
if (ret)
2779+
goto out;
2780+
2781+
/* Fix endianness of the SMPT DWORDs. */
2782+
for (i = 0; i < smpt_header->length; i++)
2783+
smpt[i] = le32_to_cpu(smpt[i]);
2784+
2785+
sector_map = spi_nor_get_map_in_use(nor, smpt);
2786+
if (!sector_map) {
2787+
ret = -EINVAL;
2788+
goto out;
2789+
}
2790+
2791+
ret = spi_nor_init_non_uniform_erase_map(nor, sector_map);
2792+
if (ret)
2793+
goto out;
2794+
2795+
spi_nor_regions_sort_erase_types(&nor->erase_map);
2796+
/* fall through */
2797+
out:
2798+
kfree(smpt);
2799+
return ret;
2800+
}
2801+
25652802
/**
25662803
* spi_nor_parse_sfdp() - parse the Serial Flash Discoverable Parameters.
25672804
* @nor: pointer to a 'struct spi_nor'
@@ -2657,7 +2894,7 @@ static int spi_nor_parse_sfdp(struct spi_nor *nor,
26572894

26582895
switch (SFDP_PARAM_HEADER_ID(param_header)) {
26592896
case SFDP_SECTOR_MAP_ID:
2660-
dev_info(dev, "non-uniform erase sector maps are not supported yet.\n");
2897+
err = spi_nor_parse_smpt(nor, param_header);
26612898
break;
26622899

26632900
default:

include/linux/mtd/spi-nor.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,17 @@ static inline u64 spi_nor_region_end(const struct spi_nor_erase_region *region)
414414
return (region->offset & ~SNOR_ERASE_FLAGS_MASK) + region->size;
415415
}
416416

417+
static inline void spi_nor_region_mark_end(struct spi_nor_erase_region *region)
418+
{
419+
region->offset |= SNOR_LAST_REGION;
420+
}
421+
422+
static inline void
423+
spi_nor_region_mark_overlay(struct spi_nor_erase_region *region)
424+
{
425+
region->offset |= SNOR_OVERLAID_REGION;
426+
}
427+
417428
static inline bool spi_nor_has_uniform_erase(const struct spi_nor *nor)
418429
{
419430
return !!nor->erase_map.uniform_erase_type;

0 commit comments

Comments
 (0)