465 lines
11 KiB
C
465 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2023 Rockchip Electronics Co., Ltd.
|
|
*/
|
|
|
|
#include <linux/edac.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/rockchip/rockchip_sip.h>
|
|
#include <soc/rockchip/rockchip_dmc.h>
|
|
#include <soc/rockchip/rockchip_sip.h>
|
|
|
|
#include "edac_module.h"
|
|
|
|
#define MAX_CS (4)
|
|
|
|
#define MAX_CH (1)
|
|
|
|
#define RK_EDAC_MOD "1"
|
|
|
|
/* ECCCADDR0 */
|
|
#define ECC_CORR_RANK_SHIFT (24)
|
|
#define ECC_CORR_RANK_MASK (0x3)
|
|
#define ECC_CORR_ROW_MASK (0x3ffff)
|
|
/* ECCCADDR1 */
|
|
#define ECC_CORR_CID_SHIFT (28)
|
|
#define ECC_CORR_CID_MASK (0x3)
|
|
#define ECC_CORR_BG_SHIFT (24)
|
|
#define ECC_CORR_BG_MASK (0x3)
|
|
#define ECC_CORR_BANK_SHIFT (16)
|
|
#define ECC_CORR_BANK_MASK (0x7)
|
|
#define ECC_CORR_COL_MASK (0xfff)
|
|
/* ECCUADDR0 */
|
|
#define ECC_UNCORR_RANK_SHIFT (24)
|
|
#define ECC_UNCORR_RANK_MASK (0x3)
|
|
#define ECC_UNCORR_ROW_MASK (0x3ffff)
|
|
/* ECCUADDR1 */
|
|
#define ECC_UNCORR_CID_SHIFT (28)
|
|
#define ECC_UNCORR_CID_MASK (0x3)
|
|
#define ECC_UNCORR_BG_SHIFT (24)
|
|
#define ECC_UNCORR_BG_MASK (0x3)
|
|
#define ECC_UNCORR_BANK_SHIFT (16)
|
|
#define ECC_UNCORR_BANK_MASK (0x7)
|
|
#define ECC_UNCORR_COL_MASK (0xfff)
|
|
|
|
#define DDR_ECC_CE_DATA_POISON (1)
|
|
#define DDR_ECC_UE_DATA_POISON (0)
|
|
|
|
#define DDR_ECC_POISON_EN (1)
|
|
#define DDR_ECC_POISON_DIS (0)
|
|
|
|
/* [15:8]: high version + [7:0]: low version */
|
|
#define DDR_ECC_CFG_VER (0x100)
|
|
#define DDR_ECC_CFG_VER_V101 (0x101)
|
|
|
|
#define DDR_ECC_TAG_KERNEL (0x5588eedd)
|
|
|
|
/**
|
|
* struct ddr_ecc_error_info - DDR ECC error log information
|
|
* @err_cnt: error count
|
|
* @rank: Rank number
|
|
* @row: Row number
|
|
* @chip_id: Chip id number
|
|
* @bank_group: Bank Group number
|
|
* @bank: Bank number
|
|
* @col: Column number
|
|
* @bitpos: Bit position
|
|
*/
|
|
struct ddr_ecc_error_info {
|
|
u32 err_cnt;
|
|
u32 rank;
|
|
u32 row;
|
|
u32 chip_id;
|
|
u32 bank_group;
|
|
u32 bank;
|
|
u32 col;
|
|
u32 bitpos;
|
|
};
|
|
|
|
/**
|
|
* struct ddr_ecc_status - DDR ECC status information to report
|
|
* @ceinfo: Correctable error log information
|
|
* @ueinfo: Uncorrectable error log information
|
|
*/
|
|
struct ddr_ecc_status {
|
|
struct ddr_ecc_error_info ceinfo;
|
|
struct ddr_ecc_error_info ueinfo;
|
|
};
|
|
|
|
/**
|
|
* struct rk_ddr_ecc_cfg - RK DDR ECC config
|
|
* @ecc_cfg_ver: version for DDR ECC config
|
|
* @ecc_poiso_en: enable ecc data poisoning
|
|
* @ecc_poison_mode: corrected/uncorrected data poisoning mode
|
|
* @ecc_poison_addr: DDR ECC Data Poisoning Address
|
|
*/
|
|
struct rk_ddr_ecc_cfg {
|
|
u32 ecc_cfg_ver;
|
|
u32 ecc_poison_en;
|
|
u32 ecc_poison_mode;
|
|
u64 ecc_poison_addr;
|
|
u64 ce_addr;
|
|
u64 ue_addr;
|
|
struct ddr_ecc_error_info poison;
|
|
u32 kernel_tag;
|
|
};
|
|
|
|
/**
|
|
* struct rk_edac_priv - RK DDR memory controller private instance data
|
|
* @name: EDAC name
|
|
* @stat: DDR ECC status information
|
|
* @ce_cnt: Correctable Error count
|
|
* @ue_cnt: Uncorrectable Error count
|
|
* @irq_ce: Corrected interrupt number
|
|
* @irq_ue: Uncorrected interrupt number
|
|
*/
|
|
struct rk_edac_priv {
|
|
char *name;
|
|
struct ddr_ecc_status stat;
|
|
u32 ce_cnt;
|
|
u32 ue_cnt;
|
|
int irq_ce;
|
|
int irq_ue;
|
|
};
|
|
|
|
static struct ddr_ecc_status *ddr_edac_info;
|
|
static struct rk_ddr_ecc_cfg *ddr_edac_cfg;
|
|
|
|
static char edac_poison_mode[4] = "ce";
|
|
|
|
static inline void opstate_init_int(void)
|
|
{
|
|
switch (edac_op_state) {
|
|
case EDAC_OPSTATE_POLL:
|
|
case EDAC_OPSTATE_INT:
|
|
break;
|
|
default:
|
|
edac_op_state = EDAC_OPSTATE_INT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void rockchip_edac_handle_ce_error(struct mem_ctl_info *mci,
|
|
struct ddr_ecc_status *p)
|
|
{
|
|
struct ddr_ecc_error_info *pinf;
|
|
|
|
if (p->ceinfo.err_cnt) {
|
|
pinf = &p->ceinfo;
|
|
edac_mc_printk(mci, KERN_ERR,
|
|
"DDR ECC CE error: CS%d, Row 0x%x, Bg 0x%x, Bk 0x%x, Col 0x%x bit 0x%x\n",
|
|
pinf->rank, pinf->row, pinf->bank_group,
|
|
pinf->bank, pinf->col,
|
|
pinf->bitpos);
|
|
edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
|
|
p->ceinfo.err_cnt, 0, 0, 0, 0, 0, -1,
|
|
mci->ctl_name, "");
|
|
}
|
|
}
|
|
|
|
static void rockchip_edac_handle_ue_error(struct mem_ctl_info *mci,
|
|
struct ddr_ecc_status *p)
|
|
{
|
|
struct ddr_ecc_error_info *pinf;
|
|
|
|
if (p->ueinfo.err_cnt) {
|
|
pinf = &p->ueinfo;
|
|
edac_mc_printk(mci, KERN_ERR,
|
|
"DDR ECC UE error: CS%d, Row 0x%x, Bg 0x%x, Bk 0x%x, Col 0x%x\n",
|
|
pinf->rank, pinf->row,
|
|
pinf->bank_group, pinf->bank, pinf->col);
|
|
edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
|
|
p->ueinfo.err_cnt, 0, 0, 0, 0, 0, -1,
|
|
mci->ctl_name, "");
|
|
}
|
|
}
|
|
|
|
static int rockchip_ddr_ecc_poison(struct rk_ddr_ecc_cfg *cfg)
|
|
{
|
|
struct arm_smccc_res res;
|
|
|
|
cfg->ecc_cfg_ver = DDR_ECC_CFG_VER_V101;
|
|
cfg->kernel_tag = DDR_ECC_TAG_KERNEL;
|
|
cfg->ecc_poison_en = DDR_ECC_POISON_EN;
|
|
|
|
res = sip_smc_dram(SHARE_PAGE_TYPE_DDRECC, 0,
|
|
ROCKCHIP_SIP_CONFIG_DRAM_ECC_POISON);
|
|
if ((res.a0) || (res.a1)) {
|
|
edac_printk(KERN_ERR, EDAC_MC,
|
|
"ROCKCHIP_SIP_CONFIG_DRAM_ECC_POISON not support: 0x%lx-0x%lx\n",
|
|
res.a0, res.a1);
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* disable ddr ecc poison */
|
|
cfg->ecc_poison_en = DDR_ECC_POISON_DIS;
|
|
res = sip_smc_dram(SHARE_PAGE_TYPE_DDRECC, 0,
|
|
ROCKCHIP_SIP_CONFIG_DRAM_ECC_POISON);
|
|
if ((res.a0) || (res.a1)) {
|
|
edac_printk(KERN_ERR, EDAC_MC,
|
|
"ROCKCHIP_SIP_CONFIG_DRAM_ECC_POISON not support: 0x%lx-0x%lx\n",
|
|
res.a0, res.a1);
|
|
return -ENXIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_edac_get_error_info(struct mem_ctl_info *mci)
|
|
{
|
|
struct arm_smccc_res res;
|
|
|
|
res = sip_smc_dram(SHARE_PAGE_TYPE_DDRECC, 0,
|
|
ROCKCHIP_SIP_CONFIG_DRAM_ECC);
|
|
if ((res.a0) || (res.a1)) {
|
|
edac_mc_printk(mci, KERN_ERR, "ROCKCHIP_SIP_CONFIG_DRAM_ECC not support: 0x%lx\n",
|
|
res.a0);
|
|
return -ENXIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rockchip_edac_check(struct mem_ctl_info *mci)
|
|
{
|
|
struct rk_edac_priv *priv = mci->pvt_info;
|
|
int ret;
|
|
|
|
ret = rockchip_edac_get_error_info(mci);
|
|
if (ret)
|
|
return;
|
|
|
|
priv->ce_cnt += ddr_edac_info->ceinfo.err_cnt;
|
|
priv->ue_cnt += ddr_edac_info->ceinfo.err_cnt;
|
|
rockchip_edac_handle_ce_error(mci, ddr_edac_info);
|
|
rockchip_edac_handle_ue_error(mci, ddr_edac_info);
|
|
}
|
|
|
|
static irqreturn_t rockchip_edac_mc_ce_isr(int irq, void *dev_id)
|
|
{
|
|
struct mem_ctl_info *mci = dev_id;
|
|
struct rk_edac_priv *priv = mci->pvt_info;
|
|
int ret;
|
|
|
|
ret = rockchip_edac_get_error_info(mci);
|
|
if (ret)
|
|
return IRQ_NONE;
|
|
|
|
priv->ce_cnt += ddr_edac_info->ceinfo.err_cnt;
|
|
|
|
rockchip_edac_handle_ce_error(mci, ddr_edac_info);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t rockchip_edac_mc_ue_isr(int irq, void *dev_id)
|
|
{
|
|
struct mem_ctl_info *mci = dev_id;
|
|
struct rk_edac_priv *priv = mci->pvt_info;
|
|
int ret;
|
|
|
|
ret = rockchip_edac_get_error_info(mci);
|
|
if (ret)
|
|
return IRQ_NONE;
|
|
|
|
priv->ue_cnt += ddr_edac_info->ueinfo.err_cnt;
|
|
|
|
rockchip_edac_handle_ue_error(mci, ddr_edac_info);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int rockchip_edac_trigger(const char *val, const struct kernel_param *kp)
|
|
{
|
|
int ret = 0;
|
|
unsigned long value, i;
|
|
|
|
if (ddr_edac_cfg == NULL)
|
|
return -1;
|
|
|
|
if (kstrtoul(val, 0, &value) < 0)
|
|
return 0;
|
|
|
|
if (!strncmp(edac_poison_mode, "ce", 2)) {
|
|
ddr_edac_cfg->ecc_poison_mode = DDR_ECC_CE_DATA_POISON;
|
|
} else if (!strncmp(edac_poison_mode, "ue", 2)) {
|
|
ddr_edac_cfg->ecc_poison_mode = DDR_ECC_UE_DATA_POISON;
|
|
} else {
|
|
ddr_edac_cfg->ecc_poison_mode = DDR_ECC_CE_DATA_POISON;
|
|
edac_printk(KERN_WARNING, EDAC_MC,
|
|
"unknown poison mode, used default mode: ce!\n");
|
|
}
|
|
|
|
for (i = 0; i < value; i++) {
|
|
rockchip_dmcfreq_lock();
|
|
ret = rockchip_ddr_ecc_poison(ddr_edac_cfg);
|
|
rockchip_dmcfreq_unlock();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_edac_mc_init(struct mem_ctl_info *mci,
|
|
struct platform_device *pdev)
|
|
{
|
|
struct rk_edac_priv *priv = mci->pvt_info;
|
|
struct arm_smccc_res res;
|
|
int ret;
|
|
|
|
mci->pdev = &pdev->dev;
|
|
dev_set_drvdata(mci->pdev, mci);
|
|
mci->mtype_cap = MEM_FLAG_DDR3 | MEM_FLAG_DDR4;
|
|
mci->edac_ctl_cap = EDAC_FLAG_SECDED;
|
|
mci->scrub_cap = SCRUB_NONE;
|
|
mci->scrub_mode = SCRUB_NONE;
|
|
|
|
mci->edac_cap = EDAC_FLAG_SECDED;
|
|
mci->ctl_name = priv->name;
|
|
mci->dev_name = priv->name;
|
|
mci->mod_name = RK_EDAC_MOD;
|
|
|
|
if (edac_op_state == EDAC_OPSTATE_POLL)
|
|
mci->edac_check = rockchip_edac_check;
|
|
mci->ctl_page_to_phys = NULL;
|
|
|
|
res = sip_smc_request_share_mem(1, SHARE_PAGE_TYPE_DDRECC);
|
|
if (res.a0 != 0) {
|
|
dev_err(&pdev->dev, "no ATF memory for init, ret 0x%lx\n", res.a0);
|
|
return -ENOMEM;
|
|
}
|
|
ddr_edac_info = (struct ddr_ecc_status *)res.a1;
|
|
memset(ddr_edac_info, 0, sizeof(struct ddr_ecc_status));
|
|
|
|
ddr_edac_cfg = (struct rk_ddr_ecc_cfg *)(res.a1 + (4096 / 4));
|
|
memset(ddr_edac_cfg, 0, sizeof(struct rk_ddr_ecc_cfg));
|
|
|
|
ret = rockchip_edac_get_error_info(mci);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_edac_probe(struct platform_device *pdev)
|
|
{
|
|
struct mem_ctl_info *mci;
|
|
struct edac_mc_layer layers[2];
|
|
struct rk_edac_priv *priv;
|
|
int ret;
|
|
|
|
opstate_init_int();
|
|
layers[0].type = EDAC_MC_LAYER_CHIP_SELECT;
|
|
layers[0].size = MAX_CS;
|
|
layers[0].is_virt_csrow = true;
|
|
layers[1].type = EDAC_MC_LAYER_CHANNEL;
|
|
layers[1].size = MAX_CH;
|
|
layers[1].is_virt_csrow = false;
|
|
|
|
mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers,
|
|
sizeof(struct rk_edac_priv));
|
|
if (!mci) {
|
|
edac_printk(KERN_ERR, EDAC_MC,
|
|
"Failed memory allocation for mc instance\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv = mci->pvt_info;
|
|
priv->name = "rk_edac_ecc";
|
|
ret = rockchip_edac_mc_init(mci, pdev);
|
|
if (ret) {
|
|
edac_printk(KERN_ERR, EDAC_MC,
|
|
"Failed to initialize instance\n");
|
|
goto free_edac_mc;
|
|
}
|
|
|
|
ret = edac_mc_add_mc(mci);
|
|
if (ret) {
|
|
edac_printk(KERN_ERR, EDAC_MC,
|
|
"Failed edac_mc_add_mc()\n");
|
|
goto free_edac_mc;
|
|
}
|
|
|
|
if (edac_op_state == EDAC_OPSTATE_INT) {
|
|
/* register interrupts */
|
|
priv->irq_ce = platform_get_irq_byname(pdev, "ce");
|
|
ret = devm_request_irq(&pdev->dev, priv->irq_ce,
|
|
rockchip_edac_mc_ce_isr,
|
|
0,
|
|
"[EDAC] MC err", mci);
|
|
if (ret < 0) {
|
|
edac_printk(KERN_ERR, EDAC_MC,
|
|
"%s: Unable to request ce irq %d for RK EDAC\n",
|
|
__func__, priv->irq_ce);
|
|
goto del_mc;
|
|
}
|
|
|
|
edac_printk(KERN_INFO, EDAC_MC,
|
|
"acquired ce irq %d for MC\n",
|
|
priv->irq_ce);
|
|
|
|
priv->irq_ue = platform_get_irq_byname(pdev, "ue");
|
|
ret = devm_request_irq(&pdev->dev, priv->irq_ue,
|
|
rockchip_edac_mc_ue_isr,
|
|
0,
|
|
"[EDAC] MC err", mci);
|
|
if (ret < 0) {
|
|
edac_printk(KERN_ERR, EDAC_MC,
|
|
"%s: Unable to request ue irq %d for RK EDAC\n",
|
|
__func__, priv->irq_ue);
|
|
goto del_mc;
|
|
}
|
|
|
|
edac_printk(KERN_INFO, EDAC_MC,
|
|
"acquired ue irq %d for MC\n",
|
|
priv->irq_ue);
|
|
}
|
|
|
|
return 0;
|
|
|
|
del_mc:
|
|
edac_mc_del_mc(&pdev->dev);
|
|
free_edac_mc:
|
|
edac_mc_free(mci);
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
static int rockchip_edac_remove(struct platform_device *pdev)
|
|
{
|
|
struct mem_ctl_info *mci = dev_get_drvdata(&pdev->dev);
|
|
|
|
edac_mc_del_mc(&pdev->dev);
|
|
edac_mc_free(mci);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id rk_ddr_mc_err_of_match[] = {
|
|
{ .compatible = "rockchip,rk3568-edac", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, rk_ddr_mc_err_of_match);
|
|
|
|
static struct platform_driver rockchip_edac_driver = {
|
|
.probe = rockchip_edac_probe,
|
|
.remove = rockchip_edac_remove,
|
|
.driver = {
|
|
.name = "rk_edac",
|
|
.of_match_table = rk_ddr_mc_err_of_match,
|
|
},
|
|
};
|
|
module_platform_driver(rockchip_edac_driver);
|
|
|
|
module_param_string(edac_poison_mode, edac_poison_mode, sizeof(edac_poison_mode), 0664);
|
|
MODULE_PARM_DESC(edac_poison_mode, "RK EDAC DDR ECC poison mode(ce or ue).");
|
|
|
|
module_param_call(rockchip_edac_trigger, rockchip_edac_trigger, NULL, NULL, 0664);
|
|
MODULE_PARM_DESC(rockchip_edac_trigger,
|
|
"RK EDAC DDR ECC triggered by writing the number of errors(greater than 0).");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("He Zhihuan <huan.he@rock-chips.com>\n");
|
|
MODULE_DESCRIPTION("ROCKCHIP EDAC kernel module");
|