Merge tag 'nfs-for-6.4-1' of git://git.linux-nfs.org/projects/anna/linux-nfs
[linux-block.git] / arch / powerpc / sysdev / pmi.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * pmi driver
4  *
5  * (C) Copyright IBM Deutschland Entwicklung GmbH 2005
6  *
7  * PMI (Platform Management Interrupt) is a way to communicate
8  * with the BMC (Baseboard Management Controller) via interrupts.
9  * Unlike IPMI it is bidirectional and has a low latency.
10  *
11  * Author: Christian Krafft <krafft@de.ibm.com>
12  */
13
14 #include <linux/interrupt.h>
15 #include <linux/slab.h>
16 #include <linux/completion.h>
17 #include <linux/spinlock.h>
18 #include <linux/module.h>
19 #include <linux/workqueue.h>
20 #include <linux/of_address.h>
21 #include <linux/of_device.h>
22 #include <linux/of_irq.h>
23 #include <linux/of_platform.h>
24
25 #include <asm/io.h>
26 #include <asm/pmi.h>
27
28 struct pmi_data {
29         struct list_head        handler;
30         spinlock_t              handler_spinlock;
31         spinlock_t              pmi_spinlock;
32         struct mutex            msg_mutex;
33         pmi_message_t           msg;
34         struct completion       *completion;
35         struct platform_device  *dev;
36         int                     irq;
37         u8 __iomem              *pmi_reg;
38         struct work_struct      work;
39 };
40
41 static struct pmi_data *data;
42
43 static irqreturn_t pmi_irq_handler(int irq, void *dev_id)
44 {
45         u8 type;
46         int rc;
47
48         spin_lock(&data->pmi_spinlock);
49
50         type = ioread8(data->pmi_reg + PMI_READ_TYPE);
51         pr_debug("pmi: got message of type %d\n", type);
52
53         if (type & PMI_ACK && !data->completion) {
54                 printk(KERN_WARNING "pmi: got unexpected ACK message.\n");
55                 rc = -EIO;
56                 goto unlock;
57         }
58
59         if (data->completion && !(type & PMI_ACK)) {
60                 printk(KERN_WARNING "pmi: expected ACK, but got %d\n", type);
61                 rc = -EIO;
62                 goto unlock;
63         }
64
65         data->msg.type = type;
66         data->msg.data0 = ioread8(data->pmi_reg + PMI_READ_DATA0);
67         data->msg.data1 = ioread8(data->pmi_reg + PMI_READ_DATA1);
68         data->msg.data2 = ioread8(data->pmi_reg + PMI_READ_DATA2);
69         rc = 0;
70 unlock:
71         spin_unlock(&data->pmi_spinlock);
72
73         if (rc == -EIO) {
74                 rc = IRQ_HANDLED;
75                 goto out;
76         }
77
78         if (data->msg.type & PMI_ACK) {
79                 complete(data->completion);
80                 rc = IRQ_HANDLED;
81                 goto out;
82         }
83
84         schedule_work(&data->work);
85
86         rc = IRQ_HANDLED;
87 out:
88         return rc;
89 }
90
91
92 static const struct of_device_id pmi_match[] = {
93         { .type = "ibm,pmi", .name = "ibm,pmi" },
94         { .type = "ibm,pmi" },
95         {},
96 };
97
98 MODULE_DEVICE_TABLE(of, pmi_match);
99
100 static void pmi_notify_handlers(struct work_struct *work)
101 {
102         struct pmi_handler *handler;
103
104         spin_lock(&data->handler_spinlock);
105         list_for_each_entry(handler, &data->handler, node) {
106                 pr_debug("pmi: notifying handler %p\n", handler);
107                 if (handler->type == data->msg.type)
108                         handler->handle_pmi_message(data->msg);
109         }
110         spin_unlock(&data->handler_spinlock);
111 }
112
113 static int pmi_of_probe(struct platform_device *dev)
114 {
115         struct device_node *np = dev->dev.of_node;
116         int rc;
117
118         if (data) {
119                 printk(KERN_ERR "pmi: driver has already been initialized.\n");
120                 rc = -EBUSY;
121                 goto out;
122         }
123
124         data = kzalloc(sizeof(struct pmi_data), GFP_KERNEL);
125         if (!data) {
126                 printk(KERN_ERR "pmi: could not allocate memory.\n");
127                 rc = -ENOMEM;
128                 goto out;
129         }
130
131         data->pmi_reg = of_iomap(np, 0);
132         if (!data->pmi_reg) {
133                 printk(KERN_ERR "pmi: invalid register address.\n");
134                 rc = -EFAULT;
135                 goto error_cleanup_data;
136         }
137
138         INIT_LIST_HEAD(&data->handler);
139
140         mutex_init(&data->msg_mutex);
141         spin_lock_init(&data->pmi_spinlock);
142         spin_lock_init(&data->handler_spinlock);
143
144         INIT_WORK(&data->work, pmi_notify_handlers);
145
146         data->dev = dev;
147
148         data->irq = irq_of_parse_and_map(np, 0);
149         if (!data->irq) {
150                 printk(KERN_ERR "pmi: invalid interrupt.\n");
151                 rc = -EFAULT;
152                 goto error_cleanup_iomap;
153         }
154
155         rc = request_irq(data->irq, pmi_irq_handler, 0, "pmi", NULL);
156         if (rc) {
157                 printk(KERN_ERR "pmi: can't request IRQ %d: returned %d\n",
158                                 data->irq, rc);
159                 goto error_cleanup_iomap;
160         }
161
162         printk(KERN_INFO "pmi: found pmi device at addr %p.\n", data->pmi_reg);
163
164         goto out;
165
166 error_cleanup_iomap:
167         iounmap(data->pmi_reg);
168
169 error_cleanup_data:
170         kfree(data);
171
172 out:
173         return rc;
174 }
175
176 static int pmi_of_remove(struct platform_device *dev)
177 {
178         struct pmi_handler *handler, *tmp;
179
180         free_irq(data->irq, NULL);
181         iounmap(data->pmi_reg);
182
183         spin_lock(&data->handler_spinlock);
184
185         list_for_each_entry_safe(handler, tmp, &data->handler, node)
186                 list_del(&handler->node);
187
188         spin_unlock(&data->handler_spinlock);
189
190         kfree(data);
191         data = NULL;
192
193         return 0;
194 }
195
196 static struct platform_driver pmi_of_platform_driver = {
197         .probe          = pmi_of_probe,
198         .remove         = pmi_of_remove,
199         .driver = {
200                 .name = "pmi",
201                 .of_match_table = pmi_match,
202         },
203 };
204 module_platform_driver(pmi_of_platform_driver);
205
206 int pmi_send_message(pmi_message_t msg)
207 {
208         unsigned long flags;
209         DECLARE_COMPLETION_ONSTACK(completion);
210
211         if (!data)
212                 return -ENODEV;
213
214         mutex_lock(&data->msg_mutex);
215
216         data->msg = msg;
217         pr_debug("pmi_send_message: msg is %08x\n", *(u32*)&msg);
218
219         data->completion = &completion;
220
221         spin_lock_irqsave(&data->pmi_spinlock, flags);
222         iowrite8(msg.data0, data->pmi_reg + PMI_WRITE_DATA0);
223         iowrite8(msg.data1, data->pmi_reg + PMI_WRITE_DATA1);
224         iowrite8(msg.data2, data->pmi_reg + PMI_WRITE_DATA2);
225         iowrite8(msg.type, data->pmi_reg + PMI_WRITE_TYPE);
226         spin_unlock_irqrestore(&data->pmi_spinlock, flags);
227
228         pr_debug("pmi_send_message: wait for completion\n");
229
230         wait_for_completion_interruptible_timeout(data->completion,
231                                                   PMI_TIMEOUT);
232
233         data->completion = NULL;
234
235         mutex_unlock(&data->msg_mutex);
236
237         return 0;
238 }
239 EXPORT_SYMBOL_GPL(pmi_send_message);
240
241 int pmi_register_handler(struct pmi_handler *handler)
242 {
243         if (!data)
244                 return -ENODEV;
245
246         spin_lock(&data->handler_spinlock);
247         list_add_tail(&handler->node, &data->handler);
248         spin_unlock(&data->handler_spinlock);
249
250         return 0;
251 }
252 EXPORT_SYMBOL_GPL(pmi_register_handler);
253
254 void pmi_unregister_handler(struct pmi_handler *handler)
255 {
256         if (!data)
257                 return;
258
259         pr_debug("pmi: unregistering handler %p\n", handler);
260
261         spin_lock(&data->handler_spinlock);
262         list_del(&handler->node);
263         spin_unlock(&data->handler_spinlock);
264 }
265 EXPORT_SYMBOL_GPL(pmi_unregister_handler);
266
267 MODULE_LICENSE("GPL");
268 MODULE_AUTHOR("Christian Krafft <krafft@de.ibm.com>");
269 MODULE_DESCRIPTION("IBM Platform Management Interrupt driver");