|
| 1 | +mtd: spi-nor: add support for switching between 3-byte and 4-byte addressing on w25q256 flash |
| 2 | + |
| 3 | +On some devices the flash chip needs to be in 3-byte addressing mode during |
| 4 | +reboot, otherwise the boot loader will fail to start. |
| 5 | +This mode however does not allow regular reads/writes onto the upper 16M |
| 6 | +half. W25Q256 has separate read commands for reading from >16M, however |
| 7 | +it does not have any separate write commands. |
| 8 | +This patch changes the code to leave the chip in 3-byte mode most of the |
| 9 | +time and only switch during erase/write cycles that go to >16M |
| 10 | +addresses. |
| 11 | + |
| 12 | +Signed-off-by: Felix Fietkau <nbd@nbd.name> |
| 13 | +--- |
| 14 | +--- a/drivers/mtd/spi-nor/spi-nor.c |
| 15 | ++++ b/drivers/mtd/spi-nor/spi-nor.c |
| 16 | +@@ -85,6 +85,10 @@ struct flash_info { |
| 17 | + * Use dedicated 4byte address op codes |
| 18 | + * to support memory size above 128Mib. |
| 19 | + */ |
| 20 | ++#define SPI_NOR_4B_READ_OP BIT(12) /* |
| 21 | ++ * Like SPI_NOR_4B_OPCODES, but for read |
| 22 | ++ * op code only. |
| 23 | ++ */ |
| 24 | + }; |
| 25 | + |
| 26 | + #define JEDEC_MFR(info) ((info)->id[0]) |
| 27 | +@@ -250,6 +254,15 @@ static inline u8 spi_nor_convert_3to4_er |
| 28 | + ARRAY_SIZE(spi_nor_3to4_erase)); |
| 29 | + } |
| 30 | + |
| 31 | ++static void spi_nor_set_4byte_read(struct spi_nor *nor, |
| 32 | ++ const struct flash_info *info) |
| 33 | ++{ |
| 34 | ++ nor->addr_width = 3; |
| 35 | ++ nor->ext_addr = 0; |
| 36 | ++ nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode); |
| 37 | ++ nor->flags |= SNOR_F_4B_EXT_ADDR; |
| 38 | ++} |
| 39 | ++ |
| 40 | + static void spi_nor_set_4byte_opcodes(struct spi_nor *nor, |
| 41 | + const struct flash_info *info) |
| 42 | + { |
| 43 | +@@ -467,6 +480,36 @@ static int spi_nor_erase_sector(struct s |
| 44 | + return nor->write_reg(nor, nor->erase_opcode, buf, nor->addr_width); |
| 45 | + } |
| 46 | + |
| 47 | ++static int spi_nor_check_ext_addr(struct spi_nor *nor, u32 addr) |
| 48 | ++{ |
| 49 | ++ bool ext_addr; |
| 50 | ++ int ret; |
| 51 | ++ u8 cmd; |
| 52 | ++ |
| 53 | ++ if (!(nor->flags & SNOR_F_4B_EXT_ADDR)) |
| 54 | ++ return 0; |
| 55 | ++ |
| 56 | ++ ext_addr = !!(addr & 0xff000000); |
| 57 | ++ if (nor->ext_addr == ext_addr) |
| 58 | ++ return 0; |
| 59 | ++ |
| 60 | ++ cmd = ext_addr ? SPINOR_OP_EN4B : SPINOR_OP_EX4B; |
| 61 | ++ write_enable(nor); |
| 62 | ++ ret = nor->write_reg(nor, cmd, NULL, 0); |
| 63 | ++ if (ret) |
| 64 | ++ return ret; |
| 65 | ++ |
| 66 | ++ cmd = 0; |
| 67 | ++ ret = nor->write_reg(nor, SPINOR_OP_WREAR, &cmd, 1); |
| 68 | ++ if (ret) |
| 69 | ++ return ret; |
| 70 | ++ |
| 71 | ++ nor->addr_width = 3 + ext_addr; |
| 72 | ++ nor->ext_addr = ext_addr; |
| 73 | ++ write_disable(nor); |
| 74 | ++ return 0; |
| 75 | ++} |
| 76 | ++ |
| 77 | + /* |
| 78 | + * Erase an address range on the nor chip. The address range may extend |
| 79 | + * one or more erase sectors. Return an error is there is a problem erasing. |
| 80 | +@@ -492,6 +535,10 @@ static int spi_nor_erase(struct mtd_info |
| 81 | + if (ret) |
| 82 | + return ret; |
| 83 | + |
| 84 | ++ ret = spi_nor_check_ext_addr(nor, addr + len); |
| 85 | ++ if (ret) |
| 86 | ++ return ret; |
| 87 | ++ |
| 88 | + /* whole-chip erase? */ |
| 89 | + if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) { |
| 90 | + unsigned long timeout; |
| 91 | +@@ -542,6 +589,7 @@ static int spi_nor_erase(struct mtd_info |
| 92 | + write_disable(nor); |
| 93 | + |
| 94 | + erase_err: |
| 95 | ++ spi_nor_check_ext_addr(nor, 0); |
| 96 | + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE); |
| 97 | + |
| 98 | + instr->state = ret ? MTD_ERASE_FAILED : MTD_ERASE_DONE; |
| 99 | +@@ -834,7 +882,9 @@ static int spi_nor_lock(struct mtd_info |
| 100 | + if (ret) |
| 101 | + return ret; |
| 102 | + |
| 103 | ++ spi_nor_check_ext_addr(nor, ofs + len); |
| 104 | + ret = nor->flash_lock(nor, ofs, len); |
| 105 | ++ spi_nor_check_ext_addr(nor, 0); |
| 106 | + |
| 107 | + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_UNLOCK); |
| 108 | + return ret; |
| 109 | +@@ -849,7 +899,9 @@ static int spi_nor_unlock(struct mtd_inf |
| 110 | + if (ret) |
| 111 | + return ret; |
| 112 | + |
| 113 | ++ spi_nor_check_ext_addr(nor, ofs + len); |
| 114 | + ret = nor->flash_unlock(nor, ofs, len); |
| 115 | ++ spi_nor_check_ext_addr(nor, 0); |
| 116 | + |
| 117 | + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK); |
| 118 | + return ret; |
| 119 | +@@ -1151,7 +1203,7 @@ static const struct flash_info spi_nor_i |
| 120 | + { "w25q80", INFO(0xef5014, 0, 64 * 1024, 16, SECT_4K) }, |
| 121 | + { "w25q80bl", INFO(0xef4014, 0, 64 * 1024, 16, SECT_4K) }, |
| 122 | + { "w25q128", INFO(0xef4018, 0, 64 * 1024, 256, SECT_4K) }, |
| 123 | +- { "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K) }, |
| 124 | ++ { "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_4B_READ_OP) }, |
| 125 | + |
| 126 | + /* Catalyst / On Semiconductor -- non-JEDEC */ |
| 127 | + { "cat25c11", CAT25_INFO( 16, 8, 16, 1, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, |
| 128 | +@@ -1205,6 +1257,9 @@ static int spi_nor_read(struct mtd_info |
| 129 | + if (ret) |
| 130 | + return ret; |
| 131 | + |
| 132 | ++ if (nor->flags & SNOR_F_4B_EXT_ADDR) |
| 133 | ++ nor->addr_width = 4; |
| 134 | ++ |
| 135 | + while (len) { |
| 136 | + loff_t addr = from; |
| 137 | + |
| 138 | +@@ -1229,6 +1284,18 @@ static int spi_nor_read(struct mtd_info |
| 139 | + ret = 0; |
| 140 | + |
| 141 | + read_err: |
| 142 | ++ if (nor->flags & SNOR_F_4B_EXT_ADDR) { |
| 143 | ++ u8 val = 0; |
| 144 | ++ |
| 145 | ++ if ((from + len) & 0xff000000) { |
| 146 | ++ write_enable(nor); |
| 147 | ++ nor->write_reg(nor, SPINOR_OP_WREAR, &val, 1); |
| 148 | ++ write_disable(nor); |
| 149 | ++ } |
| 150 | ++ |
| 151 | ++ nor->addr_width = 3; |
| 152 | ++ } |
| 153 | ++ |
| 154 | + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ); |
| 155 | + return ret; |
| 156 | + } |
| 157 | +@@ -1330,6 +1397,10 @@ static int spi_nor_write(struct mtd_info |
| 158 | + if (ret) |
| 159 | + return ret; |
| 160 | + |
| 161 | ++ ret = spi_nor_check_ext_addr(nor, to + len); |
| 162 | ++ if (ret < 0) |
| 163 | ++ return ret; |
| 164 | ++ |
| 165 | + for (i = 0; i < len; ) { |
| 166 | + ssize_t written; |
| 167 | + loff_t addr = to + i; |
| 168 | +@@ -1377,6 +1448,7 @@ static int spi_nor_write(struct mtd_info |
| 169 | + } |
| 170 | + |
| 171 | + write_err: |
| 172 | ++ spi_nor_check_ext_addr(nor, 0); |
| 173 | + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE); |
| 174 | + return ret; |
| 175 | + } |
| 176 | +@@ -1715,8 +1787,10 @@ int spi_nor_scan(struct spi_nor *nor, co |
| 177 | + else if (mtd->size > 0x1000000) { |
| 178 | + /* enable 4-byte addressing if the device exceeds 16MiB */ |
| 179 | + nor->addr_width = 4; |
| 180 | +- if (JEDEC_MFR(info) == SNOR_MFR_SPANSION || |
| 181 | +- info->flags & SPI_NOR_4B_OPCODES) |
| 182 | ++ if (info->flags & SPI_NOR_4B_READ_OP) |
| 183 | ++ spi_nor_set_4byte_read(nor, info); |
| 184 | ++ else if (JEDEC_MFR(info) == SNOR_MFR_SPANSION || |
| 185 | ++ info->flags & SPI_NOR_4B_OPCODES) |
| 186 | + spi_nor_set_4byte_opcodes(nor, info); |
| 187 | + else |
| 188 | + set_4byte(nor, info, 1); |
| 189 | +--- a/include/linux/mtd/spi-nor.h |
| 190 | ++++ b/include/linux/mtd/spi-nor.h |
| 191 | +@@ -90,6 +90,7 @@ |
| 192 | + /* Used for Macronix and Winbond flashes. */ |
| 193 | + #define SPINOR_OP_EN4B 0xb7 /* Enter 4-byte mode */ |
| 194 | + #define SPINOR_OP_EX4B 0xe9 /* Exit 4-byte mode */ |
| 195 | ++#define SPINOR_OP_WREAR 0xc5 /* Write extended address register */ |
| 196 | + |
| 197 | + /* Used for Spansion flashes only. */ |
| 198 | + #define SPINOR_OP_BRWR 0x17 /* Bank register write */ |
| 199 | +@@ -141,6 +142,7 @@ enum spi_nor_option_flags { |
| 200 | + SNOR_F_NO_OP_CHIP_ERASE = BIT(2), |
| 201 | + SNOR_F_S3AN_ADDR_DEFAULT = BIT(3), |
| 202 | + SNOR_F_READY_XSR_RDY = BIT(4), |
| 203 | ++ SNOR_F_4B_EXT_ADDR = BIT(5), |
| 204 | + }; |
| 205 | + |
| 206 | + /** |
| 207 | +@@ -188,6 +190,7 @@ struct spi_nor { |
| 208 | + enum read_mode flash_read; |
| 209 | + bool sst_write_second; |
| 210 | + u32 flags; |
| 211 | ++ u8 ext_addr; |
| 212 | + u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE]; |
| 213 | + |
| 214 | + int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops); |
0 commit comments