Support for Flash Memory W25N01G of 128MB (#8166)
[inav.git] / src / main / drivers / flash_w25n01g.c
blobfc809aa85ef1d8e1293d2a2233845810a8df51d0
1 /*
2 * This file is part of iNav.
4 * iNav are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
8 * any later version.
10 * iNav are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
21 #include <stdbool.h>
22 #include <stdint.h>
24 #include "platform.h"
26 #ifdef USE_FLASH_W25N01G
28 #include "drivers/bus.h"
29 #include "drivers/io.h"
30 #include "drivers/time.h"
32 #include "flash_w25n01g.h"
34 // Device size parameters
35 #define W25N01G_PAGE_SIZE 2048
36 #define W25N01G_PAGES_PER_BLOCK 64
37 #define W25N01G_BLOCKS_PER_DIE 1024
39 // BB replacement area
40 #define W25N01G_BB_MARKER_BLOCKS 1
41 #define W25N01G_BB_REPLACEMENT_BLOCKS 20
42 #define W25N01G_BB_MANAGEMENT_BLOCKS (W25N01G_BB_REPLACEMENT_BLOCKS + W25N01G_BB_MARKER_BLOCKS)
44 // blocks are zero-based index
45 #define W25N01G_BB_REPLACEMENT_START_BLOCK (W25N01G_BLOCKS_PER_DIE - W25N01G_BB_REPLACEMENT_BLOCKS)
46 #define W25N01G_BB_MANAGEMENT_START_BLOCK (W25N01G_BLOCKS_PER_DIE - W25N01G_BB_MANAGEMENT_BLOCKS)
47 #define W25N01G_BB_MARKER_BLOCK (W25N01G_BB_REPLACEMENT_START_BLOCK - W25N01G_BB_MARKER_BLOCKS)
49 // Instructions
50 #define W25N01G_INSTRUCTION_RDID 0x9F
51 #define W25N01G_INSTRUCTION_DEVICE_RESET 0xFF
52 #define W25N01G_INSTRUCTION_READ_STATUS_REG 0x05
53 #define W25N01G_INSTRUCTION_READ_STATUS_ALTERNATE_REG 0x0F
54 #define W25N01G_INSTRUCTION_WRITE_STATUS_REG 0x01
55 #define W25N01G_INSTRUCTION_WRITE_STATUS_ALTERNATE_REG 0x1F
56 #define W25N01G_INSTRUCTION_WRITE_ENABLE 0x06
57 #define W25N01G_INSTRUCTION_DIE_SELECT 0xC2
58 #define W25N01G_INSTRUCTION_BLOCK_ERASE 0xD8
59 #define W25N01G_INSTRUCTION_READ_BBM_LUT 0xA5
60 #define W25N01G_INSTRUCTION_BB_MANAGEMENT 0xA1
61 #define W25N01G_INSTRUCTION_PROGRAM_DATA_LOAD 0x02
62 #define W25N01G_INSTRUCTION_RANDOM_PROGRAM_DATA_LOAD 0x84
63 #define W25N01G_INSTRUCTION_PROGRAM_EXECUTE 0x10
64 #define W25N01G_INSTRUCTION_PAGE_DATA_READ 0x13
65 #define W25N01G_INSTRUCTION_READ_DATA 0x03
66 #define W25N01G_INSTRUCTION_FAST_READ 0x1B
67 #define W25N01G_INSTRUCTION_FAST_READ_QUAD_OUTPUT 0x6B
69 // Config/status register addresses
70 #define W25N01G_PROT_REG 0xA0
71 #define W25N01G_CONF_REG 0xB0
72 #define W25N01G_STAT_REG 0xC0
74 // Bits in config/status register 1 (W25N01G_PROT_REG)
75 #define W25N01G_PROT_CLEAR (0)
76 #define W25N01G_PROT_SRP1_ENABLE (1 << 0)
77 #define W25N01G_PROT_WP_E_ENABLE (1 << 1)
78 #define W25N01G_PROT_TB_ENABLE (1 << 2)
79 #define W25N01G_PROT_PB0_ENABLE (1 << 3)
80 #define W25N01G_PROT_PB1_ENABLE (1 << 4)
81 #define W25N01G_PROT_PB2_ENABLE (1 << 5)
82 #define W25N01G_PROT_PB3_ENABLE (1 << 6)
83 #define W25N01G_PROT_SRP2_ENABLE (1 << 7)
85 // Bits in config/status register 2 (W25N01G_CONF_REG)
86 #define W25N01G_CONFIG_ECC_ENABLE (1 << 4)
87 #define W25N01G_CONFIG_BUFFER_READ_MODE (1 << 3)
89 // Bits in config/status register 3 (W25N01G_STATREG)
90 #define W25N01G_STATUS_BBM_LUT_FULL (1 << 6)
91 #define W25N01G_STATUS_FLAG_ECC_POS 4
92 #define W25N01G_STATUS_FLAG_ECC_MASK ((1 << 5)|(1 << 4))
93 #define W25N01G_STATUS_FLAG_ECC(status) (((status) & W25N01G_STATUS_FLAG_ECC_MASK) >> 4)
94 #define W25N01G_STATUS_PROGRAM_FAIL (1 << 3)
95 #define W25N01G_STATUS_ERASE_FAIL (1 << 2)
96 #define W25N01G_STATUS_FLAG_WRITE_ENABLED (1 << 1)
97 #define W25N01G_STATUS_FLAG_BUSY (1 << 0)
98 #define W25N01G_BBLUT_TABLE_ENTRY_COUNT 20
99 #define W25N01G_BBLUT_TABLE_ENTRY_SIZE 4 // in bytes
101 // Bits in LBA for BB LUT
102 #define W25N01G_BBLUT_STATUS_ENABLED (1 << 15)
103 #define W25N01G_BBLUT_STATUS_INVALID (1 << 14)
104 #define W25N01G_BBLUT_STATUS_MASK (W25N01G_BBLUT_STATUS_ENABLED | W25N01G_BBLUT_STATUS_INVALID)
106 // Some useful defs and macros
107 #define W25N01G_LINEAR_TO_COLUMN(laddr) ((laddr) % W25N01G_PAGE_SIZE)
108 #define W25N01G_LINEAR_TO_PAGE(laddr) ((laddr) / W25N01G_PAGE_SIZE)
109 #define W25N01G_LINEAR_TO_BLOCK(laddr) (W25N01G_LINEAR_TO_PAGE(laddr) / W25N01G_PAGES_PER_BLOCK)
110 #define W25N01G_BLOCK_TO_PAGE(block) ((block) * W25N01G_PAGES_PER_BLOCK)
111 #define W25N01G_BLOCK_TO_LINEAR(block) (W25N01G_BLOCK_TO_PAGE(block) * W25N01G_PAGE_SIZE)
113 // IMPORTANT: Timeout values are currently required to be set to the highest value required by any of the supported flash chips by this driver
114 // The timeout values (2ms minimum to avoid 1 tick advance in consecutive calls to millis).
115 #define W25N01G_TIMEOUT_PAGE_READ_MS 2 // tREmax = 60us (ECC enabled)
116 #define W25N01G_TIMEOUT_PAGE_PROGRAM_MS 2 // tPPmax = 700us
117 #define W25N01G_TIMEOUT_BLOCK_ERASE_MS 15 // tBEmax = 10ms
118 #define W25N01G_TIMEOUT_RESET_MS 500 // tRSTmax = 500ms
120 // Sizes (in bits)
121 #define W28N01G_STATUS_REGISTER_SIZE 8
122 #define W28N01G_STATUS_PAGE_ADDRESS_SIZE 16
123 #define W28N01G_STATUS_COLUMN_ADDRESS_SIZE 16
125 // JEDEC ID
126 #define JEDEC_ID_WINBOND_W25N01GV 0xEFAA21
128 static busDevice_t *busDev = NULL;
129 static flashGeometry_t geometry;
132 * Whether we've performed an action that could have made the device busy for writes.
134 * This allows us to avoid polling for writable status when it is definitely ready already.
136 static bool couldBeBusy = false;
138 static timeMs_t timeoutAt = 0;
140 static bool w25n01g_waitForReadyInternal(void);
142 static void w25n01g_setTimeout(timeMs_t timeoutMillis)
144 timeMs_t now = millis();
145 timeoutAt = now + timeoutMillis;
146 couldBeBusy = true;
150 * Send the given command byte to the device.
152 static void w25n01g_performOneByteCommand(uint8_t command)
154 busTransfer(busDev, NULL, &command, 1);
157 static void w25n01g_performCommandWithPageAddress(uint8_t command, uint32_t pageAddress)
159 uint8_t cmd[4] = { command, 0, (pageAddress >> 8) & 0xff, (pageAddress >> 0) & 0xff};
160 busTransfer(busDev, NULL, cmd, sizeof(cmd));
163 static uint8_t w25n01g_readRegister(uint8_t reg)
165 uint8_t command[3] = { W25N01G_INSTRUCTION_READ_STATUS_REG, reg, 0 };
166 uint8_t in[3];
168 busTransfer(busDev, in, command, sizeof(command));
170 return in[2];
173 static void w25n01g_writeRegister(uint8_t reg, uint8_t data)
175 uint8_t cmd[3] = { W25N01G_INSTRUCTION_WRITE_STATUS_REG, reg, data };
176 busTransfer(busDev, NULL, cmd, sizeof(cmd));
179 static void w25n01g_deviceReset(void)
181 w25n01g_performOneByteCommand(W25N01G_INSTRUCTION_DEVICE_RESET);
182 w25n01g_setTimeout(W25N01G_TIMEOUT_RESET_MS);
183 w25n01g_waitForReadyInternal();
184 // Protection for upper 1/32 (BP[3:0] = 0101, TB=0), WP-E on; to protect bad block replacement area
185 // DON'T DO THIS. This will prevent writes through the bblut as well.
186 // w25n01g_writeRegister(busdev, W25N01G_PROT_REG, W25N01G_PROT_PB0_ENABLE|W25N01G_PROT_PB2_ENABLE|W25N01G_PROT_WP_E_ENABLE);
187 // No protection, WP-E off, WP-E prevents use of IO2
188 w25n01g_writeRegister(W25N01G_PROT_REG, W25N01G_PROT_CLEAR);
189 // Buffered read mode (BUF = 1), ECC enabled (ECC = 1)
190 w25n01g_writeRegister(W25N01G_CONF_REG, W25N01G_CONFIG_ECC_ENABLE | W25N01G_CONFIG_BUFFER_READ_MODE);
193 bool w25n01g_isReady(void)
195 uint8_t status = w25n01g_readRegister(W25N01G_STAT_REG);
197 // If couldBeBusy is false, don't bother to poll the flash chip for its status
198 couldBeBusy = couldBeBusy && ((status & W25N01G_STATUS_FLAG_BUSY) != 0);
200 return !couldBeBusy;
203 static bool w25n01g_waitForReadyInternal(void)
205 while (!w25n01g_isReady()) {
206 timeMs_t now = millis();
207 if (cmp32(now, timeoutAt) >= 0) {
208 return false;
211 timeoutAt = 0;
212 return true;
215 bool w25n01g_waitForReady(timeMs_t timeoutMillis)
217 w25n01g_setTimeout(timeoutMillis);
218 return w25n01g_waitForReadyInternal();
222 * The flash requires this write enable command to be sent before commands that would cause
223 * a write like program and erase.
225 static void w25n01g_writeEnable(void)
227 w25n01g_performOneByteCommand(W25N01G_INSTRUCTION_WRITE_ENABLE);
229 // Assume that we're about to do some writing, so the device is just about to become busy
230 couldBeBusy = true;
233 bool w25n01g_detect(uint32_t chipID)
235 switch (chipID) {
236 case JEDEC_ID_WINBOND_W25N01GV:
237 geometry.sectors = 1024; // Blocks
238 geometry.pagesPerSector = 64; // Pages/Blocks
239 geometry.pageSize = 2048;
240 break;
242 default:
243 // Unsupported chip
244 geometry.sectors = 0;
245 geometry.pagesPerSector = 0;
246 geometry.sectorSize = 0;
247 geometry.totalSize = 0;
248 return false;
251 geometry.flashType = FLASH_TYPE_NAND;
252 geometry.sectorSize = geometry.pagesPerSector * geometry.pageSize;
253 geometry.totalSize = geometry.sectorSize * geometry.sectors;
255 /*flashPartitionSet(FLASH_PARTITION_TYPE_BADBLOCK_MANAGEMENT,
256 W25N01G_BB_MANAGEMENT_START_BLOCK,
257 W25N01G_BB_MANAGEMENT_START_BLOCK + W25N01G_BB_MANAGEMENT_BLOCKS - 1);*/
259 couldBeBusy = true; // Just for luck we'll assume the chip could be busy even though it isn't specced to be
261 w25n01g_deviceReset();
263 // Upper 4MB (32 blocks * 128KB/block) will be used for bad block replacement area.
264 // Blocks in this area are only written through bad block LUT,
265 // and factory written bad block marker in unused blocks are retained.
266 // When a replacement block is required,
267 // (1) "Read BB LUT" command is used to obtain the last block mapped,
268 // (2) blocks after the last block is scanned for a good block,
269 // (3) the first good block is used for replacement, and the BB LUT is updated.
270 // There are only 20 BB LUT entries, and there are 32 replacement blocks.
271 // There will be a least chance of running out of replacement blocks.
272 // If it ever run out, the device becomes unusable.
274 return true;
278 * Erase a sector full of bytes to all 1's at the given byte offset in the flash chip.
280 void w25n01g_eraseSector(uint32_t address)
282 w25n01g_waitForReadyInternal();
283 w25n01g_writeEnable();
284 w25n01g_performCommandWithPageAddress(W25N01G_INSTRUCTION_BLOCK_ERASE, W25N01G_LINEAR_TO_PAGE(address));
285 w25n01g_setTimeout(W25N01G_TIMEOUT_BLOCK_ERASE_MS);
288 // W25N01G does not support full chip erase.
289 // Call eraseSector repeatedly.
290 void w25n01g_eraseCompletely(void)
292 for (uint32_t block = 0; block < geometry.sectors; block++) {
293 w25n01g_eraseSector(W25N01G_BLOCK_TO_LINEAR(block));
297 static void w25n01g_programDataLoad(uint16_t columnAddress, const uint8_t *data, int length)
299 w25n01g_waitForReadyInternal();
301 uint8_t cmd[3] = {W25N01G_INSTRUCTION_PROGRAM_DATA_LOAD, columnAddress >> 8, columnAddress & 0xff};
303 busTransferDescriptor_t transferDescr[] = {{.length = sizeof(cmd), .rxBuf = NULL, .txBuf = cmd}, {.length = length, .rxBuf = NULL, .txBuf = (uint8_t *)data}};
304 busTransferMultiple(busDev, transferDescr, sizeof(transferDescr) / sizeof(transferDescr[0]));
306 w25n01g_setTimeout(W25N01G_TIMEOUT_PAGE_PROGRAM_MS);
309 static void w25n01g_randomProgramDataLoad(uint16_t columnAddress, const uint8_t *data, int length)
311 w25n01g_waitForReadyInternal();
313 uint8_t cmd[3] = {W25N01G_INSTRUCTION_RANDOM_PROGRAM_DATA_LOAD, columnAddress >> 8, columnAddress & 0xff};
315 busTransferDescriptor_t transferDescr[] = {{.length = sizeof(cmd), .rxBuf = NULL, .txBuf = cmd}, {.length = length, .rxBuf = NULL, .txBuf = (uint8_t *)data}};
316 busTransferMultiple(busDev, transferDescr, sizeof(transferDescr) / sizeof(transferDescr[0]));
318 w25n01g_setTimeout(W25N01G_TIMEOUT_PAGE_PROGRAM_MS);
321 static void w25n01g_programExecute(uint32_t pageAddress)
323 w25n01g_waitForReadyInternal();
324 w25n01g_performCommandWithPageAddress(W25N01G_INSTRUCTION_PROGRAM_EXECUTE, pageAddress);
325 w25n01g_setTimeout(W25N01G_TIMEOUT_PAGE_PROGRAM_MS);
328 // Writes are done in three steps:
329 // (1) Load internal data buffer with data to write
330 // - We use "Random Load Program Data", as "Load Program Data" resets unused data bytes in the buffer to 0xff.
331 // - Each "Random Load Program Data" instruction must be accompanied by at least a single data.
332 // - Each "Random Load Program Data" instruction terminates at the rising of CS.
333 // (2) Enable write
334 // (3) Issue "Execute Program"
337 flashfs page program behavior
338 - Single program never crosses page boundary.
339 - Except for this characteristic, it program arbitral size.
340 - Write address is, naturally, not a page boundary.
341 To cope with this behavior.
342 pageProgramBegin:
343 If buffer is dirty and programLoadAddress != address, then the last page is a partial write;
344 issue PAGE_PROGRAM_EXECUTE to flash buffer contents, clear dirty and record the address as programLoadAddress and programStartAddress.
345 Else do nothing.
346 pageProgramContinue:
347 Mark buffer as dirty.
348 If programLoadAddress is on page boundary, then issue PROGRAM_LOAD_DATA, else issue RANDOM_PROGRAM_LOAD_DATA.
349 Update programLoadAddress.
350 Optionally observe the programLoadAddress, and if it's on page boundary, issue PAGE_PROGRAM_EXECUTE.
351 pageProgramFinish:
352 Observe programLoadAddress. If it's on page boundary, issue PAGE_PROGRAM_EXECUTE and clear dirty, else just return.
353 If pageProgramContinue observes the page boundary, then do nothing(?).
355 bool bufferDirty = false;
356 bool isProgramming = false;
357 static uint32_t programStartAddress;
358 static uint32_t programLoadAddress;
359 static uint32_t currentPage = UINT32_MAX;
361 void w25n01g_pageProgramBegin(uint32_t address)
363 if (bufferDirty) {
364 if (address != programLoadAddress) {
365 w25n01g_waitForReadyInternal();
366 isProgramming = false;
367 w25n01g_writeEnable();
368 w25n01g_programExecute(W25N01G_LINEAR_TO_PAGE(programStartAddress));
369 bufferDirty = false;
370 isProgramming = true;
372 } else {
373 programStartAddress = programLoadAddress = address;
377 void w25n01g_pageProgramContinue(const uint8_t *data, int length)
379 // Check for page boundary overrun
380 w25n01g_waitForReadyInternal();
381 w25n01g_writeEnable();
382 isProgramming = false;
383 if (!bufferDirty) {
384 w25n01g_programDataLoad(W25N01G_LINEAR_TO_COLUMN(programLoadAddress), (uint8_t *)data, length);
385 } else {
386 w25n01g_randomProgramDataLoad(W25N01G_LINEAR_TO_COLUMN(programLoadAddress), (uint8_t *)data, length);
388 // XXX Test if write enable is reset after each data loading.
389 bufferDirty = true;
390 programLoadAddress += length;
393 void w25n01g_pageProgramFinish(void)
395 if (bufferDirty && W25N01G_LINEAR_TO_COLUMN(programLoadAddress) == 0) {
396 currentPage = W25N01G_LINEAR_TO_PAGE(programStartAddress); // reset page to the page being written
397 w25n01g_programExecute(W25N01G_LINEAR_TO_PAGE(programStartAddress));
398 bufferDirty = false;
399 isProgramming = true;
400 programStartAddress = programLoadAddress;
405 * Write bytes to a flash page. Address must not cross a page boundary.
407 * 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.
409 * Length must be smaller than the page size.
411 * This will wait for the flash to become ready before writing begins.
413 * Datasheet indicates typical programming time is 0.8ms for 256 bytes, 0.2ms for 64 bytes, 0.05ms for 16 bytes.
414 * (Although the maximum possible write time is noted as 5ms).
416 * If you want to write multiple buffers (whose sum of sizes is still not more than the page size) then you can
417 * break this operation up into one beginProgram call, one or more continueProgram calls, and one finishProgram call.
419 uint32_t w25n01g_pageProgram(uint32_t address, const uint8_t *data, int length)
421 w25n01g_pageProgramBegin(address);
422 w25n01g_pageProgramContinue((uint8_t *)data, length);
423 w25n01g_pageProgramFinish();
425 return address + length;
428 void w25n01g_flush(void)
430 if (bufferDirty) {
431 currentPage = W25N01G_LINEAR_TO_PAGE(programStartAddress); // reset page to the page being written
432 w25n01g_programExecute(W25N01G_LINEAR_TO_PAGE(programStartAddress));
433 bufferDirty = false;
434 isProgramming = true;
435 } else {
436 isProgramming = false;
440 void w25n01g_addError(uint32_t address, uint8_t code)
442 UNUSED(address);
443 UNUSED(code);
447 * Read `length` bytes into the provided `buffer` from the flash starting from the given `address` (which need not lie
448 * on a page boundary).
450 * Waits up to W25N01G_TIMEOUT_PAGE_READ_MS milliseconds for the flash to become ready before reading.
452 * The number of bytes actually read is returned, which can be zero if an error or timeout occurred.
454 // Continuous read mode (BUF = 0):
455 // (1) "Page Data Read" command is executed for the page pointed by address
456 // (2) "Read Data" command is executed for bytes not requested and data are discarded
457 // (3) "Read Data" command is executed and data are stored directly into caller's buffer
459 // Buffered read mode (BUF = 1), non-read ahead
460 // (1) If currentBufferPage != requested page, then issue PAGE_DATA_READ on requested page.
461 // (2) Compute transferLength as smaller of remaining length and requested length.
462 // (3) Issue READ_DATA on column address.
463 // (4) Return transferLength.
464 int w25n01g_readBytes(uint32_t address, uint8_t *buffer, int length)
466 uint32_t targetPage = W25N01G_LINEAR_TO_PAGE(address);
468 if (currentPage != targetPage) {
469 if (!w25n01g_waitForReadyInternal()) {
470 return 0;
472 currentPage = UINT32_MAX;
473 w25n01g_performCommandWithPageAddress(W25N01G_INSTRUCTION_PAGE_DATA_READ, targetPage);
474 if (!w25n01g_waitForReady(W25N01G_TIMEOUT_PAGE_READ_MS)) {
475 return 0;
477 currentPage = targetPage;
480 uint16_t column = W25N01G_LINEAR_TO_COLUMN(address);
481 uint16_t transferLength;
483 if (length > W25N01G_PAGE_SIZE - column) {
484 transferLength = W25N01G_PAGE_SIZE - column;
485 } else {
486 transferLength = length;
489 const uint8_t cmd[4] = {W25N01G_INSTRUCTION_READ_DATA, (column >> 8) & 0xff, (column >> 0) & 0xff, 0};
491 busTransferDescriptor_t readDescr[] = {{.length = sizeof(cmd), .rxBuf = NULL, .txBuf = cmd}, {.length = transferLength, .rxBuf = buffer, .txBuf = NULL}};
492 busTransferMultiple(busDev, readDescr, sizeof(readDescr) / sizeof(readDescr[0]));
494 if (!w25n01g_waitForReady(W25N01G_TIMEOUT_PAGE_READ_MS)) {
495 return 0;
498 // Check ECC
499 uint8_t statReg = w25n01g_readRegister(W25N01G_STAT_REG);
500 uint8_t eccCode = W25N01G_STATUS_FLAG_ECC(statReg);
501 switch (eccCode) {
502 case 0: // Successful read, no ECC correction
503 break;
505 case 1: // Successful read with ECC correction
506 case 2: // Uncorrectable ECC in a single page
507 case 3: // Uncorrectable ECC in multiple pages
508 w25n01g_addError(address, eccCode);
509 w25n01g_deviceReset();
510 break;
513 return transferLength;
516 int w25n01g_readExtensionBytes(uint32_t address, uint8_t *buffer, int length)
518 if (!w25n01g_waitForReadyInternal()) {
519 return 0;
522 w25n01g_performCommandWithPageAddress(W25N01G_INSTRUCTION_PAGE_DATA_READ, W25N01G_LINEAR_TO_PAGE(address));
524 uint32_t column = 2048;
526 uint8_t cmd[4];
527 cmd[0] = W25N01G_INSTRUCTION_READ_DATA;
528 cmd[1] = (column >> 8) & 0xff;
529 cmd[2] = (column >> 0) & 0xff;
530 cmd[3] = 0;
532 busTransferDescriptor_t readDescr[] = {{.length = sizeof(cmd), .rxBuf = NULL, .txBuf = cmd}, {.length = length, .rxBuf = buffer, .txBuf = NULL}};
533 busTransferMultiple(busDev, readDescr, sizeof(readDescr) / sizeof(readDescr[0]));
535 w25n01g_setTimeout(W25N01G_TIMEOUT_PAGE_READ_MS);
537 return length;
541 * Fetch information about the detected flash chip layout.
543 * Can be called before calling w25n01g_init() (the result would have totalSize = 0).
545 const flashGeometry_t* w25n01g_getGeometry(void)
547 return &geometry;
550 bool w25n01g_init(int flashNumToUse)
552 busDev = busDeviceInit(BUSTYPE_SPI, DEVHW_W25N01G, flashNumToUse, OWNER_FLASH);
553 if (busDev == NULL) {
554 return false;
557 uint8_t in[4] = { 0 };
558 uint32_t chipID;
560 delay(50); // short delay required after initialisation of SPI device instance.
562 busReadBuf(busDev, W25N01G_INSTRUCTION_RDID, in, sizeof(in));
564 chipID = (in[1] << 16) | (in[2] << 8) | (in[3]);
566 return w25n01g_detect(chipID);
569 #endif