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.
262 lines
5.8 KiB
262 lines
5.8 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_V3_NODE DT_INST(0, gd_gd32_nv_flash_v3) |
|
#define GD32_NV_FLASH_V3_TIMEOUT DT_PROP(GD32_NV_FLASH_V3_NODE, max_erase_time_ms) |
|
|
|
/** |
|
* @brief GD32 FMC v3 flash memory layout for GD32F4xx series. |
|
*/ |
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT) && \ |
|
defined(CONFIG_SOC_SERIES_GD32F4XX) |
|
#if (PRE_KB(512) == SOC_NV_FLASH_SIZE) |
|
static const struct flash_pages_layout gd32_fmc_v3_layout[] = { |
|
{.pages_count = 4, .pages_size = KB(16)}, |
|
{.pages_count = 1, .pages_size = KB(64)}, |
|
{.pages_count = 3, .pages_size = KB(128)}, |
|
}; |
|
#elif (PRE_KB(1024) == SOC_NV_FLASH_SIZE) |
|
static const struct flash_pages_layout gd32_fmc_v3_layout[] = { |
|
{.pages_count = 4, .pages_size = KB(16)}, |
|
{.pages_count = 1, .pages_size = KB(64)}, |
|
{.pages_count = 7, .pages_size = KB(128)}, |
|
}; |
|
#elif (PRE_KB(2048) == SOC_NV_FLASH_SIZE) |
|
static const struct flash_pages_layout gd32_fmc_v3_layout[] = { |
|
{.pages_count = 4, .pages_size = KB(16)}, |
|
{.pages_count = 1, .pages_size = KB(64)}, |
|
{.pages_count = 7, .pages_size = KB(128)}, |
|
{.pages_count = 4, .pages_size = KB(16)}, |
|
{.pages_count = 1, .pages_size = KB(64)}, |
|
{.pages_count = 7, .pages_size = KB(128)}, |
|
}; |
|
#elif (PRE_KB(3072) == SOC_NV_FLASH_SIZE) |
|
static const struct flash_pages_layout gd32_fmc_v3_layout[] = { |
|
{.pages_count = 4, .pages_size = KB(16)}, |
|
{.pages_count = 1, .pages_size = KB(64)}, |
|
{.pages_count = 7, .pages_size = KB(128)}, |
|
{.pages_count = 4, .pages_size = KB(16)}, |
|
{.pages_count = 1, .pages_size = KB(64)}, |
|
{.pages_count = 7, .pages_size = KB(128)}, |
|
{.pages_count = 4, .pages_size = KB(256)}, |
|
}; |
|
#else |
|
#error "Unknown FMC layout for GD32F4xx series." |
|
#endif |
|
#endif /* CONFIG_FLASH_PAGE_LAYOUT */ |
|
|
|
#define gd32_fmc_v3_WRITE_ERR (FMC_STAT_PGMERR | FMC_STAT_PGSERR | FMC_STAT_WPERR) |
|
#define gd32_fmc_v3_ERASE_ERR FMC_STAT_OPERR |
|
|
|
/* SN bits in FMC_CTL are not continue values, use table below to map them. */ |
|
static uint8_t gd32_fmc_v3_sectors[] = { |
|
0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 11U, |
|
16U, 17U, 18U, 19U, 20U, 21U, 22U, 23U, 24U, 25U, 26U, 27U, |
|
12U, 13U, 14U, 15U |
|
}; |
|
|
|
static inline void gd32_fmc_v3_unlock(void) |
|
{ |
|
FMC_KEY = UNLOCK_KEY0; |
|
FMC_KEY = UNLOCK_KEY1; |
|
} |
|
|
|
static inline void gd32_fmc_v3_lock(void) |
|
{ |
|
FMC_CTL |= FMC_CTL_LK; |
|
} |
|
|
|
static int gd32_fmc_v3_wait_idle(void) |
|
{ |
|
const int64_t expired_time = k_uptime_get() + GD32_NV_FLASH_V3_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) |
|
{ |
|
const struct flash_pages_layout *page_layout; |
|
uint32_t cur = 0U, next = 0U; |
|
|
|
if ((offset > SOC_NV_FLASH_SIZE) || |
|
((offset + len) > SOC_NV_FLASH_SIZE)) { |
|
return false; |
|
} |
|
|
|
if (write) { |
|
/* Check offset and len aligned to write-block-size. */ |
|
if ((offset % sizeof(flash_prg_t)) || |
|
(len % sizeof(flash_prg_t))) { |
|
return false; |
|
} |
|
|
|
} else { |
|
for (size_t i = 0; i < ARRAY_SIZE(gd32_fmc_v3_layout); i++) { |
|
page_layout = &gd32_fmc_v3_layout[i]; |
|
|
|
for (size_t j = 0; j < page_layout->pages_count; j++) { |
|
cur = next; |
|
|
|
next += page_layout->pages_size; |
|
|
|
/* Check bad offset. */ |
|
if ((offset > cur) && (offset < next)) { |
|
return false; |
|
} |
|
|
|
/* Check bad len. */ |
|
if (((offset + len) > cur) && |
|
((offset + len) < next)) { |
|
return false; |
|
} |
|
|
|
if ((offset + len) == next) { |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
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_v3_unlock(); |
|
|
|
if (FMC_STAT & FMC_STAT_BUSY) { |
|
return -EBUSY; |
|
} |
|
|
|
FMC_CTL |= FMC_CTL_PG; |
|
|
|
FMC_CTL &= ~FMC_CTL_PSZ; |
|
FMC_CTL |= CTL_PSZ(sizeof(flash_prg_t) - 1); |
|
|
|
for (size_t i = 0U; i < (len / sizeof(flash_prg_t)); i++) { |
|
*prg_flash++ = *prg_data++; |
|
} |
|
|
|
ret = gd32_fmc_v3_wait_idle(); |
|
if (ret < 0) { |
|
goto expired_out; |
|
} |
|
|
|
if (FMC_STAT & gd32_fmc_v3_WRITE_ERR) { |
|
ret = -EIO; |
|
FMC_STAT |= gd32_fmc_v3_WRITE_ERR; |
|
LOG_ERR("FMC programming failed"); |
|
} |
|
|
|
expired_out: |
|
FMC_CTL &= ~FMC_CTL_PG; |
|
|
|
gd32_fmc_v3_lock(); |
|
|
|
return ret; |
|
} |
|
|
|
static int gd32_fmc_v3_sector_erase(uint8_t sector) |
|
{ |
|
int ret = 0; |
|
|
|
gd32_fmc_v3_unlock(); |
|
|
|
if (FMC_STAT & FMC_STAT_BUSY) { |
|
return -EBUSY; |
|
} |
|
|
|
FMC_CTL |= FMC_CTL_SER; |
|
|
|
FMC_CTL &= ~FMC_CTL_SN; |
|
FMC_CTL |= CTL_SN(sector); |
|
|
|
FMC_CTL |= FMC_CTL_START; |
|
|
|
ret = gd32_fmc_v3_wait_idle(); |
|
if (ret < 0) { |
|
goto expired_out; |
|
} |
|
|
|
if (FMC_STAT & gd32_fmc_v3_ERASE_ERR) { |
|
ret = -EIO; |
|
FMC_STAT |= gd32_fmc_v3_ERASE_ERR; |
|
LOG_ERR("FMC sector %u erase failed", sector); |
|
} |
|
|
|
expired_out: |
|
FMC_CTL &= ~FMC_CTL_SER; |
|
|
|
gd32_fmc_v3_lock(); |
|
|
|
return ret; |
|
} |
|
|
|
int flash_gd32_erase_block(off_t offset, size_t size) |
|
{ |
|
const struct flash_pages_layout *page_layout; |
|
uint32_t erase_offset = 0U; |
|
uint8_t counter = 0U; |
|
int ret = 0; |
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(gd32_fmc_v3_layout); i++) { |
|
page_layout = &gd32_fmc_v3_layout[i]; |
|
|
|
for (size_t j = 0; j < page_layout->pages_count; j++) { |
|
if (erase_offset < offset) { |
|
counter++; |
|
erase_offset += page_layout->pages_size; |
|
|
|
continue; |
|
} |
|
|
|
uint8_t sector = gd32_fmc_v3_sectors[counter++]; |
|
|
|
ret = gd32_fmc_v3_sector_erase(sector); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
erase_offset += page_layout->pages_size; |
|
|
|
if (erase_offset - offset >= size) { |
|
return 0; |
|
} |
|
} |
|
} |
|
|
|
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_v3_layout; |
|
*layout_size = ARRAY_SIZE(gd32_fmc_v3_layout); |
|
} |
|
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
|
|
|