From e322fa9781ccafd2a2e6895c618a6a866c1c5304 Mon Sep 17 00:00:00 2001 From: Mathieu Choplain Date: Thu, 11 Jul 2024 18:17:06 +0200 Subject: [PATCH] drivers: flash: stm32: add STM32WB0 flash controller Adds a basic driver for the STM32WB0 flash controller (read/erase/write). Extended operations are not supported by this driver. Signed-off-by: Mathieu Choplain --- drivers/flash/CMakeLists.txt | 2 + drivers/flash/flash_stm32wb0x.c | 459 ++++++++++++++++++++++++++++++++ 2 files changed, 461 insertions(+) create mode 100644 drivers/flash/flash_stm32wb0x.c diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index c82276611af..64b9c149db3 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -84,6 +84,8 @@ if(CONFIG_SOC_FLASH_STM32) else() zephyr_library_sources_ifdef(CONFIG_DT_HAS_ST_STM32_FLASH_CONTROLLER_ENABLED flash_stm32.c flash_stm32wbax.c) endif() + elseif(CONFIG_SOC_SERIES_STM32WB0X) + zephyr_library_sources_ifdef(CONFIG_DT_HAS_ST_STM32WB0_FLASH_CONTROLLER_ENABLED flash_stm32wb0x.c) else() if(CONFIG_DT_HAS_ST_STM32_FLASH_CONTROLLER_ENABLED) zephyr_library_sources(flash_stm32.c) diff --git a/drivers/flash/flash_stm32wb0x.c b/drivers/flash/flash_stm32wb0x.c new file mode 100644 index 00000000000..3de8136aa0d --- /dev/null +++ b/drivers/flash/flash_stm32wb0x.c @@ -0,0 +1,459 @@ +/* + * Copyright (c) 2024 STMicroelectronics + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT st_stm32wb0_flash_controller + +#include +#include +#include +#include +#include +#include + +/* also brings "stm32wb0x_hal_flash.h" + * and "system_stm32wb0x.h", which provide macros + * used by the driver, such as FLASH_PAGE_SIZE or + * _MEMORY_FLASH_SIZE_ respectively. + */ +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(flash_stm32wb0x, CONFIG_FLASH_LOG_LEVEL); + +/** + * Driver private definitions & assertions + */ +#define SYSTEM_FLASH_SIZE _MEMORY_FLASH_SIZE_ +#define PAGES_IN_FLASH (SYSTEM_FLASH_SIZE / FLASH_PAGE_SIZE) + +#define WRITE_BLOCK_SIZE \ + DT_PROP(DT_INST(0, soc_nv_flash), write_block_size) + +/* Size of flash words, in bytes (equal to write block size) */ +#define WORD_SIZE WRITE_BLOCK_SIZE + +#define ERASE_BLOCK_SIZE \ + DT_PROP(DT_INST(0, soc_nv_flash), erase_block_size) + +/** + * Driver private structures + */ +struct flash_wb0x_data { + /** Used to serialize write/erase operations */ + struct k_sem write_lock; + + /** Flash size, in bytes */ + size_t flash_size; +}; + +/** + * Driver private utility functions + */ +static inline uint32_t read_mem_u32(const uint32_t *ptr) +{ + /** + * Fetch word using sys_get_le32, which performs byte-sized + * reads instead of word-sized. This is important as ptr may + * be unaligned. We also want to use le32 because the data is + * stored in little-endian inside the flash. + */ + return sys_get_le32((const uint8_t *)ptr); +} + +static inline size_t get_flash_size_in_bytes(void) +{ + /* FLASH.SIZE contains the highest flash address supported + * on this MCU, which is also the number of words in flash + * minus one. + */ + const uint32_t words_in_flash = + READ_BIT(FLASH->SIZE, FLASH_FLASH_SIZE_FLASH_SIZE) + 1; + + return words_in_flash * WORD_SIZE; +} + +/** + * @brief Returns the associated error to IRQ flags. + * + * @returns a negative error value + */ +static int error_from_irq_flags(uint32_t flags) +{ + /** + * Only two errors are expected: + * - illegal command + * - command error + */ + if (flags & FLASH_FLAG_ILLCMD) { + return -EINVAL; + } + + if (flags & FLASH_FLAG_CMDERR) { + return -EIO; + } + + /* + * Unexpected error flag -> "out of domain" + * In practice, this should never be reached. + */ + return -EDOM; +} + +static bool is_valid_flash_range(const struct device *dev, + off_t offset, uint32_t len) +{ + const struct flash_wb0x_data *data = dev->data; + uint32_t offset_plus_len; + + /* (offset + len) must not overflow */ + return !u32_add_overflow(offset, len, &offset_plus_len) + /* offset must be a valid offset in flash */ + && IN_RANGE(offset, 0, data->flash_size - 1) + /* (offset + len) must be in [0; flash size] + * because it is equal to the last accessed + * byte in flash plus one (an access of `len` + * bytes starting at `offset` touches bytes + * `offset` to `offset + len` EXCLUDED) + */ + && IN_RANGE(offset_plus_len, 0, data->flash_size); +} + +static bool is_writeable_flash_range(const struct device *dev, + off_t offset, uint32_t len) +{ + if ((offset % WRITE_BLOCK_SIZE) != 0 + || (len % WRITE_BLOCK_SIZE) != 0) { + return false; + } + + return is_valid_flash_range(dev, offset, len); +} + +static bool is_erasable_flash_range(const struct device *dev, + off_t offset, uint32_t len) +{ + if ((offset % ERASE_BLOCK_SIZE) != 0 + || (len % ERASE_BLOCK_SIZE) != 0) { + return false; + } + + return is_valid_flash_range(dev, offset, len); +} + +/** + * Driver private functions + */ + +static uint32_t poll_flash_controller(void) +{ + uint32_t flags; + + /* Poll until an interrupt flag is raised */ + do { + flags = FLASH->IRQRAW; + } while (flags == 0); + + /* Acknowledge the flag(s) we have seen */ + FLASH->IRQRAW = flags; + + return flags; +} + +static int execute_flash_command(uint8_t cmd) +{ + uint32_t irq_flags; + + /* Clear all pending interrupt bits */ + FLASH->IRQRAW = FLASH->IRQRAW; + + /* Start command */ + FLASH->COMMAND = cmd; + + /* Wait for CMDSTART */ + irq_flags = poll_flash_controller(); + + /* If command didn't start, an error occurred */ + if (!(irq_flags & FLASH_IT_CMDSTART)) { + return error_from_irq_flags(irq_flags); + } + + /** + * Both CMDSTART and CMDDONE may be set if the command was + * executed fast enough. In this case, we're already done. + * Otherwise, we need to poll again until CMDDONE/error occurs. + */ + if (!(irq_flags & FLASH_IT_CMDDONE)) { + irq_flags = poll_flash_controller(); + } + + if (!(irq_flags & FLASH_IT_CMDDONE)) { + return error_from_irq_flags(irq_flags); + } else { + return 0; + } +} + +int erase_page_range(uint32_t start_page, uint32_t page_count) +{ + int res = 0; + + __ASSERT_NO_MSG(start_page < PAGES_IN_FLASH); + __ASSERT_NO_MSG((start_page + page_count - 1) < PAGES_IN_FLASH); + + for (uint32_t i = start_page; + i < (start_page + page_count); + i++) { + /* ADDRESS[16:9] = XADR[10:3] (address of page to erase) + * ADDRESS[8:0] = 0 (row & word address, must be 0) + */ + FLASH->ADDRESS = (i << 9); + + res = execute_flash_command(FLASH_CMD_ERASE_PAGES); + if (res < 0) { + break; + } + } + + return res; +} + +int write_word_range(const void *buf, uint32_t start_word, uint32_t num_words) +{ + /* Special value to load in DATAx registers to skip + * writing corresponding word with BURSTWRITE command. + */ + const uint32_t BURST_IGNORE_VALUE = 0xFFFFFFFF; + const size_t WORDS_IN_BURST = 4; + uint32_t dst_addr = start_word; + uint32_t remaining = num_words; + /** + * Note that @p buf may not be aligned to 32-bit boundary. + * However, declaring src_ptr as uint32_t* makes the address + * increment by 4 every time we do src_ptr++, which makes it + * behave like the other counters in this function. + */ + const uint32_t *src_ptr = buf; + int res = 0; + + /** + * Write to flash is performed as a 3 step process: + * - write single words using WRITE commands until the write + * write address is aligned to flash quadword boundary + * + * - after write address is aligned to quadword, we can use + * the BURSTWRITE commands to write 4 words at a time + * + * - once less than 4 words remain to write, a last BURSTWRITE + * is used with unneeded DATAx registers filled with 0xFFFFFFFF + * (this makes BURSTWRITE ignore write to these addresses) + */ + + /* (1) Align to quadword boundary with WRITE commands */ + while (remaining > 0 && (dst_addr % WORDS_IN_BURST) != 0) { + FLASH->ADDRESS = dst_addr; + FLASH->DATA0 = read_mem_u32(src_ptr); + + res = execute_flash_command(FLASH_CMD_WRITE); + if (res < 0) { + return res; + } + + src_ptr++; + dst_addr++; + remaining--; + } + + /* (2) Write bursts of quadwords */ + while (remaining >= WORDS_IN_BURST) { + __ASSERT_NO_MSG((dst_addr % WORDS_IN_BURST) == 0); + + FLASH->ADDRESS = dst_addr; + FLASH->DATA0 = read_mem_u32(src_ptr + 0); + FLASH->DATA1 = read_mem_u32(src_ptr + 1); + FLASH->DATA2 = read_mem_u32(src_ptr + 2); + FLASH->DATA3 = read_mem_u32(src_ptr + 3); + + res = execute_flash_command(FLASH_CMD_BURSTWRITE); + if (res < 0) { + return res; + } + + src_ptr += WORDS_IN_BURST; + dst_addr += WORDS_IN_BURST; + remaining -= WORDS_IN_BURST; + } + + /* (3) Write trailing (between 1 and 3 words) */ + if (remaining > 0) { + __ASSERT_NO_MSG(remaining < WORDS_IN_BURST); + __ASSERT_NO_MSG((dst_addr % WORDS_IN_BURST) == 0); + + FLASH->ADDRESS = dst_addr; + FLASH->DATA0 = read_mem_u32(src_ptr + 0); + + FLASH->DATA1 = (remaining >= 2) + ? read_mem_u32(src_ptr + 1) + : BURST_IGNORE_VALUE; + + FLASH->DATA2 = (remaining == 3) + ? read_mem_u32(src_ptr + 2) + : BURST_IGNORE_VALUE; + + FLASH->DATA3 = BURST_IGNORE_VALUE; + + remaining = 0; + + res = execute_flash_command(FLASH_CMD_BURSTWRITE); + } + + return res; +} + +/** + * Driver subsystem API implementation + */ +int flash_wb0x_read(const struct device *dev, off_t offset, + void *buffer, size_t len) +{ + if (!len) { + return 0; + } + + if (!is_valid_flash_range(dev, offset, len)) { + return -EINVAL; + } + + const uint8_t *flash_base = ((void *)DT_REG_ADDR(DT_INST(0, st_stm32_nv_flash))); + + memcpy(buffer, flash_base + (uint32_t)offset, len); + + return 0; +} + +int flash_wb0x_write(const struct device *dev, off_t offset, + const void *buffer, size_t len) +{ + struct flash_wb0x_data *data = dev->data; + int res; + + if (!len) { + return 0; + } + + if (!is_writeable_flash_range(dev, offset, len)) { + return -EINVAL; + } + + /* Acquire driver lock */ + res = k_sem_take(&data->write_lock, K_NO_WAIT); + if (res < 0) { + return res; + } + + const uint32_t start_word = (uint32_t)offset / WORD_SIZE; + const uint32_t num_words = len / WORD_SIZE; + + res = write_word_range(buffer, start_word, num_words); + + /* Release driver lock */ + k_sem_give(&data->write_lock); + + return res; +} + +int flash_wb0x_erase(const struct device *dev, off_t offset, size_t size) +{ + struct flash_wb0x_data *data = dev->data; + int res; + + if (!size) { + return 0; + } + + if (!is_erasable_flash_range(dev, offset, size)) { + return -EINVAL; + } + + /* Acquire driver lock */ + res = k_sem_take(&data->write_lock, K_NO_WAIT); + if (res < 0) { + return res; + } + + const uint32_t start_page = (uint32_t)offset / ERASE_BLOCK_SIZE; + const uint32_t page_count = size / ERASE_BLOCK_SIZE; + + res = erase_page_range(start_page, page_count); + + /* Release driver lock */ + k_sem_give(&data->write_lock); + + return res; +} + +const struct flash_parameters *flash_wb0x_get_parameters( + const struct device *dev) +{ + static const struct flash_parameters fp = { + .write_block_size = WRITE_BLOCK_SIZE, + .erase_value = 0xff, + }; + + return &fp; +} + +#if defined(CONFIG_FLASH_PAGE_LAYOUT) +void flash_wb0x_pages_layout(const struct device *dev, + const struct flash_pages_layout **layout, + size_t *layout_size) +{ + /** + * STM32WB0 flash: single bank, 2KiB pages + * (the number of pages depends on MCU) + */ + static const struct flash_pages_layout fpl[] = {{ + .pages_count = PAGES_IN_FLASH, + .pages_size = FLASH_PAGE_SIZE + }}; + + *layout = fpl; + *layout_size = ARRAY_SIZE(fpl); +} +#endif /* CONFIG_FLASH_PAGE_LAYOUT */ + +static const struct flash_driver_api flash_wb0x_api = { + .erase = flash_wb0x_erase, + .write = flash_wb0x_write, + .read = flash_wb0x_read, + .get_parameters = flash_wb0x_get_parameters, +#ifdef CONFIG_FLASH_PAGE_LAYOUT + .page_layout = flash_wb0x_pages_layout, +#endif + /* extended operations not supported */ +}; + +int stm32wb0x_flash_init(const struct device *dev) +{ + struct flash_wb0x_data *data = dev->data; + + k_sem_init(&data->write_lock, 1, 1); + + data->flash_size = get_flash_size_in_bytes(); + + return 0; +} + +/** + * Driver device instantiation + */ +static struct flash_wb0x_data wb0x_flash_drv_data; + +DEVICE_DT_INST_DEFINE(0, stm32wb0x_flash_init, NULL, + &wb0x_flash_drv_data, NULL, POST_KERNEL, + CONFIG_FLASH_INIT_PRIORITY, &flash_wb0x_api);