2018-04-24 00:08:09 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* IIO rescale driver
*
* Copyright ( C ) 2018 Axentia Technologies AB
2022-02-13 05:57:32 +03:00
* Copyright ( C ) 2022 Liam Beguin < liambeguin @ gmail . com >
2018-04-24 00:08:09 +03:00
*
* Author : Peter Rosin < peda @ axentia . se >
*/
# include <linux/err.h>
# include <linux/gcd.h>
2022-04-13 22:01:17 +03:00
# include <linux/mod_devicetable.h>
2018-04-24 00:08:09 +03:00
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/property.h>
2022-02-13 05:57:30 +03:00
# include <linux/iio/afe/rescale.h>
2022-01-08 23:53:08 +03:00
# include <linux/iio/consumer.h>
# include <linux/iio/iio.h>
2022-02-13 05:57:30 +03:00
int rescale_process_scale ( struct rescale * rescale , int scale_type ,
int * val , int * val2 )
{
s64 tmp ;
2022-02-13 05:57:34 +03:00
int _val , _val2 ;
2022-02-13 05:57:33 +03:00
s32 rem , rem2 ;
2022-02-13 05:57:31 +03:00
u32 mult ;
u32 neg ;
2018-04-24 00:08:09 +03:00
2022-02-13 05:57:30 +03:00
switch ( scale_type ) {
case IIO_VAL_INT :
* val * = rescale - > numerator ;
if ( rescale - > denominator = = 1 )
return scale_type ;
* val2 = rescale - > denominator ;
return IIO_VAL_FRACTIONAL ;
2022-02-13 05:57:34 +03:00
case IIO_VAL_FRACTIONAL :
/*
* When the product of both scales doesn ' t overflow , avoid
* potential accuracy loss ( for in kernel consumers ) by
* keeping a fractional representation .
*/
if ( ! check_mul_overflow ( * val , rescale - > numerator , & _val ) & &
! check_mul_overflow ( * val2 , rescale - > denominator , & _val2 ) ) {
* val = _val ;
* val2 = _val2 ;
return IIO_VAL_FRACTIONAL ;
}
fallthrough ;
2022-02-13 05:57:30 +03:00
case IIO_VAL_FRACTIONAL_LOG2 :
tmp = ( s64 ) * val * 1000000000LL ;
tmp = div_s64 ( tmp , rescale - > denominator ) ;
tmp * = rescale - > numerator ;
2022-02-13 05:57:33 +03:00
tmp = div_s64_rem ( tmp , 1000000000LL , & rem ) ;
2022-02-13 05:57:30 +03:00
* val = tmp ;
2022-02-13 05:57:33 +03:00
if ( ! rem )
return scale_type ;
2022-02-13 05:57:34 +03:00
if ( scale_type = = IIO_VAL_FRACTIONAL )
tmp = * val2 ;
else
tmp = ULL ( 1 ) < < * val2 ;
2022-02-13 05:57:33 +03:00
rem2 = * val % ( int ) tmp ;
* val = * val / ( int ) tmp ;
* val2 = rem / ( int ) tmp ;
if ( rem2 )
* val2 + = div_s64 ( ( s64 ) rem2 * 1000000000LL , tmp ) ;
return IIO_VAL_INT_PLUS_NANO ;
2022-02-13 05:57:31 +03:00
case IIO_VAL_INT_PLUS_NANO :
case IIO_VAL_INT_PLUS_MICRO :
mult = scale_type = = IIO_VAL_INT_PLUS_NANO ? 1000000000L : 1000000L ;
/*
* For IIO_VAL_INT_PLUS_ { MICRO , NANO } scale types if either * val
* OR * val2 is negative the schan scale is negative , i . e .
* * val = 1 and * val2 = - 0.5 yields - 1.5 not - 0.5 .
*/
neg = * val < 0 | | * val2 < 0 ;
tmp = ( s64 ) abs ( * val ) * abs ( rescale - > numerator ) ;
* val = div_s64_rem ( tmp , abs ( rescale - > denominator ) , & rem ) ;
tmp = ( s64 ) rem * mult + ( s64 ) abs ( * val2 ) * abs ( rescale - > numerator ) ;
tmp = div_s64 ( tmp , abs ( rescale - > denominator ) ) ;
* val + = div_s64_rem ( tmp , mult , val2 ) ;
/*
* If only one of the rescaler elements or the schan scale is
* negative , the combined scale is negative .
*/
if ( neg ^ ( ( rescale - > numerator < 0 ) ^ ( rescale - > denominator < 0 ) ) ) {
if ( * val )
* val = - * val ;
else
* val2 = - * val2 ;
}
2022-02-13 05:57:30 +03:00
return scale_type ;
default :
return - EOPNOTSUPP ;
}
}
2022-07-10 04:31:08 +03:00
EXPORT_SYMBOL_NS_GPL ( rescale_process_scale , IIO_RESCALE ) ;
2018-04-24 00:08:09 +03:00
2022-02-13 05:57:32 +03:00
int rescale_process_offset ( struct rescale * rescale , int scale_type ,
int scale , int scale2 , int schan_off ,
int * val , int * val2 )
{
s64 tmp , tmp2 ;
switch ( scale_type ) {
case IIO_VAL_FRACTIONAL :
tmp = ( s64 ) rescale - > offset * scale2 ;
* val = div_s64 ( tmp , scale ) + schan_off ;
return IIO_VAL_INT ;
case IIO_VAL_INT :
* val = div_s64 ( rescale - > offset , scale ) + schan_off ;
return IIO_VAL_INT ;
case IIO_VAL_FRACTIONAL_LOG2 :
tmp = ( s64 ) rescale - > offset * ( 1 < < scale2 ) ;
* val = div_s64 ( tmp , scale ) + schan_off ;
return IIO_VAL_INT ;
case IIO_VAL_INT_PLUS_NANO :
tmp = ( s64 ) rescale - > offset * 1000000000LL ;
tmp2 = ( ( s64 ) scale * 1000000000LL ) + scale2 ;
* val = div64_s64 ( tmp , tmp2 ) + schan_off ;
return IIO_VAL_INT ;
case IIO_VAL_INT_PLUS_MICRO :
tmp = ( s64 ) rescale - > offset * 1000000LL ;
tmp2 = ( ( s64 ) scale * 1000000LL ) + scale2 ;
* val = div64_s64 ( tmp , tmp2 ) + schan_off ;
return IIO_VAL_INT ;
default :
return - EOPNOTSUPP ;
}
}
2022-07-10 04:31:08 +03:00
EXPORT_SYMBOL_NS_GPL ( rescale_process_offset , IIO_RESCALE ) ;
2022-02-13 05:57:32 +03:00
2018-04-24 00:08:09 +03:00
static int rescale_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan ,
int * val , int * val2 , long mask )
{
struct rescale * rescale = iio_priv ( indio_dev ) ;
2022-02-13 05:57:32 +03:00
int scale , scale2 ;
int schan_off = 0 ;
2018-04-24 00:08:09 +03:00
int ret ;
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
2021-05-18 12:27:41 +03:00
if ( rescale - > chan_processed )
/*
* When only processed channels are supported , we
* read the processed data and scale it by 1 / 1
* augmented with whatever the rescaler has calculated .
*/
return iio_read_channel_processed ( rescale - > source , val ) ;
else
return iio_read_channel_raw ( rescale - > source , val ) ;
2018-04-24 00:08:09 +03:00
case IIO_CHAN_INFO_SCALE :
2021-05-18 12:27:41 +03:00
if ( rescale - > chan_processed ) {
/*
* Processed channels are scaled 1 - to - 1
*/
* val = 1 ;
* val2 = 1 ;
ret = IIO_VAL_FRACTIONAL ;
} else {
ret = iio_read_channel_scale ( rescale - > source , val , val2 ) ;
}
2022-02-13 05:57:30 +03:00
return rescale_process_scale ( rescale , ret , val , val2 ) ;
2022-02-13 05:57:32 +03:00
case IIO_CHAN_INFO_OFFSET :
/*
* Processed channels are scaled 1 - to - 1 and source offset is
* already taken into account .
*
* In other cases , real world measurement are expressed as :
*
* schan_scale * ( raw + schan_offset )
*
* Given that the rescaler parameters are applied recursively :
*
* rescaler_scale * ( schan_scale * ( raw + schan_offset ) +
* rescaler_offset )
*
* Or ,
*
* ( rescaler_scale * schan_scale ) * ( raw +
* ( schan_offset + rescaler_offset / schan_scale )
*
* Thus , reusing the original expression the parameters exposed
* to userspace are :
*
* scale = schan_scale * rescaler_scale
* offset = schan_offset + rescaler_offset / schan_scale
*/
if ( rescale - > chan_processed ) {
* val = rescale - > offset ;
return IIO_VAL_INT ;
}
if ( iio_channel_has_info ( rescale - > source - > channel ,
IIO_CHAN_INFO_OFFSET ) ) {
ret = iio_read_channel_offset ( rescale - > source ,
& schan_off , NULL ) ;
if ( ret ! = IIO_VAL_INT )
return ret < 0 ? ret : - EOPNOTSUPP ;
}
ret = iio_read_channel_scale ( rescale - > source , & scale , & scale2 ) ;
return rescale_process_offset ( rescale , ret , scale , scale2 ,
schan_off , val , val2 ) ;
2018-04-24 00:08:09 +03:00
default :
return - EINVAL ;
}
}
static int rescale_read_avail ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan ,
const int * * vals , int * type , int * length ,
long mask )
{
struct rescale * rescale = iio_priv ( indio_dev ) ;
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
* type = IIO_VAL_INT ;
return iio_read_avail_channel_raw ( rescale - > source ,
vals , length ) ;
default :
return - EINVAL ;
}
}
static const struct iio_info rescale_info = {
. read_raw = rescale_read_raw ,
. read_avail = rescale_read_avail ,
} ;
static ssize_t rescale_read_ext_info ( struct iio_dev * indio_dev ,
uintptr_t private ,
struct iio_chan_spec const * chan ,
char * buf )
{
struct rescale * rescale = iio_priv ( indio_dev ) ;
return iio_read_channel_ext_info ( rescale - > source ,
rescale - > ext_info [ private ] . name ,
buf ) ;
}
static ssize_t rescale_write_ext_info ( struct iio_dev * indio_dev ,
uintptr_t private ,
struct iio_chan_spec const * chan ,
const char * buf , size_t len )
{
struct rescale * rescale = iio_priv ( indio_dev ) ;
return iio_write_channel_ext_info ( rescale - > source ,
rescale - > ext_info [ private ] . name ,
buf , len ) ;
}
static int rescale_configure_channel ( struct device * dev ,
struct rescale * rescale )
{
struct iio_chan_spec * chan = & rescale - > chan ;
struct iio_chan_spec const * schan = rescale - > source - > channel ;
chan - > indexed = 1 ;
chan - > output = schan - > output ;
chan - > ext_info = rescale - > ext_info ;
chan - > type = rescale - > cfg - > type ;
2022-05-24 10:54:48 +03:00
if ( iio_channel_has_info ( schan , IIO_CHAN_INFO_RAW ) & &
2021-05-18 12:27:41 +03:00
iio_channel_has_info ( schan , IIO_CHAN_INFO_SCALE ) ) {
dev_info ( dev , " using raw+scale source channel \n " ) ;
} else if ( iio_channel_has_info ( schan , IIO_CHAN_INFO_PROCESSED ) ) {
dev_info ( dev , " using processed channel \n " ) ;
rescale - > chan_processed = true ;
} else {
dev_err ( dev , " source channel is not supported \n " ) ;
2018-04-24 00:08:09 +03:00
return - EINVAL ;
}
chan - > info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) |
BIT ( IIO_CHAN_INFO_SCALE ) ;
2022-02-13 05:57:32 +03:00
if ( rescale - > offset )
chan - > info_mask_separate | = BIT ( IIO_CHAN_INFO_OFFSET ) ;
2021-05-18 12:27:41 +03:00
/*
* Using . read_avail ( ) is fringe to begin with and makes no sense
* whatsoever for processed channels , so we make sure that this cannot
* be called on a processed channel .
*/
if ( iio_channel_has_available ( schan , IIO_CHAN_INFO_RAW ) & &
! rescale - > chan_processed )
2018-04-24 00:08:09 +03:00
chan - > info_mask_separate_available | = BIT ( IIO_CHAN_INFO_RAW ) ;
return 0 ;
}
static int rescale_current_sense_amplifier_props ( struct device * dev ,
struct rescale * rescale )
{
u32 sense ;
u32 gain_mult = 1 ;
u32 gain_div = 1 ;
u32 factor ;
int ret ;
ret = device_property_read_u32 ( dev , " sense-resistor-micro-ohms " ,
& sense ) ;
if ( ret ) {
dev_err ( dev , " failed to read the sense resistance: %d \n " , ret ) ;
return ret ;
}
device_property_read_u32 ( dev , " sense-gain-mult " , & gain_mult ) ;
device_property_read_u32 ( dev , " sense-gain-div " , & gain_div ) ;
/*
* Calculate the scaling factor , 1 / ( gain * sense ) , or
* gain_div / ( gain_mult * sense ) , while trying to keep the
* numerator / denominator from overflowing .
*/
factor = gcd ( sense , 1000000 ) ;
rescale - > numerator = 1000000 / factor ;
rescale - > denominator = sense / factor ;
factor = gcd ( rescale - > numerator , gain_mult ) ;
rescale - > numerator / = factor ;
rescale - > denominator * = gain_mult / factor ;
factor = gcd ( rescale - > denominator , gain_div ) ;
rescale - > numerator * = gain_div / factor ;
rescale - > denominator / = factor ;
return 0 ;
}
static int rescale_current_sense_shunt_props ( struct device * dev ,
struct rescale * rescale )
{
u32 shunt ;
u32 factor ;
int ret ;
ret = device_property_read_u32 ( dev , " shunt-resistor-micro-ohms " ,
& shunt ) ;
if ( ret ) {
dev_err ( dev , " failed to read the shunt resistance: %d \n " , ret ) ;
return ret ;
}
factor = gcd ( shunt , 1000000 ) ;
rescale - > numerator = 1000000 / factor ;
rescale - > denominator = shunt / factor ;
return 0 ;
}
static int rescale_voltage_divider_props ( struct device * dev ,
struct rescale * rescale )
{
int ret ;
u32 factor ;
ret = device_property_read_u32 ( dev , " output-ohms " ,
& rescale - > denominator ) ;
if ( ret ) {
dev_err ( dev , " failed to read output-ohms: %d \n " , ret ) ;
return ret ;
}
ret = device_property_read_u32 ( dev , " full-ohms " ,
& rescale - > numerator ) ;
if ( ret ) {
dev_err ( dev , " failed to read full-ohms: %d \n " , ret ) ;
return ret ;
}
factor = gcd ( rescale - > numerator , rescale - > denominator ) ;
rescale - > numerator / = factor ;
rescale - > denominator / = factor ;
return 0 ;
}
2022-02-13 05:57:36 +03:00
static int rescale_temp_sense_rtd_props ( struct device * dev ,
struct rescale * rescale )
{
u32 factor ;
u32 alpha ;
u32 iexc ;
u32 tmp ;
int ret ;
u32 r0 ;
ret = device_property_read_u32 ( dev , " excitation-current-microamp " ,
& iexc ) ;
if ( ret ) {
dev_err ( dev , " failed to read excitation-current-microamp: %d \n " ,
ret ) ;
return ret ;
}
ret = device_property_read_u32 ( dev , " alpha-ppm-per-celsius " , & alpha ) ;
if ( ret ) {
dev_err ( dev , " failed to read alpha-ppm-per-celsius: %d \n " ,
ret ) ;
return ret ;
}
ret = device_property_read_u32 ( dev , " r-naught-ohms " , & r0 ) ;
if ( ret ) {
dev_err ( dev , " failed to read r-naught-ohms: %d \n " , ret ) ;
return ret ;
}
tmp = r0 * iexc * alpha / 1000000 ;
factor = gcd ( tmp , 1000000 ) ;
rescale - > numerator = 1000000 / factor ;
rescale - > denominator = tmp / factor ;
rescale - > offset = - 1 * ( ( r0 * iexc ) / 1000 ) ;
return 0 ;
}
2022-02-13 05:57:37 +03:00
static int rescale_temp_transducer_props ( struct device * dev ,
struct rescale * rescale )
{
s32 offset = 0 ;
s32 sense = 1 ;
s32 alpha ;
int ret ;
device_property_read_u32 ( dev , " sense-offset-millicelsius " , & offset ) ;
device_property_read_u32 ( dev , " sense-resistor-ohms " , & sense ) ;
ret = device_property_read_u32 ( dev , " alpha-ppm-per-celsius " , & alpha ) ;
if ( ret ) {
dev_err ( dev , " failed to read alpha-ppm-per-celsius: %d \n " , ret ) ;
return ret ;
}
rescale - > numerator = 1000000 ;
rescale - > denominator = alpha * sense ;
rescale - > offset = div_s64 ( ( s64 ) offset * rescale - > denominator ,
rescale - > numerator ) ;
return 0 ;
}
2018-04-24 00:08:09 +03:00
enum rescale_variant {
CURRENT_SENSE_AMPLIFIER ,
CURRENT_SENSE_SHUNT ,
VOLTAGE_DIVIDER ,
2022-02-13 05:57:36 +03:00
TEMP_SENSE_RTD ,
2022-02-13 05:57:37 +03:00
TEMP_TRANSDUCER ,
2018-04-24 00:08:09 +03:00
} ;
static const struct rescale_cfg rescale_cfg [ ] = {
[ CURRENT_SENSE_AMPLIFIER ] = {
. type = IIO_CURRENT ,
. props = rescale_current_sense_amplifier_props ,
} ,
[ CURRENT_SENSE_SHUNT ] = {
. type = IIO_CURRENT ,
. props = rescale_current_sense_shunt_props ,
} ,
[ VOLTAGE_DIVIDER ] = {
. type = IIO_VOLTAGE ,
. props = rescale_voltage_divider_props ,
} ,
2022-02-13 05:57:36 +03:00
[ TEMP_SENSE_RTD ] = {
. type = IIO_TEMP ,
. props = rescale_temp_sense_rtd_props ,
} ,
2022-02-13 05:57:37 +03:00
[ TEMP_TRANSDUCER ] = {
. type = IIO_TEMP ,
. props = rescale_temp_transducer_props ,
} ,
2018-04-24 00:08:09 +03:00
} ;
static const struct of_device_id rescale_match [ ] = {
{ . compatible = " current-sense-amplifier " ,
. data = & rescale_cfg [ CURRENT_SENSE_AMPLIFIER ] , } ,
{ . compatible = " current-sense-shunt " ,
. data = & rescale_cfg [ CURRENT_SENSE_SHUNT ] , } ,
{ . compatible = " voltage-divider " ,
. data = & rescale_cfg [ VOLTAGE_DIVIDER ] , } ,
2022-02-13 05:57:36 +03:00
{ . compatible = " temperature-sense-rtd " ,
. data = & rescale_cfg [ TEMP_SENSE_RTD ] , } ,
2022-02-13 05:57:37 +03:00
{ . compatible = " temperature-transducer " ,
. data = & rescale_cfg [ TEMP_TRANSDUCER ] , } ,
2018-04-24 00:08:09 +03:00
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , rescale_match ) ;
static int rescale_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct iio_dev * indio_dev ;
struct iio_channel * source ;
struct rescale * rescale ;
int sizeof_ext_info ;
int sizeof_priv ;
int i ;
int ret ;
source = devm_iio_channel_get ( dev , NULL ) ;
2020-08-29 09:47:17 +03:00
if ( IS_ERR ( source ) )
return dev_err_probe ( dev , PTR_ERR ( source ) ,
" failed to get source channel \n " ) ;
2018-04-24 00:08:09 +03:00
sizeof_ext_info = iio_get_channel_ext_info_count ( source ) ;
if ( sizeof_ext_info ) {
sizeof_ext_info + = 1 ; /* one extra entry for the sentinel */
sizeof_ext_info * = sizeof ( * rescale - > ext_info ) ;
}
sizeof_priv = sizeof ( * rescale ) + sizeof_ext_info ;
indio_dev = devm_iio_device_alloc ( dev , sizeof_priv ) ;
if ( ! indio_dev )
return - ENOMEM ;
rescale = iio_priv ( indio_dev ) ;
2022-04-13 22:01:17 +03:00
rescale - > cfg = device_get_match_data ( dev ) ;
2018-04-24 00:08:09 +03:00
rescale - > numerator = 1 ;
rescale - > denominator = 1 ;
2022-02-13 05:57:32 +03:00
rescale - > offset = 0 ;
2018-04-24 00:08:09 +03:00
ret = rescale - > cfg - > props ( dev , rescale ) ;
if ( ret )
return ret ;
if ( ! rescale - > numerator | | ! rescale - > denominator ) {
dev_err ( dev , " invalid scaling factor. \n " ) ;
return - EINVAL ;
}
platform_set_drvdata ( pdev , indio_dev ) ;
rescale - > source = source ;
indio_dev - > name = dev_name ( dev ) ;
indio_dev - > info = & rescale_info ;
indio_dev - > modes = INDIO_DIRECT_MODE ;
indio_dev - > channels = & rescale - > chan ;
indio_dev - > num_channels = 1 ;
if ( sizeof_ext_info ) {
rescale - > ext_info = devm_kmemdup ( dev ,
source - > channel - > ext_info ,
sizeof_ext_info , GFP_KERNEL ) ;
if ( ! rescale - > ext_info )
return - ENOMEM ;
for ( i = 0 ; rescale - > ext_info [ i ] . name ; + + i ) {
struct iio_chan_spec_ext_info * ext_info =
& rescale - > ext_info [ i ] ;
if ( source - > channel - > ext_info [ i ] . read )
ext_info - > read = rescale_read_ext_info ;
if ( source - > channel - > ext_info [ i ] . write )
ext_info - > write = rescale_write_ext_info ;
ext_info - > private = i ;
}
}
ret = rescale_configure_channel ( dev , rescale ) ;
if ( ret )
return ret ;
return devm_iio_device_register ( dev , indio_dev ) ;
}
static struct platform_driver rescale_driver = {
. probe = rescale_probe ,
. driver = {
. name = " iio-rescale " ,
. of_match_table = rescale_match ,
} ,
} ;
module_platform_driver ( rescale_driver ) ;
MODULE_DESCRIPTION ( " IIO rescale driver " ) ;
MODULE_AUTHOR ( " Peter Rosin <peda@axentia.se> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;