123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Xilinx Event Management Driver
- *
- * Copyright (C) 2021 Xilinx, Inc.
- *
- * Abhyuday Godhasara <[email protected]>
- */
- #include <linux/cpuhotplug.h>
- #include <linux/firmware/xlnx-event-manager.h>
- #include <linux/firmware/xlnx-zynqmp.h>
- #include <linux/hashtable.h>
- #include <linux/interrupt.h>
- #include <linux/irq.h>
- #include <linux/irqdomain.h>
- #include <linux/module.h>
- #include <linux/of_irq.h>
- #include <linux/platform_device.h>
- #include <linux/slab.h>
- static DEFINE_PER_CPU_READ_MOSTLY(int, cpu_number1);
- static int virq_sgi;
- static int event_manager_availability = -EACCES;
- /* SGI number used for Event management driver */
- #define XLNX_EVENT_SGI_NUM (15)
- /* Max number of driver can register for same event */
- #define MAX_DRIVER_PER_EVENT (10U)
- /* Max HashMap Order for PM API feature check (1<<7 = 128) */
- #define REGISTERED_DRIVER_MAX_ORDER (7)
- #define MAX_BITS (32U) /* Number of bits available for error mask */
- #define FIRMWARE_VERSION_MASK (0xFFFFU)
- #define REGISTER_NOTIFIER_FIRMWARE_VERSION (2U)
- static DEFINE_HASHTABLE(reg_driver_map, REGISTERED_DRIVER_MAX_ORDER);
- static int sgi_num = XLNX_EVENT_SGI_NUM;
- static bool is_need_to_unregister;
- /**
- * struct agent_cb - Registered callback function and private data.
- * @agent_data: Data passed back to handler function.
- * @eve_cb: Function pointer to store the callback function.
- * @list: member to create list.
- */
- struct agent_cb {
- void *agent_data;
- event_cb_func_t eve_cb;
- struct list_head list;
- };
- /**
- * struct registered_event_data - Registered Event Data.
- * @key: key is the combine id(Node-Id | Event-Id) of type u64
- * where upper u32 for Node-Id and lower u32 for Event-Id,
- * And this used as key to index into hashmap.
- * @cb_type: Type of Api callback, like PM_NOTIFY_CB, etc.
- * @wake: If this flag set, firmware will wake up processor if is
- * in sleep or power down state.
- * @cb_list_head: Head of call back data list which contain the information
- * about registered handler and private data.
- * @hentry: hlist_node that hooks this entry into hashtable.
- */
- struct registered_event_data {
- u64 key;
- enum pm_api_cb_id cb_type;
- bool wake;
- struct list_head cb_list_head;
- struct hlist_node hentry;
- };
- static bool xlnx_is_error_event(const u32 node_id)
- {
- if (node_id == EVENT_ERROR_PMC_ERR1 ||
- node_id == EVENT_ERROR_PMC_ERR2 ||
- node_id == EVENT_ERROR_PSM_ERR1 ||
- node_id == EVENT_ERROR_PSM_ERR2)
- return true;
- return false;
- }
- static int xlnx_add_cb_for_notify_event(const u32 node_id, const u32 event, const bool wake,
- event_cb_func_t cb_fun, void *data)
- {
- u64 key = 0;
- bool present_in_hash = false;
- struct registered_event_data *eve_data;
- struct agent_cb *cb_data;
- struct agent_cb *cb_pos;
- struct agent_cb *cb_next;
- key = ((u64)node_id << 32U) | (u64)event;
- /* Check for existing entry in hash table for given key id */
- hash_for_each_possible(reg_driver_map, eve_data, hentry, key) {
- if (eve_data->key == key) {
- present_in_hash = true;
- break;
- }
- }
- if (!present_in_hash) {
- /* Add new entry if not present in HASH table */
- eve_data = kmalloc(sizeof(*eve_data), GFP_KERNEL);
- if (!eve_data)
- return -ENOMEM;
- eve_data->key = key;
- eve_data->cb_type = PM_NOTIFY_CB;
- eve_data->wake = wake;
- INIT_LIST_HEAD(&eve_data->cb_list_head);
- cb_data = kmalloc(sizeof(*cb_data), GFP_KERNEL);
- if (!cb_data) {
- kfree(eve_data);
- return -ENOMEM;
- }
- cb_data->eve_cb = cb_fun;
- cb_data->agent_data = data;
- /* Add into callback list */
- list_add(&cb_data->list, &eve_data->cb_list_head);
- /* Add into HASH table */
- hash_add(reg_driver_map, &eve_data->hentry, key);
- } else {
- /* Search for callback function and private data in list */
- list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) {
- if (cb_pos->eve_cb == cb_fun &&
- cb_pos->agent_data == data) {
- return 0;
- }
- }
- /* Add multiple handler and private data in list */
- cb_data = kmalloc(sizeof(*cb_data), GFP_KERNEL);
- if (!cb_data)
- return -ENOMEM;
- cb_data->eve_cb = cb_fun;
- cb_data->agent_data = data;
- list_add(&cb_data->list, &eve_data->cb_list_head);
- }
- return 0;
- }
- static int xlnx_add_cb_for_suspend(event_cb_func_t cb_fun, void *data)
- {
- struct registered_event_data *eve_data;
- struct agent_cb *cb_data;
- /* Check for existing entry in hash table for given cb_type */
- hash_for_each_possible(reg_driver_map, eve_data, hentry, PM_INIT_SUSPEND_CB) {
- if (eve_data->cb_type == PM_INIT_SUSPEND_CB) {
- pr_err("Found as already registered\n");
- return -EINVAL;
- }
- }
- /* Add new entry if not present */
- eve_data = kmalloc(sizeof(*eve_data), GFP_KERNEL);
- if (!eve_data)
- return -ENOMEM;
- eve_data->key = 0;
- eve_data->cb_type = PM_INIT_SUSPEND_CB;
- INIT_LIST_HEAD(&eve_data->cb_list_head);
- cb_data = kmalloc(sizeof(*cb_data), GFP_KERNEL);
- if (!cb_data)
- return -ENOMEM;
- cb_data->eve_cb = cb_fun;
- cb_data->agent_data = data;
- /* Add into callback list */
- list_add(&cb_data->list, &eve_data->cb_list_head);
- hash_add(reg_driver_map, &eve_data->hentry, PM_INIT_SUSPEND_CB);
- return 0;
- }
- static int xlnx_remove_cb_for_suspend(event_cb_func_t cb_fun)
- {
- bool is_callback_found = false;
- struct registered_event_data *eve_data;
- struct agent_cb *cb_pos;
- struct agent_cb *cb_next;
- struct hlist_node *tmp;
- is_need_to_unregister = false;
- /* Check for existing entry in hash table for given cb_type */
- hash_for_each_possible_safe(reg_driver_map, eve_data, tmp, hentry, PM_INIT_SUSPEND_CB) {
- if (eve_data->cb_type == PM_INIT_SUSPEND_CB) {
- /* Delete the list of callback */
- list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) {
- if (cb_pos->eve_cb == cb_fun) {
- is_callback_found = true;
- list_del_init(&cb_pos->list);
- kfree(cb_pos);
- }
- }
- /* remove an object from a hashtable */
- hash_del(&eve_data->hentry);
- kfree(eve_data);
- is_need_to_unregister = true;
- }
- }
- if (!is_callback_found) {
- pr_warn("Didn't find any registered callback for suspend event\n");
- return -EINVAL;
- }
- return 0;
- }
- static int xlnx_remove_cb_for_notify_event(const u32 node_id, const u32 event,
- event_cb_func_t cb_fun, void *data)
- {
- bool is_callback_found = false;
- struct registered_event_data *eve_data;
- u64 key = ((u64)node_id << 32U) | (u64)event;
- struct agent_cb *cb_pos;
- struct agent_cb *cb_next;
- struct hlist_node *tmp;
- is_need_to_unregister = false;
- /* Check for existing entry in hash table for given key id */
- hash_for_each_possible_safe(reg_driver_map, eve_data, tmp, hentry, key) {
- if (eve_data->key == key) {
- /* Delete the list of callback */
- list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) {
- if (cb_pos->eve_cb == cb_fun &&
- cb_pos->agent_data == data) {
- is_callback_found = true;
- list_del_init(&cb_pos->list);
- kfree(cb_pos);
- }
- }
- /* Remove HASH table if callback list is empty */
- if (list_empty(&eve_data->cb_list_head)) {
- /* remove an object from a HASH table */
- hash_del(&eve_data->hentry);
- kfree(eve_data);
- is_need_to_unregister = true;
- }
- }
- }
- if (!is_callback_found) {
- pr_warn("Didn't find any registered callback for 0x%x 0x%x\n",
- node_id, event);
- return -EINVAL;
- }
- return 0;
- }
- /**
- * xlnx_register_event() - Register for the event.
- * @cb_type: Type of callback from pm_api_cb_id,
- * PM_NOTIFY_CB - for Error Events,
- * PM_INIT_SUSPEND_CB - for suspend callback.
- * @node_id: Node-Id related to event.
- * @event: Event Mask for the Error Event.
- * @wake: Flag specifying whether the subsystem should be woken upon
- * event notification.
- * @cb_fun: Function pointer to store the callback function.
- * @data: Pointer for the driver instance.
- *
- * Return: Returns 0 on successful registration else error code.
- */
- int 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)
- {
- int ret = 0;
- u32 eve;
- int pos;
- if (event_manager_availability)
- return event_manager_availability;
- if (cb_type != PM_NOTIFY_CB && cb_type != PM_INIT_SUSPEND_CB) {
- pr_err("%s() Unsupported Callback 0x%x\n", __func__, cb_type);
- return -EINVAL;
- }
- if (!cb_fun)
- return -EFAULT;
- if (cb_type == PM_INIT_SUSPEND_CB) {
- ret = xlnx_add_cb_for_suspend(cb_fun, data);
- } else {
- if (!xlnx_is_error_event(node_id)) {
- /* Add entry for Node-Id/Event in hash table */
- ret = xlnx_add_cb_for_notify_event(node_id, event, wake, cb_fun, data);
- } else {
- /* Add into Hash table */
- for (pos = 0; pos < MAX_BITS; pos++) {
- eve = event & (1 << pos);
- if (!eve)
- continue;
- /* Add entry for Node-Id/Eve in hash table */
- ret = xlnx_add_cb_for_notify_event(node_id, eve, wake, cb_fun,
- data);
- /* Break the loop if got error */
- if (ret)
- break;
- }
- if (ret) {
- /* Skip the Event for which got the error */
- pos--;
- /* Remove registered(during this call) event from hash table */
- for ( ; pos >= 0; pos--) {
- eve = event & (1 << pos);
- if (!eve)
- continue;
- xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun, data);
- }
- }
- }
- if (ret) {
- pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, node_id,
- event, ret);
- return ret;
- }
- /* Register for Node-Id/Event combination in firmware */
- ret = zynqmp_pm_register_notifier(node_id, event, wake, true);
- if (ret) {
- pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, node_id,
- event, ret);
- /* Remove already registered event from hash table */
- if (xlnx_is_error_event(node_id)) {
- for (pos = 0; pos < MAX_BITS; pos++) {
- eve = event & (1 << pos);
- if (!eve)
- continue;
- xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun, data);
- }
- } else {
- xlnx_remove_cb_for_notify_event(node_id, event, cb_fun, data);
- }
- return ret;
- }
- }
- return ret;
- }
- EXPORT_SYMBOL_GPL(xlnx_register_event);
- /**
- * xlnx_unregister_event() - Unregister for the event.
- * @cb_type: Type of callback from pm_api_cb_id,
- * PM_NOTIFY_CB - for Error Events,
- * PM_INIT_SUSPEND_CB - for suspend callback.
- * @node_id: Node-Id related to event.
- * @event: Event Mask for the Error Event.
- * @cb_fun: Function pointer of callback function.
- * @data: Pointer of agent's private data.
- *
- * Return: Returns 0 on successful unregistration else error code.
- */
- int 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)
- {
- int ret = 0;
- u32 eve, pos;
- is_need_to_unregister = false;
- if (event_manager_availability)
- return event_manager_availability;
- if (cb_type != PM_NOTIFY_CB && cb_type != PM_INIT_SUSPEND_CB) {
- pr_err("%s() Unsupported Callback 0x%x\n", __func__, cb_type);
- return -EINVAL;
- }
- if (!cb_fun)
- return -EFAULT;
- if (cb_type == PM_INIT_SUSPEND_CB) {
- ret = xlnx_remove_cb_for_suspend(cb_fun);
- } else {
- /* Remove Node-Id/Event from hash table */
- if (!xlnx_is_error_event(node_id)) {
- xlnx_remove_cb_for_notify_event(node_id, event, cb_fun, data);
- } else {
- for (pos = 0; pos < MAX_BITS; pos++) {
- eve = event & (1 << pos);
- if (!eve)
- continue;
- xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun, data);
- }
- }
- /* Un-register if list is empty */
- if (is_need_to_unregister) {
- /* Un-register for Node-Id/Event combination */
- ret = zynqmp_pm_register_notifier(node_id, event, false, false);
- if (ret) {
- pr_err("%s() failed for 0x%x and 0x%x: %d\n",
- __func__, node_id, event, ret);
- return ret;
- }
- }
- }
- return ret;
- }
- EXPORT_SYMBOL_GPL(xlnx_unregister_event);
- static void xlnx_call_suspend_cb_handler(const u32 *payload)
- {
- bool is_callback_found = false;
- struct registered_event_data *eve_data;
- u32 cb_type = payload[0];
- struct agent_cb *cb_pos;
- struct agent_cb *cb_next;
- /* Check for existing entry in hash table for given cb_type */
- hash_for_each_possible(reg_driver_map, eve_data, hentry, cb_type) {
- if (eve_data->cb_type == cb_type) {
- list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) {
- cb_pos->eve_cb(&payload[0], cb_pos->agent_data);
- is_callback_found = true;
- }
- }
- }
- if (!is_callback_found)
- pr_warn("Didn't find any registered callback for suspend event\n");
- }
- static void xlnx_call_notify_cb_handler(const u32 *payload)
- {
- bool is_callback_found = false;
- struct registered_event_data *eve_data;
- u64 key = ((u64)payload[1] << 32U) | (u64)payload[2];
- int ret;
- struct agent_cb *cb_pos;
- struct agent_cb *cb_next;
- /* Check for existing entry in hash table for given key id */
- hash_for_each_possible(reg_driver_map, eve_data, hentry, key) {
- if (eve_data->key == key) {
- list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) {
- cb_pos->eve_cb(&payload[0], cb_pos->agent_data);
- is_callback_found = true;
- }
- /* re register with firmware to get future events */
- ret = zynqmp_pm_register_notifier(payload[1], payload[2],
- eve_data->wake, true);
- if (ret) {
- pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__,
- payload[1], payload[2], ret);
- list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head,
- list) {
- /* Remove already registered event from hash table */
- xlnx_remove_cb_for_notify_event(payload[1], payload[2],
- cb_pos->eve_cb,
- cb_pos->agent_data);
- }
- }
- }
- }
- if (!is_callback_found)
- pr_warn("Didn't find any registered callback for 0x%x 0x%x\n",
- payload[1], payload[2]);
- }
- static void xlnx_get_event_callback_data(u32 *buf)
- {
- zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf);
- }
- static irqreturn_t xlnx_event_handler(int irq, void *dev_id)
- {
- u32 cb_type, node_id, event, pos;
- u32 payload[CB_MAX_PAYLOAD_SIZE] = {0};
- u32 event_data[CB_MAX_PAYLOAD_SIZE] = {0};
- /* Get event data */
- xlnx_get_event_callback_data(payload);
- /* First element is callback type, others are callback arguments */
- cb_type = payload[0];
- if (cb_type == PM_NOTIFY_CB) {
- node_id = payload[1];
- event = payload[2];
- if (!xlnx_is_error_event(node_id)) {
- xlnx_call_notify_cb_handler(payload);
- } else {
- /*
- * Each call back function expecting payload as an input arguments.
- * We can get multiple error events as in one call back through error
- * mask. So payload[2] may can contain multiple error events.
- * In reg_driver_map database we store data in the combination of single
- * node_id-error combination.
- * So coping the payload message into event_data and update the
- * event_data[2] with Error Mask for single error event and use
- * event_data as input argument for registered call back function.
- *
- */
- memcpy(event_data, payload, (4 * CB_MAX_PAYLOAD_SIZE));
- /* Support Multiple Error Event */
- for (pos = 0; pos < MAX_BITS; pos++) {
- if ((0 == (event & (1 << pos))))
- continue;
- event_data[2] = (event & (1 << pos));
- xlnx_call_notify_cb_handler(event_data);
- }
- }
- } else if (cb_type == PM_INIT_SUSPEND_CB) {
- xlnx_call_suspend_cb_handler(payload);
- } else {
- pr_err("%s() Unsupported Callback %d\n", __func__, cb_type);
- }
- return IRQ_HANDLED;
- }
- static int xlnx_event_cpuhp_start(unsigned int cpu)
- {
- enable_percpu_irq(virq_sgi, IRQ_TYPE_NONE);
- return 0;
- }
- static int xlnx_event_cpuhp_down(unsigned int cpu)
- {
- disable_percpu_irq(virq_sgi);
- return 0;
- }
- static void xlnx_disable_percpu_irq(void *data)
- {
- disable_percpu_irq(virq_sgi);
- }
- static int xlnx_event_init_sgi(struct platform_device *pdev)
- {
- int ret = 0;
- int cpu = smp_processor_id();
- /*
- * IRQ related structures are used for the following:
- * for each SGI interrupt ensure its mapped by GIC IRQ domain
- * and that each corresponding linux IRQ for the HW IRQ has
- * a handler for when receiving an interrupt from the remote
- * processor.
- */
- struct irq_domain *domain;
- struct irq_fwspec sgi_fwspec;
- struct device_node *interrupt_parent = NULL;
- struct device *parent = pdev->dev.parent;
- /* Find GIC controller to map SGIs. */
- interrupt_parent = of_irq_find_parent(parent->of_node);
- if (!interrupt_parent) {
- dev_err(&pdev->dev, "Failed to find property for Interrupt parent\n");
- return -EINVAL;
- }
- /* Each SGI needs to be associated with GIC's IRQ domain. */
- domain = irq_find_host(interrupt_parent);
- of_node_put(interrupt_parent);
- /* Each mapping needs GIC domain when finding IRQ mapping. */
- sgi_fwspec.fwnode = domain->fwnode;
- /*
- * When irq domain looks at mapping each arg is as follows:
- * 3 args for: interrupt type (SGI), interrupt # (set later), type
- */
- sgi_fwspec.param_count = 1;
- /* Set SGI's hwirq */
- sgi_fwspec.param[0] = sgi_num;
- virq_sgi = irq_create_fwspec_mapping(&sgi_fwspec);
- per_cpu(cpu_number1, cpu) = cpu;
- ret = request_percpu_irq(virq_sgi, xlnx_event_handler, "xlnx_event_mgmt",
- &cpu_number1);
- WARN_ON(ret);
- if (ret) {
- irq_dispose_mapping(virq_sgi);
- return ret;
- }
- irq_to_desc(virq_sgi);
- irq_set_status_flags(virq_sgi, IRQ_PER_CPU);
- return ret;
- }
- static void xlnx_event_cleanup_sgi(struct platform_device *pdev)
- {
- int cpu = smp_processor_id();
- per_cpu(cpu_number1, cpu) = cpu;
- cpuhp_remove_state(CPUHP_AP_ONLINE_DYN);
- on_each_cpu(xlnx_disable_percpu_irq, NULL, 1);
- irq_clear_status_flags(virq_sgi, IRQ_PER_CPU);
- free_percpu_irq(virq_sgi, &cpu_number1);
- irq_dispose_mapping(virq_sgi);
- }
- static int xlnx_event_manager_probe(struct platform_device *pdev)
- {
- int ret;
- ret = zynqmp_pm_feature(PM_REGISTER_NOTIFIER);
- if (ret < 0) {
- dev_err(&pdev->dev, "Feature check failed with %d\n", ret);
- return ret;
- }
- if ((ret & FIRMWARE_VERSION_MASK) <
- REGISTER_NOTIFIER_FIRMWARE_VERSION) {
- dev_err(&pdev->dev, "Register notifier version error. Expected Firmware: v%d - Found: v%d\n",
- REGISTER_NOTIFIER_FIRMWARE_VERSION,
- ret & FIRMWARE_VERSION_MASK);
- return -EOPNOTSUPP;
- }
- /* Initialize the SGI */
- ret = xlnx_event_init_sgi(pdev);
- if (ret) {
- dev_err(&pdev->dev, "SGI Init has been failed with %d\n", ret);
- return ret;
- }
- /* Setup function for the CPU hot-plug cases */
- cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "soc/event:starting",
- xlnx_event_cpuhp_start, xlnx_event_cpuhp_down);
- ret = zynqmp_pm_register_sgi(sgi_num, 0);
- if (ret) {
- dev_err(&pdev->dev, "SGI %d Registration over TF-A failed with %d\n", sgi_num, ret);
- xlnx_event_cleanup_sgi(pdev);
- return ret;
- }
- event_manager_availability = 0;
- dev_info(&pdev->dev, "SGI %d Registered over TF-A\n", sgi_num);
- dev_info(&pdev->dev, "Xilinx Event Management driver probed\n");
- return ret;
- }
- static int xlnx_event_manager_remove(struct platform_device *pdev)
- {
- int i;
- struct registered_event_data *eve_data;
- struct hlist_node *tmp;
- int ret;
- struct agent_cb *cb_pos;
- struct agent_cb *cb_next;
- hash_for_each_safe(reg_driver_map, i, tmp, eve_data, hentry) {
- list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) {
- list_del_init(&cb_pos->list);
- kfree(cb_pos);
- }
- hash_del(&eve_data->hentry);
- kfree(eve_data);
- }
- ret = zynqmp_pm_register_sgi(0, 1);
- if (ret)
- dev_err(&pdev->dev, "SGI unregistration over TF-A failed with %d\n", ret);
- xlnx_event_cleanup_sgi(pdev);
- event_manager_availability = -EACCES;
- return ret;
- }
- static struct platform_driver xlnx_event_manager_driver = {
- .probe = xlnx_event_manager_probe,
- .remove = xlnx_event_manager_remove,
- .driver = {
- .name = "xlnx_event_manager",
- },
- };
- module_param(sgi_num, uint, 0);
- module_platform_driver(xlnx_event_manager_driver);
|