diff --git a/ion/src/device/Makefile b/ion/src/device/Makefile index 3d243bd47..31a060382 100644 --- a/ion/src/device/Makefile +++ b/ion/src/device/Makefile @@ -21,6 +21,7 @@ objs += $(addprefix ion/src/device/, \ device.o\ display.o\ events.o\ + external_flash.o\ flash.o\ keyboard.o\ led.o\ diff --git a/ion/src/device/device.cpp b/ion/src/device/device.cpp index e25cc4106..d01941f32 100644 --- a/ion/src/device/device.cpp +++ b/ion/src/device/device.cpp @@ -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) { diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 27a9902c7..90c17f9e8 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -1,209 +1,165 @@ #include "external_flash.h" -#include -#include -#include 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(&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(c)); + QUADSPI.CCR()->set(ccr); + + // FIXME: Handle access sizes + if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { + for (size_t i=0; iset(data[i]); + } + } else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + for (size_t i=0; iget(); + } + } + + /* 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(QSPIBaseAddress)), source, length); + // TODO: Apprently, here we must issue a new send_command every 256 byte + wait(); + set_as_memory_mapped(); } } } } - -#endif diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index f651f4635..659a4f8dc 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -1,6 +1,10 @@ #ifndef ION_DEVICE_EXTERNAL_FLASH_H #define ION_DEVICE_EXTERNAL_FLASH_H +#include +#include +#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) +}; } }