18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * SiFive Platform EDAC Driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2018-2019 SiFive, Inc.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * This driver is partially based on octeon_edac-pc.c
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci#include <linux/edac.h>
118c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
128c2ecf20Sopenharmony_ci#include "edac_module.h"
138c2ecf20Sopenharmony_ci#include <soc/sifive/sifive_l2_cache.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#define DRVNAME "sifive_edac"
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_cistruct sifive_edac_priv {
188c2ecf20Sopenharmony_ci	struct notifier_block notifier;
198c2ecf20Sopenharmony_ci	struct edac_device_ctl_info *dci;
208c2ecf20Sopenharmony_ci};
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci/**
238c2ecf20Sopenharmony_ci * EDAC error callback
248c2ecf20Sopenharmony_ci *
258c2ecf20Sopenharmony_ci * @event: non-zero if unrecoverable.
268c2ecf20Sopenharmony_ci */
278c2ecf20Sopenharmony_cistatic
288c2ecf20Sopenharmony_ciint ecc_err_event(struct notifier_block *this, unsigned long event, void *ptr)
298c2ecf20Sopenharmony_ci{
308c2ecf20Sopenharmony_ci	const char *msg = (char *)ptr;
318c2ecf20Sopenharmony_ci	struct sifive_edac_priv *p;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	p = container_of(this, struct sifive_edac_priv, notifier);
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	if (event == SIFIVE_L2_ERR_TYPE_UE)
368c2ecf20Sopenharmony_ci		edac_device_handle_ue(p->dci, 0, 0, msg);
378c2ecf20Sopenharmony_ci	else if (event == SIFIVE_L2_ERR_TYPE_CE)
388c2ecf20Sopenharmony_ci		edac_device_handle_ce(p->dci, 0, 0, msg);
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	return NOTIFY_OK;
418c2ecf20Sopenharmony_ci}
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic int ecc_register(struct platform_device *pdev)
448c2ecf20Sopenharmony_ci{
458c2ecf20Sopenharmony_ci	struct sifive_edac_priv *p;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
488c2ecf20Sopenharmony_ci	if (!p)
498c2ecf20Sopenharmony_ci		return -ENOMEM;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	p->notifier.notifier_call = ecc_err_event;
528c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, p);
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	p->dci = edac_device_alloc_ctl_info(0, "sifive_ecc", 1, "sifive_ecc",
558c2ecf20Sopenharmony_ci					    1, 1, NULL, 0,
568c2ecf20Sopenharmony_ci					    edac_device_alloc_index());
578c2ecf20Sopenharmony_ci	if (!p->dci)
588c2ecf20Sopenharmony_ci		return -ENOMEM;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	p->dci->dev = &pdev->dev;
618c2ecf20Sopenharmony_ci	p->dci->mod_name = "Sifive ECC Manager";
628c2ecf20Sopenharmony_ci	p->dci->ctl_name = dev_name(&pdev->dev);
638c2ecf20Sopenharmony_ci	p->dci->dev_name = dev_name(&pdev->dev);
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	if (edac_device_add_device(p->dci)) {
668c2ecf20Sopenharmony_ci		dev_err(p->dci->dev, "failed to register with EDAC core\n");
678c2ecf20Sopenharmony_ci		goto err;
688c2ecf20Sopenharmony_ci	}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	register_sifive_l2_error_notifier(&p->notifier);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	return 0;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cierr:
758c2ecf20Sopenharmony_ci	edac_device_free_ctl_info(p->dci);
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	return -ENXIO;
788c2ecf20Sopenharmony_ci}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_cistatic int ecc_unregister(struct platform_device *pdev)
818c2ecf20Sopenharmony_ci{
828c2ecf20Sopenharmony_ci	struct sifive_edac_priv *p = platform_get_drvdata(pdev);
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	unregister_sifive_l2_error_notifier(&p->notifier);
858c2ecf20Sopenharmony_ci	edac_device_del_device(&pdev->dev);
868c2ecf20Sopenharmony_ci	edac_device_free_ctl_info(p->dci);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	return 0;
898c2ecf20Sopenharmony_ci}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic struct platform_device *sifive_pdev;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistatic int __init sifive_edac_init(void)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	int ret;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	sifive_pdev = platform_device_register_simple(DRVNAME, 0, NULL, 0);
988c2ecf20Sopenharmony_ci	if (IS_ERR(sifive_pdev))
998c2ecf20Sopenharmony_ci		return PTR_ERR(sifive_pdev);
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	ret = ecc_register(sifive_pdev);
1028c2ecf20Sopenharmony_ci	if (ret)
1038c2ecf20Sopenharmony_ci		platform_device_unregister(sifive_pdev);
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	return ret;
1068c2ecf20Sopenharmony_ci}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cistatic void __exit sifive_edac_exit(void)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	ecc_unregister(sifive_pdev);
1118c2ecf20Sopenharmony_ci	platform_device_unregister(sifive_pdev);
1128c2ecf20Sopenharmony_ci}
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_cimodule_init(sifive_edac_init);
1158c2ecf20Sopenharmony_cimodule_exit(sifive_edac_exit);
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ciMODULE_AUTHOR("SiFive Inc.");
1188c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SiFive platform EDAC driver");
1198c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
120