blob: 81daec426c831b00af3518ef09609ab579e13904 [file] [log] [blame]
/*
* 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);