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
22static DEFINE_PER_CPU_READ_MOSTLY(int, cpu_number1);
23
24static int virq_sgi;
25static 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
41static DEFINE_HASHTABLE(reg_driver_map, REGISTERED_DRIVER_MAX_ORDER);
42static int sgi_num = XLNX_EVENT_SGI_NUM;
43
44static 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 */
52struct 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 */
70struct 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
78static 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
89static 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
153static 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
189static 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
224static 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 */
281int 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}
360EXPORT_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 */
374int 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}
423EXPORT_SYMBOL_GPL(xlnx_unregister_event);
424
425static 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
446static 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("Unhandled SGI node 0x%x event 0x%x. Expected with Xen hypervisor\n",
481			payload[1], payload[2]);
482}
483
484static void xlnx_get_event_callback_data(u32 *buf)
485{
486	zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf);
487}
488
489static 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
536static int xlnx_event_cpuhp_start(unsigned int cpu)
537{
538	enable_percpu_irq(virq_sgi, IRQ_TYPE_NONE);
539
540	return 0;
541}
542
543static int xlnx_event_cpuhp_down(unsigned int cpu)
544{
545	disable_percpu_irq(virq_sgi);
546
547	return 0;
548}
549
550static void xlnx_disable_percpu_irq(void *data)
551{
552	disable_percpu_irq(virq_sgi);
553}
554
555static int xlnx_event_init_sgi(struct platform_device *pdev)
556{
557	int ret = 0;
558	int cpu;
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	cpu = get_cpu();
596	per_cpu(cpu_number1, cpu) = cpu;
597	ret = request_percpu_irq(virq_sgi, xlnx_event_handler, "xlnx_event_mgmt",
598				 &cpu_number1);
599	put_cpu();
600
601	WARN_ON(ret);
602	if (ret) {
603		irq_dispose_mapping(virq_sgi);
604		return ret;
605	}
606
607	irq_to_desc(virq_sgi);
608	irq_set_status_flags(virq_sgi, IRQ_PER_CPU);
609
610	return ret;
611}
612
613static void xlnx_event_cleanup_sgi(struct platform_device *pdev)
614{
615	int cpu = smp_processor_id();
616
617	per_cpu(cpu_number1, cpu) = cpu;
618
619	cpuhp_remove_state(CPUHP_AP_ONLINE_DYN);
620
621	on_each_cpu(xlnx_disable_percpu_irq, NULL, 1);
622
623	irq_clear_status_flags(virq_sgi, IRQ_PER_CPU);
624	free_percpu_irq(virq_sgi, &cpu_number1);
625	irq_dispose_mapping(virq_sgi);
626}
627
628static int xlnx_event_manager_probe(struct platform_device *pdev)
629{
630	int ret;
631
632	ret = zynqmp_pm_feature(PM_REGISTER_NOTIFIER);
633	if (ret < 0) {
634		dev_err(&pdev->dev, "Feature check failed with %d\n", ret);
635		return ret;
636	}
637
638	if ((ret & FIRMWARE_VERSION_MASK) <
639	    REGISTER_NOTIFIER_FIRMWARE_VERSION) {
640		dev_err(&pdev->dev, "Register notifier version error. Expected Firmware: v%d - Found: v%d\n",
641			REGISTER_NOTIFIER_FIRMWARE_VERSION,
642			ret & FIRMWARE_VERSION_MASK);
643		return -EOPNOTSUPP;
644	}
645
646	/* Initialize the SGI */
647	ret = xlnx_event_init_sgi(pdev);
648	if (ret) {
649		dev_err(&pdev->dev, "SGI Init has been failed with %d\n", ret);
650		return ret;
651	}
652
653	/* Setup function for the CPU hot-plug cases */
654	cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "soc/event:starting",
655			  xlnx_event_cpuhp_start, xlnx_event_cpuhp_down);
656
657	ret = zynqmp_pm_register_sgi(sgi_num, 0);
658	if (ret) {
659		dev_err(&pdev->dev, "SGI %d Registration over TF-A failed with %d\n", sgi_num, ret);
660		xlnx_event_cleanup_sgi(pdev);
661		return ret;
662	}
663
664	event_manager_availability = 0;
665
666	dev_info(&pdev->dev, "SGI %d Registered over TF-A\n", sgi_num);
667	dev_info(&pdev->dev, "Xilinx Event Management driver probed\n");
668
669	return ret;
670}
671
672static void xlnx_event_manager_remove(struct platform_device *pdev)
673{
674	int i;
675	struct registered_event_data *eve_data;
676	struct hlist_node *tmp;
677	int ret;
678	struct agent_cb *cb_pos;
679	struct agent_cb *cb_next;
680
681	hash_for_each_safe(reg_driver_map, i, tmp, eve_data, hentry) {
682		list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) {
683			list_del_init(&cb_pos->list);
684			kfree(cb_pos);
685		}
686		hash_del(&eve_data->hentry);
687		kfree(eve_data);
688	}
689
690	ret = zynqmp_pm_register_sgi(0, 1);
691	if (ret)
692		dev_err(&pdev->dev, "SGI unregistration over TF-A failed with %d\n", ret);
693
694	xlnx_event_cleanup_sgi(pdev);
695
696	event_manager_availability = -EACCES;
697}
698
699static struct platform_driver xlnx_event_manager_driver = {
700	.probe = xlnx_event_manager_probe,
701	.remove_new = xlnx_event_manager_remove,
702	.driver = {
703		.name = "xlnx_event_manager",
704	},
705};
706module_param(sgi_num, uint, 0);
707module_platform_driver(xlnx_event_manager_driver);
708