blob: 2d2deb81e49c3c48bbc7c93ffc85503417572c9b [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 Quanta Computer lnc.
*/
#include <linux/edac.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include "edac_module.h"
#define ECC_ENABLE BIT(24)
#define ECC_EN_INT_MASK 0x7fffff87
#define INT_STATUS_ADDR 116
#define INT_ACK_ADDR 117
#define INT_MASK_ADDR 118
#define ECC_EN_ADDR 93
#define ECC_C_ADDR_ADDR 98
#define ECC_C_DATA_ADDR 100
#define ECC_C_ID_ADDR 101
#define ECC_C_SYND_ADDR 99
#define ECC_U_ADDR_ADDR 95
#define ECC_U_DATA_ADDR 97
#define ECC_U_ID_ADDR 101
#define ECC_U_SYND_ADDR 96
#define ECC_ERROR -1
#define EDAC_MSG_SIZE 256
#define EDAC_MOD_NAME "npcm7xx-edac"
struct ecc_error_signature_info {
u32 ecc_addr;
u32 ecc_data;
u32 ecc_id;
u32 ecc_synd;
};
struct npcm7xx_ecc_int_status {
u32 int_mask;
u32 int_status;
u32 int_ack;
u32 ce_cnt;
u32 ue_cnt;
struct ecc_error_signature_info ceinfo;
struct ecc_error_signature_info ueinfo;
};
struct npcm7xx_edac_priv {
void __iomem *baseaddr;
char message[EDAC_MSG_SIZE];
struct npcm7xx_ecc_int_status stat;
};
/**
* npcm7xx_edac_get_ecc_syndrom - Get the current ecc error info
* @base: Pointer to the base address of the ddr memory controller
* @p: Pointer to the Nuvoton ecc status structure
*
* Determines there is any ecc error or not
*
* Return: ECC detection status
*/
static int npcm7xx_edac_get_ecc_syndrom(void __iomem *base,
struct npcm7xx_ecc_int_status *p)
{
int status = 0;
u32 int_status = 0;
int_status = readl(base + 4*INT_STATUS_ADDR);
writel(int_status, base + 4*INT_ACK_ADDR);
edac_dbg(3, "int_status: %#08x\n", int_status);
if ((int_status & (1 << 6)) == (1 << 6)) {
edac_dbg(3, "6-Mult uncorrectable detected.\n");
p->ue_cnt++;
status = ECC_ERROR;
}
if ((int_status & (1 << 5)) == (1 << 5)) {
edac_dbg(3, "5-An uncorrectable detected\n");
p->ue_cnt++;
status = ECC_ERROR;
}
if ((int_status & (1 << 4)) == (1 << 4)) {
edac_dbg(3, "4-mult correctable detected.\n");
p->ce_cnt++;
status = ECC_ERROR;
}
if ((int_status & (1 << 3)) == (1 << 3)) {
edac_dbg(3, "3-A correctable detected.\n");
p->ce_cnt++;
status = ECC_ERROR;
}
if (status == ECC_ERROR) {
u32 ecc_id;
p->ceinfo.ecc_addr = readl(base + 4*ECC_C_ADDR_ADDR);
p->ceinfo.ecc_data = readl(base + 4*ECC_C_DATA_ADDR);
p->ceinfo.ecc_synd = readl(base + 4*ECC_C_SYND_ADDR);
p->ueinfo.ecc_addr = readl(base + 4*ECC_U_ADDR_ADDR);
p->ueinfo.ecc_data = readl(base + 4*ECC_U_DATA_ADDR);
p->ueinfo.ecc_synd = readl(base + 4*ECC_U_SYND_ADDR);
/* ECC_C_ID_ADDR has same value as ECC_U_ID_ADDR */
ecc_id = readl(base + 4*ECC_C_ID_ADDR);
p->ueinfo.ecc_id = ecc_id & 0xffff;
p->ceinfo.ecc_id = ecc_id >> 16;
}
return status;
}
/**
* npcm7xx_edac_handle_error - Handle controller error types CE and UE
* @mci: Pointer to the edac memory controller instance
* @p: Pointer to the Nuvoton ecc status structure
*
* Handles the controller ECC correctable and un correctable error.
*/
static void npcm7xx_edac_handle_error(struct mem_ctl_info *mci,
struct npcm7xx_ecc_int_status *p)
{
struct npcm7xx_edac_priv *priv = mci->pvt_info;
u32 page, offset;
if (p->ce_cnt) {
snprintf(priv->message, EDAC_MSG_SIZE,
"DDR ECC: synd=%#08x addr=%#08x data=%#08x source_id=%#08x ",
p->ceinfo.ecc_synd, p->ceinfo.ecc_addr,
p->ceinfo.ecc_data, p->ceinfo.ecc_id);
page = p->ceinfo.ecc_addr >> PAGE_SHIFT;
offset = p->ceinfo.ecc_addr & ~PAGE_MASK;
edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
p->ce_cnt, page, offset,
p->ceinfo.ecc_synd,
0, 0, -1,
priv->message, "");
}
if (p->ue_cnt) {
snprintf(priv->message, EDAC_MSG_SIZE,
"DDR ECC: synd=%#08x addr=%#08x data=%#08x source_id=%#08x ",
p->ueinfo.ecc_synd, p->ueinfo.ecc_addr,
p->ueinfo.ecc_data, p->ueinfo.ecc_id);
page = p->ueinfo.ecc_addr >> PAGE_SHIFT;
offset = p->ueinfo.ecc_addr & ~PAGE_MASK;
edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
p->ue_cnt, page, offset,
p->ueinfo.ecc_synd,
0, 0, -1,
priv->message, "");
}
memset(p, 0, sizeof(*p));
}
/**
* npcm7xx_edac_check - Check controller for ECC errors
* @mci: Pointer to the edac memory controller instance
*
* This routine is used to check and post ECC errors and is called by
* this driver's CE and UE interrupt handler.
*/
static void npcm7xx_edac_check(struct mem_ctl_info *mci)
{
struct npcm7xx_edac_priv *priv = mci->pvt_info;
int status = 0;
status = npcm7xx_edac_get_ecc_syndrom(priv->baseaddr, &priv->stat);
if (status != ECC_ERROR)
return;
npcm7xx_edac_handle_error(mci, &priv->stat);
}
/**
* npcm7xx_edac_isr - CE/UE interrupt service routine
* @irq: The virtual interrupt number being serviced.
* @dev_id: A pointer to the EDAC memory controller instance
* associated with the interrupt being handled.
*
* This routine implements the interrupt handler for both correctable
* (CE) and uncorrectable (UE) ECC errors for the Nuvoton Cadence DDR
* controller. It simply calls through to the routine used to check,
* report and clear the ECC status.
*
* Unconditionally returns IRQ_HANDLED.
*/
static irqreturn_t npcm7xx_edac_isr(int irq, void *dev_id)
{
struct mem_ctl_info *mci = dev_id;
int npcm_edac_report = 0;
npcm_edac_report = edac_get_report_status();
if (npcm_edac_report != EDAC_REPORTING_DISABLED)
npcm7xx_edac_check(mci);
return IRQ_HANDLED;
}
static int npcm7xx_edac_register_irq(struct mem_ctl_info *mci,
struct platform_device *pdev)
{
int status = 0;
int mc_irq;
struct npcm7xx_edac_priv *priv = mci->pvt_info;
/* Only enable MC interrupts with ECC - clear int_mask[6:3] */
writel(ECC_EN_INT_MASK, priv->baseaddr + 4*INT_MASK_ADDR);
mc_irq = platform_get_irq(pdev, 0);
if (!mc_irq) {
edac_printk(KERN_ERR, EDAC_MC, "Unable to map interrupts.\n");
status = -ENODEV;
goto fail;
}
status = devm_request_irq(&pdev->dev, mc_irq, npcm7xx_edac_isr, 0,
"npcm-memory-controller", mci);
if (status < 0) {
edac_printk(KERN_ERR, EDAC_MC,
"Unable to request irq %d for ECC",
mc_irq);
status = -ENODEV;
goto fail;
}
return 0;
fail:
return status;
}
static const struct of_device_id npcm7xx_edac_of_match[] = {
{ .compatible = "nuvoton,npcm7xx-sdram-edac"},
{ /* end of table */ }
};
MODULE_DEVICE_TABLE(of, npcm7xx_edac_of_match);
/**
* npcm7xx_edac_mc_init - Initialize driver instance
* @mci: Pointer to the edac memory controller instance
* @pdev: Pointer to the platform_device struct
*
* Performs initialization of the EDAC memory controller instance and
* related driver-private data associated with the memory controller the
* instance is bound to.
*
* Returns 0 if OK; otherwise, < 0 on error.
*/
static int npcm7xx_edac_mc_init(struct mem_ctl_info *mci,
struct platform_device *pdev)
{
const struct of_device_id *id;
id = of_match_device(npcm7xx_edac_of_match, &pdev->dev);
if (!id)
return -ENODEV;
/* Initialize controller capabilities and configuration */
mci->mtype_cap = MEM_FLAG_DDR4;
mci->edac_ctl_cap = EDAC_FLAG_SECDED;
mci->edac_cap = EDAC_FLAG_SECDED;
mci->scrub_cap = SCRUB_FLAG_HW_SRC;
mci->scrub_mode = SCRUB_HW_SRC;
mci->ctl_name = id->compatible;
mci->dev_name = dev_name(&pdev->dev);
mci->mod_name = EDAC_MOD_NAME;
edac_op_state = EDAC_OPSTATE_INT;
return 0;
}
/**
* npcm7xx_edac_get_eccstate - Return the controller ecc enable/disable status
* @base: Pointer to the ddr memory controller base address
*
* Get the ECC enable/disable status for the controller
*
* Return: a ecc status boolean i.e true/false - enabled/disabled.
*/
static bool npcm7xx_edac_get_eccstate(void __iomem *base)
{
u32 ecc_en;
bool state = false;
ecc_en = readl(base + 4*ECC_EN_ADDR);
if (ecc_en & ECC_ENABLE) {
edac_printk(KERN_INFO, EDAC_MC, "ECC reporting and correcting on. ");
state = true;
}
return state;
}
/**
* npcm7xx_edac_mc_probe - Check controller and bind driver
* @pdev: Pointer to the platform_device struct
*
* Probes a specific controller instance for binding with the driver.
*
* Return: 0 if the controller instance was successfully bound to the
* driver; otherwise, < 0 on error.
*/
static int npcm7xx_edac_mc_probe(struct platform_device *pdev)
{
struct mem_ctl_info *mci;
struct edac_mc_layer layers[1];
struct npcm7xx_edac_priv *priv;
struct resource *res;
void __iomem *baseaddr;
int rc;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
baseaddr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(baseaddr)) {
edac_printk(KERN_ERR, EDAC_MOD_NAME,
"DDR controller regs not defined\n");
return PTR_ERR(baseaddr);
}
/*
* Check if ECC is enabled.
* If not, there is no useful monitoring that can be done
* for this controller.
*/
if (!npcm7xx_edac_get_eccstate(baseaddr)) {
edac_printk(KERN_INFO, EDAC_MC, "ECC disabled\n");
return -ENXIO;
}
/*
* Allocate an EDA controller instance and perform the appropriate
* initialization.
*/
layers[0].type = EDAC_MC_LAYER_ALL_MEM;
layers[0].size = 1;
mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers,
sizeof(struct npcm7xx_edac_priv));
if (!mci) {
edac_printk(KERN_ERR, EDAC_MC,
"Failed memory allocation for mc instance\n");
return -ENOMEM;
}
mci->pdev = &pdev->dev;
priv = mci->pvt_info;
priv->baseaddr = baseaddr;
platform_set_drvdata(pdev, mci);
rc = npcm7xx_edac_mc_init(mci, pdev);
if (rc) {
edac_printk(KERN_ERR, EDAC_MC,
"Failed to initialize instance\n");
goto free_edac_mc;
}
/* Attempt to register it with the EDAC subsystem */
rc = edac_mc_add_mc(mci);
if (rc) {
edac_printk(KERN_ERR, EDAC_MC,
"Failed to register with EDAC core\n");
goto free_edac_mc;
}
/* Register interrupts */
rc = npcm7xx_edac_register_irq(mci, pdev);
if (rc)
goto free_edac_mc;
return 0;
free_edac_mc:
edac_mc_free(mci);
return rc;
}
/**
* npcm7xx_edac_mc_remove - Unbind driver from controller
* @pdev: Pointer to the platform_device struct
*
* Return: Unconditionally 0
*/
static int npcm7xx_edac_mc_remove(struct platform_device *pdev)
{
struct mem_ctl_info *mci = platform_get_drvdata(pdev);
edac_mc_del_mc(&pdev->dev);
edac_mc_free(mci);
return 0;
}
static struct platform_driver npcm7xx_edac_driver = {
.probe = npcm7xx_edac_mc_probe,
.remove = npcm7xx_edac_mc_remove,
.driver = {
.name = EDAC_MOD_NAME,
.of_match_table = npcm7xx_edac_of_match,
},
};
module_platform_driver(npcm7xx_edac_driver);
MODULE_AUTHOR("Quanta Computer Inc.");
MODULE_DESCRIPTION("Nuvoton NPCM7xx EDAC Driver");
MODULE_LICENSE("GPL v2");