[ion/device] Clean the external flash init code

This commit is contained in:
Romain Goyet
2018-11-20 15:25:23 +01:00
parent a89115d6fd
commit 2270ebc1f3
4 changed files with 166 additions and 177 deletions

View File

@@ -21,6 +21,7 @@ objs += $(addprefix ion/src/device/, \
device.o\
display.o\
events.o\
external_flash.o\
flash.o\
keyboard.o\
led.o\

View File

@@ -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) {

View File

@@ -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 Powerup 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

View File

@@ -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)
};
}
}