]>
Commit | Line | Data |
---|---|---|
4d5e29a6 JT |
1 | /* |
2 | * SPI flash operations | |
3 | * | |
4 | * Copyright (C) 2008 Atmel Corporation | |
5 | * Copyright (C) 2010 Reinhard Meyer, EMK Elektronik | |
6 | * Copyright (C) 2013 Jagannadha Sutradharudu Teki, Xilinx Inc. | |
7 | * | |
0c88a84a | 8 | * SPDX-License-Identifier: GPL-2.0+ |
4d5e29a6 JT |
9 | */ |
10 | ||
11 | #include <common.h> | |
c6136aad | 12 | #include <errno.h> |
ff063ed4 | 13 | #include <malloc.h> |
4d5e29a6 JT |
14 | #include <spi.h> |
15 | #include <spi_flash.h> | |
16 | #include <watchdog.h> | |
146bad96 | 17 | #include <linux/compiler.h> |
41b358d7 | 18 | #include <linux/log2.h> |
4d5e29a6 | 19 | |
898e76c9 | 20 | #include "sf_internal.h" |
4d5e29a6 JT |
21 | |
22 | static void spi_flash_addr(u32 addr, u8 *cmd) | |
23 | { | |
24 | /* cmd[0] is actual command */ | |
25 | cmd[1] = addr >> 16; | |
26 | cmd[2] = addr >> 8; | |
27 | cmd[3] = addr >> 0; | |
28 | } | |
29 | ||
9f4322fd | 30 | int spi_flash_cmd_read_status(struct spi_flash *flash, u8 *rs) |
4d5e29a6 | 31 | { |
4d5e29a6 | 32 | int ret; |
9f4322fd | 33 | u8 cmd; |
4d5e29a6 | 34 | |
9f4322fd JT |
35 | cmd = CMD_READ_STATUS; |
36 | ret = spi_flash_read_common(flash, &cmd, 1, rs, 1); | |
4d5e29a6 | 37 | if (ret < 0) { |
9f4322fd | 38 | debug("SF: fail to read status register\n"); |
4d5e29a6 JT |
39 | return ret; |
40 | } | |
41 | ||
42 | return 0; | |
43 | } | |
44 | ||
baaaa753 JT |
45 | static int read_fsr(struct spi_flash *flash, u8 *fsr) |
46 | { | |
47 | int ret; | |
48 | const u8 cmd = CMD_FLAG_STATUS; | |
49 | ||
50 | ret = spi_flash_read_common(flash, &cmd, 1, fsr, 1); | |
51 | if (ret < 0) { | |
52 | debug("SF: fail to read flag status register\n"); | |
53 | return ret; | |
54 | } | |
55 | ||
56 | return 0; | |
57 | } | |
58 | ||
2ba863fa | 59 | int spi_flash_cmd_write_status(struct spi_flash *flash, u8 ws) |
06795122 | 60 | { |
06795122 JT |
61 | u8 cmd; |
62 | int ret; | |
63 | ||
9f4322fd | 64 | cmd = CMD_WRITE_STATUS; |
2ba863fa | 65 | ret = spi_flash_write_common(flash, &cmd, 1, &ws, 1); |
06795122 | 66 | if (ret < 0) { |
9f4322fd | 67 | debug("SF: fail to write status register\n"); |
06795122 JT |
68 | return ret; |
69 | } | |
70 | ||
9f4322fd | 71 | return 0; |
06795122 | 72 | } |
06795122 | 73 | |
d08a1baf | 74 | #if defined(CONFIG_SPI_FLASH_SPANSION) || defined(CONFIG_SPI_FLASH_WINBOND) |
9f4322fd | 75 | int spi_flash_cmd_read_config(struct spi_flash *flash, u8 *rc) |
6cba6fdf | 76 | { |
6cba6fdf | 77 | int ret; |
9f4322fd | 78 | u8 cmd; |
6cba6fdf | 79 | |
9f4322fd JT |
80 | cmd = CMD_READ_CONFIG; |
81 | ret = spi_flash_read_common(flash, &cmd, 1, rc, 1); | |
6cba6fdf | 82 | if (ret < 0) { |
9f4322fd | 83 | debug("SF: fail to read config register\n"); |
6cba6fdf JT |
84 | return ret; |
85 | } | |
86 | ||
87 | return 0; | |
88 | } | |
89 | ||
9f4322fd | 90 | int spi_flash_cmd_write_config(struct spi_flash *flash, u8 wc) |
d08a1baf | 91 | { |
9f4322fd | 92 | u8 data[2]; |
d08a1baf JT |
93 | u8 cmd; |
94 | int ret; | |
95 | ||
9f4322fd JT |
96 | ret = spi_flash_cmd_read_status(flash, &data[0]); |
97 | if (ret < 0) | |
d08a1baf | 98 | return ret; |
d08a1baf | 99 | |
9f4322fd JT |
100 | cmd = CMD_WRITE_STATUS; |
101 | data[1] = wc; | |
102 | ret = spi_flash_write_common(flash, &cmd, 1, &data, 2); | |
103 | if (ret) { | |
104 | debug("SF: fail to write config register\n"); | |
105 | return ret; | |
d08a1baf JT |
106 | } |
107 | ||
9f4322fd | 108 | return 0; |
d08a1baf JT |
109 | } |
110 | #endif | |
111 | ||
4d5e29a6 | 112 | #ifdef CONFIG_SPI_FLASH_BAR |
70ccf594 | 113 | static int spi_flash_write_bank(struct spi_flash *flash, u32 offset) |
4d5e29a6 | 114 | { |
70ccf594 | 115 | u8 cmd, bank_sel; |
4d5e29a6 JT |
116 | int ret; |
117 | ||
70ccf594 JT |
118 | bank_sel = offset / (SPI_FLASH_16MB_BOUN << flash->shift); |
119 | if (bank_sel == flash->bank_curr) | |
120 | goto bar_end; | |
4d5e29a6 JT |
121 | |
122 | cmd = flash->bank_write_cmd; | |
123 | ret = spi_flash_write_common(flash, &cmd, 1, &bank_sel, 1); | |
124 | if (ret < 0) { | |
125 | debug("SF: fail to write bank register\n"); | |
126 | return ret; | |
127 | } | |
6152dd15 | 128 | |
70ccf594 JT |
129 | bar_end: |
130 | flash->bank_curr = bank_sel; | |
131 | return flash->bank_curr; | |
6152dd15 | 132 | } |
4d5e29a6 JT |
133 | #endif |
134 | ||
b902e07c | 135 | #ifdef CONFIG_SF_DUAL_FLASH |
f77f4691 JT |
136 | static void spi_flash_dual_flash(struct spi_flash *flash, u32 *addr) |
137 | { | |
138 | switch (flash->dual_flash) { | |
139 | case SF_DUAL_STACKED_FLASH: | |
140 | if (*addr >= (flash->size >> 1)) { | |
141 | *addr -= flash->size >> 1; | |
142 | flash->spi->flags |= SPI_XFER_U_PAGE; | |
143 | } else { | |
144 | flash->spi->flags &= ~SPI_XFER_U_PAGE; | |
145 | } | |
146 | break; | |
056fbc73 JT |
147 | case SF_DUAL_PARALLEL_FLASH: |
148 | *addr >>= flash->shift; | |
149 | break; | |
f77f4691 JT |
150 | default: |
151 | debug("SF: Unsupported dual_flash=%d\n", flash->dual_flash); | |
152 | break; | |
153 | } | |
154 | } | |
b902e07c | 155 | #endif |
f77f4691 | 156 | |
baaaa753 | 157 | static int spi_flash_sr_ready(struct spi_flash *flash) |
06bc1756 | 158 | { |
4efad20a | 159 | u8 sr; |
baaaa753 JT |
160 | int ret; |
161 | ||
162 | ret = spi_flash_cmd_read_status(flash, &sr); | |
163 | if (ret < 0) | |
164 | return ret; | |
165 | ||
166 | return !(sr & STATUS_WIP); | |
167 | } | |
168 | ||
169 | static int spi_flash_fsr_ready(struct spi_flash *flash) | |
170 | { | |
171 | u8 fsr; | |
172 | int ret; | |
173 | ||
174 | ret = read_fsr(flash, &fsr); | |
175 | if (ret < 0) | |
176 | return ret; | |
177 | ||
178 | return fsr & STATUS_PEC; | |
179 | } | |
180 | ||
181 | static int spi_flash_ready(struct spi_flash *flash) | |
182 | { | |
183 | int sr, fsr; | |
184 | ||
185 | sr = spi_flash_sr_ready(flash); | |
186 | if (sr < 0) | |
187 | return sr; | |
188 | ||
189 | fsr = 1; | |
190 | if (flash->flags & SNOR_F_USE_FSR) { | |
191 | fsr = spi_flash_fsr_ready(flash); | |
192 | if (fsr < 0) | |
193 | return fsr; | |
194 | } | |
195 | ||
196 | return sr && fsr; | |
197 | } | |
198 | ||
199 | int spi_flash_cmd_wait_ready(struct spi_flash *flash, unsigned long timeout) | |
200 | { | |
4efad20a | 201 | int timebase, ret; |
06bc1756 | 202 | |
4efad20a | 203 | timebase = get_timer(0); |
06bc1756 | 204 | |
4efad20a | 205 | while (get_timer(timebase) < timeout) { |
baaaa753 | 206 | ret = spi_flash_ready(flash); |
06bc1756 SDPP |
207 | if (ret < 0) |
208 | return ret; | |
baaaa753 | 209 | if (ret) |
4efad20a | 210 | return 0; |
06bc1756 SDPP |
211 | } |
212 | ||
4efad20a JT |
213 | printf("SF: Timeout!\n"); |
214 | ||
215 | return -ETIMEDOUT; | |
06bc1756 SDPP |
216 | } |
217 | ||
4d5e29a6 JT |
218 | int spi_flash_write_common(struct spi_flash *flash, const u8 *cmd, |
219 | size_t cmd_len, const void *buf, size_t buf_len) | |
220 | { | |
221 | struct spi_slave *spi = flash->spi; | |
222 | unsigned long timeout = SPI_FLASH_PROG_TIMEOUT; | |
223 | int ret; | |
224 | ||
225 | if (buf == NULL) | |
226 | timeout = SPI_FLASH_PAGE_ERASE_TIMEOUT; | |
227 | ||
228 | ret = spi_claim_bus(flash->spi); | |
229 | if (ret) { | |
230 | debug("SF: unable to claim SPI bus\n"); | |
231 | return ret; | |
232 | } | |
233 | ||
234 | ret = spi_flash_cmd_write_enable(flash); | |
235 | if (ret < 0) { | |
236 | debug("SF: enabling write failed\n"); | |
237 | return ret; | |
238 | } | |
239 | ||
240 | ret = spi_flash_cmd_write(spi, cmd, cmd_len, buf, buf_len); | |
241 | if (ret < 0) { | |
242 | debug("SF: write cmd failed\n"); | |
243 | return ret; | |
244 | } | |
245 | ||
246 | ret = spi_flash_cmd_wait_ready(flash, timeout); | |
247 | if (ret < 0) { | |
248 | debug("SF: write %s timed out\n", | |
249 | timeout == SPI_FLASH_PROG_TIMEOUT ? | |
250 | "program" : "page erase"); | |
251 | return ret; | |
252 | } | |
253 | ||
254 | spi_release_bus(spi); | |
255 | ||
256 | return ret; | |
257 | } | |
258 | ||
a5e8199a | 259 | int spi_flash_cmd_erase_ops(struct spi_flash *flash, u32 offset, size_t len) |
4d5e29a6 | 260 | { |
f77f4691 | 261 | u32 erase_size, erase_addr; |
ff063ed4 | 262 | u8 cmd[SPI_FLASH_CMD_LEN]; |
4d5e29a6 JT |
263 | int ret = -1; |
264 | ||
f4f51a8f | 265 | erase_size = flash->erase_size; |
4d5e29a6 JT |
266 | if (offset % erase_size || len % erase_size) { |
267 | debug("SF: Erase offset/length not multiple of erase size\n"); | |
268 | return -1; | |
269 | } | |
270 | ||
439fcb9b BM |
271 | if (flash->flash_is_locked) { |
272 | if (flash->flash_is_locked(flash, offset, len) > 0) { | |
273 | printf("offset 0x%x is protected and cannot be erased\n", | |
274 | offset); | |
275 | return -EINVAL; | |
276 | } | |
c3c016cf FE |
277 | } |
278 | ||
f4f51a8f | 279 | cmd[0] = flash->erase_cmd; |
4d5e29a6 | 280 | while (len) { |
f77f4691 JT |
281 | erase_addr = offset; |
282 | ||
b902e07c | 283 | #ifdef CONFIG_SF_DUAL_FLASH |
f77f4691 JT |
284 | if (flash->dual_flash > SF_SINGLE_FLASH) |
285 | spi_flash_dual_flash(flash, &erase_addr); | |
b902e07c | 286 | #endif |
4d5e29a6 | 287 | #ifdef CONFIG_SPI_FLASH_BAR |
70ccf594 | 288 | ret = spi_flash_write_bank(flash, erase_addr); |
6152dd15 | 289 | if (ret < 0) |
4d5e29a6 | 290 | return ret; |
4d5e29a6 | 291 | #endif |
f77f4691 | 292 | spi_flash_addr(erase_addr, cmd); |
4d5e29a6 JT |
293 | |
294 | debug("SF: erase %2x %2x %2x %2x (%x)\n", cmd[0], cmd[1], | |
f77f4691 | 295 | cmd[2], cmd[3], erase_addr); |
4d5e29a6 JT |
296 | |
297 | ret = spi_flash_write_common(flash, cmd, sizeof(cmd), NULL, 0); | |
298 | if (ret < 0) { | |
299 | debug("SF: erase failed\n"); | |
300 | break; | |
301 | } | |
302 | ||
303 | offset += erase_size; | |
304 | len -= erase_size; | |
305 | } | |
306 | ||
307 | return ret; | |
308 | } | |
309 | ||
a5e8199a | 310 | int spi_flash_cmd_write_ops(struct spi_flash *flash, u32 offset, |
4d5e29a6 JT |
311 | size_t len, const void *buf) |
312 | { | |
313 | unsigned long byte_addr, page_size; | |
f77f4691 | 314 | u32 write_addr; |
4d5e29a6 | 315 | size_t chunk_len, actual; |
ff063ed4 | 316 | u8 cmd[SPI_FLASH_CMD_LEN]; |
4d5e29a6 JT |
317 | int ret = -1; |
318 | ||
319 | page_size = flash->page_size; | |
320 | ||
439fcb9b BM |
321 | if (flash->flash_is_locked) { |
322 | if (flash->flash_is_locked(flash, offset, len) > 0) { | |
323 | printf("offset 0x%x is protected and cannot be written\n", | |
324 | offset); | |
325 | return -EINVAL; | |
326 | } | |
c3c016cf FE |
327 | } |
328 | ||
3163aaa6 | 329 | cmd[0] = flash->write_cmd; |
4d5e29a6 | 330 | for (actual = 0; actual < len; actual += chunk_len) { |
f77f4691 JT |
331 | write_addr = offset; |
332 | ||
b902e07c | 333 | #ifdef CONFIG_SF_DUAL_FLASH |
f77f4691 JT |
334 | if (flash->dual_flash > SF_SINGLE_FLASH) |
335 | spi_flash_dual_flash(flash, &write_addr); | |
b902e07c | 336 | #endif |
4d5e29a6 | 337 | #ifdef CONFIG_SPI_FLASH_BAR |
70ccf594 | 338 | ret = spi_flash_write_bank(flash, write_addr); |
6152dd15 | 339 | if (ret < 0) |
4d5e29a6 | 340 | return ret; |
4d5e29a6 JT |
341 | #endif |
342 | byte_addr = offset % page_size; | |
b4141195 | 343 | chunk_len = min(len - actual, (size_t)(page_size - byte_addr)); |
4d5e29a6 JT |
344 | |
345 | if (flash->spi->max_write_size) | |
b4141195 MY |
346 | chunk_len = min(chunk_len, |
347 | (size_t)flash->spi->max_write_size); | |
4d5e29a6 | 348 | |
f77f4691 | 349 | spi_flash_addr(write_addr, cmd); |
4d5e29a6 | 350 | |
2ba863fa | 351 | debug("SF: 0x%p => cmd = { 0x%02x 0x%02x%02x%02x } chunk_len = %zu\n", |
4d5e29a6 JT |
352 | buf + actual, cmd[0], cmd[1], cmd[2], cmd[3], chunk_len); |
353 | ||
354 | ret = spi_flash_write_common(flash, cmd, sizeof(cmd), | |
355 | buf + actual, chunk_len); | |
356 | if (ret < 0) { | |
357 | debug("SF: write failed\n"); | |
358 | break; | |
359 | } | |
360 | ||
361 | offset += chunk_len; | |
362 | } | |
363 | ||
364 | return ret; | |
365 | } | |
366 | ||
367 | int spi_flash_read_common(struct spi_flash *flash, const u8 *cmd, | |
368 | size_t cmd_len, void *data, size_t data_len) | |
369 | { | |
370 | struct spi_slave *spi = flash->spi; | |
371 | int ret; | |
372 | ||
373 | ret = spi_claim_bus(flash->spi); | |
374 | if (ret) { | |
375 | debug("SF: unable to claim SPI bus\n"); | |
376 | return ret; | |
377 | } | |
378 | ||
379 | ret = spi_flash_cmd_read(spi, cmd, cmd_len, data, data_len); | |
380 | if (ret < 0) { | |
381 | debug("SF: read cmd failed\n"); | |
382 | return ret; | |
383 | } | |
384 | ||
385 | spi_release_bus(spi); | |
386 | ||
387 | return ret; | |
388 | } | |
389 | ||
146bad96 TR |
390 | void __weak spi_flash_copy_mmap(void *data, void *offset, size_t len) |
391 | { | |
392 | memcpy(data, offset, len); | |
393 | } | |
394 | ||
a5e8199a | 395 | int spi_flash_cmd_read_ops(struct spi_flash *flash, u32 offset, |
4d5e29a6 JT |
396 | size_t len, void *data) |
397 | { | |
ab92224f | 398 | u8 *cmd, cmdsz; |
f77f4691 | 399 | u32 remain_len, read_len, read_addr; |
ab92224f | 400 | int bank_sel = 0; |
4d5e29a6 JT |
401 | int ret = -1; |
402 | ||
403 | /* Handle memory-mapped SPI */ | |
404 | if (flash->memory_map) { | |
ac5cce38 PS |
405 | ret = spi_claim_bus(flash->spi); |
406 | if (ret) { | |
407 | debug("SF: unable to claim SPI bus\n"); | |
408 | return ret; | |
409 | } | |
004f15b6 | 410 | spi_xfer(flash->spi, 0, NULL, NULL, SPI_XFER_MMAP); |
146bad96 | 411 | spi_flash_copy_mmap(data, flash->memory_map + offset, len); |
004f15b6 | 412 | spi_xfer(flash->spi, 0, NULL, NULL, SPI_XFER_MMAP_END); |
ac5cce38 | 413 | spi_release_bus(flash->spi); |
4d5e29a6 JT |
414 | return 0; |
415 | } | |
416 | ||
ff063ed4 | 417 | cmdsz = SPI_FLASH_CMD_LEN + flash->dummy_byte; |
c6136aad JT |
418 | cmd = calloc(1, cmdsz); |
419 | if (!cmd) { | |
420 | debug("SF: Failed to allocate cmd\n"); | |
421 | return -ENOMEM; | |
422 | } | |
4d5e29a6 | 423 | |
ff063ed4 | 424 | cmd[0] = flash->read_cmd; |
4d5e29a6 | 425 | while (len) { |
f77f4691 JT |
426 | read_addr = offset; |
427 | ||
b902e07c | 428 | #ifdef CONFIG_SF_DUAL_FLASH |
f77f4691 JT |
429 | if (flash->dual_flash > SF_SINGLE_FLASH) |
430 | spi_flash_dual_flash(flash, &read_addr); | |
b902e07c | 431 | #endif |
4d5e29a6 | 432 | #ifdef CONFIG_SPI_FLASH_BAR |
70ccf594 JT |
433 | ret = spi_flash_write_bank(flash, read_addr); |
434 | if (ret < 0) | |
4d5e29a6 | 435 | return ret; |
70ccf594 | 436 | bank_sel = flash->bank_curr; |
4d5e29a6 | 437 | #endif |
056fbc73 JT |
438 | remain_len = ((SPI_FLASH_16MB_BOUN << flash->shift) * |
439 | (bank_sel + 1)) - offset; | |
4d5e29a6 JT |
440 | if (len < remain_len) |
441 | read_len = len; | |
442 | else | |
443 | read_len = remain_len; | |
444 | ||
f77f4691 | 445 | spi_flash_addr(read_addr, cmd); |
4d5e29a6 | 446 | |
ff063ed4 | 447 | ret = spi_flash_read_common(flash, cmd, cmdsz, data, read_len); |
4d5e29a6 JT |
448 | if (ret < 0) { |
449 | debug("SF: read failed\n"); | |
450 | break; | |
451 | } | |
452 | ||
453 | offset += read_len; | |
454 | len -= read_len; | |
455 | data += read_len; | |
456 | } | |
457 | ||
a52a178f | 458 | free(cmd); |
4d5e29a6 JT |
459 | return ret; |
460 | } | |
10ca45d0 JT |
461 | |
462 | #ifdef CONFIG_SPI_FLASH_SST | |
463 | static int sst_byte_write(struct spi_flash *flash, u32 offset, const void *buf) | |
464 | { | |
465 | int ret; | |
466 | u8 cmd[4] = { | |
467 | CMD_SST_BP, | |
468 | offset >> 16, | |
469 | offset >> 8, | |
470 | offset, | |
471 | }; | |
472 | ||
473 | debug("BP[%02x]: 0x%p => cmd = { 0x%02x 0x%06x }\n", | |
474 | spi_w8r8(flash->spi, CMD_READ_STATUS), buf, cmd[0], offset); | |
475 | ||
476 | ret = spi_flash_cmd_write_enable(flash); | |
477 | if (ret) | |
478 | return ret; | |
479 | ||
480 | ret = spi_flash_cmd_write(flash->spi, cmd, sizeof(cmd), buf, 1); | |
481 | if (ret) | |
482 | return ret; | |
483 | ||
484 | return spi_flash_cmd_wait_ready(flash, SPI_FLASH_PROG_TIMEOUT); | |
485 | } | |
486 | ||
487 | int sst_write_wp(struct spi_flash *flash, u32 offset, size_t len, | |
488 | const void *buf) | |
489 | { | |
490 | size_t actual, cmd_len; | |
491 | int ret; | |
492 | u8 cmd[4]; | |
493 | ||
494 | ret = spi_claim_bus(flash->spi); | |
495 | if (ret) { | |
496 | debug("SF: Unable to claim SPI bus\n"); | |
497 | return ret; | |
498 | } | |
499 | ||
500 | /* If the data is not word aligned, write out leading single byte */ | |
501 | actual = offset % 2; | |
502 | if (actual) { | |
503 | ret = sst_byte_write(flash, offset, buf); | |
504 | if (ret) | |
505 | goto done; | |
506 | } | |
507 | offset += actual; | |
508 | ||
509 | ret = spi_flash_cmd_write_enable(flash); | |
510 | if (ret) | |
511 | goto done; | |
512 | ||
513 | cmd_len = 4; | |
514 | cmd[0] = CMD_SST_AAI_WP; | |
515 | cmd[1] = offset >> 16; | |
516 | cmd[2] = offset >> 8; | |
517 | cmd[3] = offset; | |
518 | ||
519 | for (; actual < len - 1; actual += 2) { | |
520 | debug("WP[%02x]: 0x%p => cmd = { 0x%02x 0x%06x }\n", | |
521 | spi_w8r8(flash->spi, CMD_READ_STATUS), buf + actual, | |
522 | cmd[0], offset); | |
523 | ||
524 | ret = spi_flash_cmd_write(flash->spi, cmd, cmd_len, | |
525 | buf + actual, 2); | |
526 | if (ret) { | |
527 | debug("SF: sst word program failed\n"); | |
528 | break; | |
529 | } | |
530 | ||
531 | ret = spi_flash_cmd_wait_ready(flash, SPI_FLASH_PROG_TIMEOUT); | |
532 | if (ret) | |
533 | break; | |
534 | ||
535 | cmd_len = 1; | |
536 | offset += 2; | |
537 | } | |
538 | ||
539 | if (!ret) | |
540 | ret = spi_flash_cmd_write_disable(flash); | |
541 | ||
542 | /* If there is a single trailing byte, write it out */ | |
543 | if (!ret && actual != len) | |
544 | ret = sst_byte_write(flash, offset, buf + actual); | |
545 | ||
546 | done: | |
547 | debug("SF: sst: program %s %zu bytes @ 0x%zx\n", | |
548 | ret ? "failure" : "success", len, offset - actual); | |
549 | ||
550 | spi_release_bus(flash->spi); | |
551 | return ret; | |
552 | } | |
74c2cee4 BM |
553 | |
554 | int sst_write_bp(struct spi_flash *flash, u32 offset, size_t len, | |
555 | const void *buf) | |
556 | { | |
557 | size_t actual; | |
558 | int ret; | |
559 | ||
560 | ret = spi_claim_bus(flash->spi); | |
561 | if (ret) { | |
562 | debug("SF: Unable to claim SPI bus\n"); | |
563 | return ret; | |
564 | } | |
565 | ||
566 | for (actual = 0; actual < len; actual++) { | |
567 | ret = sst_byte_write(flash, offset, buf + actual); | |
568 | if (ret) { | |
569 | debug("SF: sst byte program failed\n"); | |
570 | break; | |
571 | } | |
572 | offset++; | |
573 | } | |
574 | ||
575 | if (!ret) | |
576 | ret = spi_flash_cmd_write_disable(flash); | |
577 | ||
578 | debug("SF: sst: program %s %zu bytes @ 0x%zx\n", | |
579 | ret ? "failure" : "success", len, offset - actual); | |
580 | ||
581 | spi_release_bus(flash->spi); | |
582 | return ret; | |
583 | } | |
10ca45d0 | 584 | #endif |
41b358d7 FE |
585 | |
586 | #ifdef CONFIG_SPI_FLASH_STMICRO | |
587 | static void stm_get_locked_range(struct spi_flash *flash, u8 sr, loff_t *ofs, | |
588 | u32 *len) | |
589 | { | |
590 | u8 mask = SR_BP2 | SR_BP1 | SR_BP0; | |
591 | int shift = ffs(mask) - 1; | |
592 | int pow; | |
593 | ||
594 | if (!(sr & mask)) { | |
595 | /* No protection */ | |
596 | *ofs = 0; | |
597 | *len = 0; | |
598 | } else { | |
599 | pow = ((sr & mask) ^ mask) >> shift; | |
600 | *len = flash->size >> pow; | |
601 | *ofs = flash->size - *len; | |
602 | } | |
603 | } | |
604 | ||
605 | /* | |
606 | * Return 1 if the entire region is locked, 0 otherwise | |
607 | */ | |
c3c016cf | 608 | static int stm_is_locked_sr(struct spi_flash *flash, u32 ofs, u32 len, |
41b358d7 FE |
609 | u8 sr) |
610 | { | |
611 | loff_t lock_offs; | |
612 | u32 lock_len; | |
613 | ||
614 | stm_get_locked_range(flash, sr, &lock_offs, &lock_len); | |
615 | ||
616 | return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs); | |
617 | } | |
618 | ||
619 | /* | |
620 | * Check if a region of the flash is (completely) locked. See stm_lock() for | |
621 | * more info. | |
622 | * | |
623 | * Returns 1 if entire region is locked, 0 if any portion is unlocked, and | |
624 | * negative on errors. | |
625 | */ | |
c3c016cf | 626 | int stm_is_locked(struct spi_flash *flash, u32 ofs, size_t len) |
41b358d7 FE |
627 | { |
628 | int status; | |
629 | u8 sr; | |
630 | ||
631 | status = spi_flash_cmd_read_status(flash, &sr); | |
632 | if (status < 0) | |
633 | return status; | |
634 | ||
635 | return stm_is_locked_sr(flash, ofs, len, sr); | |
636 | } | |
637 | ||
638 | /* | |
639 | * Lock a region of the flash. Compatible with ST Micro and similar flash. | |
640 | * Supports only the block protection bits BP{0,1,2} in the status register | |
641 | * (SR). Does not support these features found in newer SR bitfields: | |
642 | * - TB: top/bottom protect - only handle TB=0 (top protect) | |
643 | * - SEC: sector/block protect - only handle SEC=0 (block protect) | |
644 | * - CMP: complement protect - only support CMP=0 (range is not complemented) | |
645 | * | |
646 | * Sample table portion for 8MB flash (Winbond w25q64fw): | |
647 | * | |
648 | * SEC | TB | BP2 | BP1 | BP0 | Prot Length | Protected Portion | |
649 | * -------------------------------------------------------------------------- | |
650 | * X | X | 0 | 0 | 0 | NONE | NONE | |
651 | * 0 | 0 | 0 | 0 | 1 | 128 KB | Upper 1/64 | |
652 | * 0 | 0 | 0 | 1 | 0 | 256 KB | Upper 1/32 | |
653 | * 0 | 0 | 0 | 1 | 1 | 512 KB | Upper 1/16 | |
654 | * 0 | 0 | 1 | 0 | 0 | 1 MB | Upper 1/8 | |
655 | * 0 | 0 | 1 | 0 | 1 | 2 MB | Upper 1/4 | |
656 | * 0 | 0 | 1 | 1 | 0 | 4 MB | Upper 1/2 | |
657 | * X | X | 1 | 1 | 1 | 8 MB | ALL | |
658 | * | |
659 | * Returns negative on errors, 0 on success. | |
660 | */ | |
661 | int stm_lock(struct spi_flash *flash, u32 ofs, size_t len) | |
662 | { | |
663 | u8 status_old, status_new; | |
664 | u8 mask = SR_BP2 | SR_BP1 | SR_BP0; | |
665 | u8 shift = ffs(mask) - 1, pow, val; | |
666 | ||
667 | spi_flash_cmd_read_status(flash, &status_old); | |
668 | ||
669 | /* SPI NOR always locks to the end */ | |
670 | if (ofs + len != flash->size) { | |
671 | /* Does combined region extend to end? */ | |
672 | if (!stm_is_locked_sr(flash, ofs + len, flash->size - ofs - len, | |
673 | status_old)) | |
674 | return -EINVAL; | |
675 | len = flash->size - ofs; | |
676 | } | |
677 | ||
678 | /* | |
679 | * Need smallest pow such that: | |
680 | * | |
681 | * 1 / (2^pow) <= (len / size) | |
682 | * | |
683 | * so (assuming power-of-2 size) we do: | |
684 | * | |
685 | * pow = ceil(log2(size / len)) = log2(size) - floor(log2(len)) | |
686 | */ | |
687 | pow = ilog2(flash->size) - ilog2(len); | |
688 | val = mask - (pow << shift); | |
689 | if (val & ~mask) | |
690 | return -EINVAL; | |
691 | ||
692 | /* Don't "lock" with no region! */ | |
693 | if (!(val & mask)) | |
694 | return -EINVAL; | |
695 | ||
696 | status_new = (status_old & ~mask) | val; | |
697 | ||
698 | /* Only modify protection if it will not unlock other areas */ | |
699 | if ((status_new & mask) <= (status_old & mask)) | |
700 | return -EINVAL; | |
701 | ||
702 | spi_flash_cmd_write_status(flash, status_new); | |
703 | ||
704 | return 0; | |
705 | } | |
706 | ||
707 | /* | |
708 | * Unlock a region of the flash. See stm_lock() for more info | |
709 | * | |
710 | * Returns negative on errors, 0 on success. | |
711 | */ | |
712 | int stm_unlock(struct spi_flash *flash, u32 ofs, size_t len) | |
713 | { | |
714 | uint8_t status_old, status_new; | |
715 | u8 mask = SR_BP2 | SR_BP1 | SR_BP0; | |
716 | u8 shift = ffs(mask) - 1, pow, val; | |
717 | ||
718 | spi_flash_cmd_read_status(flash, &status_old); | |
719 | ||
720 | /* Cannot unlock; would unlock larger region than requested */ | |
721 | if (stm_is_locked_sr(flash, status_old, ofs - flash->erase_size, | |
722 | flash->erase_size)) | |
723 | return -EINVAL; | |
724 | /* | |
725 | * Need largest pow such that: | |
726 | * | |
727 | * 1 / (2^pow) >= (len / size) | |
728 | * | |
729 | * so (assuming power-of-2 size) we do: | |
730 | * | |
731 | * pow = floor(log2(size / len)) = log2(size) - ceil(log2(len)) | |
732 | */ | |
733 | pow = ilog2(flash->size) - order_base_2(flash->size - (ofs + len)); | |
734 | if (ofs + len == flash->size) { | |
735 | val = 0; /* fully unlocked */ | |
736 | } else { | |
737 | val = mask - (pow << shift); | |
738 | /* Some power-of-two sizes are not supported */ | |
739 | if (val & ~mask) | |
740 | return -EINVAL; | |
741 | } | |
742 | ||
743 | status_new = (status_old & ~mask) | val; | |
744 | ||
745 | /* Only modify protection if it will not lock other areas */ | |
746 | if ((status_new & mask) >= (status_old & mask)) | |
747 | return -EINVAL; | |
748 | ||
749 | spi_flash_cmd_write_status(flash, status_new); | |
750 | ||
751 | return 0; | |
752 | } | |
753 | #endif /* CONFIG_SPI_FLASH_STMICRO */ |