Add driver support for Bus Error Unit present in SiFive's FU740 chip. Currently the driver reports erroneous events only using Platform-level interrupts. The support for reporting events using hart-local interrupts can be added in future. Signed-off-by: Yash Shah <yash.shah@xxxxxxxxxx> --- drivers/soc/sifive/Kconfig | 5 + drivers/soc/sifive/Makefile | 1 + drivers/soc/sifive/sifive_beu.c | 197 ++++++++++++++++++++++++++++++++++++++++ include/soc/sifive/sifive_beu.h | 16 ++++ 4 files changed, 219 insertions(+) create mode 100644 drivers/soc/sifive/sifive_beu.c create mode 100644 include/soc/sifive/sifive_beu.h diff --git a/drivers/soc/sifive/Kconfig b/drivers/soc/sifive/Kconfig index 58cf8c4..d575fc1 100644 --- a/drivers/soc/sifive/Kconfig +++ b/drivers/soc/sifive/Kconfig @@ -7,4 +7,9 @@ config SIFIVE_L2 help Support for the L2 cache controller on SiFive platforms. +config SIFIVE_BEU + bool "Sifive Bus Error Unit" + help + Support for the Bus Error Unit on SiFive platforms. + endif diff --git a/drivers/soc/sifive/Makefile b/drivers/soc/sifive/Makefile index b5caff7..1b43ecd 100644 --- a/drivers/soc/sifive/Makefile +++ b/drivers/soc/sifive/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_SIFIVE_L2) += sifive_l2_cache.o +obj-$(CONFIG_SIFIVE_BEU) += sifive_beu.o diff --git a/drivers/soc/sifive/sifive_beu.c b/drivers/soc/sifive/sifive_beu.c new file mode 100644 index 0000000..87b69ba --- /dev/null +++ b/drivers/soc/sifive/sifive_beu.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SiFive Bus Error Unit driver + * Copyright (C) 2020 SiFive + * Author: Yash Shah <yash.shah@xxxxxxxxxx> + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <soc/sifive/sifive_beu.h> + +#define SIFIVE_BEU_CAUSE 0x00 +#define SIFIVE_BEU_VALUE 0x08 +#define SIFIVE_BEU_ENABLE 0x10 +#define SIFIVE_BEU_PLIC_INTR 0x18 +#define SIFIVE_BEU_ACCRUED 0x20 +#define SIFIVE_BEU_LOCAL_INTR 0x28 + +#define LOCAL_INTERRUPT 0 +#define PLIC_INTERRUPT 1 +#define MAX_ERR_EVENTS 5 + +enum beu_err_events { + RESERVED = -1, + NO_ERR, + ITIM_CORR_ECC = 2, + ITIM_UNCORR_ECC, + TILINKBUS_ERR = 5, + DCACHE_CORR_ECC, + DCACHE_UNCORR_ECC +}; + +static +int err_events[MAX_ERR_EVENTS] = {ITIM_CORR_ECC, ITIM_UNCORR_ECC, TILINKBUS_ERR, + DCACHE_CORR_ECC, DCACHE_UNCORR_ECC}; + +struct beu_sifive_ddata { + void __iomem *regs; + int irq; +}; + +static int beu_enable_event(struct beu_sifive_ddata *ddata, + int event, int intr_type) +{ + unsigned char event_mask = BIT(event), val; + + val = readb(ddata->regs + SIFIVE_BEU_ENABLE); + val |= event_mask; + writeb(val, ddata->regs + SIFIVE_BEU_ENABLE); + + if (intr_type == PLIC_INTERRUPT) { + val = readb(ddata->regs + SIFIVE_BEU_PLIC_INTR); + val |= event_mask; + writeb(val, ddata->regs + SIFIVE_BEU_PLIC_INTR); + } else if (intr_type == LOCAL_INTERRUPT) { + val = readb(ddata->regs + SIFIVE_BEU_LOCAL_INTR); + val |= event_mask; + writeb(event_mask, ddata->regs + SIFIVE_BEU_LOCAL_INTR); + } + + return 0; +} + +static ATOMIC_NOTIFIER_HEAD(beu_chain); + +int register_sifive_beu_error_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&beu_chain, nb); +} + +int unregister_sifive_beu_error_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&beu_chain, nb); +} + +static irqreturn_t beu_sifive_irq(int irq, void *data) +{ + struct beu_sifive_ddata *ddata = data; + unsigned char cause, addr; + + addr = readb(ddata->regs + SIFIVE_BEU_VALUE); + cause = readb(ddata->regs + SIFIVE_BEU_CAUSE); + switch (cause) { + case NO_ERR: + break; + case ITIM_CORR_ECC: + pr_err("BEU: ITIM ECCFIX @ %d\n", addr); + atomic_notifier_call_chain(&beu_chain, SIFIVE_BEU_ERR_TYPE_CE, + "ITIM ECCFIX"); + break; + case ITIM_UNCORR_ECC: + pr_err("BEU: ITIM ECCFAIL @ %d\n", addr); + atomic_notifier_call_chain(&beu_chain, SIFIVE_BEU_ERR_TYPE_UE, + "ITIM ECCFAIL"); + break; + case TILINKBUS_ERR: + pr_err("BEU: Load or Store TILINK BUS ERR occurred\n"); + break; + case DCACHE_CORR_ECC: + pr_err("BEU: DATACACHE ECCFIX @ %d\n", addr); + atomic_notifier_call_chain(&beu_chain, SIFIVE_BEU_ERR_TYPE_CE, + "DCACHE ECCFIX"); + break; + case DCACHE_UNCORR_ECC: + pr_err("BEU: DATACACHE ECCFAIL @ %d\n", addr); + atomic_notifier_call_chain(&beu_chain, SIFIVE_BEU_ERR_TYPE_UE, + "DCACHE ECCFAIL"); + break; + default: + pr_err("BEU: Unidentified cause\n"); + break; + } + writeb(0, ddata->regs + SIFIVE_BEU_CAUSE); + writeb(0, ddata->regs + SIFIVE_BEU_ACCRUED); + + return IRQ_HANDLED; +} + +static int beu_sifive_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct beu_sifive_ddata *ddata; + struct resource *res; + int ret, i; + + ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ddata->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(ddata->regs)) { + dev_err(dev, "Unable to map IO resources\n"); + return PTR_ERR(ddata->regs); + } + + ddata->irq = platform_get_irq(pdev, 0); + if (ddata->irq < 0) { + dev_err(dev, "Unable to find interrupt\n"); + ret = ddata->irq; + return ret; + } + + ret = devm_request_irq(dev, ddata->irq, beu_sifive_irq, 0, + dev_name(dev), ddata); + if (ret) { + dev_err(dev, "Unable to request IRQ\n"); + return ret; + } + + for (i = 0; i < MAX_ERR_EVENTS; i++) { + ret = beu_enable_event(ddata, err_events[i], PLIC_INTERRUPT); + if (ret) { + dev_err(dev, "Unable to register PLIC interrupt\n"); + return ret; + } + } + + platform_set_drvdata(pdev, ddata); + + return 0; +} + +static int beu_sifive_remove(struct platform_device *pdev) +{ + struct beu_sifive_ddata *ddata = platform_get_drvdata(pdev); + + /* Mask all error events */ + writeb(0, ddata->regs + SIFIVE_BEU_ENABLE); + writeb(0, ddata->regs + SIFIVE_BEU_PLIC_INTR); + writeb(0, ddata->regs + SIFIVE_BEU_LOCAL_INTR); + + return 0; +} + +static const struct of_device_id beu_sifive_of_match[] = { + { .compatible = "sifive,beu0" }, + {}, +}; +MODULE_DEVICE_TABLE(of, beu_sifive_of_match); + +static struct platform_driver beu_sifive_driver = { + .probe = beu_sifive_probe, + .remove = beu_sifive_remove, + .driver = { + .name = "beu-sifive", + .of_match_table = beu_sifive_of_match, + }, +}; +module_platform_driver(beu_sifive_driver); + +MODULE_DESCRIPTION("SiFive BEU driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/soc/sifive/sifive_beu.h b/include/soc/sifive/sifive_beu.h new file mode 100644 index 0000000..c2ab688 --- /dev/null +++ b/include/soc/sifive/sifive_beu.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * SiFive Bus Error unit header file + * + */ + +#ifndef __SOC_SIFIVE_BEU_H +#define __SOC_SIFIVE_BEU_H + +extern int register_sifive_beu_error_notifier(struct notifier_block *nb); +extern int unregister_sifive_beu_error_notifier(struct notifier_block *nb); + +#define SIFIVE_BEU_ERR_TYPE_CE 0 +#define SIFIVE_BEU_ERR_TYPE_UE 1 + +#endif /* __SOC_SIFIVE_BEU_H */ -- 2.7.4