#include #include #include #include #include #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< 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; }