|  | /* | 
|  | * Copyright (c) 2019 Linaro Limited | 
|  | * Copyright (c) 2020 STMicroelectronics | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define LOG_DOMAIN flash_stm32wb | 
|  | #define LOG_LEVEL CONFIG_FLASH_LOG_LEVEL | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(LOG_DOMAIN); | 
|  |  | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/device.h> | 
|  | #include <string.h> | 
|  | #include <zephyr/drivers/flash.h> | 
|  | #include <zephyr/init.h> | 
|  | #include <soc.h> | 
|  | #include <zephyr/sys/__assert.h> | 
|  |  | 
|  | #include "flash_stm32.h" | 
|  | #include "stm32_hsem.h" | 
|  | #if defined(CONFIG_BT) | 
|  | #include "shci.h" | 
|  | #endif | 
|  |  | 
|  | #define STM32WBX_PAGE_SHIFT	12 | 
|  |  | 
|  | /* | 
|  | * Up to 255 4K pages | 
|  | */ | 
|  | static uint32_t get_page(off_t offset) | 
|  | { | 
|  | return offset >> STM32WBX_PAGE_SHIFT; | 
|  | } | 
|  |  | 
|  | static inline void flush_cache(FLASH_TypeDef *regs) | 
|  | { | 
|  | if (regs->ACR & FLASH_ACR_DCEN) { | 
|  | regs->ACR &= ~FLASH_ACR_DCEN; | 
|  | /* Datasheet: DCRST: Data cache reset | 
|  | * This bit can be written only when the data cache is disabled | 
|  | */ | 
|  | regs->ACR |= FLASH_ACR_DCRST; | 
|  | regs->ACR &= ~FLASH_ACR_DCRST; | 
|  | regs->ACR |= FLASH_ACR_DCEN; | 
|  | } | 
|  |  | 
|  | if (regs->ACR & FLASH_ACR_ICEN) { | 
|  | regs->ACR &= ~FLASH_ACR_ICEN; | 
|  | /* Datasheet: ICRST: Instruction cache reset : | 
|  | * This bit can be written only when the instruction cache | 
|  | * is disabled | 
|  | */ | 
|  | regs->ACR |= FLASH_ACR_ICRST; | 
|  | regs->ACR &= ~FLASH_ACR_ICRST; | 
|  | regs->ACR |= FLASH_ACR_ICEN; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int write_dword(const struct device *dev, off_t offset, uint64_t val) | 
|  | { | 
|  | volatile uint32_t *flash = (uint32_t *)(offset + FLASH_STM32_BASE_ADDRESS); | 
|  | FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); | 
|  | uint32_t tmp; | 
|  | int ret, rc; | 
|  | uint32_t cpu1_sem_status; | 
|  | uint32_t cpu2_sem_status = 0; | 
|  | uint32_t key; | 
|  |  | 
|  | /* if the control register is locked, do not fail silently */ | 
|  | if (regs->CR & FLASH_CR_LOCK) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Check if this double word is erased and value isn't 0. | 
|  | * | 
|  | * It is allowed to write only zeros over an already written dword | 
|  | * See 3.3.8 in reference manual. | 
|  | */ | 
|  | if ((flash[0] != 0xFFFFFFFFUL || | 
|  | flash[1] != 0xFFFFFFFFUL) && val != 0UL) { | 
|  | LOG_ERR("Word at offs %ld not erased", (long)offset); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | ret = flash_stm32_check_status(dev); | 
|  | if (ret < 0) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Implementation of STM32 AN5289, proposed in STM32WB Cube Application | 
|  | * BLE_RfWithFlash | 
|  | * https://github.com/STMicroelectronics/STM32CubeWB/tree/master/Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_RfWithFlash | 
|  | */ | 
|  |  | 
|  | do { | 
|  | /** | 
|  | * When the PESD bit mechanism is used by CPU2 to protect its | 
|  | * timing, the PESD bit should be polled here. | 
|  | * If the PESD is set, the CPU1 will be stalled when reading | 
|  | * literals from an ISR that may occur after the flash | 
|  | * processing has been requested but suspended due to the PESD | 
|  | * bit. | 
|  | * | 
|  | * Note: This code is required only when the PESD mechanism is | 
|  | * used to protect the CPU2 timing. | 
|  | * However, keeping that code make it compatible with both | 
|  | * mechanisms. | 
|  | */ | 
|  | while (LL_FLASH_IsActiveFlag_OperationSuspended()) { | 
|  | ; | 
|  | } | 
|  |  | 
|  | /* Enter critical section */ | 
|  | key = irq_lock(); | 
|  |  | 
|  | /** | 
|  | *  Depending on the application implementation, in case a | 
|  | *  multitasking is possible with an OS, it should be checked | 
|  | *  here if another task in the application disallowed flash | 
|  | *  processing to protect some latency in critical code | 
|  | *  execution. | 
|  | *  When flash processing is ongoing, the CPU cannot access the | 
|  | *  flash anymore.Trying to access the flash during that time | 
|  | *  stalls the CPU. | 
|  | *  The only way for CPU1 to disallow flash processing is to | 
|  | *  take CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID. | 
|  | */ | 
|  | cpu1_sem_status = LL_HSEM_GetStatus(HSEM, | 
|  | CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID); | 
|  | if (cpu1_sem_status == 0) { | 
|  | /** | 
|  | *  Check now if the CPU2 disallows flash processing to | 
|  | *  protect its timing. If the semaphore is locked, the | 
|  | *  CPU2 does not allow flash processing | 
|  | * | 
|  | *  Note: By default, the CPU2 uses the PESD mechanism | 
|  | *  to protect its timing, therefore, it is useless to | 
|  | *  get/release the semaphore. | 
|  | * | 
|  | *  However, keeping that code make it compatible with | 
|  | *  both mechanisms. | 
|  | *  The protection by semaphore is enabled on CPU2 side | 
|  | *  with the command SHCI_C2_SetFlashActivityControl() | 
|  | * | 
|  | */ | 
|  | cpu2_sem_status = LL_HSEM_1StepLock(HSEM, | 
|  | CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID); | 
|  | if (cpu2_sem_status == 0) { | 
|  | /** | 
|  | * When CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID is | 
|  | * taken, it is allowed to only write one | 
|  | * single 64bits data. | 
|  | * When several 64bits data need to be erased, | 
|  | * the application shall first exit from the | 
|  | * critical section and try again. | 
|  | */ | 
|  | /* Set the PG bit */ | 
|  | regs->CR |= FLASH_CR_PG; | 
|  |  | 
|  | /* Flush the register write */ | 
|  | tmp = regs->CR; | 
|  |  | 
|  | /* Perform the data write operation at desired | 
|  | * memory address | 
|  | */ | 
|  | flash[0] = (uint32_t)val; | 
|  | flash[1] = (uint32_t)(val >> 32); | 
|  |  | 
|  | /** | 
|  | *  Release the semaphore to give the | 
|  | *  opportunity to CPU2 to protect its timing | 
|  | *  versus the next flash operation by taking | 
|  | *  this semaphore. | 
|  | *  Note that the CPU2 is polling on this | 
|  | *  semaphore so CPU1 shall release it as fast | 
|  | *  as possible. | 
|  | *  This is why this code is protected by a | 
|  | *  critical section. | 
|  | */ | 
|  | LL_HSEM_ReleaseLock(HSEM, | 
|  | CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID, | 
|  | 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Exit critical section */ | 
|  | irq_unlock(key); | 
|  |  | 
|  | } while (cpu2_sem_status || cpu1_sem_status); | 
|  |  | 
|  | /* Wait until the BSY bit is cleared */ | 
|  | rc = flash_stm32_wait_flash_idle(dev); | 
|  |  | 
|  | /* Clear the PG bit */ | 
|  | regs->CR &= (~FLASH_CR_PG); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int erase_page(const struct device *dev, uint32_t page) | 
|  | { | 
|  | uint32_t cpu1_sem_status; | 
|  | uint32_t cpu2_sem_status = 0; | 
|  | uint32_t key; | 
|  |  | 
|  | FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); | 
|  | int rc; | 
|  |  | 
|  | /* if the control register is locked, do not fail silently */ | 
|  | if (regs->CR & FLASH_CR_LOCK) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Check that no Flash memory operation is ongoing */ | 
|  | rc = flash_stm32_wait_flash_idle(dev); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If an erase operation in Flash memory also concerns data in the data | 
|  | * or instruction cache, the user has to ensure that these data | 
|  | * are rewritten before they are accessed during code execution. | 
|  | */ | 
|  | flush_cache(regs); | 
|  |  | 
|  | /* Implementation of STM32 AN5289, proposed in STM32WB Cube Application | 
|  | * BLE_RfWithFlash | 
|  | * https://github.com/STMicroelectronics/STM32CubeWB/tree/master/Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_RfWithFlash | 
|  | */ | 
|  |  | 
|  | do { | 
|  | /** | 
|  | * When the PESD bit mechanism is used by CPU2 to protect its | 
|  | * timing, the PESD bit should be polled here. | 
|  | * If the PESD is set, the CPU1 will be stalled when reading | 
|  | * literals from an ISR that may occur after the flash | 
|  | * processing has been requested but suspended due to the PESD | 
|  | * bit. | 
|  | * | 
|  | * Note: This code is required only when the PESD mechanism is | 
|  | * used to protect the CPU2 timing. | 
|  | * However, keeping that code make it compatible with both | 
|  | * mechanisms. | 
|  | */ | 
|  | while (LL_FLASH_IsActiveFlag_OperationSuspended()) { | 
|  | ; | 
|  | } | 
|  |  | 
|  | /* Enter critical section */ | 
|  | key = irq_lock(); | 
|  |  | 
|  | /** | 
|  | *  Depending on the application implementation, in case a | 
|  | *  multitasking is possible with an OS, it should be checked | 
|  | *  here if another task in the application disallowed flash | 
|  | *  processing to protect some latency in critical code | 
|  | *  execution. | 
|  | *  When flash processing is ongoing, the CPU cannot access the | 
|  | *  flash anymore.Trying to access the flash during that time | 
|  | *  stalls the CPU. | 
|  | *  The only way for CPU1 to disallow flash processing is to | 
|  | *  take CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID. | 
|  | */ | 
|  | cpu1_sem_status = LL_HSEM_GetStatus(HSEM, | 
|  | CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID); | 
|  | if (cpu1_sem_status == 0) { | 
|  | /** | 
|  | *  Check now if the CPU2 disallows flash processing to | 
|  | *  protect its timing. If the semaphore is locked, the | 
|  | *  CPU2 does not allow flash processing | 
|  | * | 
|  | *  Note: By default, the CPU2 uses the PESD mechanism | 
|  | *  to protect its timing, therefore, it is useless to | 
|  | *  get/release the semaphore. | 
|  | * | 
|  | *  However, keeping that code make it compatible with | 
|  | *  both mechanisms. | 
|  | *  The protection by semaphore is enabled on CPU2 side | 
|  | *  with the command SHCI_C2_SetFlashActivityControl() | 
|  | * | 
|  | */ | 
|  | cpu2_sem_status = LL_HSEM_1StepLock(HSEM, | 
|  | CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID); | 
|  | if (cpu2_sem_status == 0) { | 
|  | /** | 
|  | * When CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID is | 
|  | * taken, it is allowed to only erase one | 
|  | * sector. | 
|  | * When several sectors need to be erased, | 
|  | * the application shall first exit from the | 
|  | * critical section and try again. | 
|  | */ | 
|  | regs->CR |= FLASH_CR_PER; | 
|  | regs->CR &= ~FLASH_CR_PNB_Msk; | 
|  | regs->CR |= page << FLASH_CR_PNB_Pos; | 
|  |  | 
|  | regs->CR |= FLASH_CR_STRT; | 
|  |  | 
|  | /** | 
|  | *  Release the semaphore to give the | 
|  | *  opportunity to CPU2 to protect its timing | 
|  | *  versus the next flash operation by taking | 
|  | *  this semaphore. | 
|  | *  Note that the CPU2 is polling on this | 
|  | *  semaphore so CPU1 shall release it as fast | 
|  | *  as possible. | 
|  | *  This is why this code is protected by a | 
|  | *  critical section. | 
|  | */ | 
|  | LL_HSEM_ReleaseLock(HSEM, | 
|  | CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID, | 
|  | 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Exit critical section */ | 
|  | irq_unlock(key); | 
|  |  | 
|  | } while (cpu2_sem_status || cpu1_sem_status); | 
|  |  | 
|  |  | 
|  | /* Wait for the BSY bit */ | 
|  | rc = flash_stm32_wait_flash_idle(dev); | 
|  |  | 
|  | regs->CR &= ~FLASH_CR_PER; | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | int flash_stm32_block_erase_loop(const struct device *dev, | 
|  | unsigned int offset, | 
|  | unsigned int len) | 
|  | { | 
|  | int i, rc = 0; | 
|  |  | 
|  | #if defined(CONFIG_BT) | 
|  | /** | 
|  | *  Notify the CPU2 that some flash erase activity may be executed | 
|  | *  On reception of this command, the CPU2 enables the BLE timing | 
|  | *  protection versus flash erase processing. | 
|  | *  The Erase flash activity will be executed only when the BLE RF is | 
|  | *  idle for at least 25ms. | 
|  | *  The CPU2 will prevent all flash activity (write or erase) in all | 
|  | *  cases when the BL RF Idle is shorter than 25ms. | 
|  | */ | 
|  | SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON); | 
|  | #endif /* CONFIG_BT */ | 
|  |  | 
|  | i = get_page(offset); | 
|  | for (; i <= get_page(offset + len - 1) ; ++i) { | 
|  | rc = erase_page(dev, i); | 
|  | if (rc < 0) { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | #if defined(CONFIG_BT) | 
|  | /** | 
|  | *  Notify the CPU2 there will be no request anymore to erase the flash | 
|  | *  On reception of this command, the CPU2 disables the BLE timing | 
|  | *  protection versus flash erase processing | 
|  | */ | 
|  | SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF); | 
|  | #endif /* CONFIG_BT */ | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | int flash_stm32_write_range(const struct device *dev, unsigned int offset, | 
|  | const void *data, unsigned int len) | 
|  | { | 
|  | int i, rc = 0; | 
|  |  | 
|  | for (i = 0; i < len; i += 8, offset += 8U) { | 
|  | rc = write_dword(dev, offset, | 
|  | UNALIGNED_GET((const uint64_t *) data + (i >> 3))); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | void flash_stm32_page_layout(const struct device *dev, | 
|  | const struct flash_pages_layout **layout, | 
|  | size_t *layout_size) | 
|  | { | 
|  | static struct flash_pages_layout stm32wb_flash_layout = { | 
|  | .pages_count = 0, | 
|  | .pages_size = 0, | 
|  | }; | 
|  |  | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | if (stm32wb_flash_layout.pages_count == 0) { | 
|  | stm32wb_flash_layout.pages_count = FLASH_SIZE / FLASH_PAGE_SIZE; | 
|  | stm32wb_flash_layout.pages_size = FLASH_PAGE_SIZE; | 
|  | } | 
|  |  | 
|  | *layout = &stm32wb_flash_layout; | 
|  | *layout_size = 1; | 
|  | } | 
|  |  | 
|  | int flash_stm32_check_status(const struct device *dev) | 
|  | { | 
|  | FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); | 
|  | uint32_t error = 0; | 
|  |  | 
|  | /* Save Flash errors */ | 
|  | error = (regs->SR & FLASH_FLAG_SR_ERRORS); | 
|  | error |= (regs->ECCR & FLASH_FLAG_ECCC); | 
|  |  | 
|  | /* Clear systematic Option and Engineering bits validity error */ | 
|  | if (error & FLASH_FLAG_OPTVERR) { | 
|  | regs->SR |= FLASH_FLAG_SR_ERRORS; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (error) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } |