diff --git a/make/source.mk b/make/source.mk
index f3dec33c2..a129fb773 100644
--- a/make/source.mk
+++ b/make/source.mk
@@ -372,6 +372,7 @@ endif
ifneq ($(filter ONBOARDFLASH,$(FEATURES)),)
SRC += \
+ drivers/flash.c \
drivers/flash_m25p16.c \
io/flashfs.c \
pg/flash.c \
diff --git a/src/main/blackbox/blackbox_io.c b/src/main/blackbox/blackbox_io.c
index eac6e95c4..c9d4ae12f 100644
--- a/src/main/blackbox/blackbox_io.c
+++ b/src/main/blackbox/blackbox_io.c
@@ -298,12 +298,13 @@ bool isBlackboxErased(void)
#endif
/**
- * Close the Blackbox logging device immediately without attempting to flush any remaining data.
+ * Close the Blackbox logging device.
*/
void blackboxDeviceClose(void)
{
switch (blackboxConfig()->device) {
case BLACKBOX_DEVICE_SERIAL:
+ // Can immediately close without attempting to flush any remaining data.
// Since the serial port could be shared with other processes, we have to give it back here
closeSerialPort(blackboxPort);
blackboxPort = NULL;
@@ -316,6 +317,12 @@ void blackboxDeviceClose(void)
mspSerialAllocatePorts();
}
break;
+#ifdef USE_FLASHFS
+ case BLACKBOX_DEVICE_FLASH:
+ // Some flash device, e.g., NAND devices, require explicit close to flush internally buffered data.
+ flashfsClose();
+ break;
+#endif
default:
;
}
diff --git a/src/main/drivers/flash.c b/src/main/drivers/flash.c
new file mode 100644
index 000000000..cc09af55a
--- /dev/null
+++ b/src/main/drivers/flash.c
@@ -0,0 +1,153 @@
+/*
+ * This file is part of Betaflight/Cleanflight.
+ *
+ * Betaflight/Cleanflight is free software: you can redistribute it
+ * and/or modify it 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.
+ *
+ * Betaflight/Cleanflight is distributed in the hope that it 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 Betaflight/Cleanflight. If not, see .
+ */
+
+#include
+#include
+
+#include "platform.h"
+
+#include "build/debug.h"
+
+#ifdef USE_FLASH
+
+#include "flash.h"
+#include "flash_impl.h"
+#include "flash_m25p16.h"
+#include "drivers/bus_spi.h"
+#include "drivers/io.h"
+#include "drivers/time.h"
+
+static busDevice_t busInstance;
+static busDevice_t *busdev;
+
+static flashDevice_t flashDevice;
+
+// Read chip identification and send it to device detect
+
+bool flashInit(const flashConfig_t *flashConfig)
+{
+ busdev = &busInstance;
+ busdev->bustype = BUSTYPE_SPI;
+ spiBusSetInstance(busdev, spiInstanceByDevice(SPI_CFG_TO_DEV(flashConfig->spiDevice)));
+ if (flashConfig->csTag) {
+ busdev->busdev_u.spi.csnPin = IOGetByTag(flashConfig->csTag);
+ } else {
+ return false;
+ }
+
+ IOInit(busdev->busdev_u.spi.csnPin, OWNER_FLASH_CS, 0);
+ IOConfigGPIO(busdev->busdev_u.spi.csnPin, SPI_IO_CS_CFG);
+ IOHi(busdev->busdev_u.spi.csnPin);
+
+#ifndef M25P16_SPI_SHARED
+ //Maximum speed for standard READ command is 20mHz, other commands tolerate 25mHz
+ //spiSetDivisor(busdev->busdev_u.spi.instance, SPI_CLOCK_FAST);
+ spiSetDivisor(busdev->busdev_u.spi.instance, SPI_CLOCK_STANDARD*2);
+#endif
+
+ flashDevice.busdev = busdev;
+
+ const uint8_t out[] = { SPIFLASH_INSTRUCTION_RDID, 0, 0, 0 };
+
+ delay(50); // short delay required after initialisation of SPI device instance.
+
+ /* Just in case transfer fails and writes nothing, so we don't try to verify the ID against random garbage
+ * from the stack:
+ */
+ uint8_t in[4];
+ in[1] = 0;
+
+ // Clearing the CS bit terminates the command early so we don't have to read the chip UID:
+ spiBusTransfer(busdev, out, in, sizeof(out));
+
+ // Manufacturer, memory type, and capacity
+ uint32_t chipID = (in[1] << 16) | (in[2] << 8) | (in[3]);
+
+#ifdef USE_FLASH_M25P16
+ if (m25p16_detect(&flashDevice, chipID)) {
+ return true;
+ }
+#endif
+
+ spiPreInitCs(flashConfig->csTag);
+
+ return false;
+}
+
+bool flashIsReady(void)
+{
+ return flashDevice.vTable->isReady(&flashDevice);
+}
+
+bool flashWaitForReady(uint32_t timeoutMillis)
+{
+ return flashDevice.vTable->waitForReady(&flashDevice, timeoutMillis);
+}
+
+void flashEraseSector(uint32_t address)
+{
+ flashDevice.vTable->eraseSector(&flashDevice, address);
+}
+
+void flashEraseCompletely(void)
+{
+ flashDevice.vTable->eraseCompletely(&flashDevice);
+}
+
+void flashPageProgramBegin(uint32_t address)
+{
+ flashDevice.vTable->pageProgramBegin(&flashDevice, address);
+}
+
+void flashPageProgramContinue(const uint8_t *data, int length)
+{
+ flashDevice.vTable->pageProgramContinue(&flashDevice, data, length);
+}
+
+void flashPageProgramFinish(void)
+{
+ flashDevice.vTable->pageProgramFinish(&flashDevice);
+}
+
+void flashPageProgram(uint32_t address, const uint8_t *data, int length)
+{
+ flashDevice.vTable->pageProgram(&flashDevice, address, data, length);
+}
+
+int flashReadBytes(uint32_t address, uint8_t *buffer, int length)
+{
+ return flashDevice.vTable->readBytes(&flashDevice, address, buffer, length);
+}
+
+void flashFlush(void)
+{
+ flashDevice.vTable->flush(&flashDevice);
+}
+
+static const flashGeometry_t noFlashGeometry = {
+ .totalSize = 0,
+};
+
+const flashGeometry_t *flashGetGeometry(void)
+{
+ if (flashDevice.vTable && flashDevice.vTable->getGeometry) {
+ return flashDevice.vTable->getGeometry(&flashDevice);
+ }
+
+ return &noFlashGeometry;
+}
+#endif // USE_FLASH
diff --git a/src/main/drivers/flash.h b/src/main/drivers/flash.h
index e2d20f1d4..24eb694d7 100644
--- a/src/main/drivers/flash.h
+++ b/src/main/drivers/flash.h
@@ -19,10 +19,39 @@
#include
+#include "pg/flash.h"
+#include "drivers/io.h"
+
+// Maximum page size of all supported SPI flash devices.
+// Used to detect flashfs allocation size being too small.
+#define FLASH_MAX_PAGE_SIZE 2048
+
+#define SPIFLASH_INSTRUCTION_RDID 0x9F
+
+typedef enum {
+ FLASH_TYPE_NOR = 0,
+ FLASH_TYPE_NAND
+} flashType_e;
+
typedef struct flashGeometry_s {
uint16_t sectors; // Count of the number of erasable blocks on the device
- const uint16_t pageSize; // In bytes
+ uint16_t pageSize; // In bytes
uint32_t sectorSize; // This is just pagesPerSector * pageSize
uint32_t totalSize; // This is just sectorSize * sectors
uint16_t pagesPerSector;
+ flashType_e flashType;
} flashGeometry_t;
+
+bool flashInit(const flashConfig_t *flashConfig);
+
+bool flashIsReady(void);
+bool flashWaitForReady(uint32_t timeoutMillis);
+void flashEraseSector(uint32_t address);
+void flashEraseCompletely(void);
+void flashPageProgramBegin(uint32_t address);
+void flashPageProgramContinue(const uint8_t *data, int length);
+void flashPageProgramFinish(void);
+void flashPageProgram(uint32_t address, const uint8_t *data, int length);
+int flashReadBytes(uint32_t address, uint8_t *buffer, int length);
+void flashFlush(void);
+const flashGeometry_t *flashGetGeometry(void);
diff --git a/src/main/drivers/flash_impl.h b/src/main/drivers/flash_impl.h
new file mode 100644
index 000000000..1b0644de7
--- /dev/null
+++ b/src/main/drivers/flash_impl.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of Betaflight/Cleanflight.
+ *
+ * Betaflight/Cleanflight is free software: you can redistribute it and/or modify
+ * it 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.
+ *
+ * Betaflight/Cleanflight is distributed in the hope that it 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 Cleanflight. If not, see .
+ *
+ * Author: jflyper
+ */
+
+#pragma once
+
+#include "drivers/bus.h"
+
+struct flashVTable_s;
+
+typedef struct flashDevice_s {
+ busDevice_t *busdev;
+ const struct flashVTable_s *vTable;
+ flashGeometry_t geometry;
+ uint32_t currentWriteAddress;
+ bool isLargeFlash;
+ // Whether we've performed an action that could have made the device busy
+ // for writes. This allows us to avoid polling for writable status
+ // when it is definitely ready already.
+ bool couldBeBusy;
+} flashDevice_t;
+
+typedef struct flashVTable_s {
+ bool (*isReady)(flashDevice_t *fdevice);
+ bool (*waitForReady)(flashDevice_t *fdevice, uint32_t timeoutMillis);
+ void (*eraseSector)(flashDevice_t *fdevice, uint32_t address);
+ void (*eraseCompletely)(flashDevice_t *fdevice);
+ void (*pageProgramBegin)(flashDevice_t *fdevice, uint32_t address);
+ void (*pageProgramContinue)(flashDevice_t *fdevice, const uint8_t *data, int length);
+ void (*pageProgramFinish)(flashDevice_t *fdevice);
+ void (*pageProgram)(flashDevice_t *fdevice, uint32_t address, const uint8_t *data, int length);
+ void (*flush)(flashDevice_t *fdevice);
+ int (*readBytes)(flashDevice_t *fdevice, uint32_t address, uint8_t *buffer, int length);
+ const flashGeometry_t *(*getGeometry)(flashDevice_t *fdevice);
+} flashVTable_t;
diff --git a/src/main/drivers/flash_m25p16.c b/src/main/drivers/flash_m25p16.c
index 01cca5afa..0ec7bb65b 100644
--- a/src/main/drivers/flash_m25p16.c
+++ b/src/main/drivers/flash_m25p16.c
@@ -20,10 +20,13 @@
#include "platform.h"
+#include "build/debug.h"
+
#ifdef USE_FLASH_M25P16
#include "drivers/bus_spi.h"
#include "drivers/flash.h"
+#include "drivers/flash_impl.h"
#include "drivers/io.h"
#include "drivers/time.h"
@@ -31,7 +34,7 @@
#include "flash_m25p16.h"
-#define M25P16_INSTRUCTION_RDID 0x9F
+#define M25P16_INSTRUCTION_RDID SPIFLASH_INSTRUCTION_RDID
#define M25P16_INSTRUCTION_READ_BYTES 0x03
#define M25P16_INSTRUCTION_READ_STATUS_REG 0x05
#define M25P16_INSTRUCTION_WRITE_STATUS_REG 0x01
@@ -47,6 +50,7 @@
#define W25Q256_INSTRUCTION_ENTER_4BYTE_ADDRESS_MODE 0xB7
// Format is manufacturer, memory type, then capacity
+// See also flash_m25p16.h for additional JEDEC IDs.
#define JEDEC_ID_MACRONIX_MX25L3206E 0xC22016
#define JEDEC_ID_MACRONIX_MX25L6406E 0xC22017
#define JEDEC_ID_MACRONIX_MX25L25635E 0xC22019
@@ -56,13 +60,8 @@
#define JEDEC_ID_WINBOND_W25Q16 0xEF4015
#define JEDEC_ID_WINBOND_W25Q64 0xEF4017
#define JEDEC_ID_WINBOND_W25Q128 0xEF4018
-#define JEDEC_ID_WINBOND_W25Q256 0xEF4019
#define JEDEC_ID_CYPRESS_S25FL128L 0x016018
-static busDevice_t busInstance;
-static busDevice_t *bus;
-static bool isLargeFlash = false;
-
// The timeout we expect between being able to issue page program instructions
#define DEFAULT_TIMEOUT_MILLIS 6
@@ -70,15 +69,11 @@ static bool isLargeFlash = false;
#define SECTOR_ERASE_TIMEOUT_MILLIS 5000
#define BULK_ERASE_TIMEOUT_MILLIS 21000
-static flashGeometry_t geometry = {.pageSize = M25P16_PAGESIZE};
+#define M25P16_PAGESIZE 256
-/*
- * Whether we've performed an action that could have made the device busy for writes.
- *
- * This allows us to avoid polling for writable status when it is definitely ready already.
- */
-static bool couldBeBusy = false;
+STATIC_ASSERT(M25P16_PAGESIZE < FLASH_MAX_PAGE_SIZE, M25P16_PAGESIZE_too_small);
+const flashVTable_t m25p16_vTable;
static void m25p16_disable(busDevice_t *bus)
{
@@ -115,12 +110,12 @@ static void m25p16_performOneByteCommand(busDevice_t *bus, uint8_t command)
* The flash requires this write enable command to be sent before commands that would cause
* a write like program and erase.
*/
-static void m25p16_writeEnable(busDevice_t *bus)
+static void m25p16_writeEnable(flashDevice_t *fdevice)
{
- m25p16_performOneByteCommand(bus, M25P16_INSTRUCTION_WRITE_ENABLE);
+ m25p16_performOneByteCommand(fdevice->busdev, M25P16_INSTRUCTION_WRITE_ENABLE);
// Assume that we're about to do some writing, so the device is just about to become busy
- couldBeBusy = true;
+ fdevice->couldBeBusy = true;
}
static uint8_t m25p16_readStatus(busDevice_t *bus)
@@ -133,18 +128,18 @@ static uint8_t m25p16_readStatus(busDevice_t *bus)
return in[1];
}
-bool m25p16_isReady(void)
+static bool m25p16_isReady(flashDevice_t *fdevice)
{
// If couldBeBusy is false, don't bother to poll the flash chip for its status
- couldBeBusy = couldBeBusy && ((m25p16_readStatus(bus) & M25P16_STATUS_FLAG_WRITE_IN_PROGRESS) != 0);
+ fdevice->couldBeBusy = fdevice->couldBeBusy && ((m25p16_readStatus(fdevice->busdev) & M25P16_STATUS_FLAG_WRITE_IN_PROGRESS) != 0);
- return !couldBeBusy;
+ return !fdevice->couldBeBusy;
}
-bool m25p16_waitForReady(uint32_t timeoutMillis)
+static bool m25p16_waitForReady(flashDevice_t *fdevice, uint32_t timeoutMillis)
{
uint32_t time = millis();
- while (!m25p16_isReady()) {
+ while (!m25p16_isReady(fdevice)) {
if (millis() - time > timeoutMillis) {
return false;
}
@@ -158,115 +153,61 @@ bool m25p16_waitForReady(uint32_t timeoutMillis)
*
* Returns true if we get valid ident, false if something bad happened like there is no M25P16.
*/
-static bool m25p16_readIdentification(void)
+
+bool m25p16_detect(flashDevice_t *fdevice, uint32_t chipID)
{
- const uint8_t out[] = { M25P16_INSTRUCTION_RDID, 0, 0, 0 };
-
- delay(50); // short delay required after initialisation of SPI device instance.
-
- /* Just in case transfer fails and writes nothing, so we don't try to verify the ID against random garbage
- * from the stack:
- */
- uint8_t in[4];
- in[1] = 0;
-
- // Clearing the CS bit terminates the command early so we don't have to read the chip UID:
- m25p16_transfer(bus, out, in, sizeof(out));
-
- // Manufacturer, memory type, and capacity
- const uint32_t chipID = (in[1] << 16) | (in[2] << 8) | (in[3]);
-
- // All supported chips use the same pagesize of 256 bytes
-
switch (chipID) {
case JEDEC_ID_WINBOND_W25Q16:
case JEDEC_ID_MICRON_M25P16:
- geometry.sectors = 32;
- geometry.pagesPerSector = 256;
+ fdevice->geometry.sectors = 32;
+ fdevice->geometry.pagesPerSector = 256;
break;
case JEDEC_ID_MACRONIX_MX25L3206E:
- geometry.sectors = 64;
- geometry.pagesPerSector = 256;
+ fdevice->geometry.sectors = 64;
+ fdevice->geometry.pagesPerSector = 256;
break;
case JEDEC_ID_MICRON_N25Q064:
case JEDEC_ID_WINBOND_W25Q64:
case JEDEC_ID_MACRONIX_MX25L6406E:
- geometry.sectors = 128;
- geometry.pagesPerSector = 256;
+ fdevice->geometry.sectors = 128;
+ fdevice->geometry.pagesPerSector = 256;
break;
case JEDEC_ID_MICRON_N25Q128:
case JEDEC_ID_WINBOND_W25Q128:
case JEDEC_ID_CYPRESS_S25FL128L:
- geometry.sectors = 256;
- geometry.pagesPerSector = 256;
+ fdevice->geometry.sectors = 256;
+ fdevice->geometry.pagesPerSector = 256;
break;
case JEDEC_ID_WINBOND_W25Q256:
case JEDEC_ID_MACRONIX_MX25L25635E:
- geometry.sectors = 512;
- geometry.pagesPerSector = 256;
+ fdevice->geometry.sectors = 512;
+ fdevice->geometry.pagesPerSector = 256;
break;
default:
// Unsupported chip or not an SPI NOR flash
- geometry.sectors = 0;
- geometry.pagesPerSector = 0;
-
- geometry.sectorSize = 0;
- geometry.totalSize = 0;
+ fdevice->geometry.sectors = 0;
+ fdevice->geometry.pagesPerSector = 0;
+ fdevice->geometry.sectorSize = 0;
+ fdevice->geometry.totalSize = 0;
return false;
}
- geometry.sectorSize = geometry.pagesPerSector * geometry.pageSize;
- geometry.totalSize = geometry.sectorSize * geometry.sectors;
+ fdevice->geometry.flashType = FLASH_TYPE_NOR;
+ fdevice->geometry.pageSize = M25P16_PAGESIZE;
+ fdevice->geometry.sectorSize = fdevice->geometry.pagesPerSector * fdevice->geometry.pageSize;
+ fdevice->geometry.totalSize = fdevice->geometry.sectorSize * fdevice->geometry.sectors;
- if (geometry.totalSize > 16 * 1024 * 1024) {
- isLargeFlash = true;
- m25p16_performOneByteCommand(bus, W25Q256_INSTRUCTION_ENTER_4BYTE_ADDRESS_MODE);
+ if (fdevice->geometry.totalSize > 16 * 1024 * 1024) {
+ fdevice->isLargeFlash = true;
+ m25p16_performOneByteCommand(fdevice->busdev, W25Q256_INSTRUCTION_ENTER_4BYTE_ADDRESS_MODE);
}
- couldBeBusy = true; // Just for luck we'll assume the chip could be busy even though it isn't specced to be
-
+ fdevice->couldBeBusy = true; // Just for luck we'll assume the chip could be busy even though it isn't specced to be
+ fdevice->vTable = &m25p16_vTable;
return true;
}
-/**
- * Initialize the driver, must be called before any other routines.
- *
- * Attempts to detect a connected m25p16. If found, true is returned and device capacity can be fetched with
- * m25p16_getGeometry().
- */
-
-bool m25p16_init(const flashConfig_t *flashConfig)
-{
- /*
- if we have already detected a flash device we can simply exit
- */
- if (geometry.sectors) {
- return true;
- }
-
- bus = &busInstance;
- bus->bustype = BUSTYPE_SPI;
- spiBusSetInstance(bus, spiInstanceByDevice(SPI_CFG_TO_DEV(flashConfig->spiDevice)));
- if (flashConfig->csTag) {
- bus->busdev_u.spi.csnPin = IOGetByTag(flashConfig->csTag);
- } else {
- return false;
- }
-
- IOInit(bus->busdev_u.spi.csnPin, OWNER_FLASH_CS, 0);
- IOConfigGPIO(bus->busdev_u.spi.csnPin, SPI_IO_CS_CFG);
-
- m25p16_disable(bus);
-
-#ifndef M25P16_SPI_SHARED
- //Maximum speed for standard READ command is 20mHz, other commands tolerate 25mHz
- spiSetDivisor(bus->busdev_u.spi.instance, SPI_CLOCK_FAST);
-#endif
-
- return m25p16_readIdentification();
-}
-
-void m25p16_setCommandAddress(uint8_t *buf, uint32_t address, bool useLongAddress)
+static void m25p16_setCommandAddress(uint8_t *buf, uint32_t address, bool useLongAddress)
{
if (useLongAddress) {
*buf++ = (address >> 24) & 0xff;
@@ -279,58 +220,59 @@ void m25p16_setCommandAddress(uint8_t *buf, uint32_t address, bool useLongAddres
/**
* Erase a sector full of bytes to all 1's at the given byte offset in the flash chip.
*/
-void m25p16_eraseSector(uint32_t address)
+static void m25p16_eraseSector(flashDevice_t *fdevice, uint32_t address)
{
uint8_t out[5] = { M25P16_INSTRUCTION_SECTOR_ERASE };
- m25p16_setCommandAddress(&out[1], address, isLargeFlash);
+ m25p16_setCommandAddress(&out[1], address, fdevice->isLargeFlash);
- m25p16_waitForReady(SECTOR_ERASE_TIMEOUT_MILLIS);
+ m25p16_waitForReady(fdevice, SECTOR_ERASE_TIMEOUT_MILLIS);
- m25p16_writeEnable(bus);
+ m25p16_writeEnable(fdevice);
- m25p16_transfer(bus, out, NULL, sizeof(out));
+ m25p16_transfer(fdevice->busdev, out, NULL, sizeof(out));
}
-void m25p16_eraseCompletely(void)
+static void m25p16_eraseCompletely(flashDevice_t *fdevice)
{
- m25p16_waitForReady(BULK_ERASE_TIMEOUT_MILLIS);
+ m25p16_waitForReady(fdevice, BULK_ERASE_TIMEOUT_MILLIS);
- m25p16_writeEnable(bus);
+ m25p16_writeEnable(fdevice);
- m25p16_performOneByteCommand(bus, M25P16_INSTRUCTION_BULK_ERASE);
+ m25p16_performOneByteCommand(fdevice->busdev, M25P16_INSTRUCTION_BULK_ERASE);
}
-static uint32_t currentWriteAddress;
-
-void m25p16_pageProgramBegin(uint32_t address)
+static void m25p16_pageProgramBegin(flashDevice_t *fdevice, uint32_t address)
{
- currentWriteAddress = address;
+ UNUSED(fdevice);
+
+ fdevice->currentWriteAddress = address;
}
-void m25p16_pageProgramContinue(const uint8_t *data, int length)
+static void m25p16_pageProgramContinue(flashDevice_t *fdevice, const uint8_t *data, int length)
{
uint8_t command[5] = { M25P16_INSTRUCTION_PAGE_PROGRAM };
- m25p16_setCommandAddress(&command[1], currentWriteAddress, isLargeFlash);
+ m25p16_setCommandAddress(&command[1], fdevice->currentWriteAddress, fdevice->isLargeFlash);
- m25p16_waitForReady(DEFAULT_TIMEOUT_MILLIS);
+ m25p16_waitForReady(fdevice, DEFAULT_TIMEOUT_MILLIS);
- m25p16_writeEnable(bus);
+ m25p16_writeEnable(fdevice);
- m25p16_enable(bus);
+ m25p16_enable(fdevice->busdev);
- spiTransfer(bus->busdev_u.spi.instance, command, NULL, isLargeFlash ? 5 : 4);
+ spiTransfer(fdevice->busdev->busdev_u.spi.instance, command, NULL, fdevice->isLargeFlash ? 5 : 4);
- spiTransfer(bus->busdev_u.spi.instance, data, NULL, length);
+ spiTransfer(fdevice->busdev->busdev_u.spi.instance, data, NULL, length);
- m25p16_disable(bus);
+ m25p16_disable(fdevice->busdev);
- currentWriteAddress += length;
+ fdevice->currentWriteAddress += length;
}
-void m25p16_pageProgramFinish(void)
+static void m25p16_pageProgramFinish(flashDevice_t *fdevice)
{
+ UNUSED(fdevice);
}
/**
@@ -348,13 +290,13 @@ void m25p16_pageProgramFinish(void)
* 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 m25p16_pageProgram(uint32_t address, const uint8_t *data, int length)
+static void m25p16_pageProgram(flashDevice_t *fdevice, uint32_t address, const uint8_t *data, int length)
{
- m25p16_pageProgramBegin(address);
+ m25p16_pageProgramBegin(fdevice, address);
- m25p16_pageProgramContinue(data, length);
+ m25p16_pageProgramContinue(fdevice, data, length);
- m25p16_pageProgramFinish();
+ m25p16_pageProgramFinish(fdevice);
}
/**
@@ -365,22 +307,22 @@ void m25p16_pageProgram(uint32_t address, const uint8_t *data, int length)
*
* The number of bytes actually read is returned, which can be zero if an error or timeout occurred.
*/
-int m25p16_readBytes(uint32_t address, uint8_t *buffer, int length)
+static int m25p16_readBytes(flashDevice_t *fdevice, uint32_t address, uint8_t *buffer, int length)
{
uint8_t command[5] = { M25P16_INSTRUCTION_READ_BYTES };
- m25p16_setCommandAddress(&command[1], address, isLargeFlash);
+ m25p16_setCommandAddress(&command[1], address, fdevice->isLargeFlash);
- if (!m25p16_waitForReady(DEFAULT_TIMEOUT_MILLIS)) {
+ if (!m25p16_waitForReady(fdevice, DEFAULT_TIMEOUT_MILLIS)) {
return 0;
}
- m25p16_enable(bus);
+ m25p16_enable(fdevice->busdev);
- spiTransfer(bus->busdev_u.spi.instance, command, NULL, isLargeFlash ? 5 : 4);
- spiTransfer(bus->busdev_u.spi.instance, NULL, buffer, length);
+ spiTransfer(fdevice->busdev->busdev_u.spi.instance, command, NULL, fdevice->isLargeFlash ? 5 : 4);
+ spiTransfer(fdevice->busdev->busdev_u.spi.instance, NULL, buffer, length);
- m25p16_disable(bus);
+ m25p16_disable(fdevice->busdev);
return length;
}
@@ -390,9 +332,21 @@ int m25p16_readBytes(uint32_t address, uint8_t *buffer, int length)
*
* Can be called before calling m25p16_init() (the result would have totalSize = 0).
*/
-const flashGeometry_t* m25p16_getGeometry(void)
+static const flashGeometry_t* m25p16_getGeometry(flashDevice_t *fdevice)
{
- return &geometry;
+ return &fdevice->geometry;
}
+const flashVTable_t m25p16_vTable = {
+ .isReady = m25p16_isReady,
+ .waitForReady = m25p16_waitForReady,
+ .eraseSector = m25p16_eraseSector,
+ .eraseCompletely = m25p16_eraseCompletely,
+ .pageProgramBegin = m25p16_pageProgramBegin,
+ .pageProgramContinue = m25p16_pageProgramContinue,
+ .pageProgramFinish = m25p16_pageProgramFinish,
+ .pageProgram = m25p16_pageProgram,
+ .readBytes = m25p16_readBytes,
+ .getGeometry = m25p16_getGeometry,
+};
#endif
diff --git a/src/main/drivers/flash_m25p16.h b/src/main/drivers/flash_m25p16.h
index 1fa8cb7a9..16cacfd82 100644
--- a/src/main/drivers/flash_m25p16.h
+++ b/src/main/drivers/flash_m25p16.h
@@ -17,27 +17,8 @@
#pragma once
-#include
-#include "flash.h"
+#include "flash_impl.h"
-#define M25P16_PAGESIZE 256
+#define JEDEC_ID_WINBOND_W25Q256 0xEF4019
-struct flashConfig_s;
-bool m25p16_init(const struct flashConfig_s *flashConfig);
-
-void m25p16_eraseSector(uint32_t address);
-void m25p16_eraseCompletely(void);
-
-void m25p16_pageProgram(uint32_t address, const uint8_t *data, int length);
-
-void m25p16_pageProgramBegin(uint32_t address);
-void m25p16_pageProgramContinue(const uint8_t *data, int length);
-void m25p16_pageProgramFinish(void);
-
-int m25p16_readBytes(uint32_t address, uint8_t *buffer, int length);
-
-bool m25p16_isReady(void);
-bool m25p16_waitForReady(uint32_t timeoutMillis);
-
-struct flashGeometry_s;
-const struct flashGeometry_s* m25p16_getGeometry(void);
+bool m25p16_detect(flashDevice_t *fdevice, uint32_t chipID);
diff --git a/src/main/fc/fc_init.c b/src/main/fc/fc_init.c
index 476412c5b..94e3554e2 100644
--- a/src/main/fc/fc_init.c
+++ b/src/main/fc/fc_init.c
@@ -49,7 +49,7 @@
#include "drivers/compass/compass.h"
#include "drivers/dma.h"
#include "drivers/exti.h"
-#include "drivers/flash_m25p16.h"
+#include "drivers/flash.h"
#include "drivers/inverter.h"
#include "drivers/io.h"
#include "drivers/light_led.h"
@@ -685,8 +685,8 @@ void init(void)
#endif
#ifdef USE_FLASHFS
-#if defined(USE_FLASH_M25P16)
- m25p16_init(flashConfig());
+#if defined(USE_FLASH)
+ flashInit(flashConfig());
#endif
flashfsInit();
#endif
diff --git a/src/main/io/flashfs.c b/src/main/io/flashfs.c
index 6f6eb4541..47dd79710 100644
--- a/src/main/io/flashfs.c
+++ b/src/main/io/flashfs.c
@@ -34,7 +34,6 @@
#include
#include "drivers/flash.h"
-#include "drivers/flash_m25p16.h"
#include "io/flashfs.h"
@@ -69,7 +68,7 @@ static void flashfsSetTailAddress(uint32_t address)
void flashfsEraseCompletely(void)
{
- m25p16_eraseCompletely();
+ flashEraseCompletely();
flashfsClearBuffer();
@@ -82,7 +81,7 @@ void flashfsEraseCompletely(void)
*/
void flashfsEraseRange(uint32_t start, uint32_t end)
{
- const flashGeometry_t *geometry = m25p16_getGeometry();
+ const flashGeometry_t *geometry = flashGetGeometry();
if (geometry->sectorSize <= 0)
return;
@@ -99,7 +98,7 @@ void flashfsEraseRange(uint32_t start, uint32_t end)
}
for (int i = startSector; i < endSector; i++) {
- m25p16_eraseSector(i * geometry->sectorSize);
+ flashEraseSector(i * geometry->sectorSize);
}
}
@@ -108,7 +107,7 @@ void flashfsEraseRange(uint32_t start, uint32_t end)
*/
bool flashfsIsReady(void)
{
- return m25p16_isReady();
+ return flashIsReady();
}
bool flashfsIsSupported(void)
@@ -118,7 +117,7 @@ bool flashfsIsSupported(void)
uint32_t flashfsGetSize(void)
{
- return m25p16_getGeometry()->totalSize;
+ return flashGetGeometry()->totalSize;
}
static uint32_t flashfsTransmitBufferUsed(void)
@@ -147,7 +146,7 @@ uint32_t flashfsGetWriteBufferFreeSpace(void)
const flashGeometry_t* flashfsGetGeometry(void)
{
- return m25p16_getGeometry();
+ return flashGetGeometry();
}
/**
@@ -179,12 +178,14 @@ static uint32_t flashfsWriteBuffers(uint8_t const **buffers, uint32_t *bufferSiz
bytesTotal += bufferSizes[i];
}
- if (!sync && !m25p16_isReady()) {
+ if (!sync && !flashIsReady()) {
return 0;
}
uint32_t bytesTotalRemaining = bytesTotal;
+ uint16_t pageSize = flashfsGetGeometry()->pageSize;
+
while (bytesTotalRemaining > 0) {
uint32_t bytesTotalThisIteration;
uint32_t bytesRemainThisIteration;
@@ -193,8 +194,8 @@ static uint32_t flashfsWriteBuffers(uint8_t const **buffers, uint32_t *bufferSiz
* Each page needs to be saved in a separate program operation, so
* if we would cross a page boundary, only write up to the boundary in this iteration:
*/
- if (tailAddress % M25P16_PAGESIZE + bytesTotalRemaining > M25P16_PAGESIZE) {
- bytesTotalThisIteration = M25P16_PAGESIZE - tailAddress % M25P16_PAGESIZE;
+ if (tailAddress % pageSize + bytesTotalRemaining > pageSize) {
+ bytesTotalThisIteration = pageSize - tailAddress % pageSize;
} else {
bytesTotalThisIteration = bytesTotalRemaining;
}
@@ -207,7 +208,7 @@ static uint32_t flashfsWriteBuffers(uint8_t const **buffers, uint32_t *bufferSiz
break;
}
- m25p16_pageProgramBegin(tailAddress);
+ flashPageProgramBegin(tailAddress);
bytesRemainThisIteration = bytesTotalThisIteration;
@@ -215,7 +216,7 @@ static uint32_t flashfsWriteBuffers(uint8_t const **buffers, uint32_t *bufferSiz
if (bufferSizes[i] > 0) {
// Is buffer larger than our write limit? Write our limit out of it
if (bufferSizes[i] >= bytesRemainThisIteration) {
- m25p16_pageProgramContinue(buffers[i], bytesRemainThisIteration);
+ flashPageProgramContinue(buffers[i], bytesRemainThisIteration);
buffers[i] += bytesRemainThisIteration;
bufferSizes[i] -= bytesRemainThisIteration;
@@ -224,7 +225,7 @@ static uint32_t flashfsWriteBuffers(uint8_t const **buffers, uint32_t *bufferSiz
break;
} else {
// We'll still have more to write after finishing this buffer off
- m25p16_pageProgramContinue(buffers[i], bufferSizes[i]);
+ flashPageProgramContinue(buffers[i], bufferSizes[i]);
bytesRemainThisIteration -= bufferSizes[i];
@@ -234,7 +235,7 @@ static uint32_t flashfsWriteBuffers(uint8_t const **buffers, uint32_t *bufferSiz
}
}
- m25p16_pageProgramFinish();
+ flashPageProgramFinish();
bytesTotalRemaining -= bytesTotalThisIteration;
@@ -481,7 +482,7 @@ int flashfsReadAbs(uint32_t address, uint8_t *buffer, unsigned int len)
// Since the read could overlap data in our dirty buffers, force a sync to clear those first
flashfsFlushSync();
- bytesRead = m25p16_readBytes(address, buffer, len);
+ bytesRead = flashReadBytes(address, buffer, len);
return bytesRead;
}
@@ -504,13 +505,15 @@ int flashfsIdentifyStartOfFreeSpace(void)
/* We can choose whatever power of 2 size we like, which determines how much wastage of free space we'll have
* at the end of the last written data. But smaller blocksizes will require more searching.
*/
- FREE_BLOCK_SIZE = 2048,
+ FREE_BLOCK_SIZE = 2048, // XXX This can't be smaller than page size for underlying flash device.
/* We don't expect valid data to ever contain this many consecutive uint32_t's of all 1 bits: */
FREE_BLOCK_TEST_SIZE_INTS = 4, // i.e. 16 bytes
FREE_BLOCK_TEST_SIZE_BYTES = FREE_BLOCK_TEST_SIZE_INTS * sizeof(uint32_t)
};
+ STATIC_ASSERT(FREE_BLOCK_SIZE >= FLASH_MAX_PAGE_SIZE, FREE_BLOCK_SIZE_too_small);
+
union {
uint8_t bytes[FREE_BLOCK_TEST_SIZE_BYTES];
uint32_t ints[FREE_BLOCK_TEST_SIZE_INTS];
@@ -526,7 +529,7 @@ int flashfsIdentifyStartOfFreeSpace(void)
while (left < right) {
mid = (left + right) / 2;
- if (m25p16_readBytes(mid * FREE_BLOCK_SIZE, testBuffer.bytes, FREE_BLOCK_TEST_SIZE_BYTES) < FREE_BLOCK_TEST_SIZE_BYTES) {
+ if (flashReadBytes(mid * FREE_BLOCK_SIZE, testBuffer.bytes, FREE_BLOCK_TEST_SIZE_BYTES) < FREE_BLOCK_TEST_SIZE_BYTES) {
// Unexpected timeout from flash, so bail early (reporting the device fuller than it really is)
break;
}
@@ -563,6 +566,23 @@ bool flashfsIsEOF(void)
return tailAddress >= flashfsGetSize();
}
+void flashfsClose(void)
+{
+ switch(flashfsGetGeometry()->flashType) {
+ case FLASH_TYPE_NOR:
+ break;
+
+ case FLASH_TYPE_NAND:
+ flashFlush();
+
+ // Advance tailAddress to next page boundary.
+ uint32_t pageSize = flashfsGetGeometry()->pageSize;
+ flashfsSetTailAddress((tailAddress + pageSize - 1) & ~(pageSize - 1));
+
+ break;
+ }
+}
+
/**
* Call after initializing the flash chip in order to set up the filesystem.
*/
diff --git a/src/main/io/flashfs.h b/src/main/io/flashfs.h
index db3d0f3e0..ab0943189 100644
--- a/src/main/io/flashfs.h
+++ b/src/main/io/flashfs.h
@@ -45,6 +45,7 @@ int flashfsReadAbs(uint32_t offset, uint8_t *data, unsigned int len);
bool flashfsFlushAsync(void);
void flashfsFlushSync(void);
+void flashfsClose(void);
void flashfsInit(void);
bool flashfsIsSupported(void);
diff --git a/src/main/msc/usbd_storage_emfat.c b/src/main/msc/usbd_storage_emfat.c
index f830ecb84..b43352a1c 100644
--- a/src/main/msc/usbd_storage_emfat.c
+++ b/src/main/msc/usbd_storage_emfat.c
@@ -34,7 +34,7 @@
#include "drivers/light_led.h"
#include "drivers/time.h"
-#include "drivers/flash_m25p16.h"
+#include "drivers/flash.h"
#include "io/flashfs.h"
@@ -68,9 +68,9 @@ static int8_t STORAGE_Init(uint8_t lun)
LED0_ON;
-#ifdef USE_FLASHFS
-#if defined(USE_FLASH_M25P16)
- m25p16_init(flashConfig());
+#ifdef USE_FLASHFS
+#ifdef USE_FLASH
+ flashInit(flashConfig());
#endif
flashfsInit();
#endif
diff --git a/src/main/target/BLUEJAYF4/hardware_revision.c b/src/main/target/BLUEJAYF4/hardware_revision.c
index d952fbed1..ac10aa142 100644
--- a/src/main/target/BLUEJAYF4/hardware_revision.c
+++ b/src/main/target/BLUEJAYF4/hardware_revision.c
@@ -24,7 +24,7 @@
#include "build/build_config.h"
#include "drivers/bus_spi.h"
-#include "drivers/flash_m25p16.h"
+#include "drivers/flash.h"
#include "drivers/io.h"
#include "drivers/time.h"
@@ -85,7 +85,7 @@ void updateHardwareRevision(void)
if flash exists on PB3 then Rev1
*/
flashConfig_t flashConfig = { .csTag = IO_TAG(PB3) };
- if (m25p16_init(&flashConfig)) {
+ if (flashInit(&flashConfig)) {
hardwareRevision = BJF4_REV1;
} else {
IOInit(IOGetByTag(IO_TAG(PB3)), OWNER_FREE, 0);
diff --git a/src/main/target/common_fc_post.h b/src/main/target/common_fc_post.h
index 23676d63a..9c42755d0 100644
--- a/src/main/target/common_fc_post.h
+++ b/src/main/target/common_fc_post.h
@@ -119,3 +119,7 @@
#if defined(USE_GYRO_SPI_MPU6500) || defined(USE_GYRO_SPI_MPU9250) || defined(USE_GYRO_SPI_ICM20689)
#define USE_32K_CAPABLE_GYRO
#endif
+
+#if defined(USE_FLASH_M25P16)
+#define USE_FLASH
+#endif