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#define WINBOND_NOR_OP_SELDIE 0xc2 /* Select active die */
14
15#define WINBOND_NOR_WREAR_OP(buf) \
16 SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_WREAR, 0), \
17 SPI_MEM_OP_NO_ADDR, \
18 SPI_MEM_OP_NO_DUMMY, \
19 SPI_MEM_OP_DATA_OUT(1, buf, 0))
20
21#define WINBOND_NOR_SELDIE_OP(buf) \
22 SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_SELDIE, 0), \
23 SPI_MEM_OP_NO_ADDR, \
24 SPI_MEM_OP_NO_DUMMY, \
25 SPI_MEM_OP_DATA_OUT(1, buf, 0))
26
27static int
28w25q128_post_bfpt_fixups(struct spi_nor *nor,
29 const struct sfdp_parameter_header *bfpt_header,
30 const struct sfdp_bfpt *bfpt)
31{
32 /*
33 * Zetta ZD25Q128C is a clone of the Winbond device. But the encoded
34 * size is really wrong. It seems that they confused Mbit with MiB.
35 * Thus the flash is discovered as a 2MiB device.
36 */
37 if (bfpt_header->major == SFDP_JESD216_MAJOR &&
38 bfpt_header->minor == SFDP_JESD216_MINOR &&
39 nor->params->size == SZ_2M &&
40 nor->params->erase_map.regions[0].size == SZ_2M) {
41 nor->params->size = SZ_16M;
42 nor->params->erase_map.regions[0].size = SZ_16M;
43 }
44
45 return 0;
46}
47
48static const struct spi_nor_fixups w25q128_fixups = {
49 .post_bfpt = w25q128_post_bfpt_fixups,
50};
51
52static int
53w25q256_post_bfpt_fixups(struct spi_nor *nor,
54 const struct sfdp_parameter_header *bfpt_header,
55 const struct sfdp_bfpt *bfpt)
56{
57 /*
58 * W25Q256JV supports 4B opcodes but W25Q256FV does not.
59 * Unfortunately, Winbond has re-used the same JEDEC ID for both
60 * variants which prevents us from defining a new entry in the parts
61 * table.
62 * To differentiate between W25Q256JV and W25Q256FV check SFDP header
63 * version: only JV has JESD216A compliant structure (version 5).
64 */
65 if (bfpt_header->major == SFDP_JESD216_MAJOR &&
66 bfpt_header->minor == SFDP_JESD216A_MINOR)
67 nor->flags |= SNOR_F_4B_OPCODES;
68
69 return 0;
70}
71
72static const struct spi_nor_fixups w25q256_fixups = {
73 .post_bfpt = w25q256_post_bfpt_fixups,
74};
75
76/**
77 * winbond_nor_select_die() - Set active die.
78 * @nor: pointer to 'struct spi_nor'.
79 * @die: die to set active.
80 *
81 * Certain Winbond chips feature more than a single die. This is mostly hidden
82 * to the user, except that some chips may experience time deviation when
83 * modifying the status bits between dies, which in some corner cases may
84 * produce problematic races. Being able to explicitly select a die to check its
85 * state in this case may be useful.
86 *
87 * Return: 0 on success, -errno otherwise.
88 */
89static int winbond_nor_select_die(struct spi_nor *nor, u8 die)
90{
91 int ret;
92
93 nor->bouncebuf[0] = die;
94
95 if (nor->spimem) {
96 struct spi_mem_op op = WINBOND_NOR_SELDIE_OP(nor->bouncebuf);
97
98 spi_nor_spimem_setup_op(nor, op: &op, proto: nor->reg_proto);
99
100 ret = spi_mem_exec_op(mem: nor->spimem, op: &op);
101 } else {
102 ret = spi_nor_controller_ops_write_reg(nor,
103 WINBOND_NOR_OP_SELDIE,
104 buf: nor->bouncebuf, len: 1);
105 }
106
107 if (ret)
108 dev_dbg(nor->dev, "error %d selecting die %d\n", ret, die);
109
110 return ret;
111}
112
113static int winbond_nor_multi_die_ready(struct spi_nor *nor)
114{
115 int ret, i;
116
117 for (i = 0; i < nor->params->n_dice; i++) {
118 ret = winbond_nor_select_die(nor, die: i);
119 if (ret)
120 return ret;
121
122 ret = spi_nor_sr_ready(nor);
123 if (ret <= 0)
124 return ret;
125 }
126
127 return 1;
128}
129
130static int
131winbond_nor_multi_die_post_sfdp_fixups(struct spi_nor *nor)
132{
133 /*
134 * SFDP supports dice numbers, but this information is only available in
135 * optional additional tables which are not provided by these chips.
136 * Dice number has an impact though, because these devices need extra
137 * care when reading the busy bit.
138 */
139 nor->params->n_dice = nor->params->size / SZ_64M;
140 nor->params->ready = winbond_nor_multi_die_ready;
141
142 return 0;
143}
144
145static const struct spi_nor_fixups winbond_nor_multi_die_fixups = {
146 .post_sfdp = winbond_nor_multi_die_post_sfdp_fixups,
147};
148
149static const struct flash_info winbond_nor_parts[] = {
150 {
151 .id = SNOR_ID(0xef, 0x30, 0x10),
152 .name = "w25x05",
153 .size = SZ_64K,
154 .no_sfdp_flags = SECT_4K,
155 }, {
156 .id = SNOR_ID(0xef, 0x30, 0x11),
157 .name = "w25x10",
158 .size = SZ_128K,
159 .no_sfdp_flags = SECT_4K,
160 }, {
161 .id = SNOR_ID(0xef, 0x30, 0x12),
162 .name = "w25x20",
163 .size = SZ_256K,
164 .no_sfdp_flags = SECT_4K,
165 }, {
166 .id = SNOR_ID(0xef, 0x30, 0x13),
167 .name = "w25x40",
168 .size = SZ_512K,
169 .no_sfdp_flags = SECT_4K,
170 }, {
171 .id = SNOR_ID(0xef, 0x30, 0x14),
172 .name = "w25x80",
173 .size = SZ_1M,
174 .no_sfdp_flags = SECT_4K,
175 }, {
176 .id = SNOR_ID(0xef, 0x30, 0x15),
177 .name = "w25x16",
178 .size = SZ_2M,
179 .no_sfdp_flags = SECT_4K,
180 }, {
181 .id = SNOR_ID(0xef, 0x30, 0x16),
182 .name = "w25x32",
183 .size = SZ_4M,
184 .no_sfdp_flags = SECT_4K,
185 }, {
186 .id = SNOR_ID(0xef, 0x30, 0x17),
187 .name = "w25x64",
188 .size = SZ_8M,
189 .no_sfdp_flags = SECT_4K,
190 }, {
191 .id = SNOR_ID(0xef, 0x40, 0x12),
192 .name = "w25q20cl",
193 .size = SZ_256K,
194 .no_sfdp_flags = SECT_4K,
195 }, {
196 .id = SNOR_ID(0xef, 0x40, 0x14),
197 .name = "w25q80bl",
198 .size = SZ_1M,
199 .no_sfdp_flags = SECT_4K,
200 }, {
201 .id = SNOR_ID(0xef, 0x40, 0x16),
202 .name = "w25q32",
203 .size = SZ_4M,
204 .no_sfdp_flags = SECT_4K,
205 }, {
206 .id = SNOR_ID(0xef, 0x40, 0x17),
207 .name = "w25q64",
208 .size = SZ_8M,
209 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
210 }, {
211 .id = SNOR_ID(0xef, 0x40, 0x18),
212 /* Flavors w/ and w/o SFDP. */
213 .name = "w25q128",
214 .size = SZ_16M,
215 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
216 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
217 .fixups = &w25q128_fixups,
218 }, {
219 .id = SNOR_ID(0xef, 0x40, 0x19),
220 .name = "w25q256",
221 .size = SZ_32M,
222 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
223 .fixups = &w25q256_fixups,
224 }, {
225 .id = SNOR_ID(0xef, 0x40, 0x20),
226 .name = "w25q512jvq",
227 .size = SZ_64M,
228 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
229 }, {
230 /* W25Q01JV */
231 .id = SNOR_ID(0xef, 0x40, 0x21),
232 .fixups = &winbond_nor_multi_die_fixups,
233 }, {
234 .id = SNOR_ID(0xef, 0x50, 0x12),
235 .name = "w25q20bw",
236 .size = SZ_256K,
237 .no_sfdp_flags = SECT_4K,
238 }, {
239 .id = SNOR_ID(0xef, 0x50, 0x14),
240 .name = "w25q80",
241 .size = SZ_1M,
242 .no_sfdp_flags = SECT_4K,
243 }, {
244 .id = SNOR_ID(0xef, 0x60, 0x12),
245 .name = "w25q20ew",
246 .size = SZ_256K,
247 .no_sfdp_flags = SECT_4K,
248 }, {
249 .id = SNOR_ID(0xef, 0x60, 0x15),
250 .name = "w25q16dw",
251 .size = SZ_2M,
252 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
253 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
254 }, {
255 .id = SNOR_ID(0xef, 0x60, 0x16),
256 .name = "w25q32dw",
257 .size = SZ_4M,
258 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
259 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
260 .otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
261 }, {
262 .id = SNOR_ID(0xef, 0x60, 0x17),
263 .name = "w25q64dw",
264 .size = SZ_8M,
265 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
266 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
267 }, {
268 .id = SNOR_ID(0xef, 0x60, 0x18),
269 .name = "w25q128fw",
270 .size = SZ_16M,
271 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
272 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
273 }, {
274 .id = SNOR_ID(0xef, 0x60, 0x19),
275 .name = "w25q256jw",
276 .size = SZ_32M,
277 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
278 }, {
279 .id = SNOR_ID(0xef, 0x60, 0x20),
280 .name = "w25q512nwq",
281 .otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
282 }, {
283 .id = SNOR_ID(0xef, 0x70, 0x15),
284 .name = "w25q16jv-im/jm",
285 .size = SZ_2M,
286 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
287 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
288 }, {
289 .id = SNOR_ID(0xef, 0x70, 0x16),
290 .name = "w25q32jv",
291 .size = SZ_4M,
292 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
293 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
294 }, {
295 .id = SNOR_ID(0xef, 0x70, 0x17),
296 .name = "w25q64jvm",
297 .size = SZ_8M,
298 .no_sfdp_flags = SECT_4K,
299 }, {
300 .id = SNOR_ID(0xef, 0x70, 0x18),
301 .name = "w25q128jv",
302 .size = SZ_16M,
303 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
304 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
305 }, {
306 .id = SNOR_ID(0xef, 0x70, 0x19),
307 .name = "w25q256jvm",
308 }, {
309 /* W25Q02JV */
310 .id = SNOR_ID(0xef, 0x70, 0x22),
311 .fixups = &winbond_nor_multi_die_fixups,
312 }, {
313 .id = SNOR_ID(0xef, 0x71, 0x19),
314 .name = "w25m512jv",
315 .size = SZ_64M,
316 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
317 }, {
318 .id = SNOR_ID(0xef, 0x80, 0x16),
319 .name = "w25q32jwm",
320 .size = SZ_4M,
321 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
322 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
323 .otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
324 }, {
325 .id = SNOR_ID(0xef, 0x80, 0x17),
326 .name = "w25q64jwm",
327 .size = SZ_8M,
328 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
329 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
330 }, {
331 .id = SNOR_ID(0xef, 0x80, 0x18),
332 .name = "w25q128jwm",
333 .size = SZ_16M,
334 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
335 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
336 }, {
337 .id = SNOR_ID(0xef, 0x80, 0x19),
338 .name = "w25q256jwm",
339 .size = SZ_32M,
340 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
341 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
342 }, {
343 .id = SNOR_ID(0xef, 0x80, 0x20),
344 .name = "w25q512nwm",
345 .otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
346 },
347};
348
349/**
350 * winbond_nor_write_ear() - Write Extended Address Register.
351 * @nor: pointer to 'struct spi_nor'.
352 * @ear: value to write to the Extended Address Register.
353 *
354 * Return: 0 on success, -errno otherwise.
355 */
356static int winbond_nor_write_ear(struct spi_nor *nor, u8 ear)
357{
358 int ret;
359
360 nor->bouncebuf[0] = ear;
361
362 if (nor->spimem) {
363 struct spi_mem_op op = WINBOND_NOR_WREAR_OP(nor->bouncebuf);
364
365 spi_nor_spimem_setup_op(nor, op: &op, proto: nor->reg_proto);
366
367 ret = spi_mem_exec_op(mem: nor->spimem, op: &op);
368 } else {
369 ret = spi_nor_controller_ops_write_reg(nor,
370 WINBOND_NOR_OP_WREAR,
371 buf: nor->bouncebuf, len: 1);
372 }
373
374 if (ret)
375 dev_dbg(nor->dev, "error %d writing EAR\n", ret);
376
377 return ret;
378}
379
380/**
381 * winbond_nor_set_4byte_addr_mode() - Set 4-byte address mode for Winbond
382 * flashes.
383 * @nor: pointer to 'struct spi_nor'.
384 * @enable: true to enter the 4-byte address mode, false to exit the 4-byte
385 * address mode.
386 *
387 * Return: 0 on success, -errno otherwise.
388 */
389static int winbond_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
390{
391 int ret;
392
393 ret = spi_nor_set_4byte_addr_mode_en4b_ex4b(nor, enable);
394 if (ret || enable)
395 return ret;
396
397 /*
398 * On Winbond W25Q256FV, leaving 4byte mode causes the Extended Address
399 * Register to be set to 1, so all 3-byte-address reads come from the
400 * second 16M. We must clear the register to enable normal behavior.
401 */
402 ret = spi_nor_write_enable(nor);
403 if (ret)
404 return ret;
405
406 ret = winbond_nor_write_ear(nor, ear: 0);
407 if (ret)
408 return ret;
409
410 return spi_nor_write_disable(nor);
411}
412
413static const struct spi_nor_otp_ops winbond_nor_otp_ops = {
414 .read = spi_nor_otp_read_secr,
415 .write = spi_nor_otp_write_secr,
416 .erase = spi_nor_otp_erase_secr,
417 .lock = spi_nor_otp_lock_sr2,
418 .is_locked = spi_nor_otp_is_locked_sr2,
419};
420
421static int winbond_nor_late_init(struct spi_nor *nor)
422{
423 struct spi_nor_flash_parameter *params = nor->params;
424
425 if (params->otp.org)
426 params->otp.ops = &winbond_nor_otp_ops;
427
428 /*
429 * Winbond seems to require that the Extended Address Register to be set
430 * to zero when exiting the 4-Byte Address Mode, at least for W25Q256FV.
431 * This requirement is not described in the JESD216 SFDP standard, thus
432 * it is Winbond specific. Since we do not know if other Winbond flashes
433 * have the same requirement, play safe and overwrite the method parsed
434 * from BFPT, if any.
435 */
436 params->set_4byte_addr_mode = winbond_nor_set_4byte_addr_mode;
437
438 return 0;
439}
440
441static const struct spi_nor_fixups winbond_nor_fixups = {
442 .late_init = winbond_nor_late_init,
443};
444
445const struct spi_nor_manufacturer spi_nor_winbond = {
446 .name = "winbond",
447 .parts = winbond_nor_parts,
448 .nparts = ARRAY_SIZE(winbond_nor_parts),
449 .fixups = &winbond_nor_fixups,
450};
451

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of linux/drivers/mtd/spi-nor/winbond.c