blob: 8bf8fcd8d3b3dfaf540669a0a731dd14d8e95aea [file] [log] [blame]
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -07001/*
2 * Copyright 2008, Network Appliance Inc.
3 * Author: Jason McMullan <mcmullan <at> netapp.com>
4 * Licensed under the GPL-2 or later.
5 */
6
Furquan Shaikhc28984d2016-11-20 21:04:00 -08007#include <console/console.h>
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -07008#include <stdlib.h>
9#include <spi_flash.h>
Furquan Shaikhc28984d2016-11-20 21:04:00 -080010#include <spi-generic.h>
Furquan Shaikh810e2cd2016-12-05 20:32:24 -080011#include <string.h>
Patrick Rudolph0f8bf022018-03-09 14:20:25 +010012#include <assert.h>
Patrick Rudolphe63a5f12018-03-12 11:34:53 +010013#include <delay.h>
14#include <lib.h>
Edward O'Callaghanc4561e22014-06-26 15:02:40 +100015
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -070016#include "spi_flash_internal.h"
17
18/* M25Pxx-specific commands */
19#define CMD_W25_WREN 0x06 /* Write Enable */
20#define CMD_W25_WRDI 0x04 /* Write Disable */
21#define CMD_W25_RDSR 0x05 /* Read Status Register */
22#define CMD_W25_WRSR 0x01 /* Write Status Register */
Patrick Rudolph0f8bf022018-03-09 14:20:25 +010023#define CMD_W25_RDSR2 0x35 /* Read Status2 Register */
24#define CMD_W25_WRSR2 0x31 /* Write Status2 Register */
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -070025#define CMD_W25_READ 0x03 /* Read Data Bytes */
26#define CMD_W25_FAST_READ 0x0b /* Read Data Bytes at Higher Speed */
27#define CMD_W25_PP 0x02 /* Page Program */
28#define CMD_W25_SE 0x20 /* Sector (4K) Erase */
29#define CMD_W25_BE 0xd8 /* Block (64K) Erase */
30#define CMD_W25_CE 0xc7 /* Chip Erase */
31#define CMD_W25_DP 0xb9 /* Deep Power-down */
32#define CMD_W25_RES 0xab /* Release from DP, and Read Signature */
Patrick Rudolphe63a5f12018-03-12 11:34:53 +010033#define CMD_VOLATILE_SREG_WREN 0x50 /* Write Enable for Volatile SREG */
34
35/* tw: Maximum time to write a flash cell in milliseconds */
36#define WINBOND_FLASH_TIMEOUT 30
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -070037
38struct winbond_spi_flash_params {
Patrick Rudolph79ab8432018-08-03 08:19:00 +020039 uint16_t id;
40 uint8_t l2_page_size_shift;
41 uint8_t pages_per_sector_shift : 4;
42 uint8_t sectors_per_block_shift : 4;
43 uint8_t nr_blocks_shift;
Patrick Rudolph0f8bf022018-03-09 14:20:25 +010044 uint8_t bp_bits : 3;
45 uint8_t protection_granularity_shift : 5;
Patrick Rudolph79ab8432018-08-03 08:19:00 +020046 char name[10];
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -070047};
48
Patrick Rudolph0f8bf022018-03-09 14:20:25 +010049union status_reg1_bp3 {
50 uint8_t u;
51 struct {
52 uint8_t busy : 1;
53 uint8_t wel : 1;
54 uint8_t bp : 3;
55 uint8_t tb : 1;
56 uint8_t sec : 1;
57 uint8_t srp0 : 1;
58 };
59};
60
61union status_reg1_bp4 {
62 uint8_t u;
63 struct {
64 uint8_t busy : 1;
65 uint8_t wel : 1;
66 uint8_t bp : 4;
67 uint8_t tb : 1;
68 uint8_t srp0 : 1;
69 };
70};
71
72union status_reg2 {
73 uint8_t u;
74 struct {
75 uint8_t srp1 : 1;
76 uint8_t qe : 1;
77 uint8_t res : 1;
78 uint8_t lb : 3;
79 uint8_t cmp : 1;
80 uint8_t sus : 1;
81 };
82};
83
Patrick Rudolphe63a5f12018-03-12 11:34:53 +010084struct status_regs {
85 union {
86 struct {
87#if defined(__BIG_ENDIAN)
88 union status_reg2 reg2;
89 union {
90 union status_reg1_bp3 reg1_bp3;
91 union status_reg1_bp4 reg1_bp4;
92 };
93#else
94 union {
95 union status_reg1_bp3 reg1_bp3;
96 union status_reg1_bp4 reg1_bp4;
97 };
98 union status_reg2 reg2;
99#endif
100 };
101 u16 u;
102 };
103};
104
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700105static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
106 {
Mike Banon72812fa2019-01-08 19:11:34 +0300107 .id = 0x2014,
108 .l2_page_size_shift = 8,
109 .pages_per_sector_shift = 4,
110 .sectors_per_block_shift = 4,
111 .nr_blocks_shift = 4,
112 .name = "W25P80",
113 },
114 {
115 .id = 0x2015,
116 .l2_page_size_shift = 8,
117 .pages_per_sector_shift = 4,
118 .sectors_per_block_shift = 4,
119 .nr_blocks_shift = 5,
120 .name = "W25P16",
121 },
122 {
123 .id = 0x2016,
124 .l2_page_size_shift = 8,
125 .pages_per_sector_shift = 4,
126 .sectors_per_block_shift = 4,
127 .nr_blocks_shift = 6,
128 .name = "W25P32",
129 },
130 {
131 .id = 0x3014,
132 .l2_page_size_shift = 8,
133 .pages_per_sector_shift = 4,
134 .sectors_per_block_shift = 4,
135 .nr_blocks_shift = 4,
136 .name = "W25X80",
137 },
138 {
Patrick Rudolph79ab8432018-08-03 08:19:00 +0200139 .id = 0x3015,
140 .l2_page_size_shift = 8,
141 .pages_per_sector_shift = 4,
142 .sectors_per_block_shift = 4,
143 .nr_blocks_shift = 5,
144 .name = "W25X16",
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700145 },
146 {
Patrick Rudolph79ab8432018-08-03 08:19:00 +0200147 .id = 0x3016,
148 .l2_page_size_shift = 8,
149 .pages_per_sector_shift = 4,
150 .sectors_per_block_shift = 4,
151 .nr_blocks_shift = 6,
152 .name = "W25X32",
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700153 },
154 {
Patrick Rudolph79ab8432018-08-03 08:19:00 +0200155 .id = 0x3017,
156 .l2_page_size_shift = 8,
157 .pages_per_sector_shift = 4,
158 .sectors_per_block_shift = 4,
159 .nr_blocks_shift = 7,
160 .name = "W25X64",
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700161 },
162 {
Patrick Rudolph79ab8432018-08-03 08:19:00 +0200163 .id = 0x4014,
164 .l2_page_size_shift = 8,
165 .pages_per_sector_shift = 4,
166 .sectors_per_block_shift = 4,
167 .nr_blocks_shift = 4,
Mike Banon72812fa2019-01-08 19:11:34 +0300168 .name = "W25Q80_V",
Kyösti Mälkki76f7b792018-06-14 22:25:58 +0300169 },
170 {
Patrick Rudolph79ab8432018-08-03 08:19:00 +0200171 .id = 0x4015,
172 .l2_page_size_shift = 8,
173 .pages_per_sector_shift = 4,
174 .sectors_per_block_shift = 4,
175 .nr_blocks_shift = 5,
Mike Banon72812fa2019-01-08 19:11:34 +0300176 .name = "W25Q16_V",
177 .protection_granularity_shift = 16,
178 .bp_bits = 3,
179 },
180 {
181 .id = 0x6015,
182 .l2_page_size_shift = 8,
183 .pages_per_sector_shift = 4,
184 .sectors_per_block_shift = 4,
185 .nr_blocks_shift = 5,
186 .name = "W25Q16DW",
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100187 .protection_granularity_shift = 16,
188 .bp_bits = 3,
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700189 },
190 {
Patrick Rudolph79ab8432018-08-03 08:19:00 +0200191 .id = 0x4016,
192 .l2_page_size_shift = 8,
193 .pages_per_sector_shift = 4,
194 .sectors_per_block_shift = 4,
195 .nr_blocks_shift = 6,
Mike Banon72812fa2019-01-08 19:11:34 +0300196 .name = "W25Q32_V",
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100197 .protection_granularity_shift = 16,
198 .bp_bits = 3,
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700199 },
200 {
Patrick Rudolph79ab8432018-08-03 08:19:00 +0200201 .id = 0x6016,
202 .l2_page_size_shift = 8,
203 .pages_per_sector_shift = 4,
204 .sectors_per_block_shift = 4,
205 .nr_blocks_shift = 6,
206 .name = "W25Q32DW",
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100207 .protection_granularity_shift = 16,
208 .bp_bits = 3,
David Hendricks43e92522014-03-21 19:45:02 -0700209 },
210 {
Patrick Rudolph79ab8432018-08-03 08:19:00 +0200211 .id = 0x4017,
212 .l2_page_size_shift = 8,
213 .pages_per_sector_shift = 4,
214 .sectors_per_block_shift = 4,
215 .nr_blocks_shift = 7,
Mike Banon72812fa2019-01-08 19:11:34 +0300216 .name = "W25Q64_V",
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100217 .protection_granularity_shift = 17,
218 .bp_bits = 3,
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700219 },
220 {
Patrick Rudolph79ab8432018-08-03 08:19:00 +0200221 .id = 0x6017,
222 .l2_page_size_shift = 8,
223 .pages_per_sector_shift = 4,
224 .sectors_per_block_shift = 4,
225 .nr_blocks_shift = 7,
226 .name = "W25Q64DW",
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100227 .protection_granularity_shift = 17,
228 .bp_bits = 3,
Aaron Durbin16000c82013-09-19 12:19:51 -0500229 },
230 {
Patrick Rudolph79ab8432018-08-03 08:19:00 +0200231 .id = 0x4018,
232 .l2_page_size_shift = 8,
233 .pages_per_sector_shift = 4,
234 .sectors_per_block_shift = 4,
235 .nr_blocks_shift = 8,
Mike Banon72812fa2019-01-08 19:11:34 +0300236 .name = "W25Q128_V",
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100237 .protection_granularity_shift = 18,
238 .bp_bits = 3,
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700239 },
Mohan D'Costabdae9be2014-09-25 14:40:44 +0900240 {
Patrick Rudolph79ab8432018-08-03 08:19:00 +0200241 .id = 0x6018,
242 .l2_page_size_shift = 8,
243 .pages_per_sector_shift = 4,
244 .sectors_per_block_shift = 4,
245 .nr_blocks_shift = 8,
246 .name = "W25Q128FW",
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100247 .protection_granularity_shift = 18,
248 .bp_bits = 3,
Mohan D'Costabdae9be2014-09-25 14:40:44 +0900249 },
Varadarajan Narayanan934c6832016-03-21 12:56:43 +0530250 {
Patrick Rudolphfca506d2018-10-01 16:00:16 +0200251 .id = 0x7018,
252 .l2_page_size_shift = 8,
253 .pages_per_sector_shift = 4,
254 .sectors_per_block_shift = 4,
255 .nr_blocks_shift = 8,
256 .name = "W25Q128J",
257 .protection_granularity_shift = 18,
258 .bp_bits = 3,
259 },
260 {
Patrick Rudolph79ab8432018-08-03 08:19:00 +0200261 .id = 0x4019,
262 .l2_page_size_shift = 8,
263 .pages_per_sector_shift = 4,
264 .sectors_per_block_shift = 4,
265 .nr_blocks_shift = 9,
Mike Banon72812fa2019-01-08 19:11:34 +0300266 .name = "W25Q256_V",
267 .protection_granularity_shift = 16,
268 .bp_bits = 4,
269 },
270 {
271 .id = 0x7019,
272 .l2_page_size_shift = 8,
273 .pages_per_sector_shift = 4,
274 .sectors_per_block_shift = 4,
275 .nr_blocks_shift = 9,
276 .name = "W25Q256J",
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100277 .protection_granularity_shift = 16,
278 .bp_bits = 4,
Varadarajan Narayanan934c6832016-03-21 12:56:43 +0530279 },
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700280};
281
Furquan Shaikhc28984d2016-11-20 21:04:00 -0800282static int winbond_write(const struct spi_flash *flash, u32 offset, size_t len,
283 const void *buf)
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700284{
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700285 unsigned long byte_addr;
286 unsigned long page_size;
287 size_t chunk_len;
288 size_t actual;
David Hendricks032c8432014-04-11 19:48:55 -0700289 int ret = 0;
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700290 u8 cmd[4];
291
Furquan Shaikhfc1a1232017-05-12 00:19:56 -0700292 page_size = flash->page_size;
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700293
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700294 for (actual = 0; actual < len; actual += chunk_len) {
Aaron Durbin41f66902016-12-17 13:16:07 -0600295 byte_addr = offset % page_size;
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700296 chunk_len = min(len - actual, page_size - byte_addr);
Furquan Shaikhde705fa2017-04-19 19:27:28 -0700297 chunk_len = spi_crop_chunk(&flash->spi, sizeof(cmd), chunk_len);
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700298
299 cmd[0] = CMD_W25_PP;
300 cmd[1] = (offset >> 16) & 0xff;
301 cmd[2] = (offset >> 8) & 0xff;
302 cmd[3] = offset & 0xff;
Martin Rothe7d0a372017-06-24 14:02:00 -0600303#if IS_ENABLED(CONFIG_DEBUG_SPI_FLASH)
Stefan Reinauer8ea5a342012-05-14 13:52:32 -0700304 printk(BIOS_SPEW, "PP: 0x%p => cmd = { 0x%02x 0x%02x%02x%02x }"
305 " chunk_len = %zu\n", buf + actual,
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700306 cmd[0], cmd[1], cmd[2], cmd[3], chunk_len);
Stefan Reinauer5649b082012-05-23 11:03:29 -0700307#endif
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700308
Furquan Shaikh810e2cd2016-12-05 20:32:24 -0800309 ret = spi_flash_cmd(&flash->spi, CMD_W25_WREN, NULL, 0);
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700310 if (ret < 0) {
311 printk(BIOS_WARNING, "SF: Enabling Write failed\n");
312 goto out;
313 }
314
Furquan Shaikh810e2cd2016-12-05 20:32:24 -0800315 ret = spi_flash_cmd_write(&flash->spi, cmd, sizeof(cmd),
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700316 buf + actual, chunk_len);
317 if (ret < 0) {
318 printk(BIOS_WARNING, "SF: Winbond Page Program failed\n");
319 goto out;
320 }
321
322 ret = spi_flash_cmd_wait_ready(flash, SPI_FLASH_PROG_TIMEOUT);
323 if (ret)
324 goto out;
325
326 offset += chunk_len;
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700327 }
328
Martin Rothe7d0a372017-06-24 14:02:00 -0600329#if IS_ENABLED(CONFIG_DEBUG_SPI_FLASH)
Marc Jones747127d2012-12-03 22:16:29 -0700330 printk(BIOS_SPEW, "SF: Winbond: Successfully programmed %zu bytes @"
Stefan Reinauer8ea5a342012-05-14 13:52:32 -0700331 " 0x%lx\n", len, (unsigned long)(offset - len));
Marc Jones747127d2012-12-03 22:16:29 -0700332#endif
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700333 ret = 0;
334
335out:
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700336 return ret;
337}
338
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100339/*
340 * Convert BPx, TB and CMP to a region.
341 * SEC (if available) must be zero.
342 */
343static void winbond_bpbits_to_region(const size_t granularity,
344 const u8 bp,
345 bool tb,
346 const bool cmp,
347 const size_t flash_size,
348 struct region *out)
349{
350 size_t protected_size =
351 min(bp ? granularity << (bp - 1) : 0, flash_size);
352
353 if (cmp) {
354 protected_size = flash_size - protected_size;
355 tb = !tb;
356 }
357
Patrick Rudolphe7360152018-12-03 09:41:06 +0100358 out->offset = tb ? 0 : flash_size - protected_size;
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100359 out->size = protected_size;
360}
361
362/*
363 * Available on all devices.
364 * Read block protect bits from Status/Status2 Reg.
365 * Converts block protection bits to a region.
366 *
367 * Returns:
368 * -1 on error
369 * 1 if region is covered by write protection
370 * 0 if a part of region isn't covered by write protection
371 */
372static int winbond_get_write_protection(const struct spi_flash *flash,
373 const struct region *region)
374{
375 const struct winbond_spi_flash_params *params;
376 struct region wp_region;
377 union status_reg2 reg2;
378 u8 bp, tb;
379 int ret;
380
381 params = (const struct winbond_spi_flash_params *)flash->driver_private;
382 const size_t granularity = (1 << params->protection_granularity_shift);
383
384 if (params->bp_bits == 3) {
Patrick Rudolph87471362018-09-25 14:26:33 +0200385 union status_reg1_bp3 reg1_bp3 = { .u = 0 };
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100386
387 ret = spi_flash_cmd(&flash->spi, flash->status_cmd, &reg1_bp3.u,
388 sizeof(reg1_bp3.u));
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100389
390 if (reg1_bp3.sec) {
391 // FIXME: not supported
392 return -1;
393 }
394
395 bp = reg1_bp3.bp;
396 tb = reg1_bp3.tb;
397 } else if (params->bp_bits == 4) {
Patrick Rudolph87471362018-09-25 14:26:33 +0200398 union status_reg1_bp4 reg1_bp4 = { .u = 0 };
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100399
400 ret = spi_flash_cmd(&flash->spi, flash->status_cmd, &reg1_bp4.u,
401 sizeof(reg1_bp4.u));
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100402
403 bp = reg1_bp4.bp;
404 tb = reg1_bp4.tb;
405 } else {
406 // FIXME: not supported
407 return -1;
408 }
Patrick Rudolph87471362018-09-25 14:26:33 +0200409 if (ret)
410 return ret;
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100411
412 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg2.u,
413 sizeof(reg2.u));
414 if (ret)
415 return ret;
416
417 winbond_bpbits_to_region(granularity, bp, tb, reg2.cmp, flash->size,
418 &wp_region);
419
Patrick Rudolph87471362018-09-25 14:26:33 +0200420 if (!region_sz(&wp_region)) {
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100421 printk(BIOS_DEBUG, "WINBOND: flash isn't protected\n");
422
423 return 0;
424 }
425
426 printk(BIOS_DEBUG, "WINBOND: flash protected range 0x%08zx-0x%08zx\n",
Patrick Rudolph87471362018-09-25 14:26:33 +0200427 region_offset(&wp_region),
428 region_offset(&wp_region) + region_sz(&wp_region));
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100429
430 return region_is_subregion(&wp_region, region);
431}
432
Patrick Rudolphe63a5f12018-03-12 11:34:53 +0100433/**
434 * Common method to write some bit of the status register 1 & 2 at the same
435 * time. Only change bits that are one in @mask.
436 * Compare the final result to make sure that the register isn't locked.
437 *
438 * @param mask: The bits that are affected by @val
439 * @param val: The bits to write
440 * @param non_volatile: Make setting permanent
441 *
442 * @return 0 on success
443 */
444static int winbond_flash_cmd_status(const struct spi_flash *flash,
445 const u16 mask,
446 const u16 val,
447 const bool non_volatile)
448{
449 struct {
450 u8 cmd;
451 u16 sreg;
452 } __packed cmdbuf;
453 u8 reg8;
454 int ret;
455
456 if (!flash)
457 return -1;
458
459 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, &reg8, sizeof(reg8));
460 if (ret)
461 return ret;
462
463 cmdbuf.sreg = reg8;
464
465 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg8, sizeof(reg8));
466 if (ret)
467 return ret;
468
469 cmdbuf.sreg |= reg8 << 8;
470
471 if ((val & mask) == (cmdbuf.sreg & mask))
472 return 0;
473
474 if (non_volatile) {
475 ret = spi_flash_cmd(&flash->spi, CMD_W25_WREN, NULL, 0);
476 } else {
477 ret = spi_flash_cmd(&flash->spi, CMD_VOLATILE_SREG_WREN, NULL,
478 0);
479 }
480 if (ret)
481 return ret;
482
483 cmdbuf.sreg &= ~mask;
484 cmdbuf.sreg |= val & mask;
485 cmdbuf.cmd = CMD_W25_WRSR;
486
487 /* Legacy method of writing status register 1 & 2 */
488 ret = spi_flash_cmd_write(&flash->spi, (u8 *)&cmdbuf, sizeof(cmdbuf),
489 NULL, 0);
490 if (ret)
491 return ret;
492
493 if (non_volatile) {
494 /* Wait tw */
495 ret = spi_flash_cmd_wait_ready(flash, WINBOND_FLASH_TIMEOUT);
496 if (ret)
497 return ret;
498 } else {
499 /* Wait tSHSL */
500 udelay(1);
501 }
502
503 /* Now read the status register to make sure it's not locked */
504 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, &reg8, sizeof(reg8));
505 if (ret)
506 return ret;
507
508 cmdbuf.sreg = reg8;
509
510 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg8, sizeof(reg8));
511 if (ret)
512 return ret;
513
514 cmdbuf.sreg |= reg8 << 8;
515
516 printk(BIOS_DEBUG, "WINBOND: SREG=%02x SREG2=%02x\n",
517 cmdbuf.sreg & 0xff,
518 cmdbuf.sreg >> 8);
519
520 /* Compare against expected result */
521 if ((val & mask) != (cmdbuf.sreg & mask)) {
522 printk(BIOS_ERR, "WINBOND: SREG is locked!\n");
523 ret = -1;
524 }
525
526 return ret;
527}
528
529/*
530 * Available on all devices.
531 * Protect a region starting from start of flash or end of flash.
532 * The caller must provide a supported protected region size.
533 * SEC isn't supported and set to zero.
534 * Write block protect bits to Status/Status2 Reg.
535 * Optionally lock the status register if lock_sreg is set with the provided
536 * mode.
537 *
538 * @param flash: The flash to operate on
539 * @param region: The region to write protect
540 * @param non_volatile: Make setting permanent
541 * @param mode: Optional status register lock-down mode
542 *
543 * @return 0 on success
544 */
545static int
546winbond_set_write_protection(const struct spi_flash *flash,
547 const struct region *region,
548 const bool non_volatile,
549 const enum spi_flash_status_reg_lockdown mode)
550{
551 const struct winbond_spi_flash_params *params;
552 struct status_regs mask, val;
553 struct region wp_region;
554 u8 cmp, bp, tb;
555 int ret;
556
557 /* Need to touch TOP or BOTTOM */
558 if (region_offset(region) != 0 &&
559 (region_offset(region) + region_sz(region)) != flash->size)
560 return -1;
561
562 params = (const struct winbond_spi_flash_params *)flash->driver_private;
563 if (!params)
564 return -1;
565
566 if (params->bp_bits != 3 && params->bp_bits != 4) {
567 /* FIXME: not implemented */
568 return -1;
569 }
570
571 wp_region = *region;
572
573 if (region_offset(&wp_region) == 0)
Patrick Rudolphe63a5f12018-03-12 11:34:53 +0100574 tb = 1;
Patrick Rudolphe7360152018-12-03 09:41:06 +0100575 else
576 tb = 0;
Patrick Rudolphe63a5f12018-03-12 11:34:53 +0100577
578 if (region_sz(&wp_region) > flash->size / 2) {
579 cmp = 1;
580 wp_region.offset = tb ? 0 : region_sz(&wp_region);
581 wp_region.size = flash->size - region_sz(&wp_region);
582 tb = !tb;
583 } else {
584 cmp = 0;
585 }
586
587 if (region_sz(&wp_region) == 0) {
588 bp = 0;
589 } else if (IS_POWER_OF_2(region_sz(&wp_region)) &&
590 (region_sz(&wp_region) >=
591 (1 << params->protection_granularity_shift))) {
592 bp = log2(region_sz(&wp_region)) -
593 params->protection_granularity_shift + 1;
594 } else {
595 printk(BIOS_ERR, "WINBOND: ERROR: unsupported region size\n");
596 return -1;
597 }
598
599 /* Write block protection bits */
600
601 if (params->bp_bits == 3) {
602 val.reg1_bp3 = (union status_reg1_bp3) { .bp = bp, .tb = tb,
603 .sec = 0 };
604 mask.reg1_bp3 = (union status_reg1_bp3) { .bp = ~0, .tb = 1,
605 .sec = 1 };
606 } else {
607 val.reg1_bp4 = (union status_reg1_bp4) { .bp = bp, .tb = tb };
608 mask.reg1_bp4 = (union status_reg1_bp4) { .bp = ~0, .tb = 1 };
609 }
610
611 val.reg2 = (union status_reg2) { .cmp = cmp };
612 mask.reg2 = (union status_reg2) { .cmp = 1 };
613
614 if (mode != SPI_WRITE_PROTECTION_PRESERVE) {
615 u8 srp;
616 switch (mode) {
617 case SPI_WRITE_PROTECTION_NONE:
618 srp = 0;
619 break;
620 case SPI_WRITE_PROTECTION_PIN:
621 srp = 1;
622 break;
623 case SPI_WRITE_PROTECTION_REBOOT:
624 srp = 2;
625 break;
626 case SPI_WRITE_PROTECTION_PERMANENT:
627 srp = 3;
628 break;
629 default:
630 return -1;
631 }
632
633 if (params->bp_bits == 3) {
634 val.reg1_bp3.srp0 = !!(srp & 1);
635 mask.reg1_bp3.srp0 = 1;
636 } else {
637 val.reg1_bp4.srp0 = !!(srp & 1);
638 mask.reg1_bp4.srp0 = 1;
639 }
640
641 val.reg2.srp1 = !!(srp & 2);
642 mask.reg2.srp1 = 1;
643 }
644
645 ret = winbond_flash_cmd_status(flash, mask.u, val.u, non_volatile);
646 if (ret)
647 return ret;
648
649 printk(BIOS_DEBUG, "WINBOND: write-protection set to range "
650 "0x%08zx-0x%08zx\n", region_offset(region),
651 region_offset(region) + region_sz(region));
652
653 return ret;
654}
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100655
Furquan Shaikhe2fc5e22017-05-17 17:26:01 -0700656static const struct spi_flash_ops spi_flash_ops = {
657 .write = winbond_write,
658 .erase = spi_flash_cmd_erase,
659 .status = spi_flash_cmd_status,
660#if IS_ENABLED(CONFIG_SPI_FLASH_NO_FAST_READ)
661 .read = spi_flash_cmd_read_slow,
662#else
663 .read = spi_flash_cmd_read_fast,
664#endif
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100665 .get_write_protection = winbond_get_write_protection,
Patrick Rudolphe63a5f12018-03-12 11:34:53 +0100666 .set_write_protection = winbond_set_write_protection,
Furquan Shaikhe2fc5e22017-05-17 17:26:01 -0700667};
668
Furquan Shaikhbd9e32e2017-05-15 23:28:41 -0700669int spi_flash_probe_winbond(const struct spi_slave *spi, u8 *idcode,
Furquan Shaikh30221b42017-05-15 14:35:15 -0700670 struct spi_flash *flash)
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700671{
672 const struct winbond_spi_flash_params *params;
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700673 unsigned int i;
674
675 for (i = 0; i < ARRAY_SIZE(winbond_spi_flash_table); i++) {
676 params = &winbond_spi_flash_table[i];
677 if (params->id == ((idcode[1] << 8) | idcode[2]))
678 break;
679 }
680
681 if (i == ARRAY_SIZE(winbond_spi_flash_table)) {
682 printk(BIOS_WARNING, "SF: Unsupported Winbond ID %02x%02x\n",
683 idcode[1], idcode[2]);
Furquan Shaikh30221b42017-05-15 14:35:15 -0700684 return -1;
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700685 }
686
Furquan Shaikh30221b42017-05-15 14:35:15 -0700687 memcpy(&flash->spi, spi, sizeof(*spi));
688 flash->name = params->name;
Patrick Rudolph79ab8432018-08-03 08:19:00 +0200689
690 /* Params are in power-of-two. */
691 flash->page_size = 1 << params->l2_page_size_shift;
692 flash->sector_size = flash->page_size *
693 (1 << params->pages_per_sector_shift);
694 flash->size = flash->sector_size *
695 (1 << params->sectors_per_block_shift) *
696 (1 << params->nr_blocks_shift);
Furquan Shaikh30221b42017-05-15 14:35:15 -0700697 flash->erase_cmd = CMD_W25_SE;
698 flash->status_cmd = CMD_W25_RDSR;
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700699
Furquan Shaikhe2fc5e22017-05-17 17:26:01 -0700700 flash->ops = &spi_flash_ops;
Patrick Rudolph0f8bf022018-03-09 14:20:25 +0100701 flash->driver_private = params;
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700702
Furquan Shaikh30221b42017-05-15 14:35:15 -0700703 return 0;
Stefan Reinauer1c56d9b2012-05-10 11:27:32 -0700704}