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:
parent
748683fae6
commit
ecbaac60bd
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -152,4 +152,6 @@ source "drivers/flash/Kconfig.xmc4xxx"
|
|||
|
||||
source "drivers/flash/Kconfig.ifx_cat1"
|
||||
|
||||
source "drivers/flash/Kconfig.numaker"
|
||||
|
||||
endif # FLASH
|
||||
|
|
16
drivers/flash/Kconfig.numaker
Normal file
16
drivers/flash/Kconfig.numaker
Normal 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.
|
284
drivers/flash/soc_flash_numaker.c
Normal file
284
drivers/flash/soc_flash_numaker.c
Normal 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);
|
|
@ -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>;
|
||||
|
|
9
dts/bindings/flash_controller/nuvoton,numaker-fmc.yaml
Normal file
9
dts/bindings/flash_controller/nuvoton,numaker-fmc.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
description: Nuvoton NuMaker Flash Controller
|
||||
|
||||
compatible: "nuvoton,numaker-fmc"
|
||||
|
||||
include: flash-controller.yaml
|
||||
|
||||
properties:
|
||||
reg:
|
||||
required: true
|
Loading…
Reference in a new issue