1
2
3
4
5
6
7
8
9
10#include <linux/cpuhotplug.h>
11#include <linux/firmware/xlnx-event-manager.h>
12#include <linux/firmware/xlnx-zynqmp.h>
13#include <linux/hashtable.h>
14#include <linux/interrupt.h>
15#include <linux/irq.h>
16#include <linux/irqdomain.h>
17#include <linux/module.h>
18#include <linux/of_irq.h>
19#include <linux/platform_device.h>
20#include <linux/slab.h>
21
22static DEFINE_PER_CPU_READ_MOSTLY(int, cpu_number1);
23
24static int virq_sgi;
25static int event_manager_availability = -EACCES;
26
27
28#define XLNX_EVENT_SGI_NUM (15)
29
30
31#define MAX_DRIVER_PER_EVENT (10U)
32
33
34#define REGISTERED_DRIVER_MAX_ORDER (7)
35
36#define MAX_BITS (32U)
37
38#define FIRMWARE_VERSION_MASK (0xFFFFU)
39#define REGISTER_NOTIFIER_FIRMWARE_VERSION (2U)
40
41static DEFINE_HASHTABLE(reg_driver_map, REGISTERED_DRIVER_MAX_ORDER);
42static int sgi_num = XLNX_EVENT_SGI_NUM;
43
44
45
46
47
48
49
50
51
52
53
54
55
56struct registered_event_data {
57 u64 key;
58 enum pm_api_cb_id cb_type;
59 void *agent_data;
60
61 event_cb_func_t eve_cb;
62 bool wake;
63 struct hlist_node hentry;
64};
65
66static bool xlnx_is_error_event(const u32 node_id)
67{
68 if (node_id == EVENT_ERROR_PMC_ERR1 ||
69 node_id == EVENT_ERROR_PMC_ERR2 ||
70 node_id == EVENT_ERROR_PSM_ERR1 ||
71 node_id == EVENT_ERROR_PSM_ERR2)
72 return true;
73
74 return false;
75}
76
77static int xlnx_add_cb_for_notify_event(const u32 node_id, const u32 event, const bool wake,
78 event_cb_func_t cb_fun, void *data)
79{
80 u64 key = 0;
81 struct registered_event_data *eve_data;
82
83 key = ((u64)node_id << 32U) | (u64)event;
84
85 hash_for_each_possible(reg_driver_map, eve_data, hentry, key) {
86 if (eve_data->key == key) {
87 pr_err("Found as already registered\n");
88 return -EINVAL;
89 }
90 }
91
92
93 eve_data = kmalloc(sizeof(*eve_data), GFP_KERNEL);
94 if (!eve_data)
95 return -ENOMEM;
96
97 eve_data->key = key;
98 eve_data->cb_type = PM_NOTIFY_CB;
99 eve_data->eve_cb = cb_fun;
100 eve_data->wake = wake;
101 eve_data->agent_data = data;
102
103 hash_add(reg_driver_map, &eve_data->hentry, key);
104
105 return 0;
106}
107
108static int xlnx_add_cb_for_suspend(event_cb_func_t cb_fun, void *data)
109{
110 struct registered_event_data *eve_data;
111
112
113 hash_for_each_possible(reg_driver_map, eve_data, hentry, PM_INIT_SUSPEND_CB) {
114 if (eve_data->cb_type == PM_INIT_SUSPEND_CB) {
115 pr_err("Found as already registered\n");
116 return -EINVAL;
117 }
118 }
119
120
121 eve_data = kmalloc(sizeof(*eve_data), GFP_KERNEL);
122 if (!eve_data)
123 return -ENOMEM;
124
125 eve_data->key = 0;
126 eve_data->cb_type = PM_INIT_SUSPEND_CB;
127 eve_data->eve_cb = cb_fun;
128 eve_data->agent_data = data;
129
130 hash_add(reg_driver_map, &eve_data->hentry, PM_INIT_SUSPEND_CB);
131
132 return 0;
133}
134
135static int xlnx_remove_cb_for_suspend(event_cb_func_t cb_fun)
136{
137 bool is_callback_found = false;
138 struct registered_event_data *eve_data;
139
140
141 hash_for_each_possible(reg_driver_map, eve_data, hentry, PM_INIT_SUSPEND_CB) {
142 if (eve_data->cb_type == PM_INIT_SUSPEND_CB &&
143 eve_data->eve_cb == cb_fun) {
144 is_callback_found = true;
145
146 hash_del(&eve_data->hentry);
147 kfree(eve_data);
148 }
149 }
150 if (!is_callback_found) {
151 pr_warn("Didn't find any registered callback for suspend event\n");
152 return -EINVAL;
153 }
154
155 return 0;
156}
157
158static int xlnx_remove_cb_for_notify_event(const u32 node_id, const u32 event,
159 event_cb_func_t cb_fun)
160{
161 bool is_callback_found = false;
162 struct registered_event_data *eve_data;
163 u64 key = ((u64)node_id << 32U) | (u64)event;
164
165
166 hash_for_each_possible(reg_driver_map, eve_data, hentry, key) {
167 if (eve_data->key == key &&
168 eve_data->eve_cb == cb_fun) {
169 is_callback_found = true;
170
171 hash_del(&eve_data->hentry);
172 kfree(eve_data);
173 }
174 }
175 if (!is_callback_found) {
176 pr_warn("Didn't find any registered callback for 0x%x 0x%x\n",
177 node_id, event);
178 return -EINVAL;
179 }
180
181 return 0;
182}
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198int xlnx_register_event(const enum pm_api_cb_id cb_type, const u32 node_id, const u32 event,
199 const bool wake, event_cb_func_t cb_fun, void *data)
200{
201 int ret = 0;
202 u32 eve;
203 int pos;
204
205 if (event_manager_availability)
206 return event_manager_availability;
207
208 if (cb_type != PM_NOTIFY_CB && cb_type != PM_INIT_SUSPEND_CB) {
209 pr_err("%s() Unsupported Callback 0x%x\n", __func__, cb_type);
210 return -EINVAL;
211 }
212
213 if (!cb_fun)
214 return -EFAULT;
215
216 if (cb_type == PM_INIT_SUSPEND_CB) {
217 ret = xlnx_add_cb_for_suspend(cb_fun, data);
218 } else {
219 if (!xlnx_is_error_event(node_id)) {
220
221 ret = xlnx_add_cb_for_notify_event(node_id, event, wake, cb_fun, data);
222 } else {
223
224 for (pos = 0; pos < MAX_BITS; pos++) {
225 eve = event & (1 << pos);
226 if (!eve)
227 continue;
228
229
230 ret = xlnx_add_cb_for_notify_event(node_id, eve, wake, cb_fun,
231 data);
232
233 if (ret)
234 break;
235 }
236 if (ret) {
237
238 pos--;
239
240 for ( ; pos >= 0; pos--) {
241 eve = event & (1 << pos);
242 if (!eve)
243 continue;
244 xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun);
245 }
246 }
247 }
248
249 if (ret) {
250 pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, node_id,
251 event, ret);
252 return ret;
253 }
254
255
256 ret = zynqmp_pm_register_notifier(node_id, event, wake, true);
257 if (ret) {
258 pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, node_id,
259 event, ret);
260
261 if (xlnx_is_error_event(node_id)) {
262 for (pos = 0; pos < MAX_BITS; pos++) {
263 eve = event & (1 << pos);
264 if (!eve)
265 continue;
266 xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun);
267 }
268 } else {
269 xlnx_remove_cb_for_notify_event(node_id, event, cb_fun);
270 }
271 return ret;
272 }
273 }
274
275 return ret;
276}
277EXPORT_SYMBOL_GPL(xlnx_register_event);
278
279
280
281
282
283
284
285
286
287
288
289
290int xlnx_unregister_event(const enum pm_api_cb_id cb_type, const u32 node_id, const u32 event,
291 event_cb_func_t cb_fun)
292{
293 int ret;
294 u32 eve, pos;
295
296 if (event_manager_availability)
297 return event_manager_availability;
298
299 if (cb_type != PM_NOTIFY_CB && cb_type != PM_INIT_SUSPEND_CB) {
300 pr_err("%s() Unsupported Callback 0x%x\n", __func__, cb_type);
301 return -EINVAL;
302 }
303
304 if (!cb_fun)
305 return -EFAULT;
306
307 if (cb_type == PM_INIT_SUSPEND_CB) {
308 ret = xlnx_remove_cb_for_suspend(cb_fun);
309 } else {
310
311 if (!xlnx_is_error_event(node_id)) {
312 xlnx_remove_cb_for_notify_event(node_id, event, cb_fun);
313 } else {
314 for (pos = 0; pos < MAX_BITS; pos++) {
315 eve = event & (1 << pos);
316 if (!eve)
317 continue;
318
319 xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun);
320 }
321 }
322
323
324 ret = zynqmp_pm_register_notifier(node_id, event, false, false);
325 if (ret) {
326 pr_err("%s() failed for 0x%x and 0x%x: %d\n",
327 __func__, node_id, event, ret);
328 return ret;
329 }
330 }
331
332 return ret;
333}
334EXPORT_SYMBOL_GPL(xlnx_unregister_event);
335
336static void xlnx_call_suspend_cb_handler(const u32 *payload)
337{
338 bool is_callback_found = false;
339 struct registered_event_data *eve_data;
340 u32 cb_type = payload[0];
341
342
343 hash_for_each_possible(reg_driver_map, eve_data, hentry, cb_type) {
344 if (eve_data->cb_type == cb_type) {
345 eve_data->eve_cb(&payload[0], eve_data->agent_data);
346 is_callback_found = true;
347 }
348 }
349 if (!is_callback_found)
350 pr_warn("Didn't find any registered callback for suspend event\n");
351}
352
353static void xlnx_call_notify_cb_handler(const u32 *payload)
354{
355 bool is_callback_found = false;
356 struct registered_event_data *eve_data;
357 u64 key = ((u64)payload[1] << 32U) | (u64)payload[2];
358 int ret;
359
360
361 hash_for_each_possible(reg_driver_map, eve_data, hentry, key) {
362 if (eve_data->key == key) {
363 eve_data->eve_cb(&payload[0], eve_data->agent_data);
364 is_callback_found = true;
365
366
367 ret = zynqmp_pm_register_notifier(payload[1], payload[2],
368 eve_data->wake, true);
369 if (ret) {
370 pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__,
371 payload[1], payload[2], ret);
372
373 xlnx_remove_cb_for_notify_event(payload[1], payload[2],
374 eve_data->eve_cb);
375 }
376 }
377 }
378 if (!is_callback_found)
379 pr_warn("Didn't find any registered callback for 0x%x 0x%x\n",
380 payload[1], payload[2]);
381}
382
383static void xlnx_get_event_callback_data(u32 *buf)
384{
385 zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf);
386}
387
388static irqreturn_t xlnx_event_handler(int irq, void *dev_id)
389{
390 u32 cb_type, node_id, event, pos;
391 u32 payload[CB_MAX_PAYLOAD_SIZE] = {0};
392 u32 event_data[CB_MAX_PAYLOAD_SIZE] = {0};
393
394
395 xlnx_get_event_callback_data(payload);
396
397
398 cb_type = payload[0];
399
400 if (cb_type == PM_NOTIFY_CB) {
401 node_id = payload[1];
402 event = payload[2];
403 if (!xlnx_is_error_event(node_id)) {
404 xlnx_call_notify_cb_handler(payload);
405 } else {
406
407
408
409
410
411
412
413
414
415
416
417 memcpy(event_data, payload, (4 * CB_MAX_PAYLOAD_SIZE));
418
419 for (pos = 0; pos < MAX_BITS; pos++) {
420 if ((0 == (event & (1 << pos))))
421 continue;
422 event_data[2] = (event & (1 << pos));
423 xlnx_call_notify_cb_handler(event_data);
424 }
425 }
426 } else if (cb_type == PM_INIT_SUSPEND_CB) {
427 xlnx_call_suspend_cb_handler(payload);
428 } else {
429 pr_err("%s() Unsupported Callback %d\n", __func__, cb_type);
430 }
431
432 return IRQ_HANDLED;
433}
434
435static int xlnx_event_cpuhp_start(unsigned int cpu)
436{
437 enable_percpu_irq(virq_sgi, IRQ_TYPE_NONE);
438
439 return 0;
440}
441
442static int xlnx_event_cpuhp_down(unsigned int cpu)
443{
444 disable_percpu_irq(virq_sgi);
445
446 return 0;
447}
448
449static void xlnx_disable_percpu_irq(void *data)
450{
451 disable_percpu_irq(virq_sgi);
452}
453
454static int xlnx_event_init_sgi(struct platform_device *pdev)
455{
456 int ret = 0;
457 int cpu = smp_processor_id();
458
459
460
461
462
463
464
465 struct irq_domain *domain;
466 struct irq_fwspec sgi_fwspec;
467 struct device_node *interrupt_parent = NULL;
468 struct device *parent = pdev->dev.parent;
469
470
471 interrupt_parent = of_irq_find_parent(parent->of_node);
472 if (!interrupt_parent) {
473 dev_err(&pdev->dev, "Failed to find property for Interrupt parent\n");
474 return -EINVAL;
475 }
476
477
478 domain = irq_find_host(interrupt_parent);
479 of_node_put(interrupt_parent);
480
481
482 sgi_fwspec.fwnode = domain->fwnode;
483
484
485
486
487
488 sgi_fwspec.param_count = 1;
489
490
491 sgi_fwspec.param[0] = sgi_num;
492 virq_sgi = irq_create_fwspec_mapping(&sgi_fwspec);
493
494 per_cpu(cpu_number1, cpu) = cpu;
495 ret = request_percpu_irq(virq_sgi, xlnx_event_handler, "xlnx_event_mgmt",
496 &cpu_number1);
497 WARN_ON(ret);
498 if (ret) {
499 irq_dispose_mapping(virq_sgi);
500 return ret;
501 }
502
503 irq_to_desc(virq_sgi);
504 irq_set_status_flags(virq_sgi, IRQ_PER_CPU);
505
506 return ret;
507}
508
509static void xlnx_event_cleanup_sgi(struct platform_device *pdev)
510{
511 int cpu = smp_processor_id();
512
513 per_cpu(cpu_number1, cpu) = cpu;
514
515 cpuhp_remove_state(CPUHP_AP_ONLINE_DYN);
516
517 on_each_cpu(xlnx_disable_percpu_irq, NULL, 1);
518
519 irq_clear_status_flags(virq_sgi, IRQ_PER_CPU);
520 free_percpu_irq(virq_sgi, &cpu_number1);
521 irq_dispose_mapping(virq_sgi);
522}
523
524static int xlnx_event_manager_probe(struct platform_device *pdev)
525{
526 int ret;
527
528 ret = zynqmp_pm_feature(PM_REGISTER_NOTIFIER);
529 if (ret < 0) {
530 dev_err(&pdev->dev, "Feature check failed with %d\n", ret);
531 return ret;
532 }
533
534 if ((ret & FIRMWARE_VERSION_MASK) <
535 REGISTER_NOTIFIER_FIRMWARE_VERSION) {
536 dev_err(&pdev->dev, "Register notifier version error. Expected Firmware: v%d - Found: v%d\n",
537 REGISTER_NOTIFIER_FIRMWARE_VERSION,
538 ret & FIRMWARE_VERSION_MASK);
539 return -EOPNOTSUPP;
540 }
541
542
543 ret = xlnx_event_init_sgi(pdev);
544 if (ret) {
545 dev_err(&pdev->dev, "SGI Init has been failed with %d\n", ret);
546 return ret;
547 }
548
549
550 cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "soc/event:starting",
551 xlnx_event_cpuhp_start, xlnx_event_cpuhp_down);
552
553 ret = zynqmp_pm_invoke_fn(PM_IOCTL, 0, IOCTL_REGISTER_SGI, sgi_num,
554 0, NULL);
555 if (ret) {
556 dev_err(&pdev->dev, "SGI %d Registration over TF-A failed with %d\n", sgi_num, ret);
557 xlnx_event_cleanup_sgi(pdev);
558 return ret;
559 }
560
561 event_manager_availability = 0;
562
563 dev_info(&pdev->dev, "SGI %d Registered over TF-A\n", sgi_num);
564 dev_info(&pdev->dev, "Xilinx Event Management driver probed\n");
565
566 return ret;
567}
568
569static int xlnx_event_manager_remove(struct platform_device *pdev)
570{
571 int i;
572 struct registered_event_data *eve_data;
573 struct hlist_node *tmp;
574 int ret;
575
576 hash_for_each_safe(reg_driver_map, i, tmp, eve_data, hentry) {
577 hash_del(&eve_data->hentry);
578 kfree(eve_data);
579 }
580
581 ret = zynqmp_pm_invoke_fn(PM_IOCTL, 0, IOCTL_REGISTER_SGI, 0, 1, NULL);
582 if (ret)
583 dev_err(&pdev->dev, "SGI unregistration over TF-A failed with %d\n", ret);
584
585 xlnx_event_cleanup_sgi(pdev);
586
587 event_manager_availability = -EACCES;
588
589 return ret;
590}
591
592static struct platform_driver xlnx_event_manager_driver = {
593 .probe = xlnx_event_manager_probe,
594 .remove = xlnx_event_manager_remove,
595 .driver = {
596 .name = "xlnx_event_manager",
597 },
598};
599module_param(sgi_num, uint, 0);
600module_platform_driver(xlnx_event_manager_driver);
601