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 | 120 +++++++++++++++++++++++++++++++++++++++++++
include/linux/iio/consumer.h | 36 +++++++++++++
2 files changed, 156 insertions(+)
diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
index 2a1ecef2b82007f5ee8e8d0f8b35846bc4d2f94a..a6ec724b7c1fb197e6261c1162f8315deda20fd7 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -631,6 +631,57 @@ int iio_multiply_value(int *result, s64 multiplier,
}
EXPORT_SYMBOL_GPL(iio_multiply_value);
+int iio_divide_by_value(int *result, s64 numerator,
+ unsigned int type, int val, int val2)
+{
+ s64 tmp_num, tmp_den;
+
+ switch (type) {
+ case IIO_VAL_INT:
+ tmp_num = numerator;
+ tmp_den = val;
+ break;
+ case IIO_VAL_INT_PLUS_MICRO:
+ case IIO_VAL_INT_PLUS_NANO:
+ switch (type) {
+ case IIO_VAL_INT_PLUS_MICRO:
+ tmp_num = MICRO;
+ tmp_den = MICRO;
+ break;
+
+ case IIO_VAL_INT_PLUS_NANO:
+ tmp_num = NANO;
+ tmp_den = NANO;
+ break;
+ }
+
+ tmp_num *= (s64)numerator;
+ tmp_den = (s64)abs(val) * tmp_den + (s64)abs(val2);
+
+ if (val < 0 || val2 < 0)
+ tmp_num *= -1;
+
+ break;
+ case IIO_VAL_FRACTIONAL:
+ tmp_num = (s64)numerator * (s64)val2;
+ tmp_den = val;
+ break;
+ case IIO_VAL_FRACTIONAL_LOG2:
+ tmp_num = (s64)numerator << val2;
+ tmp_den = val;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!tmp_den)
+ return -ERANGE;
+
+ *result = div64_s64(tmp_num, tmp_den);
+
+ return IIO_VAL_INT;
+}
+
static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan,
int raw, int *processed,
unsigned int scale)
@@ -699,6 +750,53 @@ int iio_convert_raw_to_processed(struct iio_channel *chan, int raw,
}
EXPORT_SYMBOL_GPL(iio_convert_raw_to_processed);
+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;
+ int ret;
+
+ scale_type = iio_channel_read(chan, &scale_val, &scale_val2,
+ IIO_CHAN_INFO_SCALE);
+ if (scale_type >= 0) {
+ ret = iio_divide_by_value(raw, processed, scale_type, scale_val, scale_val2);
+ if (ret < 0)
+ return ret;
+ } else {
+ *raw = processed;
+ }
+
+ if (!scale)
+ return -ERANGE;
+
+ *raw = div_s64(*raw, scale);
+
+ 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;
+}
+
int iio_read_channel_attribute(struct iio_channel *chan, int *val, int *val2,
enum iio_chan_info_enum attribute)
{
@@ -1035,3 +1133,25 @@ 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;
+
+ scoped_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;
+
+ ret = iio_channel_write(chan, processed, 0, IIO_CHAN_INFO_RAW);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iio_write_channel_processed_scale);
+
diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
index c8c6261c81f934480e16854412e269207be60adc..dc84b8b4c61911d1a58427f1a9c798cae3954ac1 100644
--- a/include/linux/iio/consumer.h
+++ b/include/linux/iio/consumer.h
@@ -399,6 +399,24 @@ int iio_read_channel_scale(struct iio_channel *chan, int *val,
int iio_multiply_value(int *result, s64 multiplier,
unsigned int type, int val, int val2);
+/**
+ * iio_divide_by_value() - Divide by an IIO value
+ * @result: Destination pointer for the division result
+ * @numerator: Numerator.
+ * @type: One of the IIO_VAL_* constants. This decides how the @val
+ * and @val2 parameters are interpreted.
+ * @val: Denominator.
+ * @val2: Denominator. @val2 use depends on type.
+ *
+ * Divide an s64 number by an IIO value, storing the result as
+ * IIO_VAL_INT. This is typically used for scaling.
+ *
+ * Returns:
+ * IIO_VAL_INT on success or a negative error-number on failure.
+ */
+int iio_divide_by_value(int *result, s64 numerator,
+ unsigned int type, int val, int val2);
+
/**
* iio_convert_raw_to_processed() - Converts a raw value to a processed value
* @chan: The channel being queried
@@ -469,4 +487,22 @@ 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
+ *
+ * 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.
+ *
+ * Return: an error code or 0.
+ *
+ */
+int iio_write_channel_processed_scale(struct iio_channel *chan, int val,
+ unsigned int scale);
+
#endif
--
2.51.0
On Thu, 25 Sep 2025 14:37:34 +0200 Romain Gantois <romain.gantois@bootlin.com> 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> Just one trivial thing I noticed. Thanks for adding the unit tests btw. Makes me much more confident we don't have the corner case sign errors that plagued the similar functions before unit tests and fixes were applied. J > --- > drivers/iio/inkern.c | 120 +++++++++++++++++++++++++++++++++++++++++++ > include/linux/iio/consumer.h | 36 +++++++++++++ > 2 files changed, 156 insertions(+) > > diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c > index 2a1ecef2b82007f5ee8e8d0f8b35846bc4d2f94a..a6ec724b7c1fb197e6261c1162f8315deda20fd7 100644 > --- a/drivers/iio/inkern.c > +++ b/drivers/iio/inkern.c > @@ -631,6 +631,57 @@ int iio_multiply_value(int *result, s64 multiplier, > } > EXPORT_SYMBOL_GPL(iio_multiply_value); > > +int iio_divide_by_value(int *result, s64 numerator, > + unsigned int type, int val, int val2) > +{ > + s64 tmp_num, tmp_den; > + > + switch (type) { > + case IIO_VAL_INT: > + tmp_num = numerator; > + tmp_den = val; > + break; > + case IIO_VAL_INT_PLUS_MICRO: > + case IIO_VAL_INT_PLUS_NANO: > + switch (type) { > + case IIO_VAL_INT_PLUS_MICRO: > + tmp_num = MICRO; > + tmp_den = MICRO; > + break; > + > + case IIO_VAL_INT_PLUS_NANO: > + tmp_num = NANO; > + tmp_den = NANO; > + break; > + } > + > + tmp_num *= (s64)numerator; Seems to be casting an s64 to an s64. > + tmp_den = (s64)abs(val) * tmp_den + (s64)abs(val2); > + > + if (val < 0 || val2 < 0) > + tmp_num *= -1; > + > + break; > + case IIO_VAL_FRACTIONAL: > + tmp_num = (s64)numerator * (s64)val2; > + tmp_den = val; > + break; > + case IIO_VAL_FRACTIONAL_LOG2: > + tmp_num = (s64)numerator << val2; > + tmp_den = val; > + break; > + default: > + return -EINVAL; > + } > + > + if (!tmp_den) > + return -ERANGE; > + > + *result = div64_s64(tmp_num, tmp_den); > + > + return IIO_VAL_INT; > +}
On 9/25/25 7:37 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 | 120 +++++++++++++++++++++++++++++++++++++++++++ > include/linux/iio/consumer.h | 36 +++++++++++++ > 2 files changed, 156 insertions(+) > > diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c > index 2a1ecef2b82007f5ee8e8d0f8b35846bc4d2f94a..a6ec724b7c1fb197e6261c1162f8315deda20fd7 100644 > --- a/drivers/iio/inkern.c > +++ b/drivers/iio/inkern.c > @@ -631,6 +631,57 @@ int iio_multiply_value(int *result, s64 multiplier, > } > EXPORT_SYMBOL_GPL(iio_multiply_value); > > +int iio_divide_by_value(int *result, s64 numerator, > + unsigned int type, int val, int val2) > +{ > + s64 tmp_num, tmp_den; > + > + switch (type) { > + case IIO_VAL_INT: > + tmp_num = numerator; > + tmp_den = val; > + break; > + case IIO_VAL_INT_PLUS_MICRO: > + case IIO_VAL_INT_PLUS_NANO: > + switch (type) { > + case IIO_VAL_INT_PLUS_MICRO: > + tmp_num = MICRO; > + tmp_den = MICRO; > + break; > + > + case IIO_VAL_INT_PLUS_NANO: > + tmp_num = NANO; > + tmp_den = NANO; > + break; > + } > + > + tmp_num *= (s64)numerator; > + tmp_den = (s64)abs(val) * tmp_den + (s64)abs(val2); > + > + if (val < 0 || val2 < 0) > + tmp_num *= -1; > + > + break; > + case IIO_VAL_FRACTIONAL: > + tmp_num = (s64)numerator * (s64)val2; > + tmp_den = val; > + break; > + case IIO_VAL_FRACTIONAL_LOG2: > + tmp_num = (s64)numerator << val2; > + tmp_den = val; > + break; > + default: > + return -EINVAL; > + } > + > + if (!tmp_den) > + return -ERANGE; > + > + *result = div64_s64(tmp_num, tmp_den); > + > + return IIO_VAL_INT; > +} > + > static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan, > int raw, int *processed, > unsigned int scale) > @@ -699,6 +750,53 @@ int iio_convert_raw_to_processed(struct iio_channel *chan, int raw, > } > EXPORT_SYMBOL_GPL(iio_convert_raw_to_processed); > > +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; > + int ret; > + > + scale_type = iio_channel_read(chan, &scale_val, &scale_val2, > + IIO_CHAN_INFO_SCALE); > + if (scale_type >= 0) { > + ret = iio_divide_by_value(raw, processed, scale_type, scale_val, scale_val2); > + if (ret < 0) > + return ret; > + } else { > + *raw = processed; > + } > + > + if (!scale) > + return -ERANGE; > + > + *raw = div_s64(*raw, scale); > + > + 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; > + } There are some rounding biases in this function, but I'm not sure if it is worth trying to make a perfectly fair function. > + > + return 0; > +} > + > int iio_read_channel_attribute(struct iio_channel *chan, int *val, int *val2, > enum iio_chan_info_enum attribute) > { > @@ -1035,3 +1133,25 @@ 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; > + > + scoped_guard(mutex, &iio_dev_opaque->info_exist_lock) { Can just use regular guard(mutex)() here and save some indent. > + if (!chan->indio_dev->info) > + return -ENODEV; > + > + ret = iio_convert_processed_to_raw_unlocked(chan, val, &processed, scale); > + if (ret) > + return ret; > + > + ret = iio_channel_write(chan, processed, 0, IIO_CHAN_INFO_RAW); And this can return directly. > + } > + > + return ret; > +} > +EXPORT_SYMBOL_GPL(iio_write_channel_processed_scale); > + > diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h > index c8c6261c81f934480e16854412e269207be60adc..dc84b8b4c61911d1a58427f1a9c798cae3954ac1 100644 > --- a/include/linux/iio/consumer.h > +++ b/include/linux/iio/consumer.h > @@ -399,6 +399,24 @@ int iio_read_channel_scale(struct iio_channel *chan, int *val, > int iio_multiply_value(int *result, s64 multiplier, > unsigned int type, int val, int val2); > > +/** > + * iio_divide_by_value() - Divide by an IIO value > + * @result: Destination pointer for the division result > + * @numerator: Numerator. > + * @type: One of the IIO_VAL_* constants. This decides how the @val > + * and @val2 parameters are interpreted. > + * @val: Denominator. > + * @val2: Denominator. @val2 use depends on type. > + * > + * Divide an s64 number by an IIO value, storing the result as > + * IIO_VAL_INT. This is typically used for scaling. > + * > + * Returns: > + * IIO_VAL_INT on success or a negative error-number on failure. > + */ > +int iio_divide_by_value(int *result, s64 numerator, > + unsigned int type, int val, int val2); > + > /** > * iio_convert_raw_to_processed() - Converts a raw value to a processed value > * @chan: The channel being queried > @@ -469,4 +487,22 @@ 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 This could be more explicit about what sort of scaling this is. The function divides by this factor rather than multiplies by it. > + * > + * 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. > + * Could be helpful even if obvious to most people... Context: May sleep. > + * Return: an error code or 0. > + * > + */ > +int iio_write_channel_processed_scale(struct iio_channel *chan, int val, > + unsigned int scale); > + > #endif >
Hello David, On Thursday, 25 September 2025 23:10:14 CEST David Lechner wrote: > On 9/25/25 7:37 AM, Romain Gantois wrote: > > Add a function to allow IIO consumers to write a processed value to a ... > > +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; > > + int ret; > > + > > + scale_type = iio_channel_read(chan, &scale_val, &scale_val2, > > + IIO_CHAN_INFO_SCALE); > > + if (scale_type >= 0) { > > + ret = iio_divide_by_value(raw, processed, scale_type, scale_val, > > scale_val2); + if (ret < 0) > > + return ret; > > + } else { > > + *raw = processed; > > + } > > + > > + if (!scale) > > + return -ERANGE; > > + > > + *raw = div_s64(*raw, scale); > > + > > + 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; > > + } > > There are some rounding biases in this function, but I'm not sure if > it is worth trying to make a perfectly fair function. I'm unfamiliar with the notion of rounding bias, does it mean that nested calls of this function would tend to amplify rounding errors? In this case, would rounding to the nearest integer instead of whatever is being done by the integer division here be a good solution? Maybe I'm misunderstanding what you mean, in that case please let me know. Thanks, -- Romain Gantois, Bootlin Embedded Linux and Kernel engineering https://bootlin.com
Apologies if you are receiving the same message twice. Re-sending as text email so that the mailing lists will pick up the reply. On Wed, Oct 1, 2025 at 9:19 AM Romain Gantois <romain.gantois@bootlin.com> wrote: > > Hello David, > > On Thursday, 25 September 2025 23:10:14 CEST David Lechner wrote: > > On 9/25/25 7:37 AM, Romain Gantois wrote: > > > Add a function to allow IIO consumers to write a processed value to a > ... > > > +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; > > > + int ret; > > > + > > > + scale_type = iio_channel_read(chan, &scale_val, &scale_val2, > > > + IIO_CHAN_INFO_SCALE); > > > + if (scale_type >= 0) { > > > + ret = iio_divide_by_value(raw, processed, scale_type, > scale_val, > > > scale_val2); + if (ret < 0) > > > + return ret; > > > + } else { > > > + *raw = processed; > > > + } > > > + > > > + if (!scale) > > > + return -ERANGE; > > > + > > > + *raw = div_s64(*raw, scale); > > > + > > > + 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; > > > + } > > > > There are some rounding biases in this function, but I'm not sure if > > it is worth trying to make a perfectly fair function. > > I'm unfamiliar with the notion of rounding bias, does it mean that nested > calls of this function would tend to amplify rounding errors? In this case, > would rounding to the nearest integer instead of whatever is being done by the > integer division here be a good solution? In this case, the issue is when you are taking multiple samples. When you look at the average of all of the samples, you will be able to see the bias. For example, in one of the drivers I was looking at there is an offset of xxxx.6. Since the IIO_VAL_INT_PLUS_MICRO case is just dropping any fractional part, the raw value will be on average 0.6 lsb lower that the requested value. This could be a problem in an application where high precision is required. But probably not noticeable in cases where 1 lsb is less than the noise level. The floor division for IIO_VAL_FRACTIONAL creates a similar bias. DIV_ROUND_CLOSEST can help there, but even that has a small bias because values of exactly 0.5 always get rounded in the same direction. That kind of bias is much smaller though, so easier to ignore. > > Maybe I'm misunderstanding what you mean, in that case please let me know. > > Thanks, > > -- > Romain Gantois, Bootlin > Embedded Linux and Kernel engineering > https://bootlin.com
© 2016 - 2025 Red Hat, Inc.