285 lines
6.6 KiB
C
285 lines
6.6 KiB
C
/*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Copyright (c) 2023 Nuvoton Technology Corporation.
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nuvoton_numaker_fmc
|
|
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/flash.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include "flash_priv.h"
|
|
#include <NuMicro.h>
|
|
|
|
LOG_MODULE_REGISTER(flash_numaker, CONFIG_FLASH_LOG_LEVEL);
|
|
|
|
#define SOC_NV_FLASH_NODE DT_INST(0, soc_nv_flash)
|
|
#define SOC_NV_FLASH_WRITE_BLOCK_SIZE DT_PROP_OR(SOC_NV_FLASH_NODE, write_block_size, 0x04)
|
|
|
|
struct flash_numaker_data {
|
|
FMC_T *fmc;
|
|
struct k_sem write_lock;
|
|
uint32_t flash_block_base;
|
|
};
|
|
|
|
static const struct flash_parameters flash_numaker_parameters = {
|
|
.write_block_size = SOC_NV_FLASH_WRITE_BLOCK_SIZE,
|
|
.erase_value = 0xff,
|
|
};
|
|
|
|
/* Validate offset and length */
|
|
static bool flash_numaker_is_range_valid(off_t offset, size_t len)
|
|
{
|
|
uint32_t aprom_size = (FMC_APROM_END - FMC_APROM_BASE);
|
|
|
|
/* check for min value */
|
|
if ((offset < 0) || (len == 0)) {
|
|
return false;
|
|
}
|
|
|
|
/* check for max value */
|
|
if (offset >= aprom_size || len > aprom_size || (aprom_size - offset) < len) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Erase a flash memory area.
|
|
*
|
|
* param dev Device struct
|
|
* param offset The address's offset
|
|
* param len The size of the buffer
|
|
* return 0 on success
|
|
* return -EINVAL erroneous code
|
|
*/
|
|
|
|
static int flash_numaker_erase(const struct device *dev, off_t offset, size_t len)
|
|
{
|
|
struct flash_numaker_data *dev_data = dev->data;
|
|
uint32_t rc = 0;
|
|
unsigned int key;
|
|
int page_nums = (len / FMC_FLASH_PAGE_SIZE);
|
|
uint32_t addr = dev_data->flash_block_base + offset;
|
|
|
|
/* return SUCCESS for len == 0 (required by tests/drivers/flash) */
|
|
if (!len) {
|
|
return 0;
|
|
}
|
|
|
|
/* Validate range */
|
|
if (!flash_numaker_is_range_valid(offset, len)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check alignment and erase only by pages */
|
|
if (((addr % FMC_FLASH_PAGE_SIZE) != 0) || ((len % FMC_FLASH_PAGE_SIZE) != 0)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* take semaphore */
|
|
if (k_sem_take(&dev_data->write_lock, K_NO_WAIT)) {
|
|
return -EACCES;
|
|
}
|
|
|
|
SYS_UnlockReg();
|
|
key = irq_lock();
|
|
while (page_nums) {
|
|
if (((len >= FMC_BANK_SIZE)) && ((addr % FMC_BANK_SIZE) == 0)) {
|
|
if (FMC_Erase_Bank(addr)) {
|
|
LOG_ERR("Erase flash bank failed or erase time-out");
|
|
rc = -EIO;
|
|
goto done;
|
|
}
|
|
page_nums -= (FMC_BANK_SIZE / FMC_FLASH_PAGE_SIZE);
|
|
addr += FMC_BANK_SIZE;
|
|
} else {
|
|
/* erase page */
|
|
if (FMC_Erase(addr)) {
|
|
LOG_ERR("Erase flash page failed or erase time-out");
|
|
rc = -EIO;
|
|
goto done;
|
|
}
|
|
page_nums--;
|
|
addr += FMC_FLASH_PAGE_SIZE;
|
|
}
|
|
}
|
|
|
|
done:
|
|
SYS_LockReg();
|
|
irq_unlock(key);
|
|
/* release semaphore */
|
|
k_sem_give(&dev_data->write_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Read a flash memory area.
|
|
*
|
|
* param dev Device struct
|
|
* param offset The address's offset
|
|
* param data The buffer to store or read the value
|
|
* param length The size of the buffer
|
|
* return 0 on success,
|
|
* return -EIO erroneous code
|
|
*/
|
|
static int flash_numaker_read(const struct device *dev, off_t offset, void *data, size_t len)
|
|
{
|
|
struct flash_numaker_data *dev_data = dev->data;
|
|
uint32_t addr = dev_data->flash_block_base + offset;
|
|
|
|
/* return SUCCESS for len == 0 (required by tests/drivers/flash) */
|
|
if (!len) {
|
|
return 0;
|
|
}
|
|
|
|
/* Validate range */
|
|
if (!flash_numaker_is_range_valid(offset, len)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* read flash */
|
|
memcpy(data, (void *)addr, len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t flash_numaker_block_write(uint32_t u32_addr, uint8_t *pu8_data, int block_size)
|
|
{
|
|
int32_t retval;
|
|
uint32_t *pu32_data = (uint32_t *)pu8_data;
|
|
|
|
SYS_UnlockReg();
|
|
if (block_size == 4) {
|
|
retval = FMC_Write(u32_addr, *pu32_data);
|
|
} else if (block_size == 8) {
|
|
retval = FMC_Write8Bytes(u32_addr, *pu32_data, *(pu32_data + 1));
|
|
} else {
|
|
retval = -1;
|
|
}
|
|
SYS_LockReg();
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int flash_numaker_write(const struct device *dev, off_t offset, const void *data, size_t len)
|
|
{
|
|
struct flash_numaker_data *dev_data = dev->data;
|
|
uint32_t rc = 0;
|
|
unsigned int key;
|
|
uint32_t addr = dev_data->flash_block_base + offset;
|
|
int block_size = flash_numaker_parameters.write_block_size;
|
|
int blocks = (len / flash_numaker_parameters.write_block_size);
|
|
uint8_t *pu8_data = (uint8_t *)data;
|
|
|
|
/* return SUCCESS for len == 0 (required by tests/drivers/flash) */
|
|
if (!len) {
|
|
return 0;
|
|
}
|
|
|
|
/* Validate range */
|
|
if (!flash_numaker_is_range_valid(offset, len)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Validate address alignment */
|
|
if ((addr % flash_numaker_parameters.write_block_size) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Validate write size be multiples of the write block size */
|
|
if ((len % block_size) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Validate offset be multiples of the write block size */
|
|
if ((offset % block_size) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (k_sem_take(&dev_data->write_lock, K_FOREVER)) {
|
|
return -EACCES;
|
|
}
|
|
|
|
key = irq_lock();
|
|
|
|
while (blocks) {
|
|
if (flash_numaker_block_write(addr, pu8_data, block_size)) {
|
|
rc = -EIO;
|
|
goto done;
|
|
}
|
|
pu8_data += block_size;
|
|
addr += block_size;
|
|
blocks--;
|
|
}
|
|
|
|
done:
|
|
irq_unlock(key);
|
|
|
|
k_sem_give(&dev_data->write_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
static const struct flash_pages_layout dev_layout = {
|
|
.pages_count =
|
|
DT_REG_SIZE(SOC_NV_FLASH_NODE) / DT_PROP(SOC_NV_FLASH_NODE, erase_block_size),
|
|
.pages_size = DT_PROP(SOC_NV_FLASH_NODE, erase_block_size),
|
|
};
|
|
|
|
static void flash_numaker_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 const struct flash_parameters *flash_numaker_get_parameters(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
return &flash_numaker_parameters;
|
|
}
|
|
|
|
static struct flash_numaker_data flash_data;
|
|
|
|
static const struct flash_driver_api flash_numaker_api = {
|
|
.erase = flash_numaker_erase,
|
|
.write = flash_numaker_write,
|
|
.read = flash_numaker_read,
|
|
.get_parameters = flash_numaker_get_parameters,
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
.page_layout = flash_numaker_pages_layout,
|
|
#endif
|
|
};
|
|
|
|
static int flash_numaker_init(const struct device *dev)
|
|
{
|
|
struct flash_numaker_data *dev_data = dev->data;
|
|
|
|
k_sem_init(&dev_data->write_lock, 1, 1);
|
|
|
|
/* Enable FMC ISP function */
|
|
SYS_UnlockReg();
|
|
FMC_Open();
|
|
/* Enable APROM update. */
|
|
FMC_ENABLE_AP_UPDATE();
|
|
SYS_LockReg();
|
|
dev_data->flash_block_base = (uint32_t)FMC_APROM_BASE;
|
|
dev_data->fmc = (FMC_T *)DT_REG_ADDR(DT_NODELABEL(fmc));
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEVICE_DT_INST_DEFINE(0, flash_numaker_init, NULL, &flash_data, NULL, POST_KERNEL,
|
|
CONFIG_FLASH_INIT_PRIORITY, &flash_numaker_api);
|