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