1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Xilinx Event Management Driver
4 *
5 * Copyright (C) 2021 Xilinx, Inc.
6 *
7 * Abhyuday Godhasara <abhyuday.godhasara@xilinx.com>
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
22 static DEFINE_PER_CPU_READ_MOSTLY(int, cpu_number1);
23
24 static int virq_sgi;
25 static int event_manager_availability = -EACCES;
26
27 /* SGI number used for Event management driver */
28 #define XLNX_EVENT_SGI_NUM (15)
29
30 /* Max number of driver can register for same event */
31 #define MAX_DRIVER_PER_EVENT (10U)
32
33 /* Max HashMap Order for PM API feature check (1<<7 = 128) */
34 #define REGISTERED_DRIVER_MAX_ORDER (7)
35
36 #define MAX_BITS (32U) /* Number of bits available for error mask */
37
38 #define FIRMWARE_VERSION_MASK (0xFFFFU)
39 #define REGISTER_NOTIFIER_FIRMWARE_VERSION (2U)
40
41 static DEFINE_HASHTABLE(reg_driver_map, REGISTERED_DRIVER_MAX_ORDER);
42 static int sgi_num = XLNX_EVENT_SGI_NUM;
43
44 static bool is_need_to_unregister;
45
46 /**
47 * struct agent_cb - Registered callback function and private data.
48 * @agent_data: Data passed back to handler function.
49 * @eve_cb: Function pointer to store the callback function.
50 * @list: member to create list.
51 */
52 struct agent_cb {
53 void *agent_data;
54 event_cb_func_t eve_cb;
55 struct list_head list;
56 };
57
58 /**
59 * struct registered_event_data - Registered Event Data.
60 * @key: key is the combine id(Node-Id | Event-Id) of type u64
61 * where upper u32 for Node-Id and lower u32 for Event-Id,
62 * And this used as key to index into hashmap.
63 * @cb_type: Type of Api callback, like PM_NOTIFY_CB, etc.
64 * @wake: If this flag set, firmware will wake up processor if is
65 * in sleep or power down state.
66 * @cb_list_head: Head of call back data list which contain the information
67 * about registered handler and private data.
68 * @hentry: hlist_node that hooks this entry into hashtable.
69 */
70 struct registered_event_data {
71 u64 key;
72 enum pm_api_cb_id cb_type;
73 bool wake;
74 struct list_head cb_list_head;
75 struct hlist_node hentry;
76 };
77
xlnx_is_error_event(const u32 node_id)78 static bool xlnx_is_error_event(const u32 node_id)
79 {
80 if (node_id == EVENT_ERROR_PMC_ERR1 ||
81 node_id == EVENT_ERROR_PMC_ERR2 ||
82 node_id == EVENT_ERROR_PSM_ERR1 ||
83 node_id == EVENT_ERROR_PSM_ERR2)
84 return true;
85
86 return false;
87 }
88
xlnx_add_cb_for_notify_event(const u32 node_id,const u32 event,const bool wake,event_cb_func_t cb_fun,void * data)89 static int xlnx_add_cb_for_notify_event(const u32 node_id, const u32 event, const bool wake,
90 event_cb_func_t cb_fun, void *data)
91 {
92 u64 key = 0;
93 bool present_in_hash = false;
94 struct registered_event_data *eve_data;
95 struct agent_cb *cb_data;
96 struct agent_cb *cb_pos;
97 struct agent_cb *cb_next;
98
99 key = ((u64)node_id << 32U) | (u64)event;
100 /* Check for existing entry in hash table for given key id */
101 hash_for_each_possible(reg_driver_map, eve_data, hentry, key) {
102 if (eve_data->key == key) {
103 present_in_hash = true;
104 break;
105 }
106 }
107
108 if (!present_in_hash) {
109 /* Add new entry if not present in HASH table */
110 eve_data = kmalloc(sizeof(*eve_data), GFP_KERNEL);
111 if (!eve_data)
112 return -ENOMEM;
113 eve_data->key = key;
114 eve_data->cb_type = PM_NOTIFY_CB;
115 eve_data->wake = wake;
116 INIT_LIST_HEAD(&eve_data->cb_list_head);
117
118 cb_data = kmalloc(sizeof(*cb_data), GFP_KERNEL);
119 if (!cb_data) {
120 kfree(eve_data);
121 return -ENOMEM;
122 }
123 cb_data->eve_cb = cb_fun;
124 cb_data->agent_data = data;
125
126 /* Add into callback list */
127 list_add(&cb_data->list, &eve_data->cb_list_head);
128
129 /* Add into HASH table */
130 hash_add(reg_driver_map, &eve_data->hentry, key);
131 } else {
132 /* Search for callback function and private data in list */
133 list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) {
134 if (cb_pos->eve_cb == cb_fun &&
135 cb_pos->agent_data == data) {
136 return 0;
137 }
138 }
139
140 /* Add multiple handler and private data in list */
141 cb_data = kmalloc(sizeof(*cb_data), GFP_KERNEL);
142 if (!cb_data)
143 return -ENOMEM;
144 cb_data->eve_cb = cb_fun;
145 cb_data->agent_data = data;
146
147 list_add(&cb_data->list, &eve_data->cb_list_head);
148 }
149
150 return 0;
151 }
152
xlnx_add_cb_for_suspend(event_cb_func_t cb_fun,void * data)153 static int xlnx_add_cb_for_suspend(event_cb_func_t cb_fun, void *data)
154 {
155 struct registered_event_data *eve_data;
156 struct agent_cb *cb_data;
157
158 /* Check for existing entry in hash table for given cb_type */
159 hash_for_each_possible(reg_driver_map, eve_data, hentry, PM_INIT_SUSPEND_CB) {
160 if (eve_data->cb_type == PM_INIT_SUSPEND_CB) {
161 pr_err("Found as already registered\n");
162 return -EINVAL;
163 }
164 }
165
166 /* Add new entry if not present */
167 eve_data = kmalloc(sizeof(*eve_data), GFP_KERNEL);
168 if (!eve_data)
169 return -ENOMEM;
170
171 eve_data->key = 0;
172 eve_data->cb_type = PM_INIT_SUSPEND_CB;
173 INIT_LIST_HEAD(&eve_data->cb_list_head);
174
175 cb_data = kmalloc(sizeof(*cb_data), GFP_KERNEL);
176 if (!cb_data)
177 return -ENOMEM;
178 cb_data->eve_cb = cb_fun;
179 cb_data->agent_data = data;
180
181 /* Add into callback list */
182 list_add(&cb_data->list, &eve_data->cb_list_head);
183
184 hash_add(reg_driver_map, &eve_data->hentry, PM_INIT_SUSPEND_CB);
185
186 return 0;
187 }
188
xlnx_remove_cb_for_suspend(event_cb_func_t cb_fun)189 static int xlnx_remove_cb_for_suspend(event_cb_func_t cb_fun)
190 {
191 bool is_callback_found = false;
192 struct registered_event_data *eve_data;
193 struct agent_cb *cb_pos;
194 struct agent_cb *cb_next;
195 struct hlist_node *tmp;
196
197 is_need_to_unregister = false;
198
199 /* Check for existing entry in hash table for given cb_type */
200 hash_for_each_possible_safe(reg_driver_map, eve_data, tmp, hentry, PM_INIT_SUSPEND_CB) {
201 if (eve_data->cb_type == PM_INIT_SUSPEND_CB) {
202 /* Delete the list of callback */
203 list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) {
204 if (cb_pos->eve_cb == cb_fun) {
205 is_callback_found = true;
206 list_del_init(&cb_pos->list);
207 kfree(cb_pos);
208 }
209 }
210 /* remove an object from a hashtable */
211 hash_del(&eve_data->hentry);
212 kfree(eve_data);
213 is_need_to_unregister = true;
214 }
215 }
216 if (!is_callback_found) {
217 pr_warn("Didn't find any registered callback for suspend event\n");
218 return -EINVAL;
219 }
220
221 return 0;
222 }
223
xlnx_remove_cb_for_notify_event(const u32 node_id,const u32 event,event_cb_func_t cb_fun,void * data)224 static int xlnx_remove_cb_for_notify_event(const u32 node_id, const u32 event,
225 event_cb_func_t cb_fun, void *data)
226 {
227 bool is_callback_found = false;
228 struct registered_event_data *eve_data;
229 u64 key = ((u64)node_id << 32U) | (u64)event;
230 struct agent_cb *cb_pos;
231 struct agent_cb *cb_next;
232 struct hlist_node *tmp;
233
234 is_need_to_unregister = false;
235
236 /* Check for existing entry in hash table for given key id */
237 hash_for_each_possible_safe(reg_driver_map, eve_data, tmp, hentry, key) {
238 if (eve_data->key == key) {
239 /* Delete the list of callback */
240 list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) {
241 if (cb_pos->eve_cb == cb_fun &&
242 cb_pos->agent_data == data) {
243 is_callback_found = true;
244 list_del_init(&cb_pos->list);
245 kfree(cb_pos);
246 }
247 }
248
249 /* Remove HASH table if callback list is empty */
250 if (list_empty(&eve_data->cb_list_head)) {
251 /* remove an object from a HASH table */
252 hash_del(&eve_data->hentry);
253 kfree(eve_data);
254 is_need_to_unregister = true;
255 }
256 }
257 }
258 if (!is_callback_found) {
259 pr_warn("Didn't find any registered callback for 0x%x 0x%x\n",
260 node_id, event);
261 return -EINVAL;
262 }
263
264 return 0;
265 }
266
267 /**
268 * xlnx_register_event() - Register for the event.
269 * @cb_type: Type of callback from pm_api_cb_id,
270 * PM_NOTIFY_CB - for Error Events,
271 * PM_INIT_SUSPEND_CB - for suspend callback.
272 * @node_id: Node-Id related to event.
273 * @event: Event Mask for the Error Event.
274 * @wake: Flag specifying whether the subsystem should be woken upon
275 * event notification.
276 * @cb_fun: Function pointer to store the callback function.
277 * @data: Pointer for the driver instance.
278 *
279 * Return: Returns 0 on successful registration else error code.
280 */
xlnx_register_event(const enum pm_api_cb_id cb_type,const u32 node_id,const u32 event,const bool wake,event_cb_func_t cb_fun,void * data)281 int xlnx_register_event(const enum pm_api_cb_id cb_type, const u32 node_id, const u32 event,
282 const bool wake, event_cb_func_t cb_fun, void *data)
283 {
284 int ret = 0;
285 u32 eve;
286 int pos;
287
288 if (event_manager_availability)
289 return event_manager_availability;
290
291 if (cb_type != PM_NOTIFY_CB && cb_type != PM_INIT_SUSPEND_CB) {
292 pr_err("%s() Unsupported Callback 0x%x\n", __func__, cb_type);
293 return -EINVAL;
294 }
295
296 if (!cb_fun)
297 return -EFAULT;
298
299 if (cb_type == PM_INIT_SUSPEND_CB) {
300 ret = xlnx_add_cb_for_suspend(cb_fun, data);
301 } else {
302 if (!xlnx_is_error_event(node_id)) {
303 /* Add entry for Node-Id/Event in hash table */
304 ret = xlnx_add_cb_for_notify_event(node_id, event, wake, cb_fun, data);
305 } else {
306 /* Add into Hash table */
307 for (pos = 0; pos < MAX_BITS; pos++) {
308 eve = event & (1 << pos);
309 if (!eve)
310 continue;
311
312 /* Add entry for Node-Id/Eve in hash table */
313 ret = xlnx_add_cb_for_notify_event(node_id, eve, wake, cb_fun,
314 data);
315 /* Break the loop if got error */
316 if (ret)
317 break;
318 }
319 if (ret) {
320 /* Skip the Event for which got the error */
321 pos--;
322 /* Remove registered(during this call) event from hash table */
323 for ( ; pos >= 0; pos--) {
324 eve = event & (1 << pos);
325 if (!eve)
326 continue;
327 xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun, data);
328 }
329 }
330 }
331
332 if (ret) {
333 pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, node_id,
334 event, ret);
335 return ret;
336 }
337
338 /* Register for Node-Id/Event combination in firmware */
339 ret = zynqmp_pm_register_notifier(node_id, event, wake, true);
340 if (ret) {
341 pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, node_id,
342 event, ret);
343 /* Remove already registered event from hash table */
344 if (xlnx_is_error_event(node_id)) {
345 for (pos = 0; pos < MAX_BITS; pos++) {
346 eve = event & (1 << pos);
347 if (!eve)
348 continue;
349 xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun, data);
350 }
351 } else {
352 xlnx_remove_cb_for_notify_event(node_id, event, cb_fun, data);
353 }
354 return ret;
355 }
356 }
357
358 return ret;
359 }
360 EXPORT_SYMBOL_GPL(xlnx_register_event);
361
362 /**
363 * xlnx_unregister_event() - Unregister for the event.
364 * @cb_type: Type of callback from pm_api_cb_id,
365 * PM_NOTIFY_CB - for Error Events,
366 * PM_INIT_SUSPEND_CB - for suspend callback.
367 * @node_id: Node-Id related to event.
368 * @event: Event Mask for the Error Event.
369 * @cb_fun: Function pointer of callback function.
370 * @data: Pointer of agent's private data.
371 *
372 * Return: Returns 0 on successful unregistration else error code.
373 */
xlnx_unregister_event(const enum pm_api_cb_id cb_type,const u32 node_id,const u32 event,event_cb_func_t cb_fun,void * data)374 int xlnx_unregister_event(const enum pm_api_cb_id cb_type, const u32 node_id, const u32 event,
375 event_cb_func_t cb_fun, void *data)
376 {
377 int ret = 0;
378 u32 eve, pos;
379
380 is_need_to_unregister = false;
381
382 if (event_manager_availability)
383 return event_manager_availability;
384
385 if (cb_type != PM_NOTIFY_CB && cb_type != PM_INIT_SUSPEND_CB) {
386 pr_err("%s() Unsupported Callback 0x%x\n", __func__, cb_type);
387 return -EINVAL;
388 }
389
390 if (!cb_fun)
391 return -EFAULT;
392
393 if (cb_type == PM_INIT_SUSPEND_CB) {
394 ret = xlnx_remove_cb_for_suspend(cb_fun);
395 } else {
396 /* Remove Node-Id/Event from hash table */
397 if (!xlnx_is_error_event(node_id)) {
398 xlnx_remove_cb_for_notify_event(node_id, event, cb_fun, data);
399 } else {
400 for (pos = 0; pos < MAX_BITS; pos++) {
401 eve = event & (1 << pos);
402 if (!eve)
403 continue;
404
405 xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun, data);
406 }
407 }
408
409 /* Un-register if list is empty */
410 if (is_need_to_unregister) {
411 /* Un-register for Node-Id/Event combination */
412 ret = zynqmp_pm_register_notifier(node_id, event, false, false);
413 if (ret) {
414 pr_err("%s() failed for 0x%x and 0x%x: %d\n",
415 __func__, node_id, event, ret);
416 return ret;
417 }
418 }
419 }
420
421 return ret;
422 }
423 EXPORT_SYMBOL_GPL(xlnx_unregister_event);
424
xlnx_call_suspend_cb_handler(const u32 * payload)425 static void xlnx_call_suspend_cb_handler(const u32 *payload)
426 {
427 bool is_callback_found = false;
428 struct registered_event_data *eve_data;
429 u32 cb_type = payload[0];
430 struct agent_cb *cb_pos;
431 struct agent_cb *cb_next;
432
433 /* Check for existing entry in hash table for given cb_type */
434 hash_for_each_possible(reg_driver_map, eve_data, hentry, cb_type) {
435 if (eve_data->cb_type == cb_type) {
436 list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) {
437 cb_pos->eve_cb(&payload[0], cb_pos->agent_data);
438 is_callback_found = true;
439 }
440 }
441 }
442 if (!is_callback_found)
443 pr_warn("Didn't find any registered callback for suspend event\n");
444 }
445
xlnx_call_notify_cb_handler(const u32 * payload)446 static void xlnx_call_notify_cb_handler(const u32 *payload)
447 {
448 bool is_callback_found = false;
449 struct registered_event_data *eve_data;
450 u64 key = ((u64)payload[1] << 32U) | (u64)payload[2];
451 int ret;
452 struct agent_cb *cb_pos;
453 struct agent_cb *cb_next;
454
455 /* Check for existing entry in hash table for given key id */
456 hash_for_each_possible(reg_driver_map, eve_data, hentry, key) {
457 if (eve_data->key == key) {
458 list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) {
459 cb_pos->eve_cb(&payload[0], cb_pos->agent_data);
460 is_callback_found = true;
461 }
462
463 /* re register with firmware to get future events */
464 ret = zynqmp_pm_register_notifier(payload[1], payload[2],
465 eve_data->wake, true);
466 if (ret) {
467 pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__,
468 payload[1], payload[2], ret);
469 list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head,
470 list) {
471 /* Remove already registered event from hash table */
472 xlnx_remove_cb_for_notify_event(payload[1], payload[2],
473 cb_pos->eve_cb,
474 cb_pos->agent_data);
475 }
476 }
477 }
478 }
479 if (!is_callback_found)
480 pr_warn("Didn't find any registered callback for 0x%x 0x%x\n",
481 payload[1], payload[2]);
482 }
483
xlnx_get_event_callback_data(u32 * buf)484 static void xlnx_get_event_callback_data(u32 *buf)
485 {
486 zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf);
487 }
488
xlnx_event_handler(int irq,void * dev_id)489 static irqreturn_t xlnx_event_handler(int irq, void *dev_id)
490 {
491 u32 cb_type, node_id, event, pos;
492 u32 payload[CB_MAX_PAYLOAD_SIZE] = {0};
493 u32 event_data[CB_MAX_PAYLOAD_SIZE] = {0};
494
495 /* Get event data */
496 xlnx_get_event_callback_data(payload);
497
498 /* First element is callback type, others are callback arguments */
499 cb_type = payload[0];
500
501 if (cb_type == PM_NOTIFY_CB) {
502 node_id = payload[1];
503 event = payload[2];
504 if (!xlnx_is_error_event(node_id)) {
505 xlnx_call_notify_cb_handler(payload);
506 } else {
507 /*
508 * Each call back function expecting payload as an input arguments.
509 * We can get multiple error events as in one call back through error
510 * mask. So payload[2] may can contain multiple error events.
511 * In reg_driver_map database we store data in the combination of single
512 * node_id-error combination.
513 * So coping the payload message into event_data and update the
514 * event_data[2] with Error Mask for single error event and use
515 * event_data as input argument for registered call back function.
516 *
517 */
518 memcpy(event_data, payload, (4 * CB_MAX_PAYLOAD_SIZE));
519 /* Support Multiple Error Event */
520 for (pos = 0; pos < MAX_BITS; pos++) {
521 if ((0 == (event & (1 << pos))))
522 continue;
523 event_data[2] = (event & (1 << pos));
524 xlnx_call_notify_cb_handler(event_data);
525 }
526 }
527 } else if (cb_type == PM_INIT_SUSPEND_CB) {
528 xlnx_call_suspend_cb_handler(payload);
529 } else {
530 pr_err("%s() Unsupported Callback %d\n", __func__, cb_type);
531 }
532
533 return IRQ_HANDLED;
534 }
535
xlnx_event_cpuhp_start(unsigned int cpu)536 static int xlnx_event_cpuhp_start(unsigned int cpu)
537 {
538 enable_percpu_irq(virq_sgi, IRQ_TYPE_NONE);
539
540 return 0;
541 }
542
xlnx_event_cpuhp_down(unsigned int cpu)543 static int xlnx_event_cpuhp_down(unsigned int cpu)
544 {
545 disable_percpu_irq(virq_sgi);
546
547 return 0;
548 }
549
xlnx_disable_percpu_irq(void * data)550 static void xlnx_disable_percpu_irq(void *data)
551 {
552 disable_percpu_irq(virq_sgi);
553 }
554
xlnx_event_init_sgi(struct platform_device * pdev)555 static int xlnx_event_init_sgi(struct platform_device *pdev)
556 {
557 int ret = 0;
558 int cpu = smp_processor_id();
559 /*
560 * IRQ related structures are used for the following:
561 * for each SGI interrupt ensure its mapped by GIC IRQ domain
562 * and that each corresponding linux IRQ for the HW IRQ has
563 * a handler for when receiving an interrupt from the remote
564 * processor.
565 */
566 struct irq_domain *domain;
567 struct irq_fwspec sgi_fwspec;
568 struct device_node *interrupt_parent = NULL;
569 struct device *parent = pdev->dev.parent;
570
571 /* Find GIC controller to map SGIs. */
572 interrupt_parent = of_irq_find_parent(parent->of_node);
573 if (!interrupt_parent) {
574 dev_err(&pdev->dev, "Failed to find property for Interrupt parent\n");
575 return -EINVAL;
576 }
577
578 /* Each SGI needs to be associated with GIC's IRQ domain. */
579 domain = irq_find_host(interrupt_parent);
580 of_node_put(interrupt_parent);
581
582 /* Each mapping needs GIC domain when finding IRQ mapping. */
583 sgi_fwspec.fwnode = domain->fwnode;
584
585 /*
586 * When irq domain looks at mapping each arg is as follows:
587 * 3 args for: interrupt type (SGI), interrupt # (set later), type
588 */
589 sgi_fwspec.param_count = 1;
590
591 /* Set SGI's hwirq */
592 sgi_fwspec.param[0] = sgi_num;
593 virq_sgi = irq_create_fwspec_mapping(&sgi_fwspec);
594
595 per_cpu(cpu_number1, cpu) = cpu;
596 ret = request_percpu_irq(virq_sgi, xlnx_event_handler, "xlnx_event_mgmt",
597 &cpu_number1);
598 WARN_ON(ret);
599 if (ret) {
600 irq_dispose_mapping(virq_sgi);
601 return ret;
602 }
603
604 irq_to_desc(virq_sgi);
605 irq_set_status_flags(virq_sgi, IRQ_PER_CPU);
606
607 return ret;
608 }
609
xlnx_event_cleanup_sgi(struct platform_device * pdev)610 static void xlnx_event_cleanup_sgi(struct platform_device *pdev)
611 {
612 int cpu = smp_processor_id();
613
614 per_cpu(cpu_number1, cpu) = cpu;
615
616 cpuhp_remove_state(CPUHP_AP_ONLINE_DYN);
617
618 on_each_cpu(xlnx_disable_percpu_irq, NULL, 1);
619
620 irq_clear_status_flags(virq_sgi, IRQ_PER_CPU);
621 free_percpu_irq(virq_sgi, &cpu_number1);
622 irq_dispose_mapping(virq_sgi);
623 }
624
xlnx_event_manager_probe(struct platform_device * pdev)625 static int xlnx_event_manager_probe(struct platform_device *pdev)
626 {
627 int ret;
628
629 ret = zynqmp_pm_feature(PM_REGISTER_NOTIFIER);
630 if (ret < 0) {
631 dev_err(&pdev->dev, "Feature check failed with %d\n", ret);
632 return ret;
633 }
634
635 if ((ret & FIRMWARE_VERSION_MASK) <
636 REGISTER_NOTIFIER_FIRMWARE_VERSION) {
637 dev_err(&pdev->dev, "Register notifier version error. Expected Firmware: v%d - Found: v%d\n",
638 REGISTER_NOTIFIER_FIRMWARE_VERSION,
639 ret & FIRMWARE_VERSION_MASK);
640 return -EOPNOTSUPP;
641 }
642
643 /* Initialize the SGI */
644 ret = xlnx_event_init_sgi(pdev);
645 if (ret) {
646 dev_err(&pdev->dev, "SGI Init has been failed with %d\n", ret);
647 return ret;
648 }
649
650 /* Setup function for the CPU hot-plug cases */
651 cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "soc/event:starting",
652 xlnx_event_cpuhp_start, xlnx_event_cpuhp_down);
653
654 ret = zynqmp_pm_register_sgi(sgi_num, 0);
655 if (ret) {
656 dev_err(&pdev->dev, "SGI %d Registration over TF-A failed with %d\n", sgi_num, ret);
657 xlnx_event_cleanup_sgi(pdev);
658 return ret;
659 }
660
661 event_manager_availability = 0;
662
663 dev_info(&pdev->dev, "SGI %d Registered over TF-A\n", sgi_num);
664 dev_info(&pdev->dev, "Xilinx Event Management driver probed\n");
665
666 return ret;
667 }
668
xlnx_event_manager_remove(struct platform_device * pdev)669 static void xlnx_event_manager_remove(struct platform_device *pdev)
670 {
671 int i;
672 struct registered_event_data *eve_data;
673 struct hlist_node *tmp;
674 int ret;
675 struct agent_cb *cb_pos;
676 struct agent_cb *cb_next;
677
678 hash_for_each_safe(reg_driver_map, i, tmp, eve_data, hentry) {
679 list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) {
680 list_del_init(&cb_pos->list);
681 kfree(cb_pos);
682 }
683 hash_del(&eve_data->hentry);
684 kfree(eve_data);
685 }
686
687 ret = zynqmp_pm_register_sgi(0, 1);
688 if (ret)
689 dev_err(&pdev->dev, "SGI unregistration over TF-A failed with %d\n", ret);
690
691 xlnx_event_cleanup_sgi(pdev);
692
693 event_manager_availability = -EACCES;
694 }
695
696 static struct platform_driver xlnx_event_manager_driver = {
697 .probe = xlnx_event_manager_probe,
698 .remove_new = xlnx_event_manager_remove,
699 .driver = {
700 .name = "xlnx_event_manager",
701 },
702 };
703 module_param(sgi_num, uint, 0);
704 module_platform_driver(xlnx_event_manager_driver);
705