1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * A64FX diag driver. |
4 | * Copyright (c) 2022 Fujitsu Ltd. |
5 | */ |
6 | |
7 | #include <linux/acpi.h> |
8 | #include <linux/interrupt.h> |
9 | #include <linux/irq.h> |
10 | #include <linux/module.h> |
11 | #include <linux/platform_device.h> |
12 | |
13 | #define A64FX_DIAG_IRQ 1 |
14 | #define BMC_DIAG_INTERRUPT_ENABLE 0x40 |
15 | #define BMC_DIAG_INTERRUPT_STATUS 0x44 |
16 | #define BMC_DIAG_INTERRUPT_MASK BIT(31) |
17 | |
18 | struct a64fx_diag_priv { |
19 | void __iomem *mmsc_reg_base; |
20 | int irq; |
21 | bool has_nmi; |
22 | }; |
23 | |
24 | static irqreturn_t a64fx_diag_handler_nmi(int irq, void *dev_id) |
25 | { |
26 | nmi_panic(NULL, msg: "a64fx_diag: interrupt received\n" ); |
27 | |
28 | return IRQ_HANDLED; |
29 | } |
30 | |
31 | static irqreturn_t a64fx_diag_handler_irq(int irq, void *dev_id) |
32 | { |
33 | panic(fmt: "a64fx_diag: interrupt received\n" ); |
34 | |
35 | return IRQ_HANDLED; |
36 | } |
37 | |
38 | static void a64fx_diag_interrupt_clear(struct a64fx_diag_priv *priv) |
39 | { |
40 | void __iomem *diag_status_reg_addr; |
41 | u32 mmsc; |
42 | |
43 | diag_status_reg_addr = priv->mmsc_reg_base + BMC_DIAG_INTERRUPT_STATUS; |
44 | mmsc = readl(addr: diag_status_reg_addr); |
45 | if (mmsc & BMC_DIAG_INTERRUPT_MASK) |
46 | writel(BMC_DIAG_INTERRUPT_MASK, addr: diag_status_reg_addr); |
47 | } |
48 | |
49 | static void a64fx_diag_interrupt_enable(struct a64fx_diag_priv *priv) |
50 | { |
51 | void __iomem *diag_enable_reg_addr; |
52 | u32 mmsc; |
53 | |
54 | diag_enable_reg_addr = priv->mmsc_reg_base + BMC_DIAG_INTERRUPT_ENABLE; |
55 | mmsc = readl(addr: diag_enable_reg_addr); |
56 | if (!(mmsc & BMC_DIAG_INTERRUPT_MASK)) { |
57 | mmsc |= BMC_DIAG_INTERRUPT_MASK; |
58 | writel(val: mmsc, addr: diag_enable_reg_addr); |
59 | } |
60 | } |
61 | |
62 | static void a64fx_diag_interrupt_disable(struct a64fx_diag_priv *priv) |
63 | { |
64 | void __iomem *diag_enable_reg_addr; |
65 | u32 mmsc; |
66 | |
67 | diag_enable_reg_addr = priv->mmsc_reg_base + BMC_DIAG_INTERRUPT_ENABLE; |
68 | mmsc = readl(addr: diag_enable_reg_addr); |
69 | if (mmsc & BMC_DIAG_INTERRUPT_MASK) { |
70 | mmsc &= ~BMC_DIAG_INTERRUPT_MASK; |
71 | writel(val: mmsc, addr: diag_enable_reg_addr); |
72 | } |
73 | } |
74 | |
75 | static int a64fx_diag_probe(struct platform_device *pdev) |
76 | { |
77 | struct device *dev = &pdev->dev; |
78 | struct a64fx_diag_priv *priv; |
79 | unsigned long irq_flags; |
80 | int ret; |
81 | |
82 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
83 | if (priv == NULL) |
84 | return -ENOMEM; |
85 | |
86 | priv->mmsc_reg_base = devm_platform_ioremap_resource(pdev, index: 0); |
87 | if (IS_ERR(ptr: priv->mmsc_reg_base)) |
88 | return PTR_ERR(ptr: priv->mmsc_reg_base); |
89 | |
90 | priv->irq = platform_get_irq(pdev, A64FX_DIAG_IRQ); |
91 | if (priv->irq < 0) |
92 | return priv->irq; |
93 | |
94 | platform_set_drvdata(pdev, data: priv); |
95 | |
96 | irq_flags = IRQF_PERCPU | IRQF_NOBALANCING | IRQF_NO_AUTOEN | |
97 | IRQF_NO_THREAD; |
98 | ret = request_nmi(irq: priv->irq, handler: &a64fx_diag_handler_nmi, flags: irq_flags, |
99 | name: "a64fx_diag_nmi" , NULL); |
100 | if (ret) { |
101 | ret = request_irq(irq: priv->irq, handler: &a64fx_diag_handler_irq, |
102 | flags: irq_flags, name: "a64fx_diag_irq" , NULL); |
103 | if (ret) { |
104 | dev_err(dev, "cannot register IRQ %d\n" , ret); |
105 | return ret; |
106 | } |
107 | enable_irq(irq: priv->irq); |
108 | } else { |
109 | enable_nmi(irq: priv->irq); |
110 | priv->has_nmi = true; |
111 | } |
112 | |
113 | a64fx_diag_interrupt_clear(priv); |
114 | a64fx_diag_interrupt_enable(priv); |
115 | |
116 | return 0; |
117 | } |
118 | |
119 | static void a64fx_diag_remove(struct platform_device *pdev) |
120 | { |
121 | struct a64fx_diag_priv *priv = platform_get_drvdata(pdev); |
122 | |
123 | a64fx_diag_interrupt_disable(priv); |
124 | a64fx_diag_interrupt_clear(priv); |
125 | |
126 | if (priv->has_nmi) |
127 | free_nmi(irq: priv->irq, NULL); |
128 | else |
129 | free_irq(priv->irq, NULL); |
130 | } |
131 | |
132 | static const struct acpi_device_id a64fx_diag_acpi_match[] = { |
133 | { "FUJI2007" , 0 }, |
134 | { }, |
135 | }; |
136 | MODULE_DEVICE_TABLE(acpi, a64fx_diag_acpi_match); |
137 | |
138 | |
139 | static struct platform_driver a64fx_diag_driver = { |
140 | .driver = { |
141 | .name = "a64fx_diag_driver" , |
142 | .acpi_match_table = ACPI_PTR(a64fx_diag_acpi_match), |
143 | }, |
144 | .probe = a64fx_diag_probe, |
145 | .remove_new = a64fx_diag_remove, |
146 | }; |
147 | |
148 | module_platform_driver(a64fx_diag_driver); |
149 | |
150 | MODULE_AUTHOR("Hitomi Hasegawa <hasegawa-hitomi@fujitsu.com>" ); |
151 | MODULE_DESCRIPTION("A64FX diag driver" ); |
152 | |