2020-11-23 23:23:36 +03:00
// SPDX-License-Identifier: GPL-2.0
//
// System Control and Management Interface (SCMI) based regulator driver
//
2021-03-16 15:48:54 +03:00
// Copyright (C) 2020-2021 ARM Ltd.
2020-11-23 23:23:36 +03:00
//
// Implements a regulator driver on top of the SCMI Voltage Protocol.
//
// The ARM SCMI Protocol aims in general to hide as much as possible all the
// underlying operational details while providing an abstracted interface for
// its users to operate upon: as a consequence the resulting operational
// capabilities and configurability of this regulator device are much more
// limited than the ones usually available on a standard physical regulator.
//
// The supported SCMI regulator ops are restricted to the bare minimum:
//
// - 'status_ops': enable/disable/is_enabled
// - 'voltage_ops': get_voltage_sel/set_voltage_sel
// list_voltage/map_voltage
//
// Each SCMI regulator instance is associated, through the means of a proper DT
// entry description, to a specific SCMI Voltage Domain.
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/linear_range.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/regulator/driver.h>
# include <linux/regulator/machine.h>
# include <linux/regulator/of_regulator.h>
# include <linux/scmi_protocol.h>
# include <linux/slab.h>
# include <linux/types.h>
2021-03-16 15:48:54 +03:00
static const struct scmi_voltage_proto_ops * voltage_ops ;
2020-11-23 23:23:36 +03:00
struct scmi_regulator {
u32 id ;
struct scmi_device * sdev ;
2021-03-16 15:48:54 +03:00
struct scmi_protocol_handle * ph ;
2020-11-23 23:23:36 +03:00
struct regulator_dev * rdev ;
struct device_node * of_node ;
struct regulator_desc desc ;
struct regulator_config conf ;
} ;
struct scmi_regulator_info {
int num_doms ;
struct scmi_regulator * * sregv ;
} ;
static int scmi_reg_enable ( struct regulator_dev * rdev )
{
struct scmi_regulator * sreg = rdev_get_drvdata ( rdev ) ;
2021-03-16 15:48:54 +03:00
return voltage_ops - > config_set ( sreg - > ph , sreg - > id ,
SCMI_VOLTAGE_ARCH_STATE_ON ) ;
2020-11-23 23:23:36 +03:00
}
static int scmi_reg_disable ( struct regulator_dev * rdev )
{
struct scmi_regulator * sreg = rdev_get_drvdata ( rdev ) ;
2021-03-16 15:48:54 +03:00
return voltage_ops - > config_set ( sreg - > ph , sreg - > id ,
SCMI_VOLTAGE_ARCH_STATE_OFF ) ;
2020-11-23 23:23:36 +03:00
}
static int scmi_reg_is_enabled ( struct regulator_dev * rdev )
{
int ret ;
u32 config ;
struct scmi_regulator * sreg = rdev_get_drvdata ( rdev ) ;
2021-03-16 15:48:54 +03:00
ret = voltage_ops - > config_get ( sreg - > ph , sreg - > id , & config ) ;
2020-11-23 23:23:36 +03:00
if ( ret ) {
dev_err ( & sreg - > sdev - > dev ,
" Error %d reading regulator %s status. \n " ,
ret , sreg - > desc . name ) ;
return ret ;
}
return config & SCMI_VOLTAGE_ARCH_STATE_ON ;
}
static int scmi_reg_get_voltage_sel ( struct regulator_dev * rdev )
{
int ret ;
s32 volt_uV ;
struct scmi_regulator * sreg = rdev_get_drvdata ( rdev ) ;
2021-03-16 15:48:54 +03:00
ret = voltage_ops - > level_get ( sreg - > ph , sreg - > id , & volt_uV ) ;
2020-11-23 23:23:36 +03:00
if ( ret )
return ret ;
return sreg - > desc . ops - > map_voltage ( rdev , volt_uV , volt_uV ) ;
}
static int scmi_reg_set_voltage_sel ( struct regulator_dev * rdev ,
unsigned int selector )
{
s32 volt_uV ;
struct scmi_regulator * sreg = rdev_get_drvdata ( rdev ) ;
volt_uV = sreg - > desc . ops - > list_voltage ( rdev , selector ) ;
if ( volt_uV < = 0 )
return - EINVAL ;
2021-03-16 15:48:54 +03:00
return voltage_ops - > level_set ( sreg - > ph , sreg - > id , 0x0 , volt_uV ) ;
2020-11-23 23:23:36 +03:00
}
static const struct regulator_ops scmi_reg_fixed_ops = {
. enable = scmi_reg_enable ,
. disable = scmi_reg_disable ,
. is_enabled = scmi_reg_is_enabled ,
} ;
static const struct regulator_ops scmi_reg_linear_ops = {
. enable = scmi_reg_enable ,
. disable = scmi_reg_disable ,
. is_enabled = scmi_reg_is_enabled ,
. get_voltage_sel = scmi_reg_get_voltage_sel ,
. set_voltage_sel = scmi_reg_set_voltage_sel ,
. list_voltage = regulator_list_voltage_linear ,
. map_voltage = regulator_map_voltage_linear ,
} ;
static const struct regulator_ops scmi_reg_discrete_ops = {
. enable = scmi_reg_enable ,
. disable = scmi_reg_disable ,
. is_enabled = scmi_reg_is_enabled ,
. get_voltage_sel = scmi_reg_get_voltage_sel ,
. set_voltage_sel = scmi_reg_set_voltage_sel ,
. list_voltage = regulator_list_voltage_table ,
. map_voltage = regulator_map_voltage_iterate ,
} ;
static int
scmi_config_linear_regulator_mappings ( struct scmi_regulator * sreg ,
const struct scmi_voltage_info * vinfo )
{
s32 delta_uV ;
/*
* Note that SCMI voltage domains describable by linear ranges
* ( segments ) { low , high , step } are guaranteed to come in one single
* triplet by the SCMI Voltage Domain protocol support itself .
*/
delta_uV = ( vinfo - > levels_uv [ SCMI_VOLTAGE_SEGMENT_HIGH ] -
vinfo - > levels_uv [ SCMI_VOLTAGE_SEGMENT_LOW ] ) ;
/* Rule out buggy negative-intervals answers from fw */
if ( delta_uV < 0 ) {
dev_err ( & sreg - > sdev - > dev ,
" Invalid volt-range %d-%duV for domain %d \n " ,
vinfo - > levels_uv [ SCMI_VOLTAGE_SEGMENT_LOW ] ,
vinfo - > levels_uv [ SCMI_VOLTAGE_SEGMENT_HIGH ] ,
sreg - > id ) ;
return - EINVAL ;
}
if ( ! delta_uV ) {
/* Just one fixed voltage exposed by SCMI */
sreg - > desc . fixed_uV =
vinfo - > levels_uv [ SCMI_VOLTAGE_SEGMENT_LOW ] ;
sreg - > desc . n_voltages = 1 ;
sreg - > desc . ops = & scmi_reg_fixed_ops ;
} else {
/* One simple linear mapping. */
sreg - > desc . min_uV =
vinfo - > levels_uv [ SCMI_VOLTAGE_SEGMENT_LOW ] ;
sreg - > desc . uV_step =
vinfo - > levels_uv [ SCMI_VOLTAGE_SEGMENT_STEP ] ;
sreg - > desc . linear_min_sel = 0 ;
2021-05-21 10:30:20 +03:00
sreg - > desc . n_voltages = ( delta_uV / sreg - > desc . uV_step ) + 1 ;
2020-11-23 23:23:36 +03:00
sreg - > desc . ops = & scmi_reg_linear_ops ;
}
return 0 ;
}
static int
scmi_config_discrete_regulator_mappings ( struct scmi_regulator * sreg ,
const struct scmi_voltage_info * vinfo )
{
/* Discrete non linear levels are mapped to volt_table */
sreg - > desc . n_voltages = vinfo - > num_levels ;
if ( sreg - > desc . n_voltages > 1 ) {
sreg - > desc . volt_table = ( const unsigned int * ) vinfo - > levels_uv ;
sreg - > desc . ops = & scmi_reg_discrete_ops ;
} else {
sreg - > desc . fixed_uV = vinfo - > levels_uv [ 0 ] ;
sreg - > desc . ops = & scmi_reg_fixed_ops ;
}
return 0 ;
}
static int scmi_regulator_common_init ( struct scmi_regulator * sreg )
{
int ret ;
struct device * dev = & sreg - > sdev - > dev ;
const struct scmi_voltage_info * vinfo ;
2021-03-16 15:48:54 +03:00
vinfo = voltage_ops - > info_get ( sreg - > ph , sreg - > id ) ;
2020-11-23 23:23:36 +03:00
if ( ! vinfo ) {
dev_warn ( dev , " Failure to get voltage domain %d \n " ,
sreg - > id ) ;
return - ENODEV ;
}
/*
* Regulator framework does not fully support negative voltages
* so we discard any voltage domain reported as supporting negative
* voltages : as a consequence each levels_uv entry is guaranteed to
* be non - negative from here on .
*/
if ( vinfo - > negative_volts_allowed ) {
dev_warn ( dev , " Negative voltages NOT supported...skip %s \n " ,
sreg - > of_node - > full_name ) ;
return - EOPNOTSUPP ;
}
sreg - > desc . name = devm_kasprintf ( dev , GFP_KERNEL , " %s " , vinfo - > name ) ;
if ( ! sreg - > desc . name )
return - ENOMEM ;
sreg - > desc . id = sreg - > id ;
sreg - > desc . type = REGULATOR_VOLTAGE ;
sreg - > desc . owner = THIS_MODULE ;
sreg - > desc . of_match_full_name = true ;
sreg - > desc . of_match = sreg - > of_node - > full_name ;
sreg - > desc . regulators_node = " regulators " ;
if ( vinfo - > segmented )
ret = scmi_config_linear_regulator_mappings ( sreg , vinfo ) ;
else
ret = scmi_config_discrete_regulator_mappings ( sreg , vinfo ) ;
if ( ret )
return ret ;
/*
* Using the scmi device here to have DT searched from Voltage
* protocol node down .
*/
sreg - > conf . dev = dev ;
/* Store for later retrieval via rdev_get_drvdata() */
sreg - > conf . driver_data = sreg ;
return 0 ;
}
static int process_scmi_regulator_of_node ( struct scmi_device * sdev ,
2021-03-16 15:48:54 +03:00
struct scmi_protocol_handle * ph ,
2020-11-23 23:23:36 +03:00
struct device_node * np ,
struct scmi_regulator_info * rinfo )
{
u32 dom , ret ;
ret = of_property_read_u32 ( np , " reg " , & dom ) ;
if ( ret )
return ret ;
if ( dom > = rinfo - > num_doms )
return - ENODEV ;
if ( rinfo - > sregv [ dom ] ) {
dev_err ( & sdev - > dev ,
" SCMI Voltage Domain %d already in use. Skipping: %s \n " ,
dom , np - > full_name ) ;
return - EINVAL ;
}
rinfo - > sregv [ dom ] = devm_kzalloc ( & sdev - > dev ,
sizeof ( struct scmi_regulator ) ,
GFP_KERNEL ) ;
if ( ! rinfo - > sregv [ dom ] )
return - ENOMEM ;
rinfo - > sregv [ dom ] - > id = dom ;
rinfo - > sregv [ dom ] - > sdev = sdev ;
2021-03-16 15:48:54 +03:00
rinfo - > sregv [ dom ] - > ph = ph ;
2020-11-23 23:23:36 +03:00
/* get hold of good nodes */
of_node_get ( np ) ;
rinfo - > sregv [ dom ] - > of_node = np ;
dev_dbg ( & sdev - > dev ,
" Found SCMI Regulator entry -- OF node [%d] -> %s \n " ,
dom , np - > full_name ) ;
return 0 ;
}
static int scmi_regulator_probe ( struct scmi_device * sdev )
{
int d , ret , num_doms ;
struct device_node * np , * child ;
const struct scmi_handle * handle = sdev - > handle ;
struct scmi_regulator_info * rinfo ;
2021-03-16 15:48:54 +03:00
struct scmi_protocol_handle * ph ;
2020-11-23 23:23:36 +03:00
2021-03-16 15:48:54 +03:00
if ( ! handle )
2020-11-23 23:23:36 +03:00
return - ENODEV ;
2021-03-16 15:48:54 +03:00
voltage_ops = handle - > devm_protocol_get ( sdev ,
SCMI_PROTOCOL_VOLTAGE , & ph ) ;
if ( IS_ERR ( voltage_ops ) )
return PTR_ERR ( voltage_ops ) ;
num_doms = voltage_ops - > num_domains_get ( ph ) ;
2023-01-26 21:05:11 +03:00
if ( ! num_doms )
return 0 ;
if ( num_doms < 0 ) {
dev_err ( & sdev - > dev , " failed to get voltage domains - err:%d \n " ,
num_doms ) ;
2020-11-23 23:23:36 +03:00
return num_doms ;
}
rinfo = devm_kzalloc ( & sdev - > dev , sizeof ( * rinfo ) , GFP_KERNEL ) ;
if ( ! rinfo )
return - ENOMEM ;
/* Allocate pointers array for all possible domains */
rinfo - > sregv = devm_kcalloc ( & sdev - > dev , num_doms ,
sizeof ( void * ) , GFP_KERNEL ) ;
if ( ! rinfo - > sregv )
return - ENOMEM ;
rinfo - > num_doms = num_doms ;
/*
* Start collecting into rinfo - > sregv possibly good SCMI Regulators as
* described by a well - formed DT entry and associated with an existing
* plausible SCMI Voltage Domain number , all belonging to this SCMI
* platform instance node ( handle - > dev - > of_node ) .
*/
2022-06-22 06:48:16 +03:00
of_node_get ( handle - > dev - > of_node ) ;
2020-11-23 23:23:36 +03:00
np = of_find_node_by_name ( handle - > dev - > of_node , " regulators " ) ;
for_each_child_of_node ( np , child ) {
2021-03-16 15:48:54 +03:00
ret = process_scmi_regulator_of_node ( sdev , ph , child , rinfo ) ;
2020-11-23 23:23:36 +03:00
/* abort on any mem issue */
2021-02-26 04:39:35 +03:00
if ( ret = = - ENOMEM ) {
of_node_put ( child ) ;
2020-11-23 23:23:36 +03:00
return ret ;
2021-02-26 04:39:35 +03:00
}
2020-11-23 23:23:36 +03:00
}
2022-05-16 10:44:33 +03:00
of_node_put ( np ) ;
2020-11-23 23:23:36 +03:00
/*
* Register a regulator for each valid regulator - DT - entry that we
* can successfully reach via SCMI and has a valid associated voltage
* domain .
*/
for ( d = 0 ; d < num_doms ; d + + ) {
struct scmi_regulator * sreg = rinfo - > sregv [ d ] ;
/* Skip empty slots */
if ( ! sreg )
continue ;
ret = scmi_regulator_common_init ( sreg ) ;
/* Skip invalid voltage domains */
if ( ret )
continue ;
sreg - > rdev = devm_regulator_register ( & sdev - > dev , & sreg - > desc ,
& sreg - > conf ) ;
if ( IS_ERR ( sreg - > rdev ) ) {
sreg - > rdev = NULL ;
continue ;
}
dev_info ( & sdev - > dev ,
" Regulator %s registered for domain [%d] \n " ,
sreg - > desc . name , sreg - > id ) ;
}
dev_set_drvdata ( & sdev - > dev , rinfo ) ;
return 0 ;
}
static void scmi_regulator_remove ( struct scmi_device * sdev )
{
int d ;
struct scmi_regulator_info * rinfo ;
rinfo = dev_get_drvdata ( & sdev - > dev ) ;
if ( ! rinfo )
return ;
for ( d = 0 ; d < rinfo - > num_doms ; d + + ) {
if ( ! rinfo - > sregv [ d ] )
continue ;
of_node_put ( rinfo - > sregv [ d ] - > of_node ) ;
}
}
static const struct scmi_device_id scmi_regulator_id_table [ ] = {
{ SCMI_PROTOCOL_VOLTAGE , " regulator " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( scmi , scmi_regulator_id_table ) ;
static struct scmi_driver scmi_drv = {
. name = " scmi-regulator " ,
. probe = scmi_regulator_probe ,
. remove = scmi_regulator_remove ,
. id_table = scmi_regulator_id_table ,
} ;
module_scmi_driver ( scmi_drv ) ;
MODULE_AUTHOR ( " Cristian Marussi <cristian.marussi@arm.com> " ) ;
MODULE_DESCRIPTION ( " ARM SCMI regulator driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;