blob: 3de8136aa0d81fea32071e2e03f250cef46da056 [file] [log] [blame]
/*
* Copyright (c) 2024 STMicroelectronics
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT st_stm32wb0_flash_controller
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/math_extras.h>
/* <soc.h> 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 <soc.h>
#include <stm32_ll_bus.h>
#include <stm32_ll_rcc.h>
#include <stm32_ll_system.h>
#include <zephyr/logging/log.h>
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);