blob: 2d944f18e14fc0d6588211b3efba60f85dcd100b [file]
/*
* Copyright (c) 2026 Microchip Technology Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT microchip_nvmctrl_g2
#include <soc.h>
#include <zephyr/irq.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/sys/clock.h>
#include <zephyr/drivers/clock_control/mchp_clock_control.h>
#include <zephyr/drivers/flash/mchp_flash.h>
LOG_MODULE_REGISTER(flash_mchp_nvmctrl_g2);
#define SOC_NV_FLASH_COMPAT(node_id) \
COND_CODE_1(DT_NODE_HAS_COMPAT(node_id, soc_nv_flash), (node_id), ())
#define SOC_NV_FLASH_NODE(n) DT_INST_FOREACH_CHILD_STATUS_OKAY(n, SOC_NV_FLASH_COMPAT)
#define SOC_NV_FLASH_BASE_ADDR(n) DT_REG_ADDR(SOC_NV_FLASH_NODE(n))
#define SOC_NV_FLASH_SIZE(n) DT_REG_SIZE(SOC_NV_FLASH_NODE(n))
#define SOC_NV_FLASH_WRITE_BLOCK_SIZE(n) DT_PROP(SOC_NV_FLASH_NODE(n), write_block_size)
#define SOC_NV_FLASH_ERASE_BLOCK_SIZE(n) DT_PROP(SOC_NV_FLASH_NODE(n), erase_block_size)
#define SOC_NV_DATA_FLASH_START_ADDR(n) DT_PROP(SOC_NV_FLASH_NODE(n), data_flash_start_addr)
#define SOC_NV_USER_ROW_START_ADDR(n) DT_PROP(SOC_NV_FLASH_NODE(n), user_row_start_addr)
#define SOC_NV_FLASH_UNSUPPORTED_REGION_COUNT(n) \
DT_PROP_LEN(SOC_NV_FLASH_NODE(n), unsupported_region) / 2
#define SOC_NV_FLASH_UNSUPPORTED_REGIONS(n) DT_PROP(SOC_NV_FLASH_NODE(n), unsupported_region)
#define TIMEOUT_VALUE_US 100000U
#define MCHP_FLASH_G2_WAIT_STATE_0_FREQ_MHZ 21000000U
#define MCHP_FLASH_WAITSTATE_0 0U
#define MCHP_FLASH_G2_WAIT_STATE_1_FREQ_MHZ 42000000U
#define MCHP_FLASH_WAITSTATE_1 1U
#define MCHP_FLASH_G2_WAIT_STATE_2_FREQ_MHZ 48000000U
#define MCHP_FLASH_WAITSTATE_2 2U
#define DELAY_US 2U
#define IS_MCHP_FLASH_G2_ALIGNED(value, alignment) (((value) & ((alignment) - 1)) == 0U)
enum flash_kind {
MAIN_FLASH = 0U,
DATA_FLASH = 1U,
USER_ROW = 2U,
};
enum flash_operation {
OPERATION_READ = 0U,
OPERATION_WRITE = 1U,
OPERATION_ERASE = 2U,
};
struct flash_mchp_g2_clock {
const struct device *clock_dev;
clock_control_subsys_t mclk_sys;
};
struct nvmctrl_mchp_g2_config {
nvmctrl_registers_t *regs;
struct flash_mchp_g2_clock flash_clock;
struct flash_parameters parameters;
#if CONFIG_FLASH_PAGE_LAYOUT
struct flash_pages_layout page_layout;
#endif
uint32_t base_addr;
uint32_t size;
uint32_t write_block_size;
uint32_t erase_block_size;
uint32_t data_flash_start_addr;
uint32_t user_row_start_addr;
uint32_t unimplemented_region_count;
uint32_t *unimplemented_regions;
};
struct nvmctrl_mchp_g2_data {
struct k_sem sem_lock;
};
static inline void nvmctrl_intflag_ready_wait(nvmctrl_registers_t *regs)
{
if (WAIT_FOR(((regs->NVMCTRL_INTFLAG & NVMCTRL_INTFLAG_READY_Msk) ==
NVMCTRL_INTFLAG_READY_Msk),
TIMEOUT_VALUE_US, k_busy_wait(DELAY_US)) == false) {
LOG_WRN("NVMCTRL_STATUS_READY wait timed out");
}
}
static enum flash_kind
get_flash_kind_from_address(const struct nvmctrl_mchp_g2_config *const config,
uint32_t absolute_address)
{
if (absolute_address >= config->user_row_start_addr) {
return USER_ROW;
} else if (absolute_address >= config->data_flash_start_addr) {
return DATA_FLASH;
}
return MAIN_FLASH;
}
static void init_nvmctrl_reg_defaults(nvmctrl_registers_t *regs, uint8_t flash_wait_states)
{
regs->NVMCTRL_CTRLB =
(NVMCTRL_CTRLB_CACHEDIS_CACHE_DF_DIS_MAIN_EN |
NVMCTRL_CTRLB_READMODE_NO_MISS_PENALTY | NVMCTRL_CTRLB_SLEEPPRM_WAKEONACCESS |
NVMCTRL_CTRLB_RWS(flash_wait_states) | NVMCTRL_CTRLB_MANW_Msk);
regs->NVMCTRL_INTENCLR = NVMCTRL_INTENCLR_Msk;
}
static uint16_t get_err_status(nvmctrl_registers_t *regs)
{
uint16_t status =
(regs->NVMCTRL_STATUS &
(NVMCTRL_STATUS_NVME_Msk | NVMCTRL_STATUS_LOCKE_Msk | NVMCTRL_STATUS_PROGE_Msk));
regs->NVMCTRL_STATUS |= status;
return status;
}
static int execute_cmd(const struct device *dev, uint16_t cmd)
{
const struct nvmctrl_mchp_g2_config *const config = dev->config;
uint16_t nvm_error;
nvmctrl_registers_t *regs = config->regs;
nvmctrl_intflag_ready_wait(regs);
regs->NVMCTRL_CTRLA = cmd | NVMCTRL_CTRLA_CMDEX_KEY;
nvmctrl_intflag_ready_wait(regs);
nvm_error = get_err_status(regs);
if (nvm_error != 0) {
if ((nvm_error & NVMCTRL_STATUS_NVME_Msk) != 0) {
LOG_ERR("At least one error has been registered from the NVM Controller");
}
if ((nvm_error & NVMCTRL_STATUS_LOCKE_Msk) != 0) {
LOG_ERR("Programming attempt on at least one locked lock region has "
"happened");
}
if ((nvm_error & NVMCTRL_STATUS_PROGE_Msk) != 0) {
LOG_ERR("An invalid command and/or a bad keyword was/were written in the "
"NVM Command register");
}
return -EIO;
}
return 0;
}
static int set_addr_execute_cmd(const struct device *dev, uint32_t address, uint16_t cmd)
{
const struct nvmctrl_mchp_g2_config *const config = dev->config;
config->regs->NVMCTRL_ADDR = address >> 1U;
return execute_cmd(dev, cmd);
}
static int get_nvmctrl_commands(enum flash_kind f_kind, enum flash_operation op, uint16_t *command)
{
int ret = -EINVAL;
switch (op) {
case OPERATION_WRITE:
if (f_kind == MAIN_FLASH) {
*command = NVMCTRL_CTRLA_CMD_WP;
ret = 0;
} else if (f_kind == DATA_FLASH) {
*command = NVMCTRL_CTRLA_CMD_DFWP;
ret = 0;
} else if (f_kind == USER_ROW) {
*command = NVMCTRL_CTRLA_CMD_WAP;
ret = 0;
} else {
LOG_ERR("Unsupported Flash Type");
}
break;
case OPERATION_ERASE:
if (f_kind == MAIN_FLASH) {
*command = NVMCTRL_CTRLA_CMD_ER;
ret = 0;
} else if (f_kind == DATA_FLASH) {
*command = NVMCTRL_CTRLA_CMD_DFER;
ret = 0;
} else if (f_kind == USER_ROW) {
*command = NVMCTRL_CTRLA_CMD_EAR;
ret = 0;
} else {
LOG_ERR("Unsupported Flash Type");
}
break;
default:
LOG_ERR("Unsupported Operation");
}
return ret;
}
static int validate_flash_parameters(const struct nvmctrl_mchp_g2_config *const config,
off_t offset, size_t size, enum flash_operation op)
{
if (offset > config->size || size > config->size || (offset + size) > config->size) {
LOG_WRN("Offset+Size is beyond flash addressable range."
" Off: %#08x, Sz: %#08x, MaxSz: %#08x",
(uint32_t)offset, size, config->size);
return -EINVAL;
}
for (int i = 0; i < config->unimplemented_region_count; i++) {
uint32_t unimp_region_start = config->unimplemented_regions[i * 2];
uint32_t unimp_region_end = config->unimplemented_regions[(i * 2) + 1];
if ((config->base_addr + offset + size) > unimp_region_start &&
(config->base_addr + offset + size) <= unimp_region_end) {
LOG_WRN("Offset+Size lies in unimplemented flash range."
" Off: %#08x, Sz: %#08x",
(uint32_t)offset, size);
return -EINVAL;
}
}
switch (op) {
case OPERATION_READ:
break;
case OPERATION_WRITE:
if (!(IS_MCHP_FLASH_G2_ALIGNED(offset, config->write_block_size))) {
LOG_WRN("WRITE: Offset should be multiples of write block size."
" Off: %#08x",
(uint32_t)offset);
return -EINVAL;
}
if (!(IS_MCHP_FLASH_G2_ALIGNED(size, config->write_block_size))) {
LOG_WRN("WRITE: Size should be multiples of write block size."
" Sz: %#08x",
size);
return -EINVAL;
}
break;
case OPERATION_ERASE:
if (size < config->erase_block_size) {
LOG_WRN("ERASE: Cannot erase less than a size of an erase block."
" Sz: %#08x",
size);
return -EINVAL;
}
if (!IS_MCHP_FLASH_G2_ALIGNED(size, config->erase_block_size)) {
LOG_WRN("ERASE: Size should be multiples of erase block size."
" Sz: %#08x",
size);
return -EINVAL;
}
if (!IS_MCHP_FLASH_G2_ALIGNED(offset, config->erase_block_size)) {
LOG_WRN("ERASE: Offset should be multiples of erase-block size."
" Sz: %#08x",
size);
return -EINVAL;
}
break;
default:
LOG_ERR("Unsupported operation");
return -EINVAL;
}
return 0;
}
static int flash_mchp_read(const struct device *dev, off_t offset, void *data_buff,
size_t no_of_bytes)
{
const struct nvmctrl_mchp_g2_config *const config = dev->config;
struct nvmctrl_mchp_g2_data *data = dev->data;
int ret;
if (no_of_bytes == 0) {
return 0;
}
ret = validate_flash_parameters(config, offset, no_of_bytes, OPERATION_READ);
if (ret != 0) {
LOG_ERR("Error occurred in validating Flash read arguments");
return ret;
}
k_sem_take(&data->sem_lock, K_FOREVER);
memcpy(data_buff, (uint8_t *)(config->base_addr + offset), no_of_bytes);
k_sem_give(&data->sem_lock);
return 0;
}
static int flash_mchp_write(const struct device *dev, off_t offset, const void *data_buff,
size_t no_of_bytes)
{
const struct nvmctrl_mchp_g2_config *const config = dev->config;
struct nvmctrl_mchp_g2_data *data = dev->data;
int nvm_error = -EIO;
uint16_t command;
uint32_t i;
uint32_t word_value;
const uint8_t *src = (const uint8_t *)data_buff;
uint32_t *dst = (uint32_t *)(config->base_addr + offset);
if (no_of_bytes == 0) {
return 0;
}
nvm_error = validate_flash_parameters(config, offset, no_of_bytes, OPERATION_WRITE);
if (nvm_error != 0) {
LOG_ERR("Error occurred in validating Flash write arguments");
return nvm_error;
}
nvm_error = get_nvmctrl_commands(
get_flash_kind_from_address(config, (offset + config->base_addr)), OPERATION_WRITE,
&command);
if (nvm_error != 0) {
LOG_ERR("Error obtaining Flash command");
return -EIO;
}
while (no_of_bytes > 0) {
LOG_DBG("Writing block at address %#08x, Size: %#08x",
(uint32_t)(config->base_addr + offset), no_of_bytes);
k_sem_take(&data->sem_lock, K_FOREVER);
for (i = 0U; i < (config->write_block_size / 4U); i++) {
memcpy(&word_value, src, sizeof(word_value));
*dst = word_value;
dst++;
src += 4;
}
nvm_error = set_addr_execute_cmd(dev, (config->base_addr + offset), command);
k_sem_give(&data->sem_lock);
if (nvm_error != 0) {
LOG_ERR("Error while writing block at address %#08x, Size: %#08x",
(uint32_t)(config->base_addr + offset), no_of_bytes);
break;
}
no_of_bytes -= config->write_block_size;
offset += config->write_block_size;
}
return nvm_error;
}
static int flash_mchp_erase(const struct device *dev, off_t offset, size_t no_of_bytes)
{
const struct nvmctrl_mchp_g2_config *const config = dev->config;
struct nvmctrl_mchp_g2_data *data = dev->data;
int nvm_error = -EIO;
uint16_t command;
if (no_of_bytes == 0) {
return 0;
}
nvm_error = validate_flash_parameters(config, offset, no_of_bytes, OPERATION_ERASE);
if (nvm_error != 0) {
LOG_ERR("Error occurred in validating Flash erase arguments");
return nvm_error;
}
nvm_error = get_nvmctrl_commands(
get_flash_kind_from_address(config, (offset + config->base_addr)), OPERATION_ERASE,
&command);
if (nvm_error != 0) {
LOG_ERR("Error obtaining Flash command");
return -EIO;
}
while (no_of_bytes > 0U) {
LOG_DBG("Erasing block at address %#08x", (uint32_t)(config->base_addr + offset));
k_sem_take(&data->sem_lock, K_FOREVER);
nvm_error = set_addr_execute_cmd(dev, (config->base_addr + offset), command);
k_sem_give(&data->sem_lock);
if (nvm_error != 0) {
LOG_ERR("Error while erasing block at address %#08x",
(uint32_t)(config->base_addr + offset));
break;
}
no_of_bytes -= config->erase_block_size;
offset += config->erase_block_size;
}
return nvm_error;
}
#if CONFIG_FLASH_EX_OP_ENABLED
static int flash_mchp_ex_op(const struct device *dev, uint16_t opr_code, const uintptr_t offset,
void *out)
{
const struct nvmctrl_mchp_g2_config *const config = dev->config;
struct nvmctrl_mchp_g2_data *data = dev->data;
int ret;
uint32_t address;
k_sem_take(&data->sem_lock, K_FOREVER);
switch (opr_code) {
/* Lock a specific region of flash memory */
case FLASH_EX_OP_REGION_LOCK:
address = config->base_addr + *(uint32_t *)offset;
ret = set_addr_execute_cmd(dev, address, NVMCTRL_CTRLA_CMD_LR);
break;
/* Unlock a specific region of flash memory */
case FLASH_EX_OP_REGION_UNLOCK:
address = config->base_addr + *(uint32_t *)offset;
ret = set_addr_execute_cmd(dev, address, NVMCTRL_CTRLA_CMD_UR);
break;
/* Sets the Power Reduction mode of flash memory */
case FLASH_EX_OP_SET_PWR_REDUCTION_MODE:
ret = execute_cmd(dev, NVMCTRL_CTRLA_CMD_SPRM);
break;
/* Clears the Power Reduction mode of flash memory */
case FLASH_EX_OP_CLR_PWR_REDUCTION_MODE:
ret = execute_cmd(dev, NVMCTRL_CTRLA_CMD_CPRM);
break;
/* Clears the flash page buffer */
case FLASH_EX_OP_CLR_PAGE_BUFFER:
ret = execute_cmd(dev, NVMCTRL_CTRLA_CMD_PBC);
break; /* Set Security Bit */
case FLASH_EX_OP_SET_SECURITY_BIT:
ret = execute_cmd(dev, NVMCTRL_CTRLA_CMD_SSB);
break;
/* Invalidates all cache lines */
case FLASH_EX_OP_INVALIDATE_CACHE_LINES:
ret = execute_cmd(dev, NVMCTRL_CTRLA_CMD_INVALL);
break;
/* Set Chip Erase Hard Lock */
case FLASH_EX_OP_SET_CHIP_ERASE_HARDLOCK:
ret = execute_cmd(dev, NVMCTRL_CTRLA_CMD_SCEHL);
break;
default:
ret = -EINVAL;
}
k_sem_give(&data->sem_lock);
return ret;
}
#endif /* CONFIG_FLASH_EX_OP_ENABLED */
static int nvmctrl_init(const struct device *nvmctrl_dev)
{
const struct nvmctrl_mchp_g2_config *config = nvmctrl_dev->config;
struct nvmctrl_mchp_g2_data *data = nvmctrl_dev->data;
uint32_t operating_freq;
uint8_t flash_wait_states;
int ret;
/* Turn-On Global clock for NVMCTRL */
ret = clock_control_on(config->flash_clock.clock_dev, config->flash_clock.mclk_sys);
if ((ret != 0) && (ret != -EALREADY)) {
LOG_ERR("Failed to enable the GCLK for NVMCTRL: %d", ret);
return ret;
}
ret = clock_control_get_rate(config->flash_clock.clock_dev, config->flash_clock.mclk_sys,
&operating_freq);
if (ret != 0) {
/* If clock freq could not be retrieved, set the waitstate to max */
flash_wait_states = MCHP_FLASH_WAITSTATE_2;
} else if (operating_freq <= MCHP_FLASH_G2_WAIT_STATE_0_FREQ_MHZ) {
flash_wait_states = MCHP_FLASH_WAITSTATE_0;
} else if (operating_freq <= MCHP_FLASH_G2_WAIT_STATE_1_FREQ_MHZ) {
flash_wait_states = MCHP_FLASH_WAITSTATE_1;
} else {
flash_wait_states = MCHP_FLASH_WAITSTATE_2;
}
init_nvmctrl_reg_defaults(config->regs, flash_wait_states);
k_sem_init(&data->sem_lock, 1, 1);
return 0;
}
#if CONFIG_FLASH_PAGE_LAYOUT
#define FLASH_MCHP_G2_CONFIG_PAGE_LAYOUT(n) \
.page_layout.pages_count = SOC_NV_FLASH_SIZE(n) / SOC_NV_FLASH_ERASE_BLOCK_SIZE(n), \
.page_layout.pages_size = SOC_NV_FLASH_ERASE_BLOCK_SIZE(n),
#else
#define FLASH_MCHP_G2_CONFIG_PAGE_LAYOUT(n)
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
#if CONFIG_FLASH_PAGE_LAYOUT
void flash_mchp_page_layout(const struct device *dev, const struct flash_pages_layout **layout,
size_t *layout_size)
{
const struct nvmctrl_mchp_g2_config *const config = dev->config;
*layout = &config->page_layout;
*layout_size = 1;
}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
static const struct flash_parameters *flash_mchp_get_parameters(const struct device *dev)
{
const struct nvmctrl_mchp_g2_config *const config = dev->config;
return &config->parameters;
}
static DEVICE_API(flash, flash_mchp_g2_driver_api) = {
.read = flash_mchp_read,
.write = flash_mchp_write,
.erase = flash_mchp_erase,
.get_parameters = flash_mchp_get_parameters,
#ifdef CONFIG_FLASH_PAGE_LAYOUT
.page_layout = flash_mchp_page_layout,
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
#ifdef CONFIG_FLASH_EX_OP_ENABLED
.ex_op = flash_mchp_ex_op,
#endif /*CONFIG_FLASH_EX_OP_ENABLED*/
};
#define MCHP_NVMCTRL_G2_INIT(n) \
static struct nvmctrl_mchp_g2_data nvmctrl_data_##n; \
static uint32_t dt_unimplemented_regions##n[] = SOC_NV_FLASH_UNSUPPORTED_REGIONS(n); \
static const struct nvmctrl_mchp_g2_config nvmctrl_config_##n = { \
.regs = (nvmctrl_registers_t *)DT_INST_REG_ADDR(n), \
.flash_clock.clock_dev = DEVICE_DT_GET(DT_INST_PHANDLE(n, clock_parent)), \
.flash_clock.mclk_sys = (void *)(DT_INST_CLOCKS_CELL_BY_NAME(n, mclk, subsystem)), \
.parameters = \
{ \
.write_block_size = SOC_NV_FLASH_WRITE_BLOCK_SIZE(n), \
.erase_value = 0xff, \
}, \
.base_addr = SOC_NV_FLASH_BASE_ADDR(n), \
.size = SOC_NV_FLASH_SIZE(n), \
.write_block_size = SOC_NV_FLASH_WRITE_BLOCK_SIZE(n), \
.erase_block_size = SOC_NV_FLASH_ERASE_BLOCK_SIZE(n), \
.data_flash_start_addr = SOC_NV_DATA_FLASH_START_ADDR(n), \
.user_row_start_addr = SOC_NV_USER_ROW_START_ADDR(n), \
.unimplemented_region_count = SOC_NV_FLASH_UNSUPPORTED_REGION_COUNT(n), \
.unimplemented_regions = dt_unimplemented_regions##n, \
FLASH_MCHP_G2_CONFIG_PAGE_LAYOUT(n)}; \
DEVICE_DT_INST_DEFINE(n, nvmctrl_init, NULL, &nvmctrl_data_##n, &nvmctrl_config_##n, \
POST_KERNEL, CONFIG_FLASH_INIT_PRIORITY, &flash_mchp_g2_driver_api);
DT_INST_FOREACH_STATUS_OKAY(MCHP_NVMCTRL_G2_INIT)