drivers: flash: support for Nuvoton numaker series FMC

Add Nuvoton numaker series flash memory controller(FMC) with erase,
 read & write features of soc-flash. Also update Nuvoton manifest
 to include zephyrproject-rtos/hal_nuvoton#6.

Signed-off-by: cyliang tw <cyliang@nuvoton.com>
This commit is contained in:
cyliang tw 2023-07-20 21:10:29 +08:00 committed by Fabio Baltieri
parent 748683fae6
commit ecbaac60bd
8 changed files with 335 additions and 8 deletions

View file

@ -20,3 +20,7 @@ CONFIG_UART_INTERRUPT_DRIVEN=y
# console
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
# Enable FMC
CONFIG_FLASH=y
CONFIG_SOC_FLASH_NUMAKER=y

View file

@ -109,3 +109,4 @@ zephyr_library_include_directories_ifdef(
zephyr_library_sources_ifdef(CONFIG_FLASH_SHELL flash_shell.c)
zephyr_library_sources_ifdef(CONFIG_FLASH_JESD216 jesd216.c)
zephyr_library_sources_ifdef(CONFIG_FLASH_INFINEON_CAT1 flash_ifx_cat1.c)
zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_NUMAKER soc_flash_numaker.c)

View file

@ -152,4 +152,6 @@ source "drivers/flash/Kconfig.xmc4xxx"
source "drivers/flash/Kconfig.ifx_cat1"
source "drivers/flash/Kconfig.numaker"
endif # FLASH

View file

@ -0,0 +1,16 @@
# NUMAKER GPIO driver configuration options
# Copyright (c) 2023 Nuvoton Technology Corporation.
# SPDX-License-Identifier: Apache-2.0
config SOC_FLASH_NUMAKER
bool "Nuvoton NuMaker MCU flash driver"
default y
select FLASH_HAS_PAGE_LAYOUT
select FLASH_HAS_DRIVER_ENABLED
select HAS_NUMAKER_FMC
depends on DT_HAS_NUVOTON_NUMAKER_FMC_ENABLED
help
This option enables the FMC driver for Nuvoton NuMaker family of
processors.
Say y if you wish to enable NuMaker FMC.

View file

@ -0,0 +1,284 @@
/*
* 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);

View file

@ -12,6 +12,10 @@
#include <zephyr/dt-bindings/gpio/gpio.h>
/ {
chosen {
zephyr,flash-controller = &fmc;
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
@ -28,13 +32,6 @@
reg = <0x20000000 DT_SIZE_K(512)>;
};
flash0: flash@0 {
compatible = "soc-nv-flash";
reg = <0 DT_SIZE_K(1024)>;
erase-block-size = <4096>;
write-block-size = <4>;
};
sysclk: system-clock {
compatible = "fixed-clock";
clock-frequency = <200000000>;
@ -65,6 +62,20 @@
status = "okay";
};
fmc: flash-controller@4000c000 {
compatible = "nuvoton,numaker-fmc";
reg = <0x4000c000 0x110>;
#address-cells = <1>;
#size-cells = <1>;
flash0: flash@0 {
compatible = "soc-nv-flash";
reg = <0 DT_SIZE_K(1024)>;
erase-block-size = <4096>;
write-block-size = <4>;
};
};
uart0: serial@40070000 {
compatible = "nuvoton,numaker-uart";
reg = <0x40070000 0x1000>;

View file

@ -0,0 +1,9 @@
description: Nuvoton NuMaker Flash Controller
compatible: "nuvoton,numaker-fmc"
include: flash-controller.yaml
properties:
reg:
required: true

View file

@ -178,7 +178,7 @@ manifest:
groups:
- hal
- name: hal_nuvoton
revision: 8a2b5de1670b59fcacd20d7495cb1c0f26fbe7bd
revision: 3e0a4c4d3328b2f72b164219add19d5308b53cb5
path: modules/hal/nuvoton
groups:
- hal