[PATCH 2/4] iio: add processed write API

Romain Gantois posted 4 patches 2 weeks, 2 days ago
There is a newer version of this series
[PATCH 2/4] iio: add processed write API
Posted by Romain Gantois 2 weeks, 2 days ago
Add a function to allow IIO consumers to write a processed value to a
channel.

Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
 drivers/iio/inkern.c         | 99 ++++++++++++++++++++++++++++++++++++++++++++
 include/linux/iio/consumer.h | 17 ++++++++
 2 files changed, 116 insertions(+)

diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
index c174ebb7d5e6d183674b7ffb15c4ce0f65fa3aed..6486fdb7c66a4c84312541f0f42cc24469972a9c 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -598,6 +598,85 @@ int iio_read_channel_average_raw(struct iio_channel *chan, int *val)
 }
 EXPORT_SYMBOL_GPL(iio_read_channel_average_raw);
 
+static int iio_convert_processed_to_raw_unlocked(struct iio_channel *chan,
+						 int processed, int *raw,
+						 unsigned int scale)
+{
+	int scale_type, scale_val, scale_val2;
+	int offset_type, offset_val, offset_val2;
+	s64 tmp_num, tmp_den;
+
+	scale_type = iio_channel_read(chan, &scale_val, &scale_val2,
+				      IIO_CHAN_INFO_SCALE);
+	if (scale_type >= 0) {
+		switch (scale_type) {
+		case IIO_VAL_INT:
+			tmp_num = processed;
+			tmp_den = scale_val;
+			break;
+		case IIO_VAL_INT_PLUS_MICRO:
+			tmp_num = (s64)processed * 1000000LL;
+
+			if (scale_val2 < 0) {
+				tmp_den = (s64)scale_val * 1000000LL - (s64)scale_val2;
+				tmp_den *= -1;
+			} else {
+				tmp_den = (s64)scale_val * 1000000LL + (s64)scale_val2;
+			}
+
+			break;
+		case IIO_VAL_INT_PLUS_NANO:
+			tmp_num = (s64)processed * 1000000000LL;
+
+			if (scale_val2 < 0) {
+				tmp_den = (s64)scale_val * 1000000000LL - (s64)scale_val2;
+				tmp_den *= -1;
+			} else {
+				tmp_den = (s64)scale_val * 1000000000LL + (s64)scale_val2;
+			}
+
+			break;
+		case IIO_VAL_FRACTIONAL:
+			tmp_num = (s64)processed * (s64)scale_val2;
+			tmp_den = scale_val;
+			break;
+		case IIO_VAL_FRACTIONAL_LOG2:
+			tmp_num = (s64)processed << scale_val2;
+			tmp_den = scale_val;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		tmp_den *= scale;
+
+		*raw = div64_s64(tmp_num, tmp_den);
+	}
+
+	offset_type = iio_channel_read(chan, &offset_val, &offset_val2,
+				       IIO_CHAN_INFO_OFFSET);
+	if (offset_type >= 0) {
+		switch (offset_type) {
+		case IIO_VAL_INT:
+		case IIO_VAL_INT_PLUS_MICRO:
+		case IIO_VAL_INT_PLUS_NANO:
+			break;
+		case IIO_VAL_FRACTIONAL:
+			offset_val /= offset_val2;
+			break;
+		case IIO_VAL_FRACTIONAL_LOG2:
+			offset_val >>= offset_val2;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		*raw -= offset_val;
+	}
+
+	return 0;
+}
+
 static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan,
 						 int raw, int *processed,
 						 unsigned int scale)
@@ -1028,3 +1107,23 @@ ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf)
 	return do_iio_read_channel_label(chan->indio_dev, chan->channel, buf);
 }
 EXPORT_SYMBOL_GPL(iio_read_channel_label);
