From cb16786a7432ab8cdcf8e2500c97bfad214b11e0 Mon Sep 17 00:00:00 2001 From: Raphael Coeffic Date: Fri, 13 Oct 2023 11:51:11 +0200 Subject: [PATCH] feat: SPI flash driver (#4012) Co-authored-by: Richard Li --- radio/src/drivers/frftl.cpp | 1155 +++++++++++++++++ radio/src/drivers/frftl.h | 82 ++ radio/src/hal/fatfs_diskio.cpp | 15 + radio/src/hal/fatfs_diskio.h | 5 + radio/src/hal/storage.cpp | 8 + radio/src/hal/storage.h | 1 + .../arm/stm32/bootloader/CMakeLists.txt | 5 + .../common/arm/stm32/bootloader/bin_files.cpp | 10 +- .../common/arm/stm32/bootloader/boot.cpp | 2 + .../common/arm/stm32/diskio_spi_flash.cpp | 195 +++ .../common/arm/stm32/diskio_spi_flash.h | 26 + .../targets/common/arm/stm32/spi_flash.cpp | 378 ++++++ .../src/targets/common/arm/stm32/spi_flash.h | 38 + radio/src/targets/horus/CMakeLists.txt | 3 + radio/src/targets/horus/hal.h | 73 +- radio/src/targets/nv14/CMakeLists.txt | 3 + radio/src/targets/nv14/hal.h | 9 + 17 files changed, 1977 insertions(+), 31 deletions(-) create mode 100644 radio/src/drivers/frftl.cpp create mode 100644 radio/src/drivers/frftl.h create mode 100644 radio/src/targets/common/arm/stm32/diskio_spi_flash.cpp create mode 100644 radio/src/targets/common/arm/stm32/diskio_spi_flash.h create mode 100644 radio/src/targets/common/arm/stm32/spi_flash.cpp create mode 100644 radio/src/targets/common/arm/stm32/spi_flash.h diff --git a/radio/src/drivers/frftl.cpp b/radio/src/drivers/frftl.cpp new file mode 100644 index 000000000..a47132820 --- /dev/null +++ b/radio/src/drivers/frftl.cpp @@ -0,0 +1,1155 @@ +/* + * Copyright (C) EdgeTX + * + * Authors: + * Dr. Richard Li + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + */ + +/* + * FrFTL - Flash Resident Flash Translation Layer + * + * This is a FTL designed for NOR flash, logical to physical mapping uses 2 layers + * of translation tables all resident in flash. It comes with mechanisms to ensure + * the integrity of the data in previous state when power out occurs in the middle + * of flash programming. + * + * It can be used to back the FatFS library by ChaN and included the support of + * CTRL_SYNC and CTRL_TRIM functions for best performance. + * + * + * How it works: + * The FTL organize the storage as 4096 byte pages. The translations from a logical page + * to a physical page are recorded in the translation table. The translation table is + * organized into TT pages in which each TT page can store the mapping of 1024 pages. + * the TT pages are organized into 2 layers: master TT (MTT) and secondary TT (STT). + * the logical page no. of MTT is 0, and followed by STT from 1 onwards, therefore + * all the translation for TT pages are resided in the MTT page. + * + * For logical to physical lookup, there are 2 cases, depends on what the logical page no. is: + * 1. MTT page -> data page + * 2. MTT page -> STT page -> data page + * + * The page buffer is designed to delay the changes and write back to the flash in larger + * translation context to minimize the amount of TT page updates, so that the write + * amplification effect and wearing is minimized and improving the overall read/write + * performance. + * + */ + +#include "frftl.h" + +#include "crc.h" +#include "definitions.h" + +#include +#include + +#define SECTOR_SIZE 512 +#define PAGE_SIZE 4096 +#define SECTORS_PER_PAGE (PAGE_SIZE / SECTOR_SIZE) +#define TT_PAGE_MAGIC 0xEF87364A +#define TT_RECORDS_PER_PAGE 1024 + +// The multiplier for cache buffers, min recommendation is 2 +#define BUFFER_SIZE_MULTIPLIER 4 + +// Will cap the buffer size to this when exceeded +#define MAX_BUFFER_SIZE 64 + +// Reserve pages to minimize the erase cycles when the FS is full, +// should be at least 2 times of BUFFER_SIZE_MULTIPLIER +#define RESERVED_PAGES_MULTIPLIER 16 + +#define LOCKED 1 +#define UNLOCKED 0 + +typedef enum { + UNKNOWN, + USED, + ERASE_REQUIRED, + ERASED +} PhysicalPageState; + +typedef enum { + NONE, + PROGRAM, + ERASE_PROGRAM, + RELOCATE_ERASE_PROGRAM +} ProgramMode; + +// +// Structures in FLASH +// +typedef struct { + uint32_t magicStart; + uint32_t logicalPageNo; + uint32_t serial; + uint16_t padding; + uint16_t crc16; +} TransTableHeader; + +typedef struct { + union { + TransTableHeader header; // 16 bytes + uint8_t __padding[1024]; // 1KB + }; + uint8_t sectStatus[TT_RECORDS_PER_PAGE]; // 1KB + uint16_t physicalPageNo[TT_RECORDS_PER_PAGE]; // 2KB +} TransTable; + +typedef union { + TransTable tt; // 3KB + 16B + uint8_t data[PAGE_SIZE]; // 4KB +} Page; + +// +// Structures in RAM only +// +typedef struct { + uint16_t physicalPageNo; + uint8_t sectStatus; +} PageInfo; + +typedef struct PageBuffer PageBuffer; + +struct PageBuffer { + Page page; + PageBuffer* lruPrev; // Double linked-list for LRU implementation + PageBuffer* lruNext; // Double linked-list for LRU implementation + PageBuffer* hashNext; // Linked-list for hash table implementation + uint16_t logicalPageNo; // Required for first program or reprogram + uint16_t physicalPageNo; + uint8_t lock; // Page locked for delayed update + uint8_t sectorProgramRequired; // Record which sector need to be programmed + uint8_t pMode; +}; + +static const uint16_t supportedFlashSizes[] = { 4, 8, 16, 32, 64, 128, 256 }; + +static PhysicalPageState getPhysicalPageState(FrFTL* ftl, + uint16_t physicalPageNo) +{ + uint32_t idx = physicalPageNo >> 4; + uint32_t result = + (ftl->physicalPageState[idx] >> ((physicalPageNo & 0xf) * 2)); + return (PhysicalPageState)(result & 0x3); +} + +static void setPhysicalPageState(FrFTL* ftl, uint16_t physicalPageNo, + PhysicalPageState state) +{ + uint32_t idx = physicalPageNo >> 4; + uint32_t mask = 0x3 << ((physicalPageNo & 0xf) * 2); + ftl->physicalPageState[idx] &= ~mask; + ftl->physicalPageState[idx] |= + ((state & 0x3) << ((physicalPageNo & 0xf) * 2)); +} + + +static const uint16_t crc16_ccitt_start = 0xFFFF; + +static inline uint16_t crc16_x25_ccitt(const void* buf, uint32_t len) { + return crc16(CRC_1021, (const uint8_t*)buf, len, crc16_ccitt_start); +} + +static inline uint16_t calcCRC(TransTableHeader* header) +{ + header->padding = 0xffff; + return crc16_x25_ccitt(header, sizeof(TransTableHeader) - 2); +} + +static void resolveUnknownState(FrFTL* ftl, uint16_t count) +{ + if (ftl->physicalPageStateResolved) { + return; + } + + uint16_t idx = ftl->writeFrontier; + bool earlyEnd = false; + + const FrFTLOps* cb = ftl->callbacks; + for (uint16_t i = 0; i < ftl->physicalPageCount; i++) { + if (getPhysicalPageState(ftl, idx) == UNKNOWN) { + PhysicalPageState state = + cb->isFlashErased(idx * PAGE_SIZE) ? ERASED : ERASE_REQUIRED; + setPhysicalPageState(ftl, idx, state); + count--; + if (count == 0) { + earlyEnd = true; + break; + } + } + idx++; + if (idx >= ftl->physicalPageCount) { + idx = 0; + } + } + if (!earlyEnd) { + ftl->physicalPageStateResolved = true; + } +} + +static PageBuffer* findPageFromHashTable(FrFTL* ftl, uint16_t physicalPageNo) +{ + PageBuffer** hashTable = (PageBuffer**) ftl->hashTable; + uint16_t hash = physicalPageNo % ftl->pageBufferSize; + PageBuffer* lastBuffer = nullptr; + PageBuffer* buffer = hashTable[hash]; + while (buffer) { + if (buffer->physicalPageNo == physicalPageNo) { + // Update search order with LRU + if (lastBuffer) { + lastBuffer->hashNext = buffer->hashNext; + buffer->hashNext = hashTable[hash]; + hashTable[hash] = buffer; + } + return buffer; + } + lastBuffer = buffer; + buffer = buffer->hashNext; + } + + return nullptr; +} + +static void removePageFromHashTable(FrFTL* ftl, uint16_t physicalPageNo) +{ + PageBuffer** hashTable = (PageBuffer**) ftl->hashTable; + uint16_t hash = physicalPageNo % ftl->pageBufferSize; + PageBuffer* lastBuffer = nullptr; + PageBuffer* buffer = hashTable[hash]; + while (buffer) { + if (buffer->physicalPageNo == physicalPageNo) { + if (lastBuffer == nullptr) { + hashTable[hash] = buffer->hashNext; + } else { + lastBuffer->hashNext = buffer->hashNext; + } + buffer->hashNext = nullptr; + return; + } + lastBuffer = buffer; + buffer = buffer->hashNext; + } +} + +static void addPageToHashTable(FrFTL* ftl, PageBuffer* buffer) +{ + PageBuffer** hashTable = (PageBuffer**) ftl->hashTable; + uint16_t hash = buffer->physicalPageNo % ftl->pageBufferSize; + buffer->hashNext = hashTable[hash]; + hashTable[hash] = buffer; +} + +static void movePageBufferToLRUHead(FrFTL* ftl, PageBuffer* buffer) +{ + // Take buffer out from the linked list + if (buffer->lruPrev) { + buffer->lruPrev->lruNext = buffer->lruNext; + } else { + // Already is head, done + return; + } + + if (buffer->lruNext) { + buffer->lruNext->lruPrev = buffer->lruPrev; + } else { + // Tail detected, need to update tail pointer + ftl->bufferTail = buffer->lruPrev; + } + + // Put buffer to buffer head + ((PageBuffer*) ftl->bufferHead)->lruPrev = buffer; + buffer->lruNext = (PageBuffer*) ftl->bufferHead; + buffer->lruPrev = nullptr; + ftl->bufferHead = buffer; +} + +static PageBuffer* rawFindReplacableBuffer(FrFTL* ftl) +{ + PageBuffer* buffer = (PageBuffer*) ftl->bufferTail; + while (buffer && buffer->lock == LOCKED) { + buffer = buffer->lruPrev; + } + + return buffer; +} + +static void movePageBufferToLRUTail(FrFTL* ftl, PageBuffer* buffer) +{ + // Take buffer out from the linked list + if (buffer->lruNext) { + buffer->lruNext->lruPrev = buffer->lruPrev; + } else { + // Already is tail, done + return; + } + + if (buffer->lruPrev) { + buffer->lruPrev->lruNext = buffer->lruNext; + } else { + // Head detected, need to update head pointer + ftl->bufferHead = buffer->lruNext; + } + + // Put buffer to buffer tail + ((PageBuffer*) ftl->bufferTail)->lruNext = buffer; + buffer->lruPrev = (PageBuffer*) ftl->bufferTail; + buffer->lruNext = nullptr; + ftl->bufferTail = buffer; +} + +static PageBuffer* findReplacableBuffer(FrFTL* ftl) +{ + PageBuffer* buffer = rawFindReplacableBuffer(ftl); + if (!buffer) + { + // All buffers are locked, flush lock pages + if (!ftlSync(ftl)) { + return nullptr; + } + + // Try again + buffer = rawFindReplacableBuffer(ftl); + } + + if (buffer) { + // Invalidate buffer from hash table + if (buffer->physicalPageNo != 0xffff) { + removePageFromHashTable(ftl, buffer->physicalPageNo); + buffer->physicalPageNo = 0xffff; + } + + // Update LRU + movePageBufferToLRUHead(ftl, buffer); + } + + return buffer; +} + +static PageBuffer* findPhysicalPageInBuffer(FrFTL* ftl, uint16_t physicalPageNo) +{ + PageBuffer* buffer = findPageFromHashTable(ftl, physicalPageNo); + if (buffer) { + // Update LRU + movePageBufferToLRUHead(ftl, buffer); + } + + return buffer; +} + +static PageBuffer* loadPhysicalPageInBuffer(FrFTL* ftl, uint16_t logicalPageNo, + uint16_t physicalPageNo) +{ + // Find page in buffer + PageBuffer* currentBuffer = findPhysicalPageInBuffer(ftl, physicalPageNo); + if (currentBuffer == nullptr) { + // Page not in buffer, load page in buffer + if ((currentBuffer = findReplacableBuffer(ftl)) == nullptr) { + return nullptr; // BUG + }; + + const FrFTLOps* cb = ftl->callbacks; + if (!cb->flashRead(physicalPageNo * PAGE_SIZE, currentBuffer->page.data, + PAGE_SIZE)) { + return nullptr; + } + currentBuffer->logicalPageNo = logicalPageNo; + currentBuffer->physicalPageNo = physicalPageNo; + currentBuffer->lock = UNLOCKED; + currentBuffer->sectorProgramRequired = 0; + currentBuffer->pMode = NONE; + + // Add new page to hash table + addPageToHashTable(ftl, currentBuffer); + } + + return currentBuffer; +} + +static PageBuffer* initPhysicalPageInBuffer(FrFTL* ftl, uint16_t logicalPageNo, + uint16_t physicalPageNo) +{ + // Find page in buffer + PageBuffer* currentBuffer = findPhysicalPageInBuffer(ftl, physicalPageNo); + if (currentBuffer == nullptr) { + // Page not in buffer, init page in buffer + if ((currentBuffer = findReplacableBuffer(ftl)) == nullptr) { + return nullptr; // BUG + }; + currentBuffer->logicalPageNo = logicalPageNo; + currentBuffer->physicalPageNo = physicalPageNo; + currentBuffer->lock = LOCKED; + currentBuffer->sectorProgramRequired = 0; + currentBuffer->pMode = ERASE_PROGRAM; + memset(currentBuffer->page.data, 0xff, PAGE_SIZE); + + // Add new page to hash table + addPageToHashTable(ftl, currentBuffer); + } + + return currentBuffer; +} + +static bool hasFreeBuffers(FrFTL* ftl, uint16_t bufferCount) +{ + uint16_t freeBufferFound = 0; + PageBuffer* pageBuffer = (PageBuffer*) ftl->bufferHead; + while (pageBuffer != nullptr) + { + if (pageBuffer->lock == UNLOCKED) { + freeBufferFound++; + if (freeBufferFound >= bufferCount) { + return true; + } + } + pageBuffer = pageBuffer->lruNext; + } + + return false; +} + +static bool readPhysicalSector(FrFTL* ftl, uint8_t* buffer, + uint16_t logicalPageNo, uint16_t physicalPageNo, + uint8_t pageSectorNo) +{ + PageBuffer* pageBuffer = loadPhysicalPageInBuffer(ftl, logicalPageNo, physicalPageNo); + if (!pageBuffer) return false; + + memcpy(buffer, pageBuffer->page.data + pageSectorNo * SECTOR_SIZE, SECTOR_SIZE); + return true; +} + +static bool readPhysicalPageInfo(FrFTL* ftl, PageInfo* pageInfo, + uint16_t logicalPageNo, + uint16_t physicalPageNo, uint16_t recordNo) +{ + PageBuffer* pageBuffer = loadPhysicalPageInBuffer(ftl, logicalPageNo, physicalPageNo); + if (!pageBuffer) return false; + + // Page found in buffer + const TransTable* tt = &pageBuffer->page.tt; + pageInfo->physicalPageNo = tt->physicalPageNo[recordNo]; + pageInfo->sectStatus = tt->sectStatus[recordNo]; + return true; +} + +static bool readPageInfo(FrFTL* ftl, PageInfo* pageInfo, uint16_t logicalPageNo) +{ + if (logicalPageNo < TT_RECORDS_PER_PAGE) { + // Read from master TT + return readPhysicalPageInfo(ftl, pageInfo, 0, ftl->mttPhysicalPageNo, + logicalPageNo); + } + + // Lookup from secondary TT from master TT + PageInfo secondaryTTInfo; + uint16_t sttLogicalPageNo = logicalPageNo / TT_RECORDS_PER_PAGE; + if (!readPhysicalPageInfo(ftl, &secondaryTTInfo, 0, ftl->mttPhysicalPageNo, + sttLogicalPageNo)) { + return false; + } + + // Read from secondary TT + return readPhysicalPageInfo(ftl, pageInfo, sttLogicalPageNo, + secondaryTTInfo.physicalPageNo, + logicalPageNo % TT_RECORDS_PER_PAGE); +} + +static bool updatePhysicalPageInfo(FrFTL* ftl, PageInfo* pageInfo, + uint16_t logicalPageNo, + uint16_t physicalPageNo, uint16_t recordNo) +{ + PageBuffer* pageBuffer = + loadPhysicalPageInBuffer(ftl, logicalPageNo, physicalPageNo); + if (!pageBuffer) { + return false; + } + + // Update info, need to lock and ensure update + pageBuffer->lock = LOCKED; + if (pageBuffer->pMode == NONE) { + pageBuffer->pMode = PROGRAM; + } + + TransTable* tt = &pageBuffer->page.tt; + tt->physicalPageNo[recordNo] = pageInfo->physicalPageNo; + tt->sectStatus[recordNo] = pageInfo->sectStatus; + + return true; +} + +static bool updatePageInfo(FrFTL* ftl, PageInfo* pageInfo, + uint16_t logicalPageNo) +{ + if (logicalPageNo < TT_RECORDS_PER_PAGE) { + // Update to master TT + return updatePhysicalPageInfo(ftl, pageInfo, 0, ftl->mttPhysicalPageNo, + logicalPageNo); + } else { + // Lookup from secondary TT from master TT + PageInfo secondaryTTInfo; + uint16_t sttLogicalPageNo = logicalPageNo / TT_RECORDS_PER_PAGE; + if (!readPhysicalPageInfo(ftl, &secondaryTTInfo, 0, ftl->mttPhysicalPageNo, + sttLogicalPageNo)) { + return false; + } + + // Program to secondary TT + return updatePhysicalPageInfo(ftl, pageInfo, sttLogicalPageNo, + secondaryTTInfo.physicalPageNo, + logicalPageNo % TT_RECORDS_PER_PAGE); + } +} + +static uint16_t allocatePhysicalPage(FrFTL* ftl) +{ + uint16_t lookupCount = 0; + while (getPhysicalPageState(ftl, ftl->writeFrontier) == USED) { + ftl->writeFrontier++; + if (ftl->writeFrontier >= ftl->physicalPageCount) { + ftl->writeFrontier = 0; + } + lookupCount++; + if (lookupCount > ftl->physicalPageCount) { + return 0xffff; // BUG + } + } + + uint16_t physicalPageNo = ftl->writeFrontier++; + if (ftl->writeFrontier >= ftl->physicalPageCount) { + ftl->writeFrontier = 0; + } + + return physicalPageNo; +} + +static bool programPage(FrFTL* ftl, PageBuffer* buffer, bool doErase) +{ + const FrFTLOps* cb = ftl->callbacks; + uint32_t pageAddr = buffer->physicalPageNo * PAGE_SIZE; + + if (doErase && getPhysicalPageState(ftl, buffer->physicalPageNo) != ERASED) { + // Do erase on the fly + if (!cb->flashErase(pageAddr)) { + return false; + } + } + + if (buffer->logicalPageNo < ftl->ttPageCount) { + buffer->sectorProgramRequired = 0xff; + } + + // Sector by sector programming: + // As flash requires 256 bytes per program command, it will be more efficient to program by sector + uint8_t* dataPtr = buffer->page.data; + for (uint8_t i = 0; i < SECTORS_PER_PAGE; i++) + { + uint8_t sectMask = 1 << i; + if ((buffer->sectorProgramRequired & sectMask) != 0) { + if (!cb->flashProgram(pageAddr, dataPtr, SECTOR_SIZE)) + { + return false; + } + } + pageAddr += SECTOR_SIZE; + dataPtr += SECTOR_SIZE; + } + + return true; +} + +static bool programPageInBuffer(FrFTL* ftl, PageBuffer* buffer) +{ + uint16_t oldPhysicalPageNo; + + switch (buffer->pMode) { + case PROGRAM: + // Program only + if (!programPage(ftl, buffer, false)) { + return false; + } + setPhysicalPageState(ftl, buffer->physicalPageNo, USED); + if (!findPageFromHashTable(ftl, buffer->physicalPageNo)) { + addPageToHashTable(ftl, buffer); + } + break; + case ERASE_PROGRAM: + if (!programPage(ftl, buffer, true)) { + return false; + } + setPhysicalPageState(ftl, buffer->physicalPageNo, USED); + if (!findPageFromHashTable(ftl, buffer->physicalPageNo)) { + addPageToHashTable(ftl, buffer); + } + break; + case RELOCATE_ERASE_PROGRAM: + // Reprogram + oldPhysicalPageNo = buffer->physicalPageNo; + setPhysicalPageState(ftl, oldPhysicalPageNo, ERASE_REQUIRED); + removePageFromHashTable(ftl, oldPhysicalPageNo); + buffer->physicalPageNo = allocatePhysicalPage(ftl); + if (buffer->physicalPageNo == 0xffff) { + return false; + } + + if (buffer->logicalPageNo < ftl->ttPageCount) { + if (buffer->logicalPageNo == 0) { + // MTT need update physicalPageNo + buffer->page.tt.physicalPageNo[0] = buffer->physicalPageNo; + } + + // TT page, need update serial and CRC + buffer->page.tt.header.serial++; + buffer->page.tt.header.crc16 = calcCRC(&buffer->page.tt.header); + } + + if (!programPage(ftl, buffer, true)) { + return false; + } + + setPhysicalPageState(ftl, buffer->physicalPageNo, USED); + addPageToHashTable(ftl, buffer); + if (buffer->logicalPageNo == 0) { + // MTT page, need update mttPhysicalPageNo + ftl->mttPhysicalPageNo = buffer->physicalPageNo; + } + break; + + default: + break; + } + + return true; +} + +static bool lockTTPages(FrFTL* ftl, uint16_t logicalPageNo) +{ + // Read TT pages and lock it for later update + PageBuffer* ttBuffer; + PageInfo ttPageInfo; + uint16_t ttPageNo = logicalPageNo / TT_RECORDS_PER_PAGE; + if (!readPageInfo(ftl, &ttPageInfo, ttPageNo)) { + return false; + } + ttBuffer = + loadPhysicalPageInBuffer(ftl, ttPageNo, ttPageInfo.physicalPageNo); + ttBuffer->lock = LOCKED; + ttBuffer->pMode = RELOCATE_ERASE_PROGRAM; + if (ttPageNo > 0) { + // TT page not MTT page, need to lock MTT page as well + ttBuffer = loadPhysicalPageInBuffer(ftl, 0, ftl->mttPhysicalPageNo); + ttBuffer->lock = LOCKED; + ttBuffer->pMode = RELOCATE_ERASE_PROGRAM; + } + return true; +} + +bool ftlSync(FrFTL* ftl) +{ + PageBuffer* pageBuffer = ((PageBuffer*)(ftl->pageBuffer)); + + // First program data pages + for (uint16_t i = 0; i < ftl->pageBufferSize; i++) { + PageBuffer* currentBuffer = pageBuffer + i; + if (currentBuffer->lock == LOCKED) { + if (currentBuffer->logicalPageNo >= ftl->ttPageCount) { + if (!programPageInBuffer(ftl, currentBuffer)) { + return false; + } + + // Update PageInfo in TT pages + PageInfo pageInfo; + if (!readPageInfo(ftl, &pageInfo, currentBuffer->logicalPageNo)) { + return false; + } + pageInfo.physicalPageNo = currentBuffer->physicalPageNo; + if (!updatePageInfo(ftl, &pageInfo, currentBuffer->logicalPageNo)) { + return false; + } + + // Unlock buffer + currentBuffer->lock = UNLOCKED; + currentBuffer->sectorProgramRequired = 0; + currentBuffer->pMode = NONE; + } + } + } + + // Second program STT pages + PageBuffer* mttBuffer = + loadPhysicalPageInBuffer(ftl, 0, ftl->mttPhysicalPageNo); + if (!mttBuffer) { + return false; + } + for (uint16_t i = 0; i < ftl->pageBufferSize; i++) { + PageBuffer* currentBuffer = pageBuffer + i; + if (currentBuffer->lock == LOCKED) { + int16_t logicalPageNo = currentBuffer->logicalPageNo; + if (logicalPageNo > 0 && + logicalPageNo < ftl->ttPageCount) { + if (!programPageInBuffer(ftl, currentBuffer)) { + return false; + } + + // Update PageInfo in MTT page + TransTable* tt = &mttBuffer->page.tt; + tt->physicalPageNo[logicalPageNo] = currentBuffer->physicalPageNo; + + // Unlock buffer + currentBuffer->lock = UNLOCKED; + currentBuffer->pMode = NONE; + } + } + } + + // Finally program MTT page + if (mttBuffer->lock == LOCKED) { + if (!programPageInBuffer(ftl, mttBuffer)) { + return false; + } + + // Unlock buffer + mttBuffer->lock = UNLOCKED; + mttBuffer->pMode = NONE; + } + + return true; +} + +bool ftlWrite(FrFTL* ftl, uint32_t startSectorNo, uint32_t noOfSectors, + const uint8_t* buf) +{ + resolveUnknownState(ftl, ftl->ttPageCount); + if (startSectorNo + noOfSectors > ftl->usableSectorCount) { + return false; + } + + uint32_t sectorNo = startSectorNo; + while (noOfSectors > 0) { + // Max no. of sectors need to be rewritten is 3, + // need to ensure has enough free buffers + if (!hasFreeBuffers(ftl, 3)) { + // Flush the buffers first if free space is not found + if (!ftlSync(ftl)) { + return false; + } + } + + uint16_t logicalPageNo = sectorNo / SECTORS_PER_PAGE + ftl->ttPageCount; + uint8_t pageSectorNo = sectorNo % SECTORS_PER_PAGE; + + // Read page info + PageInfo pageInfo; + readPageInfo(ftl, &pageInfo, logicalPageNo); + PageBuffer* dataBuffer; + + // Allocate new physical page for uninitialized logical page + if (pageInfo.physicalPageNo == 0xffff) { + // Need allocate physical page + if ((pageInfo.physicalPageNo = allocatePhysicalPage(ftl)) == 0xffff) { + return false; + } + + // Init page in buffer, locked for delayed program + dataBuffer = initPhysicalPageInBuffer(ftl, logicalPageNo, pageInfo.physicalPageNo); + if (!dataBuffer) { + return false; + } + + pageInfo.sectStatus = 0xff; + + if (!updatePageInfo(ftl, &pageInfo, logicalPageNo)) { + return false; + } + } else { + dataBuffer = + loadPhysicalPageInBuffer(ftl, logicalPageNo, pageInfo.physicalPageNo); + if (!dataBuffer) { + return false; + } + } + + uint8_t sectMask = 1 << pageSectorNo; + if ((pageInfo.sectStatus & sectMask) != 0) { + // Sector never write, append information + pageInfo.sectStatus &= ~sectMask; + if (!updatePageInfo(ftl, &pageInfo, logicalPageNo)) { + return false; + } + + // Update sector, locked for delayed program + dataBuffer->lock = LOCKED; + if (dataBuffer->pMode == NONE) { + dataBuffer->pMode = PROGRAM; + } + memcpy(dataBuffer->page.data + pageSectorNo * SECTOR_SIZE, buf, + SECTOR_SIZE); + dataBuffer->sectorProgramRequired |= sectMask; + } else { + // Sector already written, use replace write + // Lock data page for delayed update with reprogram + dataBuffer->lock = LOCKED; + dataBuffer->pMode = RELOCATE_ERASE_PROGRAM; + memcpy(dataBuffer->page.data + pageSectorNo * SECTOR_SIZE, buf, + SECTOR_SIZE); + dataBuffer->sectorProgramRequired = ~pageInfo.sectStatus; + + if (!lockTTPages(ftl, logicalPageNo)) { + return false; + } + } + + noOfSectors--; + sectorNo++; + buf += SECTOR_SIZE; + } + + return true; +} + +bool ftlRead(FrFTL* ftl, uint32_t sectorNo, uint8_t* buffer) +{ + // doGC(ftl, ftl->ttPageCount, 1); + if (sectorNo >= ftl->usableSectorCount) { + return false; + } + + uint16_t logicalPageNo = sectorNo / SECTORS_PER_PAGE + ftl->ttPageCount; + uint8_t pageSectorNo = sectorNo % SECTORS_PER_PAGE; + + // Read page info + PageInfo pageInfo; + readPageInfo(ftl, &pageInfo, logicalPageNo); + + // Check if sector written before + uint8_t sectMask = 1 << pageSectorNo; + if ((pageInfo.sectStatus & sectMask) != 0) { + // Sector never write, return init content + memset(buffer, 0xff, SECTOR_SIZE); + return true; + } + + return readPhysicalSector(ftl, buffer, logicalPageNo, + pageInfo.physicalPageNo, pageSectorNo); +} + +bool ftlTrim(FrFTL* ftl, uint32_t startSectorNo, uint32_t noOfSectors) +{ + resolveUnknownState(ftl, ftl->ttPageCount); + if (startSectorNo + noOfSectors > ftl->usableSectorCount) { + return false; + } + + uint32_t sectorNo = startSectorNo; + while (noOfSectors > 0) { + // Max no. of sectors need to be rewritten is 3, + // need to ensure has enough free buffers + if (!hasFreeBuffers(ftl, 3)) { + // Flush the buffers first if free space is not found + if (!ftlSync(ftl)) { + return false; + } + } + + uint16_t logicalPageNo = sectorNo / SECTORS_PER_PAGE + ftl->ttPageCount; + uint8_t pageSectorNo = sectorNo % SECTORS_PER_PAGE; + + // Read page info + PageInfo pageInfo; + readPageInfo(ftl, &pageInfo, logicalPageNo); + + // Check if physical page in used + if (pageInfo.physicalPageNo != 0xffff) { + uint8_t sectMask = 1 << pageSectorNo; + if ((pageInfo.sectStatus & sectMask) == 0) { + // Sector used, free it + PageBuffer* dataBuffer = loadPhysicalPageInBuffer( + ftl, logicalPageNo, pageInfo.physicalPageNo); + if (!dataBuffer) { + return false; + } + + pageInfo.sectStatus |= sectMask; + if (pageInfo.sectStatus == 0xff) { + // Free whole page + setPhysicalPageState(ftl, pageInfo.physicalPageNo, ERASE_REQUIRED); + removePageFromHashTable(ftl, pageInfo.physicalPageNo); + pageInfo.physicalPageNo = 0xffff; // Invalidate page info + dataBuffer->physicalPageNo = 0xffff; // Invalidate buffer + dataBuffer->lock = UNLOCKED; + + // Move freed page buffer to LRU tail + movePageBufferToLRUTail(ftl, dataBuffer); + + } else { + // Locked for delayed relocate, fill and program + dataBuffer->lock = LOCKED; + dataBuffer->pMode = RELOCATE_ERASE_PROGRAM; + dataBuffer->sectorProgramRequired = ~pageInfo.sectStatus; + } + + // Update page info + if (!updatePageInfo(ftl, &pageInfo, logicalPageNo)) { + return false; + } + + if (!lockTTPages(ftl, logicalPageNo)) { + return false; + } + } + } + noOfSectors--; + sectorNo++; + } + + return true; +} + +static void initPageBuffer(FrFTL* ftl) +{ + // Init page buffer + uint32_t bufferSize = sizeof(PageBuffer) * ftl->pageBufferSize; + ftl->memoryUsed += bufferSize; + ftl->pageBuffer = malloc(bufferSize); + ftl->bufferHead = ftl->pageBuffer; + + for (uint16_t i = 0; i < ftl->pageBufferSize; i++) { + PageBuffer* currentBuffer = ((PageBuffer*)ftl->pageBuffer) + i; + if (i == 0) { + // First buffer + currentBuffer->lruPrev = nullptr; + } else { + currentBuffer->lruPrev = ((PageBuffer*)ftl->pageBuffer) + i - 1; + } + if (i == ftl->pageBufferSize - 1) { + // Last buffer + currentBuffer->lruNext = nullptr; + ftl->bufferTail = currentBuffer; + } else { + currentBuffer->lruNext = ((PageBuffer*)ftl->pageBuffer) + i + 1; + } + currentBuffer->hashNext = nullptr; + currentBuffer->logicalPageNo = 0xffff; + currentBuffer->physicalPageNo = 0xffff; + currentBuffer->lock = UNLOCKED; + currentBuffer->pMode = NONE; + } + + // Init hash table + bufferSize = sizeof(PageBuffer*) * ftl->pageBufferSize; + ftl->memoryUsed += bufferSize; + ftl->hashTable = malloc(bufferSize); + memset(ftl->hashTable, 0, bufferSize); +} + +static void initTransTablePage(TransTable* tt, uint32_t logicalPageNo) +{ + memset(tt, 0xff, sizeof(TransTable)); + TransTableHeader* header = &tt->header; + header->magicStart = TT_PAGE_MAGIC; + header->logicalPageNo = logicalPageNo; + header->serial = 1; + header->crc16 = calcCRC(header); +} + +static void updateTransTablePage(TransTable* tt, uint32_t logicalPageNo) +{ + TransTableHeader* header = &tt->header; + header->logicalPageNo = logicalPageNo; + header->crc16 = calcCRC(header); +} + +void createFTL(FrFTL* ftl) +{ + // Resolve the first few blocks for proper startup + ftl->writeFrontier = 0; + resolveUnknownState(ftl, ftl->pageBufferSize); + + uint16_t i = 1; + TransTable tt; + initTransTablePage(&tt, i); + + uint32_t addr = PAGE_SIZE; + const FrFTLOps* cb = ftl->callbacks; + do { + if (getPhysicalPageState(ftl, i) != ERASED) { + cb->flashErase(addr); + } + cb->flashProgram(addr, (const uint8_t*)&tt, PAGE_SIZE); + setPhysicalPageState(ftl, i, USED); + + i += 1; + addr += PAGE_SIZE; + updateTransTablePage(&tt, i); + + } while(i < ftl->ttPageCount); + + i = 0; + initTransTablePage(&tt, i); + do { + tt.physicalPageNo[i] = i; + tt.sectStatus[i] = 0; + } while(++i < ftl->ttPageCount); + + if (getPhysicalPageState(ftl, 0) != ERASED) { + cb->flashErase(0); + } + + cb->flashProgram(0, (const uint8_t*)&tt, PAGE_SIZE); + setPhysicalPageState(ftl, 0, USED); + + ftl->writeFrontier = ftl->ttPageCount; +} + +static bool loadFTL(FrFTL* ftl) +{ + // Scan for MTT + uint32_t currentSerial = 0; + uint16_t currentPhysicalMTTPageNo = 0xffff; + + const FrFTLOps* cb = ftl->callbacks; + for (uint16_t i = 0; i < ftl->physicalPageCount; i++) { + TransTableHeader header; + if (!cb->flashRead(i * PAGE_SIZE, (uint8_t*)&header, sizeof(header))) { + return false; + } + if (header.magicStart == TT_PAGE_MAGIC && header.logicalPageNo == 0 && + header.crc16 == calcCRC(&header)) { + // MTT detected + if (header.serial > currentSerial || currentSerial - header.serial > 0x7fffffff) { + // Newer MTT found + currentSerial = header.serial; + currentPhysicalMTTPageNo = i; + } + } + } + + if (currentPhysicalMTTPageNo != 0xffff) { + // MTT found, load data + ftl->mttPhysicalPageNo = currentPhysicalMTTPageNo; + setPhysicalPageState(ftl, currentPhysicalMTTPageNo, USED); + ftl->writeFrontier = currentPhysicalMTTPageNo + 1; + if (ftl->writeFrontier >= ftl->physicalPageCount) { + ftl->writeFrontier = 0; + } + + PageBuffer* mtt = + loadPhysicalPageInBuffer(ftl, 0, currentPhysicalMTTPageNo); + + // Check if MTT valid + if (!mtt || mtt->page.tt.physicalPageNo[0] != currentPhysicalMTTPageNo) { + return false; + } + + PageBuffer* tt = mtt; + + for (uint16_t i = 0; i < ftl->ttPageCount; i++) { + if (i > 0) { + // Not MTT, read TT page and check integrity + uint16_t ttPhysicalPageNo = mtt->page.tt.physicalPageNo[i]; + + // Check wrong range of physical page no. + if (ttPhysicalPageNo >= ftl->physicalPageCount) { + return false; + } + + // Load TT page + tt = loadPhysicalPageInBuffer(ftl, i, ttPhysicalPageNo); + if (!tt) { + return false; + } + + // Check if TT valid + if (tt->page.tt.header.magicStart != TT_PAGE_MAGIC || + tt->page.tt.header.crc16 != calcCRC(&tt->page.tt.header)) { + return false; + } + } + + // Mark used page + for (uint16_t j = 0; j < TT_RECORDS_PER_PAGE; j++) { + uint16_t currentPhysicalPageNo = tt->page.tt.physicalPageNo[j]; + if (currentPhysicalPageNo != 0xffff) { + // Check wrong range of physical page no. + if (currentPhysicalPageNo >= ftl->physicalPageCount) { + return false; + } + // Used page + setPhysicalPageState(ftl, currentPhysicalPageNo, USED); + } + } + } + + // Walk forward to ensure some pages are resolved + resolveUnknownState(ftl, ftl->pageBufferSize); + return true; + } + + return false; +} + +bool ftlInit(FrFTL* ftl, const FrFTLOps* cb, uint16_t flashSizeInMB) +{ + // Check flash size + bool found = false; + for (uint8_t i = 0; i < sizeof(supportedFlashSizes); i++) { + if (flashSizeInMB == supportedFlashSizes[i]) { + found = true; + break; + } + } + if (!found) { + return false; + } + + memset(ftl, 0, sizeof(FrFTL)); + ftl->callbacks = cb; + ftl->mttPhysicalPageNo = 0; + ftl->physicalPageCount = flashSizeInMB > 128 ? 65535 : flashSizeInMB * 1024 * 1024 / PAGE_SIZE; + ftl->ttPageCount = ftl->physicalPageCount / TT_RECORDS_PER_PAGE + (ftl->physicalPageCount % TT_RECORDS_PER_PAGE > 0 ? 1 : 0); + ftl->usableSectorCount = + (ftl->physicalPageCount - ftl->ttPageCount * RESERVED_PAGES_MULTIPLIER) * + SECTORS_PER_PAGE; + uint32_t stateSize = + ftl->physicalPageCount / 16 + (ftl->physicalPageCount % 16 > 0 ? 1 : 0); + ftl->physicalPageState = (uint32_t*)calloc(stateSize, sizeof(uint32_t)); + ftl->physicalPageStateResolved = false; + ftl->memoryUsed += stateSize * sizeof(uint32_t); + ftl->pageBufferSize = ftl->ttPageCount * BUFFER_SIZE_MULTIPLIER; + if (ftl->pageBufferSize > MAX_BUFFER_SIZE) { + ftl->pageBufferSize = MAX_BUFFER_SIZE; + } + initPageBuffer(ftl); + + if (!loadFTL(ftl)) { + // Need reset physical page state before create + memset(ftl->physicalPageState, 0, stateSize * sizeof(uint32_t)); + createFTL(ftl); + } + return true; +} + +void ftlDeInit(FrFTL* ftl) +{ + free(ftl->pageBuffer); + free(ftl->physicalPageState); + free(ftl->hashTable); +} diff --git a/radio/src/drivers/frftl.h b/radio/src/drivers/frftl.h new file mode 100644 index 000000000..28ad44f5b --- /dev/null +++ b/radio/src/drivers/frftl.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) EdgeTX + * + * Authors: + * Dr. Richard Li + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + */ + +/* + * FrFTL - Flash Resident Flash Translation Layer + * + * This is a FTL designed for NOR flash, logical to physical mapping uses 2 layers + * of translation tables all resident in flash. It comes with mechanisms to ensure + * the integrity of the data in previous state when power out occurs in the middle + * of flash programming. + * + * It can be used to back the FatFS library by ChaN and included the support of + * CTRL_SYNC and CTRL_TRIM functions for best performance. + * + */ + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + bool (*flashRead)(uint32_t addr, uint8_t* buf, uint32_t len); + bool (*flashProgram)(uint32_t addr, const uint8_t* buf, uint32_t len); + bool (*flashErase)(uint32_t addr); + bool (*isFlashErased)(uint32_t addr); +} FrFTLOps; + +typedef struct { + const FrFTLOps* callbacks; + uint32_t mttPhysicalPageNo; + uint16_t physicalPageCount; + uint8_t ttPageCount; + uint32_t usableSectorCount; + uint16_t writeFrontier; + uint32_t* physicalPageState; + bool physicalPageStateResolved; + uint16_t pageBufferSize; + void *pageBuffer; + void* bufferHead; // LRU most used + void* bufferTail; // LRU least used + void* hashTable; // Fast cache lookup + uint32_t memoryUsed; +} FrFTL; + +bool ftlInit(FrFTL* ftl, const FrFTLOps* cb, uint16_t flashSizeInMB); +void ftlDeInit(FrFTL* ftl); + +bool ftlWrite(FrFTL* ftl, uint32_t startSectorNo, uint32_t noOfSectors, const uint8_t* buf); +bool ftlRead(FrFTL* ftl, uint32_t sectorNo, uint8_t* buffer); + +bool ftlTrim(FrFTL* ftl, uint32_t startSectorNo, uint32_t noOfSectors); +bool ftlSync(FrFTL* ftl); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/radio/src/hal/fatfs_diskio.cpp b/radio/src/hal/fatfs_diskio.cpp index ed5b544f2..d96b98370 100644 --- a/radio/src/hal/fatfs_diskio.cpp +++ b/radio/src/hal/fatfs_diskio.cpp @@ -58,6 +58,21 @@ int fatfsRegisterDriver(const diskio_driver_t* drv, uint8_t lun) return 1; } +void fatfsUnregisterDrivers() +{ + for (uint8_t i = 0; i < _fatfs_n_drives; i++) { + auto& drive = _fatfs_drives[i]; + if (drive.initialized) { + disk_ioctl(i, CTRL_SYNC, 0); + if (drive.drv->deinit) { + drive.drv->deinit(drive.lun); + } + drive.initialized = false; + } + } + _fatfs_n_drives = 0; +} + const diskio_driver_t* fatfsGetDriver(uint8_t pdrv) { if (pdrv >= _fatfs_n_drives) { diff --git a/radio/src/hal/fatfs_diskio.h b/radio/src/hal/fatfs_diskio.h index 548e66ad3..4866e412a 100644 --- a/radio/src/hal/fatfs_diskio.h +++ b/radio/src/hal/fatfs_diskio.h @@ -28,6 +28,8 @@ struct diskio_driver_t { DSTATUS (*initialize)(BYTE pdrv); + DSTATUS (*deinit)(BYTE pdrv); + DSTATUS (*status)(BYTE pdrv); DRESULT (*read)(BYTE pdrv, BYTE* buff, DWORD sector, UINT count); @@ -40,6 +42,9 @@ struct diskio_driver_t // returns 1 if successful, 0 otherwise int fatfsRegisterDriver(const diskio_driver_t* drv, uint8_t lun); +// gracefully tear down all drivers +void fatfsUnregisterDrivers(); + // returns a pyhsical disk driver or NULL const diskio_driver_t* fatfsGetDriver(uint8_t pdrv); diff --git a/radio/src/hal/storage.cpp b/radio/src/hal/storage.cpp index e5cbbaa5c..039844d46 100644 --- a/radio/src/hal/storage.cpp +++ b/radio/src/hal/storage.cpp @@ -30,6 +30,9 @@ #elif defined(STORAGE_USE_SDCARD_SPI) #include "diskio_spi.h" #define _STORAGE_DRIVER sdcard_spi_driver +#elif defined(STORAGE_USE_SPI_FLASH) + #include "diskio_spi_flash.h" + #define _STORAGE_DRIVER spi_flash_diskio_driver #else #error "No supported storage driver configured" #endif @@ -62,6 +65,11 @@ void storageInit() } } +void storageDeInit() +{ + fatfsUnregisterDrivers(); +} + void storagePreMountHook() { #if defined(DISK_CACHE) diff --git a/radio/src/hal/storage.h b/radio/src/hal/storage.h index 7a018f953..693a6ec79 100644 --- a/radio/src/hal/storage.h +++ b/radio/src/hal/storage.h @@ -26,6 +26,7 @@ // Note: can be used to attach the pyhsical volume driver // to the generic FatFs driver void storageInit(); +void storageDeInit(); // Called before the storage is mounted void storagePreMountHook(); diff --git a/radio/src/targets/common/arm/stm32/bootloader/CMakeLists.txt b/radio/src/targets/common/arm/stm32/bootloader/CMakeLists.txt index f11bbcd9c..4e3ef1831 100644 --- a/radio/src/targets/common/arm/stm32/bootloader/CMakeLists.txt +++ b/radio/src/targets/common/arm/stm32/bootloader/CMakeLists.txt @@ -78,6 +78,11 @@ if(PCB STREQUAL X10 OR PCB STREQUAL X12S OR PCB STREQUAL NV14) ../../../../../thirdparty/libopenui/thirdparty/lz4/lz4.c ../../../../../targets/common/arm/stm32/diskio_sdio.cpp ../../../../../targets/common/arm/stm32/rtc_driver.cpp + ../../../../../targets/common/arm/stm32/diskio_spi_flash.cpp + ../../../../../targets/common/arm/stm32/spi_flash.cpp + ../../../../../targets/common/arm/stm32/stm32_spi.cpp + ../../../../../drivers/frftl.cpp + ../../../../../crc.cpp ../../../../../targets/${TARGET_DIR}/sdram_driver.c ../../../../../targets/${TARGET_DIR}/haptic_driver.cpp ) diff --git a/radio/src/targets/common/arm/stm32/bootloader/bin_files.cpp b/radio/src/targets/common/arm/stm32/bootloader/bin_files.cpp index f37032871..474cfcff0 100644 --- a/radio/src/targets/common/arm/stm32/bootloader/bin_files.cpp +++ b/radio/src/targets/common/arm/stm32/bootloader/bin_files.cpp @@ -41,15 +41,21 @@ UINT BlockCount; void sdInit(void) { - static FATFS fatFS __DMA; - storageInit(); + static FATFS fatFS __DMA; if (f_mount(&fatFS, "", 1) == FR_OK) { f_chdir("/"); } } +void sdDone() +{ + // unmount + f_mount(0, "", 0); + storageDeInit(); +} + FRESULT openBinDir(MemoryType mt) { FRESULT fr = f_chdir(getBinaryPath(mt)); diff --git a/radio/src/targets/common/arm/stm32/bootloader/boot.cpp b/radio/src/targets/common/arm/stm32/bootloader/boot.cpp index 2ba72da76..d2d2635b4 100644 --- a/radio/src/targets/common/arm/stm32/bootloader/boot.cpp +++ b/radio/src/targets/common/arm/stm32/bootloader/boot.cpp @@ -550,11 +550,13 @@ int bootloaderMain() if (state != ST_FLASHING && state != ST_USB) { if (pwrOffPressed()) { + sdDone(); boardOff(); } } if (state == ST_REBOOT) { + sdDone(); #if !defined(SIMU) #if defined(RTC_BACKUP_RAM) rtcInit(); diff --git a/radio/src/targets/common/arm/stm32/diskio_spi_flash.cpp b/radio/src/targets/common/arm/stm32/diskio_spi_flash.cpp new file mode 100644 index 000000000..bdd7db867 --- /dev/null +++ b/radio/src/targets/common/arm/stm32/diskio_spi_flash.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + */ + +#include "diskio_spi_flash.h" +#include "spi_flash.h" + +#if !defined(DISABLE_FLASH_FTL) +#define USE_FLASH_FTL +#endif + +#if defined(USE_FLASH_FTL) +#include "drivers/frftl.h" + +static FrFTL _frftl; + +static bool flashRead(uint32_t addr, uint8_t* buf, uint32_t len) +{ + flashSpiRead(addr, buf, len); + return true; +} + +static bool flashWrite(uint32_t addr, const uint8_t *buf, uint32_t len) +{ + uint32_t pageSize = flashSpiGetPageSize(); + if(len % pageSize != 0) + return false; + + while(len > 0) { + flashSpiWrite(addr, buf, pageSize); + len -= pageSize; + buf += pageSize; + addr += pageSize; + } + + if(len != 0) { + return false; + } + + return true; +} + +static bool flashErase(uint32_t addr) +{ + flashSpiErase(addr); + return true; +} + +static bool isFlashErased(uint32_t addr) +{ + return flashSpiIsErased(addr); +} + +static const FrFTLOps _frftl_cb = { + .flashRead = flashRead, + .flashProgram = flashWrite, + .flashErase = flashErase, + .isFlashErased = isFlashErased, +}; +#endif + +static DSTATUS spi_flash_initialize(BYTE lun) +{ + if (!flashSpiInit()) { + return STA_NOINIT; + } + +#if defined(USE_FLASH_FTL) + int flashSize = flashSpiGetSize(); + int flashSizeMB = flashSize / 1024 / 1024; + + if (!ftlInit(&_frftl, &_frftl_cb, flashSizeMB)) { + return STA_NOINIT; + } +#endif + + return 0; +} + +static DSTATUS spi_flash_status (BYTE lun) +{ + return 0; +} + +static DRESULT spi_flash_read(BYTE lun, BYTE * buff, DWORD sector, UINT count) +{ +#if defined(USE_FLASH_FTL) + while(count) { + + if(!ftlRead(&_frftl, sector, (uint8_t*)buff)) { + return RES_ERROR; + } + + buff += 512; + sector++; + count --; + } +#else + flashSpiRead((uint32_t)sector * 512, buff, count * 512); +#endif + + return RES_OK; +} + +static DRESULT spi_flash_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count) +{ +#if defined(USE_FLASH_FTL) + if (!ftlWrite(&_frftl, sector, count, (uint8_t*)buff)) { + return RES_ERROR; + } +#else + // write in page size + uint32_t address = (uint32_t)sector * 512; + count <<= 1; + while(count) { + if (!flashSpiWrite(address, buff, 256)) { + return RES_ERROR; + } + buff += 256; + address += 256; + count -= 1; + } +#endif + + return RES_OK; +} + +static DRESULT spi_flash_ioctl(BYTE lun, BYTE ctrl, void *buff) +{ + DRESULT res = RES_OK; + + switch (ctrl) { + + case GET_SECTOR_COUNT: /* Get number of sectors on the disk (DWORD) */ +#if defined(USE_FLASH_FTL) + *(DWORD*)buff = _frftl.usableSectorCount; +#else + *(DWORD*)buff = flashSpiGetSize() / 512; +#endif + break; + + case GET_SECTOR_SIZE: /* Get R/W sector size (WORD) */ + *(WORD*)buff = 512; + break; + + case GET_BLOCK_SIZE : /* Get erase block size in unit of sector (DWORD) */ + *(DWORD*)buff = 4096 / 512; + break; + + case CTRL_SYNC: +#if defined(USE_FLASH_FTL) + if (!ftlSync(&_frftl)) { + res = RES_ERROR; + } +#else + flashSpiSync(); +#endif + break; + + case CTRL_TRIM: +#if defined(USE_FLASH_FTL) + if (!ftlTrim(&_frftl, *(DWORD*)buff, 1 + *((DWORD*)buff + 1) - *(DWORD*)buff)) { + res = RES_ERROR; + } +#endif + break; + } + + return res; +} + +const diskio_driver_t spi_flash_diskio_driver = { + .initialize = spi_flash_initialize, + .status = spi_flash_status, + .read = spi_flash_read, + .write = spi_flash_write, + .ioctl = spi_flash_ioctl, +}; diff --git a/radio/src/targets/common/arm/stm32/diskio_spi_flash.h b/radio/src/targets/common/arm/stm32/diskio_spi_flash.h new file mode 100644 index 000000000..9732d6da4 --- /dev/null +++ b/radio/src/targets/common/arm/stm32/diskio_spi_flash.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + */ + +#pragma once + +#include "hal/fatfs_diskio.h" + +extern const diskio_driver_t spi_flash_diskio_driver; diff --git a/radio/src/targets/common/arm/stm32/spi_flash.cpp b/radio/src/targets/common/arm/stm32/spi_flash.cpp new file mode 100644 index 000000000..100ce9559 --- /dev/null +++ b/radio/src/targets/common/arm/stm32/spi_flash.cpp @@ -0,0 +1,378 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + */ + +#include "stm32_spi.h" +#include "delays_driver.h" + +#include "hal.h" + +#define CS_HIGH() stm32_spi_unselect(&_flash_spi) +#define CS_LOW() stm32_spi_select(&_flash_spi) + +#define MIN(a,b) (a < b ? a : b) + +#define FLASH_SECTOR_SIZE 4096 +#define FLASH_SECTOR_MASK (FLASH_SECTOR_SIZE - 1) + +#define FLASH_BLOCK_SIZE 32768 +#define FLASH_BLOCK_MASK (FLASH_BLOCK_SIZE - 1) + +#define FLASH_PAGE_SIZE 256 +#define FLASH_PAGE_MASK (FLASH_PAGE_SIZE - 1) + +#define FLASH_CMD_READ_ID 0x90 +#define FLASH_CMD_READ_JEDEC_ID 0x9f +#define FLASH_CMD_READ_SFDP 0x5a + +#define FLASH_CMD_WRITE 0x02 +#define FLASH_CMD_READ 0x03 + +#define FLASH_CMD_STATUS 0x05 +#define FLASH_CMD_WRITE_ENABLE 0x06 + +#define FLASH_CMD_SECTOR_ERASE 0x20 +#define FLASH_CMD_BLOCK_ERASE 0x52 +#define FLASH_CMD_CHIP_ERASE 0xc7 + +#define FLASH_CMD_EN4B 0xb7 + + +struct SpiFlashDescriptor { + uint16_t id; + uint8_t log2Size; + bool use4BytesAddress; +}; + +// * RadioMaster/Eachine TX16S, RadioKing TX18S and Jumper T18 use GD25Q127C (16 MByte) +// * FlySky PL18, Paladin EV and NV14 use WinBond W25Q64JV (8 MByte) + +// TODO: +// - fallback for flash size discovery (no JEDEC SFDP; see 'pico-bootrom') + + +const stm32_spi_t _flash_spi = { + .SPIx = FLASH_SPI, + .SPI_GPIOx = FLASH_SPI_GPIO, + .SPI_Pins = FLASH_SPI_SCK_GPIO_PIN | FLASH_SPI_MISO_GPIO_PIN | FLASH_SPI_MOSI_GPIO_PIN, + .CS_GPIOx = FLASH_SPI_CS_GPIO, + .CS_Pin = FLASH_SPI_CS_GPIO_PIN, + .DMA = nullptr, + .DMA_Channel = 0, + .txDMA_Stream = 0, + .rxDMA_Stream = 0, +}; + +static SpiFlashDescriptor _flashDescriptor; + +static inline void flash_select() { stm32_spi_select(&_flash_spi); } +static inline void flash_unselect() { stm32_spi_unselect(&_flash_spi); } + +static inline uint8_t flash_rw_byte(uint8_t value) +{ + return stm32_spi_transfer_byte(&_flash_spi, value); +} + +static inline uint8_t flash_read_byte() +{ + return flash_rw_byte(0xFF); +} + +static void flash_rw_bytes(const uint8_t* tx, uint8_t* rx, uint16_t count) +{ + stm32_spi_transfer_bytes(&_flash_spi, tx, rx, count); +} + +static inline void flash_put_cmd_addr(uint8_t cmd, uint32_t addr) +{ + addr |= cmd << 24; + for (int i = 0; i < 4; i++) { + flash_rw_byte(addr >> 24); + addr <<= 8; + } +} + +static inline void flash_put_cmd_4b_addr(uint8_t cmd, uint32_t addr) +{ + flash_rw_byte(cmd); + + for (int i = 0; i < 4; i++) { + flash_rw_byte(addr >> 24); + addr <<= 8; + } +} + +static inline void flash_do_cmd(uint8_t cmd, const uint8_t* tx, uint8_t* rx, uint16_t count) +{ + flash_select(); + flash_rw_byte(cmd); + flash_rw_bytes(tx, rx, count); + flash_unselect(); +} + +static void flash_wait_for_not_busy() +{ + uint8_t status; + do { + flash_do_cmd(FLASH_CMD_STATUS, 0, &status, 1); + } while (status & 0x01); +} + +static void flash_enable_write() +{ + flash_do_cmd(FLASH_CMD_WRITE_ENABLE, 0, 0, 0); +} + +static void flash_read_sfdp(uint32_t addr, uint8_t *rx, uint16_t count) +{ + flash_select(); + flash_put_cmd_addr(FLASH_CMD_READ_SFDP, addr); + flash_read_byte(); // dummy byte + flash_rw_bytes(0, rx, count); + flash_unselect(); +} + +static inline uint32_t bytes_to_u32le(const uint8_t *b) { + return b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24); +} + +static bool flash_read_id(SpiFlashDescriptor* desc) +{ + uint8_t rxbuf[2]; + + flash_select(); + flash_put_cmd_addr(FLASH_CMD_READ_ID, 0); + flash_rw_bytes(0, rxbuf, 2); + flash_unselect(); + + uint16_t id = (rxbuf[0] << 8) | rxbuf[1]; + if (id == 0xFFFF) { + return false; + } + + desc->id = id; + return true; +} + +static bool flash_read_sfdp(SpiFlashDescriptor* desc) +{ + uint8_t rxbuf[16]; + + // check magic signature + flash_read_sfdp(0, rxbuf, 16); + if (rxbuf[0] != 'S' || rxbuf[1] != 'F' || rxbuf[2] != 'D' || rxbuf[3] != 'P') { + return false; + } + + // check JEDEC ID is 0 + if (rxbuf[8] != 0) { + return false; + } + + // read param table + uint32_t param_table_ptr = bytes_to_u32le(rxbuf + 12) & 0xffffffu; + flash_read_sfdp(param_table_ptr, rxbuf, 8); + + // 1st DWORD + uint32_t param_table_dword = bytes_to_u32le(rxbuf); + + if ((param_table_dword & 3) != 1) { + // 4 kilobyte erase is unavailable + return false; + } + + if (((param_table_dword >> 17) & 3) != 0) { + // 4-byte addressing supported + desc->use4BytesAddress = true; + flash_do_cmd(FLASH_CMD_EN4B, 0, 0, 0); + } + + // 2nd DWORD: flash memory density + // - MSB set: array >= 2 Gbit, encoded as log2 of number of bits + // - MSB clear: array < 2 Gbit, encoded as direct bit count + + param_table_dword = bytes_to_u32le(rxbuf + 4); + if (param_table_dword & (1u << 31)) { + param_table_dword &= ~(1u << 31); + } else { + uint32_t ctr = 0; + param_table_dword += 1; + while (param_table_dword >>= 1) + ++ctr; + param_table_dword = ctr; + } + desc->log2Size = param_table_dword - 3; + + return true; +} + +bool flashSpiInit(void) +{ + stm32_spi_init(&_flash_spi); + delay_ms(1); + + if (!flash_read_id(&_flashDescriptor)) { + return false; + } + + if (!flash_read_sfdp(&_flashDescriptor)) { + return false; + } + + return true; +} + +void flashSpiSync() +{ + uint8_t status; + do { + flash_do_cmd(FLASH_CMD_STATUS, 0, &status, 1); + } while (status & 0x01); +} + +uint32_t flashSpiGetSize() +{ + return (1UL << _flashDescriptor.log2Size); +} + +bool flashSpiIsErased(uint32_t address) +{ + // verify sector alignment + if((address & FLASH_SECTOR_MASK) != 0) + return false; + + flash_wait_for_not_busy(); + flash_select(); + + if (_flashDescriptor.use4BytesAddress) { + flash_put_cmd_4b_addr(FLASH_CMD_READ, address); + } else { + flash_put_cmd_addr(FLASH_CMD_READ, address); + } + + bool ret = true; + for(uint32_t i = 0; i < FLASH_SECTOR_SIZE; i++) { + uint8_t byte = flash_rw_byte(0xFF); + if (byte != 0xff) { + ret = false; + break; + } + } + + flash_unselect(); + return ret; +} + +uint32_t flashSpiRead(uint32_t address, uint8_t* data, uint32_t size) +{ + size = MIN(size, (uint32_t)(flashSpiGetSize() - address)); + + flash_wait_for_not_busy(); + flash_select(); + + if (_flashDescriptor.use4BytesAddress) { + flash_put_cmd_4b_addr(FLASH_CMD_READ, address); + } else { + flash_put_cmd_addr(FLASH_CMD_READ, address); + } + flash_rw_bytes(0, data, size); + + flash_unselect(); + return size; +} + +uint32_t flashSpiWrite(uint32_t address, const uint8_t* data, uint32_t size) +{ + size = MIN(size, (uint32_t)(flashSpiGetSize() - address)); + + // verify page alignment and size + if (size != FLASH_PAGE_SIZE || (address & FLASH_PAGE_MASK) != 0) { + return 0; + } + + flash_wait_for_not_busy(); + flash_enable_write(); + + flash_select(); + if (_flashDescriptor.use4BytesAddress) { + flash_put_cmd_4b_addr(FLASH_CMD_WRITE, address); + } else { + flash_put_cmd_addr(FLASH_CMD_WRITE, address); + } + flash_rw_bytes(data, 0, size); + flash_unselect(); + + flash_wait_for_not_busy(); + return size; +} + +int flashSpiErase(uint32_t address) +{ + if((address & FLASH_SECTOR_MASK) != 0) + return -1; + + flash_wait_for_not_busy(); + flash_enable_write(); + + flash_select(); + if (_flashDescriptor.use4BytesAddress) { + flash_put_cmd_4b_addr(FLASH_CMD_SECTOR_ERASE, address); + } else { + flash_put_cmd_addr(FLASH_CMD_SECTOR_ERASE, address); + } + flash_unselect(); + + flash_wait_for_not_busy(); + return 0; +} + +int flashSpiBlockErase(uint32_t address) +{ + // verify block alignment + if((address & FLASH_BLOCK_MASK) != 0) + return -1; + + flash_wait_for_not_busy(); + flash_enable_write(); + + flash_select(); + if (_flashDescriptor.use4BytesAddress) { + flash_put_cmd_4b_addr(FLASH_CMD_BLOCK_ERASE, address); + } else { + flash_put_cmd_addr(FLASH_CMD_BLOCK_ERASE, address); + } + flash_unselect(); + + flash_wait_for_not_busy(); + return 0; +} + +void flashSpiEraseAll() +{ + flash_wait_for_not_busy(); + flash_enable_write(); + flash_do_cmd(FLASH_CMD_CHIP_ERASE, 0, 0, 0); + flash_wait_for_not_busy(); +} + +uint16_t flashSpiGetPageSize() +{ + return FLASH_PAGE_SIZE; +} diff --git a/radio/src/targets/common/arm/stm32/spi_flash.h b/radio/src/targets/common/arm/stm32/spi_flash.h new file mode 100644 index 000000000..935afdffc --- /dev/null +++ b/radio/src/targets/common/arm/stm32/spi_flash.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + */ + +#include +#include + +bool flashSpiInit(); + +uint32_t flashSpiRead(uint32_t address, uint8_t* data, uint32_t size); +uint32_t flashSpiWrite(uint32_t address, const uint8_t* data, uint32_t size); + +uint32_t flashSpiGetSize(); +uint16_t flashSpiGetPageSize(); + +int flashSpiErase(uint32_t address); +bool flashSpiIsErased(uint32_t address); +int flashSpiBlockErase(uint32_t address); + +void flashSpiEraseAll(); +void flashSpiSync(); diff --git a/radio/src/targets/horus/CMakeLists.txt b/radio/src/targets/horus/CMakeLists.txt index 3e3c851b6..7a55b19a0 100644 --- a/radio/src/targets/horus/CMakeLists.txt +++ b/radio/src/targets/horus/CMakeLists.txt @@ -272,6 +272,9 @@ set(FIRMWARE_SRC targets/common/arm/stm32/trainer_driver.cpp targets/common/arm/stm32/ads79xx.cpp targets/common/arm/stm32/diskio_sdio.cpp + targets/common/arm/stm32/spi_flash.cpp + targets/common/arm/stm32/diskio_spi_flash.cpp + drivers/frftl.cpp ) if(BOOTLOADER) diff --git a/radio/src/targets/horus/hal.h b/radio/src/targets/horus/hal.h index 0d1cc8d16..8a78db14e 100644 --- a/radio/src/targets/horus/hal.h +++ b/radio/src/targets/horus/hal.h @@ -281,6 +281,7 @@ #define ADC_EXT_CHANNELS { ADC_CHANNEL_RTC_BATT } #define ADC_DMA DMA2 #define ADC_DMA_CHANNEL LL_DMA_CHANNEL_2 + // TODO: use for SPI1_RX instead? #define ADC_DMA_STREAM LL_DMA_STREAM_0 #define ADC_DMA_STREAM_IRQ DMA2_Stream0_IRQn #define ADC_DMA_STREAM_IRQHandler DMA2_Stream0_IRQHandler @@ -624,7 +625,7 @@ #define SD_PRESENT_GPIO_PIN GPIO_Pin_5 // PC.05 #define SD_PRESENT_LL_GPIO_PIN LL_GPIO_PIN_5 // PC.05 #define SD_SDIO_DMA DMA2 -#define SD_SDIO_DMA_STREAM DMA2_Stream3 +#define SD_SDIO_DMA_STREAM DMA2_Stream3 // or Stream6 #define SD_SDIO_DMA_CHANNEL LL_DMA_CHANNEL_4 #define SD_SDIO_DMA_IRQn DMA2_Stream3_IRQn #define SD_SDIO_DMA_IRQHANDLER DMA2_Stream3_IRQHandler @@ -633,35 +634,49 @@ #define SD_SDIO_TRANSFER_CLK_DIV SD_SDIO_CLK_DIV(24000000) #define STORAGE_USE_SDIO -// EEPROM +// SPI NOR Flash #if defined(PCBX12S) && PCBREV >= 13 - #define EEPROM_RCC_AHB1Periph RCC_AHB1Periph_GPIOA - #define EEPROM_RCC_APB1Periph RCC_APB1Periph_SPI1 - #define EEPROM_SPI_CS_GPIO GPIOA - #define EEPROM_SPI_CS_GPIO_PIN GPIO_Pin_15 // PA.15 - #define EEPROM_SPI_SCK_GPIO GPIOA - #define EEPROM_SPI_SCK_GPIO_PIN GPIO_Pin_5 // PA.05 - #define EEPROM_SPI_SCK_GPIO_PinSource GPIO_PinSource5 - #define EEPROM_SPI_MISO_GPIO GPIOA - #define EEPROM_SPI_MISO_GPIO_PIN GPIO_Pin_6 // PA.06 - #define EEPROM_SPI_MISO_GPIO_PinSource GPIO_PinSource6 - #define EEPROM_SPI_MOSI_GPIO GPIOA - #define EEPROM_SPI_MOSI_GPIO_PIN GPIO_Pin_7 // PA.07 - #define EEPROM_SPI_MOSI_GPIO_PinSource GPIO_PinSource7 + #define FLASH_SPI SPI1 + #define FLASH_SPI_CS_GPIO GPIOA + #define FLASH_SPI_CS_GPIO_PIN LL_GPIO_PIN_15 // PA.15 + #define FLASH_SPI_GPIO GPIOA + #define FLASH_SPI_SCK_GPIO_PIN LL_GPIO_PIN_5 // PA.05 + #define FLASH_SPI_MISO_GPIO_PIN LL_GPIO_PIN_6 // PA.06 + #define FLASH_SPI_MOSI_GPIO_PIN LL_GPIO_PIN_7 // PA.07 + // SPI1_TX: DMA2 Stream 3 (SDIO) / Stream 5 (Ext. module timer) + // #define FLASH_SPI_TX_DMA_CHANNEL DMA_Channel_3 + // #define FLASH_SPI_TX_DMA_STREAM DMA2_Stream3 + // #define FLASH_SPI_TX_DMA_IRQn DMA2_Stream3_IRQn + // #define FLASH_SPI_TX_DMA_IRQHandler DMA2_Stream3_IRQHandler + // #define FLASH_SPI_TX_DMA_FLAG_TC DMA_IT_TCIF3 + // #define FLASH_SPI_TX_DMA_STATUS_REG HISR + // SPI1_RX: DMA2 Stream 0 / Stream 2 + // #define FLASH_SPI_RX_DMA_CHANNEL DMA_Channel_3 + // #define FLASH_SPI_RX_DMA_STREAM DMA2_Stream5 + // #define FLASH_SPI_RX_DMA_IRQn DMA2_Stream5_IRQn + // #define FLASH_SPI_RX_DMA_IRQHandler DMA2_Stream5_IRQHandler + // #define FLASH_SPI_RX_DMA_STATUS_REG HISR + // #define FLASH_SPI_RX_DMA_FLAG_TC DMA_IT_TCIF5 #elif defined(PCBX10) - #define EEPROM_RCC_AHB1Periph RCC_AHB1Periph_GPIOI - #define EEPROM_RCC_APB1Periph RCC_APB1Periph_SPI2 - #define EEPROM_SPI_CS_GPIO GPIOI - #define EEPROM_SPI_CS_GPIO_PIN GPIO_Pin_0 // PI.00 - #define EEPROM_SPI_SCK_GPIO GPIOI - #define EEPROM_SPI_SCK_GPIO_PIN GPIO_Pin_1 // PI.01 - #define EEPROM_SPI_SCK_GPIO_PinSource GPIO_PinSource1 - #define EEPROM_SPI_MISO_GPIO GPIOI - #define EEPROM_SPI_MISO_GPIO_PIN GPIO_Pin_2 // PI.02 - #define EEPROM_SPI_MISO_GPIO_PinSource GPIO_PinSource2 - #define EEPROM_SPI_MOSI_GPIO GPIOI - #define EEPROM_SPI_MOSI_GPIO_PIN GPIO_Pin_3 // PI.03 - #define EEPROM_SPI_MOSI_GPIO_PinSource GPIO_PinSource3 + #define FLASH_SPI SPI2 + #define FLASH_SPI_CS_GPIO GPIOI + #define FLASH_SPI_CS_GPIO_PIN LL_GPIO_PIN_0 // PI.00 + #define FLASH_SPI_GPIO GPIOI + #define FLASH_SPI_SCK_GPIO_PIN LL_GPIO_PIN_1 // PI.01 + #define FLASH_SPI_MISO_GPIO_PIN LL_GPIO_PIN_2 // PI.02 + #define FLASH_SPI_MOSI_GPIO_PIN LL_GPIO_PIN_3 // PI.03 + // #define FLASH_SPI_TX_DMA_CHANNEL DMA_Channel_0 + // #define FLASH_SPI_TX_DMA_STREAM DMA1_Stream4 + // #define FLASH_SPI_TX_DMA_IRQn DMA1_Stream4_IRQn + // #define FLASH_SPI_TX_DMA_IRQHandler DMA1_Stream4_IRQHandler + // #define FLASH_SPI_TX_DMA_FLAG_TC DMA_IT_TCIF4 + // #define FLASH_SPI_TX_DMA_STATUS_REG HISR + // #define FLASH_SPI_RX_DMA_CHANNEL DMA_Channel_0 + // #define FLASH_SPI_RX_DMA_STREAM DMA1_Stream3 + // #define FLASH_SPI_RX_DMA_IRQn DMA1_Stream3_IRQn + // #define FLASH_SPI_RX_DMA_IRQHandler DMA1_Stream3_IRQHandler + // #define FLASH_SPI_RX_DMA_STATUS_REG LISR + // #define FLASH_SPI_RX_DMA_FLAG_TC DMA_IT_TCIF3 #endif // Audio @@ -819,7 +834,7 @@ #define FLYSKY_HALL_SERIAL_USART_IRQn UART4_IRQn #define FLYSKY_HALL_SERIAL_DMA DMA1 #define FLYSKY_HALL_DMA_Stream_RX LL_DMA_STREAM_2 - #define FLYSKY_HALL_DMA_Stream_TX LL_DMA_STREAM_4 + // #define FLYSKY_HALL_DMA_Stream_TX LL_DMA_STREAM_4 #endif diff --git a/radio/src/targets/nv14/CMakeLists.txt b/radio/src/targets/nv14/CMakeLists.txt index 626447b63..af6c92486 100644 --- a/radio/src/targets/nv14/CMakeLists.txt +++ b/radio/src/targets/nv14/CMakeLists.txt @@ -153,6 +153,9 @@ set(FIRMWARE_SRC targets/common/arm/stm32/trainer_driver.cpp targets/common/arm/stm32/pwr_driver.cpp targets/common/arm/stm32/diskio_sdio.cpp + targets/common/arm/stm32/spi_flash.cpp + targets/common/arm/stm32/diskio_spi_flash.cpp + drivers/frftl.cpp targets/common/arm/stm32/vs1053b.cpp ) diff --git a/radio/src/targets/nv14/hal.h b/radio/src/targets/nv14/hal.h index 20115e4be..a737dfa95 100644 --- a/radio/src/targets/nv14/hal.h +++ b/radio/src/targets/nv14/hal.h @@ -307,6 +307,15 @@ #define SD_SDIO_TRANSFER_CLK_DIV SD_SDIO_CLK_DIV(24000000) #define STORAGE_USE_SDIO +// SPI NOR Flash +#define FLASH_SPI SPI6 +#define FLASH_SPI_CS_GPIO GPIOG +#define FLASH_SPI_CS_GPIO_PIN LL_GPIO_PIN_6 // PG.06 +#define FLASH_SPI_GPIO GPIOG +#define FLASH_SPI_SCK_GPIO_PIN LL_GPIO_PIN_13 // PG.13 +#define FLASH_SPI_MISO_GPIO_PIN LL_GPIO_PIN_12 // PG.12 +#define FLASH_SPI_MOSI_GPIO_PIN LL_GPIO_PIN_14 // PG.14 + // SDRAM #define SDRAM_RCC_AHB1Periph (RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOG | RCC_AHB1Periph_GPIOH) #define SDRAM_RCC_AHB3Periph RCC_AHB3Periph_FMC