diff --git a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.dts b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.dts index 5f8ac877f8e8d..44c6d4587a297 100644 --- a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.dts +++ b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.dts @@ -18,6 +18,8 @@ zephyr,shell-uart = &sercom2; zephyr,sram = &sram0; zephyr,flash = &flash0; + zephyr,code-partition = &slot0_partition; + zephyr,flash-controller = &nvmctrl; }; aliases { @@ -40,14 +42,21 @@ #address-cells = <1>; #size-cells = <1>; - /* - * The final 16 KiB is reserved for the application. - * Storage partition will be used by FCB/LittleFS/NVS - * if enabled. - */ - storage_partition: partition@fc000 { - label = "storage"; - reg = <0x000fc000 0x00004000>; + boot_partition: partition@0 { + reg = <0x00000000 0x00010000>; /* 64 KB bootloader */ + read-only; + }; + + slot0_partition: partition@10000 { + reg = <0x00010000 0x00070000>; /* 448 KB primary app slot */ + }; + + slot1_partition: partition@80000 { + reg = <0x00080000 0x00070000>; /* 448 KB secondary app slot */ + }; + + storage_partition: partition@f0000 { + reg = <0x000f0000 0x00010000>; /* 64 KB storage area */ }; }; }; diff --git a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml index 06b83ffc0fc55..902dc3a936c90 100644 --- a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml +++ b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml @@ -10,6 +10,8 @@ toolchain: flash: 1024 ram: 256 supported: + - flash + - mcuboot - pinctrl - pwm - reset diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index 3ed0fdaf9879f..e1deef2ed9d84 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -31,6 +31,7 @@ zephyr_library_sources_ifdef(CONFIG_FLASH_ANDES_QSPI_XIP flash_andes_qspi_xip.c) zephyr_library_sources_ifdef(CONFIG_FLASH_CAD_QSPI_NOR flash_cadence_qspi_nor.c flash_cadence_qspi_nor_ll.c) zephyr_library_sources_ifdef(CONFIG_FLASH_CDNS_NAND flash_cadence_nand.c flash_cadence_nand_ll.c) zephyr_library_sources_ifdef(CONFIG_FLASH_INFINEON_CAT1 flash_ifx_cat1.c) +zephyr_library_sources_ifdef(CONFIG_FLASH_MCHP_NVMCTRL_G1 flash_mchp_nvmctrl_g1.c) zephyr_library_sources_ifdef(CONFIG_FLASH_MCUX_FLEXSPI_HYPERFLASH flash_mcux_flexspi_hyperflash.c) zephyr_library_sources_ifdef(CONFIG_FLASH_MCUX_FLEXSPI_MX25UM51345G flash_mcux_flexspi_mx25um51345g.c) zephyr_library_sources_ifdef(CONFIG_FLASH_MCUX_FLEXSPI_NOR flash_mcux_flexspi_nor.c) diff --git a/drivers/flash/Kconfig b/drivers/flash/Kconfig index f563bf703aafe..4ff1f0b882335 100644 --- a/drivers/flash/Kconfig +++ b/drivers/flash/Kconfig @@ -182,6 +182,7 @@ source "drivers/flash/Kconfig.it51xxx_m1k" source "drivers/flash/Kconfig.it8xxx2" source "drivers/flash/Kconfig.lpc" source "drivers/flash/Kconfig.max32" +source "drivers/flash/Kconfig.mchp" source "drivers/flash/Kconfig.mcux" source "drivers/flash/Kconfig.mspi" source "drivers/flash/Kconfig.nor" diff --git a/drivers/flash/Kconfig.mchp b/drivers/flash/Kconfig.mchp new file mode 100644 index 0000000000000..5b54dfef8bb6a --- /dev/null +++ b/drivers/flash/Kconfig.mchp @@ -0,0 +1,26 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +config FLASH_MCHP_NVMCTRL_G1 + bool "Microchip G1 Flash Driver for NVMCTRL" + default y + depends on DT_HAS_MICROCHIP_NVMCTRL_G1_FLASH_ENABLED + select FLASH_HAS_PAGE_LAYOUT + select FLASH_HAS_EX_OP + select FLASH_EX_OP_ENABLED + select FLASH_HAS_DRIVER_ENABLED + select FLASH_HAS_EXPLICIT_ERASE + select MPU_ALLOW_FLASH_WRITE if ARM_MPU + help + Enable Flash driver for Microchip Non Volatile Memory Controller. + +config FLASH_HAS_UNALIGNED_WRITE + bool "Provide unaligned write support" + depends on FLASH_MCHP_NVMCTRL_G1 + help + Enable this option to allow the Zephyr flash API to write data to flash + memory at offsets that are not aligned to the device's write block size + When enabled, the driver will handle unaligned writes internally, typically + using read-modify-write operations to ensure data integrity. This allows + applications to write data starting at any offset within the flash memory + space, not just at aligned addresses. diff --git a/drivers/flash/flash_mchp_nvmctrl_g1.c b/drivers/flash/flash_mchp_nvmctrl_g1.c new file mode 100644 index 0000000000000..09deafd523f94 --- /dev/null +++ b/drivers/flash/flash_mchp_nvmctrl_g1.c @@ -0,0 +1,1381 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file flash_mchp_nvmctrl_g1.c + * @brief G1 Flash driver for NVMCTRL peripheral. + * + * Implements Zephyr Flash API support with basic flash memory + * operations. + * + */ + +#include +#include +#include +#include +#include +#include + +/******************************************* + * @brief Devicetree definitions + *******************************************/ +#define DT_DRV_COMPAT microchip_nvmctrl_g1_flash + +/******************************************* + * Const and Macro Defines + *******************************************/ +LOG_MODULE_REGISTER(flash_mchp_nvmctrl_g1); + +/* Number of lock regions in the SoC non-volatile flash. */ +#define SOC_NV_FLASH_LOCK_REGIONS DT_INST_PROP(0, lock_regions) + +/* Size of each lock region in the SoC non-volatile flash. */ +#define SOC_NV_FLASH_LOCK_REGION_SIZE ((SOC_NV_FLASH_SIZE) / (SOC_NV_FLASH_LOCK_REGIONS)) + +/* Device tree node identifier for SoC non-volatile flash instance 0. */ +#define SOC_NV_FLASH_NODE DT_INST(0, soc_nv_flash) + +/* Size of the SoC non-volatile flash, in bytes. */ +#define SOC_NV_FLASH_SIZE DT_REG_SIZE(SOC_NV_FLASH_NODE) + +/* Base address of the SoC non-volatile flash. */ +#define SOC_NV_FLASH_BASE_ADDRESS DT_REG_ADDR(SOC_NV_FLASH_NODE) + +/* Default size of a flash write block in bytes */ +#define FLASH_WRITE_BLOCK_SIZE_DEFAULT 8 + +/* Write block size of the SoC non-volatile flash, in bytes. */ +#define SOC_NV_FLASH_WRITE_BLOCK_SIZE \ + DT_PROP_OR(SOC_NV_FLASH_NODE, write_block_size, FLASH_WRITE_BLOCK_SIZE_DEFAULT) + +/* Default size of a flash erase block in bytes */ +#define FLASH_ERASE_BLOCK_SIZE_DEFAULT 8192 + +/* Erase block size of the SoC non-volatile flash, in bytes. */ +#define SOC_NV_FLASH_ERASE_BLOCK_SIZE \ + DT_PROP_OR(SOC_NV_FLASH_NODE, erase_block_size, FLASH_ERASE_BLOCK_SIZE_DEFAULT) + +/* Device tree node identifier for the user row region of SoC non-volatile flash. */ +#define SOC_NV_USERROW_NODE DT_INST(1, soc_nv_flash) + +/* Size of the userpage region in the SoC non-volatile flash, in bytes. */ +#define SOC_NV_USERROW_SIZE DT_REG_SIZE(SOC_NV_USERROW_NODE) + +/* Base address of the userpage region in the SoC non-volatile flash. */ +#define SOC_NV_USERROW_BASE_ADDR DT_REG_ADDR(SOC_NV_USERROW_NODE) + +/* Write block size of the userpage region, in bytes. */ +#define SOC_NV_USERROW_WRITE_BLOCK_SIZE DT_PROP(SOC_NV_USERROW_NODE, write_block_size) + +/* Erase block size of the userpage region, in bytes. */ +#define SOC_NV_USERROW_ERASE_BLOCK_SIZE DT_PROP(SOC_NV_USERROW_NODE, erase_block_size) + +/* Number of flash page layouts supported by the MCHP flash driver. */ +#define FLASH_MCHP_LAYOUT_SIZE 0x1 + +/* Size of a double word in bytes for MCHP flash. */ +#define FLASH_MCHP_DOUBLE_WORD_SIZE 0x8 + +/* Size of a quad word in bytes for MCHP flash. */ +#define FLASH_MCHP_QUAD_WORD_SIZE 0x10 + +/* Size of a page in bytes for MCHP flash. */ +#define FLASH_MCHP_PAGE_SIZE 0x200 + +/* Device config */ +#define DEV_CFG(dev) ((const struct flash_mchp_dev_config *const)(dev)->config) + +/* NVMCTRL Register */ +#define NVM_REGS ((const struct flash_mchp_dev_config *)(dev)->config)->regs + +/** @brief Default value of flash memory after an erase operation. */ +#define FLASH_ERASE_DEFAULT_VALUE 0xFF + +/** + * @def FLASH_MCHP_SUCCESS + * @brief Macro indicating successful operation. + */ +#define FLASH_MCHP_SUCCESS 0 + +/**< Encodes the write mode value for the NVMCTRL_CTRLA register. */ +#define FLASH_SET_WMODE(mode) ((mode) << NVMCTRL_CTRLA_WMODE_Pos) + +/** + * @brief Calculate the address in flash memory. + * + * This macro computes the address in flash memory by adding the specified + * offset to the base address of the flash memory. + * + * @param a Offset to be added to the base address of the flash memory. + */ +#define FLASH_MEMORY(a) ((uint32_t *)((uint8_t *)((a) + SOC_NV_FLASH_BASE_ADDRESS))) + +/* Timeout values for WAIT_FOR macro */ +#define TIMEOUT_VALUE_US 100000 + +#define DELAY_US 2 + +/******************************************* + * Enums and Structs + *******************************************/ +/** + * @struct flash_mchp_clock + * @brief Structure to hold device clock configuration. + */ +struct flash_mchp_clock { + + /* Clock driver */ + const struct device *clock_dev; + + /* Main clock subsystem. */ + clock_control_subsys_t mclk_sys; +}; + +/** + * @struct flash_mchp_dev_data + * @brief Structure to hold flash device data. + */ +struct flash_mchp_dev_data { + + /* Pointer to the Flash device instance. */ + const struct device *dev; + + /* Semaphore lock for flash APIs operations */ + struct k_mutex flash_data_lock; + + /* Stores the current interrupt flag status */ + volatile uint16_t interrupt_flag_status; +}; + +/** + * @struct flash_mchp_dev_config + * @brief Structure to hold flash device configuration. + */ +struct flash_mchp_dev_config { + + /* Pointer to Flash peripheral registers */ + nvmctrl_registers_t *regs; + + /* Flash base address */ + uint32_t base_addr; + + /* Flash clock control */ + struct flash_mchp_clock flash_clock; + + /* Function to configure IRQ */ + void (*irq_config_func)(const struct device *dev); + + /* Flash memory parameters */ + struct flash_parameters flash_param; + +#ifdef CONFIG_FLASH_PAGE_LAYOUT + /* Flash pages layouts */ + struct flash_pages_layout flash_layout; +#endif /* CONFIG_FLASH_PAGE_LAYOUT */ +}; + +/** + * @enum flash_mchp_write_mode + * @brief Enumeration for Flash write modes. + * + * This enumeration defines the different write modes available for the + * Flash. Each mode specifies how data is written to the non-volatile memory. + */ +enum flash_mchp_write_mode { + NVMCTRL_WMODE_MAN, /* Manual Write Mode */ + NVMCTRL_WMODE_ADW, /* Automatic Double Word Write Mode */ + NVMCTRL_WMODE_AQW, /* Automatic Quad Word Write Mode */ + NVMCTRL_WMODE_AP /* Automatic Page Write Mode */ +}; + +/******************************************* + * Helper functions + *******************************************/ +/** + * @brief Check if a given value is aligned to a specified alignment. + * + * This function determines whether the provided value is aligned to the + * specified alignment boundary. Alignment is typically a power of two, + * and this function checks if the value is a multiple of the alignment. + * + * @param value The value to be checked for alignment. + * @param alignment The alignment boundary to check against. This should + * be a power of two. + * + * @return FLASH_MCHP_SUCCESS if the value is aligned to the specified alignment, + * -EINVAL otherwise. + */ +static inline int flash_aligned(size_t value, size_t alignment) +{ + return (((value & (alignment - 1)) == 0) ? FLASH_MCHP_SUCCESS : -EINVAL); +} + +/** + * @brief Initializes the NVMCTRL module with automatic wait state generation. + * + * This function configures the NVMCTRL_CTRLA register to enable automatic wait + * state generation by enabling the automatic wait state mask (AUTOWS). + * + * @param dev Pointer to the device structure representing the flash controller. + */ +static inline void flash_enable_auto_wait_state(const struct device *dev) +{ + /* Automatic wait state generation */ + NVM_REGS->NVMCTRL_CTRLA = NVMCTRL_CTRLA_AUTOWS_Msk; +} + +/** + * @brief Enable NVMCTRL interrupt. + * + * @param dev Pointer to the device structure representing the flash controller. + * + */ +static inline void flash_interrupt_enable(const struct device *dev) +{ + const uint16_t enable_mask = NVMCTRL_INTENSET_ADDRE_Msk | NVMCTRL_INTENSET_PROGE_Msk | + NVMCTRL_INTENSET_LOCKE_Msk | NVMCTRL_INTENSET_NVME_Msk; + + NVM_REGS->NVMCTRL_INTENSET = enable_mask; +} + +/** + * @brief Initializes the flash controller for the specified device. + * + * This function enables the automatic wait state and interrupt for the flash + * controller associated with the given device. It should be called before + * performing any flash operations to ensure the controller is properly configured. + * + * @param dev Pointer to the device structure representing the flash controller. + */ +static void flash_controller_init(const struct device *dev) +{ + flash_enable_auto_wait_state(dev); + flash_interrupt_enable(dev); +} + +/** + * @brief Set the write mode for the NVMCTRL peripheral. + * + * This function configures the write mode of the NVMCTRL (Non-Volatile Memory + * Controller) by updating the NVMCTRL_CTRLA register with the specified mode. + * The function ensures that only the write mode bits are modified, preserving + * the other bits in the register. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param mode Write mode to set for the NVMCTRL. + */ +static inline void flash_set_write_mode(const struct device *dev, enum flash_mchp_write_mode mode) +{ + uint16_t reg = NVM_REGS->NVMCTRL_CTRLA; + + /* Clear the write mode bits and set the new mode */ + reg &= ~NVMCTRL_CTRLA_WMODE_Msk; + reg |= FLASH_SET_WMODE(mode); + + /* Write back the updated value */ + NVM_REGS->NVMCTRL_CTRLA = reg; +} + +/** + * @brief Retrieve and clear the interrupt flag status of the NVMCTRL + * peripheral. + * + * This function reads the current interrupt flag status from the + * NVMCTRL_INTFLAG register and then clears the interrupt flags by writing the + * same value back to the register. This operation is typically used to + * acknowledge and clear interrupt flags. + * + * @param dev Pointer to the device structure representing the flash controller. + * + */ +static inline void flash_clear_interrupt_flag(const struct device *dev) +{ + struct flash_mchp_dev_data *mchp_flash_data = dev->data; + + mchp_flash_data->interrupt_flag_status = NVM_REGS->NVMCTRL_INTFLAG; + + /* Clear NVMCTRL INTFLAG register */ + NVM_REGS->NVMCTRL_INTFLAG = mchp_flash_data->interrupt_flag_status; +} + +/** + * @brief Retrieve and report the error status of the NVM controller. + * + * This function examines the interrupt flag status of the NVMCTRL (Non-Volatile + * Memory Controller) to determine if any errors have occurred. It checks for + * address, programming, lock, and NVM errors The function returns a success or + * failure code based on the presence of errors. + * + * @return Returns `FLASH_MCHP_SUCCESS` if no errors are detected, or + * `-EIO` if any error flags are set. + */ +static int flash_get_interrupt_status_error(const struct device *dev) +{ + int ret = FLASH_MCHP_SUCCESS; + + struct flash_mchp_dev_data *mchp_flash_data = dev->data; + uint16_t status = mchp_flash_data->interrupt_flag_status; + + /* Combine all error masks */ + const uint16_t error_mask = NVMCTRL_INTFLAG_ADDRE_Msk | NVMCTRL_INTFLAG_PROGE_Msk | + NVMCTRL_INTFLAG_LOCKE_Msk | NVMCTRL_INTFLAG_NVME_Msk; + + if ((status & error_mask) != 0) { + LOG_ERR("flash operation failed with status 0x%x", status); + ret = -EIO; + } + + return ret; +} + +/** + * @brief Block until the NVMCTRL indicates it is ready. + * + * This function continuously checks the NVMCTRL status register until the + * "ready" bit is set, indicating that the NVMCTRL is no longer busy with + * programming or erasing operations and is ready for a new command. + * + * @param dev Pointer to the device structure representing the flash controller. + * + * @note This function blocks execution until the NVMCTRL is ready. + */ +static inline void flash_status_ready_wait(const struct device *dev) +{ + /* Wait until the NVM controller is ready */ + if (!WAIT_FOR(((NVM_REGS->NVMCTRL_STATUS & NVMCTRL_STATUS_READY_Msk) == + NVMCTRL_STATUS_READY_Msk), + TIMEOUT_VALUE_US, k_busy_wait(DELAY_US))) { + LOG_ERR("NVMCTRL_STATUS_READY wait timed out"); + } +} + +/** + * @brief Executes a flash memory controller command. + * + * Combines the specified command with the required command execution key and writes + * the result to the NVM controller's control register to initiate the desired flash operation. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param command The flash controller command to execute (e.g., erase, unlock, write). + */ +static inline void flash_process_command(const struct device *dev, uint16_t command) +{ + NVM_REGS->NVMCTRL_CTRLB = command | NVMCTRL_CTRLB_CMDEX_KEY; +} + +/** + * @brief Issue a command to clear the flash page buffer. + * + * This function sends the Page Buffer Clear (PBC) command to the flash controller, + * preparing the page buffer for a new write operation. + * + * @param dev Pointer to the device structure representing the flash controller. + */ +static inline void flash_pagebuffer_clear(const struct device *dev) +{ + flash_process_command(dev, NVMCTRL_CTRLB_CMD_PBC); +} + +/** + * @brief Write a double word (64 bits) to flash memory. + * + * This function writes a double word (typically 64 bits) to the specified flash memory address. + * The data is written in 32-bit chunks, as required by the flash controller's page buffer. + * The function waits for the flash to be ready before writing and checks the status after the + * operation. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param data Pointer to the source data to be written (must be at least 64 bits). + * @param address Destination address in flash memory where the data will be written. + * + * @retval FLASH_MCHP_SUCCESS if the write operation is successful. + */ +static int flash_doubleword_write(const struct device *dev, const void *data, uint32_t address) +{ + int ret = EINVAL; + uint8_t num_words = FLASH_MCHP_DOUBLE_WORD_SIZE / sizeof(uint32_t); + const uint32_t *src = (const uint32_t *)data; + uint32_t *dst = FLASH_MEMORY(address); + + flash_pagebuffer_clear(dev); + + flash_set_write_mode(dev, NVMCTRL_WMODE_ADW); + + /* writing 32-bit data into the given address. Writes to the page buffer must be 32 bits */ + for (uint8_t i = 0U; i < num_words; i++) { + *dst = *src; + dst++; + src++; + } + + flash_status_ready_wait(dev); + + ret = flash_get_interrupt_status_error(dev); + + return ret; +} + +/** + * @brief Write a quad word (128 bits) to flash memory. + * + * This function writes a quad word (typically 128 bits) to the specified flash memory address. + * The data is written in 32-bit chunks, as required by the flash controller's page buffer. + * The function waits for the flash to be ready before writing and checks the status after the + * operation. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param data Pointer to the source data to be written (must be at least 128 bits). + * @param address Destination address in flash memory where the data will be written. + * + * @retval FLASH_MCHP_SUCCESS if the write operation is successful. + */ +static int flash_quadword_write(const struct device *dev, const void *data, uint32_t address) +{ + int ret = EINVAL; + uint8_t num_words = FLASH_MCHP_QUAD_WORD_SIZE / sizeof(uint32_t); + const uint32_t *src = (const uint32_t *)data; + uint32_t *dst = FLASH_MEMORY(address); + + flash_pagebuffer_clear(dev); + + flash_set_write_mode(dev, NVMCTRL_WMODE_AQW); + + /* writing 32-bit data into the given address. Writes to the page buffer must be 32 bits */ + for (uint8_t i = 0U; i < num_words; i++) { + *dst = *src; + dst++; + src++; + } + + flash_status_ready_wait(dev); + + ret = flash_get_interrupt_status_error(dev); + + return ret; +} + +/** + * @brief Erases a memory block in the Microchip NVMCTRL. + * + * This function issues a command to erase a block of memory at the specified + * address in the Non-Volatile Memory Controller (NVMCTRL). It prepares the + * controller to accept a new command, sets the address, and executes the + * erase block command. The function then checks the status to ensure the + * operation was successful. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param address The memory address of the block to be erased. + * + * @retval FLASH_MCHP_SUCCESS if the erase operation is successful. + */ +static int flash_erase_block(const struct device *dev, uint32_t address) +{ + int ret = EINVAL; + + /* Set address and command */ + NVM_REGS->NVMCTRL_ADDR = address; + flash_process_command(dev, NVMCTRL_CTRLB_CMD_EB); + + flash_status_ready_wait(dev); + + ret = flash_get_interrupt_status_error(dev); + + return ret; +} + +/** + * @brief Writes a page of data to flash memory at the specified address. + * + * This function writes a block of 32-bit data to the flash memory page starting + * at the given address. The data is written in 32-bit words, and the write + * operation is performed in page mode. The function waits until the flash is + * ready, sets the appropriate write mode, and then writes the data to the page + * buffer. After writing, it checks the status of the operation. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param data Pointer to the source data buffer to be written (must be 32-bit aligned). + * @param address Destination address in flash memory where the data will be written. + * + * @retval FLASH_MCHP_SUCCESS if the write operation is successful. + */ +static int flash_page_write(const struct device *dev, const void *data, uint32_t address) +{ + int ret = EINVAL; + uint8_t num_words = FLASH_MCHP_PAGE_SIZE / sizeof(uint32_t); + const uint32_t *src = (const uint32_t *)data; + uint32_t *dst = FLASH_MEMORY(address); + + flash_pagebuffer_clear(dev); + + flash_set_write_mode(dev, NVMCTRL_WMODE_AP); + + /* Writes to the page buffer must be 32 bits */ + for (uint8_t i = 0U; i < num_words; i++) { + *dst = *src; + dst++; + src++; + } + + flash_status_ready_wait(dev); + + ret = flash_get_interrupt_status_error(dev); + + return ret; +} + +/** + * @brief Validate the range of a flash memory operation. + * + * This function checks whether the specified offset and length are within + * the valid range of the flash memory. It ensures that the offset is not + * negative and that the operation does not exceed the total flash size. + * + * @param offset The starting offset of the flash memory operation. + * @param len The length of the flash memory operation. + * + * @return 0 if the range is valid, -EINVAL if the range is invalid. + */ +static int flash_valid_range(off_t offset, size_t len) +{ + if (offset < 0) { + LOG_WRN("0x%lx: before start of flash", (long)offset); + return -EINVAL; + } + if ((offset + len) > SOC_NV_FLASH_SIZE) { + LOG_WRN("0x%lx: ends past the end of flash", (long)offset); + return -EINVAL; + } + + return FLASH_MCHP_SUCCESS; +} + +#ifdef CONFIG_FLASH_HAS_UNALIGNED_WRITE +/** + * @brief Handles unaligned start of a flash write operation. + * + * This function performs a read-modify-write for the initial unaligned bytes + * at the start of a flash write. It updates the offset, buffer pointer, and + * remaining length to reflect the bytes written, so that the caller can proceed + * with aligned writes. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param offset Pointer to the current offset in flash memory; updated by this function. + * @param buffer Pointer to the current data buffer pointer; updated by this function. + * @param len Pointer to the remaining length to write; updated by this function. + * + * @return FLASH_MCHP_SUCCESS (0) on success, or a negative error code on failure. + */ +static int flash_handle_unaligned_start(const struct device *dev, off_t *offset, + const uint8_t **buffer, size_t *len) +{ + /* Offset is already aligned, nothing to do */ + if (flash_aligned(*offset, FLASH_MCHP_DOUBLE_WORD_SIZE) == FLASH_MCHP_SUCCESS) { + return FLASH_MCHP_SUCCESS; + } + + int ret = -EINVAL; + uint32_t aligned_addr = *offset & ~(FLASH_MCHP_DOUBLE_WORD_SIZE - 1); + uint8_t doubleword_buf[FLASH_MCHP_DOUBLE_WORD_SIZE]; + size_t start_offset = (*offset - aligned_addr); + const uint8_t *src = (const uint8_t *)aligned_addr; + size_t bytes_to_update = ((*len) < (FLASH_MCHP_DOUBLE_WORD_SIZE - start_offset)) + ? (*len) + : (FLASH_MCHP_DOUBLE_WORD_SIZE - start_offset); + + /* Read existing data. */ + for (size_t i = 0; i < FLASH_MCHP_DOUBLE_WORD_SIZE; i++) { + doubleword_buf[i] = src[i]; + } + + /* Overwrite the relevant bytes. */ + for (size_t i = 0; i < bytes_to_update; i++) { + doubleword_buf[start_offset + i] = (*buffer)[i]; + } + + ret = flash_doubleword_write(dev, doubleword_buf, aligned_addr); + if (ret != FLASH_MCHP_SUCCESS) { + LOG_ERR("double word write failed at 0x%lx", (long)aligned_addr); + return ret; + } + + (*offset) += bytes_to_update; + (*buffer) += bytes_to_update; + (*len) -= bytes_to_update; + + return ret; +} + +/** + * @brief Handles unaligned end of a flash write operation. + * + * This function performs a read-modify-write for the final unaligned bytes + * at the end of a flash write. It does not update the offset, buffer, or length, + * as it is intended to be called after all aligned writes are complete. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param offset Offset in flash memory where the unaligned write should begin. + * @param buffer Pointer to the data buffer containing the bytes to write. + * @param len Number of bytes to write at the end (less than a double word). + * + * @return FLASH_MCHP_SUCCESS (0) on success, or a negative error code on failure. + */ +static int flash_handle_unaligned_end(const struct device *dev, off_t offset, const uint8_t *buffer, + size_t len) +{ + int ret = -EINVAL; + uint32_t aligned_addr = offset; + uint8_t doubleword_buf[FLASH_MCHP_DOUBLE_WORD_SIZE]; + const uint8_t *src = (const uint8_t *)aligned_addr; + + /* Read existing data */ + for (size_t i = 0; i < FLASH_MCHP_DOUBLE_WORD_SIZE; i++) { + doubleword_buf[i] = src[i]; + } + + /* Overwrite the relevant bytes. */ + for (size_t i = 0; i < len; i++) { + doubleword_buf[i] = buffer[i]; + } + + ret = flash_doubleword_write(dev, doubleword_buf, aligned_addr); + if (ret != FLASH_MCHP_SUCCESS) { + LOG_ERR("double word write failed at 0x%lx", (long)aligned_addr); + } + + return ret; +} +#endif /*CONFIG_FLASH_HAS_UNALIGNED_WRITE*/ + +/** + * @brief Write data to flash memory in blocks as specified by the device tree. + * + * This function writes data to flash memory using the block size defined + * in the device tree (such as 8, 16, or 512 bytes). It checks alignment and + * remaining data length, and writes each block in turn, updating the offset and + * buffer pointer as it goes. If a write fails, it stops and returns an error. + * + * @param dev Pointer to the flash controller device. + * @param offset Pointer to the offset in flash memory to start writing. + * @param buffer Pointer to the data buffer pointer. + * @param len Pointer to the number of bytes left to write. + * + * @return FLASH_MCHP_SUCCESS (0) on success, or a negative error code if a write fails. + */ +static int flash_write_aligned_blocks(const struct device *dev, off_t *offset, + const uint8_t **buffer, size_t *len) +{ + int ret = -EINVAL; + + /* Writing 0 bytes shall succeed */ + if (*len == 0) { + return FLASH_MCHP_SUCCESS; + } + + /* Return error if data length is less than minimum write block size */ + if (*len < FLASH_MCHP_DOUBLE_WORD_SIZE) { + return ret; + } + + while (*len >= FLASH_MCHP_DOUBLE_WORD_SIZE) { + + if ((*len >= FLASH_MCHP_PAGE_SIZE) && + (flash_aligned(*offset, FLASH_MCHP_PAGE_SIZE) == FLASH_MCHP_SUCCESS)) { + ret = flash_page_write(dev, *buffer, *offset); + if (ret != FLASH_MCHP_SUCCESS) { + LOG_ERR("page write failed at 0x%lx", (long)*offset); + break; + } + *offset += FLASH_MCHP_PAGE_SIZE; + *buffer += FLASH_MCHP_PAGE_SIZE; + *len -= FLASH_MCHP_PAGE_SIZE; + continue; + } + + if ((*len >= FLASH_MCHP_QUAD_WORD_SIZE) && + (flash_aligned(*offset, FLASH_MCHP_QUAD_WORD_SIZE) == FLASH_MCHP_SUCCESS)) { + ret = flash_quadword_write(dev, *buffer, *offset); + if (ret != FLASH_MCHP_SUCCESS) { + LOG_ERR("quad word write failed at 0x%lx", (long)*offset); + break; + } + *offset += FLASH_MCHP_QUAD_WORD_SIZE; + *buffer += FLASH_MCHP_QUAD_WORD_SIZE; + *len -= FLASH_MCHP_QUAD_WORD_SIZE; + continue; + } + + if ((*len >= FLASH_MCHP_DOUBLE_WORD_SIZE) && + (flash_aligned(*offset, FLASH_MCHP_DOUBLE_WORD_SIZE) == FLASH_MCHP_SUCCESS)) { + ret = flash_doubleword_write(dev, *buffer, *offset); + if (ret != FLASH_MCHP_SUCCESS) { + LOG_ERR("double word write failed at 0x%lx", (long)*offset); + break; + } + *offset += FLASH_MCHP_DOUBLE_WORD_SIZE; + *buffer += FLASH_MCHP_DOUBLE_WORD_SIZE; + *len -= FLASH_MCHP_DOUBLE_WORD_SIZE; + continue; + } + + break; + } + + return ret; +} + +/** + * @brief Write data to flash memory, supporting both aligned and unaligned writes. + * + * This function writes data to flash memory at the given offset. It checks that the + * write is within a valid memory range and, if needed, handles unaligned start and end + * regions. For the main part of the write, it uses the write block size specified in + * the device tree (such as 8, 16, or 512 bytes). The function also locks the flash + * data structure during the operation to ensure thread safety. + * + * @param dev Flash controller device pointer. + * @param offset Offset in flash memory to start writing. + * @param data Pointer to the data buffer to write. + * @param len Number of bytes to write. + * + * @return FLASH_MCHP_SUCCESS (0) on success, -EINVAL for alignment errors, or a negative error code + * on failure. + */ +static int flash_mchp_write(const struct device *dev, off_t offset, const void *data, size_t len) +{ + int ret = -EINVAL; + struct flash_mchp_dev_data *mchp_flash_data = dev->data; + const uint8_t *buffer = (const uint8_t *)data; + + offset += DEV_CFG(dev)->base_addr; + + ret = flash_valid_range(offset, len); + if (ret != FLASH_MCHP_SUCCESS) { + return ret; + } + +#ifndef CONFIG_FLASH_HAS_UNALIGNED_WRITE + ret = flash_aligned(offset, SOC_NV_FLASH_WRITE_BLOCK_SIZE); + if (ret != FLASH_MCHP_SUCCESS) { + LOG_WRN("0x%lx: not on a write block boundary", (long)offset); + return ret; + } + + ret = flash_aligned(len, SOC_NV_FLASH_WRITE_BLOCK_SIZE); + if (ret != FLASH_MCHP_SUCCESS) { + LOG_WRN("%zu: not a integer number of write blocks", len); + return ret; + } +#endif /*!CONFIG_FLASH_HAS_UNALIGNED_WRITE*/ + + k_mutex_lock(&mchp_flash_data->flash_data_lock, K_MSEC(10)); + +#ifdef CONFIG_FLASH_HAS_UNALIGNED_WRITE + /* Handle unaligned start */ + ret = flash_handle_unaligned_start(dev, &offset, &buffer, &len); + if (ret != FLASH_MCHP_SUCCESS) { + k_mutex_unlock(&mchp_flash_data->flash_data_lock); + LOG_ERR("flash unaligned write at start failed: %d", ret); + return ret; + } +#endif + + ret = flash_write_aligned_blocks(dev, &offset, &buffer, &len); + if (ret != FLASH_MCHP_SUCCESS) { + k_mutex_unlock(&mchp_flash_data->flash_data_lock); + LOG_ERR("flash aligned write failed: %d", ret); + return ret; + } + +#ifdef CONFIG_FLASH_HAS_UNALIGNED_WRITE + /* Handle unaligned end */ + if (len > 0) { + ret = flash_handle_unaligned_end(dev, offset, buffer, len); + } +#endif /*CONFIG_FLASH_HAS_UNALIGNED_WRITE*/ + + k_mutex_unlock(&mchp_flash_data->flash_data_lock); + + return ret; +} + +/** + * @brief Erases the flash memory block containing the specified address. + * + * This function erases the block of flash memory in which the given offset resides. + * The offset must be aligned to the erase block size, and the size must be a multiple + * of the erase block size. The function locks the flash data structure during the + * operation to ensure thread safety, and it checks for valid range and alignment + * before proceeding. The function waits for the flash to be ready before performing + * the erase operation. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param offset Offset in flash memory where the erase should begin (relative to base address). + * @param size Number of bytes to erase (must be a multiple of the erase block size). + * + * @return FLASH_MCHP_SUCCESS (0) on success, + * -EINVAL if alignment requirements are not met, + * -EIO if an erase operation fails, + * or other error codes as appropriate. + */ +static int flash_mchp_erase(const struct device *dev, off_t offset, size_t size) +{ + int ret = -EINVAL; + struct flash_mchp_dev_data *mchp_flash_data = dev->data; + uint32_t page_size = SOC_NV_FLASH_ERASE_BLOCK_SIZE; + + offset += DEV_CFG(dev)->base_addr; + + ret = flash_valid_range(offset, size); + if (ret != FLASH_MCHP_SUCCESS) { + return ret; + } + + ret = flash_aligned(offset, page_size); + if (ret != FLASH_MCHP_SUCCESS) { + LOG_WRN("0x%lx: not on a erase block boundary", (long)offset); + return ret; + } + + ret = flash_aligned(size, page_size); + if (ret != FLASH_MCHP_SUCCESS) { + LOG_WRN("%zu: not a integer number of erase blocks", size); + return ret; + } + + k_mutex_lock(&mchp_flash_data->flash_data_lock, K_MSEC(10)); + + while (size > 0U) { + + /* Erase the block */ + ret = flash_erase_block(dev, offset); + if (ret != FLASH_MCHP_SUCCESS) { + LOG_ERR("erase operation failed at 0x%lx", (long)offset); + ret = -EIO; + break; + } + + /* Update size and offset for the next pages */ + size -= page_size; + offset += page_size; + } + + k_mutex_unlock(&mchp_flash_data->flash_data_lock); + + return ret; +} + +/** + * @brief Read data from the flash memory. + * + * This function reads a specified number of bytes from the flash memory + * at a given offset and copies it into the provided buffer. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param offset Offset in the flash memory from which to start reading. + * @param data Pointer to the buffer where the read data will be stored. + * @param len Number of bytes to read from the flash memory. + * + * @return Returns NVMCTRL_MCHP_SUCCESS upon successful completion, or a negative error code + * if the operation fails. + */ +static int flash_mchp_read(const struct device *dev, off_t offset, void *data, size_t len) +{ + int ret = -EINVAL; + struct flash_mchp_dev_data *mchp_flash_data = dev->data; + uint32_t flash_base_addr = DEV_CFG(dev)->base_addr; + + ret = flash_valid_range(offset, len); + if (ret != FLASH_MCHP_SUCCESS) { + return ret; + } + + uint8_t *dst = (uint8_t *)data; + const uint8_t *src = (const uint8_t *)flash_base_addr + offset; + + k_mutex_lock(&mchp_flash_data->flash_data_lock, K_MSEC(10)); + + for (size_t i = 0; i < len; i++) { + dst[i] = src[i]; + } + + k_mutex_unlock(&mchp_flash_data->flash_data_lock); + + return ret; +} + +/** + * @brief Retrieve the flash parameters for a given device. + * + * This function provides access to the flash parameters associated with + * a specific flash device. The parameters include details such as the + * minimal write alignment and size, device capabilities, and the value + * used to fill erased areas of the flash memory. + * + * @param dev Pointer to the device structure representing the flash controller. + * + * @return Pointer to a `flash_parameters` structure containing the flash + * device's parameters. The returned structure includes: + * - `write_block_size`: The minimal write alignment and size. + * - `caps.no_explicit_erase`: Indicates whether the device requires + * explicit erase operations or not. + * - `erase_value`: The value used to fill erased areas of the flash memory. + */ +static const struct flash_parameters *flash_mchp_get_parameters(const struct device *dev) +{ + return (&DEV_CFG(dev)->flash_param); +} + +#ifdef CONFIG_FLASH_PAGE_LAYOUT +/** + * @brief Retrieve the flash page layout for a Microchip NVM controller. + * + * This function provides the layout of flash pages for the specified device. + * It retrieves the page layout and the size of the layout from the device's + * configuration and assigns them to the provided pointers. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param layout Pointer to store the address of the flash pages layout array. + * @param layout_size Pointer to store the size of the flash pages layout array. + * + */ +static void flash_mchp_page_layout(const struct device *dev, + const struct flash_pages_layout **layout, size_t *layout_size) +{ + *layout = &(DEV_CFG(dev)->flash_layout); + *layout_size = FLASH_MCHP_LAYOUT_SIZE; +} +#endif /*CONFIG_FLASH_PAGE_LAYOUT*/ + +#ifdef CONFIG_FLASH_EX_OP_ENABLED +/** + * @brief Determines if a given address is outside the user row range in NVMCTRL. + * + * This function checks whether the specified address falls outside the + * defined user row range in the NVMCTRL memory. If the address is outside + * the range, it returns a failure code; otherwise, it returns a success code. + * + * @param address The address to be checked. + * @return Returns FLASH_MCHP_SUCCESS if the address is within the user row range, + * otherwise returns -EINVAL if the address is outside the range. + */ +static int flash_check_offset_user_range(uint32_t address) +{ + int ret = FLASH_MCHP_SUCCESS; + + /* Check if the address is outside the user row range */ + if ((address < SOC_NV_USERROW_BASE_ADDR) || + (address > (SOC_NV_USERROW_BASE_ADDR + SOC_NV_USERROW_SIZE))) { + ret = -EINVAL; + } + + return ret; +} + +/** + * @brief Erases a user row in the Microchip NVMCTRL. + * + * This function issues a command to erase a user row at the specified address + * in the Non-Volatile Memory Controller (NVMCTRL). It prepares the controller + * to accept a new command, sets the address, and executes the erase row + * command. The function then checks the status to ensure the operation was + * successful. + * + * @param[in] dev Pointer to the device structure representing the NVMCTRL hardware instance. + * @param address The memory address of the user row to be erased. + * + * @retval FLASH_MCHP_SUCCESS if the erase operation is successful. + */ +static int flash_user_row_erase(const struct device *dev, uint32_t address) +{ + int ret = -EINVAL; + + /* Set address and command */ + NVM_REGS->NVMCTRL_ADDR = address; + flash_process_command(dev, NVMCTRL_CTRLB_CMD_EP); + + flash_status_ready_wait(dev); + + ret = flash_get_interrupt_status_error(dev); + + return ret; +} + +/** + * @brief Writes data to the user row area of flash memory. + * + * This function writes a buffer of data to the user row section of flash memory, + * starting at the specified offset. The address and data length must be aligned + * to the user row write block size. The function checks for valid address range + * and alignment before performing the write operation. Data is written in + * quad-word (typically 16-byte) blocks. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param in Pointer to a flash_mchp_ex_op_userrow_data_t structure containing + * the offset, data pointer, and data length to be written. + * @param out Unused output pointer (reserved for future use). + * + * @return FLASH_MCHP_SUCCESS (0) on success, + * -EINVAL if alignment requirements are not met, + * or other error codes as appropriate. + */ +static int flash_ex_op_user_row_write(const struct device *dev, const uintptr_t in, void *out) +{ + ARG_UNUSED(out); + int ret = -EINVAL; + struct flash_mchp_dev_data *mchp_flash_data = dev->data; + + const flash_mchp_ex_op_userrow_data_t *userrow_data = + (const flash_mchp_ex_op_userrow_data_t *)in; + + const uint8_t *buffer = (const uint8_t *)userrow_data->data; + uint32_t address = userrow_data->offset + SOC_NV_USERROW_BASE_ADDR; + size_t len = userrow_data->data_len; + + ret = flash_aligned(address, SOC_NV_USERROW_WRITE_BLOCK_SIZE); + if (ret != FLASH_MCHP_SUCCESS) { + LOG_WRN("0x%lx: not on a write block boundary", (long)address); + return ret; + } + + ret = flash_aligned(len, SOC_NV_USERROW_WRITE_BLOCK_SIZE); + if (ret != FLASH_MCHP_SUCCESS) { + LOG_WRN("%zu: not a integer number of write blocks", len); + return ret; + } + + ret = flash_check_offset_user_range(address); + if (ret != FLASH_MCHP_SUCCESS) { + return ret; + } + + uint32_t num_quad_words = (len / SOC_NV_USERROW_WRITE_BLOCK_SIZE); + + k_mutex_lock(&mchp_flash_data->flash_data_lock, K_MSEC(10)); + + for (uint32_t write_count = 0U; write_count < num_quad_words; write_count++) { + ret = flash_quadword_write(dev, buffer, address); + if (ret != FLASH_MCHP_SUCCESS) { + break; + } + + buffer += SOC_NV_USERROW_WRITE_BLOCK_SIZE; + address += SOC_NV_USERROW_WRITE_BLOCK_SIZE; + } + + k_mutex_unlock(&mchp_flash_data->flash_data_lock); + + return ret; +} + +/** + * @brief Erases the user row area of flash memory. + * + * This function erases the entire user row section of flash memory, starting at + * the base address defined by SOC_NV_USERROW_BASE_ADDR. It waits for the flash + * to be ready before performing the erase operation. The input and output + * parameters are currently unused. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param in Unused input parameter (reserved for future use). + * @param out Unused output parameter (reserved for future use). + * + * @return FLASH_MCHP_SUCCESS (0) on success, + * -EIO if the erase operation fails. + */ +static int flash_ex_op_user_row_erase(const struct device *dev, const uintptr_t in, void *out) +{ + ARG_UNUSED(in); + ARG_UNUSED(out); + + int ret = -EINVAL; + struct flash_mchp_dev_data *mchp_flash_data = dev->data; + + k_mutex_lock(&mchp_flash_data->flash_data_lock, K_MSEC(10)); + + /* Erase the user page */ + ret = flash_user_row_erase(dev, SOC_NV_USERROW_BASE_ADDR); + if (ret != FLASH_MCHP_SUCCESS) { + LOG_ERR("User page erase failed"); + ret = -EIO; + } + + k_mutex_unlock(&mchp_flash_data->flash_data_lock); + + return ret; +} + +/** + * @brief Lock all regions of the SoC non-volatile flash. + * + * This function iterates over all lock regions of the SoC non-volatile flash and issues + * a lock command for each region. It waits for the flash to be ready before issuing each command. + * + * @param dev Pointer to the flash device structure. + * @param in Unused input parameter (reserved for future use or interface compatibility). + * @param out Unused output parameter (reserved for future use or interface compatibility). + * + * @retval FLASH_MCHP_SUCCESS if all regions are successfully locked. + */ +static int flash_ex_op_region_lock(const struct device *dev, const uintptr_t in, void *out) +{ + ARG_UNUSED(in); + ARG_UNUSED(out); + int ret = -EINVAL; + + for (off_t offset = 0; offset < SOC_NV_FLASH_SIZE; + offset += SOC_NV_FLASH_LOCK_REGION_SIZE) { + + /* Set address and command */ + NVM_REGS->NVMCTRL_ADDR = offset + SOC_NV_FLASH_BASE_ADDRESS; + flash_process_command(dev, NVMCTRL_CTRLB_CMD_LR); + + ret = flash_get_interrupt_status_error(dev); + if (ret != FLASH_MCHP_SUCCESS) { + break; + } + } + + return ret; +} + +/** + * @brief Unlock all regions of the SoC non-volatile flash. + * + * This function iterates over all lock regions of the SoC non-volatile flash and issues + * an unlock command for each region. It waits for the flash to be ready before issuing each + * command. + * + * @param dev Pointer to the flash device structure. + * @param in Unused input parameter (reserved for future use or interface compatibility). + * @param out Unused output parameter (reserved for future use or interface compatibility). + * + * @retval FLASH_MCHP_SUCCESS if all regions are successfully unlocked. + */ +static int flash_ex_op_region_unlock(const struct device *dev, const uintptr_t in, void *out) +{ + ARG_UNUSED(in); + ARG_UNUSED(out); + int ret = -EINVAL; + + for (off_t offset = 0; offset < SOC_NV_FLASH_SIZE; + offset += SOC_NV_FLASH_LOCK_REGION_SIZE) { + + /* Set address and command */ + NVM_REGS->NVMCTRL_ADDR = offset + SOC_NV_FLASH_BASE_ADDRESS; + flash_process_command(dev, NVMCTRL_CTRLB_CMD_UR); + + ret = flash_get_interrupt_status_error(dev); + if (ret != FLASH_MCHP_SUCCESS) { + break; + } + } + + return ret; +} + +/** + * @brief Executes an extended flash operation based on the provided operation code. + * + * This function acts as a dispatcher for various extended flash operations, such as + * erasing or writing the user row, and locking or unlocking flash regions. The specific + * operation to perform is determined by the @p code parameter. + * + * @param dev Pointer to the device structure representing the flash controller. + * @param code Operation code specifying which extended operation to perform. + * @param in Pointer to input data required by the operation (usage depends on operation). + * @param out Pointer to output data buffer (usage depends on operation). + * + * @return FLASH_MCHP_SUCCESS (0) on success, + * -EINVAL if the operation code is invalid, + * or other error codes as returned by the respective functions. + */ +static int flash_mchp_ex_op(const struct device *dev, uint16_t code, const uintptr_t in, void *out) +{ + int ret = -EINVAL; + + switch (code) { + case FLASH_EX_OP_USER_ROW_ERASE: + ret = flash_ex_op_user_row_erase(dev, in, out); + break; + case FLASH_EX_OP_USER_ROW_WRITE: + ret = flash_ex_op_user_row_write(dev, in, out); + break; + case FLASH_EX_OP_REGION_LOCK: + ret = flash_ex_op_region_lock(dev, in, out); + break; + case FLASH_EX_OP_REGION_UNLOCK: + ret = flash_ex_op_region_unlock(dev, in, out); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +#endif /*CONFIG_FLASH_EX_OP_ENABLED*/ + +/** + * @brief Interrupt Service Routine for the Microchip NVMCTRL peripheral. + * + * This function handles interrupts from the Microchip NVMCTRL peripheral. + * It clears the interrupt flag to acknowledge the interrupt and releases + * a semaphore to allow other operations to proceed. + * + * @param dev Pointer to the device structure for the flash controller. + */ +static void flash_mchp_isr(const struct device *dev) +{ + flash_clear_interrupt_flag(dev); +} + +/** + * @brief Initializes the Microchip NVMCTRL peripheral. + * + * This function sets up the necessary resources and configurations for the + * Microchip flash memory controller to operate. It initializes mutexes and + * semaphores, enables the clock for the controller, configures interrupts, + * and performs any necessary hardware initialization. + * + * @param dev Pointer to the device structure for the flash controller. + * + * @return Returns 0 upon successful initialization. + * + */ +static int flash_mchp_init(const struct device *dev) +{ + int ret = -EINVAL; + + const struct flash_mchp_dev_config *const mchp_flash_cfg = DEV_CFG(dev); + struct flash_mchp_dev_data *mchp_flash_data = dev->data; + + ret = clock_control_on(mchp_flash_cfg->flash_clock.clock_dev, + mchp_flash_cfg->flash_clock.mclk_sys); + + if ((ret == FLASH_MCHP_SUCCESS) || (ret == -EALREADY)) { + + k_mutex_init(&(mchp_flash_data->flash_data_lock)); + + mchp_flash_cfg->irq_config_func(dev); + + flash_controller_init(dev); + + ret = FLASH_MCHP_SUCCESS; + } + + return ret; +} + +/** + * @brief NVMCTRL driver API structure. + */ +static DEVICE_API(flash, flash_mchp_api) = { + .write = flash_mchp_write, + .read = flash_mchp_read, + .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*/ +}; + +/** + * @brief Declare the FLASH IRQ handler. + * + * @param n Instance number. + */ +#define FLASH_MCHP_IRQ_HANDLER_DECL(n) \ + static void flash_mchp_irq_config_##n(const struct device *dev) + +/** + * @brief Define and connect the FLASH IRQ handler for a given instance. + * + * This macro defines the IRQ configuration function for the specified FLASH instance, + * connects the FLASH interrupt to its handler, and enables the IRQ. + * + * @param n Instance number. + */ +#define FLASH_MCHP_IRQ_HANDLER(n) \ + static void flash_mchp_irq_config_##n(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(n, 0, irq), DT_INST_IRQ_BY_IDX(n, 0, priority), \ + flash_mchp_isr, DEVICE_DT_INST_GET(n), 0); \ + irq_enable(DT_INST_IRQ_BY_IDX(n, 0, irq)); \ + } + +/** + * @brief Configures the flash memory page layout for the FLASH. + * + */ +#ifdef CONFIG_FLASH_PAGE_LAYOUT +#define FLASH_LAYOUT \ + .flash_layout = {.pages_count = SOC_NV_FLASH_SIZE / SOC_NV_FLASH_ERASE_BLOCK_SIZE, \ + .pages_size = SOC_NV_FLASH_ERASE_BLOCK_SIZE}, +#else +#define FLASH_LAYOUT +#endif /*CONFIG_FLASH_PAGE_LAYOUT*/ + +/* + * @brief Define the FLASH configuration. + * + * @param n Instance number. + */ +#define FLASH_MCHP_CONFIG_DEFN(n) \ + static const struct flash_mchp_dev_config flash_mchp_config_##n = { \ + .regs = (nvmctrl_registers_t *)DT_INST_REG_ADDR(n), \ + .base_addr = SOC_NV_FLASH_BASE_ADDRESS, \ + .flash_clock.clock_dev = DEVICE_DT_GET(DT_NODELABEL(clock)), \ + .flash_clock.mclk_sys = (void *)(DT_INST_CLOCKS_CELL_BY_NAME(n, mclk, subsystem)), \ + .irq_config_func = flash_mchp_irq_config_##n, \ + .flash_param = {.write_block_size = SOC_NV_FLASH_WRITE_BLOCK_SIZE, \ + .caps = {.no_explicit_erase = false}, \ + .erase_value = FLASH_ERASE_DEFAULT_VALUE}, \ + FLASH_LAYOUT} + +/** + * @brief Macro to define the flash data structure for a specific instance. + * + * This macro defines the flash data structure for a specific instance of the Microchip flash + * device. + * + * @param n Instance number. + */ +#define FLASH_MCHP_DATA_DEFN(n) static struct flash_mchp_dev_data flash_mchp_data_##n + +/** + * @brief Macro to define the device structure for a specific instance of the flash device. + * + * This macro defines the device structure for a specific instance of the Microchip flash device. + * It uses the DEVICE_DT_INST_DEFINE macro to create the device instance with the specified + * initialization function, data structure, configuration structure, and driver API. + * + * @param n Instance number. + */ +#define FLASH_MCHP_DEVICE_DT_DEFN(n) \ + DEVICE_DT_INST_DEFINE(n, flash_mchp_init, NULL, &flash_mchp_data_##n, \ + &flash_mchp_config_##n, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &flash_mchp_api) + +/** + * @brief Initialize the FLASH device. + * + * @param n Instance number. + */ +#define FLASH_MCHP_DEVICE_INIT(n) \ + FLASH_MCHP_IRQ_HANDLER_DECL(n); \ + FLASH_MCHP_CONFIG_DEFN(n); \ + FLASH_MCHP_DATA_DEFN(n); \ + FLASH_MCHP_DEVICE_DT_DEFN(n); \ + FLASH_MCHP_IRQ_HANDLER(n); + +/** + * @brief Initialize all FLASH instances. + */ +DT_INST_FOREACH_STATUS_OKAY(FLASH_MCHP_DEVICE_INIT) diff --git a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi index efc33042eff97..dd959162f108e 100644 --- a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi +++ b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi @@ -28,11 +28,6 @@ }; soc { - flash0: flash@0 { - compatible = "soc-nv-flash"; - write-block-size = <8>; - }; - sram0: memory@20000000 { compatible = "mmio-sram"; }; @@ -81,6 +76,31 @@ clock-names = "mclk", "gclk"; }; + nvmctrl: nvmctrl@41004000 { + compatible = "microchip,nvmctrl-g1-flash"; + status = "okay"; + reg = <0x41004000 0x30>; + interrupts = <29 0>, <30 0>; + clocks = <&mclkperiph CLOCK_MCHP_MCLKPERIPH_ID_APBB_NVMCTRL>; + clock-names = "mclk"; + lock-regions = <32>; + #address-cells = <1>; + #size-cells = <1>; + + flash0: flash@0 { + compatible = "soc-nv-flash"; + write-block-size = <8>; + erase-block-size = <8192>; + }; + + userrow: nvmuserrow@804000 { + compatible = "soc-nv-flash"; + reg = <0x00804000 0x200>; + write-block-size = <8>; + erase-block-size = <512>; + }; + }; + pinctrl: pinctrl@41008000 { compatible = "microchip,port-g1-pinctrl"; #address-cells = <1>; diff --git a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x_18.dtsi b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x_18.dtsi index 47eaed3e6a44b..2d59a84ad911e 100644 --- a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x_18.dtsi +++ b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x_18.dtsi @@ -6,12 +6,14 @@ / { soc { - flash0: flash@0 { - reg = <0x0 DT_SIZE_K(256)>; - }; - sram0: memory@20000000 { reg = <0x20000000 DT_SIZE_K(128)>; }; + + nvmctrl@41004000 { + flash0: flash@0 { + reg = <0x0 DT_SIZE_K(256)>; + }; + }; }; }; diff --git a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x_19.dtsi b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x_19.dtsi index ee586debb0339..34f9d42e8cc99 100644 --- a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x_19.dtsi +++ b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x_19.dtsi @@ -6,12 +6,14 @@ / { soc { - flash0: flash@0 { - reg = <0x0 DT_SIZE_K(512)>; - }; - sram0: memory@20000000 { reg = <0x20000000 DT_SIZE_K(192)>; }; + + nvmctrl@41004000 { + flash0: flash@0 { + reg = <0x0 DT_SIZE_K(512)>; + }; + }; }; }; diff --git a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x_20.dtsi b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x_20.dtsi index d3cb3e7a8bb9f..0b1aa23a011bc 100644 --- a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x_20.dtsi +++ b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x_20.dtsi @@ -6,12 +6,14 @@ / { soc { - flash0: flash@0 { - reg = <0x0 DT_SIZE_K(1024)>; - }; - sram0: memory@20000000 { reg = <0x20000000 DT_SIZE_K(256)>; }; + + nvmctrl@41004000 { + flash0: flash@0 { + reg = <0x0 DT_SIZE_K(1024)>; + }; + }; }; }; diff --git a/dts/bindings/flash_controller/microchip,nvmctrl-g1-flash.yaml b/dts/bindings/flash_controller/microchip,nvmctrl-g1-flash.yaml new file mode 100644 index 0000000000000..9da6306674ab5 --- /dev/null +++ b/dts/bindings/flash_controller/microchip,nvmctrl-g1-flash.yaml @@ -0,0 +1,25 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +title: Microchip NVMCTRL (Nonvolatile Memory Controller) + +description: | + Microchip NVMCTRL Flash driver + + Group g1 NVMCTRL Flash driver supports following hardware peripherals: + - module name="NVMCTRL" id="U2409" version="1.0.0" + +compatible: "microchip,nvmctrl-g1-flash" + +include: flash-controller.yaml + +properties: + lock-regions: + type: int + required: true + description: | + Number of lock regions. + + The main address space is divided into 32 equal size regions. The region size is + dependent on the flash memory size.Each region can be protected against write or + erase operation. diff --git a/include/zephyr/drivers/flash/mchp_flash.h b/include/zephyr/drivers/flash/mchp_flash.h new file mode 100644 index 0000000000000..40e0c28c4d2c7 --- /dev/null +++ b/include/zephyr/drivers/flash/mchp_flash.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file mchp_flash.h + * @brief Microchip Flash Controller Driver Header + * + * @details + * This header provides conditional inclusion of Microchip flash controller + * driver headers for supported controller versions (e.g., G1, G2). + * Depending on the build configuration, the appropriate version-specific + * header file is included to enable support for the corresponding flash + * controller peripheral. + * + */ + +#ifndef INCLUDE_ZEPHYR_DRIVERS_FLASH_MCHP_FLASH_H_ +#define INCLUDE_ZEPHYR_DRIVERS_FLASH_MCHP_FLASH_H_ + +#ifdef CONFIG_FLASH_MCHP_NVMCTRL_G1 +#include "mchp_nvmctrl_g1.h" +#endif /* CONFIG_FLASH_MCHP_NVMCTRL_G1 */ + +#endif /* INCLUDE_ZEPHYR_DRIVERS_FLASH_MCHP_FLASH_H_ */ diff --git a/include/zephyr/drivers/flash/mchp_nvmctrl_g1.h b/include/zephyr/drivers/flash/mchp_nvmctrl_g1.h new file mode 100644 index 0000000000000..049cc92a74756 --- /dev/null +++ b/include/zephyr/drivers/flash/mchp_nvmctrl_g1.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file mchp_nvmctrl_g1.h + * @brief Extended Flash Operations for Microchip NVMCTRL G1 + * + * This header provides definitions and data structures for additional + * flash memory operations specific to the Microchip NVMCTRL G1 + * flash controller. It extends the standard flash driver capabilities + * by enabling advanced operations such as user row access and region + * locking/unlocking. + * + * @note This file should only be included when targeting devices + * with the NVMCTRL G1 flash controller. + */ + +#ifndef INCLUDE_ZEPHYR_DRIVERS_FLASH_MCHP_NVMCTRL_G1_H_ +#define INCLUDE_ZEPHYR_DRIVERS_FLASH_MCHP_NVMCTRL_G1_H_ + +/** + * @brief Extended flash operation codes for MCHP flash controller. + * + * This enumeration defines the set of extended operations that can be performed + * on the flash memory, such as erasing or writing the user row, and locking or + * unlocking specific flash regions. + */ +typedef enum { + /* Erase the user row in flash memory. */ + FLASH_EX_OP_USER_ROW_ERASE, + + /* Write data to the user row in flash memory. */ + FLASH_EX_OP_USER_ROW_WRITE, + + /* Lock a specific region of flash memory. */ + FLASH_EX_OP_REGION_LOCK, + + /* Unlock a specific region of flash memory. */ + FLASH_EX_OP_REGION_UNLOCK +} flash_mchp_ex_ops_t; + +/** + * @brief Structure for user row data operations in MCHP flash. + * + * This structure is used to specify the parameters for operations + * involving the user row region of flash memory, such as writing data + * to or erasing a portion of the user row. + */ +typedef struct flash_mchp_ex_op_userrow_data { + /* Pointer to the data buffer to be written or read. */ + const void *data; + + /* Length of the data buffer in bytes. */ + size_t data_len; + + /* Offset within the user row region where the operation starts. */ + off_t offset; +} flash_mchp_ex_op_userrow_data_t; + +#endif /*INCLUDE_ZEPHYR_DRIVERS_FLASH_MCHP_NVMCTRL_G1_H_*/ diff --git a/samples/sysbuild/with_mcuboot/sample.yaml b/samples/sysbuild/with_mcuboot/sample.yaml index 5797bb823c135..71f5d38cf7eff 100644 --- a/samples/sysbuild/with_mcuboot/sample.yaml +++ b/samples/sysbuild/with_mcuboot/sample.yaml @@ -19,6 +19,7 @@ tests: - stm32h7s78_dk - stm32h573i_dk - stm32h750b_dk/stm32h750xx/ext_flash_app + - sam_e54_xpro integration_platforms: - nrf52840dk/nrf52840 - esp32_devkitc/esp32/procpu diff --git a/tests/boot/test_mcuboot/testcase.yaml b/tests/boot/test_mcuboot/testcase.yaml index 6cfb6d1b8de1a..b416d16d8374c 100644 --- a/tests/boot/test_mcuboot/testcase.yaml +++ b/tests/boot/test_mcuboot/testcase.yaml @@ -55,6 +55,7 @@ tests: - esp32c6_devkitc/esp32c6/hpcore - esp8684_devkitm - stm32h750b_dk/stm32h750xx/ext_flash_app + - sam_e54_xpro integration_platforms: - frdm_k64f - nrf52840dk/nrf52840