From: Vincent Wiemann Date: Sat, 6 Jan 2018 04:33:09 +0100 Subject: ramips: mtd: spi-nor: add support for switching between 3-byte and 4-byte addressing on w25q256 flash CAUTION! NEEDS TESTING! Tries to backport switching between 3-byte and 4-byte addressing on w25q256 flash from Lede master (62ede4f78389c313a8004e79330a7d055eda2f7d) by Felix Fietkau. Applied patches: mtd: spi-nor: rename SPINOR_OP_* macros of the 4-byte address op codes mtd: spi-nor: add a stateless method to support memory size above 128Mib mtd: spi-nor: add w25q256 3b-mode-switch mtd: add chunked read-io to m25p80 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: Vincent Wiemann (me@bibbl.com) diff --git a/target/linux/ramips/patches-4.4/400-mtd-spi-nor-add-w25q256-3b-mode-switch.patch b/target/linux/ramips/patches-4.4/400-mtd-spi-nor-add-w25q256-3b-mode-switch.patch new file mode 100644 index 0000000000000000000000000000000000000000..5399f547e4edd0eb1cfee8c1ec5b35e69c692ee9 --- /dev/null +++ b/target/linux/ramips/patches-4.4/400-mtd-spi-nor-add-w25q256-3b-mode-switch.patch @@ -0,0 +1,399 @@ +--- a/drivers/mtd/devices/serial_flash_cmds.h ++++ b/drivers/mtd/devices/serial_flash_cmds.h +@@ -18,19 +18,12 @@ + #define SPINOR_OP_RDVCR 0x85 + + /* JEDEC Standard - Serial Flash Discoverable Parmeters (SFDP) Commands */ +-#define SPINOR_OP_READ_1_2_2 0xbb /* DUAL I/O READ */ +-#define SPINOR_OP_READ_1_4_4 0xeb /* QUAD I/O READ */ +- + #define SPINOR_OP_WRITE 0x02 /* PAGE PROGRAM */ + #define SPINOR_OP_WRITE_1_1_2 0xa2 /* DUAL INPUT PROGRAM */ + #define SPINOR_OP_WRITE_1_2_2 0xd2 /* DUAL INPUT EXT PROGRAM */ + #define SPINOR_OP_WRITE_1_1_4 0x32 /* QUAD INPUT PROGRAM */ + #define SPINOR_OP_WRITE_1_4_4 0x12 /* QUAD INPUT EXT PROGRAM */ + +-/* READ commands with 32-bit addressing */ +-#define SPINOR_OP_READ4_1_2_2 0xbc +-#define SPINOR_OP_READ4_1_4_4 0xec +- + /* Configuration flags */ + #define FLASH_FLAG_SINGLE 0x000000ff + #define FLASH_FLAG_READ_WRITE 0x00000001 +--- a/drivers/mtd/devices/st_spi_fsm.c ++++ b/drivers/mtd/devices/st_spi_fsm.c +@@ -507,13 +507,13 @@ static struct seq_rw_config n25q_read3_c + * - 'FAST' variants configured for 8 dummy cycles (see note above.) + */ + static struct seq_rw_config n25q_read4_configs[] = { +- {FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ4_1_4_4, 0, 4, 4, 0x00, 0, 8}, +- {FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ4_1_1_4, 0, 1, 4, 0x00, 0, 8}, +- {FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ4_1_2_2, 0, 2, 2, 0x00, 0, 8}, +- {FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ4_1_1_2, 0, 1, 2, 0x00, 0, 8}, +- {FLASH_FLAG_READ_FAST, SPINOR_OP_READ4_FAST, 0, 1, 1, 0x00, 0, 8}, +- {FLASH_FLAG_READ_WRITE, SPINOR_OP_READ4, 0, 1, 1, 0x00, 0, 0}, +- {0x00, 0, 0, 0, 0, 0x00, 0, 0}, ++ {FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B, 0, 4, 4, 0x00, 0, 8}, ++ {FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B, 0, 1, 4, 0x00, 0, 8}, ++ {FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B, 0, 2, 2, 0x00, 0, 8}, ++ {FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B, 0, 1, 2, 0x00, 0, 8}, ++ {FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST_4B, 0, 1, 1, 0x00, 0, 8}, ++ {FLASH_FLAG_READ_WRITE, SPINOR_OP_READ_4B, 0, 1, 1, 0x00, 0, 0}, ++ {0x00, 0, 0, 0, 0, 0x00, 0, 0}, + }; + + /* +@@ -553,13 +553,13 @@ static int stfsm_mx25_en_32bit_addr_seq( + * entering a state that is incompatible with the SPIBoot Controller. + */ + static struct seq_rw_config stfsm_s25fl_read4_configs[] = { +- {FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ4_1_4_4, 0, 4, 4, 0x00, 2, 4}, +- {FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ4_1_1_4, 0, 1, 4, 0x00, 0, 8}, +- {FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ4_1_2_2, 0, 2, 2, 0x00, 4, 0}, +- {FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ4_1_1_2, 0, 1, 2, 0x00, 0, 8}, +- {FLASH_FLAG_READ_FAST, SPINOR_OP_READ4_FAST, 0, 1, 1, 0x00, 0, 8}, +- {FLASH_FLAG_READ_WRITE, SPINOR_OP_READ4, 0, 1, 1, 0x00, 0, 0}, +- {0x00, 0, 0, 0, 0, 0x00, 0, 0}, ++ {FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B, 0, 4, 4, 0x00, 2, 4}, ++ {FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B, 0, 1, 4, 0x00, 0, 8}, ++ {FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B, 0, 2, 2, 0x00, 4, 0}, ++ {FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B, 0, 1, 2, 0x00, 0, 8}, ++ {FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST_4B, 0, 1, 1, 0x00, 0, 8}, ++ {FLASH_FLAG_READ_WRITE, SPINOR_OP_READ_4B, 0, 1, 1, 0x00, 0, 0}, ++ {0x00, 0, 0, 0, 0, 0x00, 0, 0}, + }; + + static struct seq_rw_config stfsm_s25fl_write4_configs[] = { +--- a/drivers/mtd/spi-nor/spi-nor.c ++++ b/drivers/mtd/spi-nor/spi-nor.c +@@ -69,6 +69,14 @@ struct flash_info { + #define SPI_NOR_QUAD_READ 0x40 /* Flash supports Quad Read */ + #define USE_FSR 0x80 /* use flag status register */ + #define SPI_NOR_HAS_LOCK 0x100 /* Flash supports lock/unlock via SR */ ++#define SPI_NOR_4B_OPCODES 0x200 /* ++ * Use dedicated 4byte address op codes ++ * to support memory size above 128Mib. ++ */ ++#define SPI_NOR_4B_READ_OP 0x400 /* ++ * Like SPI_NOR_4B_OPCODES, but for read ++ * op code only. ++ */ + }; + + #define JEDEC_MFR(info) ((info)->id[0]) +@@ -182,6 +190,89 @@ static inline struct spi_nor *mtd_to_spi + return mtd->priv; + } + ++ ++static u8 spi_nor_convert_opcode(u8 opcode, const u8 table[][2], size_t size) ++{ ++ size_t i; ++ ++ for (i = 0; i < size; i++) ++ if (table[i][0] == opcode) ++ return table[i][1]; ++ ++ /* No conversion found, keep input op code. */ ++ return opcode; ++} ++ ++static inline u8 spi_nor_convert_3to4_read(u8 opcode) ++{ ++ static const u8 spi_nor_3to4_read[][2] = { ++ { SPINOR_OP_READ, SPINOR_OP_READ_4B }, ++ { SPINOR_OP_READ_FAST, SPINOR_OP_READ_FAST_4B }, ++ { SPINOR_OP_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B }, ++ { SPINOR_OP_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B }, ++ { SPINOR_OP_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B }, ++ { SPINOR_OP_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B }, ++ }; ++ ++ return spi_nor_convert_opcode(opcode, spi_nor_3to4_read, ++ ARRAY_SIZE(spi_nor_3to4_read)); ++} ++ ++static inline u8 spi_nor_convert_3to4_program(u8 opcode) ++{ ++ static const u8 spi_nor_3to4_program[][2] = { ++ { SPINOR_OP_PP, SPINOR_OP_PP_4B }, ++ { SPINOR_OP_PP_1_1_4, SPINOR_OP_PP_1_1_4_4B }, ++ { SPINOR_OP_PP_1_4_4, SPINOR_OP_PP_1_4_4_4B }, ++ }; ++ ++ return spi_nor_convert_opcode(opcode, spi_nor_3to4_program, ++ ARRAY_SIZE(spi_nor_3to4_program)); ++} ++ ++static inline u8 spi_nor_convert_3to4_erase(u8 opcode) ++{ ++ static const u8 spi_nor_3to4_erase[][2] = { ++ { SPINOR_OP_BE_4K, SPINOR_OP_BE_4K_4B }, ++ { SPINOR_OP_BE_32K, SPINOR_OP_BE_32K_4B }, ++ { SPINOR_OP_SE, SPINOR_OP_SE_4B }, ++ }; ++ ++ return spi_nor_convert_opcode(opcode, spi_nor_3to4_erase, ++ 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) ++{ ++ /* Do some manufacturer fixups first */ ++ switch (JEDEC_MFR(info)) { ++ case SNOR_MFR_SPANSION: ++ /* No small sector erase for 4-byte command set */ ++ nor->erase_opcode = SPINOR_OP_SE; ++ nor->mtd.erasesize = info->sector_size; ++ break; ++ ++ default: ++ break; ++ } ++ ++ 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); ++} ++ + /* Enable/disable 4-byte addressing mode. */ + static inline int set_4byte(struct spi_nor *nor, const struct flash_info *info, + int enable) +@@ -313,6 +404,36 @@ static void spi_nor_unlock_and_unprep(st + mutex_unlock(&nor->lock); + } + ++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. +@@ -338,6 +459,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) { + unsigned long timeout; +@@ -396,6 +521,7 @@ static int spi_nor_erase(struct mtd_info + return ret; + + erase_err: ++ spi_nor_check_ext_addr(nor, 0); + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE); + instr->state = MTD_ERASE_FAILED; + return ret; +@@ -585,7 +711,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; +@@ -600,7 +728,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; +@@ -851,7 +981,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) }, +@@ -898,8 +1028,23 @@ static int spi_nor_read(struct mtd_info + if (ret) + return ret; + ++ if (nor->flags & SNOR_F_4B_EXT_ADDR) ++ nor->addr_width = 4; ++ + ret = nor->read(nor, from, len, retlen, buf); + ++ 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; + } +@@ -988,6 +1133,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; ++ + write_enable(nor); + + page_offset = to & (nor->page_size - 1); +@@ -1018,6 +1167,7 @@ static int spi_nor_write(struct mtd_info + + ret = spi_nor_wait_till_ready(nor); + write_err: ++ spi_nor_check_ext_addr(nor, 0); + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE); + return ret; + } +@@ -1366,27 +1516,12 @@ 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) { +- /* Dedicated 4-byte command set */ +- switch (nor->flash_read) { +- case SPI_NOR_QUAD: +- nor->read_opcode = SPINOR_OP_READ4_1_1_4; +- break; +- case SPI_NOR_DUAL: +- nor->read_opcode = SPINOR_OP_READ4_1_1_2; +- break; +- case SPI_NOR_FAST: +- nor->read_opcode = SPINOR_OP_READ4_FAST; +- break; +- case SPI_NOR_NORMAL: +- nor->read_opcode = SPINOR_OP_READ4; +- break; +- } +- nor->program_opcode = SPINOR_OP_PP_4B; +- /* No small sector erase for 4-byte command set */ +- nor->erase_opcode = SPINOR_OP_SE_4B; +- mtd->erasesize = info->sector_size; +- } else ++ 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); + } else { + nor->addr_width = 3; +--- a/include/linux/mtd/spi-nor.h ++++ b/include/linux/mtd/spi-nor.h +@@ -42,9 +42,13 @@ + #define SPINOR_OP_WRSR 0x01 /* Write status register 1 byte */ + #define SPINOR_OP_READ 0x03 /* Read data bytes (low frequency) */ + #define SPINOR_OP_READ_FAST 0x0b /* Read data bytes (high frequency) */ +-#define SPINOR_OP_READ_1_1_2 0x3b /* Read data bytes (Dual SPI) */ +-#define SPINOR_OP_READ_1_1_4 0x6b /* Read data bytes (Quad SPI) */ ++#define SPINOR_OP_READ_1_1_2 0x3b /* Read data bytes (Dual Output SPI) */ ++#define SPINOR_OP_READ_1_2_2 0xbb /* Read data bytes (Dual I/O SPI) */ ++#define SPINOR_OP_READ_1_1_4 0x6b /* Read data bytes (Quad Output SPI) */ ++#define SPINOR_OP_READ_1_4_4 0xeb /* Read data bytes (Quad I/O SPI) */ + #define SPINOR_OP_PP 0x02 /* Page program (up to 256 bytes) */ ++#define SPINOR_OP_PP_1_1_4 0x32 /* Quad page program */ ++#define SPINOR_OP_PP_1_4_4 0x38 /* Quad page program */ + #define SPINOR_OP_BE_4K 0x20 /* Erase 4KiB block */ + #define SPINOR_OP_BE_4K_PMC 0xd7 /* Erase 4KiB block on PMC chips */ + #define SPINOR_OP_BE_32K 0x52 /* Erase 32KiB block */ +@@ -55,11 +59,17 @@ + #define SPINOR_OP_RDFSR 0x70 /* Read flag status register */ + + /* 4-byte address opcodes - used on Spansion and some Macronix flashes. */ +-#define SPINOR_OP_READ4 0x13 /* Read data bytes (low frequency) */ +-#define SPINOR_OP_READ4_FAST 0x0c /* Read data bytes (high frequency) */ +-#define SPINOR_OP_READ4_1_1_2 0x3c /* Read data bytes (Dual SPI) */ +-#define SPINOR_OP_READ4_1_1_4 0x6c /* Read data bytes (Quad SPI) */ ++#define SPINOR_OP_READ_4B 0x13 /* Read data bytes (low frequency) */ ++#define SPINOR_OP_READ_FAST_4B 0x0c /* Read data bytes (high frequency) */ ++#define SPINOR_OP_READ_1_1_2_4B 0x3c /* Read data bytes (Dual Output SPI) */ ++#define SPINOR_OP_READ_1_2_2_4B 0xbc /* Read data bytes (Dual I/O SPI) */ ++#define SPINOR_OP_READ_1_1_4_4B 0x6c /* Read data bytes (Quad Output SPI) */ ++#define SPINOR_OP_READ_1_4_4_4B 0xec /* Read data bytes (Quad I/O SPI) */ + #define SPINOR_OP_PP_4B 0x12 /* Page program (up to 256 bytes) */ ++#define SPINOR_OP_PP_1_1_4_4B 0x34 /* Quad page program */ ++#define SPINOR_OP_PP_1_4_4_4B 0x3e /* Quad page program */ ++#define SPINOR_OP_BE_4K_4B 0x21 /* Erase 4KiB block */ ++#define SPINOR_OP_BE_32K_4B 0x5c /* Erase 32KiB block */ + #define SPINOR_OP_SE_4B 0xdc /* Sector erase (usually 64KiB) */ + + /* Used for SST flashes only. */ +@@ -70,6 +80,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 */ +@@ -117,6 +128,7 @@ enum spi_nor_ops { + enum spi_nor_option_flags { + SNOR_F_USE_FSR = BIT(0), + SNOR_F_SST = BIT(1), ++ SNOR_F_4B_EXT_ADDR = BIT(5), + }; + + /** +@@ -166,6 +178,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); diff --git a/target/linux/ramips/patches-4.4/401-mtd-add-chunked-read-io-to-m25p80.patch b/target/linux/ramips/patches-4.4/401-mtd-add-chunked-read-io-to-m25p80.patch new file mode 100644 index 0000000000000000000000000000000000000000..b6111abad7063951c07688c66854fa743daeb6c6 --- /dev/null +++ b/target/linux/ramips/patches-4.4/401-mtd-add-chunked-read-io-to-m25p80.patch @@ -0,0 +1,21 @@ +--- a/drivers/mtd/spi-nor/spi-nor.c ++++ b/drivers/mtd/spi-nor/spi-nor.c +@@ -1176,6 +1176,7 @@ static int spi_nor_chunked_write(struct + size_t *_retlen, const u_char *_buf) + { + 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 retlen = 0; + int ret; +@@ -1184,8 +1185,8 @@ static int spi_nor_chunked_write(struct + if (!chunk_size) + chunk_size = _len; + +- if (nor->addr_width > 3) +- chunk_size -= nor->addr_width - 3; ++ if (addr_width > 3) ++ chunk_size -= addr_width - 3; + + while (retlen < _len) { + size_t len = min_t(int, chunk_size, _len - retlen);