/* * 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 DEVICE_API(flash, 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);