[PATCH 3/3] mtd: spi-nor: Add support of Sector Map Parameter Table

Yogesh Narayan Gaur yogeshnarayan.gaur at nxp.com
Thu Feb 1 20:51:31 PST 2018


Hi,

> -----Original Message-----
> From: Prabhakar Kushwaha
> Sent: Thursday, February 01, 2018 7:50 PM
> To: linux-mtd at lists.infradead.org
> Cc: boris.brezillon at free-electrons.com; cyrille.pitchen at wedev4u.fr;
> computersforpeace at gmail.com; Poonam Aggrwal
> <poonam.aggrwal at nxp.com>; Suresh Gupta <suresh.gupta at nxp.com>; Yogesh
> Narayan Gaur <yogeshnarayan.gaur at nxp.com>; Prabhakar Kushwaha
> <prabhakar.kushwaha at nxp.com>
> Subject: [PATCH 3/3] mtd: spi-nor: Add support of Sector Map Parameter Table
> 
> JESD216B defines Sector Map Parameter Table. It identifies the location and size
> of sectors within the main data array of the flash memory device and identifies
> which Erase Types are supported by each sector. This table is used when a
> memory device has sectors of more than one size Or, does not allow all Erase
> Type commands to be applied to all sectors.
> 
> Sector Map parameter table is parsed to get location and size of sectors along
> with supported Erase Type.
> 
> Also add support of selecting erase opcode based on erase address provided at
> run-time.
> 
> Signed-off-by: Prabhakar Kushwaha <prabhakar.kushwaha at nxp.com>
> ---
>  drivers/mtd/spi-nor/spi-nor.c | 293
> +++++++++++++++++++++++++++++++++++++++++-
>  include/linux/mtd/spi-nor.h   |  16 +++
>  2 files changed, 305 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index
> d445a4d..cc8bbb2d 100644
> --- a/drivers/mtd/spi-nor/spi-nor.c
> +++ b/drivers/mtd/spi-nor/spi-nor.c
> @@ -245,6 +245,10 @@ static inline u8 spi_nor_convert_3to4_erase(u8
> opcode)  static void spi_nor_set_4byte_opcodes(struct spi_nor *nor,
>  				      const struct flash_info *info)  {
> +	struct spi_nor_map *map = &nor->flash_map;
> +	struct spi_nor_region *reg;
> +	int i;
> +
>  	/* Do some manufacturer fixups first */
>  	switch (JEDEC_MFR(info)) {
>  	case SNOR_MFR_SPANSION:
> @@ -260,6 +264,16 @@ 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 (!map->flash_region)
> +		return;
> +
> +	reg = map->flash_region;
> +	for (i = 0; i < map->num_regions; i++) {
> +		reg[i].erase_opcode =
> +			spi_nor_convert_3to4_erase(reg[i].erase_opcode);
> +	}
> +
>  }
> 
>  /* Enable/disable 4-byte addressing mode. */ @@ -483,6 +497,28 @@ static int
> spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
>  	return nor->write_reg(nor, nor->erase_opcode, buf, nor->addr_width);
> }
> 
> +static void spi_nor_erase_select(struct mtd_info *mtd, u32 addr) {
> +	struct spi_nor *nor = mtd_to_spi_nor(mtd);
> +	struct spi_nor_map *map = &nor->flash_map;
> +	struct spi_nor_region *reg;
> +	int i;
> +
> +	if (!map->flash_region)
> +		return;
> +
> +	reg = map->flash_region;
> +	for (i = 0; i < map->num_regions; i++) {
> +		if (addr >= reg[i].region_start &&
> +		    addr <= reg[i].region_end) {
> +			nor->erase_opcode = reg[i].erase_opcode;
> +			mtd->erasesize = reg[i].erasesize;
> +			if (reg[i].region_size < mtd->erasesize)
> +				mtd->erasesize = reg[i].region_size;
> +		}
> +	}
> +}
> +
>  /*
>   * 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.
> @@ -491,8 +527,9 @@ static int spi_nor_erase(struct mtd_info *mtd, struct
> erase_info *instr)  {
>  	struct spi_nor *nor = mtd_to_spi_nor(mtd);
>  	u32 addr, len;
> -	uint32_t rem;
> +	uint32_t rem, erasesize;
>  	int ret;
> +	u8 erase_opcode;
> 
>  	dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr,
>  			(long long)instr->len);
> @@ -508,6 +545,9 @@ static int spi_nor_erase(struct mtd_info *mtd, struct
> erase_info *instr)
>  	if (ret)
>  		return ret;
> 
> +	erase_opcode = nor->erase_opcode;
> +	erasesize = mtd->erasesize;
> +
>  	/* whole-chip erase? */
>  	if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
>  		unsigned long timeout;
> @@ -542,6 +582,8 @@ static int spi_nor_erase(struct mtd_info *mtd, struct
> erase_info *instr)
>  		while (len) {
>  			write_enable(nor);
> 
> +			spi_nor_erase_select(mtd, addr);
> +
>  			ret = spi_nor_erase_sector(nor, addr);
>  			if (ret)
>  				goto erase_err;
> @@ -558,6 +600,8 @@ static int spi_nor_erase(struct mtd_info *mtd, struct
> erase_info *instr)
>  	write_disable(nor);
> 
>  erase_err:
> +	nor->erase_opcode = erase_opcode;
> +	mtd->erasesize = erasesize;
>  	spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE);
> 
>  	instr->state = ret ? MTD_ERASE_FAILED : MTD_ERASE_DONE; @@ -
> 1754,6 +1798,11 @@ struct spi_nor_pp_command {
>  	enum spi_nor_protocol	proto;
>  };
> 
> +struct spi_nor_erase_command {
> +	u8			opcode;
> +	u32			erasesize;
> +};
> +
>  enum spi_nor_read_command_index {
>  	SNOR_CMD_READ,
>  	SNOR_CMD_READ_FAST,
> @@ -1796,6 +1845,15 @@ enum spi_nor_pp_command_index {
>  	SNOR_CMD_PP_MAX
>  };
> 
> +enum spi_nor_erase_command_index {
> +	SNOR_CMD_ERASE_TYPE1,
> +	SNOR_CMD_ERASE_TYPE2,
> +	SNOR_CMD_ERASE_TYPE3,
> +	SNOR_CMD_ERASE_TYPE4,
> +
> +	SNOR_CMD_ERASE_MAX
> +};
> +
>  struct spi_nor_flash_parameter {
>  	u64				size;
>  	u32				page_size;
> @@ -1803,6 +1861,7 @@ struct spi_nor_flash_parameter {
>  	struct spi_nor_hwcaps		hwcaps;
>  	struct spi_nor_read_command	reads[SNOR_CMD_READ_MAX];
>  	struct spi_nor_pp_command
> 	page_programs[SNOR_CMD_PP_MAX];
> +	struct spi_nor_erase_command	erase[SNOR_CMD_ERASE_MAX];
> 
>  	int (*quad_enable)(struct spi_nor *nor);  }; @@ -2024,6 +2083,31 @@
> struct sfdp_bfpt {
>  	u32	dwords[BFPT_DWORD_MAX];
>  };
> 
> +#define SMPT_DESC_TYPE_RD_DATA_MASK		GENMASK(31, 24)
> +#define SMPT_DESC_TYPE_RD_DATA_SHIFT		24
> +#define SMPT_DESC_TYPE_ADR_LEN_MASK		GENMASK(23, 22)
> +#define SMPT_DESC_TYPE_ADDRESS_BYTES_3_ONLY	(0x1UL << 22)
> +#define SMPT_DESC_TYPE_ADDRESS_BYTES_3_OR_4	(0x3UL << 22)
> +#define SMPT_DESC_TYPE_ADDRESS_BYTES_4_ONLY	(0x2UL << 22)
> +#define SMPT_DESC_TYPE_RD_LATENCY_MASK		GENMASK(19, 16)
> +#define SMPT_DESC_TYPE_RD_LATENCY_SHIFT		16
> +#define SMPT_DESC_TYPE_CMD_INS_MASK		GENMASK(15, 8)
> +#define SMPT_DESC_TYPE_CMD_INS_SHIFT		8
> +#define SMPT_DESC_TYPE_MASK			GENMASK(1, 0)
> +#define SMPT_DESC_TYPE_MASK_SHIFT		1
> +#define SMPT_DESC_SEQ_END			(0x1UL << 0)
> +
> +#define SMPT_HEADER_REGION_COUNT		GENMASK(23, 16)
> +#define SMPT_HEADER_REGION_COUNT_SHIFT		16
> +#define SMPT_HEADER_CONFIG_ID			GENMASK(15, 8)
> +#define SMPT_HEADER_CONFIG_ID_SHIFT		8
> +
> +#define SMPT_REGION_SIZE			GENMASK(31, 8)
> +#define SMPT_REGION_SIZE_SHIFT		8
> +struct sfdp_smpt {
> +	u32	*dwords;
> +};
> +
>  /* Fast Read settings. */
> 
>  static inline void
> @@ -2258,14 +2342,18 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
> 
>  		erasesize = 1U << erasesize;
>  		opcode = (half >> 8) & 0xff;
> +
> +		params->erase[i].opcode = opcode;
> +		params->erase[i].erasesize = erasesize;
> +
>  #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) {
> +		if ((!mtd->erasesize || mtd->erasesize < erasesize) &&
> +		    (!nor->erase_opcode)) {
>  			nor->erase_opcode = opcode;
>  			mtd->erasesize = erasesize;
>  		}
> @@ -2311,6 +2399,203 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
>  	return 0;
>  }
> 
> +static int spi_nor_parse_smpt_command(struct spi_nor *nor, u32 dword1,
> +				      u32 dword2,
> +				      struct spi_nor_flash_parameter *params) {
> +	int ret = 0;
> +	u8 addr_width, config_bit;
> +	u32 cmd, mask;
> +
> +	addr_width = nor->addr_width;
> +

Modify value for nor->read_dummy along with nor->addr_width before sending cmd to driver.
As per data sheet of flash ' S25FS512S' value of dummy cycles to be passed for reading SPINOR_OP_RDAR is 8.

--
Regards
Yogesh Gaur

> +	switch (dword1 & SMPT_DESC_TYPE_ADR_LEN_MASK) {
> +	case SMPT_DESC_TYPE_ADDRESS_BYTES_3_ONLY:
> +		nor->addr_width = 3;
> +		break;
> +
> +	case SMPT_DESC_TYPE_ADDRESS_BYTES_4_ONLY:
> +		nor->addr_width = 4;
> +		break;
> +
> +	default:
> +		break;
> +	}
> +
> +	cmd = dword1 & SMPT_DESC_TYPE_CMD_INS_MASK;
> +	cmd >>= SMPT_DESC_TYPE_CMD_INS_SHIFT;
> +
> +	mask = dword1 & SMPT_DESC_TYPE_RD_DATA_MASK;
> +	mask >>= SMPT_DESC_TYPE_RD_DATA_SHIFT;
> +
> +	switch (cmd) {
> +	case SPINOR_OP_RDAR:
> +		if (nor->read_anyreg) {
> +			ret = nor->read_anyreg(nor, SPINOR_OP_RDAR,
> dword2,
> +			       &config_bit, 1);
> +			if (ret < 0) {
> +				dev_err(nor->dev, "error reading config_bit");
> +				goto err;
> +			}
> +
> +			config_bit &= mask;
> +
> +			ret = config_bit;
> +		}
> +		break;
> +
> +	default:
> +		dev_info(nor->dev, "Detection instructuion is not supported");
> +		break;
> +	}
> +
> +err:
> +	nor->addr_width = addr_width;
> +
> +	return ret;
> +}
> +
> +static int spi_nor_parse_smpt_map(struct spi_nor *nor, u32 *dword,
> +				  struct spi_nor_flash_parameter *params) {
> +	struct spi_nor_map *map = &nor->flash_map;
> +	u32 reg_size, start = 0,  opcode_index;
> +	u32 config_id, reg_count, i;
> +	struct spi_nor_region *reg;
> +
> +	config_id = *dword & SMPT_HEADER_CONFIG_ID;
> +	config_id >>= SMPT_HEADER_CONFIG_ID_SHIFT;
> +
> +	reg_count = *dword & SMPT_HEADER_REGION_COUNT;
> +	reg_count >>= SMPT_HEADER_REGION_COUNT_SHIFT;
> +	reg_count++;
> +
> +	if (map->config_sel != config_id)
> +		return reg_count;
> +
> +	map->flash_region = kmalloc((reg_count * sizeof(struct
> spi_nor_region)),
> +				    GFP_KERNEL);
> +	if (!map->flash_region)
> +		return -ENOMEM;
> +
> +	map->num_regions = reg_count;
> +
> +	dword++;
> +
> +	reg = map->flash_region;
> +	for (i = 0; reg_count > 0; reg_count--, dword++, i++) {
> +		reg_size = *dword & SMPT_REGION_SIZE;
> +		reg_size >>= SMPT_REGION_SIZE_SHIFT;
> +		reg_size = (reg_size + 1) * 256;
> +
> +		reg[i].region_size = reg_size;
> +		reg[i].region_start = start;
> +		reg[i].region_end = start + reg_size - 1;
> +
> +		opcode_index = ffs(*dword & 0x0F) - 1;
> +		reg[i].erase_opcode = params->erase[opcode_index].opcode;
> +		reg[i].erasesize = params->erase[opcode_index].erasesize;
> +
> +		start = reg[i].region_end + 1;
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * spi_nor_parse_smpt() - read and parse the Basic Flash Parameter Table.
> + * @nor:		pointer to a 'struct spi_nor'
> + * @smpt_header:	pointer to the 'struct sfdp_parameter_header'
> describing
> + *			the Sector Map Parameter Table length and version
> + * @params:		pointer to the 'struct spi_nor_flash_parameter' to be
> + *			filled
> + *
> + * The Sector Map Parameter Table identifies the location and size of
> +sectors
> + * within the main data array of the flash memory device and identifies
> +which
> + * Erase Types are supported by each sector. This table is optional
> +table as
> + * defined by the SFDP (JESD216) specification.
> + * If there is more than one user selected sector map (configuration),
> + * this table includes the definition of instructions needed to
> +determine
> + * which sector map configuration is in use. The number of sector map
> + * configuration detection commands is variable, the number of
> + * configurations is variable, and  the number of regions in each
> + * configuration is variable, thus the size of this table is variable.
> + *
> + * Return: 0 on success, -errno otherwise.
> + */
> +static int spi_nor_parse_smpt(struct spi_nor *nor,
> +			      const struct sfdp_parameter_header *smpt_header,
> +			      struct spi_nor_flash_parameter *params) {
> +	struct sfdp_smpt smpt;
> +	size_t len;
> +	int i, ret;
> +	u32 addr;
> +
> +	/* Read the Sector Map Parameter Table. */
> +	len = smpt_header->length * sizeof(u32);
> +	addr = SFDP_PARAM_HEADER_PTP(smpt_header);
> +
> +	smpt.dwords = kmalloc(len, GFP_KERNEL);
> +	if (!smpt.dwords)
> +		return -ENOMEM;
> +
> +	ret = spi_nor_read_sfdp_dma_unsafe(nor,  addr, len, smpt.dwords);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Fix endianness of the SMPT DWORDs. */
> +	for (i = 0; i < smpt_header->length; i++)
> +		smpt.dwords[i] = le32_to_cpu(smpt.dwords[i]);
> +
> +	i = 0;
> +
> +	while (!((smpt.dwords[i] & SMPT_DESC_TYPE_MASK) >>
> +		 SMPT_DESC_TYPE_MASK_SHIFT)) {
> +		ret = spi_nor_parse_smpt_command(nor, smpt.dwords[i],
> +						 smpt.dwords[i + 1], params);
> +		if (ret < 0) {
> +			dev_err(nor->dev, "error parsing smpt command");
> +			return ret;
> +		}
> +
> +		nor->flash_map.config_sel <<= 1;
> +		if (ret)
> +			nor->flash_map.config_sel |= 1;
> +
> +		if (smpt.dwords[i] & SMPT_DESC_SEQ_END)
> +			break;
> +
> +		/*
> +		 * Point to next config detect
> +		 */
> +		i = i + 2;
> +	};
> +
> +	/*
> +	 * Point to First config header
> +	 */
> +	i += 2;
> +
> +	while (((smpt.dwords[i] & SMPT_DESC_TYPE_MASK) >>
> +		SMPT_DESC_TYPE_MASK_SHIFT)) {
> +		ret = spi_nor_parse_smpt_map(nor, &smpt.dwords[i], params);
> +
> +		if (ret < 0)
> +			return ret;
> +
> +		if (ret == 0 || smpt.dwords[i] & SMPT_DESC_SEQ_END)
> +			break;
> +
> +		/*
> +		 * Point to next config header
> +		 */
> +		i = i + ret + 1;
> +	};
> +
> +	return 0;
> +}
> +
>  /**
>   * spi_nor_parse_sfdp() - parse the Serial Flash Discoverable Parameters.
>   * @nor:		pointer to a 'struct spi_nor'
> @@ -2405,7 +2690,7 @@ static int spi_nor_parse_sfdp(struct spi_nor *nor,
> 
>  		switch (SFDP_PARAM_HEADER_ID(param_header)) {
>  		case SFDP_SECTOR_MAP_ID:
> -			dev_info(dev, "non-uniform erase sector maps are not
> supported yet.\n");
> +			err = spi_nor_parse_smpt(nor, param_header, params);
>  			break;
> 
>  		default:
> diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index
> a747215..c018ffe 100644
> --- a/include/linux/mtd/spi-nor.h
> +++ b/include/linux/mtd/spi-nor.h
> @@ -107,6 +107,8 @@
>  /* Used for Spansion flashes only. */
>  #define SPINOR_OP_BRWR		0x17	/* Bank register write */
>  #define SPINOR_OP_CLSR		0x30	/* Clear status register 1 */
> +#define SPINOR_OP_RDAR		0x65	/* Read any register */
> +#define SPINOR_OP_WRAR		0x71	/* Write any register */
> 
>  /* Used for Micron flashes only. */
>  #define SPINOR_OP_RD_EVCR      0x65    /* Read EVCR register */
> @@ -235,6 +237,19 @@ enum spi_nor_option_flags {
>  	SNOR_F_USE_CLSR		= BIT(5),
>  };
> 
> +struct spi_nor_region {
> +	u32			region_size;
> +	u32			region_start;
> +	u32			region_end;
> +	u32			erasesize;
> +	u8			erase_opcode;
> +};
> +
> +struct spi_nor_map {
> +	u8			config_sel;
> +	u8			num_regions;
> +	struct spi_nor_region	*flash_region;
> +};
>  /**
>   * struct flash_info - Forward declaration of a structure used internally by
>   *		       spi_nor_scan()
> @@ -283,6 +298,7 @@ struct spi_nor {
>  	struct mtd_info		mtd;
>  	struct mutex		lock;
>  	struct device		*dev;
> +	struct spi_nor_map	flash_map;
>  	const struct flash_info	*info;
>  	u32			page_size;
>  	u8			addr_width;
> --
> 2.7.4




More information about the linux-mtd mailing list