mirror of
https://github.com/betaflight/betaflight.git
synced 2025-07-24 16:55:36 +03:00
Prepare flash code for multiple device type support (#5683)
* Prepare flash drivers for multiple device type support * Add static assertions on device page and flashfs alloc sizes.
This commit is contained in:
parent
4a5e79a534
commit
864dba98c1
13 changed files with 387 additions and 187 deletions
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue