2018-02-20 16:30:22 +03:00
// SPDX-License-Identifier: GPL-2.0+
2017-09-13 06:55:57 +03:00
/*
* ipmi_si_platform . c
*
* Handling for platform devices in IPMI ( ACPI , OF , and things
* coming from the platform .
*/
2018-05-09 18:15:48 +03:00
# define pr_fmt(fmt) "ipmi_platform: " fmt
# define dev_fmt pr_fmt
2017-09-13 06:55:57 +03:00
# include <linux/types.h>
# include <linux/module.h>
# include <linux/of_device.h>
# include <linux/of_platform.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/acpi.h>
# include "ipmi_si.h"
2017-09-18 20:38:17 +03:00
# include "ipmi_dmi.h"
2017-09-13 06:55:57 +03:00
2019-05-17 13:12:44 +03:00
static bool platform_registered ;
2017-09-13 06:55:57 +03:00
static bool si_tryplatform = true ;
# ifdef CONFIG_ACPI
static bool si_tryacpi = true ;
# endif
2017-09-18 20:38:17 +03:00
# ifdef CONFIG_OF
static bool si_tryopenfirmware = true ;
# endif
2017-09-13 06:55:57 +03:00
# ifdef CONFIG_DMI
static bool si_trydmi = true ;
2017-09-18 20:38:17 +03:00
# else
static bool si_trydmi = false ;
2017-09-13 06:55:57 +03:00
# endif
module_param_named ( tryplatform , si_tryplatform , bool , 0 ) ;
MODULE_PARM_DESC ( tryplatform , " Setting this to zero will disable the "
" default scan of the interfaces identified via platform "
2017-09-18 20:38:17 +03:00
" interfaces besides ACPI, OpenFirmware, and DMI " ) ;
2017-09-13 06:55:57 +03:00
# ifdef CONFIG_ACPI
module_param_named ( tryacpi , si_tryacpi , bool , 0 ) ;
MODULE_PARM_DESC ( tryacpi , " Setting this to zero will disable the "
" default scan of the interfaces identified via ACPI " ) ;
# endif
2017-09-18 20:38:17 +03:00
# ifdef CONFIG_OF
module_param_named ( tryopenfirmware , si_tryopenfirmware , bool , 0 ) ;
2017-11-03 12:52:45 +03:00
MODULE_PARM_DESC ( tryopenfirmware , " Setting this to zero will disable the "
2017-09-18 20:38:17 +03:00
" default scan of the interfaces identified via OpenFirmware " ) ;
# endif
2017-09-13 06:55:57 +03:00
# ifdef CONFIG_DMI
module_param_named ( trydmi , si_trydmi , bool , 0 ) ;
MODULE_PARM_DESC ( trydmi , " Setting this to zero will disable the "
" default scan of the interfaces identified via DMI " ) ;
# endif
# ifdef CONFIG_ACPI
/* For GPE-type interrupts. */
static u32 ipmi_acpi_gpe ( acpi_handle gpe_device ,
u32 gpe_number , void * context )
{
struct si_sm_io * io = context ;
ipmi_si_irq_handler ( io - > irq , io - > irq_handler_data ) ;
return ACPI_INTERRUPT_HANDLED ;
}
static void acpi_gpe_irq_cleanup ( struct si_sm_io * io )
{
if ( ! io - > irq )
return ;
ipmi_irq_start_cleanup ( io ) ;
acpi_remove_gpe_handler ( NULL , io - > irq , & ipmi_acpi_gpe ) ;
}
static int acpi_gpe_irq_setup ( struct si_sm_io * io )
{
acpi_status status ;
if ( ! io - > irq )
return 0 ;
status = acpi_install_gpe_handler ( NULL ,
io - > irq ,
ACPI_GPE_LEVEL_TRIGGERED ,
& ipmi_acpi_gpe ,
io ) ;
if ( status ! = AE_OK ) {
dev_warn ( io - > dev ,
" Unable to claim ACPI GPE %d, running polled \n " ,
io - > irq ) ;
io - > irq = 0 ;
return - EINVAL ;
} else {
io - > irq_cleanup = acpi_gpe_irq_cleanup ;
ipmi_irq_finish_setup ( io ) ;
dev_info ( io - > dev , " Using ACPI GPE %d \n " , io - > irq ) ;
return 0 ;
}
}
# endif
static struct resource *
ipmi_get_info_from_resources ( struct platform_device * pdev ,
struct si_sm_io * io )
{
struct resource * res , * res_second ;
res = platform_get_resource ( pdev , IORESOURCE_IO , 0 ) ;
if ( res ) {
2019-02-21 21:53:00 +03:00
io - > addr_space = IPMI_IO_ADDR_SPACE ;
2017-09-13 06:55:57 +03:00
} else {
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( res )
2019-02-21 21:53:00 +03:00
io - > addr_space = IPMI_MEM_ADDR_SPACE ;
2017-09-13 06:55:57 +03:00
}
if ( ! res ) {
dev_err ( & pdev - > dev , " no I/O or memory address \n " ) ;
return NULL ;
}
io - > addr_data = res - > start ;
io - > regspacing = DEFAULT_REGSPACING ;
res_second = platform_get_resource ( pdev ,
2019-02-21 21:53:00 +03:00
( io - > addr_space = = IPMI_IO_ADDR_SPACE ) ?
2017-09-13 06:55:57 +03:00
IORESOURCE_IO : IORESOURCE_MEM ,
1 ) ;
if ( res_second ) {
if ( res_second - > start > io - > addr_data )
io - > regspacing = res_second - > start - io - > addr_data ;
}
return res ;
}
2017-09-18 20:38:17 +03:00
static int platform_ipmi_probe ( struct platform_device * pdev )
2017-09-13 06:55:57 +03:00
{
struct si_sm_io io ;
2019-02-21 21:10:07 +03:00
u8 type , slave_addr , addr_source , regsize , regshift ;
2017-09-13 06:55:57 +03:00
int rv ;
2017-09-18 20:38:17 +03:00
rv = device_property_read_u8 ( & pdev - > dev , " addr-source " , & addr_source ) ;
if ( rv )
addr_source = SI_PLATFORM ;
if ( addr_source > = SI_LAST )
return - EINVAL ;
if ( addr_source = = SI_SMBIOS ) {
if ( ! si_trydmi )
return - ENODEV ;
2019-02-21 21:10:07 +03:00
} else if ( addr_source ! = SI_HARDCODED ) {
2017-09-18 20:38:17 +03:00
if ( ! si_tryplatform )
return - ENODEV ;
}
2017-09-13 06:55:57 +03:00
rv = device_property_read_u8 ( & pdev - > dev , " ipmi-type " , & type ) ;
if ( rv )
return - ENODEV ;
memset ( & io , 0 , sizeof ( io ) ) ;
2017-09-18 20:38:17 +03:00
io . addr_source = addr_source ;
2018-05-09 18:15:48 +03:00
dev_info ( & pdev - > dev , " probing via %s \n " ,
2017-09-18 20:38:17 +03:00
ipmi_addr_src_to_str ( addr_source ) ) ;
2017-09-13 06:55:57 +03:00
switch ( type ) {
2017-09-18 20:38:17 +03:00
case SI_KCS :
case SI_SMIC :
case SI_BT :
io . si_type = type ;
2017-09-13 06:55:57 +03:00
break ;
2019-02-21 21:10:07 +03:00
case SI_TYPE_INVALID : /* User disabled this in hardcode. */
return - ENODEV ;
2017-09-13 06:55:57 +03:00
default :
2017-09-18 20:38:17 +03:00
dev_err ( & pdev - > dev , " ipmi-type property is invalid \n " ) ;
2017-09-13 06:55:57 +03:00
return - EINVAL ;
}
2019-02-21 21:10:07 +03:00
io . regsize = DEFAULT_REGSIZE ;
rv = device_property_read_u8 ( & pdev - > dev , " reg-size " , & regsize ) ;
if ( ! rv )
io . regsize = regsize ;
io . regshift = 0 ;
rv = device_property_read_u8 ( & pdev - > dev , " reg-shift " , & regshift ) ;
if ( ! rv )
io . regshift = regshift ;
2017-09-18 20:38:17 +03:00
if ( ! ipmi_get_info_from_resources ( pdev , & io ) )
return - EINVAL ;
2017-09-13 06:55:57 +03:00
rv = device_property_read_u8 ( & pdev - > dev , " slave-addr " , & slave_addr ) ;
2019-04-24 20:21:13 +03:00
if ( rv )
2017-09-13 06:55:57 +03:00
io . slave_addr = 0x20 ;
2019-04-24 20:21:13 +03:00
else
2017-09-13 06:55:57 +03:00
io . slave_addr = slave_addr ;
2020-02-05 12:31:46 +03:00
io . irq = platform_get_irq_optional ( pdev , 0 ) ;
2017-09-13 06:55:57 +03:00
if ( io . irq > 0 )
io . irq_setup = ipmi_std_irq_setup ;
else
io . irq = 0 ;
io . dev = & pdev - > dev ;
2019-02-21 21:10:07 +03:00
pr_info ( " ipmi_si: %s: %s %#lx regsize %d spacing %d irq %d \n " ,
ipmi_addr_src_to_str ( addr_source ) ,
2019-02-21 21:53:00 +03:00
( io . addr_space = = IPMI_IO_ADDR_SPACE ) ? " io " : " mem " ,
2017-09-13 06:55:57 +03:00
io . addr_data , io . regsize , io . regspacing , io . irq ) ;
ipmi_si_add_smi ( & io ) ;
return 0 ;
}
# ifdef CONFIG_OF
static const struct of_device_id of_ipmi_match [ ] = {
{ . type = " ipmi " , . compatible = " ipmi-kcs " ,
. data = ( void * ) ( unsigned long ) SI_KCS } ,
{ . type = " ipmi " , . compatible = " ipmi-smic " ,
. data = ( void * ) ( unsigned long ) SI_SMIC } ,
{ . type = " ipmi " , . compatible = " ipmi-bt " ,
. data = ( void * ) ( unsigned long ) SI_BT } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , of_ipmi_match ) ;
static int of_ipmi_probe ( struct platform_device * pdev )
{
const struct of_device_id * match ;
struct si_sm_io io ;
struct resource resource ;
const __be32 * regsize , * regspacing , * regshift ;
struct device_node * np = pdev - > dev . of_node ;
int ret ;
int proplen ;
2017-09-18 20:38:17 +03:00
if ( ! si_tryopenfirmware )
return - ENODEV ;
2017-09-13 06:55:57 +03:00
dev_info ( & pdev - > dev , " probing via device tree \n " ) ;
match = of_match_device ( of_ipmi_match , & pdev - > dev ) ;
if ( ! match )
return - ENODEV ;
if ( ! of_device_is_available ( np ) )
return - EINVAL ;
ret = of_address_to_resource ( np , 0 , & resource ) ;
if ( ret ) {
2018-05-09 18:15:48 +03:00
dev_warn ( & pdev - > dev , " invalid address from OF \n " ) ;
2017-09-13 06:55:57 +03:00
return ret ;
}
regsize = of_get_property ( np , " reg-size " , & proplen ) ;
if ( regsize & & proplen ! = 4 ) {
2018-05-09 18:15:48 +03:00
dev_warn ( & pdev - > dev , " invalid regsize from OF \n " ) ;
2017-09-13 06:55:57 +03:00
return - EINVAL ;
}
regspacing = of_get_property ( np , " reg-spacing " , & proplen ) ;
if ( regspacing & & proplen ! = 4 ) {
2018-05-09 18:15:48 +03:00
dev_warn ( & pdev - > dev , " invalid regspacing from OF \n " ) ;
2017-09-13 06:55:57 +03:00
return - EINVAL ;
}
regshift = of_get_property ( np , " reg-shift " , & proplen ) ;
if ( regshift & & proplen ! = 4 ) {
2018-05-09 18:15:48 +03:00
dev_warn ( & pdev - > dev , " invalid regshift from OF \n " ) ;
2017-09-13 06:55:57 +03:00
return - EINVAL ;
}
memset ( & io , 0 , sizeof ( io ) ) ;
io . si_type = ( enum si_type ) match - > data ;
io . addr_source = SI_DEVICETREE ;
io . irq_setup = ipmi_std_irq_setup ;
if ( resource . flags & IORESOURCE_IO )
2019-02-21 21:53:00 +03:00
io . addr_space = IPMI_IO_ADDR_SPACE ;
2017-09-13 06:55:57 +03:00
else
2019-02-21 21:53:00 +03:00
io . addr_space = IPMI_MEM_ADDR_SPACE ;
2017-09-13 06:55:57 +03:00
io . addr_data = resource . start ;
io . regsize = regsize ? be32_to_cpup ( regsize ) : DEFAULT_REGSIZE ;
io . regspacing = regspacing ? be32_to_cpup ( regspacing ) : DEFAULT_REGSPACING ;
io . regshift = regshift ? be32_to_cpup ( regshift ) : 0 ;
io . irq = irq_of_parse_and_map ( pdev - > dev . of_node , 0 ) ;
io . dev = & pdev - > dev ;
dev_dbg ( & pdev - > dev , " addr 0x%lx regsize %d spacing %d irq %d \n " ,
io . addr_data , io . regsize , io . regspacing , io . irq ) ;
return ipmi_si_add_smi ( & io ) ;
}
# else
# define of_ipmi_match NULL
static int of_ipmi_probe ( struct platform_device * dev )
{
return - ENODEV ;
}
# endif
# ifdef CONFIG_ACPI
static int find_slave_address ( struct si_sm_io * io , int slave_addr )
{
# ifdef CONFIG_IPMI_DMI_DECODE
2019-02-21 23:21:17 +03:00
if ( ! slave_addr )
slave_addr = ipmi_dmi_get_slave_addr ( io - > si_type ,
io - > addr_space ,
2017-09-13 06:55:57 +03:00
io - > addr_data ) ;
# endif
return slave_addr ;
}
static int acpi_ipmi_probe ( struct platform_device * pdev )
{
struct si_sm_io io ;
acpi_handle handle ;
acpi_status status ;
unsigned long long tmp ;
struct resource * res ;
int rv = - EINVAL ;
if ( ! si_tryacpi )
return - ENODEV ;
handle = ACPI_HANDLE ( & pdev - > dev ) ;
if ( ! handle )
return - ENODEV ;
memset ( & io , 0 , sizeof ( io ) ) ;
io . addr_source = SI_ACPI ;
2018-05-09 18:15:48 +03:00
dev_info ( & pdev - > dev , " probing via ACPI \n " ) ;
2017-09-13 06:55:57 +03:00
io . addr_info . acpi_info . acpi_handle = handle ;
/* _IFT tells us the interface type: KCS, BT, etc */
status = acpi_evaluate_integer ( handle , " _IFT " , NULL , & tmp ) ;
if ( ACPI_FAILURE ( status ) ) {
dev_err ( & pdev - > dev ,
" Could not find ACPI IPMI interface type \n " ) ;
goto err_free ;
}
switch ( tmp ) {
case 1 :
io . si_type = SI_KCS ;
break ;
case 2 :
io . si_type = SI_SMIC ;
break ;
case 3 :
io . si_type = SI_BT ;
break ;
case 4 : /* SSIF, just ignore */
rv = - ENODEV ;
goto err_free ;
default :
dev_info ( & pdev - > dev , " unknown IPMI type %lld \n " , tmp ) ;
goto err_free ;
}
2019-02-21 21:10:07 +03:00
io . regsize = DEFAULT_REGSIZE ;
io . regshift = 0 ;
2017-09-13 06:55:57 +03:00
res = ipmi_get_info_from_resources ( pdev , & io ) ;
if ( ! res ) {
rv = - EINVAL ;
goto err_free ;
}
/* If _GPE exists, use it; otherwise use standard interrupts */
status = acpi_evaluate_integer ( handle , " _GPE " , NULL , & tmp ) ;
if ( ACPI_SUCCESS ( status ) ) {
io . irq = tmp ;
io . irq_setup = acpi_gpe_irq_setup ;
} else {
2020-02-05 12:31:46 +03:00
int irq = platform_get_irq_optional ( pdev , 0 ) ;
2017-09-13 06:55:57 +03:00
if ( irq > 0 ) {
io . irq = irq ;
io . irq_setup = ipmi_std_irq_setup ;
}
}
io . slave_addr = find_slave_address ( & io , io . slave_addr ) ;
io . dev = & pdev - > dev ;
dev_info ( io . dev , " %pR regsize %d spacing %d irq %d \n " ,
res , io . regsize , io . regspacing , io . irq ) ;
return ipmi_si_add_smi ( & io ) ;
err_free :
return rv ;
}
static const struct acpi_device_id acpi_ipmi_match [ ] = {
{ " IPI0001 " , 0 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( acpi , acpi_ipmi_match ) ;
# else
static int acpi_ipmi_probe ( struct platform_device * dev )
{
return - ENODEV ;
}
# endif
static int ipmi_probe ( struct platform_device * pdev )
{
if ( pdev - > dev . of_node & & of_ipmi_probe ( pdev ) = = 0 )
return 0 ;
if ( acpi_ipmi_probe ( pdev ) = = 0 )
return 0 ;
2017-09-18 20:38:17 +03:00
return platform_ipmi_probe ( pdev ) ;
2017-09-13 06:55:57 +03:00
}
static int ipmi_remove ( struct platform_device * pdev )
{
return ipmi_si_remove_by_dev ( & pdev - > dev ) ;
}
2019-06-14 20:53:59 +03:00
static int pdev_match_name ( struct device * dev , const void * data )
2019-02-22 02:41:47 +03:00
{
struct platform_device * pdev = to_platform_device ( dev ) ;
const char * name = data ;
return strcmp ( pdev - > name , name ) = = 0 ;
}
void ipmi_remove_platform_device_by_name ( char * name )
{
struct device * dev ;
while ( ( dev = bus_find_device ( & platform_bus_type , NULL , name ,
pdev_match_name ) ) ) {
struct platform_device * pdev = to_platform_device ( dev ) ;
platform_device_unregister ( pdev ) ;
2019-06-03 18:49:28 +03:00
put_device ( dev ) ;
2019-02-22 02:41:47 +03:00
}
}
2018-08-30 22:36:09 +03:00
static const struct platform_device_id si_plat_ids [ ] = {
2019-02-21 21:10:07 +03:00
{ " dmi-ipmi-si " , 0 } ,
{ " hardcode-ipmi-si " , 0 } ,
2019-02-21 23:23:07 +03:00
{ " hotmod-ipmi-si " , 0 } ,
2019-02-21 21:10:07 +03:00
{ }
2018-08-30 22:36:09 +03:00
} ;
2017-09-13 06:55:57 +03:00
struct platform_driver ipmi_platform_driver = {
. driver = {
2019-08-01 03:18:25 +03:00
. name = SI_DEVICE_NAME ,
2017-09-13 06:55:57 +03:00
. of_match_table = of_ipmi_match ,
. acpi_match_table = ACPI_PTR ( acpi_ipmi_match ) ,
} ,
. probe = ipmi_probe ,
. remove = ipmi_remove ,
2018-08-30 22:36:09 +03:00
. id_table = si_plat_ids
2017-09-13 06:55:57 +03:00
} ;
void ipmi_si_platform_init ( void )
{
2017-09-18 20:38:17 +03:00
int rv = platform_driver_register ( & ipmi_platform_driver ) ;
if ( rv )
2018-05-09 18:15:48 +03:00
pr_err ( " Unable to register driver: %d \n " , rv ) ;
2019-05-17 13:12:44 +03:00
else
platform_registered = true ;
2017-09-13 06:55:57 +03:00
}
void ipmi_si_platform_shutdown ( void )
{
2019-05-17 13:12:44 +03:00
if ( platform_registered )
platform_driver_unregister ( & ipmi_platform_driver ) ;
2017-09-13 06:55:57 +03:00
}