OpenWrt – Blame information for rev 3
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 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 | @@ -89,6 +89,10 @@ struct flash_info { |
||
17 | #define NO_CHIP_ERASE BIT(12) /* Chip does not support chip erase */ |
||
18 | #define SPI_NOR_SKIP_SFDP BIT(13) /* Skip parsing of SFDP tables */ |
||
19 | #define USE_CLSR BIT(14) /* use CLSR command */ |
||
20 | +#define SPI_NOR_4B_READ_OP BIT(15) /* |
||
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 | @@ -240,6 +244,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; |
||
3 | office | 119 | @@ -1164,7 +1216,7 @@ static const struct flash_info spi_nor_i |
1 | office | 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 | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, |
||
124 | + { "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_READ_OP) }, |
||
125 | { "w25m512jv", INFO(0xef7119, 0, 64 * 1024, 1024, |
||
126 | SECT_4K | SPI_NOR_QUAD_READ | SPI_NOR_DUAL_READ) }, |
||
127 | |||
3 | office | 128 | @@ -1224,6 +1276,9 @@ static int spi_nor_read(struct mtd_info |
1 | office | 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 | |||
3 | office | 138 | @@ -1248,6 +1303,18 @@ static int spi_nor_read(struct mtd_info |
1 | office | 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 | } |
||
3 | office | 157 | @@ -1349,6 +1416,10 @@ static int spi_nor_write(struct mtd_info |
1 | office | 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; |
||
3 | office | 168 | @@ -1389,6 +1460,7 @@ static int spi_nor_write(struct mtd_info |
1 | office | 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 | } |
||
3 | office | 176 | @@ -2805,8 +2877,10 @@ int spi_nor_scan(struct spi_nor *nor, co |
1 | office | 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 | @@ -102,6 +102,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 | @@ -229,6 +230,7 @@ enum spi_nor_option_flags { |
||
200 | SNOR_F_S3AN_ADDR_DEFAULT = BIT(3), |
||
201 | SNOR_F_READY_XSR_RDY = BIT(4), |
||
202 | SNOR_F_USE_CLSR = BIT(5), |
||
203 | + SNOR_F_4B_EXT_ADDR = BIT(6), |
||
204 | }; |
||
205 | |||
206 | /** |
||
207 | @@ -280,6 +282,7 @@ struct spi_nor { |
||
208 | enum spi_nor_protocol reg_proto; |
||
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); |