From c23e02c5820a40f90a0b2fb39174978f7cb4fc6e Mon Sep 17 00:00:00 2001 From: Andrey G Date: Sat, 14 Aug 2021 16:36:08 +0300 Subject: [PATCH] Settings ext storage (#3155) * flash_main.cpp: reorder code for easy integration of ext storage * Add EFI_STORAGE_INT_FLASH option Default set to TRUE * Add ChibiOS's Managed Flash Storage to build * Add support for QSPI flash (SST26F064A tested) * board: subaru: enable WSPI and NOR flash drivers * Add option to save settings on ext flash MFS partition * board: subaru: store settings on QSPI NOR flash --- firmware/Makefile | 1 + firmware/config/boards/subaru_eg33/board.mk | 3 + .../config/boards/subaru_eg33/efifeatures.h | 7 + firmware/config/boards/subaru_eg33/halconf.h | 2 + firmware/config/boards/subaru_eg33/mcuconf.h | 8 + firmware/config/stm32f4ems/efifeatures.h | 8 + firmware/controllers/flash_main.cpp | 125 +++- .../hw_layer/drivers/flash/hal_flash_device.c | 637 ++++++++++++++++++ .../hw_layer/drivers/flash/hal_flash_device.h | 225 +++++++ .../hw_layer/drivers/flash/sst26f_jedec.mk | 13 + 10 files changed, 1013 insertions(+), 16 deletions(-) create mode 100644 firmware/hw_layer/drivers/flash/hal_flash_device.c create mode 100644 firmware/hw_layer/drivers/flash/hal_flash_device.h create mode 100644 firmware/hw_layer/drivers/flash/sst26f_jedec.mk diff --git a/firmware/Makefile b/firmware/Makefile index 4f6873e357..c5b89aced8 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -166,6 +166,7 @@ include $(CHIBIOS)/os/ex/devices/ST/lis302dl.mk endif include $(CHIBIOS)/os/hal/lib/streams/streams.mk include $(CHIBIOS)/os/various/cpp_wrappers/chcpp.mk +include $(CHIBIOS)/os/hal/lib/complex/mfs/hal_mfs.mk ifeq ($(USE_FATFS),yes) include $(CHIBIOS)/os/various/fatfs_bindings/fatfs.mk diff --git a/firmware/config/boards/subaru_eg33/board.mk b/firmware/config/boards/subaru_eg33/board.mk index a36549434b..f1901ecd12 100644 --- a/firmware/config/boards/subaru_eg33/board.mk +++ b/firmware/config/boards/subaru_eg33/board.mk @@ -36,3 +36,6 @@ DDEFS += -DUART_USE_WAIT=FALSE ALLCSRC += $(BOARDSRC) ALLCPPSRC += $(BOARDCPPSRC) ALLINC += $(BOARDINC) + +#Serial flash support +include $(PROJECT_DIR)/hw_layer/drivers/flash/sst26f_jedec.mk diff --git a/firmware/config/boards/subaru_eg33/efifeatures.h b/firmware/config/boards/subaru_eg33/efifeatures.h index 2ba7b749e5..c6207ea809 100644 --- a/firmware/config/boards/subaru_eg33/efifeatures.h +++ b/firmware/config/boards/subaru_eg33/efifeatures.h @@ -139,4 +139,11 @@ #define EFI_NARROW_EGO_AVERAGING TRUE +/* this board has external QSPI NOR flash */ +#undef EFI_STORAGE_EXT_SNOR +#define EFI_STORAGE_EXT_SNOR TRUE + +#undef EFI_STORAGE_INT_FLASH +#define EFI_STORAGE_INT_FLASH FALSE + #endif /* EFIFEATURES_SUBARUEG33_H_ */ diff --git a/firmware/config/boards/subaru_eg33/halconf.h b/firmware/config/boards/subaru_eg33/halconf.h index 27d3a31cbd..385b732e86 100644 --- a/firmware/config/boards/subaru_eg33/halconf.h +++ b/firmware/config/boards/subaru_eg33/halconf.h @@ -13,6 +13,8 @@ /* this file is exist just to include mcuconf.h from THIS directory */ #include "mcuconf.h" +#define HAL_USE_WSPI TRUE + #include "../../../hw_layer/ports/stm32/stm32f7/cfg/halconf.h" #endif /* _HALCONF_SUBARUEG33_H_ */ diff --git a/firmware/config/boards/subaru_eg33/mcuconf.h b/firmware/config/boards/subaru_eg33/mcuconf.h index 2f1467f26a..9e3cbe1a78 100644 --- a/firmware/config/boards/subaru_eg33/mcuconf.h +++ b/firmware/config/boards/subaru_eg33/mcuconf.h @@ -76,4 +76,12 @@ //#undef STM32_CAN_CAN1_IRQ_PRIORITY //#define STM32_CAN_CAN1_IRQ_PRIORITY 4 +/* + * WSPI driver system settings. + */ +#define STM32_WSPI_USE_QUADSPI1 TRUE +#define STM32_WSPI_QUADSPI1_DMA_STREAM STM32_DMA_STREAM_ID(2, 7) +#define JEDEC_BUS_MODE JEDEC_BUS_MODE_WSPI4L +#define STM32_WSPI_QUADSPI1_PRESCALER_VALUE 40 + #endif /* _MCUCONF_SUBARUEG33_H_ */ diff --git a/firmware/config/stm32f4ems/efifeatures.h b/firmware/config/stm32f4ems/efifeatures.h index afbc87c882..b10a88a4d6 100644 --- a/firmware/config/stm32f4ems/efifeatures.h +++ b/firmware/config/stm32f4ems/efifeatures.h @@ -403,4 +403,12 @@ #define CONFIG_RESET_SWITCH_PIN 6 #endif +#ifndef EFI_STORAGE_INT_FLASH +#define EFI_STORAGE_INT_FLASH TRUE +#endif + +#ifndef EFI_STORAGE_EXT_SNOR +#define EFI_STORAGE_EXT_SNOR FALSE +#endif + #define EFI_JOYSTICK TRUE diff --git a/firmware/controllers/flash_main.cpp b/firmware/controllers/flash_main.cpp index d7fd2460bf..aa585869ae 100644 --- a/firmware/controllers/flash_main.cpp +++ b/firmware/controllers/flash_main.cpp @@ -20,6 +20,11 @@ #include "tunerstudio.h" #endif +#if EFI_STORAGE_EXT_SNOR == TRUE +#include "hal_serial_nor.h" +#include "hal_mfs.h" +#endif + #include "runtime_state.h" static bool needToWriteConfiguration = false; @@ -28,6 +33,41 @@ extern persistent_config_container_s persistentState; extern engine_configuration_s *engineConfiguration; +/* if we store settings externally */ +#if EFI_STORAGE_EXT_SNOR == TRUE + +/* Some fields in following struct is used for DMA transfers, so do no cache */ +NO_CACHE SNORDriver snor1; + +const WSPIConfig WSPIcfg1 = { + .end_cb = NULL, + .error_cb = NULL, + .dcr = STM32_DCR_FSIZE(23U) | /* 8MB device. */ + STM32_DCR_CSHT(1U) /* NCS 2 cycles delay. */ +}; + +const SNORConfig snorcfg1 = { + .busp = &WSPID1, + .buscfg = &WSPIcfg1 +}; + +/* Managed Flash Storage stuff */ +MFSDriver mfsd; + +const MFSConfig mfsd_nor_config = { + .flashp = (BaseFlash *)&snor1, + .erased = 0xFFFFFFFFU, + .bank_size = 64 * 1024U, + .bank0_start = 0U, + .bank0_sectors = 128U, /* 128 * 4 K = 0.5 Mb */ + .bank1_start = 128U, + .bank1_sectors = 128U +}; + +#define EFI_MSF_SETTINGS_RECORD_ID 1 + +#endif + /** * https://sourceforge.net/p/rusefi/tickets/335/ * @@ -108,6 +148,8 @@ int eraseAndFlashCopy(flashaddr_t storageAddress, const TStorage& data) { bool burnWithoutFlash = false; void writeToFlashNow(void) { + bool isSuccess = false; + if (burnWithoutFlash) { needToWriteConfiguration = false; return; @@ -119,12 +161,27 @@ void writeToFlashNow(void) { persistentState.version = FLASH_DATA_VERSION; persistentState.value = flashStateCrc(&persistentState); +#if EFI_STORAGE_EXT_SNOR == TRUE + mfs_error_t err; + /* In case of MFS: + * do we need to have two copies? + * do we need to protect it with CRC? */ + + err = mfsWriteRecord(&mfsd, EFI_MSF_SETTINGS_RECORD_ID, + sizeof(persistentState), (uint8_t *)&persistentState); + + if (err == MFS_NO_ERROR) + isSuccess = true; +#endif + +#if EFI_STORAGE_INT_FLASH == TRUE // Flash two copies int result1 = eraseAndFlashCopy(getFlashAddrFirstCopy(), persistentState); int result2 = eraseAndFlashCopy(getFlashAddrSecondCopy(), persistentState); // handle success/failure - bool isSuccess = (result1 == FLASH_RETURN_SUCCESS) && (result2 == FLASH_RETURN_SUCCESS); + isSuccess = (result1 == FLASH_RETURN_SUCCESS) && (result2 == FLASH_RETURN_SUCCESS); +#endif if (isSuccess) { efiPrintf("FLASH_SUCCESS"); @@ -181,7 +238,39 @@ static persisted_configuration_state_e doReadConfiguration(flashaddr_t address) * connectivity so no console output here */ static persisted_configuration_state_e readConfiguration() { + persisted_configuration_state_e result = CRC_FAILED; + efiAssert(CUSTOM_ERR_ASSERT, getCurrentRemainingStack() > EXPECTED_REMAINING_STACK, "read f", PC_ERROR); + +#if EFI_STORAGE_EXT_SNOR == TRUE + mfs_error_t err; + size_t settings_size = sizeof(persistentState); + err = mfsReadRecord(&mfsd, EFI_MSF_SETTINGS_RECORD_ID, + &settings_size, (uint8_t *)&persistentState); + + if ((err == MFS_NO_ERROR) && (sizeof(persistentState) == settings_size)) + result = PC_OK; +#endif + +#if EFI_STORAGE_INT_FLASH == TRUE + auto firstCopyAddr = getFlashAddrFirstCopy(); + auto secondyCopyAddr = getFlashAddrSecondCopy(); + + result = doReadConfiguration(firstCopyAddr); + + if (result != PC_OK) { + efiPrintf("Reading second configuration copy"); + result = doReadConfiguration(secondyCopyAddr); + } +#endif + + return result; +} + +void readFromFlash() { + persisted_configuration_state_e result = PC_OK; + +#if HW_CHECK_MODE /* * getFlashAddr does device validation, we want validation to be invoked even while we are * HW_CHECK_MODE mode where we would not need actual address @@ -190,16 +279,10 @@ static persisted_configuration_state_e readConfiguration() { auto firstCopyAddr = getFlashAddrFirstCopy(); auto secondyCopyAddr = getFlashAddrSecondCopy(); -#if HW_CHECK_MODE - persisted_configuration_state_e result = PC_OK; resetConfigurationExt(DEFAULT_ENGINE_TYPE PASS_ENGINE_PARAMETER_SUFFIX); -#else // HW_CHECK_MODE - persisted_configuration_state_e result = doReadConfiguration(firstCopyAddr); - - if (result != PC_OK) { - efiPrintf("Reading second configuration copy"); - result = doReadConfiguration(secondyCopyAddr); - } +#else + result = readConfiguration(); +#endif if (result == CRC_FAILED) { // we are here on first boot on brand new chip @@ -213,16 +296,11 @@ static persisted_configuration_state_e readConfiguration() { */ applyNonPersistentConfiguration(PASS_ENGINE_PARAMETER_SIGNATURE); } -#endif // HW_CHECK_MODE + // we can only change the state after the CRC check engineConfiguration->byFirmwareVersion = getRusEfiVersion(); memset(persistentState.persistentConfiguration.warning_message , 0, ERROR_BUFFER_SIZE); validateConfiguration(PASS_ENGINE_PARAMETER_SIGNATURE); - return result; -} - -void readFromFlash() { - persisted_configuration_state_e result = readConfiguration(); if (result == CRC_FAILED) { efiPrintf("Need to reset flash to default due to CRC"); @@ -249,6 +327,21 @@ static void writeConfigCommand() { } void initFlash() { +#if EFI_STORAGE_EXT_SNOR == TRUE + mfs_error_t err; + + /* Initializing and starting snor1 driver.*/ + snorObjectInit(&snor1); + snorStart(&snor1, &snorcfg1); + + /* MFS */ + mfsObjectInit(&mfsd); + err = mfsStart(&mfsd, &mfsd_nor_config); + if (err != MFS_NO_ERROR) { + /* hm...? */ + } +#endif + addConsoleAction("readconfig", readFromFlash); /** * This would write NOW (you should not be doing this while connected to real engine) diff --git a/firmware/hw_layer/drivers/flash/hal_flash_device.c b/firmware/hw_layer/drivers/flash/hal_flash_device.c new file mode 100644 index 0000000000..e3aada7c38 --- /dev/null +++ b/firmware/hw_layer/drivers/flash/hal_flash_device.c @@ -0,0 +1,637 @@ +/* + * hal_flash_device.h + * + * QSPI NOR flash driver with JEDEC SFDP for ChibiOS + * Tested and developed with Microchip SST26F064A + * + * @date Aug 14, 2021 + * @author Andrey Gusakov, (c) 2021 + * + * Based on ChibiOS drivers: Macronix MX25 and Micron N25Q + * ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio + * + */ + +/** + * @file hal_flash_device.c + * @brief Jedec JESD216 SFDP code. + * + * @addtogroup JEDEC_SFDP + * @{ + */ + +#include + +#include "hal.h" +#include "hal_serial_nor.h" + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver exported variables. */ +/*===========================================================================*/ + +/** + * @brief Flash descriptor. + */ +flash_descriptor_t snor_descriptor = { + .attributes = FLASH_ATTR_ERASED_IS_ONE | FLASH_ATTR_REWRITABLE | + FLASH_ATTR_SUSPEND_ERASE_CAPABLE, + .page_size = 256U, + .sectors_count = 0U, /* It is overwritten.*/ + .sectors = NULL, + .sectors_size = 0U, /* It is overwritten.*/ + .address = 0U, + .size = 0U /* It is overwritten.*/ +}; + +/* NOT TESTED YET */ +#if (SNOR_BUS_DRIVER == SNOR_BUS_DRIVER_WSPI) || defined(__DOXYGEN__) +#if (WSPI_SUPPORTS_MEMMAP == TRUE) || defined(__DOXYGEN__) +/** + * @brief Fast read command for memory mapped mode. + */ +const wspi_command_t snor_memmap_read = { + .cmd = JEDEC_CMD_READ, + .addr = 0, + .dummy = 0, //JEDEC_READ_DUMMY_CYCLES, + .cfg = WSPI_CFG_ADDR_SIZE_24 | +#if JEDEC_BUS_MODE == JEDEC_BUS_MODE_WSPI1L + WSPI_CFG_CMD_MODE_ONE_LINE | + WSPI_CFG_ADDR_MODE_ONE_LINE | + WSPI_CFG_DATA_MODE_ONE_LINE | +#elif JEDEC_BUS_MODE == JEDEC_BUS_MODE_WSPI2L + WSPI_CFG_CMD_MODE_TWO_LINES | + WSPI_CFG_ADDR_MODE_TWO_LINES | + WSPI_CFG_DATA_MODE_TWO_LINES | +#else + WSPI_CFG_CMD_MODE_FOUR_LINES | + WSPI_CFG_ADDR_MODE_FOUR_LINES | + WSPI_CFG_DATA_MODE_FOUR_LINES | +#endif + WSPI_CFG_ALT_MODE_NONE +}; +#endif +#endif + +/*===========================================================================*/ +/* Driver local variables and types. */ +/*===========================================================================*/ + +/* Hack to keep ChibiOS sources untouched and use on MCU with data cache: + * read/write through temp buffer in non-cached ram. */ +#define NO_CACHE __attribute__((section(".ram2"))) +static NO_CACHE uint8_t tmpbuf[256] __attribute__((aligned (32))); + +/* Buffer for SFDP parsing */ +static uint32_t sfdpbuf[64 / 4]; + +/* JEDEC read command.*/ +static wspi_command_t jedec_cmd_read; + +/* JEDEC erase command.*/ +static wspi_command_t jedec_cmd_erase; + +/* JEDEC page program command.*/ +static wspi_command_t jedec_cmd_program; + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +static flash_error_t jedec_poll_status(SNORDriver *devp) { + uint8_t sts; + + do { +#if JEDEC_NICE_WAITING == TRUE + osalThreadSleepMilliseconds(1); +#endif + /* Read status command.*/ + bus_cmd_receive(devp->config->busp, JEDEC_CMD_READ_STATUS_REGISTER, + 1, tmpbuf); + sts = tmpbuf[0]; + } while ((sts & JEDEC_FLAGS_STS_BUSY) != 0U); + + return FLASH_NO_ERROR; +} + +#if (SNOR_BUS_DRIVER == SNOR_BUS_DRIVER_WSPI) || defined(__DOXYGEN__) +static void jedec_reset_memory(SNORDriver *devp) { + + /* 1x JEDEC_CMD_RESET_ENABLE command.*/ + static const wspi_command_t cmd_reset_enable_1 = { + .cmd = JEDEC_CMD_RESET_ENABLE, + .cfg = WSPI_CFG_CMD_MODE_ONE_LINE, + .addr = 0, + .alt = 0, + .dummy = 0 + }; + + /* 1x JEDEC_CMD_RESET_MEMORY command.*/ + static const wspi_command_t cmd_reset_memory_1 = { + .cmd = JEDEC_CMD_RESET_MEMORY, + .cfg = WSPI_CFG_CMD_MODE_ONE_LINE, + .addr = 0, + .alt = 0, + .dummy = 0 + }; + + /* If the device is in one bit mode then the following commands are + rejected because shorter than 8 bits. If the device is in multiple + bits mode then the commands are accepted and the device is reset to + one bit mode.*/ +#if JEDEC_BUS_MODE == JEDEC_BUS_MODE_WSPI4L + /* 4x JEDEC_CMD_RESET_ENABLE command.*/ + static const wspi_command_t cmd_reset_enable_4 = { + .cmd = JEDEC_CMD_RESET_ENABLE, + .cfg = WSPI_CFG_CMD_MODE_FOUR_LINES, + .addr = 0, + .alt = 0, + .dummy = 0 + }; + + /* 4x JEDEC_CMD_RESET_MEMORY command.*/ + static const wspi_command_t cmd_reset_memory_4 = { + .cmd = JEDEC_CMD_RESET_MEMORY, + .cfg = WSPI_CFG_CMD_MODE_FOUR_LINES, + .addr = 0, + .alt = 0, + .dummy = 0 + }; + + wspiCommand(devp->config->busp, &cmd_reset_enable_4); + wspiCommand(devp->config->busp, &cmd_reset_memory_4); +#elif JEDEC_BUS_MODE == JEDEC_BUS_MODE_WSPI4L + /* 2x JEDEC_CMD_RESET_ENABLE command.*/ + static const wspi_command_t cmd_reset_enable_2 = { + .cmd = JEDEC_CMD_RESET_ENABLE, + .cfg = WSPI_CFG_CMD_MODE_TWO_LINES, + .addr = 0, + .alt = 0, + .dummy = 0 + }; + + /* 2x JEDEC_CMD_RESET_MEMORY command.*/ + static const wspi_command_t cmd_reset_memory_2 = { + .cmd = JEDEC_CMD_RESET_MEMORY, + .cfg = WSPI_CFG_CMD_MODE_TWO_LINES, + .addr = 0, + .alt = 0, + .dummy = 0 + }; + + wspiCommand(devp->config->busp, &cmd_reset_enable_2); + wspiCommand(devp->config->busp, &cmd_reset_memory_2); +#endif + + /* Now the device should be in one bit mode for sure and we perform a + device reset.*/ + wspiCommand(devp->config->busp, &cmd_reset_enable_1); + wspiCommand(devp->config->busp, &cmd_reset_memory_1); +} +#endif /* SNOR_BUS_DRIVER == SNOR_BUS_DRIVER_WSPI */ + +static void jedec_set_config(SNORDriver *devp, uint8_t val) +{ + tmpbuf[0] = 0; + tmpbuf[1] = val; + + bus_cmd_send(devp->config->busp, JEDEC_CMD_WRITE_STATUS_REGISTER, 2, tmpbuf); +} + +static uint8_t jedec_get_config(SNORDriver *devp) { + /* Read status command.*/ + bus_cmd_receive(devp->config->busp, JEDEC_CMD_READ_CONFIGURATION_REGISTER, + 1, tmpbuf); + return tmpbuf[0]; +} + +static void jedec_write_enable(SNORDriver *devp, int enable) { + /* Enabling write operation.*/ + bus_cmd(devp->config->busp, enable ? JEDEC_CMD_WRITE_ENABLE : JEDEC_CMD_WRITE_DISABLE); +} + +static void snor_device_fill_cmd(wspi_command_t *cmd, + uint32_t cfg, uint8_t opcode, + uint8_t mode_clocks, uint8_t dummy_clocks) +{ + cmd->cmd = opcode; + cmd->cfg = cfg; + cmd->dummy = 0; + cmd->alt = 0; /* ? */ + + if (mode_clocks) { + uint8_t mode_bytes = 0; + + /* Alt bytes mode - same as address */ + if ((cmd->cfg & WSPI_CFG_ADDR_MODE_MASK) == WSPI_CFG_ADDR_MODE_ONE_LINE) { + cmd->cfg |= WSPI_CFG_ALT_MODE_ONE_LINE; + mode_bytes = mode_clocks / 8; + } else if ((cmd->cfg & WSPI_CFG_ADDR_MODE_MASK) == WSPI_CFG_ADDR_MODE_TWO_LINES) { + cmd->cfg |= WSPI_CFG_ALT_MODE_TWO_LINES; + mode_bytes = mode_clocks / 4; + } else if ((cmd->cfg & WSPI_CFG_ADDR_MODE_MASK) == WSPI_CFG_ADDR_MODE_FOUR_LINES) { + cmd->cfg |= WSPI_CFG_ALT_MODE_FOUR_LINES; + mode_bytes = mode_clocks / 2; + } /* else if ((cmd->cfg & WSPI_CFG_ADDR_MODE_MASK) == WSPI_CFG_ADDR_MODE_EIGHT_LINES){ + cmd->cfg |= WSPI_CFG_ALT_MODE_EIGHT_LINES; + mode_bytes = mode_clocks / 1; + } */ + + if (mode_bytes == 1) + cmd->cfg |= WSPI_CFG_ALT_SIZE_8; + else if (mode_bytes == 2) + cmd->cfg |= WSPI_CFG_ALT_SIZE_16; + else if (mode_bytes == 3) + cmd->cfg |= WSPI_CFG_ALT_SIZE_24; + else if (mode_bytes == 4) + cmd->cfg |= WSPI_CFG_ALT_SIZE_32; + else + osalDbgAssert(0, "Failed to calculate alternative bytes size"); + } else { + cmd->cfg |= WSPI_CFG_ALT_MODE_NONE; + } + + cmd->dummy = dummy_clocks; +} + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +void snor_device_init(SNORDriver *devp) { + int i; + uint8_t cfg; + uint8_t parameter_headers_n; + /* use as temp buffer, should be at least 64 bytes */ + uint8_t *buf = (uint8_t *)sfdpbuf; + uint32_t *sfdp = sfdpbuf; + /* offset in sfdp area */ + flash_offset_t offset = 0; + const uint8_t sfdp_sign[4] = {0x53, 0x46, 0x44, 0x50}; /* "SFDP" */ + +#if SNOR_BUS_DRIVER == SNOR_BUS_DRIVER_WSPI + /* Attempting a reset of the XIP mode, it could be in an unexpected state + because a CPU reset does not reset the memory too.*/ + //snor_reset_xip(devp); + + /* Attempting a reset of the device, it could be in an unexpected state + because a CPU reset does not reset the memory too.*/ + jedec_reset_memory(devp); +#endif /* SNOR_BUS_DRIVER == SNOR_BUS_DRIVER_WSPI */ + + /* SST26VF specific: adjust configuration register */ + cfg = jedec_get_config(devp); + /* disable WP and HOLD */ + cfg |= (1 << 1); + /* WP# disable */ + cfg &= ~(1 << 7); + jedec_set_config(devp, cfg); + + /* Global Block Protection Unlock */ + jedec_write_enable(devp, 1); + bus_cmd(devp->config->busp, JEDEC_CMD_GLOBAL_BLOCK_PROTECTION_UNLOCK); + + /* Reading SFDP Header. */ + snor_device_read_sfdp(devp, offset, 8, buf); + + /* Checking if the device supports SFDP. */ + osalDbgAssert(memcmp(sfdp_sign, buf, 4) == 0, + "chip does not support SFDP"); + + /* Find JEDEC Flash Parameter Header */ + parameter_headers_n = buf[6]; + for (i = 0; i < parameter_headers_n; i++) { + int length; + /* each header is 8 bytes lont + 8 bytes of SFDP header */ + offset = 8 + (i * 8); + + snor_device_read_sfdp(devp, offset, 8, buf); + if (buf[0] != 0x00) { + /* vendor-specific header - skip. */ + continue; + } + + /* get Parameter Table Pointer */ + offset = buf[4] | (buf[5] << 8) | (buf[6] << 16); + /* and length */ + length = buf[3] * 4; /* in DWORDs */ + + if (length != 0x40) + continue; + + snor_device_read_sfdp(devp, offset, length, buf); + + break; + } + + osalDbgAssert(i != parameter_headers_n, + "JEDEC SFDP parameters block not found"); + + /* Setting up the device sizes.*/ + /* Chip density defined in bits */ + if (sfdp[1] & 0x80000000) + /* more than 4 gigabits */ + snor_descriptor.size = (size_t)(1 << ((sfdp[1] & 0x7fffffff) - 3)); + else + snor_descriptor.size = (size_t)((sfdp[1] + 1) >> 3); + /* Use sector size 1, assume smalest */ + snor_descriptor.sectors_size = (size_t)1 << (sfdp[7] & 0xff); + snor_descriptor.sectors_count = snor_descriptor.size / + snor_descriptor.sectors_size; + + /* Fastest read command */ + /* TODO: add 4-4-4 and 2-2-2 support */ + if (sfdp[0] & (1 << 21)) { + /* 1-4-4 */ + snor_device_fill_cmd(&jedec_cmd_read, + WSPI_CFG_CMD_MODE_ONE_LINE | WSPI_CFG_CMD_SIZE_8 | + WSPI_CFG_ADDR_MODE_FOUR_LINES | + WSPI_CFG_DATA_MODE_FOUR_LINES, + (sfdp[2] >> 8) & 0xff, (sfdp[2] >> 5) & 0x07, (sfdp[2] >> 0) & 0x1f); + } else if (sfdp[0] & (1 << 22)) { + /* 1-1-4 */ + snor_device_fill_cmd(&jedec_cmd_read, + WSPI_CFG_CMD_MODE_ONE_LINE | WSPI_CFG_CMD_SIZE_8 | + WSPI_CFG_ADDR_MODE_ONE_LINE | + WSPI_CFG_DATA_MODE_FOUR_LINES, + (sfdp[2] >> 24) & 0xff, (sfdp[2] >> 21) & 0x07, (sfdp[2] >> 16) & 0x1f); + } else if (sfdp[0] & (1 << 20)) { + /* 1-2-2 */ + snor_device_fill_cmd(&jedec_cmd_read, + WSPI_CFG_CMD_MODE_ONE_LINE | WSPI_CFG_CMD_SIZE_8 | + WSPI_CFG_ADDR_MODE_TWO_LINES | + WSPI_CFG_DATA_MODE_TWO_LINES, + (sfdp[3] >> 24) & 0xff, (sfdp[3] >> 21) & 0x07, (sfdp[3] >> 16) & 0x1f); + } else if (sfdp[0] & (1 << 16)) { + /* 1-1-2 */ + snor_device_fill_cmd(&jedec_cmd_read, + WSPI_CFG_CMD_MODE_ONE_LINE | WSPI_CFG_CMD_SIZE_8 | + WSPI_CFG_ADDR_MODE_ONE_LINE | + WSPI_CFG_DATA_MODE_TWO_LINES, + (sfdp[3] >> 8) & 0xff, (sfdp[3] >> 5) & 0x07, (sfdp[3] >> 0) & 0x1f); + } + if (1) { + /* Fallback to 1-1-1 */ + snor_device_fill_cmd(&jedec_cmd_read, + WSPI_CFG_CMD_MODE_ONE_LINE | WSPI_CFG_CMD_SIZE_8 | + WSPI_CFG_ADDR_MODE_ONE_LINE | + WSPI_CFG_DATA_MODE_ONE_LINE, + JEDEC_CMD_READ, 0, 0); + } + + /* TODO: get from SFDP */ + snor_device_fill_cmd(&jedec_cmd_erase, + WSPI_CFG_CMD_MODE_ONE_LINE | WSPI_CFG_CMD_SIZE_8 | + WSPI_CFG_ADDR_MODE_ONE_LINE, + JEDEC_CMD_SUBSECTOR_ERASE, 0, 0); + + snor_device_fill_cmd(&jedec_cmd_program, + WSPI_CFG_CMD_MODE_ONE_LINE | WSPI_CFG_CMD_SIZE_8 | + WSPI_CFG_ADDR_MODE_ONE_LINE | + WSPI_CFG_DATA_MODE_ONE_LINE, + JEDEC_CMD_PAGE_PROGRAM, 0, 0); + + /* TODO: how to check addressing in SFDP? */ + if (1) { + jedec_cmd_read.cfg |= WSPI_CFG_ADDR_SIZE_24; + jedec_cmd_erase.cfg |= WSPI_CFG_ADDR_SIZE_24; + jedec_cmd_program.cfg |= WSPI_CFG_ADDR_SIZE_24; + } else { + jedec_cmd_read.cfg |= WSPI_CFG_ADDR_SIZE_32; + jedec_cmd_erase.cfg |= WSPI_CFG_ADDR_SIZE_32; + jedec_cmd_program.cfg |= WSPI_CFG_ADDR_SIZE_32; + } +} + +flash_error_t snor_device_read(SNORDriver *devp, flash_offset_t offset, + size_t n, uint8_t *rp) { + while (n) { + size_t chunk = n < sizeof(tmpbuf) ? n : sizeof(tmpbuf); + + jedec_cmd_read.addr = offset; + /* read through non-cached buffer */ + if (wspiReceive(devp->config->busp, &jedec_cmd_read, chunk, tmpbuf)) + return FLASH_ERROR_READ; + + memcpy(rp, tmpbuf, chunk); + offset += chunk; + rp += chunk; + n -= chunk; + } + + return FLASH_NO_ERROR; +} + +flash_error_t snor_device_program(SNORDriver *devp, flash_offset_t offset, + size_t n, const uint8_t *pp) { + /* Data is programmed page by page.*/ + while (n > 0U) { + flash_error_t err; + + /* Data size that can be written in a single program page operation.*/ + size_t chunk = (size_t)(((offset | (snor_descriptor.page_size - 1)) + 1U) - offset); + if (chunk > n) + chunk = n; + + /* send through non-cached buffer */ + memcpy(tmpbuf, pp, chunk); + + /* Enabling write operation.*/ + jedec_write_enable(devp, 1); + + /* Page program command.*/ + jedec_cmd_program.addr = offset; + wspiSend(devp->config->busp, &jedec_cmd_program, chunk, tmpbuf); + + /* Wait for status and check errors.*/ + err = jedec_poll_status(devp); + if (err != FLASH_NO_ERROR) { + return err; + } + + /* Next page.*/ + offset += chunk; + pp += chunk; + n -= chunk; + } + + return FLASH_NO_ERROR; +} + +flash_error_t snor_device_start_erase_all(SNORDriver *devp) { + + /* Enabling write operation.*/ + jedec_write_enable(devp, 1); + + /* Bulk erase command.*/ + bus_cmd(devp->config->busp, JEDEC_CMD_BULK_ERASE); + + return FLASH_NO_ERROR; +} + +flash_error_t snor_device_start_erase_sector(SNORDriver *devp, + flash_sector_t sector) { + flash_offset_t offset = (flash_offset_t)(sector * snor_descriptor.sectors_size); + + /* Enabling write operation.*/ + jedec_write_enable(devp, 1); + + /* Sector erase command.*/ + jedec_cmd_erase.addr = offset; + wspiCommand(devp->config->busp, &jedec_cmd_erase); + + return FLASH_NO_ERROR; +} + +flash_error_t snor_device_verify_erase(SNORDriver *devp, + flash_sector_t sector) { + flash_offset_t offset; + size_t n; + + /* Read command.*/ + offset = (flash_offset_t)(sector * snor_descriptor.sectors_size); + n = snor_descriptor.sectors_size; + while (n > 0U) { + size_t i; + size_t chunk = n < sizeof(tmpbuf) ? n : sizeof(tmpbuf); + + jedec_cmd_read.addr = offset; + /* read through non-cached buffer */ + if (wspiReceive(devp->config->busp, &jedec_cmd_read, chunk, tmpbuf)) { + return FLASH_ERROR_READ; + } + + /* Checking for erased state of current buffer.*/ + for (i = 0; i < chunk; i++) { + if (tmpbuf[i] != 0xFFU) { + /* Ready state again.*/ + devp->state = FLASH_READY; + + return FLASH_ERROR_VERIFY; + } + } + + offset += chunk; + n -= chunk; + } + + return FLASH_NO_ERROR; +} + +flash_error_t snor_device_query_erase(SNORDriver *devp, uint32_t *msec) { + uint8_t sts; + + /* Read status command.*/ + bus_cmd_receive(devp->config->busp, JEDEC_CMD_READ_STATUS_REGISTER, + 1, tmpbuf); + sts = tmpbuf[0]; + + /* Busy?.*/ + if ((sts & JEDEC_FLAGS_STS_BUSY) != 0U) { + /* Recommended time before polling again, this is a simplified + implementation.*/ + if (msec != NULL) { + *msec = 1U; + } + + return FLASH_BUSY_ERASING; + } + + return FLASH_NO_ERROR; +} + +flash_error_t snor_device_read_sfdp(SNORDriver *devp, flash_offset_t offset, + size_t n, uint8_t *rp) { + /* JEDEC SFDP read command.*/ + wspi_command_t jedec_cmd_read_sfdp = { + .cmd = JEDEC_CMD_READ_DISCOVERY_PARAMETER, + .cfg = WSPI_CFG_CMD_MODE_ONE_LINE | + WSPI_CFG_CMD_SIZE_8 | + WSPI_CFG_ADDR_MODE_ONE_LINE | + WSPI_CFG_ADDR_SIZE_24 | + WSPI_CFG_ALT_MODE_NONE | + WSPI_CFG_DATA_MODE_ONE_LINE, + .addr = 0, + .alt = 0, + .dummy = 8 /* cycles, not bytes! */ + }; + + while (n) { + size_t chunk = n < sizeof(tmpbuf) ? n : sizeof(tmpbuf); + + jedec_cmd_read_sfdp.addr = offset; + /* read through non-cached buffer */ + if (wspiReceive(devp->config->busp, &jedec_cmd_read_sfdp, chunk, tmpbuf)) + return FLASH_ERROR_HW_FAILURE; + + memcpy(rp, tmpbuf, chunk); + + offset += chunk; + rp += chunk; + n -= chunk; + } + + return FLASH_NO_ERROR; +} + +#if (SNOR_BUS_DRIVER == SNOR_BUS_DRIVER_WSPI) || defined(__DOXYGEN__) +void snor_activate_xip(SNORDriver *devp) { + (void)devp; +#if 0 + static const uint8_t flash_status_xip[1] = { + (JEDEC_READ_DUMMY_CYCLES << 4U) | 0x07U + }; + + /* Activating XIP mode in the device.*/ + jedec_write_enable(devp, 1); + bus_cmd_send(devp->config->busp, JEDEC_CMD_WRITE_V_CONF_REGISTER, + 1, flash_status_xip); +#endif +} + +void snor_reset_xip(SNORDriver *devp) { + (void)devp; +#if 0 + static const uint8_t flash_conf[1] = { + (JEDEC_READ_DUMMY_CYCLES << 4U) | 0x0FU + }; + wspi_command_t cmd; + uint8_t buf[1]; + + /* Resetting XIP mode by reading one byte without XIP confirmation bit.*/ + cmd.cmd = 0U; + cmd.alt = 0xFFU; + cmd.addr = 0U; + cmd.dummy = JEDEC_READ_DUMMY_CYCLES - 2U; + cmd.cfg = WSPI_CFG_CMD_MODE_NONE | + WSPI_CFG_ADDR_SIZE_24 | +#if JEDEC_BUS_MODE == JEDEC_BUS_MODE_WSPI1L + WSPI_CFG_ADDR_MODE_ONE_LINE | + WSPI_CFG_DATA_MODE_ONE_LINE | +#elif JEDEC_BUS_MODE == JEDEC_BUS_MODE_WSPI2L + WSPI_CFG_ADDR_MODE_TWO_LINES | + WSPI_CFG_DATA_MODE_TWO_LINES | +#elif JEDEC_BUS_MODE == JEDEC_BUS_MODE_WSPI4L + WSPI_CFG_ADDR_MODE_FOUR_LINES | + WSPI_CFG_DATA_MODE_FOUR_LINES | +#else + WSPI_CFG_ADDR_MODE_EIGHT_LINES | + WSPI_CFG_DATA_MODE_EIGHT_LINES | +#endif + WSPI_CFG_ALT_MODE_FOUR_LINES | /* Always 4 lines, note.*/ + WSPI_CFG_ALT_SIZE_8; + wspiReceive(devp->config->busp, &cmd, 1, buf); + + /* Enabling write operation.*/ + jedec_write_enable(devp, 1); + /* Rewriting volatile configuration register.*/ + bus_cmd_send(devp->config->busp, JEDEC_CMD_WRITE_V_CONF_REGISTER, + 1, flash_conf); +#endif +} +#endif /* SNOR_BUS_DRIVER == SNOR_BUS_DRIVER_WSPI */ + +/** @} */ diff --git a/firmware/hw_layer/drivers/flash/hal_flash_device.h b/firmware/hw_layer/drivers/flash/hal_flash_device.h new file mode 100644 index 0000000000..1d15912dbd --- /dev/null +++ b/firmware/hw_layer/drivers/flash/hal_flash_device.h @@ -0,0 +1,225 @@ +/* + * hal_flash_device.h + * + * QSPI NOR flash driver with JEDEC SFDP for ChibiOS + * Tested and developed with Microchip SST26F064A + * + * @date Aug 14, 2021 + * @author Andrey Gusakov, (c) 2021 + */ + +/** + * @file hal_flash_device.h + * @brief Jedec JESD216 SFDP + * + * @addtogroup JEDEC_SFDP + * @{ + */ + +#ifndef HAL_FLASH_DEVICE_H +#define HAL_FLASH_DEVICE_H + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/** + * @name Device capabilities + * @{ + */ +#define SNOR_DEVICE_SUPPORTS_XIP TRUE +/** @} */ + +/** + * @name Device identification + * @{ + */ + +/** @} */ + +/** + * @name Command codes + * @{ + */ + +#define JEDEC_CMD_READ_DISCOVERY_PARAMETER 0x5A + +/* default/fallback commands */ +#define JEDEC_CMD_WRITE_STATUS_REGISTER 0x01 +#define JEDEC_CMD_PAGE_PROGRAM 0x02 +#define JEDEC_CMD_READ 0x03 +#define JEDEC_CMD_WRITE_DISABLE 0x04 +#define JEDEC_CMD_READ_STATUS_REGISTER 0x05 +#define JEDEC_CMD_WRITE_ENABLE 0x06 +#define JEDEC_CMD_SUBSECTOR_ERASE 0x20 +#define JEDEC_CMD_READ_CONFIGURATION_REGISTER 0x35 +#define JEDEC_CMD_BULK_ERASE 0xC7 +#define JEDEC_CMD_RESET_ENABLE 0x66 +#define JEDEC_CMD_GLOBAL_BLOCK_PROTECTION_UNLOCK 0x98 +#define JEDEC_CMD_RESET_MEMORY 0x99 +/** @} */ + +/** + * @name Flags status register bits + * @{ + */ +#define JEDEC_FLAGS_STS_BUSY 0x80U +/** @} */ + +/** + * @name Bus interface modes. + * @{ + */ +#define JEDEC_BUS_MODE_WSPI1L 1U +#define JEDEC_BUS_MODE_WSPI2L 2U +#define JEDEC_BUS_MODE_WSPI4L 4U +/** @} */ + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/** + * @brief Switch WSPI bus width on initialization. + * @details A bus width initialization is performed by writing the + * Enhanced Volatile Configuration Register. If the flash + * device is configured using the Non Volatile Configuration + * Register then this option is not required. + * @note This option is only valid in WSPI bus mode. + */ +#if !defined(JEDEC_SWITCH_WIDTH) || defined(__DOXYGEN__) +#define JEDEC_SWITCH_WIDTH TRUE +#endif + +/** + * @brief Device bus mode to be used. + * #note if @p JEDEC_SWITCH_WIDTH is @p FALSE then this is the bus mode + * that the device is expected to be using. + * #note if @p JEDEC_SWITCH_WIDTH is @p TRUE then this is the bus mode + * that the device will be switched in. + * @note This option is only valid in WSPI bus mode. + */ +#if !defined(JEDEC_BUS_MODE) || defined(__DOXYGEN__) +#define JEDEC_BUS_MODE JEDEC_BUS_MODE_WSPI4L +#endif + +/** + * @brief Delays insertions. + * @details If enabled this options inserts delays into the flash waiting + * routines releasing some extra CPU time for threads with lower + * priority, this may slow down the driver a bit however. + */ +#if !defined(JEDEC_NICE_WAITING) || defined(__DOXYGEN__) +#define JEDEC_NICE_WAITING TRUE +#endif + +/** + * @brief Uses 4kB sub-sectors rather than 64kB sectors. + */ +#if !defined(JEDEC_USE_SUB_SECTORS) || defined(__DOXYGEN__) +#define JEDEC_USE_SUB_SECTORS FALSE +#endif + +/** + * @brief Number of dummy cycles for fast read (1..15). + * @details This is the number of dummy cycles to be used for fast read + * operations. + */ +#if !defined(JEDEC_READ_DUMMY_CYCLES) || defined(__DOXYGEN__) +#define JEDEC_READ_DUMMY_CYCLES 8 +#endif + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +#if (JEDEC_READ_DUMMY_CYCLES < 1) || (JEDEC_READ_DUMMY_CYCLES > 15) +#error "invalid JEDEC_READ_DUMMY_CYCLES value (1..15)" +#endif + +/** + * @brief WSPI settings for command only. + */ +#define SNOR_WSPI_CFG_CMD (WSPI_CFG_CMD_MODE_ONE_LINE | \ + WSPI_CFG_ADDR_MODE_NONE | \ + WSPI_CFG_ALT_MODE_NONE | \ + WSPI_CFG_DATA_MODE_NONE | \ + WSPI_CFG_CMD_SIZE_8 | \ + WSPI_CFG_ADDR_SIZE_24) +/** + * @brief WSPI settings for command and address. + */ +#define SNOR_WSPI_CFG_CMD_ADDR (WSPI_CFG_CMD_MODE_ONE_LINE | \ + WSPI_CFG_ADDR_MODE_ONE_LINE | \ + WSPI_CFG_ALT_MODE_NONE | \ + WSPI_CFG_DATA_MODE_NONE | \ + WSPI_CFG_CMD_SIZE_8 | \ + WSPI_CFG_ADDR_SIZE_24) +/** + * @brief WSPI settings for command and data. + */ +#define SNOR_WSPI_CFG_CMD_DATA (WSPI_CFG_CMD_MODE_ONE_LINE | \ + WSPI_CFG_ADDR_MODE_NONE | \ + WSPI_CFG_ALT_MODE_NONE | \ + WSPI_CFG_DATA_MODE_ONE_LINE | \ + WSPI_CFG_CMD_SIZE_8 | \ + WSPI_CFG_ADDR_SIZE_24) +/** + * @brief WSPI settings for command, address and data. + */ +#define SNOR_WSPI_CFG_CMD_ADDR_DATA (WSPI_CFG_CMD_MODE_ONE_LINE | \ + WSPI_CFG_ADDR_MODE_ONE_LINE | \ + WSPI_CFG_ALT_MODE_NONE | \ + WSPI_CFG_DATA_MODE_ONE_LINE | \ + WSPI_CFG_CMD_SIZE_8 | \ + WSPI_CFG_ADDR_SIZE_24) + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver macros. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#if !defined(__DOXYGEN__) +extern flash_descriptor_t snor_descriptor; +#endif + +#if (SNOR_BUS_DRIVER == SNOR_BUS_DRIVER_WSPI) && (WSPI_SUPPORTS_MEMMAP == TRUE) +extern const wspi_command_t snor_memmap_read; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + void snor_device_init(SNORDriver *devp); + flash_error_t snor_device_read(SNORDriver *devp, flash_offset_t offset, + size_t n, uint8_t *rp); + flash_error_t snor_device_program(SNORDriver *devp, flash_offset_t offset, + size_t n, const uint8_t *pp); + flash_error_t snor_device_start_erase_all(SNORDriver *devp); + flash_error_t snor_device_start_erase_sector(SNORDriver *devp, + flash_sector_t sector); + flash_error_t snor_device_verify_erase(SNORDriver *devp, + flash_sector_t sector); + flash_error_t snor_device_query_erase(SNORDriver *devp, uint32_t *msec); + flash_error_t snor_device_read_sfdp(SNORDriver *devp, flash_offset_t offset, + size_t n, uint8_t *rp); +#if (SNOR_BUS_DRIVER == SNOR_BUS_DRIVER_WSPI) && \ + (SNOR_DEVICE_SUPPORTS_XIP == TRUE) + void snor_activate_xip(SNORDriver *devp); + void snor_reset_xip(SNORDriver *devp); +#endif +#ifdef __cplusplus +} +#endif + +#endif /* HAL_FLASH_DEVICE_H */ + +/** @} */ + diff --git a/firmware/hw_layer/drivers/flash/sst26f_jedec.mk b/firmware/hw_layer/drivers/flash/sst26f_jedec.mk new file mode 100644 index 0000000000..9e5ba66b4b --- /dev/null +++ b/firmware/hw_layer/drivers/flash/sst26f_jedec.mk @@ -0,0 +1,13 @@ +FLASH_DIR=$(PROJECT_DIR)/hw_layer/drivers/flash + +# List of all the Micron N25Q device files. +SNORSRC := $(CHIBIOS)/os/hal/lib/complex/serial_nor/hal_serial_nor.c \ + $(FLASH_DIR)/hal_flash_device.c + +# Required include directories +SNORINC := $(CHIBIOS)/os/hal/lib/complex/serial_nor \ + $(FLASH_DIR) + +# Shared variables +ALLCSRC += $(SNORSRC) +ALLINC += $(SNORINC)