drivers: edac: Add NXP EDAC driver

Add edac driver for NXP's ERM and EIM peripherals. It can inject ECC
error to specific channel within EIM and then report the error address,
syndrome and count within ERM.

Signed-off-by: Yves Wang <zhengjia.wang@nxp.com>
This commit is contained in:
Yves Wang
2025-10-29 16:51:40 +08:00
committed by Anas Nashif
parent f335c00ccd
commit 67e0217399
9 changed files with 485 additions and 0 deletions

View File

@@ -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

View File

@@ -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"

View File

@@ -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.

View File

@@ -0,0 +1,355 @@
/*
* Copyright 2025 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/edac.h>
#include <zephyr/drivers/edac/edac_mcux_erm.h>
#include <zephyr/logging/log.h>
#include <fsl_erm.h>
LOG_MODULE_REGISTER(edac_mcux_erm, CONFIG_EDAC_LOG_LEVEL);
#ifdef EDAC_NXP_ERROR_INJECT
#include <fsl_eim.h>
#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);

View File

@@ -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.

View File

@@ -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

View File

@@ -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_ */

View File

@@ -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)

View File

@@ -8,3 +8,4 @@ io_rw_32
Pwm
FILE
NRF_GPIO_Type
EIM_Type