5
mirror of https://gitlab.com/zephray/glider.git synced 2025-12-31 13:07:10 +00:00
glider/fw/User/spiflash.c
2025-11-23 11:45:03 +08:00

542 lines
19 KiB
C

//
// Grimoire
// Copyright 2025 Wenting Zhang
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#include "platform.h"
#include "board.h"
#include "app.h"
/* Reset Operations */
#define RESET_ENABLE_CMD 0x66
#define RESET_MEMORY_CMD 0x99
/* Identification Operations */
#define READ_ID_CMD 0x9E
#define READ_ID_CMD2 0x9F
#define MULTIPLE_IO_READ_ID_CMD 0xAF
#define READ_SERIAL_FLASH_DISCO_PARAM_CMD 0x5A
/* Read Operations */
#define READ_CMD 0x03
#define FAST_READ_CMD 0x0B
#define DUAL_OUT_FAST_READ_CMD 0x3B
#define DUAL_INOUT_FAST_READ_CMD 0xBB
#define QUAD_OUT_FAST_READ_CMD 0x6B
#define QUAD_INOUT_FAST_READ_CMD 0xEB
/* Write Operations */
#define WRITE_ENABLE_CMD 0x06
#define WRITE_DISABLE_CMD 0x04
/* Register Operations */
#define READ_STATUS_REG_CMD 0x05
#define WRITE_STATUS_REG_CMD 0x01
#define READ_LOCK_REG_CMD 0xE8
#define WRITE_LOCK_REG_CMD 0xE5
#define READ_FLAG_STATUS_REG_CMD 0x70
#define CLEAR_FLAG_STATUS_REG_CMD 0x50
#define READ_NONVOL_CFG_REG_CMD 0xB5
#define WRITE_NONVOL_CFG_REG_CMD 0xB1
#define READ_VOL_CFG_REG_CMD 0x85
#define WRITE_VOL_CFG_REG_CMD 0x81
#define READ_ENHANCED_VOL_CFG_REG_CMD 0x65
#define WRITE_ENHANCED_VOL_CFG_REG_CMD 0x61
/* Program Operations */
#define PAGE_PROG_CMD 0x02
#define DUAL_IN_FAST_PROG_CMD 0xA2
#define EXT_DUAL_IN_FAST_PROG_CMD 0xD2
#define QUAD_IN_FAST_PROG_CMD 0x32
#define EXT_QUAD_IN_FAST_PROG_CMD 0x12
/* Erase Operations */
#define SUBSECTOR_ERASE_CMD 0x20
#define SECTOR_ERASE_CMD 0xD8
#define BULK_ERASE_CMD 0xC7
#define PROG_ERASE_RESUME_CMD 0x7A
#define PROG_ERASE_SUSPEND_CMD 0x75
/* One-Time Programmable Operations */
#define READ_OTP_ARRAY_CMD 0x4B
#define PROG_OTP_ARRAY_CMD 0x42
static int spif_auto_polling_mem_ready(uint32_t timeout) {
QSPI_CommandTypeDef s_command;
QSPI_AutoPollingTypeDef s_config;
/* Configure automatic polling mode to wait for memory ready */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = READ_STATUS_REG_CMD;
s_command.AddressMode = QSPI_ADDRESS_NONE;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_1_LINE;
s_command.DummyCycles = 0;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
s_config.Match = 0;
s_config.MatchMode = QSPI_MATCH_MODE_AND;
s_config.Interval = 0x10;
s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
s_config.Mask = SPIF_SR_WIP;
s_config.StatusBytesSize = 2;
if (HAL_QSPI_AutoPolling(&hqspi, &s_command, &s_config, timeout) != HAL_OK)
return -1;
return 0;
}
static int spif_reset_memory(void) {
QSPI_CommandTypeDef s_command;
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = RESET_ENABLE_CMD;
s_command.AddressMode = QSPI_ADDRESS_NONE;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_NONE;
s_command.DummyCycles = 0;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
s_command.Instruction = RESET_MEMORY_CMD;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
return spif_auto_polling_mem_ready(HAL_QSPI_TIMEOUT_DEFAULT_VALUE);
}
static int spif_write_enable(void) {
QSPI_CommandTypeDef s_command;
QSPI_AutoPollingTypeDef s_config;
/* Enable write operations */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = WRITE_ENABLE_CMD;
s_command.AddressMode = QSPI_ADDRESS_NONE;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_NONE;
s_command.DummyCycles = 0;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
/* Configure automatic polling mode to wait for write enabling */
s_config.Match = SPIF_SR_WREN;
s_config.Mask = SPIF_SR_WREN;
s_config.MatchMode = QSPI_MATCH_MODE_AND;
s_config.StatusBytesSize = 1;
s_config.Interval = 0x10;
s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
s_command.Instruction = READ_STATUS_REG_CMD;
s_command.DataMode = QSPI_DATA_1_LINE;
if (HAL_QSPI_AutoPolling(&hqspi, &s_command, &s_config, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
}
static int spif_dummy_cycles_cfg(void) {
QSPI_CommandTypeDef s_command;
uint8_t reg = 0;
/* Initialize the read volatile configuration register command */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = READ_VOL_CFG_REG_CMD;
s_command.AddressMode = QSPI_ADDRESS_NONE;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_1_LINE;
s_command.DummyCycles = 0;
s_command.NbData = 1;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
/* Configure the command */
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
/* Reception of the data */
if (HAL_QSPI_Receive(&hqspi, (uint8_t *)(&reg), HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
/* Enable write operations */
if (spif_write_enable() != 0)
return -1;
/* Update volatile configuration register (with new dummy cycles) */
s_command.Instruction = WRITE_VOL_CFG_REG_CMD;
MODIFY_REG(reg, 0xF0F0, (SPIF_DUMMY_CYCLES_READ_QUAD << 4));
/* Configure the write volatile configuration register command */
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
/* Transmission of the data */
if (HAL_QSPI_Transmit(&hqspi, (uint8_t *)(&reg), HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
return 0;
}
int spif_init(void) {
int result;
result = spif_reset_memory();
//result += spif_dummy_cycles_cfg();
if (result != 0) {
syslog_printf("SPI flash initialization failed");
}
return result;
}
// TODO: Should have been a semaphore
static volatile bool tx_complete = false;
static volatile bool rx_complete = false;
void HAL_QSPI_TxCpltCallback(QSPI_HandleTypeDef *hqspi) {
tx_complete = true;
}
void HAL_QSPI_RxCpltCallback(QSPI_HandleTypeDef *hqspi) {
rx_complete = true;
}
int spif_read(uint32_t addr, uint32_t size, uint8_t *dat) {
static QSPI_CommandTypeDef s_command = {
.InstructionMode = QSPI_INSTRUCTION_1_LINE,
.Instruction = QUAD_INOUT_FAST_READ_CMD,
.AddressMode = QSPI_ADDRESS_4_LINES,
.AddressSize = QSPI_ADDRESS_24_BITS,
.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE,
.DataMode = QSPI_DATA_4_LINES,
.DummyCycles = SPIF_DUMMY_CYCLES_READ_QUAD,
.DdrMode = QSPI_DDR_MODE_DISABLE,
.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY,
.SIOOMode = QSPI_SIOO_INST_EVERY_CMD,
};
/* Initialize the read command */
s_command.Address = addr;
s_command.NbData = size;
/* Configure the command */
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
rx_complete = false;
/* Reception of the data */
if (HAL_QSPI_Receive_DMA(&hqspi, dat) != HAL_OK)
return -1;
while (!rx_complete);
return 0;
}
int spif_write(uint32_t addr, uint32_t size, uint8_t *dat) {
QSPI_CommandTypeDef s_command;
uint32_t end_addr, current_size, current_addr;
/* Calculation of the size between the write address and the end of the page */
current_size = SPIF_PAGE_SIZE - (addr % SPIF_PAGE_SIZE);
/* Check if the size of the data is less than the remaining place in the page */
if (current_size > size) {
current_size = size;
}
/* Initialize the adress variables */
current_addr = addr;
end_addr = addr + size;
/* Initialize the program command */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = QUAD_IN_FAST_PROG_CMD;
s_command.AddressMode = QSPI_ADDRESS_1_LINE;
s_command.AddressSize = QSPI_ADDRESS_24_BITS;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_4_LINES;
s_command.DummyCycles = 0;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
/* Perform the write page by page */
do {
s_command.Address = current_addr;
s_command.NbData = current_size;
if (spif_write_enable() != 0)
return -1;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
tx_complete = false;
if (HAL_QSPI_Transmit_DMA(&hqspi, dat) != HAL_OK)
return -1;
while (!tx_complete);
if (spif_auto_polling_mem_ready(HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != 0)
return -1;
current_addr += current_size;
dat += current_size;
current_size = ((current_addr + SPIF_PAGE_SIZE) > end_addr) ? (end_addr - current_addr) : SPIF_PAGE_SIZE;
} while (current_addr < end_addr);
return 0;
}
int spif_erase_block(uint32_t addr) {
QSPI_CommandTypeDef s_command;
/* Initialize the erase command */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = SUBSECTOR_ERASE_CMD;
s_command.AddressMode = QSPI_ADDRESS_1_LINE;
s_command.AddressSize = QSPI_ADDRESS_24_BITS;
s_command.Address = addr;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_NONE;
s_command.DummyCycles = 0;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (spif_write_enable() != 0)
return -1;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
if (spif_auto_polling_mem_ready(SPIF_SUBSECTOR_ERASE_TIMEOUT) != 0)
return -1;
}
int spif_erase_sector(uint32_t addr) {
QSPI_CommandTypeDef s_command;
/* Initialize the erase command */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = SECTOR_ERASE_CMD;
s_command.AddressMode = QSPI_ADDRESS_1_LINE;
s_command.AddressSize = QSPI_ADDRESS_24_BITS;
s_command.Address = addr;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_NONE;
s_command.DummyCycles = 0;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (spif_write_enable() != 0)
return -1;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
if (spif_auto_polling_mem_ready(SPIF_SECTOR_ERASE_TIMEOUT) != 0)
return -1;
}
int spif_erase_chip(void) {
QSPI_CommandTypeDef s_command;
/* Initialize the erase command */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = BULK_ERASE_CMD;
s_command.AddressMode = QSPI_ADDRESS_NONE;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_NONE;
s_command.DummyCycles = 0;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (spif_write_enable() != 0)
return -1;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
if (spif_auto_polling_mem_ready(SPIF_BULK_ERASE_TIMEOUT) != 0)
return -1;
}
uint8_t spif_get_status(void) {
QSPI_CommandTypeDef s_command;
uint8_t reg;
/* Initialize the read flag status register command */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = READ_FLAG_STATUS_REG_CMD;
s_command.AddressMode = QSPI_ADDRESS_NONE;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_1_LINE;
s_command.DummyCycles = 0;
s_command.NbData = 1;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
if (HAL_QSPI_Receive(&hqspi, &reg, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
if (reg & (SPIF_FSR_PRERR | SPIF_FSR_VPPERR | SPIF_FSR_PGERR | SPIF_FSR_ERERR))
return SPIF_ERROR;
else if (reg & (SPIF_FSR_PGSUS | SPIF_FSR_ERSUS))
return SPIF_SUSPENDED;
else if (reg & SPIF_FSR_READY)
return SPIF_READY;
else
return SPIF_BUSY;
}
int spif_read_id(spif_id_t *id) {
uint8_t *rxbuf = (uint8_t *)id;
QSPI_CommandTypeDef s_command;
memset(id, 0, sizeof(*id));
/* Initialize the erase command */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = READ_ID_CMD;
s_command.AddressMode = QSPI_ADDRESS_NONE;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_1_LINE;
s_command.DummyCycles = 0;
s_command.NbData = 20;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
if (HAL_QSPI_Receive(&hqspi, rxbuf, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
return 0;
}
int spif_read_jedec_id(spif_id_t *id) {
uint8_t *rxbuf = (uint8_t *)id;
QSPI_CommandTypeDef s_command;
memset(id, 0, sizeof(*id));
/* Initialize the erase command */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = READ_ID_CMD2;
s_command.AddressMode = QSPI_ADDRESS_NONE;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_1_LINE;
s_command.DummyCycles = 0;
s_command.NbData = 3;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
if (HAL_QSPI_Receive(&hqspi, rxbuf, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return -1;
return 0;
}
// spiffs abstraction layer
static spiffs_config spiffs_cfg;
spiffs spiffs_fs;
SemaphoreHandle_t spiffs_lock;
static uint8_t fs_work_buf[256 * 2];
static uint8_t fs_fds[32 * 4];
static uint8_t fs_cache_buf[(256 + 32) * 4];
static int32_t _spiffs_erase(uint32_t addr, uint32_t len) {
int res = 0;
if ((len == SPIF_SECTOR_SIZE) && ((addr & (SPIF_SECTOR_SIZE - 1)) == 0)) {
res = spif_erase_sector(addr);
}
else {
uint32_t i = 0;
uint32_t erase_count = (len + 4096 - 1) / 4096;
for (i = 0; i < erase_count; i++) {
res += spif_erase_block(addr + i * 4096);
}
}
return res;
}
static int32_t _spiffs_read(uint32_t addr, uint32_t size, uint8_t *dst) {
return spif_read(addr, size, dst);
}
static int32_t _spiffs_write(uint32_t addr, uint32_t size, uint8_t *dst) {
return spif_write(addr, size, dst);
}
void spiffs_init(void) {
spiffs_lock = xSemaphoreCreateMutex();
spiffs_cfg.hal_erase_f = _spiffs_erase;
spiffs_cfg.hal_read_f = _spiffs_read;
spiffs_cfg.hal_write_f = _spiffs_write;
int res = SPIFFS_mount(&spiffs_fs, &spiffs_cfg, fs_work_buf, fs_fds,
sizeof(fs_fds), fs_cache_buf, sizeof(fs_cache_buf), NULL);
if ((res != SPIFFS_OK) && (SPIFFS_errno(&spiffs_fs) == SPIFFS_ERR_NOT_A_FS)) {
syslog_printf("Formatting SPIFFS...");
if (SPIFFS_format(&spiffs_fs) != SPIFFS_OK) {
syslog_printf("SPIFFS format failed: %d\n", SPIFFS_errno(&spiffs_fs));
}
res = SPIFFS_mount(&spiffs_fs, &spiffs_cfg, fs_work_buf, fs_fds,
sizeof(fs_fds), fs_cache_buf, sizeof(fs_cache_buf), NULL);
}
if (res != SPIFFS_OK) {
syslog_printf("SPIFFS mount failed: %d\n", SPIFFS_errno(&spiffs_fs));
}
else {
syslog_printf("SPIFFS mounted\n");
}
}