+
+int iio_write_channel_processed_scale(struct iio_channel *chan, int val,
+				      unsigned int scale)
+{
+	struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
+	int ret, processed;
+
+	guard(mutex)(&iio_dev_opaque->info_exist_lock);
+
+	if (!chan->indio_dev->info)
+		return -ENODEV;
+
+	ret = iio_convert_processed_to_raw_unlocked(chan, val, &processed, scale);
+	if (ret)
+		return ret;
+
+	return iio_channel_write(chan, processed, 0, IIO_CHAN_INFO_RAW);
+}
+EXPORT_SYMBOL_GPL(iio_write_channel_processed_scale);
+
diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
index 6a44796164792b2dd930f8168b14de327a80a6f7..79c4804a73b0652d4c16ee5ad07c4543bccd6c92 100644
--- a/include/linux/iio/consumer.h
+++ b/include/linux/iio/consumer.h
@@ -451,4 +451,21 @@ ssize_t iio_write_channel_ext_info(struct iio_channel *chan, const char *attr,
  */
 ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf);
 
+/**
+ * iio_write_channel_processed_scale() - scale and write processed value to a given channel
+ * @chan:		The channel being queried.
+ * @val:		Value to write.
+ * @scale:		Scale factor to apply during the conversion
+ *
+ * Returns an error code or 0.
+ *
+ * This function writes a processed value to a channel. A processed value means
+ * that this value will have the correct unit and not some device internal
+ * representation. If the device does not support writing a processed value, the
+ * function will query the channel's scale and offset and write an appropriately
+ * transformed raw value.
+ */
+int iio_write_channel_processed_scale(struct iio_channel *chan, int val,
+				      unsigned int scale);
+
 #endif

