ramips: add support for switching between 3-byte and 4-byte addressing on w25q256 flash
On some devices the flash chip needs to be in 3-byte addressing mode during reboot, otherwise the boot loader will fail to start. This mode however does not allow regular reads/writes onto the upper 16M half. W25Q256 has separate read commands for reading from >16M, however it does not have any separate write commands. This patch changes the code to leave the chip in 3-byte mode most of the time and only switch during erase/write cycles that go to >16M addresses. Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
		| @@ -0,0 +1,214 @@ | |||||||
|  | mtd: spi-nor: add support for switching between 3-byte and 4-byte addressing on w25q256 flash | ||||||
|  |  | ||||||
|  | On some devices the flash chip needs to be in 3-byte addressing mode during | ||||||
|  | reboot, otherwise the boot loader will fail to start. | ||||||
|  | This mode however does not allow regular reads/writes onto the upper 16M | ||||||
|  | half. W25Q256 has separate read commands for reading from >16M, however | ||||||
|  | it does not have any separate write commands. | ||||||
|  | This patch changes the code to leave the chip in 3-byte mode most of the | ||||||
|  | time and only switch during erase/write cycles that go to >16M | ||||||
|  | addresses. | ||||||
|  |  | ||||||
|  | Signed-off-by: Felix Fietkau <nbd@nbd.name> | ||||||
|  | --- | ||||||
|  | --- a/drivers/mtd/spi-nor/spi-nor.c | ||||||
|  | +++ b/drivers/mtd/spi-nor/spi-nor.c | ||||||
|  | @@ -85,6 +85,10 @@ struct flash_info { | ||||||
|  |  					 * Use dedicated 4byte address op codes | ||||||
|  |  					 * to support memory size above 128Mib. | ||||||
|  |  					 */ | ||||||
|  | +#define SPI_NOR_4B_READ_OP	BIT(12)	/* | ||||||
|  | +					 * Like SPI_NOR_4B_OPCODES, but for read | ||||||
|  | +					 * op code only. | ||||||
|  | +					 */ | ||||||
|  |  }; | ||||||
|  |   | ||||||
|  |  #define JEDEC_MFR(info)	((info)->id[0]) | ||||||
|  | @@ -250,6 +254,15 @@ static inline u8 spi_nor_convert_3to4_er | ||||||
|  |  				      ARRAY_SIZE(spi_nor_3to4_erase)); | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | +static void spi_nor_set_4byte_read(struct spi_nor *nor, | ||||||
|  | +				   const struct flash_info *info) | ||||||
|  | +{ | ||||||
|  | +	nor->addr_width = 3; | ||||||
|  | +	nor->ext_addr = 0; | ||||||
|  | +	nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode); | ||||||
|  | +	nor->flags |= SNOR_F_4B_EXT_ADDR; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  |  static void spi_nor_set_4byte_opcodes(struct spi_nor *nor, | ||||||
|  |  				      const struct flash_info *info) | ||||||
|  |  { | ||||||
|  | @@ -467,6 +480,36 @@ static int spi_nor_erase_sector(struct s | ||||||
|  |  	return nor->write_reg(nor, nor->erase_opcode, buf, nor->addr_width); | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | +static int spi_nor_check_ext_addr(struct spi_nor *nor, u32 addr) | ||||||
|  | +{ | ||||||
|  | +	bool ext_addr; | ||||||
|  | +	int ret; | ||||||
|  | +	u8 cmd; | ||||||
|  | + | ||||||
|  | +	if (!(nor->flags & SNOR_F_4B_EXT_ADDR)) | ||||||
|  | +		return 0; | ||||||
|  | + | ||||||
|  | +	ext_addr = !!(addr & 0xff000000); | ||||||
|  | +	if (nor->ext_addr == ext_addr) | ||||||
|  | +		return 0; | ||||||
|  | + | ||||||
|  | +	cmd = ext_addr ? SPINOR_OP_EN4B : SPINOR_OP_EX4B; | ||||||
|  | +	write_enable(nor); | ||||||
|  | +	ret = nor->write_reg(nor, cmd, NULL, 0); | ||||||
|  | +	if (ret) | ||||||
|  | +		return ret; | ||||||
|  | + | ||||||
|  | +	cmd = 0; | ||||||
|  | +	ret = nor->write_reg(nor, SPINOR_OP_WREAR, &cmd, 1); | ||||||
|  | +	if (ret) | ||||||
|  | +		return ret; | ||||||
|  | + | ||||||
|  | +	nor->addr_width = 3 + ext_addr; | ||||||
|  | +	nor->ext_addr = ext_addr; | ||||||
|  | +	write_disable(nor); | ||||||
|  | +	return 0; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  |  /* | ||||||
|  |   * 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. | ||||||
|  | @@ -492,6 +535,10 @@ static int spi_nor_erase(struct mtd_info | ||||||
|  |  	if (ret) | ||||||
|  |  		return ret; | ||||||
|  |   | ||||||
|  | +	ret = spi_nor_check_ext_addr(nor, addr + len); | ||||||
|  | +	if (ret) | ||||||
|  | +		return ret; | ||||||
|  | + | ||||||
|  |  	/* whole-chip erase? */ | ||||||
|  |  	if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) { | ||||||
|  |  		unsigned long timeout; | ||||||
|  | @@ -542,6 +589,7 @@ static int spi_nor_erase(struct mtd_info | ||||||
|  |  	write_disable(nor); | ||||||
|  |   | ||||||
|  |  erase_err: | ||||||
|  | +	spi_nor_check_ext_addr(nor, 0); | ||||||
|  |  	spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE); | ||||||
|  |   | ||||||
|  |  	instr->state = ret ? MTD_ERASE_FAILED : MTD_ERASE_DONE; | ||||||
|  | @@ -834,7 +882,9 @@ static int spi_nor_lock(struct mtd_info | ||||||
|  |  	if (ret) | ||||||
|  |  		return ret; | ||||||
|  |   | ||||||
|  | +	spi_nor_check_ext_addr(nor, ofs + len); | ||||||
|  |  	ret = nor->flash_lock(nor, ofs, len); | ||||||
|  | +	spi_nor_check_ext_addr(nor, 0); | ||||||
|  |   | ||||||
|  |  	spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_UNLOCK); | ||||||
|  |  	return ret; | ||||||
|  | @@ -849,7 +899,9 @@ static int spi_nor_unlock(struct mtd_inf | ||||||
|  |  	if (ret) | ||||||
|  |  		return ret; | ||||||
|  |   | ||||||
|  | +	spi_nor_check_ext_addr(nor, ofs + len); | ||||||
|  |  	ret = nor->flash_unlock(nor, ofs, len); | ||||||
|  | +	spi_nor_check_ext_addr(nor, 0); | ||||||
|  |   | ||||||
|  |  	spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK); | ||||||
|  |  	return ret; | ||||||
|  | @@ -1151,7 +1203,7 @@ static const struct flash_info spi_nor_i | ||||||
|  |  	{ "w25q80", INFO(0xef5014, 0, 64 * 1024,  16, SECT_4K) }, | ||||||
|  |  	{ "w25q80bl", INFO(0xef4014, 0, 64 * 1024,  16, SECT_4K) }, | ||||||
|  |  	{ "w25q128", INFO(0xef4018, 0, 64 * 1024, 256, SECT_4K) }, | ||||||
|  | -	{ "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K) }, | ||||||
|  | +	{ "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_4B_READ_OP) }, | ||||||
|  |   | ||||||
|  |  	/* Catalyst / On Semiconductor -- non-JEDEC */ | ||||||
|  |  	{ "cat25c11", CAT25_INFO(  16, 8, 16, 1, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, | ||||||
|  | @@ -1205,6 +1257,9 @@ static int spi_nor_read(struct mtd_info | ||||||
|  |  	if (ret) | ||||||
|  |  		return ret; | ||||||
|  |   | ||||||
|  | +	if (nor->flags & SNOR_F_4B_EXT_ADDR) | ||||||
|  | +		nor->addr_width = 4; | ||||||
|  | + | ||||||
|  |  	while (len) { | ||||||
|  |  		loff_t addr = from; | ||||||
|  |   | ||||||
|  | @@ -1229,6 +1284,18 @@ static int spi_nor_read(struct mtd_info | ||||||
|  |  	ret = 0; | ||||||
|  |   | ||||||
|  |  read_err: | ||||||
|  | +	if (nor->flags & SNOR_F_4B_EXT_ADDR) { | ||||||
|  | +		u8 val = 0; | ||||||
|  | + | ||||||
|  | +		if ((from + len) & 0xff000000) { | ||||||
|  | +			write_enable(nor); | ||||||
|  | +			nor->write_reg(nor, SPINOR_OP_WREAR, &val, 1); | ||||||
|  | +			write_disable(nor); | ||||||
|  | +		} | ||||||
|  | + | ||||||
|  | +		nor->addr_width = 3; | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  |  	spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ); | ||||||
|  |  	return ret; | ||||||
|  |  } | ||||||
|  | @@ -1330,6 +1397,10 @@ static int spi_nor_write(struct mtd_info | ||||||
|  |  	if (ret) | ||||||
|  |  		return ret; | ||||||
|  |   | ||||||
|  | +	ret = spi_nor_check_ext_addr(nor, to + len); | ||||||
|  | +	if (ret < 0) | ||||||
|  | +		return ret; | ||||||
|  | + | ||||||
|  |  	for (i = 0; i < len; ) { | ||||||
|  |  		ssize_t written; | ||||||
|  |  		loff_t addr = to + i; | ||||||
|  | @@ -1377,6 +1448,7 @@ static int spi_nor_write(struct mtd_info | ||||||
|  |  	} | ||||||
|  |   | ||||||
|  |  write_err: | ||||||
|  | +	spi_nor_check_ext_addr(nor, 0); | ||||||
|  |  	spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE); | ||||||
|  |  	return ret; | ||||||
|  |  } | ||||||
|  | @@ -1715,8 +1787,10 @@ int spi_nor_scan(struct spi_nor *nor, co | ||||||
|  |  	else if (mtd->size > 0x1000000) { | ||||||
|  |  		/* enable 4-byte addressing if the device exceeds 16MiB */ | ||||||
|  |  		nor->addr_width = 4; | ||||||
|  | -		if (JEDEC_MFR(info) == SNOR_MFR_SPANSION || | ||||||
|  | -		    info->flags & SPI_NOR_4B_OPCODES) | ||||||
|  | +		if (info->flags & SPI_NOR_4B_READ_OP) | ||||||
|  | +			spi_nor_set_4byte_read(nor, info); | ||||||
|  | +		else if (JEDEC_MFR(info) == SNOR_MFR_SPANSION || | ||||||
|  | +			 info->flags & SPI_NOR_4B_OPCODES) | ||||||
|  |  			spi_nor_set_4byte_opcodes(nor, info); | ||||||
|  |  		else | ||||||
|  |  			set_4byte(nor, info, 1); | ||||||
|  | --- a/include/linux/mtd/spi-nor.h | ||||||
|  | +++ b/include/linux/mtd/spi-nor.h | ||||||
|  | @@ -90,6 +90,7 @@ | ||||||
|  |  /* Used for Macronix and Winbond flashes. */ | ||||||
|  |  #define SPINOR_OP_EN4B		0xb7	/* Enter 4-byte mode */ | ||||||
|  |  #define SPINOR_OP_EX4B		0xe9	/* Exit 4-byte mode */ | ||||||
|  | +#define SPINOR_OP_WREAR		0xc5	/* Write extended address register */ | ||||||
|  |   | ||||||
|  |  /* Used for Spansion flashes only. */ | ||||||
|  |  #define SPINOR_OP_BRWR		0x17	/* Bank register write */ | ||||||
|  | @@ -141,6 +142,7 @@ enum spi_nor_option_flags { | ||||||
|  |  	SNOR_F_NO_OP_CHIP_ERASE	= BIT(2), | ||||||
|  |  	SNOR_F_S3AN_ADDR_DEFAULT = BIT(3), | ||||||
|  |  	SNOR_F_READY_XSR_RDY	= BIT(4), | ||||||
|  | +	SNOR_F_4B_EXT_ADDR	= BIT(5), | ||||||
|  |  }; | ||||||
|  |   | ||||||
|  |  /** | ||||||
|  | @@ -188,6 +190,7 @@ struct spi_nor { | ||||||
|  |  	enum read_mode		flash_read; | ||||||
|  |  	bool			sst_write_second; | ||||||
|  |  	u32			flags; | ||||||
|  | +	u8			ext_addr; | ||||||
|  |  	u8			cmd_buf[SPI_NOR_MAX_CMD_SIZE]; | ||||||
|  |   | ||||||
|  |  	int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops); | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| --- a/drivers/mtd/spi-nor/spi-nor.c | --- a/drivers/mtd/spi-nor/spi-nor.c | ||||||
| +++ b/drivers/mtd/spi-nor/spi-nor.c | +++ b/drivers/mtd/spi-nor/spi-nor.c | ||||||
| @@ -1381,6 +1381,66 @@ write_err: | @@ -1453,6 +1453,67 @@ write_err: | ||||||
|  	return ret; |  	return ret; | ||||||
|  } |  } | ||||||
|   |   | ||||||
| @@ -8,6 +8,7 @@ | |||||||
| +				 size_t *_retlen, const u_char *_buf) | +				 size_t *_retlen, const u_char *_buf) | ||||||
| +{ | +{ | ||||||
| +	struct spi_nor *nor = mtd_to_spi_nor(mtd); | +	struct spi_nor *nor = mtd_to_spi_nor(mtd); | ||||||
|  | +	u32 addr_width = nor->addr_width + !!(nor->flags & SNOR_F_4B_EXT_ADDR); | ||||||
| +	int chunk_size; | +	int chunk_size; | ||||||
| +	int retlen = 0; | +	int retlen = 0; | ||||||
| +	int ret; | +	int ret; | ||||||
| @@ -16,8 +17,8 @@ | |||||||
| +	if (!chunk_size) | +	if (!chunk_size) | ||||||
| +		chunk_size = _len; | +		chunk_size = _len; | ||||||
| + | + | ||||||
| +	if (nor->addr_width > 3) | +	if (addr_width > 3) | ||||||
| +		chunk_size -= nor->addr_width - 3; | +		chunk_size -= addr_width - 3; | ||||||
| + | + | ||||||
| +	while (retlen < _len) { | +	while (retlen < _len) { | ||||||
| +		size_t len = min_t(int, chunk_size, _len - retlen); | +		size_t len = min_t(int, chunk_size, _len - retlen); | ||||||
| @@ -67,7 +68,7 @@ | |||||||
|  static int macronix_quad_enable(struct spi_nor *nor) |  static int macronix_quad_enable(struct spi_nor *nor) | ||||||
|  { |  { | ||||||
|  	int ret, val; |  	int ret, val; | ||||||
| @@ -1630,10 +1690,12 @@ int spi_nor_scan(struct spi_nor *nor, co | @@ -1702,10 +1763,12 @@ int spi_nor_scan(struct spi_nor *nor, co | ||||||
|  	} |  	} | ||||||
|   |   | ||||||
|  	/* sst nor chips use AAI word program */ |  	/* sst nor chips use AAI word program */ | ||||||
| @@ -82,7 +83,7 @@ | |||||||
|   |   | ||||||
|  	if (info->flags & USE_FSR) |  	if (info->flags & USE_FSR) | ||||||
|  		nor->flags |= SNOR_F_USE_FSR; |  		nor->flags |= SNOR_F_USE_FSR; | ||||||
| @@ -1663,11 +1725,20 @@ int spi_nor_scan(struct spi_nor *nor, co | @@ -1735,11 +1798,20 @@ int spi_nor_scan(struct spi_nor *nor, co | ||||||
|  	mtd->writebufsize = nor->page_size; |  	mtd->writebufsize = nor->page_size; | ||||||
|   |   | ||||||
|  	if (np) { |  	if (np) { | ||||||
| @@ -105,15 +106,15 @@ | |||||||
|  		nor->flash_read = SPI_NOR_FAST; |  		nor->flash_read = SPI_NOR_FAST; | ||||||
| --- a/include/linux/mtd/spi-nor.h | --- a/include/linux/mtd/spi-nor.h | ||||||
| +++ b/include/linux/mtd/spi-nor.h | +++ b/include/linux/mtd/spi-nor.h | ||||||
| @@ -141,6 +141,7 @@ enum spi_nor_option_flags { | @@ -143,6 +143,7 @@ enum spi_nor_option_flags { | ||||||
|  	SNOR_F_NO_OP_CHIP_ERASE	= BIT(2), |  | ||||||
|  	SNOR_F_S3AN_ADDR_DEFAULT = BIT(3), |  	SNOR_F_S3AN_ADDR_DEFAULT = BIT(3), | ||||||
|  	SNOR_F_READY_XSR_RDY	= BIT(4), |  	SNOR_F_READY_XSR_RDY	= BIT(4), | ||||||
| +	SNOR_F_SST		= BIT(5), |  	SNOR_F_4B_EXT_ADDR	= BIT(5), | ||||||
|  | +	SNOR_F_SST		= BIT(6), | ||||||
|  }; |  }; | ||||||
|   |   | ||||||
|  /** |  /** | ||||||
| @@ -180,6 +181,7 @@ struct spi_nor { | @@ -182,6 +183,7 @@ struct spi_nor { | ||||||
|  	struct mutex		lock; |  	struct mutex		lock; | ||||||
|  	struct device		*dev; |  	struct device		*dev; | ||||||
|  	u32			page_size; |  	u32			page_size; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Felix Fietkau
					Felix Fietkau