xref: /aosp_15_r20/external/coreboot/src/drivers/spi/winbond.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
3 #include <console/console.h>
4 #include <commonlib/helpers.h>
5 #include <spi_flash.h>
6 #include <spi-generic.h>
7 #include <delay.h>
8 #include <lib.h>
9 
10 #include "spi_flash_internal.h"
11 #include "spi_winbond.h"
12 
13 union status_reg1 {
14 	uint8_t u;
15 	struct {
16 		uint8_t busy : 1;
17 		uint8_t wel  : 1;
18 		uint8_t bp   : 3;
19 		uint8_t tb   : 1;
20 		uint8_t sec  : 1;
21 		uint8_t srp0 : 1;
22 	} bp3; /* for example: W25Q128FW */
23 	struct {
24 		uint8_t busy : 1;
25 		uint8_t wel  : 1;
26 		uint8_t bp   : 4;
27 		uint8_t tb   : 1;
28 		uint8_t srp0 : 1;
29 	} bp4; /* for example: W25Q256J */
30 };
31 
32 union status_reg2 {
33 	uint8_t u;
34 	struct {
35 		uint8_t srp1 : 1;
36 		uint8_t   qe : 1;
37 		uint8_t  res : 1;
38 		uint8_t   lb : 3;
39 		uint8_t  cmp : 1;
40 		uint8_t  sus : 1;
41 	};
42 };
43 
44 struct status_regs {
45 	union {
46 		struct {
47 #if defined(__BIG_ENDIAN)
48 			union status_reg2 reg2;
49 			union status_reg1 reg1;
50 #else
51 			union status_reg1 reg1;
52 			union status_reg2 reg2;
53 #endif
54 		};
55 		u16 u;
56 	};
57 };
58 
59 static const struct spi_flash_part_id flash_table[] = {
60 	{
61 		/* W25P80 */
62 		.id[0]				= 0x2014,
63 		.nr_sectors_shift		= 8,
64 	},
65 	{
66 		/* W25P16 */
67 		.id[0]				= 0x2015,
68 		.nr_sectors_shift		= 9,
69 	},
70 	{
71 		/* W25P32 */
72 		.id[0]				= 0x2016,
73 		.nr_sectors_shift		= 10,
74 	},
75 	{
76 		/* W25X80 */
77 		.id[0]				= 0x3014,
78 		.nr_sectors_shift		= 8,
79 		.fast_read_dual_output_support	= 1,
80 	},
81 	{
82 		/* W25X16 */
83 		.id[0]				= 0x3015,
84 		.nr_sectors_shift		= 9,
85 		.fast_read_dual_output_support	= 1,
86 	},
87 	{
88 		/* W25X32 */
89 		.id[0]				= 0x3016,
90 		.nr_sectors_shift		= 10,
91 		.fast_read_dual_output_support	= 1,
92 	},
93 	{
94 		/* W25X64 */
95 		.id[0]				= 0x3017,
96 		.nr_sectors_shift		= 11,
97 		.fast_read_dual_output_support	= 1,
98 	},
99 	{
100 		/* W25Q80_V */
101 		.id[0]				= 0x4014,
102 		.nr_sectors_shift		= 8,
103 		.fast_read_dual_output_support	= 1,
104 		.fast_read_dual_io_support	= 1,
105 	},
106 	{
107 		/* W25Q16_V */
108 		.id[0]				= 0x4015,
109 		.nr_sectors_shift		= 9,
110 		.fast_read_dual_output_support	= 1,
111 		.fast_read_dual_io_support	= 1,
112 		.protection_granularity_shift	= 16,
113 		.bp_bits			= 3,
114 	},
115 	{
116 		/* W25Q16DW */
117 		.id[0]				= 0x6015,
118 		.nr_sectors_shift		= 9,
119 		.fast_read_dual_output_support	= 1,
120 		.fast_read_dual_io_support	= 1,
121 		.protection_granularity_shift	= 16,
122 		.bp_bits			= 3,
123 	},
124 	{
125 		/* W25Q32_V */
126 		.id[0]				= 0x4016,
127 		.nr_sectors_shift		= 10,
128 		.fast_read_dual_output_support	= 1,
129 		.fast_read_dual_io_support	= 1,
130 		.protection_granularity_shift	= 16,
131 		.bp_bits			= 3,
132 	},
133 	{
134 		/* W25Q32DW */
135 		.id[0]				= 0x6016,
136 		.nr_sectors_shift		= 10,
137 		.fast_read_dual_output_support	= 1,
138 		.fast_read_dual_io_support	= 1,
139 		.protection_granularity_shift	= 16,
140 		.bp_bits			= 3,
141 	},
142 	{
143 		/* W25Q64_V */
144 		.id[0]				= 0x4017,
145 		.nr_sectors_shift		= 11,
146 		.fast_read_dual_output_support	= 1,
147 		.fast_read_dual_io_support	= 1,
148 		.protection_granularity_shift	= 17,
149 		.bp_bits			= 3,
150 	},
151 	{
152 		/* W25Q64DW */
153 		.id[0]				= 0x6017,
154 		.nr_sectors_shift		= 11,
155 		.fast_read_dual_output_support	= 1,
156 		.fast_read_dual_io_support	= 1,
157 		.protection_granularity_shift	= 17,
158 		.bp_bits			= 3,
159 	},
160 	{
161 		/* W25Q64JW */
162 		.id[0]				= 0x8017,
163 		.nr_sectors_shift		= 11,
164 		.fast_read_dual_output_support	= 1,
165 		.fast_read_dual_io_support	= 1,
166 		.protection_granularity_shift	= 17,
167 		.bp_bits			= 3,
168 	},
169 	{
170 		/* W25Q128_V */
171 		.id[0]				= 0x4018,
172 		.nr_sectors_shift		= 12,
173 		.fast_read_dual_output_support	= 1,
174 		.fast_read_dual_io_support	= 1,
175 		.protection_granularity_shift	= 18,
176 		.bp_bits			= 3,
177 	},
178 	{
179 		/* W25Q128FW */
180 		.id[0]				= 0x6018,
181 		.nr_sectors_shift		= 12,
182 		.fast_read_dual_output_support	= 1,
183 		.fast_read_dual_io_support	= 1,
184 		.protection_granularity_shift	= 18,
185 		.bp_bits			= 3,
186 	},
187 	{
188 		/* W25Q128J */
189 		.id[0]				= 0x7018,
190 		.nr_sectors_shift		= 12,
191 		.fast_read_dual_output_support	= 1,
192 		.fast_read_dual_io_support	= 1,
193 		.protection_granularity_shift	= 18,
194 		.bp_bits			= 3,
195 	},
196 	{
197 		/* W25Q128JW */
198 		.id[0]				= 0x8018,
199 		.nr_sectors_shift		= 12,
200 		.fast_read_dual_output_support	= 1,
201 		.fast_read_dual_io_support	= 1,
202 		.protection_granularity_shift	= 18,
203 		.bp_bits			= 3,
204 	},
205 	{
206 		/* W25Q512NW-IM */
207 		.id[0]				= 0x8020,
208 		.nr_sectors_shift		= 14,
209 		.fast_read_dual_output_support	= 1,
210 		.fast_read_dual_io_support	= 1,
211 		.protection_granularity_shift	= 16,
212 		.bp_bits			= 4,
213 	},
214 	{
215 		/* W25Q256_V */
216 		.id[0]				= 0x4019,
217 		.nr_sectors_shift		= 13,
218 		.fast_read_dual_output_support	= 1,
219 		.fast_read_dual_io_support	= 1,
220 		.protection_granularity_shift	= 16,
221 		.bp_bits			= 4,
222 	},
223 	{
224 		/* W25Q256J */
225 		.id[0]				= 0x7019,
226 		.nr_sectors_shift		= 13,
227 		.fast_read_dual_output_support	= 1,
228 		.fast_read_dual_io_support	= 1,
229 		.protection_granularity_shift	= 16,
230 		.bp_bits			= 4,
231 	},
232 	{
233 		/* W25Q256JW */
234 		.id[0]				= 0x6019,
235 		.nr_sectors_shift		= 13,
236 		.fast_read_dual_output_support	= 1,
237 		.fast_read_dual_io_support	= 1,
238 		.protection_granularity_shift	= 16,
239 		.bp_bits			= 4,
240 	},
241 	{
242 		/* W25Q256JW_DTR */
243 		.id[0]				= 0x8019,
244 		.nr_sectors_shift		= 13,
245 		.fast_read_dual_output_support	= 1,
246 		.fast_read_dual_io_support	= 1,
247 		.protection_granularity_shift	= 16,
248 		.bp_bits			= 4,
249 	},
250 };
251 
252 /*
253  * Convert BPx, TB and CMP to a region.
254  * SEC (if available) must be zero.
255  */
winbond_bpbits_to_region(const size_t granularity,const struct spi_flash_bpbits * bits,const size_t flash_size,struct region * out)256 static void winbond_bpbits_to_region(const size_t granularity,
257 				     const struct spi_flash_bpbits *bits,
258 				     const size_t flash_size,
259 				     struct region *out)
260 {
261 	size_t protected_size =
262 		MIN(bits->bp ? granularity << (bits->bp - 1) : 0, flash_size);
263 
264 	int tb = bits->tb;
265 	if (bits->cmp) {
266 		protected_size = flash_size - protected_size;
267 		tb = !tb;
268 	}
269 
270 	out->offset = tb ? 0 : flash_size - protected_size;
271 	out->size = protected_size;
272 }
273 
274 /*
275  * Available on all devices.
276  * Read block protect bits from Status/Status2 Reg.
277  * Converts block protection bits to a region.
278  *
279  * Returns:
280  * -1    on error
281  *  1    if region is covered by write protection
282  *  0    if a part of region isn't covered by write protection
283  */
winbond_get_write_protection(const struct spi_flash * flash,const struct region * region)284 static int winbond_get_write_protection(const struct spi_flash *flash,
285 					const struct region *region)
286 {
287 	const struct spi_flash_part_id *params;
288 	struct region wp_region;
289 	struct spi_flash_bpbits bpbits;
290 	int ret;
291 
292 	params = flash->part;
293 
294 	if (!params)
295 		return -1;
296 
297 	const size_t granularity = (1 << params->protection_granularity_shift);
298 
299 	union status_reg1 reg1 = { .u = 0 };
300 	union status_reg2 reg2 = { .u = 0 };
301 
302 	ret = spi_flash_cmd(&flash->spi, flash->status_cmd, &reg1.u,
303 			    sizeof(reg1.u));
304 	if (ret)
305 		return ret;
306 
307 	ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg2.u,
308 			    sizeof(reg2.u));
309 	if (ret)
310 		return ret;
311 
312 	if (params->bp_bits == 3) {
313 		if (reg1.bp3.sec) {
314 			// FIXME: not supported
315 			return -1;
316 		}
317 
318 		bpbits = (struct spi_flash_bpbits){
319 			.bp = reg1.bp3.bp,
320 			.cmp = reg2.cmp,
321 			.tb = reg1.bp3.tb,
322 			/*
323 			 * For W25Q*{,F}* parts:
324 			 *  srp1 srp0
325 			 *   0    0  | writable if WEL==1
326 			 *   0    1  | writable if WEL==1 && #WP==Vcc
327 			 *   1    0  | not writable until next power-down
328 			 *   1    1  | not writable, permanently
329 			 *
330 			 * checked datasheets: W25Q128FV, (W25Q80, W25Q16,
331 			 *   W25Q32)
332 			 */
333 			.winbond = {
334 				.srp0 = reg1.bp3.srp0,
335 				.srp1 = reg2.srp1,
336 			},
337 		};
338 	} else if (params->bp_bits == 4) {
339 		bpbits = (struct spi_flash_bpbits){
340 			.bp = reg1.bp4.bp,
341 			.cmp = reg2.cmp,
342 			.tb = reg1.bp4.tb,
343 			/*
344 			 * For W25Q*{J,D}* parts:
345 			 *
346 			 *  srp1 srp0
347 			 *   0    0  | writable if WEL==1
348 			 *   0    1  | writable if WEL==1 && #WP==Vcc
349 			 *   1    x  | not writable until next power-down
350 			 *
351 			 * checked datasheets: W25Q132JW, W25Q128JW, W25Q256JV.
352 			 *   W25Q16DW
353 			 *
354 			 * The srp0/srp1 bits got renamed to srp/srl in the
355 			 * datasheets, we retain the prior naming
356 			 * convention for the structs though.
357 			 */
358 			.winbond = {
359 				.srp0 = reg1.bp4.srp0,
360 				.srp1 = reg2.srp1,
361 			},
362 		};
363 	} else {
364 		// FIXME: not supported
365 		return -1;
366 	}
367 
368 	winbond_bpbits_to_region(granularity, &bpbits, flash->size,
369 				 &wp_region);
370 
371 	if (!region_sz(&wp_region)) {
372 		printk(BIOS_DEBUG, "WINBOND: flash isn't protected\n");
373 
374 		return 0;
375 	}
376 
377 	printk(BIOS_DEBUG, "WINBOND: flash protected range 0x%08zx-0x%08zx\n",
378 	       region_offset(&wp_region), region_end(&wp_region));
379 
380 	return region_is_subregion(&wp_region, region);
381 }
382 
383 /**
384  * Common method to write some bit of the status register 1 & 2 at the same
385  * time. Only change bits that are one in @mask.
386  * Compare the final result to make sure that the register isn't locked.
387  *
388  * @param mask: The bits that are affected by @val
389  * @param val: The bits to write
390  * @param non_volatile: Make setting permanent
391  *
392  * @return 0 on success
393  */
winbond_flash_cmd_status(const struct spi_flash * flash,const u16 mask,const u16 val,const bool non_volatile)394 static int winbond_flash_cmd_status(const struct spi_flash *flash,
395 				    const u16 mask,
396 				    const u16 val,
397 				    const bool non_volatile)
398 {
399 	struct {
400 		u8 cmd;
401 		u16 sreg;
402 	} __packed cmdbuf;
403 	u8 reg8;
404 	int ret;
405 
406 	if (!flash)
407 		return -1;
408 
409 	ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, &reg8, sizeof(reg8));
410 	if (ret)
411 		return ret;
412 
413 	cmdbuf.sreg = reg8;
414 
415 	ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg8, sizeof(reg8));
416 	if (ret)
417 		return ret;
418 
419 	cmdbuf.sreg |= reg8 << 8;
420 
421 	if ((val & mask) == (cmdbuf.sreg & mask))
422 		return 0;
423 
424 	if (non_volatile) {
425 		ret = spi_flash_cmd(&flash->spi, CMD_W25_WREN, NULL, 0);
426 	} else {
427 		ret = spi_flash_cmd(&flash->spi, CMD_VOLATILE_SREG_WREN, NULL,
428 				    0);
429 	}
430 	if (ret)
431 		return ret;
432 
433 	cmdbuf.sreg &= ~mask;
434 	cmdbuf.sreg |= val & mask;
435 	cmdbuf.cmd = CMD_W25_WRSR;
436 
437 	/* Legacy method of writing status register 1 & 2 */
438 	ret = spi_flash_cmd_write(&flash->spi, (u8 *)&cmdbuf, sizeof(cmdbuf),
439 				  NULL, 0);
440 	if (ret)
441 		return ret;
442 
443 	if (non_volatile) {
444 		/* Wait tw */
445 		ret = spi_flash_cmd_wait_ready(flash, WINBOND_FLASH_TIMEOUT);
446 		if (ret)
447 			return ret;
448 	} else {
449 		/* Wait tSHSL */
450 		udelay(1);
451 	}
452 
453 	/* Now read the status register to make sure it's not locked */
454 	ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, &reg8, sizeof(reg8));
455 	if (ret)
456 		return ret;
457 
458 	cmdbuf.sreg = reg8;
459 
460 	ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg8, sizeof(reg8));
461 	if (ret)
462 		return ret;
463 
464 	cmdbuf.sreg |= reg8 << 8;
465 
466 	printk(BIOS_DEBUG, "WINBOND: SREG=%02x SREG2=%02x\n",
467 	       cmdbuf.sreg & 0xff,
468 	       cmdbuf.sreg >> 8);
469 
470 	/* Compare against expected result */
471 	if ((val & mask) != (cmdbuf.sreg & mask)) {
472 		printk(BIOS_ERR, "WINBOND: SREG is locked!\n");
473 		ret = -1;
474 	}
475 
476 	return ret;
477 }
478 
479 /*
480  * Available on all devices.
481  * Protect a region starting from start of flash or end of flash.
482  * The caller must provide a supported protected region size.
483  * SEC isn't supported and set to zero.
484  * Write block protect bits to Status/Status2 Reg.
485  * Optionally lock the status register if lock_sreg is set with the provided
486  * mode.
487  *
488  * @param flash: The flash to operate on
489  * @param region: The region to write protect
490  * @param mode: Optional status register lock-down mode
491  *
492  * @return 0 on success
493  */
494 static int
winbond_set_write_protection(const struct spi_flash * flash,const struct region * region,const enum spi_flash_status_reg_lockdown mode)495 winbond_set_write_protection(const struct spi_flash *flash,
496 			     const struct region *region,
497 			     const enum spi_flash_status_reg_lockdown mode)
498 {
499 	const struct spi_flash_part_id *params;
500 	struct status_regs mask, val;
501 	struct region wp_region;
502 	u8 cmp, bp, tb;
503 	int ret;
504 
505 	/* Need to touch TOP or BOTTOM */
506 	if (region_offset(region) != 0 && region_end(region) != flash->size)
507 		return -1;
508 
509 	params = flash->part;
510 
511 	if (!params)
512 		return -1;
513 
514 	if (params->bp_bits != 3 && params->bp_bits != 4) {
515 		/* FIXME: not implemented */
516 		return -1;
517 	}
518 
519 	wp_region = *region;
520 
521 	if (region_offset(&wp_region) == 0)
522 		tb = 1;
523 	else
524 		tb = 0;
525 
526 	if (region_sz(&wp_region) > flash->size / 2) {
527 		cmp = 1;
528 		wp_region.offset = tb ? 0 : region_sz(&wp_region);
529 		wp_region.size = flash->size - region_sz(&wp_region);
530 		tb = !tb;
531 	} else {
532 		cmp = 0;
533 	}
534 
535 	if (region_sz(&wp_region) == 0) {
536 		bp = 0;
537 	} else if (IS_POWER_OF_2(region_sz(&wp_region)) &&
538 		   (region_sz(&wp_region) >=
539 		    (1 << params->protection_granularity_shift))) {
540 		bp = log2(region_sz(&wp_region)) -
541 			  params->protection_granularity_shift + 1;
542 	} else {
543 		printk(BIOS_ERR, "WINBOND: ERROR: unsupported region size\n");
544 		return -1;
545 	}
546 
547 	/* Write block protection bits */
548 
549 	if (params->bp_bits == 3) {
550 		val.reg1 = (union status_reg1) {
551 			.bp3 = { .bp = bp, .tb = tb, .sec = 0 }
552 		};
553 		mask.reg1 = (union status_reg1) {
554 			.bp3 = { .bp = ~0, .tb = 1, .sec = 1 }
555 		};
556 	} else {
557 		val.reg1 = (union status_reg1) {
558 			.bp4 = { .bp = bp, .tb = tb }
559 		};
560 		mask.reg1 = (union status_reg1) {
561 			.bp4 = { .bp = ~0, .tb = 1 }
562 		};
563 	}
564 
565 	val.reg2 = (union status_reg2) { .cmp = cmp };
566 	mask.reg2 = (union status_reg2) { .cmp = 1 };
567 
568 	if (mode != SPI_WRITE_PROTECTION_PRESERVE) {
569 		u8 srp;
570 		switch (mode) {
571 		case SPI_WRITE_PROTECTION_NONE:
572 			srp = 0;
573 		break;
574 		case SPI_WRITE_PROTECTION_PIN:
575 			srp = 1;
576 		break;
577 		case SPI_WRITE_PROTECTION_REBOOT:
578 			srp = 2;
579 		break;
580 		case SPI_WRITE_PROTECTION_PERMANENT:
581 			srp = 3;
582 		break;
583 		default:
584 			return -1;
585 		}
586 
587 		if (params->bp_bits == 3) {
588 			val.reg1.bp3.srp0 = !!(srp & 1);
589 			mask.reg1.bp3.srp0 = 1;
590 		} else {
591 			val.reg1.bp4.srp0 = !!(srp & 1);
592 			mask.reg1.bp4.srp0 = 1;
593 		}
594 
595 		val.reg2.srp1 = !!(srp & 2);
596 		mask.reg2.srp1 = 1;
597 	}
598 
599 	ret = winbond_flash_cmd_status(flash, mask.u, val.u, true);
600 	if (ret)
601 		return ret;
602 
603 	printk(BIOS_DEBUG, "WINBOND: write-protection set to range "
604 	       "0x%08zx-0x%08zx\n", region_offset(region), region_end(region));
605 
606 	return ret;
607 }
608 
609 static const struct spi_flash_protection_ops spi_flash_protection_ops = {
610 	.get_write = winbond_get_write_protection,
611 	.set_write = winbond_set_write_protection,
612 };
613 
614 const struct spi_flash_vendor_info spi_flash_winbond_vi = {
615 	.id = VENDOR_ID_WINBOND,
616 	.page_size_shift = 8,
617 	.sector_size_kib_shift = 2,
618 	.match_id_mask[0] = 0xffff,
619 	.ids = flash_table,
620 	.nr_part_ids = ARRAY_SIZE(flash_table),
621 	.desc = &spi_flash_pp_0x20_sector_desc,
622 	.prot_ops = &spi_flash_protection_ops,
623 };
624