diff mbox series

[2/4] iio: adc: ad4695: implement calibration support

Message ID 20240820-ad4695-gain-offset-v1-2-c8f6e3b47551@baylibre.com (mailing list archive)
State Accepted
Headers show
Series iio: adc: ad4695: implement calibration support | expand

Commit Message

David Lechner Aug. 20, 2024, 3:58 p.m. UTC
The AD4695 has a calibration feature that allows the user to compensate
for variations in the analog front end. This implements this feature in
the driver using the standard `calibgain` and `calibbias` attributes.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---
 drivers/iio/adc/ad4695.c | 159 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 157 insertions(+), 2 deletions(-)

Comments

kernel test robot Aug. 21, 2024, 4:21 a.m. UTC | #1
Hi David,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 0f718e10da81446df0909c9939dff2b77e3b4e95]

url:    https://github.com/intel-lab-lkp/linux/commits/David-Lechner/iio-adc-ad4695-add-2nd-regmap-for-16-bit-registers/20240821-000102
base:   0f718e10da81446df0909c9939dff2b77e3b4e95
patch link:    https://lore.kernel.org/r/20240820-ad4695-gain-offset-v1-2-c8f6e3b47551%40baylibre.com
patch subject: [PATCH 2/4] iio: adc: ad4695: implement calibration support
config: i386-buildonly-randconfig-002-20240821 (https://download.01.org/0day-ci/archive/20240821/202408211207.fmYTjQDK-lkp@intel.com/config)
compiler: clang version 18.1.5 (https://github.com/llvm/llvm-project 617a15a9eac96088ae5e9134248d8236e34b91b1)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240821/202408211207.fmYTjQDK-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202408211207.fmYTjQDK-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/iio/adc/ad4695.c:735:6: warning: unused variable 'ret' [-Wunused-variable]
     735 |         int ret;
         |             ^~~
   1 warning generated.


vim +/ret +735 drivers/iio/adc/ad4695.c

   728	
   729	static int ad4695_write_raw(struct iio_dev *indio_dev,
   730				    struct iio_chan_spec const *chan,
   731				    int val, int val2, long mask)
   732	{
   733		struct ad4695_state *st = iio_priv(indio_dev);
   734		unsigned int reg_val;
 > 735		int ret;
   736	
   737		iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
   738			switch (mask) {
   739			case IIO_CHAN_INFO_CALIBSCALE:
   740				switch (chan->type) {
   741				case IIO_VOLTAGE:
   742					if (val < 0 || val2 < 0)
   743						reg_val = 0;
   744					else if (val > 1)
   745						reg_val = U16_MAX;
   746					else
   747						reg_val = (val * (1 << 16) +
   748							   mul_u64_u32_div(val2, 1 << 16,
   749									   MICRO)) / 2;
   750	
   751					return regmap_write(st->regmap16,
   752						AD4695_REG_GAIN_IN(chan->scan_index),
   753						reg_val);
   754				default:
   755					return -EINVAL;
   756				}
   757			case IIO_CHAN_INFO_CALIBBIAS:
   758				switch (chan->type) {
   759				case IIO_VOLTAGE:
   760					if (val2 >= 0 && val > S16_MAX / 4)
   761						reg_val = S16_MAX;
   762					else if ((val2 < 0 ? -val : val) < S16_MIN / 4)
   763						reg_val = S16_MIN;
   764					else if (val2 < 0)
   765						reg_val = clamp_t(int,
   766							-(val * 4 + -val2 * 4 / MICRO),
   767							S16_MIN, S16_MAX);
   768					else if (val < 0)
   769						reg_val = clamp_t(int,
   770							val * 4 - val2 * 4 / MICRO,
   771							S16_MIN, S16_MAX);
   772					else
   773						reg_val = clamp_t(int,
   774							val * 4 + val2 * 4 / MICRO,
   775							S16_MIN, S16_MAX);
   776	
   777					return regmap_write(st->regmap16,
   778						AD4695_REG_OFFSET_IN(chan->scan_index),
   779						reg_val);
   780				default:
   781					return -EINVAL;
   782				}
   783			default:
   784				return -EINVAL;
   785			}
   786		}
   787		unreachable();
   788	}
   789
Jonathan Cameron Aug. 21, 2024, 1:16 p.m. UTC | #2
On Tue, 20 Aug 2024 10:58:36 -0500
David Lechner <dlechner@baylibre.com> wrote:

> The AD4695 has a calibration feature that allows the user to compensate
> for variations in the analog front end. This implements this feature in
> the driver using the standard `calibgain` and `calibbias` attributes.
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
Hi David,

Whilst some of the messy value manipulation is unavoidable
(oh for signed integer zero!), I wonder if we can at least
move one case into the core.

See below.

> +
> +static int ad4695_write_raw(struct iio_dev *indio_dev,
> +			    struct iio_chan_spec const *chan,
> +			    int val, int val2, long mask)
> +{
> +	struct ad4695_state *st = iio_priv(indio_dev);
> +	unsigned int reg_val;
> +	int ret;
> +
> +	iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
> +		switch (mask) {
> +		case IIO_CHAN_INFO_CALIBSCALE:
> +			switch (chan->type) {
> +			case IIO_VOLTAGE:
> +				if (val < 0 || val2 < 0)
> +					reg_val = 0;
> +				else if (val > 1)
> +					reg_val = U16_MAX;
> +				else
> +					reg_val = (val * (1 << 16) +
> +						   mul_u64_u32_div(val2, 1 << 16,
> +								   MICRO)) / 2;
Maybe worth extending iio_write_channel_info() to handle
IIO_VAL_FRACTIONAL_LOG2()?
It'll look much like this and you'll need to provide write_raw_get_fmt()
so the core know what to do with the value formatting.

I don't really like the mixture here between the read path being able
to rely on the core code to deal with the /2^X and the write path not.
> +
> +				return regmap_write(st->regmap16,
> +					AD4695_REG_GAIN_IN(chan->scan_index),
> +					reg_val);
> +			default:
> +				return -EINVAL;
David Lechner Aug. 21, 2024, 4:12 p.m. UTC | #3
On 8/21/24 8:16 AM, Jonathan Cameron wrote:
> On Tue, 20 Aug 2024 10:58:36 -0500
> David Lechner <dlechner@baylibre.com> wrote:
> 
>> The AD4695 has a calibration feature that allows the user to compensate
>> for variations in the analog front end. This implements this feature in
>> the driver using the standard `calibgain` and `calibbias` attributes.
>>
>> Signed-off-by: David Lechner <dlechner@baylibre.com>
> Hi David,
> 
> Whilst some of the messy value manipulation is unavoidable
> (oh for signed integer zero!), I wonder if we can at least
> move one case into the core.
> 
> See below.
> 
>> +
>> +static int ad4695_write_raw(struct iio_dev *indio_dev,
>> +			    struct iio_chan_spec const *chan,
>> +			    int val, int val2, long mask)
>> +{
>> +	struct ad4695_state *st = iio_priv(indio_dev);
>> +	unsigned int reg_val;
>> +	int ret;
>> +
>> +	iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
>> +		switch (mask) {
>> +		case IIO_CHAN_INFO_CALIBSCALE:
>> +			switch (chan->type) {
>> +			case IIO_VOLTAGE:
>> +				if (val < 0 || val2 < 0)
>> +					reg_val = 0;
>> +				else if (val > 1)
>> +					reg_val = U16_MAX;
>> +				else
>> +					reg_val = (val * (1 << 16) +
>> +						   mul_u64_u32_div(val2, 1 << 16,
>> +								   MICRO)) / 2;
> Maybe worth extending iio_write_channel_info() to handle
> IIO_VAL_FRACTIONAL_LOG2()?
> It'll look much like this and you'll need to provide write_raw_get_fmt()
> so the core know what to do with the value formatting.
> 
> I don't really like the mixture here between the read path being able
> to rely on the core code to deal with the /2^X and the write path not.

Sounds like a good idea to me.

It seems like we would need to add an extra out parameter to 
write_raw_get_fmt to say what we want the X in 2^X to be. For
example, we would want to make sure the val2 we get in write_raw
for this driver is 15.

>> +
>> +				return regmap_write(st->regmap16,
>> +					AD4695_REG_GAIN_IN(chan->scan_index),
>> +					reg_val);
>> +			default:
>> +				return -EINVAL;
> 
> 
>
Jonathan Cameron Aug. 21, 2024, 4:33 p.m. UTC | #4
On Wed, 21 Aug 2024 11:12:12 -0500
David Lechner <dlechner@baylibre.com> wrote:

> On 8/21/24 8:16 AM, Jonathan Cameron wrote:
> > On Tue, 20 Aug 2024 10:58:36 -0500
> > David Lechner <dlechner@baylibre.com> wrote:
> >   
> >> The AD4695 has a calibration feature that allows the user to compensate
> >> for variations in the analog front end. This implements this feature in
> >> the driver using the standard `calibgain` and `calibbias` attributes.
> >>
> >> Signed-off-by: David Lechner <dlechner@baylibre.com>  
> > Hi David,
> > 
> > Whilst some of the messy value manipulation is unavoidable
> > (oh for signed integer zero!), I wonder if we can at least
> > move one case into the core.
> > 
> > See below.
> >   
> >> +
> >> +static int ad4695_write_raw(struct iio_dev *indio_dev,
> >> +			    struct iio_chan_spec const *chan,
> >> +			    int val, int val2, long mask)
> >> +{
> >> +	struct ad4695_state *st = iio_priv(indio_dev);
> >> +	unsigned int reg_val;
> >> +	int ret;
> >> +
> >> +	iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
> >> +		switch (mask) {
> >> +		case IIO_CHAN_INFO_CALIBSCALE:
> >> +			switch (chan->type) {
> >> +			case IIO_VOLTAGE:
> >> +				if (val < 0 || val2 < 0)
> >> +					reg_val = 0;
> >> +				else if (val > 1)
> >> +					reg_val = U16_MAX;
> >> +				else
> >> +					reg_val = (val * (1 << 16) +
> >> +						   mul_u64_u32_div(val2, 1 << 16,
> >> +								   MICRO)) / 2;  
> > Maybe worth extending iio_write_channel_info() to handle
> > IIO_VAL_FRACTIONAL_LOG2()?
> > It'll look much like this and you'll need to provide write_raw_get_fmt()
> > so the core know what to do with the value formatting.
> > 
> > I don't really like the mixture here between the read path being able
> > to rely on the core code to deal with the /2^X and the write path not.  
> 
> Sounds like a good idea to me.
> 
> It seems like we would need to add an extra out parameter to 
> write_raw_get_fmt to say what we want the X in 2^X to be. For
> example, we would want to make sure the val2 we get in write_raw
> for this driver is 15.
Yes.  (I tried to reply to say I'd neglected this but managed to
just email myself. oops.)

Maybe it's too complex to bother given that.  Definitely a job
for another day rather than something to block this series on.


Jonathan

> 
> >> +
> >> +				return regmap_write(st->regmap16,
> >> +					AD4695_REG_GAIN_IN(chan->scan_index),
> >> +					reg_val);
> >> +			default:
> >> +				return -EINVAL;  
> > 
> > 
> >   
>
Jonathan Cameron Aug. 26, 2024, 10:57 a.m. UTC | #5
On Wed, 21 Aug 2024 12:21:09 +0800
kernel test robot <lkp@intel.com> wrote:

> Hi David,
> 
> kernel test robot noticed the following build warnings:
> 
> [auto build test WARNING on 0f718e10da81446df0909c9939dff2b77e3b4e95]
> 
> url:    https://github.com/intel-lab-lkp/linux/commits/David-Lechner/iio-adc-ad4695-add-2nd-regmap-for-16-bit-registers/20240821-000102
> base:   0f718e10da81446df0909c9939dff2b77e3b4e95
> patch link:    https://lore.kernel.org/r/20240820-ad4695-gain-offset-v1-2-c8f6e3b47551%40baylibre.com
> patch subject: [PATCH 2/4] iio: adc: ad4695: implement calibration support
> config: i386-buildonly-randconfig-002-20240821 (https://download.01.org/0day-ci/archive/20240821/202408211207.fmYTjQDK-lkp@intel.com/config)
> compiler: clang version 18.1.5 (https://github.com/llvm/llvm-project 617a15a9eac96088ae5e9134248d8236e34b91b1)
> reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240821/202408211207.fmYTjQDK-lkp@intel.com/reproduce)
> 
> If you fix the issue in a separate patch/commit (i.e. not just a new version of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot <lkp@intel.com>
> | Closes: https://lore.kernel.org/oe-kbuild-all/202408211207.fmYTjQDK-lkp@intel.com/
> 
> All warnings (new ones prefixed by >>):
> 
> >> drivers/iio/adc/ad4695.c:735:6: warning: unused variable 'ret' [-Wunused-variable]  
>      735 |         int ret;
>          |             ^~~
>    1 warning generated.
I dropped this whilst applying.

> 
> 
> vim +/ret +735 drivers/iio/adc/ad4695.c
> 
>    728	
>    729	static int ad4695_write_raw(struct iio_dev *indio_dev,
>    730				    struct iio_chan_spec const *chan,
>    731				    int val, int val2, long mask)
>    732	{
>    733		struct ad4695_state *st = iio_priv(indio_dev);
>    734		unsigned int reg_val;
>  > 735		int ret;  
>    736	
>    737		iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
>    738			switch (mask) {
>    739			case IIO_CHAN_INFO_CALIBSCALE:
>    740				switch (chan->type) {
>    741				case IIO_VOLTAGE:
>    742					if (val < 0 || val2 < 0)
>    743						reg_val = 0;
>    744					else if (val > 1)
>    745						reg_val = U16_MAX;
>    746					else
>    747						reg_val = (val * (1 << 16) +
>    748							   mul_u64_u32_div(val2, 1 << 16,
>    749									   MICRO)) / 2;
>    750	
>    751					return regmap_write(st->regmap16,
>    752						AD4695_REG_GAIN_IN(chan->scan_index),
>    753						reg_val);
>    754				default:
>    755					return -EINVAL;
>    756				}
>    757			case IIO_CHAN_INFO_CALIBBIAS:
>    758				switch (chan->type) {
>    759				case IIO_VOLTAGE:
>    760					if (val2 >= 0 && val > S16_MAX / 4)
>    761						reg_val = S16_MAX;
>    762					else if ((val2 < 0 ? -val : val) < S16_MIN / 4)
>    763						reg_val = S16_MIN;
>    764					else if (val2 < 0)
>    765						reg_val = clamp_t(int,
>    766							-(val * 4 + -val2 * 4 / MICRO),
>    767							S16_MIN, S16_MAX);
>    768					else if (val < 0)
>    769						reg_val = clamp_t(int,
>    770							val * 4 - val2 * 4 / MICRO,
>    771							S16_MIN, S16_MAX);
>    772					else
>    773						reg_val = clamp_t(int,
>    774							val * 4 + val2 * 4 / MICRO,
>    775							S16_MIN, S16_MAX);
>    776	
>    777					return regmap_write(st->regmap16,
>    778						AD4695_REG_OFFSET_IN(chan->scan_index),
>    779						reg_val);
>    780				default:
>    781					return -EINVAL;
>    782				}
>    783			default:
>    784				return -EINVAL;
>    785			}
>    786		}
>    787		unreachable();
>    788	}
>    789	
>
diff mbox series

Patch

diff --git a/drivers/iio/adc/ad4695.c b/drivers/iio/adc/ad4695.c
index 63d816ad2d1f..181c4146188c 100644
--- a/drivers/iio/adc/ad4695.c
+++ b/drivers/iio/adc/ad4695.c
@@ -23,6 +23,7 @@ 
 #include <linux/iio/iio.h>
 #include <linux/iio/triggered_buffer.h>
 #include <linux/iio/trigger_consumer.h>
+#include <linux/minmax.h>
 #include <linux/property.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
@@ -225,7 +226,11 @@  static const struct iio_chan_spec ad4695_channel_template = {
 	.indexed = 1,
 	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 			      BIT(IIO_CHAN_INFO_SCALE) |
-			      BIT(IIO_CHAN_INFO_OFFSET),
+			      BIT(IIO_CHAN_INFO_OFFSET) |
+			      BIT(IIO_CHAN_INFO_CALIBSCALE) |
+			      BIT(IIO_CHAN_INFO_CALIBBIAS),
+	.info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBSCALE) |
+					BIT(IIO_CHAN_INFO_CALIBBIAS),
 	.scan_type = {
 		.sign = 'u',
 		.realbits = 16,
@@ -619,7 +624,8 @@  static int ad4695_read_raw(struct iio_dev *indio_dev,
 	struct ad4695_state *st = iio_priv(indio_dev);
 	struct ad4695_channel_config *cfg = &st->channels_cfg[chan->scan_index];
 	u8 realbits = chan->scan_type.realbits;
-	int ret;
+	unsigned int reg_val;
+	int ret, tmp;
 
 	switch (mask) {
 	case IIO_CHAN_INFO_RAW:
@@ -670,6 +676,153 @@  static int ad4695_read_raw(struct iio_dev *indio_dev,
 		default:
 			return -EINVAL;
 		}
+	case IIO_CHAN_INFO_CALIBSCALE:
+		switch (chan->type) {
+		case IIO_VOLTAGE:
+			iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
+				ret = regmap_read(st->regmap16,
+					AD4695_REG_GAIN_IN(chan->scan_index),
+					&reg_val);
+				if (ret)
+					return ret;
+
+				*val = reg_val;
+				*val2 = 15;
+
+				return IIO_VAL_FRACTIONAL_LOG2;
+			}
+			unreachable();
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_CALIBBIAS:
+		switch (chan->type) {
+		case IIO_VOLTAGE:
+			iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
+				ret = regmap_read(st->regmap16,
+					AD4695_REG_OFFSET_IN(chan->scan_index),
+					&reg_val);
+				if (ret)
+					return ret;
+
+				tmp = sign_extend32(reg_val, 15);
+
+				*val = tmp / 4;
+				*val2 = abs(tmp) % 4 * MICRO / 4;
+
+				if (tmp < 0 && *val2) {
+					*val *= -1;
+					*val2 *= -1;
+				}
+
+				return IIO_VAL_INT_PLUS_MICRO;
+			}
+			unreachable();
+		default:
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad4695_write_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int val, int val2, long mask)
+{
+	struct ad4695_state *st = iio_priv(indio_dev);
+	unsigned int reg_val;
+	int ret;
+
+	iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
+		switch (mask) {
+		case IIO_CHAN_INFO_CALIBSCALE:
+			switch (chan->type) {
+			case IIO_VOLTAGE:
+				if (val < 0 || val2 < 0)
+					reg_val = 0;
+				else if (val > 1)
+					reg_val = U16_MAX;
+				else
+					reg_val = (val * (1 << 16) +
+						   mul_u64_u32_div(val2, 1 << 16,
+								   MICRO)) / 2;
+
+				return regmap_write(st->regmap16,
+					AD4695_REG_GAIN_IN(chan->scan_index),
+					reg_val);
+			default:
+				return -EINVAL;
+			}
+		case IIO_CHAN_INFO_CALIBBIAS:
+			switch (chan->type) {
+			case IIO_VOLTAGE:
+				if (val2 >= 0 && val > S16_MAX / 4)
+					reg_val = S16_MAX;
+				else if ((val2 < 0 ? -val : val) < S16_MIN / 4)
+					reg_val = S16_MIN;
+				else if (val2 < 0)
+					reg_val = clamp_t(int,
+						-(val * 4 + -val2 * 4 / MICRO),
+						S16_MIN, S16_MAX);
+				else if (val < 0)
+					reg_val = clamp_t(int,
+						val * 4 - val2 * 4 / MICRO,
+						S16_MIN, S16_MAX);
+				else
+					reg_val = clamp_t(int,
+						val * 4 + val2 * 4 / MICRO,
+						S16_MIN, S16_MAX);
+
+				return regmap_write(st->regmap16,
+					AD4695_REG_OFFSET_IN(chan->scan_index),
+					reg_val);
+			default:
+				return -EINVAL;
+			}
+		default:
+			return -EINVAL;
+		}
+	}
+	unreachable();
+}
+
+static int ad4695_read_avail(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     const int **vals, int *type, int *length,
+			     long mask)
+{
+	static const int ad4695_calibscale_available[6] = {
+		/* Range of 0 (inclusive) to 2 (exclusive) */
+		0, 15, 1, 15, U16_MAX, 15
+	};
+	static const int ad4695_calibbias_available[6] = {
+		/*
+		 * Datasheet says FSR/8 which translates to signed/4. The step
+		 * depends on oversampling ratio which is always 1 for now.
+		 */
+		S16_MIN / 4, 0, 0, MICRO / 4, S16_MAX / 4, S16_MAX % 4 * MICRO / 4
+	};
+
+	switch (mask) {
+	case IIO_CHAN_INFO_CALIBSCALE:
+		switch (chan->type) {
+		case IIO_VOLTAGE:
+			*vals = ad4695_calibscale_available;
+			*type = IIO_VAL_FRACTIONAL_LOG2;
+			return IIO_AVAIL_RANGE;
+		default:
+			return -EINVAL;
+		}
+	case IIO_CHAN_INFO_CALIBBIAS:
+		switch (chan->type) {
+		case IIO_VOLTAGE:
+			*vals = ad4695_calibbias_available;
+			*type = IIO_VAL_INT_PLUS_MICRO;
+			return IIO_AVAIL_RANGE;
+		default:
+			return -EINVAL;
+		}
 	default:
 		return -EINVAL;
 	}
@@ -705,6 +858,8 @@  static int ad4695_debugfs_reg_access(struct iio_dev *indio_dev,
 
 static const struct iio_info ad4695_info = {
 	.read_raw = &ad4695_read_raw,
+	.write_raw = &ad4695_write_raw,
+	.read_avail = &ad4695_read_avail,
 	.debugfs_reg_access = &ad4695_debugfs_reg_access,
 };