Skip to content

Commit 1f9ce7e

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> Reviewed-by: Marek Vasut <marek.vasut@gmail.com> Signed-off-by: Boris Brezillon <boris.brezillon@bootlin.com> (cherry picked from commit b038e8e) Signed-off-by: Tudor Ambarus <tudor.ambarus@microchip.com>
1 parent beee827 commit 1f9ce7e

2 files changed

Lines changed: 315 additions & 16 deletions

File tree

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

Lines changed: 303 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2096,6 +2096,36 @@ spi_nor_set_pp_settings(struct spi_nor_pp_command *pp,
20962096
* Serial Flash Discoverable Parameters (SFDP) parsing.
20972097
*/
20982098

2099+
/**
2100+
* spi_nor_read_raw() - raw read of serial flash memory. read_opcode,
2101+
* addr_width and read_dummy members of the struct spi_nor
2102+
* should be previously
2103+
* set.
2104+
* @nor: pointer to a 'struct spi_nor'
2105+
* @addr: offset in the serial flash memory
2106+
* @len: number of bytes to read
2107+
* @buf: buffer where the data is copied into
2108+
*
2109+
* Return: 0 on success, -errno otherwise.
2110+
*/
2111+
static int spi_nor_read_raw(struct spi_nor *nor, u32 addr, size_t len, u8 *buf)
2112+
{
2113+
int ret;
2114+
2115+
while (len) {
2116+
ret = nor->read(nor, addr, len, buf);
2117+
if (!ret || ret > len)
2118+
return -EIO;
2119+
if (ret < 0)
2120+
return ret;
2121+
2122+
buf += ret;
2123+
addr += ret;
2124+
len -= ret;
2125+
}
2126+
return 0;
2127+
}
2128+
20992129
/**
21002130
* spi_nor_read_sfdp() - read Serial Flash Discoverable Parameters.
21012131
* @nor: pointer to a 'struct spi_nor'
@@ -2123,22 +2153,8 @@ static int spi_nor_read_sfdp(struct spi_nor *nor, u32 addr,
21232153
nor->addr_width = 3;
21242154
nor->read_dummy = 8;
21252155

2126-
while (len) {
2127-
ret = nor->read(nor, addr, len, (u8 *)buf);
2128-
if (!ret || ret > len) {
2129-
ret = -EIO;
2130-
goto read_err;
2131-
}
2132-
if (ret < 0)
2133-
goto read_err;
2134-
2135-
buf += ret;
2136-
addr += ret;
2137-
len -= ret;
2138-
}
2139-
ret = 0;
2156+
ret = spi_nor_read_raw(nor, addr, len, buf);
21402157

2141-
read_err:
21422158
nor->read_opcode = read_opcode;
21432159
nor->addr_width = addr_width;
21442160
nor->read_dummy = read_dummy;
@@ -2698,6 +2714,277 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
26982714
return 0;
26992715
}
27002716

2717+
#define SMPT_CMD_ADDRESS_LEN_MASK GENMASK(23, 22)
2718+
#define SMPT_CMD_ADDRESS_LEN_0 (0x0UL << 22)
2719+
#define SMPT_CMD_ADDRESS_LEN_3 (0x1UL << 22)
2720+
#define SMPT_CMD_ADDRESS_LEN_4 (0x2UL << 22)
2721+
#define SMPT_CMD_ADDRESS_LEN_USE_CURRENT (0x3UL << 22)
2722+
2723+
#define SMPT_CMD_READ_DUMMY_MASK GENMASK(19, 16)
2724+
#define SMPT_CMD_READ_DUMMY_SHIFT 16
2725+
#define SMPT_CMD_READ_DUMMY(_cmd) \
2726+
(((_cmd) & SMPT_CMD_READ_DUMMY_MASK) >> SMPT_CMD_READ_DUMMY_SHIFT)
2727+
#define SMPT_CMD_READ_DUMMY_IS_VARIABLE 0xfUL
2728+
2729+
#define SMPT_CMD_READ_DATA_MASK GENMASK(31, 24)
2730+
#define SMPT_CMD_READ_DATA_SHIFT 24
2731+
#define SMPT_CMD_READ_DATA(_cmd) \
2732+
(((_cmd) & SMPT_CMD_READ_DATA_MASK) >> SMPT_CMD_READ_DATA_SHIFT)
2733+
2734+
#define SMPT_CMD_OPCODE_MASK GENMASK(15, 8)
2735+
#define SMPT_CMD_OPCODE_SHIFT 8
2736+
#define SMPT_CMD_OPCODE(_cmd) \
2737+
(((_cmd) & SMPT_CMD_OPCODE_MASK) >> SMPT_CMD_OPCODE_SHIFT)
2738+
2739+
#define SMPT_MAP_REGION_COUNT_MASK GENMASK(23, 16)
2740+
#define SMPT_MAP_REGION_COUNT_SHIFT 16
2741+
#define SMPT_MAP_REGION_COUNT(_header) \
2742+
((((_header) & SMPT_MAP_REGION_COUNT_MASK) >> \
2743+
SMPT_MAP_REGION_COUNT_SHIFT) + 1)
2744+
2745+
#define SMPT_MAP_ID_MASK GENMASK(15, 8)
2746+
#define SMPT_MAP_ID_SHIFT 8
2747+
#define SMPT_MAP_ID(_header) \
2748+
(((_header) & SMPT_MAP_ID_MASK) >> SMPT_MAP_ID_SHIFT)
2749+
2750+
#define SMPT_MAP_REGION_SIZE_MASK GENMASK(31, 8)
2751+
#define SMPT_MAP_REGION_SIZE_SHIFT 8
2752+
#define SMPT_MAP_REGION_SIZE(_region) \
2753+
(((((_region) & SMPT_MAP_REGION_SIZE_MASK) >> \
2754+
SMPT_MAP_REGION_SIZE_SHIFT) + 1) * 256)
2755+
2756+
#define SMPT_MAP_REGION_ERASE_TYPE_MASK GENMASK(3, 0)
2757+
#define SMPT_MAP_REGION_ERASE_TYPE(_region) \
2758+
((_region) & SMPT_MAP_REGION_ERASE_TYPE_MASK)
2759+
2760+
#define SMPT_DESC_TYPE_MAP BIT(1)
2761+
#define SMPT_DESC_END BIT(0)
2762+
2763+
/**
2764+
* spi_nor_smpt_addr_width() - return the address width used in the
2765+
* configuration detection command.
2766+
* @nor: pointer to a 'struct spi_nor'
2767+
* @settings: configuration detection command descriptor, dword1
2768+
*/
2769+
static u8 spi_nor_smpt_addr_width(const struct spi_nor *nor, const u32 settings)
2770+
{
2771+
switch (settings & SMPT_CMD_ADDRESS_LEN_MASK) {
2772+
case SMPT_CMD_ADDRESS_LEN_0:
2773+
return 0;
2774+
case SMPT_CMD_ADDRESS_LEN_3:
2775+
return 3;
2776+
case SMPT_CMD_ADDRESS_LEN_4:
2777+
return 4;
2778+
case SMPT_CMD_ADDRESS_LEN_USE_CURRENT:
2779+
/* fall through */
2780+
default:
2781+
return nor->addr_width;
2782+
}
2783+
}
2784+
2785+
/**
2786+
* spi_nor_smpt_read_dummy() - return the configuration detection command read
2787+
* latency, in clock cycles.
2788+
* @nor: pointer to a 'struct spi_nor'
2789+
* @settings: configuration detection command descriptor, dword1
2790+
*
2791+
* Return: the number of dummy cycles for an SMPT read
2792+
*/
2793+
static u8 spi_nor_smpt_read_dummy(const struct spi_nor *nor, const u32 settings)
2794+
{
2795+
u8 read_dummy = SMPT_CMD_READ_DUMMY(settings);
2796+
2797+
if (read_dummy == SMPT_CMD_READ_DUMMY_IS_VARIABLE)
2798+
return nor->read_dummy;
2799+
return read_dummy;
2800+
}
2801+
2802+
/**
2803+
* spi_nor_get_map_in_use() - get the configuration map in use
2804+
* @nor: pointer to a 'struct spi_nor'
2805+
* @smpt: pointer to the sector map parameter table
2806+
*/
2807+
static const u32 *spi_nor_get_map_in_use(struct spi_nor *nor, const u32 *smpt)
2808+
{
2809+
const u32 *ret = NULL;
2810+
u32 i, addr;
2811+
int err;
2812+
u8 addr_width, read_opcode, read_dummy;
2813+
u8 read_data_mask, data_byte, map_id;
2814+
2815+
addr_width = nor->addr_width;
2816+
read_dummy = nor->read_dummy;
2817+
read_opcode = nor->read_opcode;
2818+
2819+
map_id = 0;
2820+
i = 0;
2821+
/* Determine if there are any optional Detection Command Descriptors */
2822+
while (!(smpt[i] & SMPT_DESC_TYPE_MAP)) {
2823+
read_data_mask = SMPT_CMD_READ_DATA(smpt[i]);
2824+
nor->addr_width = spi_nor_smpt_addr_width(nor, smpt[i]);
2825+
nor->read_dummy = spi_nor_smpt_read_dummy(nor, smpt[i]);
2826+
nor->read_opcode = SMPT_CMD_OPCODE(smpt[i]);
2827+
addr = smpt[i + 1];
2828+
2829+
err = spi_nor_read_raw(nor, addr, 1, &data_byte);
2830+
if (err)
2831+
goto out;
2832+
2833+
/*
2834+
* Build an index value that is used to select the Sector Map
2835+
* Configuration that is currently in use.
2836+
*/
2837+
map_id = map_id << 1 | !!(data_byte & read_data_mask);
2838+
i = i + 2;
2839+
}
2840+
2841+
/* Find the matching configuration map */
2842+
while (SMPT_MAP_ID(smpt[i]) != map_id) {
2843+
if (smpt[i] & SMPT_DESC_END)
2844+
goto out;
2845+
/* increment the table index to the next map */
2846+
i += SMPT_MAP_REGION_COUNT(smpt[i]) + 1;
2847+
}
2848+
2849+
ret = smpt + i;
2850+
/* fall through */
2851+
out:
2852+
nor->addr_width = addr_width;
2853+
nor->read_dummy = read_dummy;
2854+
nor->read_opcode = read_opcode;
2855+
return ret;
2856+
}
2857+
2858+
/**
2859+
* spi_nor_region_check_overlay() - set overlay bit when the region is overlaid
2860+
* @region: pointer to a structure that describes a SPI NOR erase region
2861+
* @erase: pointer to a structure that describes a SPI NOR erase type
2862+
* @erase_type: erase type bitmask
2863+
*/
2864+
static void
2865+
spi_nor_region_check_overlay(struct spi_nor_erase_region *region,
2866+
const struct spi_nor_erase_type *erase,
2867+
const u8 erase_type)
2868+
{
2869+
int i;
2870+
2871+
for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) {
2872+
if (!(erase_type & BIT(i)))
2873+
continue;
2874+
if (region->size & erase[i].size_mask) {
2875+
spi_nor_region_mark_overlay(region);
2876+
return;
2877+
}
2878+
}
2879+
}
2880+
2881+
/**
2882+
* spi_nor_init_non_uniform_erase_map() - initialize the non-uniform erase map
2883+
* @nor: pointer to a 'struct spi_nor'
2884+
* @smpt: pointer to the sector map parameter table
2885+
*
2886+
* Return: 0 on success, -errno otherwise.
2887+
*/
2888+
static int spi_nor_init_non_uniform_erase_map(struct spi_nor *nor,
2889+
const u32 *smpt)
2890+
{
2891+
struct spi_nor_erase_map *map = &nor->erase_map;
2892+
const struct spi_nor_erase_type *erase = map->erase_type;
2893+
struct spi_nor_erase_region *region;
2894+
u64 offset;
2895+
u32 region_count;
2896+
int i, j;
2897+
u8 erase_type;
2898+
2899+
region_count = SMPT_MAP_REGION_COUNT(*smpt);
2900+
/*
2901+
* The regions will be freed when the driver detaches from the
2902+
* device.
2903+
*/
2904+
region = devm_kcalloc(nor->dev, region_count, sizeof(*region),
2905+
GFP_KERNEL);
2906+
if (!region)
2907+
return -ENOMEM;
2908+
map->regions = region;
2909+
2910+
map->uniform_erase_type = 0xff;
2911+
offset = 0;
2912+
/* Populate regions. */
2913+
for (i = 0; i < region_count; i++) {
2914+
j = i + 1; /* index for the region dword */
2915+
region[i].size = SMPT_MAP_REGION_SIZE(smpt[j]);
2916+
erase_type = SMPT_MAP_REGION_ERASE_TYPE(smpt[j]);
2917+
region[i].offset = offset | erase_type;
2918+
2919+
spi_nor_region_check_overlay(&region[i], erase, erase_type);
2920+
2921+
/*
2922+
* Save the erase types that are supported in all regions and
2923+
* can erase the entire flash memory.
2924+
*/
2925+
map->uniform_erase_type &= erase_type;
2926+
2927+
offset = (region[i].offset & ~SNOR_ERASE_FLAGS_MASK) +
2928+
region[i].size;
2929+
}
2930+
2931+
spi_nor_region_mark_end(&region[i - 1]);
2932+
2933+
return 0;
2934+
}
2935+
2936+
/**
2937+
* spi_nor_parse_smpt() - parse Sector Map Parameter Table
2938+
* @nor: pointer to a 'struct spi_nor'
2939+
* @smpt_header: sector map parameter table header
2940+
*
2941+
* This table is optional, but when available, we parse it to identify the
2942+
* location and size of sectors within the main data array of the flash memory
2943+
* device and to identify which Erase Types are supported by each sector.
2944+
*
2945+
* Return: 0 on success, -errno otherwise.
2946+
*/
2947+
static int spi_nor_parse_smpt(struct spi_nor *nor,
2948+
const struct sfdp_parameter_header *smpt_header)
2949+
{
2950+
const u32 *sector_map;
2951+
u32 *smpt;
2952+
size_t len;
2953+
u32 addr;
2954+
int i, ret;
2955+
2956+
/* Read the Sector Map Parameter Table. */
2957+
len = smpt_header->length * sizeof(*smpt);
2958+
smpt = kzalloc(len, GFP_KERNEL);
2959+
if (!smpt)
2960+
return -ENOMEM;
2961+
2962+
addr = SFDP_PARAM_HEADER_PTP(smpt_header);
2963+
ret = spi_nor_read_sfdp(nor, addr, len, smpt);
2964+
if (ret)
2965+
goto out;
2966+
2967+
/* Fix endianness of the SMPT DWORDs. */
2968+
for (i = 0; i < smpt_header->length; i++)
2969+
smpt[i] = le32_to_cpu(smpt[i]);
2970+
2971+
sector_map = spi_nor_get_map_in_use(nor, smpt);
2972+
if (!sector_map) {
2973+
ret = -EINVAL;
2974+
goto out;
2975+
}
2976+
2977+
ret = spi_nor_init_non_uniform_erase_map(nor, sector_map);
2978+
if (ret)
2979+
goto out;
2980+
2981+
spi_nor_regions_sort_erase_types(&nor->erase_map);
2982+
/* fall through */
2983+
out:
2984+
kfree(smpt);
2985+
return ret;
2986+
}
2987+
27012988
/**
27022989
* spi_nor_parse_sfdp() - parse the Serial Flash Discoverable Parameters.
27032990
* @nor: pointer to a 'struct spi_nor'
@@ -2793,7 +3080,7 @@ static int spi_nor_parse_sfdp(struct spi_nor *nor,
27933080

27943081
switch (SFDP_PARAM_HEADER_ID(param_header)) {
27953082
case SFDP_SECTOR_MAP_ID:
2796-
dev_info(dev, "non-uniform erase sector maps are not supported yet.\n");
3083+
err = spi_nor_parse_smpt(nor, param_header);
27973084
break;
27983085

27993086
default:

include/linux/mtd/spi-nor.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,18 @@ spi_nor_region_end(const struct spi_nor_erase_region *region)
413413
return (region->offset & ~SNOR_ERASE_FLAGS_MASK) + region->size;
414414
}
415415

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

0 commit comments

Comments
 (0)