2019-03-28 13:09:37 +00:00
// SPDX-License-Identifier: GPL-2.0
/*
* Lochnagar hardware monitoring features
*
* Copyright ( c ) 2016 - 2019 Cirrus Logic , Inc . and
* Cirrus Logic International Semiconductor Ltd .
*
* Author : Lucas Tanure < tanureal @ opensource . cirrus . com >
*/
# include <linux/delay.h>
# include <linux/hwmon.h>
# include <linux/hwmon-sysfs.h>
# include <linux/math64.h>
# include <linux/mfd/lochnagar.h>
# include <linux/mfd/lochnagar2_regs.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# define LN2_MAX_NSAMPLE 1023
# define LN2_SAMPLE_US 1670
# define LN2_CURR_UNITS 1000
# define LN2_VOLT_UNITS 1000
# define LN2_TEMP_UNITS 1000
# define LN2_PWR_UNITS 1000000
static const char * const lochnagar_chan_names [ ] = {
" DBVDD1 " ,
" 1V8 DSP " ,
" 1V8 CDC " ,
" VDDCORE DSP " ,
" AVDD 1V8 " ,
" SYSVDD " ,
" VDDCORE CDC " ,
" MICVDD " ,
} ;
struct lochnagar_hwmon {
struct regmap * regmap ;
long power_nsamples [ ARRAY_SIZE ( lochnagar_chan_names ) ] ;
/* Lock to ensure only a single sensor is read at a time */
struct mutex sensor_lock ;
} ;
enum lochnagar_measure_mode {
LN2_CURR = 0 ,
LN2_VOLT ,
LN2_TEMP ,
} ;
/**
* float_to_long - Convert ieee754 reading from hardware to an integer
*
* @ data : Value read from the hardware
* @ precision : Units to multiply up to eg . 1000 = milli , 1000000 = micro
*
* Return : Converted integer reading
*
* Depending on the measurement type the hardware returns an ieee754
* floating point value in either volts , amps or celsius . This function
* will convert that into an integer in a smaller unit such as micro - amps
* or milli - celsius . The hardware does not return NaN , so consideration of
* that is not required .
*/
static long float_to_long ( u32 data , u32 precision )
{
u64 man = data & 0x007FFFFF ;
int exp = ( ( data & 0x7F800000 ) > > 23 ) - 127 - 23 ;
bool negative = data & 0x80000000 ;
long result ;
man = ( man + ( 1 < < 23 ) ) * precision ;
if ( fls64 ( man ) + exp > ( int ) sizeof ( long ) * 8 - 1 )
result = LONG_MAX ;
else if ( exp < 0 )
result = ( man + ( 1ull < < ( - exp - 1 ) ) ) > > - exp ;
else
result = man < < exp ;
return negative ? - result : result ;
}
static int do_measurement ( struct regmap * regmap , int chan ,
enum lochnagar_measure_mode mode , int nsamples )
{
unsigned int val ;
int ret ;
chan = 1 < < ( chan + LOCHNAGAR2_IMON_MEASURED_CHANNELS_SHIFT ) ;
ret = regmap_write ( regmap , LOCHNAGAR2_IMON_CTRL1 ,
LOCHNAGAR2_IMON_ENA_MASK | chan | mode ) ;
if ( ret < 0 )
return ret ;
ret = regmap_write ( regmap , LOCHNAGAR2_IMON_CTRL2 , nsamples ) ;
if ( ret < 0 )
return ret ;
ret = regmap_write ( regmap , LOCHNAGAR2_IMON_CTRL3 ,
LOCHNAGAR2_IMON_CONFIGURE_MASK ) ;
if ( ret < 0 )
return ret ;
ret = regmap_read_poll_timeout ( regmap , LOCHNAGAR2_IMON_CTRL3 , val ,
val & LOCHNAGAR2_IMON_DONE_MASK ,
1000 , 10000 ) ;
if ( ret < 0 )
return ret ;
ret = regmap_write ( regmap , LOCHNAGAR2_IMON_CTRL3 ,
LOCHNAGAR2_IMON_MEASURE_MASK ) ;
if ( ret < 0 )
return ret ;
/*
* Actual measurement time is ~ 1.67 mS per sample , approximate this
* with a 1.5 mS per sample msleep and then poll for success up to
* ~ 0.17 mS * 1023 ( LN2_MAX_NSAMPLES ) . Normally for smaller values
* of nsamples the poll will complete on the first loop due to
* other latency in the system .
*/
msleep ( ( nsamples * 3 ) / 2 ) ;
ret = regmap_read_poll_timeout ( regmap , LOCHNAGAR2_IMON_CTRL3 , val ,
val & LOCHNAGAR2_IMON_DONE_MASK ,
5000 , 200000 ) ;
if ( ret < 0 )
return ret ;
return regmap_write ( regmap , LOCHNAGAR2_IMON_CTRL3 , 0 ) ;
}
static int request_data ( struct regmap * regmap , int chan , u32 * data )
{
unsigned int val ;
int ret ;
ret = regmap_write ( regmap , LOCHNAGAR2_IMON_CTRL4 ,
LOCHNAGAR2_IMON_DATA_REQ_MASK |
chan < < LOCHNAGAR2_IMON_CH_SEL_SHIFT ) ;
if ( ret < 0 )
return ret ;
ret = regmap_read_poll_timeout ( regmap , LOCHNAGAR2_IMON_CTRL4 , val ,
val & LOCHNAGAR2_IMON_DATA_RDY_MASK ,
1000 , 10000 ) ;
if ( ret < 0 )
return ret ;
ret = regmap_read ( regmap , LOCHNAGAR2_IMON_DATA1 , & val ) ;
if ( ret < 0 )
return ret ;
* data = val < < 16 ;
ret = regmap_read ( regmap , LOCHNAGAR2_IMON_DATA2 , & val ) ;
if ( ret < 0 )
return ret ;
* data | = val ;
return regmap_write ( regmap , LOCHNAGAR2_IMON_CTRL4 , 0 ) ;
}
static int read_sensor ( struct device * dev , int chan ,
enum lochnagar_measure_mode mode , int nsamples ,
unsigned int precision , long * val )
{
struct lochnagar_hwmon * priv = dev_get_drvdata ( dev ) ;
struct regmap * regmap = priv - > regmap ;
u32 data ;
int ret ;
mutex_lock ( & priv - > sensor_lock ) ;
ret = do_measurement ( regmap , chan , mode , nsamples ) ;
if ( ret < 0 ) {
dev_err ( dev , " Failed to perform measurement: %d \n " , ret ) ;
goto error ;
}
ret = request_data ( regmap , chan , & data ) ;
if ( ret < 0 ) {
dev_err ( dev , " Failed to read measurement: %d \n " , ret ) ;
goto error ;
}
* val = float_to_long ( data , precision ) ;
error :
mutex_unlock ( & priv - > sensor_lock ) ;
return ret ;
}
static int read_power ( struct device * dev , int chan , long * val )
{
struct lochnagar_hwmon * priv = dev_get_drvdata ( dev ) ;
int nsamples = priv - > power_nsamples [ chan ] ;
u64 power ;
int ret ;
if ( ! strcmp ( " SYSVDD " , lochnagar_chan_names [ chan ] ) ) {
power = 5 * LN2_PWR_UNITS ;
} else {
ret = read_sensor ( dev , chan , LN2_VOLT , 1 , LN2_PWR_UNITS , val ) ;
if ( ret < 0 )
return ret ;
power = abs ( * val ) ;
}
ret = read_sensor ( dev , chan , LN2_CURR , nsamples , LN2_PWR_UNITS , val ) ;
if ( ret < 0 )
return ret ;
power * = abs ( * val ) ;
power = DIV_ROUND_CLOSEST_ULL ( power , LN2_PWR_UNITS ) ;
if ( power > LONG_MAX )
* val = LONG_MAX ;
else
* val = power ;
return 0 ;
}
static umode_t lochnagar_is_visible ( const void * drvdata ,
enum hwmon_sensor_types type ,
u32 attr , int chan )
{
switch ( type ) {
case hwmon_in :
if ( ! strcmp ( " SYSVDD " , lochnagar_chan_names [ chan ] ) )
return 0 ;
break ;
case hwmon_power :
if ( attr = = hwmon_power_average_interval )
return 0644 ;
break ;
default :
break ;
}
return 0444 ;
}
static int lochnagar_read ( struct device * dev , enum hwmon_sensor_types type ,
u32 attr , int chan , long * val )
{
struct lochnagar_hwmon * priv = dev_get_drvdata ( dev ) ;
int interval ;
switch ( type ) {
case hwmon_in :
return read_sensor ( dev , chan , LN2_VOLT , 1 , LN2_VOLT_UNITS , val ) ;
case hwmon_curr :
return read_sensor ( dev , chan , LN2_CURR , 1 , LN2_CURR_UNITS , val ) ;
case hwmon_temp :
return read_sensor ( dev , chan , LN2_TEMP , 1 , LN2_TEMP_UNITS , val ) ;
case hwmon_power :
switch ( attr ) {
case hwmon_power_average :
return read_power ( dev , chan , val ) ;
case hwmon_power_average_interval :
interval = priv - > power_nsamples [ chan ] * LN2_SAMPLE_US ;
* val = DIV_ROUND_CLOSEST ( interval , 1000 ) ;
return 0 ;
default :
return - EOPNOTSUPP ;
}
default :
return - EOPNOTSUPP ;
}
}
static int lochnagar_read_string ( struct device * dev ,
enum hwmon_sensor_types type , u32 attr ,
int chan , const char * * str )
{
switch ( type ) {
case hwmon_in :
case hwmon_curr :
case hwmon_power :
* str = lochnagar_chan_names [ chan ] ;
return 0 ;
default :
return - EOPNOTSUPP ;
}
}
static int lochnagar_write ( struct device * dev , enum hwmon_sensor_types type ,
u32 attr , int chan , long val )
{
struct lochnagar_hwmon * priv = dev_get_drvdata ( dev ) ;
if ( type ! = hwmon_power | | attr ! = hwmon_power_average_interval )
return - EOPNOTSUPP ;
val = clamp_t ( long , val , 1 , ( LN2_MAX_NSAMPLE * LN2_SAMPLE_US ) / 1000 ) ;
val = DIV_ROUND_CLOSEST ( val * 1000 , LN2_SAMPLE_US ) ;
priv - > power_nsamples [ chan ] = val ;
return 0 ;
}
static const struct hwmon_ops lochnagar_ops = {
. is_visible = lochnagar_is_visible ,
. read = lochnagar_read ,
. read_string = lochnagar_read_string ,
. write = lochnagar_write ,
} ;
2023-04-06 22:30:25 +02:00
static const struct hwmon_channel_info * const lochnagar_info [ ] = {
2019-03-28 13:09:37 +00:00
HWMON_CHANNEL_INFO ( temp , HWMON_T_INPUT ) ,
HWMON_CHANNEL_INFO ( in , HWMON_I_INPUT | HWMON_I_LABEL ,
HWMON_I_INPUT | HWMON_I_LABEL ,
HWMON_I_INPUT | HWMON_I_LABEL ,
HWMON_I_INPUT | HWMON_I_LABEL ,
HWMON_I_INPUT | HWMON_I_LABEL ,
HWMON_I_INPUT | HWMON_I_LABEL ,
HWMON_I_INPUT | HWMON_I_LABEL ,
HWMON_I_INPUT | HWMON_I_LABEL ) ,
HWMON_CHANNEL_INFO ( curr , HWMON_C_INPUT | HWMON_C_LABEL ,
HWMON_C_INPUT | HWMON_C_LABEL ,
HWMON_C_INPUT | HWMON_C_LABEL ,
HWMON_C_INPUT | HWMON_C_LABEL ,
HWMON_C_INPUT | HWMON_C_LABEL ,
HWMON_C_INPUT | HWMON_C_LABEL ,
HWMON_C_INPUT | HWMON_C_LABEL ,
HWMON_C_INPUT | HWMON_C_LABEL ) ,
HWMON_CHANNEL_INFO ( power , HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
HWMON_P_LABEL ,
HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
HWMON_P_LABEL ,
HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
HWMON_P_LABEL ,
HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
HWMON_P_LABEL ,
HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
HWMON_P_LABEL ,
HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
HWMON_P_LABEL ,
HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
HWMON_P_LABEL ,
HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
HWMON_P_LABEL ) ,
NULL
} ;
static const struct hwmon_chip_info lochnagar_chip_info = {
. ops = & lochnagar_ops ,
. info = lochnagar_info ,
} ;
static const struct of_device_id lochnagar_of_match [ ] = {
{ . compatible = " cirrus,lochnagar2-hwmon " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , lochnagar_of_match ) ;
static int lochnagar_hwmon_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct device * hwmon_dev ;
struct lochnagar_hwmon * priv ;
int i ;
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
mutex_init ( & priv - > sensor_lock ) ;
priv - > regmap = dev_get_regmap ( dev - > parent , NULL ) ;
if ( ! priv - > regmap ) {
dev_err ( dev , " No register map found \n " ) ;
return - EINVAL ;
}
for ( i = 0 ; i < ARRAY_SIZE ( priv - > power_nsamples ) ; i + + )
priv - > power_nsamples [ i ] = 96 ;
hwmon_dev = devm_hwmon_device_register_with_info ( dev , " Lochnagar " , priv ,
& lochnagar_chip_info ,
NULL ) ;
return PTR_ERR_OR_ZERO ( hwmon_dev ) ;
}
static struct platform_driver lochnagar_hwmon_driver = {
. driver = {
. name = " lochnagar-hwmon " ,
. of_match_table = lochnagar_of_match ,
} ,
. probe = lochnagar_hwmon_probe ,
} ;
module_platform_driver ( lochnagar_hwmon_driver ) ;
MODULE_AUTHOR ( " Lucas Tanure <tanureal@opensource.cirrus.com> " ) ;
MODULE_DESCRIPTION ( " Lochnagar hardware monitoring features " ) ;
MODULE_LICENSE ( " GPL " ) ;