diff --git a/drivers/edac/CMakeLists.txt b/drivers/edac/CMakeLists.txt index 0d2e6083d04..a3993f8f8e6 100644 --- a/drivers/edac/CMakeLists.txt +++ b/drivers/edac/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright (c) 2020 Intel Corporation +# Copyright 2025 NXP # SPDX-License-Identifier: Apache-2.0 zephyr_library() @@ -7,5 +8,6 @@ zephyr_library_sources_ifdef(CONFIG_EDAC_SHELL shell.c) # zephyr-keep-sorted-start zephyr_library_sources_ifdef(CONFIG_EDAC_IBECC edac_ibecc.c) +zephyr_library_sources_ifdef(CONFIG_EDAC_NXP_ERM edac_mcux_erm.c) zephyr_library_sources_ifdef(CONFIG_EDAC_SYNOPSYS edac_synopsys.c) # zephyr-keep-sorted-stop diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index edd95050c20..760c8b13bf9 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -1,4 +1,5 @@ # Copyright (c) 2020 Intel Corp. +# Copyright 2025 NXP # SPDX-License-Identifier: Apache-2.0 # # EDAC configuration options @@ -35,6 +36,7 @@ config EDAC_SYNOPSYS help Enable the Synopsys DDR controller EDAC driver. +source "drivers/edac/Kconfig.mcux_erm" module = EDAC module-str = edac source "subsys/logging/Kconfig.template.log_config" diff --git a/drivers/edac/Kconfig.mcux_erm b/drivers/edac/Kconfig.mcux_erm new file mode 100644 index 00000000000..c472cf13435 --- /dev/null +++ b/drivers/edac/Kconfig.mcux_erm @@ -0,0 +1,38 @@ +# Copyright 2025 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +config EDAC_NXP_ERM + bool "NXP ERM driver" + default y + depends on DT_HAS_NXP_ERM_ENABLED + help + Enable the NXP Error Reporting Module driver. + +config EDAC_NXP_ERM_DEFAULT_CHANNEL + int "NXP ERM default channel" + default 0 + depends on EDAC_NXP_ERM + help + Set the NXP Error Reporting Module default channel. + +config EDAC_NXP_EIM + bool "NXP EIM driver" + default y + depends on DT_HAS_NXP_EIM_ENABLED + help + Enable the NXP Error Injection Module driver. + +config EDAC_NXP_ERROR_INJECT + bool + default y if EDAC_ERROR_INJECT && EDAC_NXP_EIM + help + This is a helper configuration that is enabled when both + EDAC_ERROR_INJECT and EDAC_NXP_EIM are enabled. + +config EDAC_NXP_ERM_VARY_WITH_EIM_CHANNEL + bool "Update EIM channel based on EIM channel" + default y + depends on EDAC_NXP_EIM && EDAC_NXP_ERM + help + Allow ERM channel to dynamically adapt based on the selected EIM channel. diff --git a/drivers/edac/edac_mcux_erm.c b/drivers/edac/edac_mcux_erm.c new file mode 100644 index 00000000000..693bf882aef --- /dev/null +++ b/drivers/edac/edac_mcux_erm.c @@ -0,0 +1,355 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(edac_mcux_erm, CONFIG_EDAC_LOG_LEVEL); + +#ifdef EDAC_NXP_ERROR_INJECT +#include + +#define EDAC_NXP_SINGLE_BIT_ERROR_MASK 0x1 +#define EDAC_NXP_DOUBLE_BIT_ERROR_MASK 0x3 + +#define EIM_CHANNEL_ENABLE(CHANNEL_ID) (0x80000000U >> (CHANNEL_ID)) + +struct edac_nxp_eim_channel { + uint32_t start_address; + uint32_t size; + uint32_t ecc_enable; + uint8_t channel_id; + uint8_t erm_channel_id; +}; +#endif /* CONFIG_EDAC_ERROR_INJECT */ + +struct edac_nxp_config { + ERM_Type *erm_base; +#ifdef EDAC_NXP_ERROR_INJECT + EIM_Type *eim_base; + const struct edac_nxp_eim_channel *eim_channels; + uint8_t eim_channel_num; +#endif /* CONFIG_EDAC_ERROR_INJECT */ + const int *erm_channels; + uint8_t erm_channel_num; + void (*irq_config_func)(const struct device *dev); +}; + +struct edac_nxp_data { + edac_notify_callback_f cb; +#ifdef EDAC_NXP_ERROR_INJECT + uint32_t eim_channel; + uint32_t eim_channel_word; + uint32_t inject_error_type; +#endif /* CONFIG_EDAC_ERROR_INJECT */ + uint32_t erm_channel; +}; + +__weak void enable_ecc(uint32_t mask) +{ + ARG_UNUSED(mask); +} + +static bool check_erm_channel(const int *erm_channels, size_t size, uint32_t value) +{ + for (size_t i = 0; i < size; i++) { + if (erm_channels[i] == value) { + return true; + } + } + return false; +} + +#ifdef EDAC_NXP_ERROR_INJECT +static bool check_eim_channel(const struct edac_nxp_eim_channel *eim_channels, size_t size, + uint32_t value) +{ + for (size_t i = 0; i < size; i++) { + if (eim_channels[i].channel_id == value) { + return true; + } + } + return false; +} + +static inline const struct edac_nxp_eim_channel * +get_eim_channel(const struct edac_nxp_eim_channel *eim_channels, size_t size, uint32_t value) +{ + for (size_t i = 0; i < size; i++) { + if (eim_channels[i].channel_id == value) { + return &eim_channels[i]; + } + } + return NULL; +} + +static int inject_set_param1(const struct device *dev, uint64_t channel) +{ + struct edac_nxp_data *data = dev->data; + const struct edac_nxp_config *config = dev->config; + + if (!check_eim_channel(config->eim_channels, config->eim_channel_num, (uint32_t)channel)) { + LOG_ERR("Invalid EIM channel %llx", channel); + return -EINVAL; + } + + data->eim_channel = (uint32_t)(channel & 0xFFFFFFFFU); + return 0; +} + +static int inject_get_param1(const struct device *dev, uint64_t *value) +{ + struct edac_nxp_data *data = dev->data; + + *value = (uint64_t)data->eim_channel; + return 0; +} + +static int inject_set_param2(const struct device *dev, uint64_t word) +{ + struct edac_nxp_data *data = dev->data; + + data->eim_channel_word = (uint32_t)(word & 0xFU); + return 0; +} + +static int inject_get_param2(const struct device *dev, uint64_t *word) +{ + struct edac_nxp_data *data = dev->data; + + *word = (uint64_t)data->eim_channel_word; + return 0; +} + +static int inject_set_error_type(const struct device *dev, uint32_t inject_error_type) +{ + struct edac_nxp_data *data = dev->data; + + data->inject_error_type = inject_error_type; + return 0; +} + +static int inject_get_error_type(const struct device *dev, uint32_t *inject_error_type) +{ + struct edac_nxp_data *data = dev->data; + + *inject_error_type = data->inject_error_type; + return 0; +} + +static int inject_error_trigger(const struct device *dev) +{ + const struct edac_nxp_config *config = dev->config; + struct edac_nxp_data *data = dev->data; + uint32_t inject_data; + const struct edac_nxp_eim_channel *eim_channel_data = + get_eim_channel(config->eim_channels, config->eim_channel_num, data->eim_channel); + + switch (data->inject_error_type) { + case EDAC_ERROR_TYPE_DRAM_COR: + inject_data = EDAC_NXP_SINGLE_BIT_ERROR_MASK; + break; + case EDAC_ERROR_TYPE_DRAM_UC: + inject_data = EDAC_NXP_DOUBLE_BIT_ERROR_MASK; + break; + default: + LOG_ERR("No error type found."); + return -EINVAL; + } + +#if defined(CONFIG_EDAC_NXP_ERM_VARY_WITH_EIM_CHANNEL) && CONFIG_EDAC_NXP_ERM_VARY_WITH_EIM_CHANNEL + if (!check_erm_channel(config->erm_channels, config->erm_channel_num, + eim_channel_data->erm_channel_id)) { + LOG_WRN("Invalid ERM channel %d", eim_channel_data->erm_channel_id); + } else { + LOG_DBG("Setting ERM channel %d for error reporting", + eim_channel_data->erm_channel_id); + data->erm_channel = eim_channel_data->erm_channel_id; + } +#endif + + if (eim_channel_data->ecc_enable) { + enable_ecc(eim_channel_data->ecc_enable); + } + + if (data->eim_channel_word == 0U) { + EIM_InjectCheckBitError(config->eim_base, data->eim_channel, inject_data); + } else { + EIM_InjectDataWordBitError(config->eim_base, data->eim_channel, inject_data, + data->eim_channel_word); + } + + EIM_EnableErrorInjectionChannels(config->eim_base, EIM_CHANNEL_ENABLE(data->eim_channel)); + ERM_EnableInterrupts(config->erm_base, data->erm_channel, kERM_AllInterruptsEnable); + LOG_INF("EIM channel %d, range 0x%x - 0x%x ECC error injection triggered.", + data->eim_channel, eim_channel_data->start_address, + eim_channel_data->start_address + eim_channel_data->size - 1); + return 0; +} +#endif /* CONFIG_EDAC_ERROR_INJECT */ + +static int errors_cor_get(const struct device *dev) +{ +#if defined(ERM_CORR_ERR_CNT0_COUNT_MASK) && ERM_CORR_ERR_CNT0_COUNT_MASK + const struct edac_nxp_config *config = dev->config; + struct edac_nxp_data *data = dev->data; + + return (int)ERM_GetErrorCount(config->erm_base, data->erm_channel); +#else + return -ENOSYS; +#endif +} + +static int notify_callback_set(const struct device *dev, edac_notify_callback_f cb) +{ + struct edac_nxp_data *data = dev->data; + unsigned int key = irq_lock(); + + data->cb = cb; + irq_unlock(key); + return 0; +} + +static void edac_nxp_isr(const struct device *dev) +{ + const struct edac_nxp_config *config = dev->config; + struct edac_nxp_data *data = dev->data; + uint32_t status = ERM_GetInterruptStatus(config->erm_base, data->erm_channel); +#if defined(ERM_SYN0_SYNDROME_MASK) && ERM_SYN0_SYNDROME_MASK + uint32_t syndrome = ERM_GetSyndrome(config->erm_base, data->erm_channel); +#else + uint32_t syndrome = -ENOSYS; +#endif + struct edac_nxp_callback_data cb_data = { + .corr_err_count = errors_cor_get(dev), + .err_syndrome = syndrome, + .err_addr = ERM_GetMemoryErrorAddr(config->erm_base, data->erm_channel), + .err_status = status, + }; + + if (kERM_SingleBitCorrectionIntFlag == (status & kERM_SingleBitCorrectionIntFlag)) { + LOG_ERR("ERM channel %d correctable ECC error detected, address/offset 0x%x, " + "syndrome " + "0x%02x, correctable ECC count %d", + data->erm_channel, cb_data.err_addr, cb_data.err_syndrome, + cb_data.corr_err_count); + ERM_ClearInterruptStatus(config->erm_base, data->erm_channel, + kERM_SingleBitCorrectionIntFlag); + } else if (kERM_NonCorrectableErrorIntFlag == (status & kERM_NonCorrectableErrorIntFlag)) { + LOG_ERR("ERM channel %d uncorrectable ECC error detected, address/offset 0x%x", + data->erm_channel, cb_data.err_addr); + ERM_ClearInterruptStatus(config->erm_base, data->erm_channel, + kERM_NonCorrectableErrorIntFlag); + } else { + LOG_ERR("ERM unknown ECC error status detected, it may caused by unaligned ERM " + "channel"); + ERM_ClearInterruptStatus(config->erm_base, data->erm_channel, kERM_AllIntsFlag); + } + if (data->cb) { + data->cb(dev, &cb_data); + } +} + +static DEVICE_API(edac, edac_nxp_api) = { +#ifdef EDAC_NXP_ERROR_INJECT + /* Error Injection functions */ + .inject_set_param1 = inject_set_param1, + .inject_get_param1 = inject_get_param1, + .inject_set_param2 = inject_set_param2, + .inject_get_param2 = inject_get_param2, + .inject_set_error_type = inject_set_error_type, + .inject_get_error_type = inject_get_error_type, + .inject_error_trigger = inject_error_trigger, +#endif /* CONFIG_EDAC_ERROR_INJECT */ + + /* Get error stats */ + .errors_cor_get = errors_cor_get, + + /* Notification callback set */ + .notify_cb_set = notify_callback_set, +}; + +static int edac_nxp_init(const struct device *dev) +{ + const struct edac_nxp_config *config = dev->config; + struct edac_nxp_data *data = dev->data; + +#ifdef EDAC_NXP_ERROR_INJECT + EIM_Init(config->eim_base); + EIM_EnableGlobalErrorInjection(config->eim_base, true); + data->eim_channel_word = 1U; + LOG_INF("EIM driver initialized"); +#endif /* CONFIG_EDAC_ERROR_INJECT */ + + ERM_Init(config->erm_base); + if (!check_erm_channel(config->erm_channels, config->erm_channel_num, + CONFIG_EDAC_NXP_ERM_DEFAULT_CHANNEL)) { + LOG_ERR("Invalid ERM channel %d", CONFIG_EDAC_NXP_ERM_DEFAULT_CHANNEL); + return -EINVAL; + } + data->erm_channel = CONFIG_EDAC_NXP_ERM_DEFAULT_CHANNEL; + + /* Clear any latched status before enabling interrupts */ + ERM_ClearInterruptStatus(config->erm_base, data->erm_channel, kERM_AllIntsFlag); + config->irq_config_func(dev); + ERM_EnableInterrupts(config->erm_base, data->erm_channel, kERM_AllInterruptsEnable); + LOG_INF("ERM driver initialized"); + + return 0; +} + +#define DT_DRV_COMPAT nxp_erm + +#ifdef EDAC_NXP_ERROR_INJECT +/* Initializes an element of the eim channel device pointer array */ +#define NXP_EIM_CHANNEL_DEV_ARRAY_INIT(node) \ + { \ + .channel_id = DT_PROP(node, channel_id), \ + .erm_channel_id = DT_PROP_OR(node, erm_channel_id, 0xFFU), \ + .start_address = DT_PROP(node, start_address), \ + .ecc_enable = DT_PROP_OR(node, ecc_enable, 0), \ + .size = DT_PROP(node, size), \ + }, +const struct edac_nxp_eim_channel edac_nxp_eim_0_channels[] = { + DT_FOREACH_CHILD_STATUS_OKAY(DT_NODELABEL(eim0), NXP_EIM_CHANNEL_DEV_ARRAY_INIT)}; + +#define EDAC_NXP_EIM_CONFIG_FIELDS \ + .eim_base = (EIM_Type *)DT_REG_ADDR(DT_NODELABEL(eim0)), \ + .eim_channels = edac_nxp_eim_0_channels, \ + .eim_channel_num = ARRAY_SIZE(edac_nxp_eim_0_channels), +#else +#define EDAC_NXP_EIM_CHANNELS +#define EDAC_NXP_EIM_CONFIG_FIELDS +#endif + +static const int edac_nxp_erm_0_channels[] = DT_INST_PROP(0, channels); + +static void edac_nxp_irq_0(const struct device *dev) +{ + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(0, 0, irq), DT_INST_IRQ_BY_IDX(0, 0, priority), edac_nxp_isr, + DEVICE_DT_INST_GET(0), 0); + irq_enable(DT_INST_IRQ_BY_IDX(0, 0, irq)); + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(0, 1, irq), DT_INST_IRQ_BY_IDX(0, 1, priority), edac_nxp_isr, + DEVICE_DT_INST_GET(0), 0); + irq_enable(DT_INST_IRQ_BY_IDX(0, 1, irq)); +} + +static const struct edac_nxp_config edac_nxp_config_0 = { + .erm_base = (ERM_Type *)DT_INST_REG_ADDR(0), + EDAC_NXP_EIM_CONFIG_FIELDS.erm_channels = edac_nxp_erm_0_channels, + .erm_channel_num = ARRAY_SIZE(edac_nxp_erm_0_channels), + .irq_config_func = edac_nxp_irq_0, +}; + +static struct edac_nxp_data edac_nxp_data_0; + +DEVICE_DT_INST_DEFINE(0, &edac_nxp_init, NULL, &edac_nxp_data_0, &edac_nxp_config_0, POST_KERNEL, + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &edac_nxp_api); diff --git a/dts/bindings/edac/nxp,eim.yaml b/dts/bindings/edac/nxp,eim.yaml new file mode 100644 index 00000000000..47b2b52042b --- /dev/null +++ b/dts/bindings/edac/nxp,eim.yaml @@ -0,0 +1,32 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: NXP Error Injection Module + +compatible: "nxp,eim" + +include: [reset-device.yaml, base.yaml] + +child-binding: + properties: + channel-id: + required: true + type: int + + erm-channel-id: + type: int + description: erm channel id for current eim channel. + + size: + required: true + type: int + description: size of the memory channel. + + start-address: + required: true + type: int + description: start address of the memory channel. + + ecc-enable: + type: int + description: ecc enable magic number for the memory channel. diff --git a/dts/bindings/edac/nxp,erm.yaml b/dts/bindings/edac/nxp,erm.yaml new file mode 100644 index 00000000000..d824f6b9b47 --- /dev/null +++ b/dts/bindings/edac/nxp,erm.yaml @@ -0,0 +1,16 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: NXP Error Reporting Module + +compatible: "nxp,erm" + +include: [reset-device.yaml, base.yaml] + +properties: + interrupts: + required: true + + channels: + type: array + required: true diff --git a/include/zephyr/drivers/edac/edac_mcux_erm.h b/include/zephyr/drivers/edac/edac_mcux_erm.h new file mode 100644 index 00000000000..67b903cccc0 --- /dev/null +++ b/include/zephyr/drivers/edac/edac_mcux_erm.h @@ -0,0 +1,37 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for extended EDAC API of NXP Error Reporting Module (ERM) + * @ingroup edac_interface_ext + */ + +#ifndef ZEPHYR_DRIVERS_EDAC_NXP_ERM_H_ +#define ZEPHYR_DRIVERS_EDAC_NXP_ERM_H_ + +/** + * @brief NXP ERM EDAC driver + * @defgroup edac_nxp_erm_interface NXP ERM EDAC + * @ingroup edac_interface_ext + * @{ + */ + +/** Callback data provided to function passed to @ref edac_notify_callback_set */ +struct edac_nxp_callback_data { + /** Number of corrected errors */ + uint8_t corr_err_count; + /** Syndrome ECC bits for last single bit ECC event */ + uint8_t err_syndrome; + /** Address of last ECC event */ + uint32_t err_addr; + /** Type of last ECC event */ + uint32_t err_status; +}; + +/** @} */ + +#endif /* ZEPHYR_DRIVERS_EDAC_NXP_ERM_H_ */ diff --git a/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake b/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake index c3d7fb52452..6ab0880c6b4 100644 --- a/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake +++ b/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake @@ -64,6 +64,8 @@ set_variable_ifdef(CONFIG_DMA_MCUX_EDMA_V3 CONFIG_MCUX_COMPONENT_driver.dma set_variable_ifdef(CONFIG_DMA_MCUX_EDMA_V4 CONFIG_MCUX_COMPONENT_driver.edma4) set_variable_ifdef(CONFIG_DMA_NXP_EDMA CONFIG_MCUX_COMPONENT_driver.edma_rev2) set_variable_ifdef(CONFIG_DMA_MCUX_EDMA_V5 CONFIG_MCUX_COMPONENT_driver.edma4) +set_variable_ifdef(CONFIG_EDAC_NXP_EIM CONFIG_MCUX_COMPONENT_driver.eim) +set_variable_ifdef(CONFIG_EDAC_NXP_ERM CONFIG_MCUX_COMPONENT_driver.erm) set_variable_ifdef(CONFIG_ENTROPY_MCUX_RNGA CONFIG_MCUX_COMPONENT_driver.rnga) set_variable_ifdef(CONFIG_ENTROPY_MCUX_TRNG CONFIG_MCUX_COMPONENT_driver.trng) set_variable_ifdef(CONFIG_ENTROPY_MCUX_CAAM CONFIG_MCUX_COMPONENT_driver.caam) diff --git a/scripts/checkpatch/typedefsfile b/scripts/checkpatch/typedefsfile index ad569c5e330..cb6632bd609 100644 --- a/scripts/checkpatch/typedefsfile +++ b/scripts/checkpatch/typedefsfile @@ -8,3 +8,4 @@ io_rw_32 Pwm FILE NRF_GPIO_Type +EIM_Type