2021-11-28 23:02:14 -08:00
// SPDX-License-Identifier: GPL-2.0
/*
* Xilinx Event Management Driver
*
* Copyright ( C ) 2021 Xilinx , Inc .
*
* Abhyuday Godhasara < abhyuday . godhasara @ xilinx . com >
*/
# 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 ;
2022-04-27 00:48:03 -07:00
static bool is_need_to_unregister ;
2022-04-27 00:48:02 -07:00
/**
* 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 ;
} ;
2021-11-28 23:02:14 -08:00
/**
* 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 .
2022-04-27 00:48:02 -07:00
* @ wake : If this flag set , firmware will wake up processor if is
2021-11-28 23:02:14 -08:00
* in sleep or power down state .
2022-04-27 00:48:02 -07:00
* @ cb_list_head : Head of call back data list which contain the information
* about registered handler and private data .
2021-11-28 23:02:14 -08:00
* @ hentry : hlist_node that hooks this entry into hashtable .
*/
struct registered_event_data {
u64 key ;
enum pm_api_cb_id cb_type ;
bool wake ;
2022-04-27 00:48:02 -07:00
struct list_head cb_list_head ;
2021-11-28 23:02:14 -08:00
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 ;
2022-04-27 00:48:02 -07:00
bool present_in_hash = false ;
2021-11-28 23:02:14 -08:00
struct registered_event_data * eve_data ;
2022-04-27 00:48:02 -07:00
struct agent_cb * cb_data ;
struct agent_cb * cb_pos ;
struct agent_cb * cb_next ;
2021-11-28 23:02:14 -08:00
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 ) {
2022-04-27 00:48:02 -07:00
present_in_hash = true ;
break ;
2021-11-28 23:02:14 -08:00
}
}
2022-04-27 00:48:02 -07:00
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 )
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 ;
}
}
2021-11-28 23:02:14 -08:00
2022-04-27 00:48:02 -07:00
/* 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 ;
2021-11-28 23:02:14 -08:00
2022-04-27 00:48:02 -07:00
list_add ( & cb_data - > list , & eve_data - > cb_list_head ) ;
}
2021-11-28 23:02:14 -08:00
return 0 ;
}
static int xlnx_add_cb_for_suspend ( event_cb_func_t cb_fun , void * data )
{
struct registered_event_data * eve_data ;
2022-04-27 00:48:02 -07:00
struct agent_cb * cb_data ;
2021-11-28 23:02:14 -08:00
/* 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 ;
2022-04-27 00:48:02 -07:00
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 ) ;
2021-11-28 23:02:14 -08:00
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 ;
2022-04-27 00:48:02 -07:00
struct agent_cb * cb_pos ;
struct agent_cb * cb_next ;
2021-11-28 23:02:14 -08:00
2022-04-27 00:48:03 -07:00
is_need_to_unregister = false ;
2021-11-28 23:02:14 -08:00
/* 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 ) {
2022-04-27 00:48:02 -07:00
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 ) ;
}
}
2021-11-28 23:02:14 -08:00
/* remove an object from a hashtable */
hash_del ( & eve_data - > hentry ) ;
kfree ( eve_data ) ;
2022-04-27 00:48:03 -07:00
is_need_to_unregister = true ;
2021-11-28 23:02:14 -08:00
}
}
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 ,
2022-04-27 00:48:03 -07:00
event_cb_func_t cb_fun , void * data )
2021-11-28 23:02:14 -08:00
{
bool is_callback_found = false ;
struct registered_event_data * eve_data ;
u64 key = ( ( u64 ) node_id < < 32U ) | ( u64 ) event ;
2022-04-27 00:48:02 -07:00
struct agent_cb * cb_pos ;
struct agent_cb * cb_next ;
2021-11-28 23:02:14 -08:00
2022-04-27 00:48:03 -07:00
is_need_to_unregister = false ;
2021-11-28 23:02:14 -08:00
/* Check for existing entry in hash table for given key id */
hash_for_each_possible ( reg_driver_map , eve_data , hentry , key ) {
2022-04-27 00:48:02 -07:00
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 ) {
2022-04-27 00:48:03 -07:00
if ( cb_pos - > eve_cb = = cb_fun & &
cb_pos - > agent_data = = data ) {
2022-04-27 00:48:02 -07:00
is_callback_found = true ;
list_del_init ( & cb_pos - > list ) ;
kfree ( cb_pos ) ;
}
}
2022-04-27 00:48:03 -07:00
/* 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 ;
}
2021-11-28 23:02:14 -08:00
}
}
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 ;
2022-04-27 00:48:03 -07:00
xlnx_remove_cb_for_notify_event ( node_id , eve , cb_fun , data ) ;
2021-11-28 23:02:14 -08:00
}
}
}
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 ;
2022-04-27 00:48:03 -07:00
xlnx_remove_cb_for_notify_event ( node_id , eve , cb_fun , data ) ;
2021-11-28 23:02:14 -08:00
}
} else {
2022-04-27 00:48:03 -07:00
xlnx_remove_cb_for_notify_event ( node_id , event , cb_fun , data ) ;
2021-11-28 23:02:14 -08:00
}
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 .
2022-04-27 00:48:03 -07:00
* @ data : Pointer of agent ' s private data .
2021-11-28 23:02:14 -08:00
*
* 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 ,
2022-04-27 00:48:03 -07:00
event_cb_func_t cb_fun , void * data )
2021-11-28 23:02:14 -08:00
{
2022-04-27 00:48:03 -07:00
int ret = 0 ;
2021-11-28 23:02:14 -08:00
u32 eve , pos ;
2022-04-27 00:48:03 -07:00
is_need_to_unregister = false ;
2021-11-28 23:02:14 -08:00
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 ) ) {
2022-04-27 00:48:03 -07:00
xlnx_remove_cb_for_notify_event ( node_id , event , cb_fun , data ) ;
2021-11-28 23:02:14 -08:00
} else {
for ( pos = 0 ; pos < MAX_BITS ; pos + + ) {
eve = event & ( 1 < < pos ) ;
if ( ! eve )
continue ;
2022-04-27 00:48:03 -07:00
xlnx_remove_cb_for_notify_event ( node_id , eve , cb_fun , data ) ;
2021-11-28 23:02:14 -08:00
}
}
2022-04-27 00:48:03 -07:00
/* 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 ;
}
2021-11-28 23:02:14 -08:00
}
}
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 ] ;
2022-04-27 00:48:02 -07:00
struct agent_cb * cb_pos ;
struct agent_cb * cb_next ;
2021-11-28 23:02:14 -08:00
/* 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 ) {
2022-04-27 00:48:02 -07:00
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 ;
}
2021-11-28 23:02:14 -08:00
}
}
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 ;
2022-04-27 00:48:02 -07:00
struct agent_cb * cb_pos ;
struct agent_cb * cb_next ;
2021-11-28 23:02:14 -08:00
/* 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 ) {
2022-04-27 00:48:02 -07:00
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 ;
}
2021-11-28 23:02:14 -08:00
/* 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 ) ;
2022-04-27 00:48:02 -07:00
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 ] ,
2022-04-27 00:48:03 -07:00
cb_pos - > eve_cb ,
cb_pos - > agent_data ) ;
2022-04-27 00:48:02 -07:00
}
2021-11-28 23:02:14 -08:00
}
}
}
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 ) ;
2022-06-07 15:42:54 -07:00
ret = zynqmp_pm_register_sgi ( sgi_num , 0 ) ;
2021-11-28 23:02:14 -08:00
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 ;
2022-04-27 00:48:02 -07:00
struct agent_cb * cb_pos ;
struct agent_cb * cb_next ;
2021-11-28 23:02:14 -08:00
hash_for_each_safe ( reg_driver_map , i , tmp , eve_data , hentry ) {
2022-04-27 00:48:02 -07:00
list_for_each_entry_safe ( cb_pos , cb_next , & eve_data - > cb_list_head , list ) {
list_del_init ( & cb_pos - > list ) ;
kfree ( cb_pos ) ;
}
2021-11-28 23:02:14 -08:00
hash_del ( & eve_data - > hentry ) ;
kfree ( eve_data ) ;
}
2022-06-07 15:42:54 -07:00
ret = zynqmp_pm_register_sgi ( 0 , 1 ) ;
2021-11-28 23:02:14 -08:00
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 ) ;