2022-02-08 18:36:30 +03:00
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2018-2021 Intel Corporation
# include <linux/bug.h>
# include <linux/device.h>
# include <linux/export.h>
# include <linux/idr.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/peci.h>
# include <linux/pm_runtime.h>
# include <linux/property.h>
# include <linux/slab.h>
# include "internal.h"
static DEFINE_IDA ( peci_controller_ida ) ;
static void peci_controller_dev_release ( struct device * dev )
{
struct peci_controller * controller = to_peci_controller ( dev ) ;
mutex_destroy ( & controller - > bus_lock ) ;
ida_free ( & peci_controller_ida , controller - > id ) ;
kfree ( controller ) ;
}
struct device_type peci_controller_type = {
. release = peci_controller_dev_release ,
} ;
2022-02-08 18:36:33 +03:00
int peci_controller_scan_devices ( struct peci_controller * controller )
2022-02-08 18:36:32 +03:00
{
int ret ;
u8 addr ;
for ( addr = PECI_BASE_ADDR ; addr < PECI_BASE_ADDR + PECI_DEVICE_NUM_MAX ; addr + + ) {
ret = peci_device_create ( controller , addr ) ;
if ( ret )
return ret ;
}
return 0 ;
}
2022-02-08 18:36:30 +03:00
static struct peci_controller * peci_controller_alloc ( struct device * dev ,
struct peci_controller_ops * ops )
{
struct peci_controller * controller ;
int ret ;
if ( ! ops - > xfer )
return ERR_PTR ( - EINVAL ) ;
controller = kzalloc ( sizeof ( * controller ) , GFP_KERNEL ) ;
if ( ! controller )
return ERR_PTR ( - ENOMEM ) ;
ret = ida_alloc_max ( & peci_controller_ida , U8_MAX , GFP_KERNEL ) ;
if ( ret < 0 )
goto err ;
controller - > id = ret ;
controller - > ops = ops ;
controller - > dev . parent = dev ;
controller - > dev . bus = & peci_bus_type ;
controller - > dev . type = & peci_controller_type ;
device_initialize ( & controller - > dev ) ;
mutex_init ( & controller - > bus_lock ) ;
return controller ;
err :
kfree ( controller ) ;
return ERR_PTR ( ret ) ;
}
2022-02-08 18:36:32 +03:00
static int unregister_child ( struct device * dev , void * dummy )
{
peci_device_destroy ( to_peci_device ( dev ) ) ;
return 0 ;
}
2022-02-08 18:36:30 +03:00
static void unregister_controller ( void * _controller )
{
struct peci_controller * controller = _controller ;
2022-02-08 18:36:32 +03:00
/*
* Detach any active PECI devices . This can ' t fail , thus we do not
* check the returned value .
*/
device_for_each_child_reverse ( & controller - > dev , NULL , unregister_child ) ;
2022-02-08 18:36:30 +03:00
device_unregister ( & controller - > dev ) ;
fwnode_handle_put ( controller - > dev . fwnode ) ;
pm_runtime_disable ( & controller - > dev ) ;
}
/**
* devm_peci_controller_add ( ) - add PECI controller
* @ dev : device for devm operations
* @ ops : pointer to controller specific methods
*
* In final stage of its probe ( ) , peci_controller driver calls
* devm_peci_controller_add ( ) to register itself with the PECI bus .
*
* Return : Pointer to the newly allocated controller or ERR_PTR ( ) in case of failure .
*/
struct peci_controller * devm_peci_controller_add ( struct device * dev ,
struct peci_controller_ops * ops )
{
struct peci_controller * controller ;
int ret ;
controller = peci_controller_alloc ( dev , ops ) ;
if ( IS_ERR ( controller ) )
return controller ;
ret = dev_set_name ( & controller - > dev , " peci-%d " , controller - > id ) ;
if ( ret )
goto err_put ;
pm_runtime_no_callbacks ( & controller - > dev ) ;
pm_suspend_ignore_children ( & controller - > dev , true ) ;
pm_runtime_enable ( & controller - > dev ) ;
device_set_node ( & controller - > dev , fwnode_handle_get ( dev_fwnode ( dev ) ) ) ;
ret = device_add ( & controller - > dev ) ;
if ( ret )
goto err_fwnode ;
ret = devm_add_action_or_reset ( dev , unregister_controller , controller ) ;
if ( ret )
return ERR_PTR ( ret ) ;
2022-02-08 18:36:32 +03:00
/*
* Ignoring retval since failures during scan are non - critical for
* controller itself .
*/
peci_controller_scan_devices ( controller ) ;
2022-02-08 18:36:30 +03:00
return controller ;
err_fwnode :
fwnode_handle_put ( controller - > dev . fwnode ) ;
pm_runtime_disable ( & controller - > dev ) ;
err_put :
put_device ( & controller - > dev ) ;
return ERR_PTR ( ret ) ;
}
EXPORT_SYMBOL_NS_GPL ( devm_peci_controller_add , PECI ) ;
2022-02-08 18:36:34 +03:00
static const struct peci_device_id *
peci_bus_match_device_id ( const struct peci_device_id * id , struct peci_device * device )
{
while ( id - > family ! = 0 ) {
if ( id - > family = = device - > info . family & &
id - > model = = device - > info . model )
return id ;
id + + ;
}
return NULL ;
}
static int peci_bus_device_match ( struct device * dev , struct device_driver * drv )
{
struct peci_device * device = to_peci_device ( dev ) ;
struct peci_driver * peci_drv = to_peci_driver ( drv ) ;
if ( dev - > type ! = & peci_device_type )
return 0 ;
return ! ! peci_bus_match_device_id ( peci_drv - > id_table , device ) ;
}
static int peci_bus_device_probe ( struct device * dev )
{
struct peci_device * device = to_peci_device ( dev ) ;
struct peci_driver * driver = to_peci_driver ( dev - > driver ) ;
return driver - > probe ( device , peci_bus_match_device_id ( driver - > id_table , device ) ) ;
}
static void peci_bus_device_remove ( struct device * dev )
{
struct peci_device * device = to_peci_device ( dev ) ;
struct peci_driver * driver = to_peci_driver ( dev - > driver ) ;
if ( driver - > remove )
driver - > remove ( device ) ;
}
2022-02-08 18:36:30 +03:00
struct bus_type peci_bus_type = {
. name = " peci " ,
2022-02-08 18:36:34 +03:00
. match = peci_bus_device_match ,
. probe = peci_bus_device_probe ,
. remove = peci_bus_device_remove ,
2022-02-08 18:36:33 +03:00
. bus_groups = peci_bus_groups ,
2022-02-08 18:36:30 +03:00
} ;
static int __init peci_init ( void )
{
int ret ;
ret = bus_register ( & peci_bus_type ) ;
if ( ret < 0 ) {
pr_err ( " peci: failed to register PECI bus type! \n " ) ;
return ret ;
}
return 0 ;
}
module_init ( peci_init ) ;
static void __exit peci_exit ( void )
{
bus_unregister ( & peci_bus_type ) ;
}
module_exit ( peci_exit ) ;
MODULE_AUTHOR ( " Jason M Bills <jason.m.bills@linux.intel.com> " ) ;
MODULE_AUTHOR ( " Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com> " ) ;
MODULE_AUTHOR ( " Iwona Winiarska <iwona.winiarska@intel.com> " ) ;
MODULE_DESCRIPTION ( " PECI bus core module " ) ;
MODULE_LICENSE ( " GPL " ) ;