Linux Audio

Check our new training course

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