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