You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
3.9 KiB
197 lines
3.9 KiB
/* |
|
* 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 */
|
|
|