| /* |
| * Copyright (c) 2022 BrainCo Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include "flash_gd32.h" |
| |
| #include <zephyr/logging/log.h> |
| #include <zephyr/kernel.h> |
| #include <gd32_fmc.h> |
| |
| LOG_MODULE_DECLARE(flash_gd32); |
| |
| #define GD32_NV_FLASH_V1_NODE DT_INST(0, gd_gd32_nv_flash_v1) |
| #define GD32_NV_FLASH_V1_TIMEOUT DT_PROP(GD32_NV_FLASH_V1_NODE, max_erase_time_ms) |
| #define GD32_NV_FLASH_V1_PAGE_SIZE DT_PROP(GD32_NV_FLASH_V1_NODE, page_size) |
| |
| #if defined(CONFIG_SOC_SERIES_GD32E10X) || \ |
| defined(CONFIG_SOC_SERIES_GD32E50X) |
| /* Some GD32 FMC v1 series require offset and len to word aligned. */ |
| #define GD32_FMC_V1_WORK_ALIGNED |
| #endif |
| |
| #ifdef FLASH_GD32_FMC_WORK_ALIGNED |
| #define GD32_FMC_V1_WRITE_ERR (FMC_STAT_PGERR | FMC_STAT_WPERR | FMC_STAT_PGAERR) |
| #else |
| #define GD32_FMC_V1_WRITE_ERR (FMC_STAT_PGERR | FMC_STAT_WPERR) |
| #endif |
| #define GD32_FMC_V1_ERASE_ERR FMC_STAT_WPERR |
| |
| #ifdef CONFIG_FLASH_PAGE_LAYOUT |
| static const struct flash_pages_layout gd32_fmc_v1_layout[] = { |
| { |
| .pages_size = GD32_NV_FLASH_V1_PAGE_SIZE, |
| .pages_count = SOC_NV_FLASH_SIZE / GD32_NV_FLASH_V1_PAGE_SIZE |
| } |
| }; |
| #endif |
| |
| static inline void gd32_fmc_v1_unlock(void) |
| { |
| FMC_KEY = UNLOCK_KEY0; |
| FMC_KEY = UNLOCK_KEY1; |
| } |
| |
| static inline void gd32_fmc_v1_lock(void) |
| { |
| FMC_CTL |= FMC_CTL_LK; |
| } |
| |
| static int gd32_fmc_v1_wait_idle(void) |
| { |
| const int64_t expired_time = k_uptime_get() + GD32_NV_FLASH_V1_TIMEOUT; |
| |
| while (FMC_STAT & FMC_STAT_BUSY) { |
| if (k_uptime_get() > expired_time) { |
| return -ETIMEDOUT; |
| } |
| } |
| |
| return 0; |
| } |
| |
| bool flash_gd32_valid_range(off_t offset, uint32_t len, bool write) |
| { |
| if ((offset > SOC_NV_FLASH_SIZE) || |
| ((offset + len) > SOC_NV_FLASH_SIZE)) { |
| return false; |
| } |
| |
| if (write) { |
| /* Check offset and len is flash_prg_t aligned. */ |
| if ((offset % sizeof(flash_prg_t)) || |
| (len % sizeof(flash_prg_t))) { |
| return false; |
| } |
| |
| #ifdef FLASH_GD32_FMC_WORK_ALIGNED |
| /* Check offset and len is word aligned. */ |
| if ((offset % sizeof(uint32_t)) || |
| (len % sizeof(uint32_t))) { |
| return false; |
| } |
| #endif |
| |
| } else { |
| if ((offset % GD32_NV_FLASH_V1_PAGE_SIZE) || |
| (len % GD32_NV_FLASH_V1_PAGE_SIZE)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| int flash_gd32_write_range(off_t offset, const void *data, size_t len) |
| { |
| flash_prg_t *prg_flash = (flash_prg_t *)((uint8_t *)SOC_NV_FLASH_ADDR + offset); |
| flash_prg_t *prg_data = (flash_prg_t *)data; |
| int ret = 0; |
| |
| gd32_fmc_v1_unlock(); |
| |
| if (FMC_STAT & FMC_STAT_BUSY) { |
| return -EBUSY; |
| } |
| |
| FMC_CTL |= FMC_CTL_PG; |
| |
| for (size_t i = 0U; i < (len / sizeof(flash_prg_t)); i++) { |
| *prg_flash++ = *prg_data++; |
| } |
| |
| ret = gd32_fmc_v1_wait_idle(); |
| if (ret < 0) { |
| goto expired_out; |
| } |
| |
| if (FMC_STAT & GD32_FMC_V1_WRITE_ERR) { |
| ret = -EIO; |
| FMC_STAT |= GD32_FMC_V1_WRITE_ERR; |
| LOG_ERR("FMC programming failed"); |
| } |
| |
| expired_out: |
| FMC_CTL &= ~FMC_CTL_PG; |
| |
| gd32_fmc_v1_lock(); |
| |
| return ret; |
| } |
| |
| static int gd32_fmc_v1_page_erase(uint32_t page_addr) |
| { |
| int ret = 0; |
| |
| gd32_fmc_v1_unlock(); |
| |
| if (FMC_STAT & FMC_STAT_BUSY) { |
| return -EBUSY; |
| } |
| |
| FMC_CTL |= FMC_CTL_PER; |
| |
| FMC_ADDR = page_addr; |
| |
| FMC_CTL |= FMC_CTL_START; |
| |
| ret = gd32_fmc_v1_wait_idle(); |
| if (ret < 0) { |
| goto expired_out; |
| } |
| |
| if (FMC_STAT & GD32_FMC_V1_ERASE_ERR) { |
| ret = -EIO; |
| FMC_STAT |= GD32_FMC_V1_ERASE_ERR; |
| LOG_ERR("FMC page %u erase failed", page_addr); |
| } |
| |
| expired_out: |
| FMC_CTL &= ~FMC_CTL_PER; |
| |
| gd32_fmc_v1_lock(); |
| |
| return ret; |
| } |
| |
| int flash_gd32_erase_block(off_t offset, size_t size) |
| { |
| uint32_t page_addr = SOC_NV_FLASH_ADDR + offset; |
| int ret = 0; |
| |
| while (size > 0U) { |
| ret = gd32_fmc_v1_page_erase(page_addr); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| size -= GD32_NV_FLASH_V1_PAGE_SIZE; |
| page_addr += GD32_NV_FLASH_V1_PAGE_SIZE; |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_FLASH_PAGE_LAYOUT |
| void flash_gd32_pages_layout(const struct device *dev, |
| const struct flash_pages_layout **layout, |
| size_t *layout_size) |
| { |
| ARG_UNUSED(dev); |
| |
| *layout = gd32_fmc_v1_layout; |
| *layout_size = ARRAY_SIZE(gd32_fmc_v1_layout); |
| } |
| #endif /* CONFIG_FLASH_PAGE_LAYOUT */ |