-- 
2.51.0
Re: [PATCH 2/4] iio: add processed write API
Posted by David Lechner 2 weeks, 1 day ago
On 9/16/25 5:24 AM, Romain Gantois wrote:
> Add a function to allow IIO consumers to write a processed value to a
> channel.
> 
> Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
> ---
>  drivers/iio/inkern.c         | 99 ++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/iio/consumer.h | 17 ++++++++
>  2 files changed, 116 insertions(+)
> 
> diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
> index c174ebb7d5e6d183674b7ffb15c4ce0f65fa3aed..6486fdb7c66a4c84312541f0f42cc24469972a9c 100644
> --- a/drivers/iio/inkern.c
> +++ b/drivers/iio/inkern.c
> @@ -598,6 +598,85 @@ int iio_read_channel_average_raw(struct iio_channel *chan, int *val)
>  }
>  EXPORT_SYMBOL_GPL(iio_read_channel_average_raw);
>  
> +static int iio_convert_processed_to_raw_unlocked(struct iio_channel *chan,
> +						 int processed, int *raw,
> +						 unsigned int scale)
> +{
> +	int scale_type, scale_val, scale_val2;
> +	int offset_type, offset_val, offset_val2;
> +	s64 tmp_num, tmp_den;
> +
> +	scale_type = iio_channel_read(chan, &scale_val, &scale_val2,
> +				      IIO_CHAN_INFO_SCALE);
> +	if (scale_type >= 0) {
> +		switch (scale_type) {
> +		case IIO_VAL_INT:
> +			tmp_num = processed;
> +			tmp_den = scale_val;
> +			break;
> +		case IIO_VAL_INT_PLUS_MICRO:
> +			tmp_num = (s64)processed * 1000000LL;
> +
> +			if (scale_val2 < 0) {
> +				tmp_den = (s64)scale_val * 1000000LL - (s64)scale_val2;
> +				tmp_den *= -1;
> +			} else {
> +				tmp_den = (s64)scale_val * 1000000LL + (s64)scale_val2;
> +			}
> +
> +			break;
> +		case IIO_VAL_INT_PLUS_NANO:
> +			tmp_num = (s64)processed * 1000000000LL;
> +
> +			if (scale_val2 < 0) {
> +				tmp_den = (s64)scale_val * 1000000000LL - (s64)scale_val2;
> +				tmp_den *= -1;
> +			} else {
> +				tmp_den = (s64)scale_val * 1000000000LL + (s64)scale_val2;
> +			}
> +
> +			break;
> +		case IIO_VAL_FRACTIONAL:
> +			tmp_num = (s64)processed * (s64)scale_val2;
> +			tmp_den = scale_val;
> +			break;
> +		case IIO_VAL_FRACTIONAL_LOG2:
> +			tmp_num = (s64)processed << scale_val2;
> +			tmp_den = scale_val;
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +
> +		tmp_den *= scale;
> +
> +		*raw = div64_s64(tmp_num, tmp_den);
> +	}

It can be quite tricky to get all of these combinations right. I would
prefer if added some unit tests like we did in [1].

[1]: https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git/commit/?h=testing&id=c732e60ee10ed0611a59513cbf9c8d35fbe7cf65

> +
> +	offset_type = iio_channel_read(chan, &offset_val, &offset_val2,
> +				       IIO_CHAN_INFO_OFFSET);
> +	if (offset_type >= 0) {
> +		switch (offset_type) {
> +		case IIO_VAL_INT:
> +		case IIO_VAL_INT_PLUS_MICRO:
> +		case IIO_VAL_INT_PLUS_NANO:
> +			break;
> +		case IIO_VAL_FRACTIONAL:
> +			offset_val /= offset_val2;
> +			break;
> +		case IIO_VAL_FRACTIONAL_LOG2:
> +			offset_val >>= offset_val2;
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +
> +		*raw -= offset_val;
> +	}
> +
> +	return 0;
> +}
Re: [PATCH 2/4] iio: add processed write API
Posted by Romain Gantois 2 weeks ago
Hello David,

On Tuesday, 16 September 2025 21:23:04 CEST David Lechner wrote:
> On 9/16/25 5:24 AM, Romain Gantois wrote:
> > Add a function to allow IIO consumers to write a processed value to a
...
> > +		case IIO_VAL_FRACTIONAL:
> > +			tmp_num = (s64)processed * (s64)scale_val2;
> > +			tmp_den = scale_val;
> > +			break;
> > +		case IIO_VAL_FRACTIONAL_LOG2:
> > +			tmp_num = (s64)processed << scale_val2;
> > +			tmp_den = scale_val;
> > +			break;
> > +		default:
> > +			return -EINVAL;
> > +		}
> > +
> > +		tmp_den *= scale;
> > +
> > +		*raw = div64_s64(tmp_num, tmp_den);
> > +	}
> 
> It can be quite tricky to get all of these combinations right. I would
> prefer if added some unit tests like we did in [1].
> 
> [1]:
> https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git/commit/?h=tes
> ting&id=c732e60ee10ed0611a59513cbf9c8d35fbe7cf65

Agreed, that would be nice, I'll look into it.

Thanks,

-- 
Romain Gantois, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
Re: [PATCH 2/4] iio: add processed write API
Posted by Andy Shevchenko 2 weeks, 2 days ago
On Tue, Sep 16, 2025 at 12:24:07PM +0200, Romain Gantois wrote:
> Add a function to allow IIO consumers to write a processed value to a
> channel.

...

