1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * SiFive Platform EDAC Driver |
4 | * |
5 | * Copyright (C) 2018-2022 SiFive, Inc. |
6 | * |
7 | * This driver is partially based on octeon_edac-pc.c |
8 | * |
9 | */ |
10 | #include <linux/edac.h> |
11 | #include <linux/platform_device.h> |
12 | #include "edac_module.h" |
13 | #include <soc/sifive/sifive_ccache.h> |
14 | |
15 | #define DRVNAME "sifive_edac" |
16 | |
17 | struct sifive_edac_priv { |
18 | struct notifier_block notifier; |
19 | struct edac_device_ctl_info *dci; |
20 | }; |
21 | |
22 | /* |
23 | * EDAC error callback |
24 | * |
25 | * @event: non-zero if unrecoverable. |
26 | */ |
27 | static |
28 | int ecc_err_event(struct notifier_block *this, unsigned long event, void *ptr) |
29 | { |
30 | const char *msg = (char *)ptr; |
31 | struct sifive_edac_priv *p; |
32 | |
33 | p = container_of(this, struct sifive_edac_priv, notifier); |
34 | |
35 | if (event == SIFIVE_CCACHE_ERR_TYPE_UE) |
36 | edac_device_handle_ue(edac_dev: p->dci, inst_nr: 0, block_nr: 0, msg); |
37 | else if (event == SIFIVE_CCACHE_ERR_TYPE_CE) |
38 | edac_device_handle_ce(edac_dev: p->dci, inst_nr: 0, block_nr: 0, msg); |
39 | |
40 | return NOTIFY_OK; |
41 | } |
42 | |
43 | static int ecc_register(struct platform_device *pdev) |
44 | { |
45 | struct sifive_edac_priv *p; |
46 | |
47 | p = devm_kzalloc(dev: &pdev->dev, size: sizeof(*p), GFP_KERNEL); |
48 | if (!p) |
49 | return -ENOMEM; |
50 | |
51 | p->notifier.notifier_call = ecc_err_event; |
52 | platform_set_drvdata(pdev, data: p); |
53 | |
54 | p->dci = edac_device_alloc_ctl_info(sizeof_private: 0, edac_device_name: "sifive_ecc" , nr_instances: 1, edac_block_name: "sifive_ecc" , |
55 | nr_blocks: 1, offset_value: 1, NULL, nr_attribs: 0, |
56 | device_index: edac_device_alloc_index()); |
57 | if (!p->dci) |
58 | return -ENOMEM; |
59 | |
60 | p->dci->dev = &pdev->dev; |
61 | p->dci->mod_name = "Sifive ECC Manager" ; |
62 | p->dci->ctl_name = dev_name(dev: &pdev->dev); |
63 | p->dci->dev_name = dev_name(dev: &pdev->dev); |
64 | |
65 | if (edac_device_add_device(edac_dev: p->dci)) { |
66 | dev_err(p->dci->dev, "failed to register with EDAC core\n" ); |
67 | goto err; |
68 | } |
69 | |
70 | register_sifive_ccache_error_notifier(nb: &p->notifier); |
71 | |
72 | return 0; |
73 | |
74 | err: |
75 | edac_device_free_ctl_info(ctl_info: p->dci); |
76 | |
77 | return -ENXIO; |
78 | } |
79 | |
80 | static int ecc_unregister(struct platform_device *pdev) |
81 | { |
82 | struct sifive_edac_priv *p = platform_get_drvdata(pdev); |
83 | |
84 | unregister_sifive_ccache_error_notifier(nb: &p->notifier); |
85 | edac_device_del_device(dev: &pdev->dev); |
86 | edac_device_free_ctl_info(ctl_info: p->dci); |
87 | |
88 | return 0; |
89 | } |
90 | |
91 | static struct platform_device *sifive_pdev; |
92 | |
93 | static int __init sifive_edac_init(void) |
94 | { |
95 | int ret; |
96 | |
97 | sifive_pdev = platform_device_register_simple(DRVNAME, id: 0, NULL, num: 0); |
98 | if (IS_ERR(ptr: sifive_pdev)) |
99 | return PTR_ERR(ptr: sifive_pdev); |
100 | |
101 | ret = ecc_register(pdev: sifive_pdev); |
102 | if (ret) |
103 | platform_device_unregister(sifive_pdev); |
104 | |
105 | return ret; |
106 | } |
107 | |
108 | static void __exit sifive_edac_exit(void) |
109 | { |
110 | ecc_unregister(pdev: sifive_pdev); |
111 | platform_device_unregister(sifive_pdev); |
112 | } |
113 | |
114 | module_init(sifive_edac_init); |
115 | module_exit(sifive_edac_exit); |
116 | |
117 | MODULE_AUTHOR("SiFive Inc." ); |
118 | MODULE_DESCRIPTION("SiFive platform EDAC driver" ); |
119 | MODULE_LICENSE("GPL v2" ); |
120 | |