File Index
Files and directories
Sign in
FILE

winbond.c

File preview with syntax highlighting.

linux-6.19.14/drivers/mtd/spi-nor/winbond.c C
  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, nor->reg_proto);
 99
100		ret = spi_mem_exec_op(nor->spimem, &op);
101	} else {
102		ret = spi_nor_controller_ops_write_reg(nor,
103						       WINBOND_NOR_OP_SELDIE,
104						       nor->bouncebuf, 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, 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		/* W25Q01NWxxIQ */
348		.id = SNOR_ID(0xef, 0x60, 0x21),
349		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
350	}, {
351		/* W25Q01NWxxIM */
352		.id = SNOR_ID(0xef, 0x80, 0x21),
353		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
354	}, {
355		/* W25Q02NWxxIM */
356		.id = SNOR_ID(0xef, 0x80, 0x22),
357		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
358	}, {
359		/* W25H512NWxxAM */
360		.id = SNOR_ID(0xef, 0xa0, 0x20),
361		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
362	}, {
363		/* W25H01NWxxAM */
364		.id = SNOR_ID(0xef, 0xa0, 0x21),
365		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
366	}, {
367		/* W25H02NWxxAM */
368		.id = SNOR_ID(0xef, 0xa0, 0x22),
369		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
370	},
371};
372
373/**
374 * winbond_nor_write_ear() - Write Extended Address Register.
375 * @nor:	pointer to 'struct spi_nor'.
376 * @ear:	value to write to the Extended Address Register.
377 *
378 * Return: 0 on success, -errno otherwise.
379 */
380static int winbond_nor_write_ear(struct spi_nor *nor, u8 ear)
381{
382	int ret;
383
384	nor->bouncebuf[0] = ear;
385
386	if (nor->spimem) {
387		struct spi_mem_op op = WINBOND_NOR_WREAR_OP(nor->bouncebuf);
388
389		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
390
391		ret = spi_mem_exec_op(nor->spimem, &op);
392	} else {
393		ret = spi_nor_controller_ops_write_reg(nor,
394						       WINBOND_NOR_OP_WREAR,
395						       nor->bouncebuf, 1);
396	}
397
398	if (ret)
399		dev_dbg(nor->dev, "error %d writing EAR\n", ret);
400
401	return ret;
402}
403
404/**
405 * winbond_nor_set_4byte_addr_mode() - Set 4-byte address mode for Winbond
406 * flashes.
407 * @nor:	pointer to 'struct spi_nor'.
408 * @enable:	true to enter the 4-byte address mode, false to exit the 4-byte
409 *		address mode.
410 *
411 * Return: 0 on success, -errno otherwise.
412 */
413static int winbond_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
414{
415	int ret;
416
417	ret = spi_nor_set_4byte_addr_mode_en4b_ex4b(nor, enable);
418	if (ret || enable)
419		return ret;
420
421	/*
422	 * On Winbond W25Q256FV, leaving 4byte mode causes the Extended Address
423	 * Register to be set to 1, so all 3-byte-address reads come from the
424	 * second 16M. We must clear the register to enable normal behavior.
425	 */
426	ret = spi_nor_write_enable(nor);
427	if (ret)
428		return ret;
429
430	ret = winbond_nor_write_ear(nor, 0);
431	if (ret)
432		return ret;
433
434	return spi_nor_write_disable(nor);
435}
436
437static const struct spi_nor_otp_ops winbond_nor_otp_ops = {
438	.read = spi_nor_otp_read_secr,
439	.write = spi_nor_otp_write_secr,
440	.erase = spi_nor_otp_erase_secr,
441	.lock = spi_nor_otp_lock_sr2,
442	.is_locked = spi_nor_otp_is_locked_sr2,
443};
444
445static int winbond_nor_late_init(struct spi_nor *nor)
446{
447	struct spi_nor_flash_parameter *params = nor->params;
448
449	if (params->otp.org)
450		params->otp.ops = &winbond_nor_otp_ops;
451
452	/*
453	 * Winbond seems to require that the Extended Address Register to be set
454	 * to zero when exiting the 4-Byte Address Mode, at least for W25Q256FV.
455	 * This requirement is not described in the JESD216 SFDP standard, thus
456	 * it is Winbond specific. Since we do not know if other Winbond flashes
457	 * have the same requirement, play safe and overwrite the method parsed
458	 * from BFPT, if any.
459	 */
460	params->set_4byte_addr_mode = winbond_nor_set_4byte_addr_mode;
461
462	return 0;
463}
464
465static const struct spi_nor_fixups winbond_nor_fixups = {
466	.late_init = winbond_nor_late_init,
467};
468
469const struct spi_nor_manufacturer spi_nor_winbond = {
470	.name = "winbond",
471	.parts = winbond_nor_parts,
472	.nparts = ARRAY_SIZE(winbond_nor_parts),
473	.fixups = &winbond_nor_fixups,
474};
~/ Read-only file preview.