mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-03-21 06:40:37 +01:00
[ion/device] Clean the external flash init code
This commit is contained in:
@@ -21,6 +21,7 @@ objs += $(addprefix ion/src/device/, \
|
||||
device.o\
|
||||
display.o\
|
||||
events.o\
|
||||
external_flash.o\
|
||||
flash.o\
|
||||
keyboard.o\
|
||||
led.o\
|
||||
|
||||
@@ -15,6 +15,7 @@ extern "C" {
|
||||
#include "usb.h"
|
||||
#include "bench/bench.h"
|
||||
#include "base64.h"
|
||||
#include "external_flash.h"
|
||||
|
||||
#define USE_SD_CARD 0
|
||||
|
||||
@@ -187,6 +188,7 @@ void initPeripherals() {
|
||||
Console::Device::init();
|
||||
SWD::Device::init();
|
||||
initSysTick();
|
||||
ExternalFlash::Device::init();
|
||||
}
|
||||
|
||||
void shutdownPeripherals(bool keepLEDAwake) {
|
||||
|
||||
@@ -1,209 +1,165 @@
|
||||
#include "external_flash.h"
|
||||
#include <ion/src/device/regs/rcc.h>
|
||||
#include <ion/src/device/regs/gpio.h>
|
||||
#include <ion/src/device/regs/quadspi.h>
|
||||
|
||||
namespace Ion {
|
||||
namespace ExternalFlash {
|
||||
namespace Device {
|
||||
|
||||
void waitController() {
|
||||
while (QUADSPI.SR()->getBUSY()) {
|
||||
}
|
||||
/* Explain here how QuadSPI and QPI are different. */
|
||||
|
||||
static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Single;
|
||||
|
||||
static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t * data, size_t dataLength);
|
||||
|
||||
static inline void send_command(Command c) {
|
||||
send_command_full(
|
||||
QUADSPI::CCR::FunctionalMode::IndirectWrite,
|
||||
DefaultOperatingMode,
|
||||
c,
|
||||
0, nullptr, 0
|
||||
);
|
||||
}
|
||||
|
||||
static enum DeviceStatusRegister : uint16_t {
|
||||
// Status Register 1
|
||||
BUSY = 1 << 0, // Erase, Program, Write Status Register in Progress
|
||||
WEL = 1 << 1, // Write Enable Latch
|
||||
BP0 = 1 << 2, // Block Protect
|
||||
BP1 = 1 << 3, // Block Protect
|
||||
BP2 = 1 << 4, // Block Protect
|
||||
TB = 1 << 5, // Top/Bottom Write Protect
|
||||
SEC = 1 << 6, // Sector Protect
|
||||
SRP = 1 << 7, // Status Register Protect 0
|
||||
// Status Register 2
|
||||
SRP1 = 1 << 8, // Status Register Protect 1
|
||||
QE = 1 << 9, // Quad-SPI Enable
|
||||
// Bits 10, 11, 12, 13 are reserved
|
||||
CMP = 1 << 14, //
|
||||
SUS = 1 << 15
|
||||
};
|
||||
static inline void send_command_single(Command c) {
|
||||
send_command_full(
|
||||
QUADSPI::CCR::FunctionalMode::IndirectWrite,
|
||||
QUADSPI::CCR::OperatingMode::Single,
|
||||
c,
|
||||
0, nullptr, 0
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Bit Volatile? Reset value Short name Long name
|
||||
------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Status Register-1 0 volatile BUSY Erase, Program, Write Status Register in Progress
|
||||
static inline void send_write_command(Command c, uint32_t address, uint8_t * data, size_t dataLength) {
|
||||
send_command_full(
|
||||
QUADSPI::CCR::FunctionalMode::IndirectWrite,
|
||||
DefaultOperatingMode,
|
||||
c,
|
||||
address, data, dataLength
|
||||
);
|
||||
}
|
||||
|
||||
Any instruction, except for Read Register, will be ignored while BUSY=1.
|
||||
It is recommended to check the BUSY bit before sending a new instruction, after a Program, Erase, Write Status Register operation.
|
||||
TODO Status polling instead of while...
|
||||
static inline void send_read_command(Command c, uint32_t address, uint8_t * data, size_t dataLength) {
|
||||
send_command_full(
|
||||
QUADSPI::CCR::FunctionalMode::IndirectRead,
|
||||
DefaultOperatingMode,
|
||||
c,
|
||||
address, data, dataLength
|
||||
);
|
||||
}
|
||||
|
||||
1 volatile WEL Write Enable Latch
|
||||
|
||||
must be set before any Program, Erase or Write Status Register instruction, with the Write Enable instruction.
|
||||
is automatically reset write-disable status of “0” after Power‐up and upon completion of any Program, Erase or Write Status Register instruction.
|
||||
|
||||
2 non-volatile BP0 Block Protect
|
||||
3 non-volatile BP1 Block Protect
|
||||
4 non-volatile BP2 Block Protect
|
||||
5 non-volatile TB Top/Bottom Write Protect
|
||||
6 non-volatile 0 SEC Sector Protect
|
||||
|
||||
controls if the Block Protect Bits (BP2, BP1, BP0) protect 4KB Sectors (SEC=1) or 64KB Blocks (SEC=0) in the Top (TB=0) or the Bottom (TB=1) of the array.
|
||||
|
||||
7 non-volatile SRP Status Register Protect 0
|
||||
Status Register-2 8 non-volatile SRP1 Status Register Protect 1
|
||||
9 non-volatile QE Quad SPI Enable
|
||||
14 non-volatile 0 CMP Complement Protect
|
||||
|
||||
CMP=0: a top 4KB sector can be protected while the rest of the array is not
|
||||
CMP=1: the top 4KB sector will become unprotected while the rest of the array become read-only.
|
||||
|
||||
15 volatile 0 SUS Suspend Status
|
||||
------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
void waitDevice() {
|
||||
static inline void wait() {
|
||||
class ExternalFlashStatusRegister : Register16 {
|
||||
public:
|
||||
using Register16::Register16;
|
||||
REGS_BOOL_FIELD(BUSY, 0);
|
||||
};
|
||||
ExternalFlashStatusRegister statusRegister(0);
|
||||
do {
|
||||
ReadStatusRegister1.write();
|
||||
} while(QUADSPI.DR()->get() & 1);
|
||||
send_read_command(Command::ReadStatusRegister, 0, reinterpret_cast<uint8_t *>(&statusRegister), sizeof(statusRegister));
|
||||
} while (statusRegister.getBUSY());
|
||||
}
|
||||
|
||||
void initGPIO() {
|
||||
/* Pin | Role | Mode | Function
|
||||
* -----+----------------------+-----------------------+-----------------
|
||||
* PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI CLK
|
||||
* PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI BK1_NCS
|
||||
* PC9 | QUADSPI BK1_IO0/SO | Alternate Function 9 | QUADSPI BK1_IO0
|
||||
* PD12 | QUADSPI BK1_IO1/SI | Alternate Function 9 | QUADSPI BK1_IO1
|
||||
* PC8 | QUADSPI BK1_IO2/WP | Alternate Function 9 | QUADSPI BK1_IO2
|
||||
* PD13 | QUADSPI BK1_IO3/HOLD | Alternate Function 9 | QUADSPI BK1_IO3 */
|
||||
static void set_as_memory_mapped() {
|
||||
send_command_full(
|
||||
QUADSPI::CCR::FunctionalMode::MemoryMapped,
|
||||
DefaultOperatingMode,
|
||||
Command::FastRead,
|
||||
0, nullptr, 0
|
||||
);
|
||||
}
|
||||
|
||||
// Enable GPIOB, C, D AHB1 peripheral clocks
|
||||
RCC.AHB1ENR()->setGPIOBEN(true);
|
||||
RCC.AHB1ENR()->setGPIOCEN(true);
|
||||
RCC.AHB1ENR()->setGPIODEN(true);
|
||||
|
||||
constexpr static GPIOPin QUADSPIPins[] = {
|
||||
GPIOPin(GPIOB, 2),
|
||||
GPIOPin(GPIOB, 6),
|
||||
GPIOPin(GPIOC, 9),
|
||||
GPIOPin(GPIOD,12),
|
||||
GPIOPin(GPIOC, 8),
|
||||
GPIOPin(GPIOD,13)
|
||||
void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t * data, size_t dataLength) {
|
||||
class QUADSPI::CCR ccr(0);
|
||||
ccr.setFMODE(functionalMode);
|
||||
if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) {
|
||||
ccr.setDMODE(operatingMode);
|
||||
}
|
||||
for(const GPIOPin & g : QUADSPIPins) {
|
||||
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction);
|
||||
g.group().AFR()->setAlternateFunction(g.pin(), (g == QUADSPIPins[1]) ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9);
|
||||
if (address != 0 || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) {
|
||||
ccr.setADMODE(operatingMode);
|
||||
ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes);
|
||||
QUADSPI.AR()->set(address);
|
||||
}
|
||||
ccr.setIMODE(operatingMode);
|
||||
ccr.setINSTRUCTION(static_cast<uint8_t>(c));
|
||||
QUADSPI.CCR()->set(ccr);
|
||||
|
||||
// FIXME: Handle access sizes
|
||||
if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) {
|
||||
for (size_t i=0; i<dataLength; i++) {
|
||||
QUADSPI.DR()->set(data[i]);
|
||||
}
|
||||
} else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) {
|
||||
for (size_t i=0; i<dataLength; i++) {
|
||||
data[i] = QUADSPI.DR()->get();
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait for the command to be sent.
|
||||
* "When configured in memory-mapped mode, because of the prefetch operations,
|
||||
* BUSY does not fall until there is a timeout, there is an abort, or the
|
||||
* peripheral is disabled." */
|
||||
if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) {
|
||||
while (QUADSPI.SR()->getBUSY()) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
initGPIO();
|
||||
initQSPI();
|
||||
initChip();
|
||||
}
|
||||
|
||||
void initGPIO() {
|
||||
for(const GPIOPin & g : QSPIPins) {
|
||||
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction);
|
||||
g.group().AFR()->setAlternateFunction(g.pin(), (g.pin() == 6 ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9));
|
||||
}
|
||||
}
|
||||
|
||||
void initQSPI() {
|
||||
// Enable QUADSPI AHB3 peripheral clocks
|
||||
RCC.AHB3ENR()->setQSPIEN(true);
|
||||
// Configure controller for target device
|
||||
QUADSPI.DCR()->setFSIZE(22); // 2^(22+1) bytes in device
|
||||
/* TODO CKMODE, CSHT
|
||||
* Instructions are initiated on the falling edge of CS and are completed on its rising edge.
|
||||
* Mode 0: SCK is low when CS is high
|
||||
* Mode 3: SCK is high when CS is high
|
||||
* Input bits are sampled/latched on the rising edge of SCK.
|
||||
* Output bits are shifted out on the falling edge of SCK.
|
||||
*
|
||||
* The IO pins should be at high impedance prior to the falling edge of the first data out clock.
|
||||
* Data bytes are shifted with Most Significant Bit first.
|
||||
*
|
||||
* Configure and enable QUADSPI peripheral
|
||||
* All instructions, except Read Data, supported SPI clock frequencies of up to 104MHz.
|
||||
* Read Data supports up to 50Mhz.
|
||||
* TODO PRESCALER
|
||||
* TODO trade-off ? consumption? */
|
||||
QUADSPI.CR()->setPRESCALER(255);
|
||||
|
||||
QUADSPI.DCR()->setCSHT(7); // Highest value. TODO: make it optimal
|
||||
QUADSPI.CR()->setPRESCALER(255); // Highest value. TODO: make it optimal
|
||||
|
||||
QUADSPI.CR()->setEN(true);
|
||||
}
|
||||
|
||||
class Instruction {
|
||||
public:
|
||||
using QUADSPI::CCR::OperatingMode;
|
||||
using QUADSPI::CCR::Size;
|
||||
using QUADSPI::CCR::FunctionalMode;
|
||||
constexpr Instruction(
|
||||
uint8_t instructionCode, OperatingMode IMode, bool SIOO,
|
||||
OperatingMode ADMode,
|
||||
uint8_t DCyc,
|
||||
OperatingMode DMode, uint32_t DLength,
|
||||
FunctionalMode FMode
|
||||
) {
|
||||
m_ccr = QUADSPI::CCR((uint32_t)0);
|
||||
m_ccr.setINSTRUCTION(instructionCode);
|
||||
m_ccr.setIMODE (IMode);
|
||||
m_ccr.setSIOO (SIOO);
|
||||
m_ccr.setADMODE(ADMode);
|
||||
m_ccr.setADSIZE(Size::ThreeBytes);
|
||||
m_ccr.setDCYC (DCyc);
|
||||
m_ccr.setDMODE (DMode);
|
||||
m_ccr.setFMODE (FMode);
|
||||
// DDR not supported by Adesto
|
||||
void initChip() {
|
||||
/* The chip initially expects commands in SPI mode. We need to use SPI to tell
|
||||
* it to switch to QPI, hence the "_single". */
|
||||
if (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) {
|
||||
send_command_single(Command::EnableQPI);
|
||||
}
|
||||
void setAlternateBytes(OperatingMode ABMode, Size ABSize) {
|
||||
m_ccr.setABMODE(ABMode);
|
||||
m_ccr.setABSIZE(ABSize);
|
||||
}
|
||||
void write() {
|
||||
QUADSPI.CCR()->set(m_ccr);
|
||||
QUADSPI.DLR()->set(0);
|
||||
}
|
||||
private:
|
||||
class QUADSPI::CCR m_ccr;
|
||||
uint32_t m_dlr;
|
||||
};
|
||||
set_as_memory_mapped();
|
||||
}
|
||||
|
||||
//TODO default ccr
|
||||
/* Modes IMODE ADMODE ABMODE DMODE
|
||||
* SPIStandard 1-1-1-1
|
||||
* SPIDualOutput 1-1-1-2
|
||||
* SPIDualIO 1-2-2-2
|
||||
* SPIQuadOutput 1-1-1-4
|
||||
* SPIQuadIO 1-4-4-4
|
||||
* Quad SPI instructions require the QE bit in Status Register-2 to be set.
|
||||
* QPI 4-4-4-4
|
||||
* Enable/Disable QPI instruction
|
||||
* ADSIZE is always ThreeBytes
|
||||
* MODE may be NoData*/
|
||||
void MassErase() {
|
||||
send_command(Command::WriteEnable);
|
||||
send_command(Command::ChipErase);
|
||||
//send_command(ReadStatusRegister);
|
||||
wait();
|
||||
set_as_memory_mapped();
|
||||
}
|
||||
|
||||
/* All Read instructions can be completed after any clocked bit.
|
||||
* However, all Write, Program or Erase instructions must complete on a byte (CS driven high
|
||||
* after a full byte has been clocked) otherwise the instruction will be terminated. */
|
||||
void EraseSector() {
|
||||
send_command(Command::WriteEnable);
|
||||
//send_command(Command::BlockErase /* PICK SIZE */, addressOfSector);
|
||||
wait();
|
||||
set_as_memory_mapped();
|
||||
}
|
||||
|
||||
using QUADSPI::CCR::OperatingMode;
|
||||
using QUADSPI::CCR::Size;
|
||||
using QUADSPI::CCR::FunctionalMode;
|
||||
|
||||
static enum InstructionSet : Instruction {
|
||||
ReadStatusRegister1 = Instruction(0x05, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectRead),
|
||||
WriteEnable = Instruction(0x06, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectWrite),
|
||||
// Before any Program, Erase or Write Status Register instruction
|
||||
EraseChip = Instruction(0x60, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectWrite)
|
||||
/*
|
||||
ReadStatusRegister2 = Instruction(0x35),
|
||||
ReadData = Instruction(0x03),
|
||||
Erase4kBlock = Instruction(0x20),
|
||||
PageProgram = Instruction(0x02)
|
||||
*/
|
||||
};
|
||||
|
||||
void eraseChip() {
|
||||
WriteEnable.write();
|
||||
EraseChip.write();
|
||||
waitDevice();
|
||||
void WriteMemory(uint32_t * source, uint32_t * destination, size_t length) {
|
||||
send_command(Command::WriteEnable);
|
||||
//send_write_command(Command::QuadPageProgram, (destination-reinterpret_cast<uint32_t *>(QSPIBaseAddress)), source, length);
|
||||
// TODO: Apprently, here we must issue a new send_command every 256 byte
|
||||
wait();
|
||||
set_as_memory_mapped();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#ifndef ION_DEVICE_EXTERNAL_FLASH_H
|
||||
#define ION_DEVICE_EXTERNAL_FLASH_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "regs/regs.h"
|
||||
|
||||
// Quad-SPI on STM32 microcontroller
|
||||
// https://www.st.com/resource/en/application_note/dm00227538.pdf
|
||||
|
||||
@@ -18,16 +22,42 @@ namespace Ion {
|
||||
namespace ExternalFlash {
|
||||
namespace Device {
|
||||
|
||||
// Read, Erase, Write
|
||||
// at the fastest possible speed
|
||||
/* Pin | Role | Mode | Function
|
||||
* -----+----------------------+-----------------------+-----------------
|
||||
* PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK
|
||||
* PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS
|
||||
* PC8 | QUADSPI BK1_IO2/WP | Alternate Function 9 | QUADSPI_BK1_IO2
|
||||
* PC9 | QUADSPI BK1_IO0/SO | Alternate Function 9 | QUADSPI_BK1_IO0
|
||||
* PD12 | QUADSPI BK1_IO1/SI | Alternate Function 9 | QUADSPI_BK1_IO1
|
||||
* PD13 | QUADSPI BK1_IO3/HOLD | Alternate Function 9 | QUADSPI_BK1_IO3
|
||||
*/
|
||||
|
||||
void eraseChip(); //block?
|
||||
void init();
|
||||
void shutdown();
|
||||
|
||||
void program(uint32_t * source, uint32_t * destination, size_t length);
|
||||
void initGPIO();
|
||||
void initQSPI();
|
||||
void initChip();
|
||||
|
||||
void read(); // in indirect read mode
|
||||
void MassErase();
|
||||
void EraseSector(int sector);
|
||||
void WriteMemory(uint32_t * source, uint32_t * destination, size_t length);
|
||||
|
||||
// memory-mapped mode
|
||||
enum class Command : uint8_t {
|
||||
ReadStatusRegister = 0x05,
|
||||
WriteEnable = 0x06,
|
||||
FastRead = 0x0B,
|
||||
QuadPageProgram = 0x33,
|
||||
EnableQPI = 0x38,
|
||||
ChipErase = 0xC7
|
||||
};
|
||||
|
||||
constexpr static uint32_t QSPIBaseAddress = 0x90000000;
|
||||
|
||||
constexpr static GPIOPin QSPIPins[] = {
|
||||
GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12),
|
||||
GPIOPin(GPIOC, 8), GPIOPin(GPIOD,13)
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user