1
0
Fork 0
mirror of https://github.com/betaflight/betaflight.git synced 2025-07-17 05:15:25 +03:00
betaflight/src/main/drivers/flash.c
Dominic Clifton 5213bb5871
Fix unified targets that use OctoSPI. (BF4.4.x) (#12307)
OctoSPI and Memory Mapped Flash support (#11825)

* STM32H730/STM32H750 - Fix use of USE_LP_UART1 instead of USE_LPUART1.

* STM32H723 - Prepare for being able to build for using non-internal-flash
config storage.

* STM32H723 - Prepare for using non-default strings.

* STM32H723 - Disable 'use custom defaults' when using USE_EXST.

* STM32H723 - Disable CUSTOM_DEFAULTS_EXTENDED when EXST is used.

* OCTOSPI - Add initialisation code.

* Add support for RAM_CODE.

* STM32H730 - Add support for RAM_CODE via the .ram_code attribute.

* OCTOSPI - Proof-of-concept for disabling/enabling memory mapped mode on
a running system.

NOTE: The HAL libs are compiled into a memory mapped region, and this cannot be used for OctoSPI access when memory mapped mode is disabled.

* OCTOSPI - Drop HAL support after determining that it's not suitable for
the memory mapped flash use-case.

* OCTOSPI - Sometimes, when disabling memory map mode, the abort fails.
Handle this by disabling the OSPI peripheral first and re-enabling it
afterwards.

* SD/FLASH - Update comments regarding possible solutions to the catch-22
issue with SD/SPI/QUADSPI/OCTOSPI pin configurations.

* OCTOSPI - Use device instance directly.

* OCTOSPI - Prepare W25Q flash driver for octospi support.

* OCTOSPI - Add octospi flash detection.

Note: The method to detect flash chips is similar to the method used for
QUADSPI and as such the code was used as a base. However the initial
OCTOSPI implementation doesn't support the non-memory-mapped use-case so
the un-tested code is gated with `USE_OCTOSPI_EXPERIMENTAL`.

The key differences are:
* uses octospi api not quadspi api.
* flash chip clock speeds should not be changed for memory-mapped flash
chips, bootloader already set it correctly.
* only certain flash chips are capable of memory mapped support.

* W25Q - Ensure w25q128fv_readRegister returns 0 if the receive fails.

* OCTOSPI - Implement octoSpiTransmit1LINE, octoSpiReceive1LINE and
octoSpiReceive4LINES.

* OCTOSPI - Specify device from init.

* OCTOSPI - More fixes and work on init.

Current status is that memory mapped mode is disabled and flash chip is
detected, but w25q128fv_detect should not be calling w25q128fv_reset.

* FLASH - Add comment regarding wasted flash space on targets that only
use one bus type for the flash chip.

* FLASH - Split `detect` into `identify` and `configure`.

* OCTOSPI - Extract flashMemoryMappedModeEnable/Disable to separate
methods.

* FLASH - Reduce size of targets that don't support the use of multiple
flash interfaces.

* Single-flash-chip targets usually only support one type of io
interface.
* Without this, compiler warnings are generated in `flashSpiInit` for
targets that only use flash chip drivers that support quadspi or octospi
that don't even use SPI for flash.

* FLASH - Use MMFLASH_CODE/DATA to conditionally move code/data to RAM.

Only targets compiled to support memory mapped flash chips need the some
specific code in RAM.  Otherwise the code/data should be in the normal
linker section.

* FLASH - W25Q Prepare for memory mapped flash usage.

* Wait/Delay functions must work with interrupts disabled.
* Code used for reading/writing must run from RAM.

* OCTOSPI - Implement remaining required methods.

* OCTOSPI - Fixes for earlier code (not last commit).

* FLASH - W25Q update timeout values from Datasheet Rev L.

* FLASH - Prepare flash driver for use when memory mapped flash is
disabled.

* System - Prepare microsISR for use when memory mapped flash is disabled.

* FLASH - Add support for CONFIG_IN_MEMORY_MAPPED_FLASH.

* Flash - Fix incorrect gating on cli flash commands.

When compiling with USE_FLASH_CHIP and without USE_FLASHFS there were
compiler warnings.

* MMFLASH - Fix release-mode build.

* FLASH - Allow SPI pins to be reconfigured when using CONFIG_IN_MEMORY_MAPPED_FLASH.

MMFLASH only works via QuadSPI/OctoSPI peripherals.

* EXST - Disable the 2GB workaround which is now causing a different
error.

The error we get with 'remove-section' enabled is:

"error in private header data: sorry, cannot handle this file".  The
cause of this new error in the objcopy codebase is an out of memory
condition, somehow the 2GB files and this error are related but the root
cause of both is still unknown.

* OCTOSPI - Add support for STM32H723.

* STM32H723 - Add linker scripts for EXST usage.

* NucleoH723ZG - Add build script to demonstrate OCTOSPI and Memory Mapped
flash support.

* FLASH - WUse the size in bits to set the size of the buffer.

* FLASH - Fix typo in W25N driver defines.

Was using W28N instead of W25N

* OCTOSPI - Fix missing semilcolon when compiling without
USE_FLASH_MEMORY_MAPPED.

* OCTPSPI - Fix missing call to 'memoryMappedModeInit'.

* SPRacingH7RF - Add example build script to allow for testing prior to
unified target / cloud-build support.
2023-02-07 23:20:18 +01:00

631 lines
17 KiB
C

/*
* 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/>.
*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "platform.h"
#include "build/debug.h"
#ifdef USE_FLASH_CHIP
#include "flash.h"
#include "flash_impl.h"
#include "flash_m25p16.h"
#include "flash_w25n01g.h"
#include "flash_w25q128fv.h"
#include "flash_w25m.h"
#include "drivers/bus_spi.h"
#include "drivers/bus_quadspi.h"
#include "drivers/bus_octospi.h"
#include "drivers/io.h"
#include "drivers/time.h"
#include "drivers/system.h"
#ifdef USE_FLASH_SPI
// 20 MHz max SPI frequency
#define FLASH_MAX_SPI_CLK_HZ 20000000
// 5 MHz max SPI init frequency
#define FLASH_MAX_SPI_INIT_CLK 5000000
static extDevice_t devInstance;
static extDevice_t *dev;
#endif
static flashDevice_t flashDevice;
static flashPartitionTable_t flashPartitionTable;
static int flashPartitions = 0;
#define FLASH_INSTRUCTION_RDID 0x9F
#ifdef USE_FLASH_MEMORY_MAPPED
MMFLASH_CODE_NOINLINE void flashMemoryMappedModeDisable(void)
{
__disable_irq();
#ifdef USE_FLASH_OCTOSPI
octoSpiDisableMemoryMappedMode(flashDevice.io.handle.octoSpi);
#else
#error Invalid configuration - Not implemented
#endif
}
MMFLASH_CODE_NOINLINE void flashMemoryMappedModeEnable(void)
{
#ifdef USE_FLASH_OCTOSPI
octoSpiEnableMemoryMappedMode(flashDevice.io.handle.octoSpi);
__enable_irq();
#else
#error Invalid configuration - Not implemented
#endif
}
#endif
#ifdef USE_FLASH_OCTOSPI
MMFLASH_CODE_NOINLINE static bool flashOctoSpiInit(const flashConfig_t *flashConfig)
{
bool detected = false;
enum {
TRY_1LINE = 0, TRY_4LINE, BAIL
} phase = TRY_1LINE;
#ifdef USE_FLASH_MEMORY_MAPPED
bool memoryMappedModeEnabledOnBoot = isMemoryMappedModeEnabledOnBoot();
#else
bool memoryMappedModeEnabledOnBoot = false;
#endif
#ifndef USE_OCTOSPI_EXPERIMENTAL
if (!memoryMappedModeEnabledOnBoot) {
return false; // Not supported yet, enable USE_OCTOSPI_EXPERIMENTAL and test/update implementation as required.
}
#endif
OCTOSPI_TypeDef *instance = octoSpiInstanceByDevice(OCTOSPI_CFG_TO_DEV(flashConfig->octoSpiDevice));
flashDevice.io.handle.octoSpi = instance;
flashDevice.io.mode = FLASHIO_OCTOSPI;
if (memoryMappedModeEnabledOnBoot) {
flashMemoryMappedModeDisable();
}
do {
#ifdef USE_OCTOSPI_EXPERIMENTAL
if (!memoryMappedMode) {
octoSpiSetDivisor(instance, OCTOSPI_CLOCK_INITIALISATION);
}
#endif
// for the memory-mapped use-case, we rely on the bootloader to have already selected the correct speed for the flash chip.
// 3 bytes for what we need, but some IC's need 8 dummy cycles after the instruction, so read 4 and make two attempts to
// assemble the chip id from the response.
uint8_t readIdResponse[4];
bool status = false;
switch (phase) {
case TRY_1LINE:
status = octoSpiReceive1LINE(instance, FLASH_INSTRUCTION_RDID, 0, readIdResponse, 4);
break;
case TRY_4LINE:
status = octoSpiReceive4LINES(instance, FLASH_INSTRUCTION_RDID, 2, readIdResponse, 3);
break;
default:
break;
}
if (!status) {
phase++;
continue;
}
#ifdef USE_OCTOSPI_EXPERIMENTAL
if (!memoryMappedModeEnabledOnBoot) {
octoSpiSetDivisor(instance, OCTOSPI_CLOCK_ULTRAFAST);
}
#endif
for (uint8_t offset = 0; offset <= 1 && !detected; offset++) {
uint32_t jedecID = (readIdResponse[offset + 0] << 16) | (readIdResponse[offset + 1] << 8) | (readIdResponse[offset + 2]);
if (offset == 0) {
#if defined(USE_FLASH_W25Q128FV)
if (!detected && w25q128fv_identify(&flashDevice, jedecID)) {
detected = true;
}
#endif
}
if (offset == 1) {
#ifdef USE_OCTOSPI_EXPERIMENTAL
if (!memoryMappedModeEnabledOnBoot) {
// These flash chips DO NOT support memory mapped mode; suitable flash read commands must be available.
#if defined(USE_FLASH_W25N01G)
if (!detected && w25n01g_identify(&flashDevice, jedecID)) {
detected = true;
}
#endif
#if defined(USE_FLASH_W25M02G)
if (!detected && w25m_identify(&flashDevice, jedecID)) {
detected = true;
}
#endif
}
#endif
}
}
phase++;
} while (phase != BAIL && !detected);
if (memoryMappedModeEnabledOnBoot) {
flashMemoryMappedModeEnable();
}
return detected;
}
#endif // USE_FLASH_OCTOSPI
#ifdef USE_FLASH_QUADSPI
static bool flashQuadSpiInit(const flashConfig_t *flashConfig)
{
bool detected = false;
enum { TRY_1LINE = 0, TRY_4LINE, BAIL};
int phase = TRY_1LINE;
QUADSPI_TypeDef *hqspi = quadSpiInstanceByDevice(QUADSPI_CFG_TO_DEV(flashConfig->quadSpiDevice));
flashDevice.io.handle.quadSpi = hqspi;
flashDevice.io.mode = FLASHIO_QUADSPI;
do {
quadSpiSetDivisor(hqspi, QUADSPI_CLOCK_INITIALISATION);
// 3 bytes for what we need, but some IC's need 8 dummy cycles after the instruction, so read 4 and make two attempts to
// assemble the chip id from the response.
uint8_t readIdResponse[4];
bool status = false;
switch (phase) {
case TRY_1LINE:
status = quadSpiReceive1LINE(hqspi, FLASH_INSTRUCTION_RDID, 0, readIdResponse, 4);
break;
case TRY_4LINE:
status = quadSpiReceive4LINES(hqspi, FLASH_INSTRUCTION_RDID, 2, readIdResponse, 3);
break;
default:
break;
}
if (!status) {
phase++;
continue;
}
quadSpiSetDivisor(hqspi, QUADSPI_CLOCK_ULTRAFAST);
for (uint8_t offset = 0; offset <= 1 && !detected; offset++) {
uint32_t jedecID = (readIdResponse[offset + 0] << 16) | (readIdResponse[offset + 1] << 8) | (readIdResponse[offset + 2]);
if (offset == 0) {
#if defined(USE_FLASH_W25Q128FV)
if (!detected && w25q128fv_identify(&flashDevice, jedecID)) {
detected = true;
}
#endif
#ifdef USE_FLASH_M25P16
if (!detected && m25p16_detect(&flashDevice, chipID)) {
detected = true;
}
#endif
}
if (offset == 1) {
#if defined(USE_FLASH_W25N01G)
if (!detected && w25n01g_identify(&flashDevice, jedecID)) {
detected = true;
}
#endif
#if defined(USE_FLASH_W25M02G)
if (!detected && w25m_identify(&flashDevice, jedecID)) {
detected = true;
}
#endif
}
if (detected) {
flashDevice.geometry.jedecId = jedecID;
}
}
phase++;
} while (phase != BAIL && !detected);
return detected;
}
#endif // USE_FLASH_QUADSPI
#ifdef USE_FLASH_SPI
static bool flashSpiInit(const flashConfig_t *flashConfig)
{
bool detected = false;
// Read chip identification and send it to device detect
dev = &devInstance;
if (flashConfig->csTag) {
dev->busType_u.spi.csnPin = IOGetByTag(flashConfig->csTag);
} else {
return false;
}
if (!IOIsFreeOrPreinit(dev->busType_u.spi.csnPin)) {
return false;
}
if (!spiSetBusInstance(dev, flashConfig->spiDevice)) {
return false;
}
// Set the callback argument when calling back to this driver for DMA completion
dev->callbackArg = (uint32_t)&flashDevice;
IOInit(dev->busType_u.spi.csnPin, OWNER_FLASH_CS, 0);
IOConfigGPIO(dev->busType_u.spi.csnPin, SPI_IO_CS_CFG);
IOHi(dev->busType_u.spi.csnPin);
//Maximum speed for standard READ command is 20mHz, other commands tolerate 25mHz
spiSetClkDivisor(dev, spiCalculateDivider(FLASH_MAX_SPI_INIT_CLK));
flashDevice.io.mode = FLASHIO_SPI;
flashDevice.io.handle.dev = dev;
delay(50); // short delay required after initialisation of SPI device instance.
/*
* Some newer chips require one dummy byte to be read; we can read
* 4 bytes for these chips while retaining backward compatibility.
*/
uint8_t readIdResponse[4] = { 0 };
spiReadRegBuf(dev, FLASH_INSTRUCTION_RDID, readIdResponse, sizeof(readIdResponse));
// Manufacturer, memory type, and capacity
uint32_t jedecID = (readIdResponse[0] << 16) | (readIdResponse[1] << 8) | (readIdResponse[2]);
#ifdef USE_FLASH_M25P16
if (m25p16_identify(&flashDevice, jedecID)) {
detected = true;
}
#endif
#if defined(USE_FLASH_W25M512) || defined(USE_FLASH_W25M)
if (!detected && w25m_identify(&flashDevice, jedecID)) {
detected = true;
}
#endif
if (!detected) {
// Newer chips
jedecID = (readIdResponse[1] << 16) | (readIdResponse[2] << 8) | (readIdResponse[3]);
}
#ifdef USE_FLASH_W25N01G
if (!detected && w25n01g_identify(&flashDevice, jedecID)) {
detected = true;
}
#endif
#ifdef USE_FLASH_W25M02G
if (!detected && w25m_identify(&flashDevice, jedecID)) {
detected = true;
}
#endif
if (detected) {
flashDevice.geometry.jedecId = jedecID;
return detected;
}
spiPreinitByTag(flashConfig->csTag);
return false;
}
#endif // USE_FLASH_SPI
void flashPreInit(const flashConfig_t *flashConfig)
{
spiPreinitRegister(flashConfig->csTag, IOCFG_IPU, 1);
}
bool flashDeviceInit(const flashConfig_t *flashConfig)
{
bool haveFlash = false;
#ifdef USE_FLASH_SPI
bool useSpi = (SPI_CFG_TO_DEV(flashConfig->spiDevice) != SPIINVALID);
if (useSpi) {
haveFlash = flashSpiInit(flashConfig);
}
#endif
#ifdef USE_FLASH_QUADSPI
bool useQuadSpi = (QUADSPI_CFG_TO_DEV(flashConfig->quadSpiDevice) != QUADSPIINVALID);
if (useQuadSpi) {
haveFlash = flashQuadSpiInit(flashConfig);
}
#endif
#ifdef USE_FLASH_OCTOSPI
bool useOctoSpi = (OCTOSPI_CFG_TO_DEV(flashConfig->octoSpiDevice) != OCTOSPIINVALID);
if (useOctoSpi) {
haveFlash = flashOctoSpiInit(flashConfig);
}
#endif
if (haveFlash && flashDevice.vTable->configure) {
uint32_t configurationFlags = 0;
#ifdef USE_FLASH_MEMORY_MAPPED
if (isMemoryMappedModeEnabledOnBoot()) {
configurationFlags |= FLASH_CF_SYSTEM_IS_MEMORY_MAPPED;
}
#endif
flashDevice.vTable->configure(&flashDevice, configurationFlags);
}
return haveFlash;
}
MMFLASH_CODE bool flashIsReady(void)
{
return flashDevice.vTable->isReady(&flashDevice);
}
MMFLASH_CODE bool flashWaitForReady(void)
{
return flashDevice.vTable->waitForReady(&flashDevice);
}
MMFLASH_CODE void flashEraseSector(uint32_t address)
{
flashDevice.callback = NULL;
flashDevice.vTable->eraseSector(&flashDevice, address);
}
void flashEraseCompletely(void)
{
flashDevice.callback = NULL;
flashDevice.vTable->eraseCompletely(&flashDevice);
}
/* The callback, if provided, will receive the totoal number of bytes transfered
* by each call to flashPageProgramContinue() once the transfer completes.
*/
MMFLASH_CODE void flashPageProgramBegin(uint32_t address, void (*callback)(uint32_t length))
{
flashDevice.vTable->pageProgramBegin(&flashDevice, address, callback);
}
MMFLASH_CODE uint32_t flashPageProgramContinue(const uint8_t **buffers, uint32_t *bufferSizes, uint32_t bufferCount)
{
uint32_t maxBytesToWrite = flashDevice.geometry.pageSize - (flashDevice.currentWriteAddress % flashDevice.geometry.pageSize);
if (bufferCount == 0) {
return 0;
}
if (bufferSizes[0] >= maxBytesToWrite) {
bufferSizes[0] = maxBytesToWrite;
bufferCount = 1;
} else {
maxBytesToWrite -= bufferSizes[0];
if ((bufferCount == 2) && (bufferSizes[1] > maxBytesToWrite)) {
bufferSizes[1] = maxBytesToWrite;
}
}
return flashDevice.vTable->pageProgramContinue(&flashDevice, buffers, bufferSizes, bufferCount);
}
MMFLASH_CODE void flashPageProgramFinish(void)
{
flashDevice.vTable->pageProgramFinish(&flashDevice);
}
MMFLASH_CODE void flashPageProgram(uint32_t address, const uint8_t *data, uint32_t length, void (*callback)(uint32_t length))
{
flashDevice.vTable->pageProgram(&flashDevice, address, data, length, callback);
}
MMFLASH_CODE int flashReadBytes(uint32_t address, uint8_t *buffer, uint32_t length)
{
flashDevice.callback = NULL;
return flashDevice.vTable->readBytes(&flashDevice, address, buffer, length);
}
MMFLASH_CODE void flashFlush(void)
{
if (flashDevice.vTable->flush) {
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;
}
/*
* Flash partitioning
*
* Partition table is not currently stored on the flash, in-memory only.
*
* Partitions are required so that Badblock management (inc spare blocks), FlashFS (Blackbox Logging), Configuration and Firmware can be kept separate and tracked.
*
* XXX FIXME
* XXX Note that Flash FS must start at sector 0.
* XXX There is existing blackbox/flash FS code the relies on this!!!
* XXX This restriction can and will be fixed by creating a set of flash operation functions that take partition as an additional parameter.
*/
static void flashConfigurePartitions(void)
{
#if defined(FIRMWARE_SIZE) || defined(CONFIG_IN_EXTERNAL_FLASH) || defined(USE_FLASHFS)
const flashGeometry_t *flashGeometry = flashGetGeometry();
if (flashGeometry->totalSize == 0) {
return;
}
flashSector_t startSector = 0;
flashSector_t endSector = flashGeometry->sectors - 1; // 0 based index
const flashPartition_t *badBlockPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_BADBLOCK_MANAGEMENT);
if (badBlockPartition) {
endSector = badBlockPartition->startSector - 1;
}
#endif
#if defined(FIRMWARE_SIZE)
const uint32_t firmwareSize = (FIRMWARE_SIZE * 1024);
flashSector_t firmwareSectors = (firmwareSize / flashGeometry->sectorSize);
if (firmwareSize % flashGeometry->sectorSize > 0) {
firmwareSectors++; // needs a portion of a sector.
}
startSector = (endSector + 1) - firmwareSectors; // + 1 for inclusive
flashPartitionSet(FLASH_PARTITION_TYPE_FIRMWARE, startSector, endSector);
endSector = startSector - 1;
startSector = 0;
#endif
#if defined(CONFIG_IN_EXTERNAL_FLASH) || defined(CONFIG_IN_MEMORY_MAPPED_FLASH)
const uint32_t configSize = EEPROM_SIZE;
flashSector_t configSectors = (configSize / flashGeometry->sectorSize);
if (configSize % flashGeometry->sectorSize > 0) {
configSectors++; // needs a portion of a sector.
}
startSector = (endSector + 1) - configSectors; // + 1 for inclusive
flashPartitionSet(FLASH_PARTITION_TYPE_CONFIG, startSector, endSector);
endSector = startSector - 1;
startSector = 0;
#endif
#ifdef USE_FLASHFS
flashPartitionSet(FLASH_PARTITION_TYPE_FLASHFS, startSector, endSector);
#endif
}
flashPartition_t *flashPartitionFindByType(uint8_t type)
{
for (int index = 0; index < FLASH_MAX_PARTITIONS; index++) {
flashPartition_t *candidate = &flashPartitionTable.partitions[index];
if (candidate->type == type) {
return candidate;
}
}
return NULL;
}
const flashPartition_t *flashPartitionFindByIndex(uint8_t index)
{
if (index >= flashPartitions) {
return NULL;
}
return &flashPartitionTable.partitions[index];
}
void flashPartitionSet(uint8_t type, uint32_t startSector, uint32_t endSector)
{
flashPartition_t *entry = flashPartitionFindByType(type);
if (!entry) {
if (flashPartitions == FLASH_MAX_PARTITIONS - 1) {
return;
}
entry = &flashPartitionTable.partitions[flashPartitions++];
}
entry->type = type;
entry->startSector = startSector;
entry->endSector = endSector;
}
// Must be in sync with FLASH_PARTITION_TYPE
static const char *flashPartitionNames[] = {
"UNKNOWN ",
"PARTITION",
"FLASHFS ",
"BBMGMT ",
"FIRMWARE ",
"CONFIG ",
};
const char *flashPartitionGetTypeName(flashPartitionType_e type)
{
if (type < ARRAYLEN(flashPartitionNames)) {
return flashPartitionNames[type];
}
return NULL;
}
bool flashInit(const flashConfig_t *flashConfig)
{
memset(&flashPartitionTable, 0x00, sizeof(flashPartitionTable));
flashPartitions = 0;
bool haveFlash = flashDeviceInit(flashConfig);
flashConfigurePartitions();
return haveFlash;
}
int flashPartitionCount(void)
{
return flashPartitions;
}
#endif // USE_FLASH_CHIP