2022-03-08 18:07:50 -08:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* This file implements handling of
* Arm Generic Diagnostic Dump and Reset Interface table ( AGDI )
*
* Copyright ( c ) 2022 , Ampere Computing LLC
*/
# define pr_fmt(fmt) "ACPI: AGDI: " fmt
# include <linux/acpi.h>
2022-04-25 15:18:02 -07:00
# include <linux/acpi_agdi.h>
2022-03-08 18:07:50 -08:00
# include <linux/arm_sdei.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/platform_device.h>
struct agdi_data {
int sdei_event ;
} ;
static int agdi_sdei_handler ( u32 sdei_event , struct pt_regs * regs , void * arg )
{
nmi_panic ( regs , " Arm Generic Diagnostic Dump and Reset SDEI event issued " ) ;
return 0 ;
}
static int agdi_sdei_probe ( struct platform_device * pdev ,
struct agdi_data * adata )
{
int err ;
err = sdei_event_register ( adata - > sdei_event , agdi_sdei_handler , pdev ) ;
if ( err ) {
dev_err ( & pdev - > dev , " Failed to register for SDEI event %d " ,
adata - > sdei_event ) ;
return err ;
}
err = sdei_event_enable ( adata - > sdei_event ) ;
if ( err ) {
sdei_event_unregister ( adata - > sdei_event ) ;
dev_err ( & pdev - > dev , " Failed to enable event %d \n " ,
adata - > sdei_event ) ;
return err ;
}
return 0 ;
}
static int agdi_probe ( struct platform_device * pdev )
{
struct agdi_data * adata = dev_get_platdata ( & pdev - > dev ) ;
if ( ! adata )
return - EINVAL ;
return agdi_sdei_probe ( pdev , adata ) ;
}
static int agdi_remove ( struct platform_device * pdev )
{
struct agdi_data * adata = dev_get_platdata ( & pdev - > dev ) ;
int err , i ;
err = sdei_event_disable ( adata - > sdei_event ) ;
if ( err )
return err ;
for ( i = 0 ; i < 3 ; i + + ) {
err = sdei_event_unregister ( adata - > sdei_event ) ;
if ( err ! = - EINPROGRESS )
break ;
schedule ( ) ;
}
return err ;
}
static struct platform_driver agdi_driver = {
. driver = {
. name = " agdi " ,
} ,
. probe = agdi_probe ,
. remove = agdi_remove ,
} ;
void __init acpi_agdi_init ( void )
{
struct acpi_table_agdi * agdi_table ;
struct agdi_data pdata ;
struct platform_device * pdev ;
acpi_status status ;
status = acpi_get_table ( ACPI_SIG_AGDI , 0 ,
( struct acpi_table_header * * ) & agdi_table ) ;
if ( ACPI_FAILURE ( status ) )
return ;
if ( agdi_table - > flags & ACPI_AGDI_SIGNALING_MODE ) {
pr_warn ( " Interrupt signaling is not supported " ) ;
goto err_put_table ;
}
pdata . sdei_event = agdi_table - > sdei_event ;
pdev = platform_device_register_data ( NULL , " agdi " , 0 , & pdata , sizeof ( pdata ) ) ;
if ( IS_ERR ( pdev ) )
goto err_put_table ;
if ( platform_driver_register ( & agdi_driver ) )
platform_device_unregister ( pdev ) ;
err_put_table :
acpi_put_table ( ( struct acpi_table_header * ) agdi_table ) ;
}