forked from betaflight/betaflight
-
Notifications
You must be signed in to change notification settings - Fork 1
/
flash_w25n01g.c
1102 lines (857 loc) · 36.3 KB
/
flash_w25n01g.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* This file is part of Cleanflight and Betaflight.
*
* Cleanflight and Betaflight are free software. You can redistribute
* this software and/or modify this software under the terms of the
* GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Cleanflight and Betaflight are distributed in the hope that they
* will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software.
*
* If not, see <http://www.gnu.org/licenses/>.
*
* Author: jflyper
*/
#include <stdbool.h>
#include <stdint.h>
#include "platform.h"
#ifdef USE_FLASH_W25N01G
#include "flash.h"
#include "flash_impl.h"
#include "flash_w25n01g.h"
#include "drivers/bus_spi.h"
#include "drivers/bus_quadspi.h"
#include "drivers/io.h"
#include "drivers/time.h"
// Device size parameters
#define W25N01G_PAGE_SIZE 2048
#define W25N01G_PAGES_PER_BLOCK 64
#define W25N01G_BLOCKS_PER_DIE 1024
// BB replacement area
#define W25N01G_BB_MARKER_BLOCKS 1
#define W25N01G_BB_REPLACEMENT_BLOCKS 20
#define W25N01G_BB_MANAGEMENT_BLOCKS (W25N01G_BB_REPLACEMENT_BLOCKS + W25N01G_BB_MARKER_BLOCKS)
// blocks are zero-based index
#define W25N01G_BB_REPLACEMENT_START_BLOCK (W25N01G_BLOCKS_PER_DIE - W25N01G_BB_REPLACEMENT_BLOCKS)
#define W25N01G_BB_MANAGEMENT_START_BLOCK (W25N01G_BLOCKS_PER_DIE - W25N01G_BB_MANAGEMENT_BLOCKS)
#define W25N01G_BB_MARKER_BLOCK (W25N01G_BB_REPLACEMENT_START_BLOCK - W25N01G_BB_MARKER_BLOCKS)
// Instructions
#define W25N01G_INSTRUCTION_RDID 0x9F
#define W25N01G_INSTRUCTION_DEVICE_RESET 0xFF
#define W25N01G_INSTRUCTION_READ_STATUS_REG 0x05
#define W25N01G_INSTRUCTION_READ_STATUS_ALTERNATE_REG 0x0F
#define W25N01G_INSTRUCTION_WRITE_STATUS_REG 0x01
#define W25N01G_INSTRUCTION_WRITE_STATUS_ALTERNATE_REG 0x1F
#define W25N01G_INSTRUCTION_WRITE_ENABLE 0x06
#define W25N01G_INSTRUCTION_DIE_SELECT 0xC2
#define W25N01G_INSTRUCTION_BLOCK_ERASE 0xD8
#define W25N01G_INSTRUCTION_READ_BBM_LUT 0xA5
#define W25N01G_INSTRUCTION_BB_MANAGEMENT 0xA1
#define W25N01G_INSTRUCTION_PROGRAM_DATA_LOAD 0x02
#define W25N01G_INSTRUCTION_RANDOM_PROGRAM_DATA_LOAD 0x84
#define W25N01G_INSTRUCTION_PROGRAM_EXECUTE 0x10
#define W25N01G_INSTRUCTION_PAGE_DATA_READ 0x13
#define W25N01G_INSTRUCTION_READ_DATA 0x03
#define W25N01G_INSTRUCTION_FAST_READ 0x1B
#define W25N01G_INSTRUCTION_FAST_READ_QUAD_OUTPUT 0x6B
// Config/status register addresses
#define W25N01G_PROT_REG 0xA0
#define W25N01G_CONF_REG 0xB0
#define W25N01G_STAT_REG 0xC0
// Bits in config/status register 1 (W25N01G_PROT_REG)
#define W25N01G_PROT_CLEAR (0)
#define W25N01G_PROT_SRP1_ENABLE (1 << 0)
#define W25N01G_PROT_WP_E_ENABLE (1 << 1)
#define W25N01G_PROT_TB_ENABLE (1 << 2)
#define W25N01G_PROT_PB0_ENABLE (1 << 3)
#define W25N01G_PROT_PB1_ENABLE (1 << 4)
#define W25N01G_PROT_PB2_ENABLE (1 << 5)
#define W25N01G_PROT_PB3_ENABLE (1 << 6)
#define W25N01G_PROT_SRP2_ENABLE (1 << 7)
// Bits in config/status register 2 (W25N01G_CONF_REG)
#define W25N01G_CONFIG_ECC_ENABLE (1 << 4)
#define W25N01G_CONFIG_BUFFER_READ_MODE (1 << 3)
// Bits in config/status register 3 (W25N01G_STATREG)
#define W25N01G_STATUS_BBM_LUT_FULL (1 << 6)
#define W25N01G_STATUS_FLAG_ECC_POS 4
#define W25N01G_STATUS_FLAG_ECC_MASK ((1 << 5)|(1 << 4))
#define W25N01G_STATUS_FLAG_ECC(status) (((status) & W25N01G_STATUS_FLAG_ECC_MASK) >> 4)
#define W25N01G_STATUS_PROGRAM_FAIL (1 << 3)
#define W25N01G_STATUS_ERASE_FAIL (1 << 2)
#define W25N01G_STATUS_FLAG_WRITE_ENABLED (1 << 1)
#define W25N01G_STATUS_FLAG_BUSY (1 << 0)
#define W25N01G_BBLUT_TABLE_ENTRY_COUNT 20
#define W25N01G_BBLUT_TABLE_ENTRY_SIZE 4 // in bytes
// Bits in LBA for BB LUT
#define W25N01G_BBLUT_STATUS_ENABLED (1 << 15)
#define W25N01G_BBLUT_STATUS_INVALID (1 << 14)
#define W25N01G_BBLUT_STATUS_MASK (W25N01G_BBLUT_STATUS_ENABLED | W25N01G_BBLUT_STATUS_INVALID)
// Some useful defs and macros
#define W25N01G_LINEAR_TO_COLUMN(laddr) ((laddr) % W25N01G_PAGE_SIZE)
#define W25N01G_LINEAR_TO_PAGE(laddr) ((laddr) / W25N01G_PAGE_SIZE)
#define W25N01G_LINEAR_TO_BLOCK(laddr) (W25N01G_LINEAR_TO_PAGE(laddr) / W25N01G_PAGES_PER_BLOCK)
#define W25N01G_BLOCK_TO_PAGE(block) ((block) * W25N01G_PAGES_PER_BLOCK)
#define W25N01G_BLOCK_TO_LINEAR(block) (W25N01G_BLOCK_TO_PAGE(block) * W25N01G_PAGE_SIZE)
// IMPORTANT: Timeout values are currently required to be set to the highest value required by any of the supported flash chips by this driver
// The timeout values (2ms minimum to avoid 1 tick advance in consecutive calls to millis).
#define W25N01G_TIMEOUT_PAGE_READ_MS 2 // tREmax = 60us (ECC enabled)
#define W25N01G_TIMEOUT_PAGE_PROGRAM_MS 2 // tPPmax = 700us
#define W25N01G_TIMEOUT_BLOCK_ERASE_MS 15 // tBEmax = 10ms
#define W25N01G_TIMEOUT_RESET_MS 500 // tRSTmax = 500ms
// Sizes (in bits)
#define W25N01G_STATUS_REGISTER_SIZE 8
#define W25N01G_STATUS_PAGE_ADDRESS_SIZE 16
#define W25N01G_STATUS_COLUMN_ADDRESS_SIZE 16
typedef struct bblut_s {
uint16_t pba;
uint16_t lba;
} bblut_t;
static bool w25n01g_waitForReady(flashDevice_t *fdevice);
static void w25n01g_setTimeout(flashDevice_t *fdevice, uint32_t timeoutMillis)
{
uint32_t now = millis();
fdevice->timeoutAt = now + timeoutMillis;
}
/**
* Send the given command byte to the device.
*/
static void w25n01g_performOneByteCommand(flashDeviceIO_t *io, uint8_t command)
{
if (io->mode == FLASHIO_SPI) {
extDevice_t *dev = io->handle.dev;
busSegment_t segments[] = {
{.u.buffers = {&command, NULL}, sizeof(command), true, NULL},
{.u.link = {NULL, NULL}, 0, true, NULL},
};
spiSequence(dev, &segments[0]);
// Block pending completion of SPI access
spiWait(dev);
}
#ifdef USE_QUADSPI
else if (io->mode == FLASHIO_QUADSPI) {
QUADSPI_TypeDef *quadSpi = io->handle.quadSpi;
quadSpiTransmit1LINE(quadSpi, command, 0, NULL, 0);
}
#endif
}
static void w25n01g_performCommandWithPageAddress(flashDeviceIO_t *io, uint8_t command, uint32_t pageAddress)
{
if (io->mode == FLASHIO_SPI) {
extDevice_t *dev = io->handle.dev;
uint8_t cmd[] = { command, 0, (pageAddress >> 8) & 0xff, (pageAddress >> 0) & 0xff};
busSegment_t segments[] = {
{.u.buffers = {cmd, NULL}, sizeof(cmd), true, NULL},
{.u.link = {NULL, NULL}, 0, true, NULL},
};
spiSequence(dev, &segments[0]);
// Block pending completion of SPI access
spiWait(dev);
}
#ifdef USE_QUADSPI
else if (io->mode == FLASHIO_QUADSPI) {
QUADSPI_TypeDef *quadSpi = io->handle.quadSpi;
quadSpiInstructionWithAddress1LINE(quadSpi, command, 0, pageAddress & 0xffff, W25N01G_STATUS_PAGE_ADDRESS_SIZE + 8);
}
#endif
}
static uint8_t w25n01g_readRegister(flashDeviceIO_t *io, uint8_t reg)
{
if (io->mode == FLASHIO_SPI) {
extDevice_t *dev = io->handle.dev;
uint8_t cmd[3] = { W25N01G_INSTRUCTION_READ_STATUS_REG, reg, 0 };
uint8_t in[3];
busSegment_t segments[] = {
{.u.buffers = {cmd, in}, sizeof(cmd), true, NULL},
{.u.link = {NULL, NULL}, 0, true, NULL},
};
// Ensure any prior DMA has completed before continuing
spiWait(dev);
spiSequence(dev, &segments[0]);
// Block pending completion of SPI access
spiWait(dev);
return in[2];
}
#ifdef USE_QUADSPI
else if (io->mode == FLASHIO_QUADSPI) {
QUADSPI_TypeDef *quadSpi = io->handle.quadSpi;
uint8_t in[W25N01G_STATUS_REGISTER_SIZE / 8];
quadSpiReceiveWithAddress1LINE(quadSpi, W25N01G_INSTRUCTION_READ_STATUS_REG, 0, reg, W25N01G_STATUS_REGISTER_SIZE, in, sizeof(in));
return in[0];
}
#endif
return 0;
}
static void w25n01g_writeRegister(flashDeviceIO_t *io, uint8_t reg, uint8_t data)
{
if (io->mode == FLASHIO_SPI) {
extDevice_t *dev = io->handle.dev;
uint8_t cmd[3] = { W25N01G_INSTRUCTION_WRITE_STATUS_REG, reg, data };
busSegment_t segments[] = {
{.u.buffers = {cmd, NULL}, sizeof(cmd), true, NULL},
{.u.link = {NULL, NULL}, 0, true, NULL},
};
// Ensure any prior DMA has completed before continuing
spiWait(dev);
spiSequence(dev, &segments[0]);
// Block pending completion of SPI access
spiWait(dev);
}
#ifdef USE_QUADSPI
else if (io->mode == FLASHIO_QUADSPI) {
QUADSPI_TypeDef *quadSpi = io->handle.quadSpi;
quadSpiTransmitWithAddress1LINE(quadSpi, W25N01G_INSTRUCTION_WRITE_STATUS_REG, 0, reg, W25N01G_STATUS_REGISTER_SIZE, &data, 1);
}
#endif
}
static void w25n01g_deviceReset(flashDevice_t *fdevice)
{
flashDeviceIO_t *io = &fdevice->io;
w25n01g_performOneByteCommand(io, W25N01G_INSTRUCTION_DEVICE_RESET);
w25n01g_setTimeout(fdevice, W25N01G_TIMEOUT_RESET_MS);
w25n01g_waitForReady(fdevice);
// Protection for upper 1/32 (BP[3:0] = 0101, TB=0), WP-E on; to protect bad block replacement area
// DON'T DO THIS. This will prevent writes through the bblut as well.
// w25n01g_writeRegister(dev, W25N01G_PROT_REG, W25N01G_PROT_PB0_ENABLE|W25N01G_PROT_PB2_ENABLE|W25N01G_PROT_WP_E_ENABLE);
// No protection, WP-E off, WP-E prevents use of IO2
w25n01g_writeRegister(io, W25N01G_PROT_REG, W25N01G_PROT_CLEAR);
// Buffered read mode (BUF = 1), ECC enabled (ECC = 1)
w25n01g_writeRegister(io, W25N01G_CONF_REG, W25N01G_CONFIG_ECC_ENABLE|W25N01G_CONFIG_BUFFER_READ_MODE);
}
bool w25n01g_isReady(flashDevice_t *fdevice)
{
// If we're waiting on DMA completion, then SPI is busy
if (fdevice->io.mode == FLASHIO_SPI) {
if (fdevice->io.handle.dev->bus->useDMA && spiIsBusy(fdevice->io.handle.dev)) {
return false;
}
}
// Irrespective of the current state of fdevice->couldBeBusy read the status or device blocks
// Poll the FLASH device to see if it's busy
fdevice->couldBeBusy = ((w25n01g_readRegister(&fdevice->io, W25N01G_STAT_REG) & W25N01G_STATUS_FLAG_BUSY) != 0);
return !fdevice->couldBeBusy;
}
static bool w25n01g_waitForReady(flashDevice_t *fdevice)
{
while (!w25n01g_isReady(fdevice)) {
uint32_t now = millis();
if (cmp32(now, fdevice->timeoutAt) >= 0) {
return false;
}
}
fdevice->timeoutAt = 0;
return true;
}
/**
* The flash requires this write enable command to be sent before commands that would cause
* a write like program and erase.
*/
static void w25n01g_writeEnable(flashDevice_t *fdevice)
{
w25n01g_performOneByteCommand(&fdevice->io, W25N01G_INSTRUCTION_WRITE_ENABLE);
// Assume that we're about to do some writing, so the device is just about to become busy
fdevice->couldBeBusy = true;
}
const flashVTable_t w25n01g_vTable;
bool w25n01g_identify(flashDevice_t *fdevice, uint32_t jedecID)
{
switch (jedecID) {
case JEDEC_ID_WINBOND_W25N01GV:
fdevice->geometry.sectors = 1024; // Blocks
fdevice->geometry.pagesPerSector = 64; // Pages/Blocks
fdevice->geometry.pageSize = 2048;
break;
default:
// Unsupported chip
fdevice->geometry.sectors = 0;
fdevice->geometry.pagesPerSector = 0;
fdevice->geometry.sectorSize = 0;
fdevice->geometry.totalSize = 0;
return false;
}
fdevice->geometry.flashType = FLASH_TYPE_NAND;
fdevice->geometry.sectorSize = fdevice->geometry.pagesPerSector * fdevice->geometry.pageSize;
fdevice->geometry.totalSize = fdevice->geometry.sectorSize * fdevice->geometry.sectors;
flashPartitionSet(FLASH_PARTITION_TYPE_BADBLOCK_MANAGEMENT,
W25N01G_BB_MANAGEMENT_START_BLOCK,
W25N01G_BB_MANAGEMENT_START_BLOCK + W25N01G_BB_MANAGEMENT_BLOCKS - 1);
fdevice->couldBeBusy = true; // Just for luck we'll assume the chip could be busy even though it isn't specced to be
fdevice->vTable = &w25n01g_vTable;
#ifndef USE_QUADSPI
// Need to set clock speed for 8kHz logging support with SPI
spiSetClkDivisor(fdevice->io.handle.dev, spiCalculateDivider(100000000));
#endif // USE_QUADSPI
return true;
}
static void w25n01g_deviceInit(flashDevice_t *flashdev);
void w25n01g_configure(flashDevice_t *fdevice, uint32_t configurationFlags)
{
if (configurationFlags & FLASH_CF_SYSTEM_IS_MEMORY_MAPPED) {
return;
}
w25n01g_deviceReset(fdevice);
// Upper 4MB (32 blocks * 128KB/block) will be used for bad block replacement area.
// Blocks in this area are only written through bad block LUT,
// and factory written bad block marker in unused blocks are retained.
// When a replacement block is required,
// (1) "Read BB LUT" command is used to obtain the last block mapped,
// (2) blocks after the last block is scanned for a good block,
// (3) the first good block is used for replacement, and the BB LUT is updated.
// There are only 20 BB LUT entries, and there are 32 replacement blocks.
// There will be a least chance of running out of replacement blocks.
// If it ever run out, the device becomes unusable.
w25n01g_deviceInit(fdevice);
}
/**
* Erase a sector full of bytes to all 1's at the given byte offset in the flash chip.
*/
void w25n01g_eraseSector(flashDevice_t *fdevice, uint32_t address)
{
w25n01g_waitForReady(fdevice);
w25n01g_writeEnable(fdevice);
w25n01g_performCommandWithPageAddress(&fdevice->io, W25N01G_INSTRUCTION_BLOCK_ERASE, W25N01G_LINEAR_TO_PAGE(address));
w25n01g_setTimeout(fdevice, W25N01G_TIMEOUT_BLOCK_ERASE_MS);
}
//
// W25N01G does not support full chip erase.
// Call eraseSector repeatedly.
void w25n01g_eraseCompletely(flashDevice_t *fdevice)
{
for (uint32_t block = 0; block < fdevice->geometry.sectors; block++) {
w25n01g_eraseSector(fdevice, W25N01G_BLOCK_TO_LINEAR(block));
}
}
#ifdef USE_QUADSPI
static void w25n01g_programDataLoad(flashDevice_t *fdevice, uint16_t columnAddress, const uint8_t *data, int length)
{
w25n01g_waitForReady(fdevice);
if (fdevice->io.mode == FLASHIO_SPI) {
extDevice_t *dev = fdevice->io.handle.dev;
uint8_t cmd[] = { W25N01G_INSTRUCTION_PROGRAM_DATA_LOAD, columnAddress >> 8, columnAddress & 0xff };
busSegment_t segments[] = {
{.u.buffers = {cmd, NULL}, sizeof(cmd), false, NULL},
{.u.buffers = {(uint8_t *)data, NULL}, length, true, NULL},
{.u.link = {NULL, NULL}, 0, true, NULL},
};
spiSequence(dev, &segments[0]);
// Block pending completion of SPI access
spiWait(dev);
}
#ifdef USE_QUADSPI
else if (fdevice->io.mode == FLASHIO_QUADSPI) {
QUADSPI_TypeDef *quadSpi = fdevice->io.handle.quadSpi;
quadSpiTransmitWithAddress1LINE(quadSpi, W25N01G_INSTRUCTION_PROGRAM_DATA_LOAD, 0, columnAddress, W25N01G_STATUS_COLUMN_ADDRESS_SIZE, data, length);
}
#endif
w25n01g_setTimeout(fdevice, W25N01G_TIMEOUT_PAGE_PROGRAM_MS);
}
static void w25n01g_randomProgramDataLoad(flashDevice_t *fdevice, uint16_t columnAddress, const uint8_t *data, int length)
{
uint8_t cmd[] = { W25N01G_INSTRUCTION_RANDOM_PROGRAM_DATA_LOAD, columnAddress >> 8, columnAddress & 0xff };
w25n01g_waitForReady(fdevice);
if (fdevice->io.mode == FLASHIO_SPI) {
extDevice_t *dev = fdevice->io.handle.dev;
busSegment_t segments[] = {
{.u.buffers = {cmd, NULL}, sizeof(cmd), false, NULL},
{.u.buffers = {(uint8_t *)data, NULL}, length, true, NULL},
{.u.link = {NULL, NULL}, 0, true, NULL},
};
spiSequence(dev, &segments[0]);
// Block pending completion of SPI access
spiWait(dev);
}
#ifdef USE_QUADSPI
else if (fdevice->io.mode == FLASHIO_QUADSPI) {
QUADSPI_TypeDef *quadSpi = fdevice->io.handle.quadSpi;
quadSpiTransmitWithAddress1LINE(quadSpi, W25N01G_INSTRUCTION_RANDOM_PROGRAM_DATA_LOAD, 0, columnAddress, W25N01G_STATUS_COLUMN_ADDRESS_SIZE, data, length);
}
#endif
w25n01g_setTimeout(fdevice, W25N01G_TIMEOUT_PAGE_PROGRAM_MS);
}
#endif
static void w25n01g_programExecute(flashDevice_t *fdevice, uint32_t pageAddress)
{
w25n01g_waitForReady(fdevice);
w25n01g_performCommandWithPageAddress(&fdevice->io, W25N01G_INSTRUCTION_PROGRAM_EXECUTE, pageAddress);
w25n01g_setTimeout(fdevice, W25N01G_TIMEOUT_PAGE_PROGRAM_MS);
}
//
// Writes are done in three steps:
// (1) Load internal data buffer with data to write
// - We use "Random Load Program Data", as "Load Program Data" resets unused data bytes in the buffer to 0xff.
// - Each "Random Load Program Data" instruction must be accompanied by at least a single data.
// - Each "Random Load Program Data" instruction terminates at the rising of CS.
// (2) Enable write
// (3) Issue "Execute Program"
//
/*
flashfs page program behavior
- Single program never crosses page boundary.
- Except for this characteristic, it program arbitral size.
- Write address is, naturally, not a page boundary.
To cope with this behavior.
If buffer is dirty and programLoadAddress != address, then the last page is a partial write;
issue PAGE_PROGRAM_EXECUTE to flash buffer contents, clear dirty and record the address as programLoadAddress and programStartAddress.
Mark buffer as dirty.
If programLoadAddress is on page boundary, then issue PROGRAM_LOAD_DATA, else issue RANDOM_PROGRAM_LOAD_DATA.
Update programLoadAddress.
Optionally observe the programLoadAddress, and if it's on page boundary, issue PAGE_PROGRAM_EXECUTE.
Observe programLoadAddress. If it's on page boundary, issue PAGE_PROGRAM_EXECUTE and clear dirty, else just return.
If pageProgramContinue observes the page boundary, then do nothing(?).
*/
static uint32_t programStartAddress;
static uint32_t programLoadAddress;
bool bufferDirty = false;
#ifdef USE_QUADSPI
bool isProgramming = false;
void w25n01g_pageProgramBegin(flashDevice_t *fdevice, uint32_t address, void (*callback)(uint32_t length))
{
fdevice->callback = callback;
if (bufferDirty) {
if (address != programLoadAddress) {
w25n01g_waitForReady(fdevice);
isProgramming = false;
w25n01g_writeEnable(fdevice);
w25n01g_programExecute(fdevice, W25N01G_LINEAR_TO_PAGE(programStartAddress));
bufferDirty = false;
isProgramming = true;
}
} else {
programStartAddress = programLoadAddress = address;
}
}
uint32_t w25n01g_pageProgramContinue(flashDevice_t *fdevice, uint8_t const **buffers, uint32_t *bufferSizes, uint32_t bufferCount)
{
if (bufferCount < 1) {
fdevice->callback(0);
return 0;
}
w25n01g_waitForReady(fdevice);
w25n01g_writeEnable(fdevice);
isProgramming = false;
if (!bufferDirty) {
w25n01g_programDataLoad(fdevice, W25N01G_LINEAR_TO_COLUMN(programLoadAddress), buffers[0], bufferSizes[0]);
} else {
w25n01g_randomProgramDataLoad(fdevice, W25N01G_LINEAR_TO_COLUMN(programLoadAddress), buffers[0], bufferSizes[0]);
}
// XXX Test if write enable is reset after each data loading.
bufferDirty = true;
programLoadAddress += bufferSizes[0];
if (fdevice->callback) {
fdevice->callback(bufferSizes[0]);
}
return bufferSizes[0];
}
static uint32_t currentPage = UINT32_MAX;
void w25n01g_pageProgramFinish(flashDevice_t *fdevice)
{
if (bufferDirty && W25N01G_LINEAR_TO_COLUMN(programLoadAddress) == 0) {
currentPage = W25N01G_LINEAR_TO_PAGE(programStartAddress); // reset page to the page being written
w25n01g_programExecute(fdevice, W25N01G_LINEAR_TO_PAGE(programStartAddress));
bufferDirty = false;
isProgramming = true;
programStartAddress = programLoadAddress;
}
}
#else
void w25n01g_pageProgramBegin(flashDevice_t *fdevice, uint32_t address, void (*callback)(uint32_t length))
{
fdevice->callback = callback;
fdevice->currentWriteAddress = address;
}
static uint32_t currentPage = UINT32_MAX;
// Called in ISR context
// Check if the status was busy and if so repeat the poll
busStatus_e w25n01g_callbackReady(uint32_t arg)
{
flashDevice_t *fdevice = (flashDevice_t *)arg;
extDevice_t *dev = fdevice->io.handle.dev;
uint8_t readyPoll = dev->bus->curSegment->u.buffers.rxData[2];
if (readyPoll & W25N01G_STATUS_FLAG_BUSY) {
return BUS_BUSY;
}
// Bus is now known not to be busy
fdevice->couldBeBusy = false;
return BUS_READY;
}
// Called in ISR context
// A write enable has just been issued
busStatus_e w25n01g_callbackWriteEnable(uint32_t arg)
{
flashDevice_t *fdevice = (flashDevice_t *)arg;
// As a write has just occurred, the device could be busy
fdevice->couldBeBusy = true;
return BUS_READY;
}
// Called in ISR context
// Write operation has just completed
busStatus_e w25n01g_callbackWriteComplete(uint32_t arg)
{
flashDevice_t *fdevice = (flashDevice_t *)arg;
fdevice->currentWriteAddress += fdevice->callbackArg;
// Call transfer completion callback
if (fdevice->callback) {
fdevice->callback(fdevice->callbackArg);
}
return BUS_READY;
}
uint32_t w25n01g_pageProgramContinue(flashDevice_t *fdevice, uint8_t const **buffers, uint32_t *bufferSizes, uint32_t bufferCount)
{
if (bufferCount < 1) {
fdevice->callback(0);
return 0;
}
// The segment list cannot be in automatic storage as this routine is non-blocking
STATIC_DMA_DATA_AUTO uint8_t readStatus[] = { W25N01G_INSTRUCTION_READ_STATUS_REG, W25N01G_STAT_REG, 0 };
STATIC_DMA_DATA_AUTO uint8_t readyStatus[3];
STATIC_DMA_DATA_AUTO uint8_t writeEnable[] = { W25N01G_INSTRUCTION_WRITE_ENABLE };
STATIC_DMA_DATA_AUTO uint8_t progExecCmd[] = { W25N01G_INSTRUCTION_PROGRAM_EXECUTE, 0, 0, 0};
STATIC_DMA_DATA_AUTO uint8_t progExecDataLoad[] = { W25N01G_INSTRUCTION_PROGRAM_DATA_LOAD, 0, 0};
STATIC_DMA_DATA_AUTO uint8_t progRandomProgDataLoad[] = { W25N01G_INSTRUCTION_RANDOM_PROGRAM_DATA_LOAD, 0, 0};
static busSegment_t segmentsDataLoad[] = {
{.u.buffers = {readStatus, readyStatus}, sizeof(readStatus), true, w25n01g_callbackReady},
{.u.buffers = {writeEnable, NULL}, sizeof(writeEnable), true, w25n01g_callbackWriteEnable},
{.u.buffers = {progExecDataLoad, NULL}, sizeof(progExecDataLoad), false, NULL},
{.u.link = {NULL, NULL}, 0, true, NULL},
};
static busSegment_t segmentsRandomDataLoad[] = {
{.u.buffers = {readStatus, readyStatus}, sizeof(readStatus), true, w25n01g_callbackReady},
{.u.buffers = {writeEnable, NULL}, sizeof(writeEnable), true, w25n01g_callbackWriteEnable},
{.u.buffers = {progRandomProgDataLoad, NULL}, sizeof(progRandomProgDataLoad), false, NULL},
{.u.link = {NULL, NULL}, 0, true, NULL},
};
static busSegment_t segmentsBuffer[] = {
{.u.buffers = {NULL, NULL}, 0, true, NULL},
{.u.link = {NULL, NULL}, 0, true, NULL},
};
static busSegment_t segmentsFlash[] = {
{.u.buffers = {readStatus, readyStatus}, sizeof(readStatus), true, w25n01g_callbackReady},
{.u.buffers = {writeEnable, NULL}, sizeof(writeEnable), true, w25n01g_callbackWriteEnable},
{.u.buffers = {progExecCmd, NULL}, sizeof(progExecCmd), true, w25n01g_callbackWriteComplete},
{.u.link = {NULL, NULL}, 0, true, NULL},
};
busSegment_t *programSegment;
// Ensure any prior DMA has completed before continuing
spiWait(fdevice->io.handle.dev);
uint32_t columnAddress;
if (bufferDirty) {
columnAddress = W25N01G_LINEAR_TO_COLUMN(programLoadAddress);
// Set the address and buffer details for the random data load
progRandomProgDataLoad[1] = (columnAddress >> 8) & 0xff;
progRandomProgDataLoad[2] = columnAddress & 0xff;
programSegment = segmentsRandomDataLoad;
} else {
programStartAddress = programLoadAddress = fdevice->currentWriteAddress;
columnAddress = W25N01G_LINEAR_TO_COLUMN(programLoadAddress);
// Set the address and buffer details for the data load
progExecDataLoad[1] = (columnAddress >> 8) & 0xff;
progExecDataLoad[2] = columnAddress & 0xff;
programSegment = segmentsDataLoad;
}
// Add the data buffer
segmentsBuffer[0].u.buffers.txData = (uint8_t *)buffers[0];
segmentsBuffer[0].len = bufferSizes[0];
segmentsBuffer[0].callback = NULL;
spiLinkSegments(fdevice->io.handle.dev, programSegment, segmentsBuffer);
bufferDirty = true;
programLoadAddress += bufferSizes[0];
if (W25N01G_LINEAR_TO_COLUMN(programLoadAddress) == 0) {
// Flash the loaded data
currentPage = W25N01G_LINEAR_TO_PAGE(programStartAddress);
progExecCmd[2] = (currentPage >> 8) & 0xff;
progExecCmd[3] = currentPage & 0xff;
spiLinkSegments(fdevice->io.handle.dev, segmentsBuffer, segmentsFlash);
bufferDirty = false;
programStartAddress = programLoadAddress;
} else {
// Callback on completion of data load
segmentsBuffer[0].callback = w25n01g_callbackWriteComplete;
}
if (!fdevice->couldBeBusy) {
// Skip the ready check
programSegment++;
}
fdevice->callbackArg = bufferSizes[0];
spiSequence(fdevice->io.handle.dev, programSegment);
if (fdevice->callback == NULL) {
// No callback was provided so block
// Block pending completion of SPI access
spiWait(fdevice->io.handle.dev);
}
return fdevice->callbackArg;
}
void w25n01g_pageProgramFinish(flashDevice_t *fdevice)
{
UNUSED(fdevice);
}
#endif // USE_QUADSPI
/**
* Write bytes to a flash page. Address must not cross a page boundary.
*
* Bits can only be set to zero, not from zero back to one again. In order to set bits to 1, use the erase command.
*
* Length must be smaller than the page size.
*
* This will wait for the flash to become ready before writing begins.
*
* Datasheet indicates typical programming time is 0.8ms for 256 bytes, 0.2ms for 64 bytes, 0.05ms for 16 bytes.
* (Although the maximum possible write time is noted as 5ms).
*
* If you want to write multiple buffers (whose sum of sizes is still not more than the page size) then you can
* break this operation up into one beginProgram call, one or more continueProgram calls, and one finishProgram call.
*/
void w25n01g_pageProgram(flashDevice_t *fdevice, uint32_t address, const uint8_t *data, uint32_t length, void (*callback)(uint32_t length))
{
w25n01g_pageProgramBegin(fdevice, address, callback);
w25n01g_pageProgramContinue(fdevice, &data, &length, 1);
w25n01g_pageProgramFinish(fdevice);
}
void w25n01g_flush(flashDevice_t *fdevice)
{
if (bufferDirty) {
currentPage = W25N01G_LINEAR_TO_PAGE(programStartAddress); // reset page to the page being written
w25n01g_programExecute(fdevice, W25N01G_LINEAR_TO_PAGE(programStartAddress));
bufferDirty = false;
}
}
void w25n01g_addError(uint32_t address, uint8_t code)
{
UNUSED(address);
UNUSED(code);
}
/**
* Read `length` bytes into the provided `buffer` from the flash starting from the given `address` (which need not lie
* on a page boundary).
*
* Waits up to W25N01G_TIMEOUT_PAGE_READ_MS milliseconds for the flash to become ready before reading.
*
* The number of bytes actually read is returned, which can be zero if an error or timeout occurred.
*/
// Continuous read mode (BUF = 0):
// (1) "Page Data Read" command is executed for the page pointed by address
// (2) "Read Data" command is executed for bytes not requested and data are discarded
// (3) "Read Data" command is executed and data are stored directly into caller's buffer
//
// Buffered read mode (BUF = 1), non-read ahead
// (1) If currentBufferPage != requested page, then issue PAGE_DATA_READ on requested page.
// (2) Compute transferLength as smaller of remaining length and requested length.
// (3) Issue READ_DATA on column address.
// (4) Return transferLength.
int w25n01g_readBytes(flashDevice_t *fdevice, uint32_t address, uint8_t *buffer, uint32_t length)
{
uint32_t targetPage = W25N01G_LINEAR_TO_PAGE(address);
if (currentPage != targetPage) {
if (!w25n01g_waitForReady(fdevice)) {
return 0;
}
currentPage = UINT32_MAX;
w25n01g_performCommandWithPageAddress(&fdevice->io, W25N01G_INSTRUCTION_PAGE_DATA_READ, targetPage);
w25n01g_setTimeout(fdevice, W25N01G_TIMEOUT_PAGE_READ_MS);
if (!w25n01g_waitForReady(fdevice)) {
return 0;
}
currentPage = targetPage;
}
uint32_t column = W25N01G_LINEAR_TO_COLUMN(address);
uint16_t transferLength;
if (length > W25N01G_PAGE_SIZE - column) {
transferLength = W25N01G_PAGE_SIZE - column;
} else {
transferLength = length;
}
if (fdevice->io.mode == FLASHIO_SPI) {
extDevice_t *dev = fdevice->io.handle.dev;
uint8_t cmd[4];
cmd[0] = W25N01G_INSTRUCTION_READ_DATA;
cmd[1] = (column >> 8) & 0xff;
cmd[2] = (column >> 0) & 0xff;
cmd[3] = 0;
busSegment_t segments[] = {
{.u.buffers = {cmd, NULL}, sizeof(cmd), false, NULL},
{.u.buffers = {NULL, buffer}, length, true, NULL},
{.u.link = {NULL, NULL}, 0, true, NULL},
};
spiSequence(dev, &segments[0]);
// Block pending completion of SPI access
spiWait(dev);
}
#ifdef USE_QUADSPI
else if (fdevice->io.mode == FLASHIO_QUADSPI) {
QUADSPI_TypeDef *quadSpi = fdevice->io.handle.quadSpi;
//quadSpiReceiveWithAddress1LINE(quadSpi, W25N01G_INSTRUCTION_READ_DATA, 8, column, W25N01G_STATUS_COLUMN_ADDRESS_SIZE, buffer, length);
quadSpiReceiveWithAddress4LINES(quadSpi, W25N01G_INSTRUCTION_FAST_READ_QUAD_OUTPUT, 8, column, W25N01G_STATUS_COLUMN_ADDRESS_SIZE, buffer, length);
}
#endif
// XXX Don't need this?
w25n01g_setTimeout(fdevice, W25N01G_TIMEOUT_PAGE_READ_MS);
if (!w25n01g_waitForReady(fdevice)) {
return 0;
}
// Check ECC
uint8_t statReg = w25n01g_readRegister(&fdevice->io, W25N01G_STAT_REG);
uint8_t eccCode = W25N01G_STATUS_FLAG_ECC(statReg);
switch (eccCode) {
case 0: // Successful read, no ECC correction
break;
case 1: // Successful read with ECC correction
case 2: // Uncorrectable ECC in a single page
case 3: // Uncorrectable ECC in multiple pages
w25n01g_addError(address, eccCode);
w25n01g_deviceReset(fdevice);
break;
}
return transferLength;
}
int w25n01g_readExtensionBytes(flashDevice_t *fdevice, uint32_t address, uint8_t *buffer, int length)
{
if (!w25n01g_waitForReady(fdevice)) {
return 0;
}
w25n01g_performCommandWithPageAddress(&fdevice->io, W25N01G_INSTRUCTION_PAGE_DATA_READ, W25N01G_LINEAR_TO_PAGE(address));
uint32_t column = 2048;
if (fdevice->io.mode == FLASHIO_SPI) {
extDevice_t *dev = fdevice->io.handle.dev;
uint8_t cmd[4];
cmd[0] = W25N01G_INSTRUCTION_READ_DATA;
cmd[1] = (column >> 8) & 0xff;
cmd[2] = (column >> 0) & 0xff;
cmd[3] = 0;
busSegment_t segments[] = {
{.u.buffers = {cmd, NULL}, sizeof(cmd), false, NULL},
{.u.buffers = {NULL, buffer}, length, true, NULL},
{.u.link = {NULL, NULL}, 0, true, NULL},
};
// Ensure any prior DMA has completed before continuing
spiWait(dev);
spiSequence(dev, &segments[0]);
// Block pending completion of SPI access
spiWait(dev);
}
#ifdef USE_QUADSPI
else if (fdevice->io.mode == FLASHIO_QUADSPI) {
QUADSPI_TypeDef *quadSpi = fdevice->io.handle.quadSpi;
quadSpiReceiveWithAddress1LINE(quadSpi, W25N01G_INSTRUCTION_READ_DATA, 8, column, W25N01G_STATUS_COLUMN_ADDRESS_SIZE, buffer, length);
}
#endif
w25n01g_setTimeout(fdevice, W25N01G_TIMEOUT_PAGE_READ_MS);
return length;
}
/**
* Fetch information about the detected flash chip layout.
*
* Can be called before calling w25n01g_init() (the result would have totalSize = 0).
*/
const flashGeometry_t* w25n01g_getGeometry(flashDevice_t *fdevice)
{
return &fdevice->geometry;
}
const flashVTable_t w25n01g_vTable = {
.configure = w25n01g_configure,
.isReady = w25n01g_isReady,
.waitForReady = w25n01g_waitForReady,
.eraseSector = w25n01g_eraseSector,
.eraseCompletely = w25n01g_eraseCompletely,
.pageProgramBegin = w25n01g_pageProgramBegin,
.pageProgramContinue = w25n01g_pageProgramContinue,
.pageProgramFinish = w25n01g_pageProgramFinish,
.pageProgram = w25n01g_pageProgram,
.flush = w25n01g_flush,
.readBytes = w25n01g_readBytes,
.getGeometry = w25n01g_getGeometry,
};
typedef volatile struct cb_context_s {
flashDevice_t *fdevice;
bblut_t *bblut;
int lutsize;
int lutindex;
} cb_context_t;
// Called in ISR context
// Read of BBLUT entry has just completed
busStatus_e w25n01g_readBBLUTCallback(uint32_t arg)
{
cb_context_t *cb_context = (cb_context_t *)arg;
flashDevice_t *fdevice = cb_context->fdevice;
uint8_t *rxData = fdevice->io.handle.dev->bus->curSegment->u.buffers.rxData;