1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright 2011-2012 Calxeda, Inc.
4  */
5 #include <linux/types.h>
6 #include <linux/kernel.h>
7 #include <linux/ctype.h>
8 #include <linux/edac.h>
9 #include <linux/interrupt.h>
10 #include <linux/of.h>
11 #include <linux/of_device.h>
12 #include <linux/platform_device.h>
13 
14 #include "edac_module.h"
15 
16 #define SR_CLR_SB_ECC_INTR	0x0
17 #define SR_CLR_DB_ECC_INTR	0x4
18 
19 struct hb_l2_drvdata {
20 	void __iomem *base;
21 	int sb_irq;
22 	int db_irq;
23 };
24 
25 static irqreturn_t highbank_l2_err_handler(int irq, void *dev_id)
26 {
27 	struct edac_device_ctl_info *dci = dev_id;
28 	struct hb_l2_drvdata *drvdata = dci->pvt_info;
29 
30 	if (irq == drvdata->sb_irq) {
31 		writel(1, drvdata->base + SR_CLR_SB_ECC_INTR);
32 		edac_device_handle_ce(dci, 0, 0, dci->ctl_name);
33 	}
34 	if (irq == drvdata->db_irq) {
35 		writel(1, drvdata->base + SR_CLR_DB_ECC_INTR);
36 		edac_device_handle_ue(dci, 0, 0, dci->ctl_name);
37 	}
38 
39 	return IRQ_HANDLED;
40 }
41 
42 static const struct of_device_id hb_l2_err_of_match[] = {
43 	{ .compatible = "calxeda,hb-sregs-l2-ecc", },
44 	{},
45 };
46 MODULE_DEVICE_TABLE(of, hb_l2_err_of_match);
47 
48 static int highbank_l2_err_probe(struct platform_device *pdev)
49 {
50 	const struct of_device_id *id;
51 	struct edac_device_ctl_info *dci;
52 	struct hb_l2_drvdata *drvdata;
53 	struct resource *r;
54 	int res = 0;
55 
56 	dci = edac_device_alloc_ctl_info(sizeof(*drvdata), "cpu",
57 		1, "L", 1, 2, NULL, 0, 0);
58 	if (!dci)
59 		return -ENOMEM;
60 
61 	drvdata = dci->pvt_info;
62 	dci->dev = &pdev->dev;
63 	platform_set_drvdata(pdev, dci);
64 
65 	if (!devres_open_group(&pdev->dev, NULL, GFP_KERNEL))
66 		return -ENOMEM;
67 
68 	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
69 	if (!r) {
70 		dev_err(&pdev->dev, "Unable to get mem resource\n");
71 		res = -ENODEV;
72 		goto err;
73 	}
74 
75 	if (!devm_request_mem_region(&pdev->dev, r->start,
76 				     resource_size(r), dev_name(&pdev->dev))) {
77 		dev_err(&pdev->dev, "Error while requesting mem region\n");
78 		res = -EBUSY;
79 		goto err;
80 	}
81 
82 	drvdata->base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
83 	if (!drvdata->base) {
84 		dev_err(&pdev->dev, "Unable to map regs\n");
85 		res = -ENOMEM;
86 		goto err;
87 	}
88 
89 	id = of_match_device(hb_l2_err_of_match, &pdev->dev);
90 	dci->mod_name = pdev->dev.driver->name;
91 	dci->ctl_name = id ? id->compatible : "unknown";
92 	dci->dev_name = dev_name(&pdev->dev);
93 
94 	if (edac_device_add_device(dci))
95 		goto err;
96 
97 	drvdata->db_irq = platform_get_irq(pdev, 0);
98 	res = devm_request_irq(&pdev->dev, drvdata->db_irq,
99 			       highbank_l2_err_handler,
100 			       0, dev_name(&pdev->dev), dci);
101 	if (res < 0)
102 		goto err2;
103 
104 	drvdata->sb_irq = platform_get_irq(pdev, 1);
105 	res = devm_request_irq(&pdev->dev, drvdata->sb_irq,
106 			       highbank_l2_err_handler,
107 			       0, dev_name(&pdev->dev), dci);
108 	if (res < 0)
109 		goto err2;
110 
111 	devres_close_group(&pdev->dev, NULL);
112 	return 0;
113 err2:
114 	edac_device_del_device(&pdev->dev);
115 err:
116 	devres_release_group(&pdev->dev, NULL);
117 	edac_device_free_ctl_info(dci);
118 	return res;
119 }
120 
121 static int highbank_l2_err_remove(struct platform_device *pdev)
122 {
123 	struct edac_device_ctl_info *dci = platform_get_drvdata(pdev);
124 
125 	edac_device_del_device(&pdev->dev);
126 	edac_device_free_ctl_info(dci);
127 	return 0;
128 }
129 
130 static struct platform_driver highbank_l2_edac_driver = {
131 	.probe = highbank_l2_err_probe,
132 	.remove = highbank_l2_err_remove,
133 	.driver = {
134 		.name = "hb_l2_edac",
135 		.of_match_table = hb_l2_err_of_match,
136 	},
137 };
138 
139 module_platform_driver(highbank_l2_edac_driver);
140 
141 MODULE_LICENSE("GPL v2");
142 MODULE_AUTHOR("Calxeda, Inc.");
143 MODULE_DESCRIPTION("EDAC Driver for Calxeda Highbank L2 Cache");
144