162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Xilinx Event Management Driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2021 Xilinx, Inc. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Abhyuday Godhasara <abhyuday.godhasara@xilinx.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/cpuhotplug.h> 1162306a36Sopenharmony_ci#include <linux/firmware/xlnx-event-manager.h> 1262306a36Sopenharmony_ci#include <linux/firmware/xlnx-zynqmp.h> 1362306a36Sopenharmony_ci#include <linux/hashtable.h> 1462306a36Sopenharmony_ci#include <linux/interrupt.h> 1562306a36Sopenharmony_ci#include <linux/irq.h> 1662306a36Sopenharmony_ci#include <linux/irqdomain.h> 1762306a36Sopenharmony_ci#include <linux/module.h> 1862306a36Sopenharmony_ci#include <linux/of_irq.h> 1962306a36Sopenharmony_ci#include <linux/platform_device.h> 2062306a36Sopenharmony_ci#include <linux/slab.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistatic DEFINE_PER_CPU_READ_MOSTLY(int, cpu_number1); 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_cistatic int virq_sgi; 2562306a36Sopenharmony_cistatic int event_manager_availability = -EACCES; 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci/* SGI number used for Event management driver */ 2862306a36Sopenharmony_ci#define XLNX_EVENT_SGI_NUM (15) 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci/* Max number of driver can register for same event */ 3162306a36Sopenharmony_ci#define MAX_DRIVER_PER_EVENT (10U) 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/* Max HashMap Order for PM API feature check (1<<7 = 128) */ 3462306a36Sopenharmony_ci#define REGISTERED_DRIVER_MAX_ORDER (7) 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#define MAX_BITS (32U) /* Number of bits available for error mask */ 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define FIRMWARE_VERSION_MASK (0xFFFFU) 3962306a36Sopenharmony_ci#define REGISTER_NOTIFIER_FIRMWARE_VERSION (2U) 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic DEFINE_HASHTABLE(reg_driver_map, REGISTERED_DRIVER_MAX_ORDER); 4262306a36Sopenharmony_cistatic int sgi_num = XLNX_EVENT_SGI_NUM; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic bool is_need_to_unregister; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci/** 4762306a36Sopenharmony_ci * struct agent_cb - Registered callback function and private data. 4862306a36Sopenharmony_ci * @agent_data: Data passed back to handler function. 4962306a36Sopenharmony_ci * @eve_cb: Function pointer to store the callback function. 5062306a36Sopenharmony_ci * @list: member to create list. 5162306a36Sopenharmony_ci */ 5262306a36Sopenharmony_cistruct agent_cb { 5362306a36Sopenharmony_ci void *agent_data; 5462306a36Sopenharmony_ci event_cb_func_t eve_cb; 5562306a36Sopenharmony_ci struct list_head list; 5662306a36Sopenharmony_ci}; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci/** 5962306a36Sopenharmony_ci * struct registered_event_data - Registered Event Data. 6062306a36Sopenharmony_ci * @key: key is the combine id(Node-Id | Event-Id) of type u64 6162306a36Sopenharmony_ci * where upper u32 for Node-Id and lower u32 for Event-Id, 6262306a36Sopenharmony_ci * And this used as key to index into hashmap. 6362306a36Sopenharmony_ci * @cb_type: Type of Api callback, like PM_NOTIFY_CB, etc. 6462306a36Sopenharmony_ci * @wake: If this flag set, firmware will wake up processor if is 6562306a36Sopenharmony_ci * in sleep or power down state. 6662306a36Sopenharmony_ci * @cb_list_head: Head of call back data list which contain the information 6762306a36Sopenharmony_ci * about registered handler and private data. 6862306a36Sopenharmony_ci * @hentry: hlist_node that hooks this entry into hashtable. 6962306a36Sopenharmony_ci */ 7062306a36Sopenharmony_cistruct registered_event_data { 7162306a36Sopenharmony_ci u64 key; 7262306a36Sopenharmony_ci enum pm_api_cb_id cb_type; 7362306a36Sopenharmony_ci bool wake; 7462306a36Sopenharmony_ci struct list_head cb_list_head; 7562306a36Sopenharmony_ci struct hlist_node hentry; 7662306a36Sopenharmony_ci}; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic bool xlnx_is_error_event(const u32 node_id) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci if (node_id == EVENT_ERROR_PMC_ERR1 || 8162306a36Sopenharmony_ci node_id == EVENT_ERROR_PMC_ERR2 || 8262306a36Sopenharmony_ci node_id == EVENT_ERROR_PSM_ERR1 || 8362306a36Sopenharmony_ci node_id == EVENT_ERROR_PSM_ERR2) 8462306a36Sopenharmony_ci return true; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci return false; 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic int xlnx_add_cb_for_notify_event(const u32 node_id, const u32 event, const bool wake, 9062306a36Sopenharmony_ci event_cb_func_t cb_fun, void *data) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci u64 key = 0; 9362306a36Sopenharmony_ci bool present_in_hash = false; 9462306a36Sopenharmony_ci struct registered_event_data *eve_data; 9562306a36Sopenharmony_ci struct agent_cb *cb_data; 9662306a36Sopenharmony_ci struct agent_cb *cb_pos; 9762306a36Sopenharmony_ci struct agent_cb *cb_next; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci key = ((u64)node_id << 32U) | (u64)event; 10062306a36Sopenharmony_ci /* Check for existing entry in hash table for given key id */ 10162306a36Sopenharmony_ci hash_for_each_possible(reg_driver_map, eve_data, hentry, key) { 10262306a36Sopenharmony_ci if (eve_data->key == key) { 10362306a36Sopenharmony_ci present_in_hash = true; 10462306a36Sopenharmony_ci break; 10562306a36Sopenharmony_ci } 10662306a36Sopenharmony_ci } 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci if (!present_in_hash) { 10962306a36Sopenharmony_ci /* Add new entry if not present in HASH table */ 11062306a36Sopenharmony_ci eve_data = kmalloc(sizeof(*eve_data), GFP_KERNEL); 11162306a36Sopenharmony_ci if (!eve_data) 11262306a36Sopenharmony_ci return -ENOMEM; 11362306a36Sopenharmony_ci eve_data->key = key; 11462306a36Sopenharmony_ci eve_data->cb_type = PM_NOTIFY_CB; 11562306a36Sopenharmony_ci eve_data->wake = wake; 11662306a36Sopenharmony_ci INIT_LIST_HEAD(&eve_data->cb_list_head); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci cb_data = kmalloc(sizeof(*cb_data), GFP_KERNEL); 11962306a36Sopenharmony_ci if (!cb_data) { 12062306a36Sopenharmony_ci kfree(eve_data); 12162306a36Sopenharmony_ci return -ENOMEM; 12262306a36Sopenharmony_ci } 12362306a36Sopenharmony_ci cb_data->eve_cb = cb_fun; 12462306a36Sopenharmony_ci cb_data->agent_data = data; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci /* Add into callback list */ 12762306a36Sopenharmony_ci list_add(&cb_data->list, &eve_data->cb_list_head); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci /* Add into HASH table */ 13062306a36Sopenharmony_ci hash_add(reg_driver_map, &eve_data->hentry, key); 13162306a36Sopenharmony_ci } else { 13262306a36Sopenharmony_ci /* Search for callback function and private data in list */ 13362306a36Sopenharmony_ci list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) { 13462306a36Sopenharmony_ci if (cb_pos->eve_cb == cb_fun && 13562306a36Sopenharmony_ci cb_pos->agent_data == data) { 13662306a36Sopenharmony_ci return 0; 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci /* Add multiple handler and private data in list */ 14162306a36Sopenharmony_ci cb_data = kmalloc(sizeof(*cb_data), GFP_KERNEL); 14262306a36Sopenharmony_ci if (!cb_data) 14362306a36Sopenharmony_ci return -ENOMEM; 14462306a36Sopenharmony_ci cb_data->eve_cb = cb_fun; 14562306a36Sopenharmony_ci cb_data->agent_data = data; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci list_add(&cb_data->list, &eve_data->cb_list_head); 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci return 0; 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistatic int xlnx_add_cb_for_suspend(event_cb_func_t cb_fun, void *data) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci struct registered_event_data *eve_data; 15662306a36Sopenharmony_ci struct agent_cb *cb_data; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci /* Check for existing entry in hash table for given cb_type */ 15962306a36Sopenharmony_ci hash_for_each_possible(reg_driver_map, eve_data, hentry, PM_INIT_SUSPEND_CB) { 16062306a36Sopenharmony_ci if (eve_data->cb_type == PM_INIT_SUSPEND_CB) { 16162306a36Sopenharmony_ci pr_err("Found as already registered\n"); 16262306a36Sopenharmony_ci return -EINVAL; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci /* Add new entry if not present */ 16762306a36Sopenharmony_ci eve_data = kmalloc(sizeof(*eve_data), GFP_KERNEL); 16862306a36Sopenharmony_ci if (!eve_data) 16962306a36Sopenharmony_ci return -ENOMEM; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci eve_data->key = 0; 17262306a36Sopenharmony_ci eve_data->cb_type = PM_INIT_SUSPEND_CB; 17362306a36Sopenharmony_ci INIT_LIST_HEAD(&eve_data->cb_list_head); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci cb_data = kmalloc(sizeof(*cb_data), GFP_KERNEL); 17662306a36Sopenharmony_ci if (!cb_data) 17762306a36Sopenharmony_ci return -ENOMEM; 17862306a36Sopenharmony_ci cb_data->eve_cb = cb_fun; 17962306a36Sopenharmony_ci cb_data->agent_data = data; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci /* Add into callback list */ 18262306a36Sopenharmony_ci list_add(&cb_data->list, &eve_data->cb_list_head); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci hash_add(reg_driver_map, &eve_data->hentry, PM_INIT_SUSPEND_CB); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci return 0; 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic int xlnx_remove_cb_for_suspend(event_cb_func_t cb_fun) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci bool is_callback_found = false; 19262306a36Sopenharmony_ci struct registered_event_data *eve_data; 19362306a36Sopenharmony_ci struct agent_cb *cb_pos; 19462306a36Sopenharmony_ci struct agent_cb *cb_next; 19562306a36Sopenharmony_ci struct hlist_node *tmp; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci is_need_to_unregister = false; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci /* Check for existing entry in hash table for given cb_type */ 20062306a36Sopenharmony_ci hash_for_each_possible_safe(reg_driver_map, eve_data, tmp, hentry, PM_INIT_SUSPEND_CB) { 20162306a36Sopenharmony_ci if (eve_data->cb_type == PM_INIT_SUSPEND_CB) { 20262306a36Sopenharmony_ci /* Delete the list of callback */ 20362306a36Sopenharmony_ci list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) { 20462306a36Sopenharmony_ci if (cb_pos->eve_cb == cb_fun) { 20562306a36Sopenharmony_ci is_callback_found = true; 20662306a36Sopenharmony_ci list_del_init(&cb_pos->list); 20762306a36Sopenharmony_ci kfree(cb_pos); 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci /* remove an object from a hashtable */ 21162306a36Sopenharmony_ci hash_del(&eve_data->hentry); 21262306a36Sopenharmony_ci kfree(eve_data); 21362306a36Sopenharmony_ci is_need_to_unregister = true; 21462306a36Sopenharmony_ci } 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci if (!is_callback_found) { 21762306a36Sopenharmony_ci pr_warn("Didn't find any registered callback for suspend event\n"); 21862306a36Sopenharmony_ci return -EINVAL; 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci return 0; 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cistatic int xlnx_remove_cb_for_notify_event(const u32 node_id, const u32 event, 22562306a36Sopenharmony_ci event_cb_func_t cb_fun, void *data) 22662306a36Sopenharmony_ci{ 22762306a36Sopenharmony_ci bool is_callback_found = false; 22862306a36Sopenharmony_ci struct registered_event_data *eve_data; 22962306a36Sopenharmony_ci u64 key = ((u64)node_id << 32U) | (u64)event; 23062306a36Sopenharmony_ci struct agent_cb *cb_pos; 23162306a36Sopenharmony_ci struct agent_cb *cb_next; 23262306a36Sopenharmony_ci struct hlist_node *tmp; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci is_need_to_unregister = false; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci /* Check for existing entry in hash table for given key id */ 23762306a36Sopenharmony_ci hash_for_each_possible_safe(reg_driver_map, eve_data, tmp, hentry, key) { 23862306a36Sopenharmony_ci if (eve_data->key == key) { 23962306a36Sopenharmony_ci /* Delete the list of callback */ 24062306a36Sopenharmony_ci list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) { 24162306a36Sopenharmony_ci if (cb_pos->eve_cb == cb_fun && 24262306a36Sopenharmony_ci cb_pos->agent_data == data) { 24362306a36Sopenharmony_ci is_callback_found = true; 24462306a36Sopenharmony_ci list_del_init(&cb_pos->list); 24562306a36Sopenharmony_ci kfree(cb_pos); 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci /* Remove HASH table if callback list is empty */ 25062306a36Sopenharmony_ci if (list_empty(&eve_data->cb_list_head)) { 25162306a36Sopenharmony_ci /* remove an object from a HASH table */ 25262306a36Sopenharmony_ci hash_del(&eve_data->hentry); 25362306a36Sopenharmony_ci kfree(eve_data); 25462306a36Sopenharmony_ci is_need_to_unregister = true; 25562306a36Sopenharmony_ci } 25662306a36Sopenharmony_ci } 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci if (!is_callback_found) { 25962306a36Sopenharmony_ci pr_warn("Didn't find any registered callback for 0x%x 0x%x\n", 26062306a36Sopenharmony_ci node_id, event); 26162306a36Sopenharmony_ci return -EINVAL; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci return 0; 26562306a36Sopenharmony_ci} 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci/** 26862306a36Sopenharmony_ci * xlnx_register_event() - Register for the event. 26962306a36Sopenharmony_ci * @cb_type: Type of callback from pm_api_cb_id, 27062306a36Sopenharmony_ci * PM_NOTIFY_CB - for Error Events, 27162306a36Sopenharmony_ci * PM_INIT_SUSPEND_CB - for suspend callback. 27262306a36Sopenharmony_ci * @node_id: Node-Id related to event. 27362306a36Sopenharmony_ci * @event: Event Mask for the Error Event. 27462306a36Sopenharmony_ci * @wake: Flag specifying whether the subsystem should be woken upon 27562306a36Sopenharmony_ci * event notification. 27662306a36Sopenharmony_ci * @cb_fun: Function pointer to store the callback function. 27762306a36Sopenharmony_ci * @data: Pointer for the driver instance. 27862306a36Sopenharmony_ci * 27962306a36Sopenharmony_ci * Return: Returns 0 on successful registration else error code. 28062306a36Sopenharmony_ci */ 28162306a36Sopenharmony_ciint xlnx_register_event(const enum pm_api_cb_id cb_type, const u32 node_id, const u32 event, 28262306a36Sopenharmony_ci const bool wake, event_cb_func_t cb_fun, void *data) 28362306a36Sopenharmony_ci{ 28462306a36Sopenharmony_ci int ret = 0; 28562306a36Sopenharmony_ci u32 eve; 28662306a36Sopenharmony_ci int pos; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci if (event_manager_availability) 28962306a36Sopenharmony_ci return event_manager_availability; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci if (cb_type != PM_NOTIFY_CB && cb_type != PM_INIT_SUSPEND_CB) { 29262306a36Sopenharmony_ci pr_err("%s() Unsupported Callback 0x%x\n", __func__, cb_type); 29362306a36Sopenharmony_ci return -EINVAL; 29462306a36Sopenharmony_ci } 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci if (!cb_fun) 29762306a36Sopenharmony_ci return -EFAULT; 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci if (cb_type == PM_INIT_SUSPEND_CB) { 30062306a36Sopenharmony_ci ret = xlnx_add_cb_for_suspend(cb_fun, data); 30162306a36Sopenharmony_ci } else { 30262306a36Sopenharmony_ci if (!xlnx_is_error_event(node_id)) { 30362306a36Sopenharmony_ci /* Add entry for Node-Id/Event in hash table */ 30462306a36Sopenharmony_ci ret = xlnx_add_cb_for_notify_event(node_id, event, wake, cb_fun, data); 30562306a36Sopenharmony_ci } else { 30662306a36Sopenharmony_ci /* Add into Hash table */ 30762306a36Sopenharmony_ci for (pos = 0; pos < MAX_BITS; pos++) { 30862306a36Sopenharmony_ci eve = event & (1 << pos); 30962306a36Sopenharmony_ci if (!eve) 31062306a36Sopenharmony_ci continue; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* Add entry for Node-Id/Eve in hash table */ 31362306a36Sopenharmony_ci ret = xlnx_add_cb_for_notify_event(node_id, eve, wake, cb_fun, 31462306a36Sopenharmony_ci data); 31562306a36Sopenharmony_ci /* Break the loop if got error */ 31662306a36Sopenharmony_ci if (ret) 31762306a36Sopenharmony_ci break; 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci if (ret) { 32062306a36Sopenharmony_ci /* Skip the Event for which got the error */ 32162306a36Sopenharmony_ci pos--; 32262306a36Sopenharmony_ci /* Remove registered(during this call) event from hash table */ 32362306a36Sopenharmony_ci for ( ; pos >= 0; pos--) { 32462306a36Sopenharmony_ci eve = event & (1 << pos); 32562306a36Sopenharmony_ci if (!eve) 32662306a36Sopenharmony_ci continue; 32762306a36Sopenharmony_ci xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun, data); 32862306a36Sopenharmony_ci } 32962306a36Sopenharmony_ci } 33062306a36Sopenharmony_ci } 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci if (ret) { 33362306a36Sopenharmony_ci pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, node_id, 33462306a36Sopenharmony_ci event, ret); 33562306a36Sopenharmony_ci return ret; 33662306a36Sopenharmony_ci } 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci /* Register for Node-Id/Event combination in firmware */ 33962306a36Sopenharmony_ci ret = zynqmp_pm_register_notifier(node_id, event, wake, true); 34062306a36Sopenharmony_ci if (ret) { 34162306a36Sopenharmony_ci pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, node_id, 34262306a36Sopenharmony_ci event, ret); 34362306a36Sopenharmony_ci /* Remove already registered event from hash table */ 34462306a36Sopenharmony_ci if (xlnx_is_error_event(node_id)) { 34562306a36Sopenharmony_ci for (pos = 0; pos < MAX_BITS; pos++) { 34662306a36Sopenharmony_ci eve = event & (1 << pos); 34762306a36Sopenharmony_ci if (!eve) 34862306a36Sopenharmony_ci continue; 34962306a36Sopenharmony_ci xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun, data); 35062306a36Sopenharmony_ci } 35162306a36Sopenharmony_ci } else { 35262306a36Sopenharmony_ci xlnx_remove_cb_for_notify_event(node_id, event, cb_fun, data); 35362306a36Sopenharmony_ci } 35462306a36Sopenharmony_ci return ret; 35562306a36Sopenharmony_ci } 35662306a36Sopenharmony_ci } 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci return ret; 35962306a36Sopenharmony_ci} 36062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(xlnx_register_event); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci/** 36362306a36Sopenharmony_ci * xlnx_unregister_event() - Unregister for the event. 36462306a36Sopenharmony_ci * @cb_type: Type of callback from pm_api_cb_id, 36562306a36Sopenharmony_ci * PM_NOTIFY_CB - for Error Events, 36662306a36Sopenharmony_ci * PM_INIT_SUSPEND_CB - for suspend callback. 36762306a36Sopenharmony_ci * @node_id: Node-Id related to event. 36862306a36Sopenharmony_ci * @event: Event Mask for the Error Event. 36962306a36Sopenharmony_ci * @cb_fun: Function pointer of callback function. 37062306a36Sopenharmony_ci * @data: Pointer of agent's private data. 37162306a36Sopenharmony_ci * 37262306a36Sopenharmony_ci * Return: Returns 0 on successful unregistration else error code. 37362306a36Sopenharmony_ci */ 37462306a36Sopenharmony_ciint xlnx_unregister_event(const enum pm_api_cb_id cb_type, const u32 node_id, const u32 event, 37562306a36Sopenharmony_ci event_cb_func_t cb_fun, void *data) 37662306a36Sopenharmony_ci{ 37762306a36Sopenharmony_ci int ret = 0; 37862306a36Sopenharmony_ci u32 eve, pos; 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci is_need_to_unregister = false; 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci if (event_manager_availability) 38362306a36Sopenharmony_ci return event_manager_availability; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci if (cb_type != PM_NOTIFY_CB && cb_type != PM_INIT_SUSPEND_CB) { 38662306a36Sopenharmony_ci pr_err("%s() Unsupported Callback 0x%x\n", __func__, cb_type); 38762306a36Sopenharmony_ci return -EINVAL; 38862306a36Sopenharmony_ci } 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci if (!cb_fun) 39162306a36Sopenharmony_ci return -EFAULT; 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci if (cb_type == PM_INIT_SUSPEND_CB) { 39462306a36Sopenharmony_ci ret = xlnx_remove_cb_for_suspend(cb_fun); 39562306a36Sopenharmony_ci } else { 39662306a36Sopenharmony_ci /* Remove Node-Id/Event from hash table */ 39762306a36Sopenharmony_ci if (!xlnx_is_error_event(node_id)) { 39862306a36Sopenharmony_ci xlnx_remove_cb_for_notify_event(node_id, event, cb_fun, data); 39962306a36Sopenharmony_ci } else { 40062306a36Sopenharmony_ci for (pos = 0; pos < MAX_BITS; pos++) { 40162306a36Sopenharmony_ci eve = event & (1 << pos); 40262306a36Sopenharmony_ci if (!eve) 40362306a36Sopenharmony_ci continue; 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun, data); 40662306a36Sopenharmony_ci } 40762306a36Sopenharmony_ci } 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci /* Un-register if list is empty */ 41062306a36Sopenharmony_ci if (is_need_to_unregister) { 41162306a36Sopenharmony_ci /* Un-register for Node-Id/Event combination */ 41262306a36Sopenharmony_ci ret = zynqmp_pm_register_notifier(node_id, event, false, false); 41362306a36Sopenharmony_ci if (ret) { 41462306a36Sopenharmony_ci pr_err("%s() failed for 0x%x and 0x%x: %d\n", 41562306a36Sopenharmony_ci __func__, node_id, event, ret); 41662306a36Sopenharmony_ci return ret; 41762306a36Sopenharmony_ci } 41862306a36Sopenharmony_ci } 41962306a36Sopenharmony_ci } 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci return ret; 42262306a36Sopenharmony_ci} 42362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(xlnx_unregister_event); 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_cistatic void xlnx_call_suspend_cb_handler(const u32 *payload) 42662306a36Sopenharmony_ci{ 42762306a36Sopenharmony_ci bool is_callback_found = false; 42862306a36Sopenharmony_ci struct registered_event_data *eve_data; 42962306a36Sopenharmony_ci u32 cb_type = payload[0]; 43062306a36Sopenharmony_ci struct agent_cb *cb_pos; 43162306a36Sopenharmony_ci struct agent_cb *cb_next; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci /* Check for existing entry in hash table for given cb_type */ 43462306a36Sopenharmony_ci hash_for_each_possible(reg_driver_map, eve_data, hentry, cb_type) { 43562306a36Sopenharmony_ci if (eve_data->cb_type == cb_type) { 43662306a36Sopenharmony_ci list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) { 43762306a36Sopenharmony_ci cb_pos->eve_cb(&payload[0], cb_pos->agent_data); 43862306a36Sopenharmony_ci is_callback_found = true; 43962306a36Sopenharmony_ci } 44062306a36Sopenharmony_ci } 44162306a36Sopenharmony_ci } 44262306a36Sopenharmony_ci if (!is_callback_found) 44362306a36Sopenharmony_ci pr_warn("Didn't find any registered callback for suspend event\n"); 44462306a36Sopenharmony_ci} 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_cistatic void xlnx_call_notify_cb_handler(const u32 *payload) 44762306a36Sopenharmony_ci{ 44862306a36Sopenharmony_ci bool is_callback_found = false; 44962306a36Sopenharmony_ci struct registered_event_data *eve_data; 45062306a36Sopenharmony_ci u64 key = ((u64)payload[1] << 32U) | (u64)payload[2]; 45162306a36Sopenharmony_ci int ret; 45262306a36Sopenharmony_ci struct agent_cb *cb_pos; 45362306a36Sopenharmony_ci struct agent_cb *cb_next; 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci /* Check for existing entry in hash table for given key id */ 45662306a36Sopenharmony_ci hash_for_each_possible(reg_driver_map, eve_data, hentry, key) { 45762306a36Sopenharmony_ci if (eve_data->key == key) { 45862306a36Sopenharmony_ci list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) { 45962306a36Sopenharmony_ci cb_pos->eve_cb(&payload[0], cb_pos->agent_data); 46062306a36Sopenharmony_ci is_callback_found = true; 46162306a36Sopenharmony_ci } 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci /* re register with firmware to get future events */ 46462306a36Sopenharmony_ci ret = zynqmp_pm_register_notifier(payload[1], payload[2], 46562306a36Sopenharmony_ci eve_data->wake, true); 46662306a36Sopenharmony_ci if (ret) { 46762306a36Sopenharmony_ci pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, 46862306a36Sopenharmony_ci payload[1], payload[2], ret); 46962306a36Sopenharmony_ci list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, 47062306a36Sopenharmony_ci list) { 47162306a36Sopenharmony_ci /* Remove already registered event from hash table */ 47262306a36Sopenharmony_ci xlnx_remove_cb_for_notify_event(payload[1], payload[2], 47362306a36Sopenharmony_ci cb_pos->eve_cb, 47462306a36Sopenharmony_ci cb_pos->agent_data); 47562306a36Sopenharmony_ci } 47662306a36Sopenharmony_ci } 47762306a36Sopenharmony_ci } 47862306a36Sopenharmony_ci } 47962306a36Sopenharmony_ci if (!is_callback_found) 48062306a36Sopenharmony_ci pr_warn("Unhandled SGI node 0x%x event 0x%x. Expected with Xen hypervisor\n", 48162306a36Sopenharmony_ci payload[1], payload[2]); 48262306a36Sopenharmony_ci} 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_cistatic void xlnx_get_event_callback_data(u32 *buf) 48562306a36Sopenharmony_ci{ 48662306a36Sopenharmony_ci zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf); 48762306a36Sopenharmony_ci} 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_cistatic irqreturn_t xlnx_event_handler(int irq, void *dev_id) 49062306a36Sopenharmony_ci{ 49162306a36Sopenharmony_ci u32 cb_type, node_id, event, pos; 49262306a36Sopenharmony_ci u32 payload[CB_MAX_PAYLOAD_SIZE] = {0}; 49362306a36Sopenharmony_ci u32 event_data[CB_MAX_PAYLOAD_SIZE] = {0}; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci /* Get event data */ 49662306a36Sopenharmony_ci xlnx_get_event_callback_data(payload); 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci /* First element is callback type, others are callback arguments */ 49962306a36Sopenharmony_ci cb_type = payload[0]; 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci if (cb_type == PM_NOTIFY_CB) { 50262306a36Sopenharmony_ci node_id = payload[1]; 50362306a36Sopenharmony_ci event = payload[2]; 50462306a36Sopenharmony_ci if (!xlnx_is_error_event(node_id)) { 50562306a36Sopenharmony_ci xlnx_call_notify_cb_handler(payload); 50662306a36Sopenharmony_ci } else { 50762306a36Sopenharmony_ci /* 50862306a36Sopenharmony_ci * Each call back function expecting payload as an input arguments. 50962306a36Sopenharmony_ci * We can get multiple error events as in one call back through error 51062306a36Sopenharmony_ci * mask. So payload[2] may can contain multiple error events. 51162306a36Sopenharmony_ci * In reg_driver_map database we store data in the combination of single 51262306a36Sopenharmony_ci * node_id-error combination. 51362306a36Sopenharmony_ci * So coping the payload message into event_data and update the 51462306a36Sopenharmony_ci * event_data[2] with Error Mask for single error event and use 51562306a36Sopenharmony_ci * event_data as input argument for registered call back function. 51662306a36Sopenharmony_ci * 51762306a36Sopenharmony_ci */ 51862306a36Sopenharmony_ci memcpy(event_data, payload, (4 * CB_MAX_PAYLOAD_SIZE)); 51962306a36Sopenharmony_ci /* Support Multiple Error Event */ 52062306a36Sopenharmony_ci for (pos = 0; pos < MAX_BITS; pos++) { 52162306a36Sopenharmony_ci if ((0 == (event & (1 << pos)))) 52262306a36Sopenharmony_ci continue; 52362306a36Sopenharmony_ci event_data[2] = (event & (1 << pos)); 52462306a36Sopenharmony_ci xlnx_call_notify_cb_handler(event_data); 52562306a36Sopenharmony_ci } 52662306a36Sopenharmony_ci } 52762306a36Sopenharmony_ci } else if (cb_type == PM_INIT_SUSPEND_CB) { 52862306a36Sopenharmony_ci xlnx_call_suspend_cb_handler(payload); 52962306a36Sopenharmony_ci } else { 53062306a36Sopenharmony_ci pr_err("%s() Unsupported Callback %d\n", __func__, cb_type); 53162306a36Sopenharmony_ci } 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci return IRQ_HANDLED; 53462306a36Sopenharmony_ci} 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_cistatic int xlnx_event_cpuhp_start(unsigned int cpu) 53762306a36Sopenharmony_ci{ 53862306a36Sopenharmony_ci enable_percpu_irq(virq_sgi, IRQ_TYPE_NONE); 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci return 0; 54162306a36Sopenharmony_ci} 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_cistatic int xlnx_event_cpuhp_down(unsigned int cpu) 54462306a36Sopenharmony_ci{ 54562306a36Sopenharmony_ci disable_percpu_irq(virq_sgi); 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci return 0; 54862306a36Sopenharmony_ci} 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_cistatic void xlnx_disable_percpu_irq(void *data) 55162306a36Sopenharmony_ci{ 55262306a36Sopenharmony_ci disable_percpu_irq(virq_sgi); 55362306a36Sopenharmony_ci} 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_cistatic int xlnx_event_init_sgi(struct platform_device *pdev) 55662306a36Sopenharmony_ci{ 55762306a36Sopenharmony_ci int ret = 0; 55862306a36Sopenharmony_ci int cpu; 55962306a36Sopenharmony_ci /* 56062306a36Sopenharmony_ci * IRQ related structures are used for the following: 56162306a36Sopenharmony_ci * for each SGI interrupt ensure its mapped by GIC IRQ domain 56262306a36Sopenharmony_ci * and that each corresponding linux IRQ for the HW IRQ has 56362306a36Sopenharmony_ci * a handler for when receiving an interrupt from the remote 56462306a36Sopenharmony_ci * processor. 56562306a36Sopenharmony_ci */ 56662306a36Sopenharmony_ci struct irq_domain *domain; 56762306a36Sopenharmony_ci struct irq_fwspec sgi_fwspec; 56862306a36Sopenharmony_ci struct device_node *interrupt_parent = NULL; 56962306a36Sopenharmony_ci struct device *parent = pdev->dev.parent; 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci /* Find GIC controller to map SGIs. */ 57262306a36Sopenharmony_ci interrupt_parent = of_irq_find_parent(parent->of_node); 57362306a36Sopenharmony_ci if (!interrupt_parent) { 57462306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to find property for Interrupt parent\n"); 57562306a36Sopenharmony_ci return -EINVAL; 57662306a36Sopenharmony_ci } 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci /* Each SGI needs to be associated with GIC's IRQ domain. */ 57962306a36Sopenharmony_ci domain = irq_find_host(interrupt_parent); 58062306a36Sopenharmony_ci of_node_put(interrupt_parent); 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci /* Each mapping needs GIC domain when finding IRQ mapping. */ 58362306a36Sopenharmony_ci sgi_fwspec.fwnode = domain->fwnode; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci /* 58662306a36Sopenharmony_ci * When irq domain looks at mapping each arg is as follows: 58762306a36Sopenharmony_ci * 3 args for: interrupt type (SGI), interrupt # (set later), type 58862306a36Sopenharmony_ci */ 58962306a36Sopenharmony_ci sgi_fwspec.param_count = 1; 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci /* Set SGI's hwirq */ 59262306a36Sopenharmony_ci sgi_fwspec.param[0] = sgi_num; 59362306a36Sopenharmony_ci virq_sgi = irq_create_fwspec_mapping(&sgi_fwspec); 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci cpu = get_cpu(); 59662306a36Sopenharmony_ci per_cpu(cpu_number1, cpu) = cpu; 59762306a36Sopenharmony_ci ret = request_percpu_irq(virq_sgi, xlnx_event_handler, "xlnx_event_mgmt", 59862306a36Sopenharmony_ci &cpu_number1); 59962306a36Sopenharmony_ci put_cpu(); 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci WARN_ON(ret); 60262306a36Sopenharmony_ci if (ret) { 60362306a36Sopenharmony_ci irq_dispose_mapping(virq_sgi); 60462306a36Sopenharmony_ci return ret; 60562306a36Sopenharmony_ci } 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_ci irq_to_desc(virq_sgi); 60862306a36Sopenharmony_ci irq_set_status_flags(virq_sgi, IRQ_PER_CPU); 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci return ret; 61162306a36Sopenharmony_ci} 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_cistatic void xlnx_event_cleanup_sgi(struct platform_device *pdev) 61462306a36Sopenharmony_ci{ 61562306a36Sopenharmony_ci int cpu = smp_processor_id(); 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci per_cpu(cpu_number1, cpu) = cpu; 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_ci cpuhp_remove_state(CPUHP_AP_ONLINE_DYN); 62062306a36Sopenharmony_ci 62162306a36Sopenharmony_ci on_each_cpu(xlnx_disable_percpu_irq, NULL, 1); 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci irq_clear_status_flags(virq_sgi, IRQ_PER_CPU); 62462306a36Sopenharmony_ci free_percpu_irq(virq_sgi, &cpu_number1); 62562306a36Sopenharmony_ci irq_dispose_mapping(virq_sgi); 62662306a36Sopenharmony_ci} 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_cistatic int xlnx_event_manager_probe(struct platform_device *pdev) 62962306a36Sopenharmony_ci{ 63062306a36Sopenharmony_ci int ret; 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_ci ret = zynqmp_pm_feature(PM_REGISTER_NOTIFIER); 63362306a36Sopenharmony_ci if (ret < 0) { 63462306a36Sopenharmony_ci dev_err(&pdev->dev, "Feature check failed with %d\n", ret); 63562306a36Sopenharmony_ci return ret; 63662306a36Sopenharmony_ci } 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci if ((ret & FIRMWARE_VERSION_MASK) < 63962306a36Sopenharmony_ci REGISTER_NOTIFIER_FIRMWARE_VERSION) { 64062306a36Sopenharmony_ci dev_err(&pdev->dev, "Register notifier version error. Expected Firmware: v%d - Found: v%d\n", 64162306a36Sopenharmony_ci REGISTER_NOTIFIER_FIRMWARE_VERSION, 64262306a36Sopenharmony_ci ret & FIRMWARE_VERSION_MASK); 64362306a36Sopenharmony_ci return -EOPNOTSUPP; 64462306a36Sopenharmony_ci } 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_ci /* Initialize the SGI */ 64762306a36Sopenharmony_ci ret = xlnx_event_init_sgi(pdev); 64862306a36Sopenharmony_ci if (ret) { 64962306a36Sopenharmony_ci dev_err(&pdev->dev, "SGI Init has been failed with %d\n", ret); 65062306a36Sopenharmony_ci return ret; 65162306a36Sopenharmony_ci } 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci /* Setup function for the CPU hot-plug cases */ 65462306a36Sopenharmony_ci cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "soc/event:starting", 65562306a36Sopenharmony_ci xlnx_event_cpuhp_start, xlnx_event_cpuhp_down); 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ci ret = zynqmp_pm_register_sgi(sgi_num, 0); 65862306a36Sopenharmony_ci if (ret) { 65962306a36Sopenharmony_ci dev_err(&pdev->dev, "SGI %d Registration over TF-A failed with %d\n", sgi_num, ret); 66062306a36Sopenharmony_ci xlnx_event_cleanup_sgi(pdev); 66162306a36Sopenharmony_ci return ret; 66262306a36Sopenharmony_ci } 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci event_manager_availability = 0; 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci dev_info(&pdev->dev, "SGI %d Registered over TF-A\n", sgi_num); 66762306a36Sopenharmony_ci dev_info(&pdev->dev, "Xilinx Event Management driver probed\n"); 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci return ret; 67062306a36Sopenharmony_ci} 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_cistatic void xlnx_event_manager_remove(struct platform_device *pdev) 67362306a36Sopenharmony_ci{ 67462306a36Sopenharmony_ci int i; 67562306a36Sopenharmony_ci struct registered_event_data *eve_data; 67662306a36Sopenharmony_ci struct hlist_node *tmp; 67762306a36Sopenharmony_ci int ret; 67862306a36Sopenharmony_ci struct agent_cb *cb_pos; 67962306a36Sopenharmony_ci struct agent_cb *cb_next; 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci hash_for_each_safe(reg_driver_map, i, tmp, eve_data, hentry) { 68262306a36Sopenharmony_ci list_for_each_entry_safe(cb_pos, cb_next, &eve_data->cb_list_head, list) { 68362306a36Sopenharmony_ci list_del_init(&cb_pos->list); 68462306a36Sopenharmony_ci kfree(cb_pos); 68562306a36Sopenharmony_ci } 68662306a36Sopenharmony_ci hash_del(&eve_data->hentry); 68762306a36Sopenharmony_ci kfree(eve_data); 68862306a36Sopenharmony_ci } 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_ci ret = zynqmp_pm_register_sgi(0, 1); 69162306a36Sopenharmony_ci if (ret) 69262306a36Sopenharmony_ci dev_err(&pdev->dev, "SGI unregistration over TF-A failed with %d\n", ret); 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci xlnx_event_cleanup_sgi(pdev); 69562306a36Sopenharmony_ci 69662306a36Sopenharmony_ci event_manager_availability = -EACCES; 69762306a36Sopenharmony_ci} 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_cistatic struct platform_driver xlnx_event_manager_driver = { 70062306a36Sopenharmony_ci .probe = xlnx_event_manager_probe, 70162306a36Sopenharmony_ci .remove_new = xlnx_event_manager_remove, 70262306a36Sopenharmony_ci .driver = { 70362306a36Sopenharmony_ci .name = "xlnx_event_manager", 70462306a36Sopenharmony_ci }, 70562306a36Sopenharmony_ci}; 70662306a36Sopenharmony_cimodule_param(sgi_num, uint, 0); 70762306a36Sopenharmony_cimodule_platform_driver(xlnx_event_manager_driver); 708