| /* |
| * Copyright (c) 2021 Telink Semiconductor |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT telink_b91_flash_controller |
| #define FLASH_SIZE DT_REG_SIZE(DT_INST(0, soc_nv_flash)) |
| #define FLASH_ORIGIN DT_REG_ADDR(DT_INST(0, soc_nv_flash)) |
| |
| #include "flash.h" |
| #include <string.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/flash.h> |
| |
| |
| /* driver definitions */ |
| #define BLOCK_64K_SIZE (0x10000u) |
| #define BLOCK_64K_PAGES (BLOCK_64K_SIZE / PAGE_SIZE) |
| #define BLOCK_32K_SIZE (0x8000u) |
| #define BLOCK_32K_PAGES (BLOCK_32K_SIZE / PAGE_SIZE) |
| #define SECTOR_SIZE (0x1000u) |
| #define SECTOR_PAGES (SECTOR_SIZE / PAGE_SIZE) |
| |
| |
| /* driver data structure */ |
| struct flash_b91_data { |
| struct k_sem write_lock; |
| }; |
| |
| /* driver parameters structure */ |
| static const struct flash_parameters flash_b91_parameters = { |
| .write_block_size = DT_PROP(DT_INST(0, soc_nv_flash), write_block_size), |
| .erase_value = 0xff, |
| }; |
| |
| |
| /* Check for correct offset and length */ |
| static bool flash_b91_is_range_valid(off_t offset, size_t len) |
| { |
| /* check for min value */ |
| if ((offset < 0) || (len < 1)) { |
| return false; |
| } |
| |
| /* check for max value */ |
| if ((offset + len) > FLASH_SIZE) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* API implementation: driver initialization */ |
| static int flash_b91_init(const struct device *dev) |
| { |
| struct flash_b91_data *dev_data = dev->data; |
| |
| k_sem_init(&dev_data->write_lock, 1, 1); |
| |
| return 0; |
| } |
| |
| /* API implementation: erase */ |
| static int flash_b91_erase(const struct device *dev, off_t offset, size_t len) |
| { |
| int page_nums = len / PAGE_SIZE; |
| struct flash_b91_data *dev_data = dev->data; |
| |
| /* return SUCCESS if len equals 0 (required by tests/drivers/flash) */ |
| if (!len) { |
| return 0; |
| } |
| |
| /* check for valid range */ |
| if (!flash_b91_is_range_valid(offset, len)) { |
| return -EINVAL; |
| } |
| |
| /* erase can be done only by pages */ |
| if (((offset % PAGE_SIZE) != 0) || ((len % PAGE_SIZE) != 0)) { |
| return -EINVAL; |
| } |
| |
| /* take semaphore */ |
| if (k_sem_take(&dev_data->write_lock, K_NO_WAIT)) { |
| return -EACCES; |
| } |
| |
| while (page_nums) { |
| /* check for 64K erase possibility, then check for 32K and so on.. */ |
| if ((page_nums >= BLOCK_64K_PAGES) && ((offset % BLOCK_64K_SIZE) == 0)) { |
| /* erase 64K block */ |
| flash_erase_64kblock(offset); |
| page_nums -= BLOCK_64K_PAGES; |
| offset += BLOCK_64K_SIZE; |
| } else if ((page_nums >= BLOCK_32K_PAGES) && ((offset % BLOCK_32K_SIZE) == 0)) { |
| /* erase 32K block */ |
| flash_erase_32kblock(offset); |
| page_nums -= BLOCK_32K_PAGES; |
| offset += BLOCK_32K_SIZE; |
| } else if ((page_nums >= SECTOR_PAGES) && ((offset % SECTOR_SIZE) == 0)) { |
| /* erase sector */ |
| flash_erase_sector(offset); |
| page_nums -= SECTOR_PAGES; |
| offset += SECTOR_SIZE; |
| } else { |
| /* erase page */ |
| flash_erase_page(offset); |
| page_nums--; |
| offset += PAGE_SIZE; |
| } |
| } |
| |
| /* release semaphore */ |
| k_sem_give(&dev_data->write_lock); |
| |
| return 0; |
| } |
| |
| /* API implementation: write */ |
| static int flash_b91_write(const struct device *dev, off_t offset, |
| const void *data, size_t len) |
| { |
| void *buf = NULL; |
| struct flash_b91_data *dev_data = dev->data; |
| |
| /* return SUCCESS if len equals 0 (required by tests/drivers/flash) */ |
| if (!len) { |
| return 0; |
| } |
| |
| /* check for valid range */ |
| if (!flash_b91_is_range_valid(offset, len)) { |
| return -EINVAL; |
| } |
| |
| /* take semaphore */ |
| if (k_sem_take(&dev_data->write_lock, K_NO_WAIT)) { |
| return -EACCES; |
| } |
| |
| /* need to store data in intermediate RAM buffer in case from flash to flash write */ |
| if (((uint32_t)data >= FLASH_ORIGIN) && |
| ((uint32_t)data < (FLASH_ORIGIN + FLASH_SIZE))) { |
| |
| buf = k_malloc(len); |
| if (buf == NULL) { |
| k_sem_give(&dev_data->write_lock); |
| return -ENOMEM; |
| } |
| |
| /* copy Flash data to RAM */ |
| memcpy(buf, data, len); |
| |
| /* substitute data with allocated buffer */ |
| data = buf; |
| } |
| |
| /* write flash */ |
| flash_write_page(offset, len, (unsigned char *)data); |
| |
| /* if ram memory is allocated for flash writing it should be free */ |
| if (buf != NULL) { |
| k_free(buf); |
| } |
| |
| /* release semaphore */ |
| k_sem_give(&dev_data->write_lock); |
| |
| return 0; |
| } |
| |
| /* API implementation: read */ |
| static int flash_b91_read(const struct device *dev, off_t offset, |
| void *data, size_t len) |
| { |
| ARG_UNUSED(dev); |
| |
| /* return SUCCESS if len equals 0 (required by tests/drivers/flash) */ |
| if (!len) { |
| return 0; |
| } |
| |
| /* check for valid range */ |
| if (!flash_b91_is_range_valid(offset, len)) { |
| return -EINVAL; |
| } |
| |
| /* read flash */ |
| flash_read_page(offset, len, (unsigned char *)data); |
| |
| return 0; |
| } |
| |
| /* API implementation: get_parameters */ |
| static const struct flash_parameters * |
| flash_b91_get_parameters(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| |
| return &flash_b91_parameters; |
| } |
| |
| /* API implementation: page_layout */ |
| #if defined(CONFIG_FLASH_PAGE_LAYOUT) |
| static const struct flash_pages_layout dev_layout = { |
| .pages_count = FLASH_SIZE / PAGE_SIZE, |
| .pages_size = PAGE_SIZE, |
| }; |
| |
| static void flash_b91_pages_layout(const struct device *dev, |
| const struct flash_pages_layout **layout, |
| size_t *layout_size) |
| { |
| *layout = &dev_layout; |
| *layout_size = 1; |
| } |
| #endif /* CONFIG_FLASH_PAGE_LAYOUT */ |
| |
| static struct flash_b91_data flash_data; |
| |
| static const struct flash_driver_api flash_b91_api = { |
| .erase = flash_b91_erase, |
| .write = flash_b91_write, |
| .read = flash_b91_read, |
| .get_parameters = flash_b91_get_parameters, |
| #if defined(CONFIG_FLASH_PAGE_LAYOUT) |
| .page_layout = flash_b91_pages_layout, |
| #endif |
| }; |
| |
| /* Driver registration */ |
| DEVICE_DT_INST_DEFINE(0, flash_b91_init, |
| NULL, &flash_data, NULL, POST_KERNEL, |
| CONFIG_FLASH_INIT_PRIORITY, &flash_b91_api); |