Files
game-and-watch-retro-go/Core/Src/gw_flash.c
James Dull 83ccc2dc4f Add support for 512Mb MX chip variant
Address flashing issue with Macronix MX25U51245G chips that identify using an invalid JEDEC ID (per datasheet).  In at least one user's case, the chips were purchased directly from DigiKey.
2022-01-12 22:33:18 +01:00

862 lines
33 KiB
C

#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "gw_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) \
{ \
.jedec_id.u32 = JEDEC_ID((_x0), (_x1), (_x2)), \
.name = (_name), \
.config = (_config), \
}
// 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;
} 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), // Stock 1MB (Mario)
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x36, "MX25U3232F", &config_quad_24b_mx), // Stock 4MB (Zelda)
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x37, "MX25U6432F", &config_quad_24b_mx), // 8MB
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x38, "MX25U1283xF", &config_quad_24b_mx), // 16MB MX25U12832F, MX25U12835F
// MX 32 bit address
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x39, "MX25U25635F", &config_quad_32b_mx), // 32 MB
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x3A, "MX25U51245G", &config_quad_32b_mx), // 64 MB
JEDEC_CONFIG_DEF(0xC2, 0x24, 0x3A, "MX25U51245G", &config_quad_32b_mx), // 64 MB variant of unknown cause supplied by DigiKey (not listed in datasheet)
JEDEC_CONFIG_DEF(0xC2, 0x95, 0x3A, "MX25U51245G-54", &config_quad_32b_mx54), // 64 MB
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x3B, "MX66U1G45G", &config_quad_32b_mx), // 128 MB
JEDEC_CONFIG_DEF(0xC2, 0x25, 0x3C, "MX66U2G45G", &config_quad_32b_mx), // 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 MB
JEDEC_CONFIG_DEF(0x34, 0x2B, 0x1A, "S25FS512S", &config_quad_32b_s), // 64 MB
// ISSI 24 bit *untested*
// TODO: Test and uncomment when it's confirmed they work well.
JEDEC_CONFIG_DEF(0x9D, 0x70, 0x18, "IS25WP128F", &config_quad_24b_issi), // 16MB
// Winbond 24 bit address
JEDEC_CONFIG_DEF(0xEF, 0x60, 0x18, "W25Q128JW-Q/N", &config_quad_24b_wb), // 16MB, tested with W25Q128JWSIQ
JEDEC_CONFIG_DEF(0xEF, 0x80, 0x18, "W25Q128JW-M", &config_quad_24b_wb), // 16MB
// Winbond 32 bit address
JEDEC_CONFIG_DEF(0xEF, 0x60, 0x20, "W25Q512NW-Q/N", &config_quad_32b_wb), // 64MB
JEDEC_CONFIG_DEF(0xEF, 0x80, 0x20, "W25Q512NW-M", &config_quad_32b_wb), // 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;
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;
// DBG("WB %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();
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 void wait_for_status(uint8_t mask, uint8_t value, uint32_t timeout)
{
uint8_t status;
uint32_t t0 = HAL_GetTick();
do {
OSPI_ReadBytes(CMD(RDSR), 0, &status, 1);
wdog_refresh();
#if 0
printf("Status: %02X\n", status);
HAL_Delay(500);
#endif
if ((timeout > 0) && (HAL_GetTick() > t0 + timeout)) {
assert(!"Status poll timeout!");
break;
}
} while ((status & mask) != value);
}
void OSPI_EnableMemoryMappedMode(void)
{
OSPI_MemoryMappedTypeDef sMemMappedCfg;
OSPI_RegularCmdTypeDef ospi_cmd;
const flash_cmd_t *cmd = CMD(READ);
assert(flash.mem_mapped_enabled == false);
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)
{
assert(flash.mem_mapped_enabled == true);
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);
// Wait for Write In Progress Bit to become zero
wait_for_status(STATUS_WIP_Msk, 0, 0);
}
void OSPI_ChipErase(void)
{
DBG("CE\n");
_OSPI_Erase(CMD(CE), 0); // Chip Erase
}
bool OSPI_Erase(uint32_t *address, uint32_t *size)
{
// 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);
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);
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);
} 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];
}
void 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)) {
assert(!"Can't communicate with the external flash! Please check the soldering.");
}
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;
DBG("Found config: %s\n", flash.name);
break;
}
}
if (flash.config->init_fn) {
flash.config->init_fn();
}
OSPI_EnableMemoryMappedMode();
}