| /* |
| * Copyright (c) 2024 Silicon Laboratories Inc. |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #define DT_DRV_COMPAT silabs_siwx91x_flash_controller |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/flash.h> |
| #include <zephyr/logging/log.h> |
| |
| #include "sl_si91x_driver.h" |
| |
| LOG_MODULE_REGISTER(siwx91x_soc_flash); |
| |
| struct siwx91x_config { |
| uintptr_t base_address; |
| uint32_t size; |
| uint32_t write_block_size; |
| uint32_t erase_block_size; |
| struct flash_parameters flash_parameters; |
| #ifdef CONFIG_FLASH_PAGE_LAYOUT |
| struct flash_pages_layout flash_pages_layout; |
| #endif |
| }; |
| |
| struct siwx91x_data { |
| struct k_sem lock; |
| }; |
| |
| static bool flash_siwx91x_range_is_in_bounds(const struct device *dev, off_t offset, size_t len) |
| { |
| const struct siwx91x_config *cfg = dev->config; |
| |
| /* Note: If offset < __rom_region_end, the user to overwriting the current firmware. User |
| * is probably doing a mistake, but not an error from this driver point of view. |
| */ |
| if (offset < 0) { |
| return false; |
| } |
| if (offset + len > cfg->size) { |
| return false; |
| } |
| return true; |
| } |
| |
| static const struct flash_parameters *flash_siwx91x_get_parameters(const struct device *dev) |
| { |
| const struct siwx91x_config *cfg = dev->config; |
| |
| return &cfg->flash_parameters; |
| } |
| |
| static int flash_siwx91x_read(const struct device *dev, off_t offset, void *buf, size_t len) |
| { |
| const struct siwx91x_config *cfg = dev->config; |
| struct siwx91x_data *data = dev->data; |
| void *location = (void *)(cfg->base_address + offset); |
| |
| if (!flash_siwx91x_range_is_in_bounds(dev, offset, len)) { |
| return -EINVAL; |
| } |
| k_sem_take(&data->lock, K_FOREVER); |
| memcpy(buf, location, len); |
| k_sem_give(&data->lock); |
| return 0; |
| } |
| |
| static int flash_siwx91x_write(const struct device *dev, off_t offset, const void *buf, size_t len) |
| { |
| const struct siwx91x_config *cfg = dev->config; |
| struct siwx91x_data *data = dev->data; |
| uint32_t ret; |
| |
| if (!flash_siwx91x_range_is_in_bounds(dev, offset, len)) { |
| return -EINVAL; |
| } |
| if (offset % cfg->write_block_size) { |
| return -EINVAL; |
| } |
| if (len % cfg->write_block_size) { |
| return -EINVAL; |
| } |
| k_sem_take(&data->lock, K_FOREVER); |
| ret = sl_si91x_command_to_write_common_flash(cfg->base_address + offset, (void *)buf, len, |
| false); |
| k_sem_give(&data->lock); |
| if (ret) { |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| static int flash_siwx91x_erase(const struct device *dev, off_t offset, size_t len) |
| { |
| const struct siwx91x_config *cfg = dev->config; |
| struct siwx91x_data *data = dev->data; |
| uint32_t ret; |
| |
| if (!flash_siwx91x_range_is_in_bounds(dev, offset, len)) { |
| return -EINVAL; |
| } |
| if (offset % cfg->erase_block_size) { |
| return -EINVAL; |
| } |
| if (len % cfg->erase_block_size) { |
| return -EINVAL; |
| } |
| k_sem_take(&data->lock, K_FOREVER); |
| ret = sl_si91x_command_to_write_common_flash(cfg->base_address + offset, NULL, len, true); |
| k_sem_give(&data->lock); |
| if (ret) { |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| #ifdef CONFIG_FLASH_PAGE_LAYOUT |
| static void flash_siwx91x_page_layout(const struct device *dev, |
| const struct flash_pages_layout **layout, size_t *layout_size) |
| { |
| const struct siwx91x_config *cfg = dev->config; |
| |
| *layout = &cfg->flash_pages_layout; |
| *layout_size = 1; |
| } |
| #endif |
| |
| static DEVICE_API(flash, siwx91x_api) = { |
| .read = flash_siwx91x_read, |
| .write = flash_siwx91x_write, |
| .erase = flash_siwx91x_erase, |
| .get_parameters = flash_siwx91x_get_parameters, |
| #ifdef CONFIG_FLASH_PAGE_LAYOUT |
| .page_layout = flash_siwx91x_page_layout, |
| #endif |
| }; |
| |
| static int flash_siwx91x_init(const struct device *dev) |
| { |
| struct siwx91x_data *data = dev->data; |
| |
| k_sem_init(&data->lock, 1, 1); |
| return 0; |
| } |
| |
| #ifdef CONFIG_FLASH_PAGE_LAYOUT |
| #define SIWX91X_PAGE_LAYOUT_FLASH_INIT(n) \ |
| .flash_pages_layout.pages_count = DT_REG_SIZE(n) / DT_PROP(n, erase_block_size), \ |
| .flash_pages_layout.pages_size = DT_PROP(n, erase_block_size), |
| #else |
| #define SIWX91X_PAGE_LAYOUT_FLASH_INIT(n) |
| #endif |
| |
| #define SIWX91X_FLASH_INIT_P(n, p) \ |
| static const struct siwx91x_config flash_siwx91x_config_##p = { \ |
| .base_address = DT_REG_ADDR(n), \ |
| .size = DT_REG_SIZE(n), \ |
| .write_block_size = DT_PROP(n, write_block_size), \ |
| .erase_block_size = DT_PROP(n, erase_block_size), \ |
| .flash_parameters.write_block_size = DT_PROP(n, write_block_size), \ |
| .flash_parameters.erase_value = 0xff, \ |
| SIWX91X_PAGE_LAYOUT_FLASH_INIT(n) \ |
| }; \ |
| static struct siwx91x_data flash_siwx91x_data_##p = { \ |
| .lock = Z_SEM_INITIALIZER(flash_siwx91x_data_##p.lock, 1, 1), \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(p, flash_siwx91x_init, NULL, &flash_siwx91x_data_##p, \ |
| &flash_siwx91x_config_##p, POST_KERNEL, CONFIG_FLASH_INIT_PRIORITY, \ |
| &siwx91x_api); |
| |
| #define SIWX91X_FLASH_INIT(p) \ |
| BUILD_ASSERT(DT_INST_CHILD_NUM_STATUS_OKAY(p) == 1); \ |
| DT_INST_FOREACH_CHILD_STATUS_OKAY_VARGS(p, SIWX91X_FLASH_INIT_P, p) |
| |
| DT_INST_FOREACH_STATUS_OKAY(SIWX91X_FLASH_INIT) |