1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2005, Intec Automation Inc. |
4 | * Copyright (C) 2014, Freescale Semiconductor, Inc. |
5 | */ |
6 | |
7 | #include <linux/mtd/spi-nor.h> |
8 | |
9 | #include "core.h" |
10 | |
11 | #define WINBOND_NOR_OP_RDEAR 0xc8 /* Read Extended Address Register */ |
12 | #define WINBOND_NOR_OP_WREAR 0xc5 /* Write Extended Address Register */ |
13 | |
14 | #define WINBOND_NOR_WREAR_OP(buf) \ |
15 | SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_WREAR, 0), \ |
16 | SPI_MEM_OP_NO_ADDR, \ |
17 | SPI_MEM_OP_NO_DUMMY, \ |
18 | SPI_MEM_OP_DATA_OUT(1, buf, 0)) |
19 | |
20 | static int |
21 | w25q256_post_bfpt_fixups(struct spi_nor *nor, |
22 | const struct sfdp_parameter_header *, |
23 | const struct sfdp_bfpt *bfpt) |
24 | { |
25 | /* |
26 | * W25Q256JV supports 4B opcodes but W25Q256FV does not. |
27 | * Unfortunately, Winbond has re-used the same JEDEC ID for both |
28 | * variants which prevents us from defining a new entry in the parts |
29 | * table. |
30 | * To differentiate between W25Q256JV and W25Q256FV check SFDP header |
31 | * version: only JV has JESD216A compliant structure (version 5). |
32 | */ |
33 | if (bfpt_header->major == SFDP_JESD216_MAJOR && |
34 | bfpt_header->minor == SFDP_JESD216A_MINOR) |
35 | nor->flags |= SNOR_F_4B_OPCODES; |
36 | |
37 | return 0; |
38 | } |
39 | |
40 | static const struct spi_nor_fixups w25q256_fixups = { |
41 | .post_bfpt = w25q256_post_bfpt_fixups, |
42 | }; |
43 | |
44 | static const struct flash_info winbond_nor_parts[] = { |
45 | { |
46 | .id = SNOR_ID(0xef, 0x30, 0x10), |
47 | .name = "w25x05" , |
48 | .size = SZ_64K, |
49 | .no_sfdp_flags = SECT_4K, |
50 | }, { |
51 | .id = SNOR_ID(0xef, 0x30, 0x11), |
52 | .name = "w25x10" , |
53 | .size = SZ_128K, |
54 | .no_sfdp_flags = SECT_4K, |
55 | }, { |
56 | .id = SNOR_ID(0xef, 0x30, 0x12), |
57 | .name = "w25x20" , |
58 | .size = SZ_256K, |
59 | .no_sfdp_flags = SECT_4K, |
60 | }, { |
61 | .id = SNOR_ID(0xef, 0x30, 0x13), |
62 | .name = "w25x40" , |
63 | .size = SZ_512K, |
64 | .no_sfdp_flags = SECT_4K, |
65 | }, { |
66 | .id = SNOR_ID(0xef, 0x30, 0x14), |
67 | .name = "w25x80" , |
68 | .size = SZ_1M, |
69 | .no_sfdp_flags = SECT_4K, |
70 | }, { |
71 | .id = SNOR_ID(0xef, 0x30, 0x15), |
72 | .name = "w25x16" , |
73 | .size = SZ_2M, |
74 | .no_sfdp_flags = SECT_4K, |
75 | }, { |
76 | .id = SNOR_ID(0xef, 0x30, 0x16), |
77 | .name = "w25x32" , |
78 | .size = SZ_4M, |
79 | .no_sfdp_flags = SECT_4K, |
80 | }, { |
81 | .id = SNOR_ID(0xef, 0x30, 0x17), |
82 | .name = "w25x64" , |
83 | .size = SZ_8M, |
84 | .no_sfdp_flags = SECT_4K, |
85 | }, { |
86 | .id = SNOR_ID(0xef, 0x40, 0x12), |
87 | .name = "w25q20cl" , |
88 | .size = SZ_256K, |
89 | .no_sfdp_flags = SECT_4K, |
90 | }, { |
91 | .id = SNOR_ID(0xef, 0x40, 0x14), |
92 | .name = "w25q80bl" , |
93 | .size = SZ_1M, |
94 | .no_sfdp_flags = SECT_4K, |
95 | }, { |
96 | .id = SNOR_ID(0xef, 0x40, 0x16), |
97 | .name = "w25q32" , |
98 | .size = SZ_4M, |
99 | .no_sfdp_flags = SECT_4K, |
100 | }, { |
101 | .id = SNOR_ID(0xef, 0x40, 0x17), |
102 | .name = "w25q64" , |
103 | .size = SZ_8M, |
104 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
105 | }, { |
106 | .id = SNOR_ID(0xef, 0x40, 0x18), |
107 | .name = "w25q128" , |
108 | .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, |
109 | }, { |
110 | .id = SNOR_ID(0xef, 0x40, 0x19), |
111 | .name = "w25q256" , |
112 | .size = SZ_32M, |
113 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
114 | .fixups = &w25q256_fixups, |
115 | }, { |
116 | .id = SNOR_ID(0xef, 0x40, 0x20), |
117 | .name = "w25q512jvq" , |
118 | .size = SZ_64M, |
119 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
120 | }, { |
121 | .id = SNOR_ID(0xef, 0x50, 0x12), |
122 | .name = "w25q20bw" , |
123 | .size = SZ_256K, |
124 | .no_sfdp_flags = SECT_4K, |
125 | }, { |
126 | .id = SNOR_ID(0xef, 0x50, 0x14), |
127 | .name = "w25q80" , |
128 | .size = SZ_1M, |
129 | .no_sfdp_flags = SECT_4K, |
130 | }, { |
131 | .id = SNOR_ID(0xef, 0x60, 0x12), |
132 | .name = "w25q20ew" , |
133 | .size = SZ_256K, |
134 | .no_sfdp_flags = SECT_4K, |
135 | }, { |
136 | .id = SNOR_ID(0xef, 0x60, 0x15), |
137 | .name = "w25q16dw" , |
138 | .size = SZ_2M, |
139 | .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, |
140 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
141 | }, { |
142 | .id = SNOR_ID(0xef, 0x60, 0x16), |
143 | .name = "w25q32dw" , |
144 | .size = SZ_4M, |
145 | .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, |
146 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
147 | .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), |
148 | }, { |
149 | .id = SNOR_ID(0xef, 0x60, 0x17), |
150 | .name = "w25q64dw" , |
151 | .size = SZ_8M, |
152 | .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, |
153 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
154 | }, { |
155 | .id = SNOR_ID(0xef, 0x60, 0x18), |
156 | .name = "w25q128fw" , |
157 | .size = SZ_16M, |
158 | .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, |
159 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
160 | }, { |
161 | .id = SNOR_ID(0xef, 0x60, 0x19), |
162 | .name = "w25q256jw" , |
163 | .size = SZ_32M, |
164 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
165 | }, { |
166 | .id = SNOR_ID(0xef, 0x60, 0x20), |
167 | .name = "w25q512nwq" , |
168 | .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), |
169 | }, { |
170 | .id = SNOR_ID(0xef, 0x70, 0x15), |
171 | .name = "w25q16jv-im/jm" , |
172 | .size = SZ_2M, |
173 | .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, |
174 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
175 | }, { |
176 | .id = SNOR_ID(0xef, 0x70, 0x16), |
177 | .name = "w25q32jv" , |
178 | .size = SZ_4M, |
179 | .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, |
180 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
181 | }, { |
182 | .id = SNOR_ID(0xef, 0x70, 0x17), |
183 | .name = "w25q64jvm" , |
184 | .size = SZ_8M, |
185 | .no_sfdp_flags = SECT_4K, |
186 | }, { |
187 | .id = SNOR_ID(0xef, 0x70, 0x18), |
188 | .name = "w25q128jv" , |
189 | .size = SZ_16M, |
190 | .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, |
191 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
192 | }, { |
193 | .id = SNOR_ID(0xef, 0x70, 0x19), |
194 | .name = "w25q256jvm" , |
195 | }, { |
196 | .id = SNOR_ID(0xef, 0x71, 0x19), |
197 | .name = "w25m512jv" , |
198 | .size = SZ_64M, |
199 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
200 | }, { |
201 | .id = SNOR_ID(0xef, 0x80, 0x16), |
202 | .name = "w25q32jwm" , |
203 | .size = SZ_4M, |
204 | .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, |
205 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
206 | .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), |
207 | }, { |
208 | .id = SNOR_ID(0xef, 0x80, 0x17), |
209 | .name = "w25q64jwm" , |
210 | .size = SZ_8M, |
211 | .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, |
212 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
213 | }, { |
214 | .id = SNOR_ID(0xef, 0x80, 0x18), |
215 | .name = "w25q128jwm" , |
216 | .size = SZ_16M, |
217 | .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, |
218 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
219 | }, { |
220 | .id = SNOR_ID(0xef, 0x80, 0x19), |
221 | .name = "w25q256jwm" , |
222 | .size = SZ_32M, |
223 | .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, |
224 | .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, |
225 | }, { |
226 | .id = SNOR_ID(0xef, 0x80, 0x20), |
227 | .name = "w25q512nwm" , |
228 | .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), |
229 | }, |
230 | }; |
231 | |
232 | /** |
233 | * winbond_nor_write_ear() - Write Extended Address Register. |
234 | * @nor: pointer to 'struct spi_nor'. |
235 | * @ear: value to write to the Extended Address Register. |
236 | * |
237 | * Return: 0 on success, -errno otherwise. |
238 | */ |
239 | static int winbond_nor_write_ear(struct spi_nor *nor, u8 ear) |
240 | { |
241 | int ret; |
242 | |
243 | nor->bouncebuf[0] = ear; |
244 | |
245 | if (nor->spimem) { |
246 | struct spi_mem_op op = WINBOND_NOR_WREAR_OP(nor->bouncebuf); |
247 | |
248 | spi_nor_spimem_setup_op(nor, op: &op, proto: nor->reg_proto); |
249 | |
250 | ret = spi_mem_exec_op(mem: nor->spimem, op: &op); |
251 | } else { |
252 | ret = spi_nor_controller_ops_write_reg(nor, |
253 | WINBOND_NOR_OP_WREAR, |
254 | buf: nor->bouncebuf, len: 1); |
255 | } |
256 | |
257 | if (ret) |
258 | dev_dbg(nor->dev, "error %d writing EAR\n" , ret); |
259 | |
260 | return ret; |
261 | } |
262 | |
263 | /** |
264 | * winbond_nor_set_4byte_addr_mode() - Set 4-byte address mode for Winbond |
265 | * flashes. |
266 | * @nor: pointer to 'struct spi_nor'. |
267 | * @enable: true to enter the 4-byte address mode, false to exit the 4-byte |
268 | * address mode. |
269 | * |
270 | * Return: 0 on success, -errno otherwise. |
271 | */ |
272 | static int winbond_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable) |
273 | { |
274 | int ret; |
275 | |
276 | ret = spi_nor_set_4byte_addr_mode_en4b_ex4b(nor, enable); |
277 | if (ret || enable) |
278 | return ret; |
279 | |
280 | /* |
281 | * On Winbond W25Q256FV, leaving 4byte mode causes the Extended Address |
282 | * Register to be set to 1, so all 3-byte-address reads come from the |
283 | * second 16M. We must clear the register to enable normal behavior. |
284 | */ |
285 | ret = spi_nor_write_enable(nor); |
286 | if (ret) |
287 | return ret; |
288 | |
289 | ret = winbond_nor_write_ear(nor, ear: 0); |
290 | if (ret) |
291 | return ret; |
292 | |
293 | return spi_nor_write_disable(nor); |
294 | } |
295 | |
296 | static const struct spi_nor_otp_ops winbond_nor_otp_ops = { |
297 | .read = spi_nor_otp_read_secr, |
298 | .write = spi_nor_otp_write_secr, |
299 | .erase = spi_nor_otp_erase_secr, |
300 | .lock = spi_nor_otp_lock_sr2, |
301 | .is_locked = spi_nor_otp_is_locked_sr2, |
302 | }; |
303 | |
304 | static int winbond_nor_late_init(struct spi_nor *nor) |
305 | { |
306 | struct spi_nor_flash_parameter *params = nor->params; |
307 | |
308 | if (params->otp.org) |
309 | params->otp.ops = &winbond_nor_otp_ops; |
310 | |
311 | /* |
312 | * Winbond seems to require that the Extended Address Register to be set |
313 | * to zero when exiting the 4-Byte Address Mode, at least for W25Q256FV. |
314 | * This requirement is not described in the JESD216 SFDP standard, thus |
315 | * it is Winbond specific. Since we do not know if other Winbond flashes |
316 | * have the same requirement, play safe and overwrite the method parsed |
317 | * from BFPT, if any. |
318 | */ |
319 | params->set_4byte_addr_mode = winbond_nor_set_4byte_addr_mode; |
320 | |
321 | return 0; |
322 | } |
323 | |
324 | static const struct spi_nor_fixups winbond_nor_fixups = { |
325 | .late_init = winbond_nor_late_init, |
326 | }; |
327 | |
328 | const struct spi_nor_manufacturer spi_nor_winbond = { |
329 | .name = "winbond" , |
330 | .parts = winbond_nor_parts, |
331 | .nparts = ARRAY_SIZE(winbond_nor_parts), |
332 | .fixups = &winbond_nor_fixups, |
333 | }; |
334 | |