> +static int iio_convert_processed_to_raw_unlocked(struct iio_channel *chan,
> +						 int processed, int *raw,
> +						 unsigned int scale)
> +{
> +	int scale_type, scale_val, scale_val2;
> +	int offset_type, offset_val, offset_val2;
> +	s64 tmp_num, tmp_den;
> +
> +	scale_type = iio_channel_read(chan, &scale_val, &scale_val2,
> +				      IIO_CHAN_INFO_SCALE);
> +	if (scale_type >= 0) {
> +		switch (scale_type) {
> +		case IIO_VAL_INT:
> +			tmp_num = processed;
> +			tmp_den = scale_val;
> +			break;
> +		case IIO_VAL_INT_PLUS_MICRO:
> +			tmp_num = (s64)processed * 1000000LL;

If you go with this, in IIO we heavily use units.h and here something like
(s64)MICRO would be appropriate. Similar to the rest of the code where one
or another constant may be used.

> +			if (scale_val2 < 0) {
> +				tmp_den = (s64)scale_val * 1000000LL - (s64)scale_val2;
> +				tmp_den *= -1;
> +			} else {
> +				tmp_den = (s64)scale_val * 1000000LL + (s64)scale_val2;
> +			}
> +
> +			break;
> +		case IIO_VAL_INT_PLUS_NANO:
> +			tmp_num = (s64)processed * 1000000000LL;
> +
> +			if (scale_val2 < 0) {
> +				tmp_den = (s64)scale_val * 1000000000LL - (s64)scale_val2;
> +				tmp_den *= -1;
> +			} else {
> +				tmp_den = (s64)scale_val * 1000000000LL + (s64)scale_val2;
> +			}
> +
> +			break;
> +		case IIO_VAL_FRACTIONAL:
> +			tmp_num = (s64)processed * (s64)scale_val2;
> +			tmp_den = scale_val;
> +			break;
> +		case IIO_VAL_FRACTIONAL_LOG2:
> +			tmp_num = (s64)processed << scale_val2;
> +			tmp_den = scale_val;
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +
> +		tmp_den *= scale;
> +
> +		*raw = div64_s64(tmp_num, tmp_den);
> +	}
> +
> +	offset_type = iio_channel_read(chan, &offset_val, &offset_val2,
> +				       IIO_CHAN_INFO_OFFSET);
> +	if (offset_type >= 0) {
> +		switch (offset_type) {
> +		case IIO_VAL_INT:
> +		case IIO_VAL_INT_PLUS_MICRO:
> +		case IIO_VAL_INT_PLUS_NANO:
> +			break;
> +		case IIO_VAL_FRACTIONAL:
> +			offset_val /= offset_val2;
> +			break;
> +		case IIO_VAL_FRACTIONAL_LOG2:
> +			offset_val >>= offset_val2;
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +
> +		*raw -= offset_val;
> +	}
> +
> +	return 0;
> +}

...

> +/**
> + * iio_write_channel_processed_scale() - scale and write processed value to a given channel
> + * @chan:		The channel being queried.
> + * @val:		Value to write.
> + * @scale:		Scale factor to apply during the conversion
> + *
> + * Returns an error code or 0.
> + *
> + * This function writes a processed value to a channel. A processed value means
> + * that this value will have the correct unit and not some device internal
> + * representation. If the device does not support writing a processed value, the
> + * function will query the channel's scale and offset and write an appropriately
> + * transformed raw value.
> + */

This needs a run via kernel-doc validator (warning: Missing Return section).
Also note, in accordance with kernel-doc documentation the Return section must
be last in the big description.

-- 
With Best Regards,
Andy Shevchenko
Re: [PATCH 2/4] iio: add processed write API
Posted by Mark Brown 2 weeks, 2 days ago
On Tue, Sep 16, 2025 at 12:24:07PM +0200, Romain Gantois wrote:
> Add a function to allow IIO consumers to write a processed value to a
> channel.

This seems unrelated to the rest of the series?
Re: [PATCH 2/4] iio: add processed write API
Posted by Romain Gantois 2 weeks, 2 days ago
Hello Mark,

On Tuesday, 16 September 2025 13:19:10 CEST Mark Brown wrote:
> On Tue, Sep 16, 2025 at 12:24:07PM +0200, Romain Gantois wrote:
> > Add a function to allow IIO consumers to write a processed value to a
> > channel.
> 
> This seems unrelated to the rest of the series?

This adds the iio_write_channel_processed_scale() function which is used in 
patch 4/4 of this series in the set_current_limit() callback.

Thanks,

-- 
Romain Gantois, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com