Files
gnwmanager/Core/Src/flash.c
2025-03-11 09:45:42 -04:00

895 lines
34 KiB
C

#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "flash.h"
#include "main.h"
#include "utils.h"
//#define DBG(...) printf(__VA_ARGS__)
#define DBG(...)
// Convenience macro to access the struct for a command
// in the active flash configuration.
#define CMD(cmd_name) &flash.config->commands[CMD_##cmd_name]
#define TMO_DEFAULT 1000
// 3-byte JEDEC ID to uint32_t
#define JEDEC_ID(_x0, _x1, _x2) ( (uint32_t) ( \
((_x0) ) | \
((_x1) << 8) | \
((_x2) << 16) \
))
// Convenience macro to initialize a flash_cmd_t struct
#define CMD_DEF(_cmd, _instr_lines, _addr_lines, _addr_size, _data_lines, _dummy) \
{ \
.cmd = (_cmd), \
.instr_lines = (_instr_lines), \
.addr_lines = (_addr_lines), \
.addr_size = (_addr_size), \
.data_lines = (_data_lines), \
.dummy = (_dummy) \
}
// Convenience macro to initialize a flash_config_t struct
#define FLASH_CONFIG_DEF(_commands, _erase1_size, _erase2_size, _erase3_size, _erase4_size, _set_quad, _init_fn) \
{ \
.commands = (_commands), \
.erase_sizes = { (_erase1_size), (_erase2_size), (_erase3_size), (_erase4_size) }, \
.set_quad = (_set_quad), \
.init_fn = (_init_fn), \
}
#define JEDEC_CONFIG_DEF(_x0, _x1, _x2, _name, _config, _size) \
{ \
.jedec_id.u32 = JEDEC_ID((_x0), (_x1), (_x2)), \
.name = (_name), \
.config = (_config), \
.size = (_size), \
}
// Generic Status Register (SR) bits
#define STATUS_WIP_Pos (0U)
#define STATUS_WIP_Msk (1UL << STATUS_WIP_Pos)
#define STATUS_WEL_Pos (1U)
#define STATUS_WEL_Msk (1UL << STATUS_WEL_Pos)
// MX and ISSI specific
#define STATUS_BP0_Pos (2U)
#define STATUS_BP0_Msk (1UL << STATUS_BP0_Pos)
#define STATUS_BP1_Pos (3U)
#define STATUS_BP1_Msk (1UL << STATUS_BP1_Pos)
#define STATUS_BP2_Pos (4U)
#define STATUS_BP2_Msk (1UL << STATUS_BP2_Pos)
#define STATUS_BP3_Pos (5U)
#define STATUS_BP3_Msk (1UL << STATUS_BP3_Pos)
#define STATUS_QE_Pos (6U)
#define STATUS_QE_Msk (1UL << STATUS_QE_Pos)
#define STATUS_SRWD_Pos (7U)
#define STATUS_SRWD_Msk (1UL << STATUS_SRWD_Pos)
// S (Spansion/Cypress/Infineon) specific CR bits
#define S_CR_QUAD_Pos (1U)
#define S_CR_QUAD_Msk (1UL << S_CR_QUAD_Pos)
// WB (Winbond) specific SR1-3 (status register) bits
#define WB_SR1_PROTECT_Msk 0xFC
#define WB_SR2_PROTECT_Msk 0x41 // excluding OTP security register lock bits
#define WB_SR2_QE_Pos 1
#define WB_SR3_PROTECT_Msk 0x04
#define WB_SR3_DRV_Msk (0b11<<5)
#define WB_SR3_DRV_Val_50 (0b10<<5) // 50%
#define WB_SR3_ADS_Pos 0
typedef enum {
LINES_0, // Mapped to HAL_OSPI_*_NONE
LINES_1, // Mapped to HAL_OSPI_*_1_LINE
LINES_4, // Mapped to HAL_OSPI_*_4_LINES
} lines_t;
typedef enum {
ADDR_SIZE_8B, // Mapped to HAL_OSPI_ADDRESS_8_BITS
ADDR_SIZE_16B, // Mapped to HAL_OSPI_ADDRESS_16_BITS
ADDR_SIZE_24B, // Mapped to HAL_OSPI_ADDRESS_24_BITS
ADDR_SIZE_32B, // Mapped to HAL_OSPI_ADDRESS_32_BITS
} addr_size_t;
const uint32_t instruction_line_map[] = {
[LINES_0] = HAL_OSPI_INSTRUCTION_NONE,
[LINES_1] = HAL_OSPI_INSTRUCTION_1_LINE,
[LINES_4] = HAL_OSPI_INSTRUCTION_4_LINES,
};
const uint32_t address_line_map[] = {
[LINES_0] = HAL_OSPI_ADDRESS_NONE,
[LINES_1] = HAL_OSPI_ADDRESS_1_LINE,
[LINES_4] = HAL_OSPI_ADDRESS_4_LINES,
};
const uint32_t address_size_map[] = {
[ADDR_SIZE_8B] = HAL_OSPI_ADDRESS_8_BITS,
[ADDR_SIZE_16B] = HAL_OSPI_ADDRESS_16_BITS,
[ADDR_SIZE_24B] = HAL_OSPI_ADDRESS_24_BITS,
[ADDR_SIZE_32B] = HAL_OSPI_ADDRESS_32_BITS,
};
const uint32_t data_line_map[] = {
[LINES_0] = HAL_OSPI_DATA_NONE,
[LINES_1] = HAL_OSPI_DATA_1_LINE,
[LINES_4] = HAL_OSPI_DATA_4_LINES,
};
enum {
CMD_WRSR, // Write Status Register
CMD_RDSR, // Read Status Register
CMD_RDCR, // Read Configuration Register
CMD_RDAR, // Read Any Register
CMD_WREN, // Write Enable
CMD_RDID, // Read Identification
CMD_RSTEN, // Reset Enable
CMD_RST, // Reset
CMD_CE, // Chip Erase
CMD_ERASE1, // Usually 4kB 20h
CMD_ERASE2, // Usually 32kB 52h
CMD_ERASE3, // Usually 64kB d8h
CMD_ERASE4, // Usually unsupported
CMD_PP, // Page Program
CMD_READ, // Read Data Bytes
CMD_COUNT,
};
typedef void (*init_fn_t)(void);
typedef struct {
uint8_t cmd; // Command / Instruction
uint8_t instr_lines : 2; // Instruction Lines
lines_t addr_lines : 2; // Address Lines
addr_size_t addr_size : 2; // Address Size
uint8_t data_lines : 2; // Data Lines
uint8_t dummy; // Dummy Cycles
} flash_cmd_t;
typedef struct {
const flash_cmd_t *commands;
uint32_t erase_sizes[4]; // [0] = ERASE1, ... [3] = ERASE4
bool set_quad; // If quad mode should be enabled
init_fn_t init_fn; // Chip/vendor specific init function
} flash_config_t;
typedef union {
uint32_t u32;
uint8_t u8[4];
} jedec_id_t;
typedef struct {
jedec_id_t jedec_id;
const char *name;
const flash_config_t *config;
uint32_t size;
} jedec_config_t;
const flash_cmd_t cmds_spi_24b[CMD_COUNT] = {
// cmd cmd i_lines a_lines a_size d_lines dummy
[CMD_WRSR] = CMD_DEF(0x01, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDSR] = CMD_DEF(0x05, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDCR] = CMD_DEF(0x15, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_WREN] = CMD_DEF(0x06, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RDID] = CMD_DEF(0x9F, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RSTEN] = CMD_DEF(0x66, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RST] = CMD_DEF(0x99, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_CE] = CMD_DEF(0x60, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0), // CE Chip Erase
[CMD_ERASE1] = CMD_DEF(0x20, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_0, 0), // SE Sector Erase
[CMD_ERASE2] = CMD_DEF(0x52, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_0, 0), // BE32K Block Erase 32K
[CMD_ERASE3] = CMD_DEF(0xD8, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_0, 0), // BE Block Erase 64K
[CMD_ERASE4] = { },
[CMD_PP] = CMD_DEF(0x02, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_1, 0), // PP
[CMD_READ] = CMD_DEF(0x0B, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_1, 8), // FAST_READ dummy=8
};
const flash_cmd_t cmds_quad_24b_mx[CMD_COUNT] = {
// cmd cmd i_lines a_lines a_size d_lines dummy
[CMD_WRSR] = CMD_DEF(0x01, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDSR] = CMD_DEF(0x05, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDCR] = CMD_DEF(0x15, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_WREN] = CMD_DEF(0x06, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RDID] = CMD_DEF(0x9F, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RSTEN] = CMD_DEF(0x66, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RST] = CMD_DEF(0x99, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_CE] = CMD_DEF(0x60, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0), // CE Chip Erase
[CMD_ERASE1] = CMD_DEF(0x20, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_0, 0), // SE Sector Erase
[CMD_ERASE2] = CMD_DEF(0x52, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_0, 0), // BE32K Block Erase 32K
[CMD_ERASE3] = CMD_DEF(0xD8, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_0, 0), // BE Block Erase 64K
[CMD_ERASE4] = { },
[CMD_PP] = CMD_DEF(0x38, LINES_1, LINES_4, ADDR_SIZE_24B, LINES_4, 0), // 4PP
[CMD_READ] = CMD_DEF(0xEB, LINES_1, LINES_4, ADDR_SIZE_24B, LINES_4, 6), // 4READ dummy=6
};
const flash_cmd_t cmds_quad_32b_mx[CMD_COUNT] = {
// cmd cmd i_lines a_lines a_size d_lines dummy
[CMD_WRSR] = CMD_DEF(0x01, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDSR] = CMD_DEF(0x05, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDCR] = CMD_DEF(0x15, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_WREN] = CMD_DEF(0x06, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RDID] = CMD_DEF(0x9F, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RSTEN] = CMD_DEF(0x66, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RST] = CMD_DEF(0x99, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_CE] = CMD_DEF(0x60, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0), // CE Chip Erase
[CMD_ERASE1] = CMD_DEF(0x21, LINES_1, LINES_1, ADDR_SIZE_32B, LINES_0, 0), // SE Sector Erase
[CMD_ERASE2] = CMD_DEF(0x5C, LINES_1, LINES_1, ADDR_SIZE_32B, LINES_0, 0), // BE32K Block Erase 32K
[CMD_ERASE3] = CMD_DEF(0xDC, LINES_1, LINES_1, ADDR_SIZE_32B, LINES_0, 0), // BE Block Erase 64K
[CMD_ERASE4] = { },
[CMD_PP] = CMD_DEF(0x3E, LINES_1, LINES_4, ADDR_SIZE_32B, LINES_4, 0), // 4PP4B
[CMD_READ] = CMD_DEF(0xEC, LINES_1, LINES_4, ADDR_SIZE_32B, LINES_4, 6), // 4READ4B dummy=6
};
const flash_cmd_t cmds_quad_32b_mx54[CMD_COUNT] = {
// cmd cmd i_lines a_lines a_size d_lines dummy
[CMD_WRSR] = CMD_DEF(0x01, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDSR] = CMD_DEF(0x05, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDCR] = CMD_DEF(0x15, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_WREN] = CMD_DEF(0x06, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RDID] = CMD_DEF(0x9F, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RSTEN] = CMD_DEF(0x66, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RST] = CMD_DEF(0x99, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_CE] = CMD_DEF(0x60, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0), // CE Chip Erase
[CMD_ERASE1] = CMD_DEF(0x20, LINES_1, LINES_1, ADDR_SIZE_32B, LINES_0, 0), // SE Sector Erase
[CMD_ERASE2] = CMD_DEF(0x52, LINES_1, LINES_1, ADDR_SIZE_32B, LINES_0, 0), // BE32K Block Erase 32K
[CMD_ERASE3] = CMD_DEF(0xD8, LINES_1, LINES_1, ADDR_SIZE_32B, LINES_0, 0), // BE Block Erase 64K
[CMD_ERASE4] = { },
[CMD_PP] = CMD_DEF(0x38, LINES_1, LINES_4, ADDR_SIZE_32B, LINES_4, 0), // 4PP
[CMD_READ] = CMD_DEF(0xEB, LINES_1, LINES_4, ADDR_SIZE_32B, LINES_4, 10), // 4READ dummy=10
};
const flash_cmd_t cmds_quad_32b_s[CMD_COUNT] = {
// cmd cmd i_lines a_lines a_size d_lines dummy
[CMD_WRSR] = CMD_DEF(0x01, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDSR] = CMD_DEF(0x05, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDCR] = CMD_DEF(0x35, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDAR] = CMD_DEF(0x65, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_1, 8),
[CMD_WREN] = CMD_DEF(0x06, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RDID] = CMD_DEF(0x9F, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RSTEN] = CMD_DEF(0x66, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RST] = CMD_DEF(0x99, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_CE] = CMD_DEF(0x60, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0), // BE Bulk Erase
[CMD_ERASE1] = CMD_DEF(0xDC, LINES_1, LINES_1, ADDR_SIZE_32B, LINES_0, 0), // 4SE Sector Erase 256K
[CMD_ERASE2] = { },
[CMD_ERASE3] = { },
[CMD_ERASE4] = { },
[CMD_PP] = CMD_DEF(0x12, LINES_1, LINES_1, ADDR_SIZE_32B, LINES_1, 0), // 4PP (no 4PP4B)
[CMD_READ] = CMD_DEF(0xEC, LINES_1, LINES_4, ADDR_SIZE_32B, LINES_4, 2 + 8), // 4QIOR
};
const flash_cmd_t cmds_quad_24b_issi[CMD_COUNT] = {
// cmd cmd i_lines a_lines a_size d_lines dummy
[CMD_WRSR] = CMD_DEF(0x01, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDSR] = CMD_DEF(0x05, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDCR] = CMD_DEF(0x15, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_WREN] = CMD_DEF(0x06, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RDID] = CMD_DEF(0x9F, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RSTEN] = CMD_DEF(0x66, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RST] = CMD_DEF(0x99, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_CE] = CMD_DEF(0x60, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0), // CE Chip Erase
[CMD_ERASE1] = CMD_DEF(0x20, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_0, 0), // SE Sector Erase
[CMD_ERASE2] = CMD_DEF(0x52, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_0, 0), // BE32K Block Erase 32K
[CMD_ERASE3] = CMD_DEF(0xD8, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_0, 0), // BE Block Erase 64K
[CMD_ERASE4] = { },
[CMD_PP] = CMD_DEF(0x38, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_4, 0), // PPQ
[CMD_READ] = CMD_DEF(0xEB, LINES_1, LINES_4, ADDR_SIZE_24B, LINES_4, 6), // FRQIO dummy=6
};
const flash_cmd_t cmds_quad_24b_wb[CMD_COUNT] = {
// cmd cmd i_lines a_lines a_size d_lines dummy
[CMD_WRSR] = CMD_DEF(0x01, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDSR] = CMD_DEF(0x05, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDCR] = CMD_DEF(0x15, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_WREN] = CMD_DEF(0x06, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RDID] = CMD_DEF(0x9F, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RSTEN] = CMD_DEF(0x66, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RST] = CMD_DEF(0x99, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_CE] = CMD_DEF(0x60, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0), // Chip Erase
[CMD_ERASE1] = CMD_DEF(0x20, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_0, 0), // Sector Erase 4KB
[CMD_ERASE2] = CMD_DEF(0x52, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_0, 0), // Block Erase 32KB
[CMD_ERASE3] = CMD_DEF(0xD8, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_0, 0), // Block Erase 64KB
[CMD_ERASE4] = { },
[CMD_PP] = CMD_DEF(0x32, LINES_1, LINES_1, ADDR_SIZE_24B, LINES_4, 0), // Quad Input Page Program
[CMD_READ] = CMD_DEF(0xEB, LINES_1, LINES_4, ADDR_SIZE_24B, LINES_4, 6), // Fast Read Quad I/O
};
const flash_cmd_t cmds_quad_32b_wb[CMD_COUNT] = {
// cmd cmd i_lines a_lines a_size d_lines dummy
[CMD_WRSR] = CMD_DEF(0x01, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDSR] = CMD_DEF(0x05, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RDCR] = CMD_DEF(0x15, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_WREN] = CMD_DEF(0x06, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RDID] = CMD_DEF(0x9F, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0),
[CMD_RSTEN] = CMD_DEF(0x66, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_RST] = CMD_DEF(0x99, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0),
[CMD_CE] = CMD_DEF(0x60, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_0, 0), // Chip Erase
[CMD_ERASE1] = CMD_DEF(0x21, LINES_1, LINES_1, ADDR_SIZE_32B, LINES_0, 0), // Sector Erase 4KB with 4-Byte Address
[CMD_ERASE2] = CMD_DEF(0xDC, LINES_1, LINES_1, ADDR_SIZE_32B, LINES_0, 0), // Block Erase 64KB with 4-Byte Address
[CMD_ERASE3] = { },
[CMD_ERASE4] = { },
[CMD_PP] = CMD_DEF(0x34, LINES_1, LINES_1, ADDR_SIZE_32B, LINES_4, 0), // Quad Page Program with 4-Byte Address
[CMD_READ] = CMD_DEF(0xEC, LINES_1, LINES_4, ADDR_SIZE_32B, LINES_4, 6), // Fast Read Quad I/O with 4-Byte Address
};
static void init_spansion(void);
static void init_mx_issi(void);
static void init_winbond(void);
const flash_config_t config_spi_24b = FLASH_CONFIG_DEF(cmds_spi_24b, 0x01000, 0x8000, 0x10000, 0, false, NULL);
const flash_config_t config_quad_24b_mx = FLASH_CONFIG_DEF(cmds_quad_24b_mx, 0x01000, 0x8000, 0x10000, 0, true, init_mx_issi);
const flash_config_t config_quad_32b_mx = FLASH_CONFIG_DEF(cmds_quad_32b_mx, 0x01000, 0x8000, 0x10000, 0, true, init_mx_issi);
const flash_config_t config_quad_32b_mx54 = FLASH_CONFIG_DEF(cmds_quad_32b_mx54, 0x01000, 0x8000, 0x10000, 0, true, init_mx_issi);
const flash_config_t config_quad_32b_s = FLASH_CONFIG_DEF(cmds_quad_32b_s, 0x40000, 0, 0, 0, true, init_spansion);
const flash_config_t config_quad_24b_issi = FLASH_CONFIG_DEF(cmds_quad_24b_issi, 0x01000, 0x8000, 0x10000, 0, true, init_mx_issi);
const flash_config_t config_quad_24b_wb = FLASH_CONFIG_DEF(cmds_quad_24b_wb, 0x01000, 0x8000, 0x10000, 0, true, init_winbond);
const flash_config_t config_quad_32b_wb = FLASH_CONFIG_DEF(cmds_quad_32b_wb, 0x01000, 0x10000, 0, 0, true, init_winbond);
const jedec_config_t jedec_map[] = {
#if (EXTFLASH_FORCE_SPI == 0)
// MX 24 bit address
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x34, "MX25U8035F", &config_quad_24b_mx, 1 << 20), // Stock 1MB (Mario)
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x36, "MX25U3232F", &config_quad_24b_mx, 4 << 20), // Stock 4MB (Zelda)
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x37, "MX25U6432F", &config_quad_24b_mx, 8 << 20), // 8MB
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x38, "MX25U1283xF", &config_quad_24b_mx, 16 << 20), // 16MB MX25U12832F, MX25U12835F
JEDEC_CONFIG_DEF(0xC2, 0x20, 0x18, "MX25L12873", &config_quad_24b_mx, 16 << 20), // 16 MB, 3.3v
// MX 32 bit address
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x39, "MX25U25635F", &config_quad_32b_mx, 32 << 20), // 32 MB
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x3A, "MX25U51245G", &config_quad_32b_mx, 64 << 20), // 64 MB
JEDEC_CONFIG_DEF(0xC2, 0x24, 0x3A, "MX25U51245G", &config_quad_32b_mx, 64 << 20), // 64 MB variant of unknown cause supplied by DigiKey (not listed in datasheet)
JEDEC_CONFIG_DEF(0xC2, 0x20, 0x1A, "MX25L51245G", &config_quad_32b_mx, 64 << 20), // 64 MB, 3.3v
JEDEC_CONFIG_DEF(0xC2, 0x95, 0x3A, "MX25U51245G-54", &config_quad_32b_mx54, 64 << 20), // 64 MB
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x3B, "MX66U1G45G", &config_quad_32b_mx, 128 << 20), // 128 MB
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x3C, "MX66U2G45G", &config_quad_32b_mx, 256 << 20), // 256 MB
// Cypress/Infineon 32 bit address
// These chips only have 64kB erase size which won't work well with the rest of the code.
JEDEC_CONFIG_DEF(0x01, 0x02, 0x20, "S25FS512S", &config_quad_32b_s, 64 << 20), // 64 MB
JEDEC_CONFIG_DEF(0x34, 0x2B, 0x1A, "S25FS512S", &config_quad_32b_s, 64 << 20), // 64 MB
// ISSI 24 bit
JEDEC_CONFIG_DEF(0x9D, 0x70, 0x18, "IS25WP128F", &config_quad_24b_issi, 16 << 20), // 16MB
// Winbond 24 bit address
JEDEC_CONFIG_DEF(0xEF, 0x60, 0x18, "W25Q128JW-Q/N", &config_quad_24b_wb, 16 << 20), // 16MB, tested with W25Q128JWSIQ
JEDEC_CONFIG_DEF(0xEF, 0x80, 0x18, "W25Q128JW-M", &config_quad_24b_wb, 16 << 20), // 16MB
// Winbond 32 bit address
JEDEC_CONFIG_DEF(0xEF, 0x60, 0x20, "W25Q512NW-Q/N", &config_quad_32b_wb, 64 << 20), // 64MB
JEDEC_CONFIG_DEF(0xEF, 0x80, 0x20, "W25Q512NW-M", &config_quad_32b_wb, 64 << 20), // 64MB, tested with W25Q512NWEIM
#endif
};
// Driver struct
static struct {
OSPI_HandleTypeDef *hospi;
jedec_id_t jedec_id;
const flash_config_t *config;
const char *name;
uint32_t size;
bool mem_mapped_enabled;
} flash = {
.config = &config_spi_24b, // Default config to use to probe status etc.
.name = "Unknown",
};
static void set_ospi_cmd(OSPI_RegularCmdTypeDef *ospi_cmd,
const flash_cmd_t *cmd,
uint32_t address,
uint8_t *data,
size_t len)
{
memset(ospi_cmd, 0x0, sizeof(*ospi_cmd));
ospi_cmd->OperationType = HAL_OSPI_OPTYPE_COMMON_CFG;
ospi_cmd->FlashId = 0;
ospi_cmd->Instruction = cmd->cmd;
ospi_cmd->InstructionSize = HAL_OSPI_INSTRUCTION_8_BITS;
ospi_cmd->InstructionMode = instruction_line_map[cmd->instr_lines];
ospi_cmd->AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE;
ospi_cmd->DummyCycles = cmd->dummy;
ospi_cmd->DQSMode = HAL_OSPI_DQS_DISABLE;
ospi_cmd->SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD;
ospi_cmd->InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;
ospi_cmd->Address = address;
ospi_cmd->AddressSize = address_size_map[cmd->addr_size];
ospi_cmd->AddressMode = address_line_map[cmd->addr_lines];
ospi_cmd->NbData = len;
ospi_cmd->DataMode = data_line_map[cmd->data_lines];
}
static void OSPI_ReadBytes(const flash_cmd_t *cmd,
uint32_t address,
uint8_t *data,
size_t len)
{
OSPI_RegularCmdTypeDef ospi_cmd;
// DBG("RB %d 0x%08x 0x%08X %d\n", cmd->cmd, address, data, len);
assert(flash.mem_mapped_enabled == false);
set_ospi_cmd(&ospi_cmd,
cmd,
address,
(uint8_t *) data,
len);
wdog_refresh();
HAL_StatusTypeDef res;
res = HAL_OSPI_Command(flash.hospi, &ospi_cmd, HAL_OSPI_TIMEOUT_DEFAULT_VALUE);
if (res != HAL_OK) {
Error_Handler();
}
if (HAL_OSPI_Receive(flash.hospi, data, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
Error_Handler();
}
}
static void OSPI_WriteBytes(const flash_cmd_t *cmd,
uint32_t address,
const uint8_t *data,
size_t len)
{
OSPI_RegularCmdTypeDef ospi_cmd;
assert(flash.mem_mapped_enabled == false);
set_ospi_cmd(&ospi_cmd,
cmd,
address,
(uint8_t *) data,
len);
wdog_refresh();
if (HAL_OSPI_Command(flash.hospi, &ospi_cmd, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
Error_Handler();
}
if (len > 0) {
if (HAL_OSPI_Transmit(flash.hospi, (uint8_t *) data, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
Error_Handler();
}
}
}
static uint8_t get_status(uint8_t mask){
uint8_t status;
OSPI_ReadBytes(CMD(RDSR), 0, &status, 1);
return status & mask;
}
static void wait_for_status(uint8_t mask, uint8_t value, uint32_t timeout)
{
uint8_t status;
uint32_t t0 = HAL_GetTick();
do {
status = get_status(mask);
wdog_refresh();
if ((timeout > 0) && (HAL_GetTick() > t0 + timeout)) {
assert(!"Status poll timeout!");
break;
}
} while (status != value);
}
bool OSPI_ChipIdle(void){ // Returns True once chip is ready for another cmd.
uint8_t status;
status = get_status(STATUS_WIP_Msk);
return status == 0;
}
void OSPI_EnableMemoryMappedMode(void)
{
OSPI_MemoryMappedTypeDef sMemMappedCfg;
OSPI_RegularCmdTypeDef ospi_cmd;
const flash_cmd_t *cmd = CMD(READ);
if(flash.mem_mapped_enabled){
return;
}
set_ospi_cmd(&ospi_cmd, cmd, 0, NULL, 0);
// Memory-mapped mode configuration for linear burst read operations
ospi_cmd.OperationType = HAL_OSPI_OPTYPE_READ_CFG;
if (HAL_OSPI_Command(flash.hospi, &ospi_cmd, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
Error_Handler();
}
// Use read instruction for write (in order to not alter the flash by accident)
ospi_cmd.OperationType = HAL_OSPI_OPTYPE_WRITE_CFG;
if (HAL_OSPI_Command(flash.hospi, &ospi_cmd, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
Error_Handler();
}
// Disable timeout counter for memory mapped mode
sMemMappedCfg.TimeOutActivation = HAL_OSPI_TIMEOUT_COUNTER_DISABLE;
sMemMappedCfg.TimeOutPeriod = 0;
// Enable memory mapped mode
if (HAL_OSPI_MemoryMapped(flash.hospi, &sMemMappedCfg) != HAL_OK) {
Error_Handler();
}
flash.mem_mapped_enabled = true;
}
void OSPI_DisableMemoryMappedMode(void)
{
if(flash.mem_mapped_enabled){
HAL_OSPI_Abort(flash.hospi);
flash.mem_mapped_enabled = false;
}
// This will *ONLY* work if you absolutely don't
// look at the memory mapped address.
// See here:
// https://community.st.com/s/question/0D50X00009XkaJuSAJ/stm32f7-qspi-exit-memory-mapped-mode
// Even having a debugger open at 0x9000_0000 will break this.
}
static void _OSPI_Erase(const flash_cmd_t *cmd, uint32_t address)
{
OSPI_WriteBytes(cmd, address, NULL, 0);
}
void OSPI_ChipErase(bool blocking)
{
OSPI_NOR_WriteEnable();
_OSPI_Erase(CMD(CE), 0); // Chip Erase
if(blocking){
wait_for_status(STATUS_WIP_Msk, 0, 0);
}
}
bool OSPI_Erase(uint32_t *address, uint32_t *size, bool blocking)
{
// Performs one erase command per call with the largest size possible.
// Sets *address and *size to values that should be passed to
// OSPI_Erase in the next iteration.
// Returns true when done.
assert(address != NULL);
assert(size != NULL);
if(blocking){
// wait until a previous command is no longer in progress
wait_for_status(STATUS_WIP_Msk, 0, 0);
}
else if(get_status(STATUS_WIP_Msk)){
// A write is in progress, no action to be performed right now
return false;
}
if(*size == 0)
return true;
uint32_t req_address = *address;
uint32_t req_size = *size;
DBG("E 0x%lx %ld\n", req_address, req_size);
// Assumes that erase sizes are sorted: 4 > 3 > 2 > 1.
// Assumes that erase sizes are powers of two.
const flash_cmd_t * erase_cmd[] = {
CMD(ERASE1),
CMD(ERASE2),
CMD(ERASE3),
CMD(ERASE4),
};
for (int i = 3; i >= 0; i--) {
uint32_t erase_size = flash.config->erase_sizes[i];
if (erase_size == 0) {
continue;
}
if ((req_size >= erase_size) && ((req_address & (erase_size - 1)) == 0)) {
*size = req_size - erase_size;
*address = req_address + erase_size;
DBG("Erasing block (%ld): 0x%08lx (%ld left)\n", erase_size, req_address, *size);
OSPI_NOR_WriteEnable();
_OSPI_Erase(erase_cmd[i], req_address);
if(blocking){
wait_for_status(STATUS_WIP_Msk, 0, 0);
}
return (*size == 0);
}
}
DBG("No suitable erase command found for addr=%08lx size=%ld!\n", *address, *size);
assert(!"Unsupported erase operation!");
return false;
}
void OSPI_EraseSync(uint32_t address, uint32_t size)
{
bool ret;
do {
ret = OSPI_Erase(&address, &size, true);
} while (ret == false);
}
void OSPI_PageProgram(uint32_t address,
const uint8_t *buffer,
size_t buffer_size)
{
assert(buffer_size <= 256);
DBG("PP cmd=%02X addr=0x%lx buf=%p len=%d\n", (*CMD(PP)).cmd, address, buffer, buffer_size);
OSPI_WriteBytes(CMD(PP), address, buffer, buffer_size);
// Wait for Write In Progress Bit to become zero
wait_for_status(STATUS_WIP_Msk, 0, TMO_DEFAULT);
}
void OSPI_NOR_WriteEnable(void)
{
OSPI_WriteBytes(CMD(WREN), 0, NULL, 0);
// Wait for Write Enable Latch to be set
wait_for_status(STATUS_WEL_Msk, STATUS_WEL_Msk, TMO_DEFAULT);
}
void OSPI_Program(uint32_t address,
const uint8_t *buffer,
size_t buffer_size)
{
unsigned iterations = (buffer_size + 255) / 256;
unsigned dest_page = address / 256;
assert((address & 0xff) == 0);
for (int i = 0; i < iterations; i++) {
OSPI_NOR_WriteEnable();
OSPI_PageProgram((i + dest_page) * 256,
buffer + (i * 256),
buffer_size > 256 ? 256 : buffer_size);
buffer_size -= 256;
}
}
void OSPI_ReadJedecId(uint8_t dest[3])
{
OSPI_ReadBytes(CMD(RDID), 0, dest, 3);
}
void OSPI_ReadSR(uint8_t dest[1])
{
OSPI_ReadBytes(CMD(RDSR), 0, dest, 1);
}
void OSPI_ReadCR(uint8_t dest[1])
{
OSPI_ReadBytes(CMD(RDCR), 0, dest, 1);
}
static void init_mx_issi(void)
{
// Shared code for both MX and ISSI
uint8_t rd_status;
DBG("%s\n", __FUNCTION__);
OSPI_ReadBytes(CMD(RDSR), 0, &rd_status, 1);
if (flash.config->set_quad && ((rd_status & STATUS_QE_Msk) == 0)) {
// WRSR - Write Status Register
// Set Quad Enable bit (6) in status register. Other bits = 0.
uint8_t wr_status = STATUS_QE_Msk;
DBG("Setting QE bit.\n");
// Set the QE bit
OSPI_NOR_WriteEnable();
OSPI_WriteBytes(CMD(WRSR), 0, &wr_status, 1);
wait_for_status(STATUS_WIP_Msk, 0, TMO_DEFAULT);
OSPI_ReadBytes(CMD(RDSR), 0, &rd_status, 1);
DBG("QE bit set. Status: %02X\n", rd_status);
}
}
static void init_spansion(void)
{
uint8_t rd_sr1;
uint8_t rd_sr2;
uint8_t rd_cr1;
uint8_t rd_cr2;
uint8_t rd_cr3;
uint8_t rd_cr4;
// SR[1-2]V
OSPI_ReadBytes(CMD(RDSR), 0x00, &rd_sr1, 1);
OSPI_ReadBytes(CMD(RDAR), 0x00800001, &rd_sr2, 1);
// CR[1-4]NV
OSPI_ReadBytes(CMD(RDCR), 0x00, &rd_cr1, 1);
OSPI_ReadBytes(CMD(RDAR), 0x03, &rd_cr2, 1);
OSPI_ReadBytes(CMD(RDAR), 0x04, &rd_cr3, 1);
OSPI_ReadBytes(CMD(RDAR), 0x05, &rd_cr4, 1);
DBG("SR1: %02X SR2: %02X CR: %02X %02X %02X %02X\n", rd_sr1, rd_sr2, rd_cr1, rd_cr2, rd_cr3, rd_cr4);
if (flash.config->set_quad && ((rd_cr1 & S_CR_QUAD_Msk) == 0)) {
// WRSR/WRR writes to {status, config}
// Clear SR1V and set bit 1 (QUAD) in CR1NV
uint8_t wr_sr[] = {0x00, S_CR_QUAD_Msk};
DBG("Setting QUAD in CR1V.\n");
// Enable write to be allowed to change the registers
OSPI_NOR_WriteEnable();
OSPI_WriteBytes(CMD(WRSR), 0, wr_sr, sizeof(wr_sr));
// Wait until WIP bit is cleared
wait_for_status(STATUS_WIP_Msk, 0, TMO_DEFAULT);
OSPI_ReadBytes(CMD(RDSR), 0, &rd_sr1, 1);
OSPI_ReadBytes(CMD(RDCR), 0, &rd_cr1, 1);
DBG("QUAD bit set. SR: %02X CR: %02X\n", rd_sr1, rd_cr1);
}
}
static void init_winbond(void)
{
// cmd i_lines a_lines a_size d_lines dummy
const flash_cmd_t cmd_rdsr2 = CMD_DEF(0x35, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0);
const flash_cmd_t cmd_rdsr3 = CMD_DEF(0x15, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0);
const flash_cmd_t cmd_wrsr2 = CMD_DEF(0x31, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0);
const flash_cmd_t cmd_wrsr3 = CMD_DEF(0x11, LINES_1, LINES_0, ADDR_SIZE_24B, LINES_1, 0);
const bool is_quad = flash.config->set_quad;
uint8_t sr1, sr2, sr3;
OSPI_ReadBytes(CMD(RDSR), 0, &sr1, 1);
OSPI_ReadBytes(&cmd_rdsr2, 0, &sr2, 1);
OSPI_ReadBytes(&cmd_rdsr3, 0, &sr3, 1);
DBG("Winbond SR1: %02X SR2: %02X SR3: %02X\n", sr1, sr2, sr3);
// try to clear writeable protect bits if set
if (sr1 & WB_SR1_PROTECT_Msk) {
DBG("clearing SR1 protect bits\n");
sr1 = 0;
OSPI_NOR_WriteEnable();
OSPI_WriteBytes(CMD(WRSR), 0, &sr1, 1);
wait_for_status(STATUS_WIP_Msk, 0, TMO_DEFAULT);
OSPI_ReadBytes(CMD(RDSR), 0, &sr1, 1);
if (sr1 & WB_SR1_PROTECT_Msk)
DBG("SR1: %02X, change failed\n", sr1);
}
if ((sr2 & WB_SR2_PROTECT_Msk) || (is_quad && !(sr2 & 1<<WB_SR2_QE_Pos))) {
DBG("clearing SR2 protect bits\n");
sr2 = 0;
if (is_quad) {
DBG("and enabling quad mode\n");
sr2 = 1<<WB_SR2_QE_Pos;
}
OSPI_NOR_WriteEnable();
OSPI_WriteBytes(&cmd_wrsr2, 0, &sr2, 1);
wait_for_status(STATUS_WIP_Msk, 0, TMO_DEFAULT);
OSPI_ReadBytes(&cmd_rdsr2, 0, &sr2, 1);
if ((sr2 & WB_SR2_PROTECT_Msk) || (is_quad && !(sr2 & 1<<WB_SR2_QE_Pos)))
DBG("SR2: %02X, change failed\n", sr2);
}
if ((sr3 & WB_SR3_PROTECT_Msk) || ((sr3 & WB_SR3_DRV_Msk) != WB_SR3_DRV_Val_50)) {
DBG("clearing SR3 protect bits, setting drive strength 50%%\n");
sr3 = WB_SR3_DRV_Val_50;
OSPI_NOR_WriteEnable();
OSPI_WriteBytes(&cmd_wrsr3, 0, &sr3, 1);
wait_for_status(STATUS_WIP_Msk, 0, TMO_DEFAULT);
OSPI_ReadBytes(&cmd_rdsr3, 0, &sr3, 1);
if ((sr3 & WB_SR3_PROTECT_Msk) || ((sr3 & WB_SR3_DRV_Msk) != WB_SR3_DRV_Val_50))
DBG("SR3: %02X, change failed\n", sr3);
}
if (is_quad && !(sr2 & 1<<WB_SR2_QE_Pos)) {
DBG("Windbond quad mode not enabled, falling back to SPI\n");
flash.config = &config_spi_24b;
flash.name = "Winbond SPI";
}
}
const char* OSPI_GetFlashName(void)
{
return flash.name;
}
uint32_t OSPI_GetSmallestEraseSize(void)
{
// Assumes that erase sizes are sorted: 4 > 3 > 2 > 1.
return flash.config->erase_sizes[0];
}
int OSPI_Init(OSPI_HandleTypeDef *hospi)
{
uint8_t status;
flash.hospi = hospi;
// Enable Reset
OSPI_WriteBytes(CMD(RSTEN), 0, NULL, 0);
HAL_Delay(2);
// Reset
OSPI_WriteBytes(CMD(RST), 0, NULL, 0);
HAL_Delay(20);
// Read ID
OSPI_ReadBytes(CMD(RDID), 0, &flash.jedec_id.u8[0], 3);
DBG("JEDEC_ID: %02X %02X %02X\n", flash.jedec_id.u8[0], flash.jedec_id.u8[1], flash.jedec_id.u8[2]);
// Check for known bad IDs
if (((flash.jedec_id.u32 & 0xffffff) == 0xffffff) ||
((flash.jedec_id.u32 & 0xffffff) == 0x000000)) {
// Can't communicate with the external flash! Please check the soldering.
return -1;
}
OSPI_ReadBytes(CMD(RDSR), 0, &status, 1);
DBG("Status: %02X\n", status);
for (int i = 0; i < ARRAY_SIZE(jedec_map); i++) {
if ((flash.jedec_id.u32 & 0xffffff) == (jedec_map[i].jedec_id.u32 & 0xffffff)) {
flash.config = jedec_map[i].config;
flash.name = jedec_map[i].name;
flash.size = jedec_map[i].size;
DBG("Found config: %s\n", flash.name);
break;
}
}
if (flash.config->init_fn) {
flash.config->init_fn();
}
OSPI_EnableMemoryMappedMode();
return 0;
}
uint32_t OSPI_GetSize(void){
return flash.size;
}