* [PATCH v3 0/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver
@ 2025-07-27 21:06 Hans de Goede
2025-07-27 21:06 ` [PATCH v3 1/2] iio: Improve iio_read_channel_processed_scale() precision Hans de Goede
2025-07-27 21:06 ` [PATCH v3 2/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver Hans de Goede
0 siblings, 2 replies; 13+ messages in thread
From: Hans de Goede @ 2025-07-27 21:06 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner
Cc: Hans de Goede, Nuno Sá, Andy Shevchenko, linux-iio
Hi,
Here is v3 of my patch to add an IIO driver for the Intel Dollar Cove TI
PMIC ADC.
Changes in v3:
- "iio: Improve iio_read_channel_processed_scale() precision"
- Use div_s64() instead of div_u64() to fix -1.0 - 0.0 range
- Directly return IIO_VAL_INT from valid cases and drop the final
return ret after the switch-case
- "iio: adc: Add Intel Dollar Cove TI PMIC ADC driver"
- Use new more compact DC_TI_ADC_DATA_REG_CH(x) macro
- Use regmap_set_bits() regmap_clear_bits() where applicable
- Use regmap_bulk_read() + be16_to_cpu() to read ADC value
- Use sign_extend32() for vbat_zse and vbat_ge reading in probe()
Changes in v2:
- Add new "iio: Improve iio_read_channel_processed_scale() precision"
patch to the series
- Add IIO_CHAN_INFO_SCALE and provide ADC scale info for Vbat
- Add IIO_CHAN_INFO_PROCESSED which applies calibration and
scaling for the VBat channel
- Address some other small review remarks
Regards,
Hans
Hans de Goede (2):
iio: Improve iio_read_channel_processed_scale() precision
iio: adc: Add Intel Dollar Cove TI PMIC ADC driver
drivers/iio/adc/Kconfig | 11 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/intel_dc_ti_adc.c | 327 ++++++++++++++++++++++++++++++
drivers/iio/inkern.c | 24 ++-
4 files changed, 359 insertions(+), 4 deletions(-)
create mode 100644 drivers/iio/adc/intel_dc_ti_adc.c
--
2.49.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v3 1/2] iio: Improve iio_read_channel_processed_scale() precision
2025-07-27 21:06 [PATCH v3 0/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver Hans de Goede
@ 2025-07-27 21:06 ` Hans de Goede
2025-07-29 17:26 ` David Lechner
2025-07-27 21:06 ` [PATCH v3 2/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver Hans de Goede
1 sibling, 1 reply; 13+ messages in thread
From: Hans de Goede @ 2025-07-27 21:06 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner
Cc: Hans de Goede, Nuno Sá, Andy Shevchenko, linux-iio
Before this change iio_read_channel_processed_scale() always assumes that
channels which advertise IIO_CHAN_INFO_PROCESSED capability return
IIO_VAL_INT on success.
Ignoring any fractional values from drivers which return
IIO_VAL_INT_PLUS_MICRO / IIO_VAL_INT_PLUS_NANO. These fractional values
might become non fractional after scaling so these should be taken into
account.
While at it also error out for IIO_VAL_* values which
iio_read_channel_processed_scale() does not know how to handle.
Signed-off-by: Hans de Goede <hansg@kernel.org>
---
Changes in v3:
- Use div_s64() instead of div_u64() to fix -1.0 - 0.0 range
- Directly return IIO_VAL_INT from valid cases and drop the final
return ret after the switch-case
Changes in v2:
- New patch in v2 of this patch-series
---
drivers/iio/inkern.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
index c174ebb7d5e6..46900be16ff8 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -714,20 +714,36 @@ int iio_read_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;
+ int ret, val2;
guard(mutex)(&iio_dev_opaque->info_exist_lock);
if (!chan->indio_dev->info)
return -ENODEV;
if (iio_channel_has_info(chan->channel, IIO_CHAN_INFO_PROCESSED)) {
- ret = iio_channel_read(chan, val, NULL,
+ ret = iio_channel_read(chan, val, &val2,
IIO_CHAN_INFO_PROCESSED);
if (ret < 0)
return ret;
- *val *= scale;
- return ret;
+ switch (ret) {
+ case IIO_VAL_INT:
+ *val *= scale;
+ return IIO_VAL_INT;
+ case IIO_VAL_INT_PLUS_MICRO:
+ *val *= scale;
+ *val += div_s64((s64)val2 * scale, 1000000LL);
+ return IIO_VAL_INT;
+ case IIO_VAL_INT_PLUS_NANO:
+ *val *= scale;
+ *val += div_s64((s64)val2 * scale, 1000000000LL);
+ return IIO_VAL_INT;
+ default:
+ dev_err_once(&chan->indio_dev->dev,
+ "unsupported processed IIO-val-type: %d\n",
+ ret);
+ return -EINVAL;
+ }
} else {
ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW);
if (ret < 0)
--
2.49.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 2/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver
2025-07-27 21:06 [PATCH v3 0/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver Hans de Goede
2025-07-27 21:06 ` [PATCH v3 1/2] iio: Improve iio_read_channel_processed_scale() precision Hans de Goede
@ 2025-07-27 21:06 ` Hans de Goede
2025-07-28 18:41 ` Jonathan Cameron
2025-07-29 17:50 ` David Lechner
1 sibling, 2 replies; 13+ messages in thread
From: Hans de Goede @ 2025-07-27 21:06 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner
Cc: Hans de Goede, Nuno Sá, Andy Shevchenko, linux-iio
Intel has 2 completely different "Dollar Cove" PMICs for its Bay Trail /
Cherry Trail SoCs. One is made by X-Powers and is called the AXP288.
The AXP288's GPADC is already supported by the X-Powers AXP288 ADC driver.
The other "Dollar Cove" PMIC is made by TI and does not have any clear TI
denomination, its MFD driver calls it the "Intel Dollar Cove TI PMIC".
Add a driver for the Intel Dollar Cove TI PMIC's general purpose ADC,
binding to the "chtdc_ti_adc" MFD cell of the MFD driver.
The "cht" in the cell name comes from Cherry Trail, but it turns out that
just like the AXP288 the Intel Dollar Cove TI PMIC is also used with both
Intel Bay Trail and Intel Cherry Trail SoCs, so this new ADC driver does
not include the cht part in its name.
This is loosely based on kernel/drivers/iio/adc/iio_dc_ti_gpadc.c
from the Acer A1-840 Android kernel source-code archive named:
"App. Guide_Acer_20151221_A_A.zip"
which is distributed by Acer from the Acer A1-840 support page:
https://www.acer.com/us-en/support/product-support/A1-840/downloads
Signed-off-by: Hans de Goede <hansg@kernel.org>
---
Changes in v3:
- Use new more compact DC_TI_ADC_DATA_REG_CH(x) macro
- Use regmap_set_bits() regmap_clear_bits() where applicable
- Use regmap_bulk_read() + be16_to_cpu() to read ADC value
- Use sign_extend32() for vbat_zse and vbat_ge reading in probe()
Changes in v2:
- Add IIO_CHAN_INFO_SCALE and provide ADC scale info for Vbat
- Add IIO_CHAN_INFO_PROCESSED which applies calibration and
scaling for the VBat channel
- Address some other small review remarks
---
drivers/iio/adc/Kconfig | 11 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/intel_dc_ti_adc.c | 327 ++++++++++++++++++++++++++++++
3 files changed, 339 insertions(+)
create mode 100644 drivers/iio/adc/intel_dc_ti_adc.c
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index ea3ba1397392..51a5fc6efbe1 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -723,6 +723,17 @@ config INGENIC_ADC
This driver can also be built as a module. If so, the module will be
called ingenic_adc.
+config INTEL_DC_TI_ADC
+ tristate "Intel Bay / Cherry Trail Dollar Cove TI ADC driver"
+ depends on INTEL_SOC_PMIC_CHTDC_TI
+ help
+ Say yes here to have support for the Dollar Cove TI PMIC ADC device.
+ Depending on platform configuration, this general purpose ADC can be
+ used for sensors such as battery voltage and thermal resistors.
+
+ To compile this driver as a module, choose M here: the module will be
+ called intel_dc_ti_adc.
+
config INTEL_MRFLD_ADC
tristate "Intel Merrifield Basin Cove ADC driver"
depends on INTEL_SOC_PMIC_MRFLD
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 09ae6edb2650..da99ba88b4e1 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_IMX8QXP_ADC) += imx8qxp-adc.o
obj-$(CONFIG_IMX93_ADC) += imx93_adc.o
obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o
obj-$(CONFIG_INGENIC_ADC) += ingenic-adc.o
+obj-$(CONFIG_INTEL_DC_TI_ADC) += intel_dc_ti_adc.o
obj-$(CONFIG_INTEL_MRFLD_ADC) += intel_mrfld_adc.o
obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
diff --git a/drivers/iio/adc/intel_dc_ti_adc.c b/drivers/iio/adc/intel_dc_ti_adc.c
new file mode 100644
index 000000000000..fcf76e651135
--- /dev/null
+++ b/drivers/iio/adc/intel_dc_ti_adc.c
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Dollar Cove TI PMIC GPADC Driver
+ *
+ * Copyright (C) 2014 Intel Corporation (Ramakrishna Pallala <ramakrishna.pallala@intel.com>)
+ * Copyright (C) 2024 - 2025 Hans de Goede <hansg@kernel.org>
+ */
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/wait.h>
+
+#include <linux/iio/driver.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+
+#define DC_TI_ADC_CNTL_REG 0x50
+#define DC_TI_ADC_START BIT(0)
+#define DC_TI_ADC_CH_SEL GENMASK(2, 1)
+#define DC_TI_ADC_EN BIT(5)
+#define DC_TI_ADC_EN_EXT_BPTH_BIAS BIT(6)
+
+#define DC_TI_VBAT_ZSE_GE_REG 0x53
+#define DC_TI_VBAT_GE GENMASK(3, 0)
+#define DC_TI_VBAT_ZSE GENMASK(7, 4)
+
+/* VBAT GE gain correction is in 0.0015 increments, ZSE is in 1.0 increments */
+#define DC_TI_VBAT_GE_STEP 15
+#define DC_TI_VBAT_GE_DIV 10000
+
+#define DC_TI_ADC_DATA_REG_CH(x) (0x54 + 2 * (x))
+
+enum dc_ti_adc_id {
+ DC_TI_ADC_VBAT,
+ DC_TI_ADC_PMICTEMP,
+ DC_TI_ADC_BATTEMP,
+ DC_TI_ADC_SYSTEMP0,
+};
+
+struct dc_ti_adc_info {
+ struct mutex lock; /* Protects against concurrent accesses to the ADC */
+ wait_queue_head_t wait;
+ struct device *dev;
+ struct regmap *regmap;
+ int vbat_zse;
+ int vbat_ge;
+ bool conversion_done;
+};
+
+static const struct iio_chan_spec dc_ti_adc_channels[] = {
+ {
+ .indexed = 1,
+ .type = IIO_VOLTAGE,
+ .channel = DC_TI_ADC_VBAT,
+ .address = DC_TI_ADC_DATA_REG_CH(0),
+ .datasheet_name = "CH0",
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_PROCESSED),
+ }, {
+ .indexed = 1,
+ .type = IIO_TEMP,
+ .channel = DC_TI_ADC_PMICTEMP,
+ .address = DC_TI_ADC_DATA_REG_CH(1),
+ .datasheet_name = "CH1",
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ }, {
+ .indexed = 1,
+ .type = IIO_TEMP,
+ .channel = DC_TI_ADC_BATTEMP,
+ .address = DC_TI_ADC_DATA_REG_CH(2),
+ .datasheet_name = "CH2",
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ }, {
+ .indexed = 1,
+ .type = IIO_TEMP,
+ .channel = DC_TI_ADC_SYSTEMP0,
+ .address = DC_TI_ADC_DATA_REG_CH(3),
+ .datasheet_name = "CH3",
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ }
+};
+
+static struct iio_map dc_ti_adc_default_maps[] = {
+ IIO_MAP("CH0", "chtdc_ti_battery", "VBAT"),
+ IIO_MAP("CH1", "chtdc_ti_battery", "PMICTEMP"),
+ IIO_MAP("CH2", "chtdc_ti_battery", "BATTEMP"),
+ IIO_MAP("CH3", "chtdc_ti_battery", "SYSTEMP0"),
+ { }
+};
+
+static irqreturn_t dc_ti_adc_isr(int irq, void *data)
+{
+ struct dc_ti_adc_info *info = data;
+
+ info->conversion_done = true;
+ wake_up(&info->wait);
+ return IRQ_HANDLED;
+}
+
+static int dc_ti_adc_scale(struct dc_ti_adc_info *info,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2)
+{
+ if (chan->channel != DC_TI_ADC_VBAT)
+ return -EINVAL;
+
+ /* Vbat ADC scale is 4.6875 mV / unit */
+ *val = 4;
+ *val2 = 687500;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int dc_ti_adc_raw_to_processed(struct dc_ti_adc_info *info,
+ struct iio_chan_spec const *chan,
+ int raw, int *val, int *val2)
+{
+ if (chan->channel != DC_TI_ADC_VBAT)
+ return -EINVAL;
+
+ /* Apply calibration */
+ raw -= info->vbat_zse;
+ raw = raw * (DC_TI_VBAT_GE_DIV - info->vbat_ge * DC_TI_VBAT_GE_STEP) /
+ DC_TI_VBAT_GE_DIV;
+ /* Vbat ADC scale is 4.6875 mV / unit */
+ raw *= 46875;
+
+ /* raw is now in 10000 units / mV, convert to milli + milli/1e6 */
+ *val = raw / 10000;
+ *val2 = (raw % 10000) * 100;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int dc_ti_adc_sample(struct dc_ti_adc_info *info,
+ struct iio_chan_spec const *chan, int *val)
+{
+ int ret, ch = chan->channel;
+ __be16 buf;
+
+ info->conversion_done = false;
+
+ /*
+ * As per TI (PMIC Vendor), the ADC enable and ADC start commands should
+ * not be sent together. Hence send the commands separately
+ */
+ ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, DC_TI_ADC_EN);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(info->regmap, DC_TI_ADC_CNTL_REG,
+ DC_TI_ADC_CH_SEL,
+ FIELD_PREP(DC_TI_ADC_CH_SEL, ch));
+ if (ret)
+ return ret;
+
+ /*
+ * As per PMIC Vendor, a minimum of 50 micro seconds delay is required
+ * between ADC Enable and ADC START commands. This is also recommended
+ * by Intel Hardware team after the timing analysis of GPADC signals.
+ * Since the I2C Write transaction to set the channel number also
+ * imparts 25 micro seconds of delay, so we need to wait for another
+ * 25 micro seconds before issuing ADC START command.
+ */
+ fsleep(25);
+
+ ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG,
+ DC_TI_ADC_START);
+ if (ret)
+ return ret;
+
+ /* TI (PMIC Vendor) recommends 5 sec timeout for conversion */
+ ret = wait_event_timeout(info->wait, info->conversion_done, 5 * HZ);
+ if (ret == 0) {
+ ret = -ETIMEDOUT;
+ goto disable_adc;
+ }
+
+ ret = regmap_bulk_read(info->regmap, chan->address, &buf, sizeof(buf));
+ if (ret)
+ goto disable_adc;
+
+ /* The ADC values are 10 bits */
+ *val = be16_to_cpu(buf) & GENMASK(9, 0);
+
+disable_adc:
+ regmap_clear_bits(info->regmap, DC_TI_ADC_CNTL_REG,
+ DC_TI_ADC_START | DC_TI_ADC_EN);
+ return ret;
+}
+
+static int dc_ti_adc_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct dc_ti_adc_info *info = iio_priv(indio_dev);
+ int ret;
+
+ if (mask == IIO_CHAN_INFO_SCALE)
+ return dc_ti_adc_scale(info, chan, val, val2);
+
+ guard(mutex)(&info->lock);
+
+ /*
+ * If channel BPTHERM has been selected, first enable the BPTHERM BIAS
+ * which provides the VREF Voltage reference to convert BPTHERM Input
+ * voltage to temperature.
+ * As per PMIC Vendor specifications, BPTHERM BIAS should be enabled
+ * 35 ms before ADC_EN command.
+ */
+ if (chan->channel == DC_TI_ADC_BATTEMP) {
+ ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG,
+ DC_TI_ADC_EN_EXT_BPTH_BIAS);
+ if (ret)
+ return ret;
+ msleep(35);
+ }
+
+ ret = dc_ti_adc_sample(info, chan, val);
+
+ if (chan->channel == DC_TI_ADC_BATTEMP)
+ regmap_clear_bits(info->regmap, DC_TI_ADC_CNTL_REG,
+ DC_TI_ADC_EN_EXT_BPTH_BIAS);
+
+ if (ret)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_PROCESSED:
+ return dc_ti_adc_raw_to_processed(info, chan, *val, val, val2);
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info dc_ti_adc_iio_info = {
+ .read_raw = dc_ti_adc_read_raw,
+};
+
+static int dc_ti_adc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
+ struct dc_ti_adc_info *info;
+ struct iio_dev *indio_dev;
+ unsigned int val;
+ int irq, ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*info));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ info = iio_priv(indio_dev);
+
+ ret = devm_mutex_init(dev, &info->lock);
+ if (ret)
+ return ret;
+
+ init_waitqueue_head(&info->wait);
+
+ info->dev = dev;
+ info->regmap = pmic->regmap;
+
+ indio_dev->name = "dc_ti_adc";
+ indio_dev->channels = dc_ti_adc_channels;
+ indio_dev->num_channels = ARRAY_SIZE(dc_ti_adc_channels);
+ indio_dev->info = &dc_ti_adc_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = regmap_read(info->regmap, DC_TI_VBAT_ZSE_GE_REG, &val);
+ if (ret)
+ return ret;
+
+ info->vbat_zse = sign_extend32(FIELD_GET(DC_TI_VBAT_ZSE, val), 3);
+ info->vbat_ge = sign_extend32(FIELD_GET(DC_TI_VBAT_GE, val), 3);
+
+ dev_dbg(dev, "vbat-zse %d vbat-ge %d\n", info->vbat_zse, info->vbat_ge);
+
+ ret = devm_iio_map_array_register(dev, indio_dev, dc_ti_adc_default_maps);
+ if (ret)
+ return ret;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, dc_ti_adc_isr,
+ IRQF_ONESHOT, indio_dev->name, info);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct platform_device_id dc_ti_adc_ids[] = {
+ { .name = "chtdc_ti_adc", },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, dc_ti_adc_ids);
+
+static struct platform_driver dc_ti_adc_driver = {
+ .driver = {
+ .name = "dc_ti_adc",
+ },
+ .probe = dc_ti_adc_probe,
+ .id_table = dc_ti_adc_ids,
+};
+module_platform_driver(dc_ti_adc_driver);
+
+MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_DESCRIPTION("Intel Dollar Cove (TI) GPADC Driver");
+MODULE_LICENSE("GPL");
--
2.49.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v3 2/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver
2025-07-27 21:06 ` [PATCH v3 2/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver Hans de Goede
@ 2025-07-28 18:41 ` Jonathan Cameron
2025-07-29 17:50 ` David Lechner
1 sibling, 0 replies; 13+ messages in thread
From: Jonathan Cameron @ 2025-07-28 18:41 UTC (permalink / raw)
To: Hans de Goede; +Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio
On Sun, 27 Jul 2025 23:06:39 +0200
Hans de Goede <hansg@kernel.org> wrote:
> Intel has 2 completely different "Dollar Cove" PMICs for its Bay Trail /
> Cherry Trail SoCs. One is made by X-Powers and is called the AXP288.
> The AXP288's GPADC is already supported by the X-Powers AXP288 ADC driver.
>
> The other "Dollar Cove" PMIC is made by TI and does not have any clear TI
> denomination, its MFD driver calls it the "Intel Dollar Cove TI PMIC".
>
> Add a driver for the Intel Dollar Cove TI PMIC's general purpose ADC,
> binding to the "chtdc_ti_adc" MFD cell of the MFD driver.
>
> The "cht" in the cell name comes from Cherry Trail, but it turns out that
> just like the AXP288 the Intel Dollar Cove TI PMIC is also used with both
> Intel Bay Trail and Intel Cherry Trail SoCs, so this new ADC driver does
> not include the cht part in its name.
>
> This is loosely based on kernel/drivers/iio/adc/iio_dc_ti_gpadc.c
> from the Acer A1-840 Android kernel source-code archive named:
> "App. Guide_Acer_20151221_A_A.zip"
> which is distributed by Acer from the Acer A1-840 support page:
> https://www.acer.com/us-en/support/product-support/A1-840/downloads
>
> Signed-off-by: Hans de Goede <hansg@kernel.org>
Looks good to me. I'll let it sit on list for a few days though to give
others time to take a final look. It's holiday season for many so
if I do apply it and we get follow on feedback we can deal with it as
patches on top.
Thanks,
Jonathan
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 1/2] iio: Improve iio_read_channel_processed_scale() precision
2025-07-27 21:06 ` [PATCH v3 1/2] iio: Improve iio_read_channel_processed_scale() precision Hans de Goede
@ 2025-07-29 17:26 ` David Lechner
2025-08-10 19:25 ` Hans de Goede
0 siblings, 1 reply; 13+ messages in thread
From: David Lechner @ 2025-07-29 17:26 UTC (permalink / raw)
To: Hans de Goede, Jonathan Cameron; +Cc: Nuno Sá, Andy Shevchenko, linux-iio
On 7/27/25 4:06 PM, Hans de Goede wrote:
> Before this change iio_read_channel_processed_scale() always assumes that
> channels which advertise IIO_CHAN_INFO_PROCESSED capability return
> IIO_VAL_INT on success.
>
> Ignoring any fractional values from drivers which return
> IIO_VAL_INT_PLUS_MICRO / IIO_VAL_INT_PLUS_NANO. These fractional values
> might become non fractional after scaling so these should be taken into
> account.
>
> While at it also error out for IIO_VAL_* values which
> iio_read_channel_processed_scale() does not know how to handle.
>
> Signed-off-by: Hans de Goede <hansg@kernel.org>
> ---
> Changes in v3:
> - Use div_s64() instead of div_u64() to fix -1.0 - 0.0 range
> - Directly return IIO_VAL_INT from valid cases and drop the final
> return ret after the switch-case
>
> Changes in v2:
> - New patch in v2 of this patch-series
> ---
> drivers/iio/inkern.c | 24 ++++++++++++++++++++----
> 1 file changed, 20 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
> index c174ebb7d5e6..46900be16ff8 100644
> --- a/drivers/iio/inkern.c
> +++ b/drivers/iio/inkern.c
> @@ -714,20 +714,36 @@ int iio_read_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;
> + int ret, val2;
>
> guard(mutex)(&iio_dev_opaque->info_exist_lock);
> if (!chan->indio_dev->info)
> return -ENODEV;
>
> if (iio_channel_has_info(chan->channel, IIO_CHAN_INFO_PROCESSED)) {
> - ret = iio_channel_read(chan, val, NULL,
> + ret = iio_channel_read(chan, val, &val2,
> IIO_CHAN_INFO_PROCESSED);
> if (ret < 0)
> return ret;
> - *val *= scale;
>
> - return ret;
> + switch (ret) {
> + case IIO_VAL_INT:
> + *val *= scale;
> + return IIO_VAL_INT;
> + case IIO_VAL_INT_PLUS_MICRO:
> + *val *= scale;
> + *val += div_s64((s64)val2 * scale, 1000000LL);
> + return IIO_VAL_INT;
> + case IIO_VAL_INT_PLUS_NANO:
> + *val *= scale;
> + *val += div_s64((s64)val2 * scale, 1000000000LL);
> + return IIO_VAL_INT;
I would feel better if we had some kunit tests on this function since
the negative values can be tricky. I.e. something similar to
iio_test_iio_format_value_fixedpoint() that tests the 4 possible
interesting cases for val and val2.
I think that would find a bug here. For example, if the processed
value is -1.5 with IIO_VAL_INT_PLUS_MICRO, then *val would be
-1 and *val2 would be 500_000 (before applying scale). And suppose
scale is 2. The expected result would be -1.5 * 2 = -3. But the math
here is:
-1 * 2 + 500_000 * 2 / 1_000_000 = -1 != -3
> + default:
> + dev_err_once(&chan->indio_dev->dev,
> + "unsupported processed IIO-val-type: %d\n",
> + ret);
> + return -EINVAL;
> + }
> } else {
> ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW);
> if (ret < 0)
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 2/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver
2025-07-27 21:06 ` [PATCH v3 2/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver Hans de Goede
2025-07-28 18:41 ` Jonathan Cameron
@ 2025-07-29 17:50 ` David Lechner
2025-07-31 11:12 ` Jonathan Cameron
1 sibling, 1 reply; 13+ messages in thread
From: David Lechner @ 2025-07-29 17:50 UTC (permalink / raw)
To: Hans de Goede, Jonathan Cameron; +Cc: Nuno Sá, Andy Shevchenko, linux-iio
On 7/27/25 4:06 PM, Hans de Goede wrote:
...
> +static const struct iio_chan_spec dc_ti_adc_channels[] = {
> + {
> + .indexed = 1,
> + .type = IIO_VOLTAGE,
> + .channel = DC_TI_ADC_VBAT,
> + .address = DC_TI_ADC_DATA_REG_CH(0),
> + .datasheet_name = "CH0",
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> + BIT(IIO_CHAN_INFO_SCALE) |
> + BIT(IIO_CHAN_INFO_PROCESSED),
> + }, {
> + .indexed = 1,
> + .type = IIO_TEMP,
> + .channel = DC_TI_ADC_PMICTEMP,
> + .address = DC_TI_ADC_DATA_REG_CH(1),
> + .datasheet_name = "CH1",
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> + }, {
> + .indexed = 1,
> + .type = IIO_TEMP,
> + .channel = DC_TI_ADC_BATTEMP,
> + .address = DC_TI_ADC_DATA_REG_CH(2),
> + .datasheet_name = "CH2",
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> + }, {
> + .indexed = 1,
> + .type = IIO_TEMP,
> + .channel = DC_TI_ADC_SYSTEMP0,
> + .address = DC_TI_ADC_DATA_REG_CH(3),
> + .datasheet_name = "CH3",
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> + }
> +};
Question for Jonathan: If we don't know the scale on these
temperature channels, should we implement IIO_CHAN_INFO_SCALE
and just always return an error? My understanding is that
by not implementing it, the scale is assumed to be 1, which
likely isn't correct in in this case.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 2/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver
2025-07-29 17:50 ` David Lechner
@ 2025-07-31 11:12 ` Jonathan Cameron
2025-08-02 11:46 ` Jonathan Cameron
0 siblings, 1 reply; 13+ messages in thread
From: Jonathan Cameron @ 2025-07-31 11:12 UTC (permalink / raw)
To: David Lechner
Cc: Hans de Goede, Jonathan Cameron, Nuno Sá, Andy Shevchenko,
linux-iio
On Tue, 29 Jul 2025 12:50:36 -0500
David Lechner <dlechner@baylibre.com> wrote:
> On 7/27/25 4:06 PM, Hans de Goede wrote:
>
> ...
>
> > +static const struct iio_chan_spec dc_ti_adc_channels[] = {
> > + {
> > + .indexed = 1,
> > + .type = IIO_VOLTAGE,
> > + .channel = DC_TI_ADC_VBAT,
> > + .address = DC_TI_ADC_DATA_REG_CH(0),
> > + .datasheet_name = "CH0",
> > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> > + BIT(IIO_CHAN_INFO_SCALE) |
> > + BIT(IIO_CHAN_INFO_PROCESSED),
> > + }, {
> > + .indexed = 1,
> > + .type = IIO_TEMP,
> > + .channel = DC_TI_ADC_PMICTEMP,
> > + .address = DC_TI_ADC_DATA_REG_CH(1),
> > + .datasheet_name = "CH1",
> > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> > + }, {
> > + .indexed = 1,
> > + .type = IIO_TEMP,
> > + .channel = DC_TI_ADC_BATTEMP,
> > + .address = DC_TI_ADC_DATA_REG_CH(2),
> > + .datasheet_name = "CH2",
> > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> > + }, {
> > + .indexed = 1,
> > + .type = IIO_TEMP,
> > + .channel = DC_TI_ADC_SYSTEMP0,
> > + .address = DC_TI_ADC_DATA_REG_CH(3),
> > + .datasheet_name = "CH3",
> > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> > + }
> > +};
>
> Question for Jonathan: If we don't know the scale on these
> temperature channels, should we implement IIO_CHAN_INFO_SCALE
> and just always return an error? My understanding is that
> by not implementing it, the scale is assumed to be 1, which
> likely isn't correct in in this case.
Good question. We have quite a bit of precedence of just
not providing scale in these cases. The docs for the main block of _scale
do say
"If known for a device, scale to be applied to <type>Y[_name]_raw
post addition of <type>[Y][_name]_offset in order to obtain the
measured value in <type> units as specified in
<type>[Y][_name]_raw documentation. "
Rather than trying to fix all those, shall we tweak the docs
to say that scale is assumed to be 1 if offset is provided?
If neither is there, the channel can be _PROCESSED anyway.
Your suggestion of returning an error would have been somewhat neat
long ago, but now I think it will just be too confusing for userspace.
Jonathan
>
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 2/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver
2025-07-31 11:12 ` Jonathan Cameron
@ 2025-08-02 11:46 ` Jonathan Cameron
0 siblings, 0 replies; 13+ messages in thread
From: Jonathan Cameron @ 2025-08-02 11:46 UTC (permalink / raw)
To: Jonathan Cameron
Cc: David Lechner, Hans de Goede, Nuno Sá, Andy Shevchenko,
linux-iio
On Thu, 31 Jul 2025 12:12:55 +0100
Jonathan Cameron <Jonathan.Cameron@huawei.com> wrote:
> On Tue, 29 Jul 2025 12:50:36 -0500
> David Lechner <dlechner@baylibre.com> wrote:
>
> > On 7/27/25 4:06 PM, Hans de Goede wrote:
> >
> > ...
> >
> > > +static const struct iio_chan_spec dc_ti_adc_channels[] = {
> > > + {
> > > + .indexed = 1,
> > > + .type = IIO_VOLTAGE,
> > > + .channel = DC_TI_ADC_VBAT,
> > > + .address = DC_TI_ADC_DATA_REG_CH(0),
> > > + .datasheet_name = "CH0",
> > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> > > + BIT(IIO_CHAN_INFO_SCALE) |
> > > + BIT(IIO_CHAN_INFO_PROCESSED),
> > > + }, {
> > > + .indexed = 1,
> > > + .type = IIO_TEMP,
> > > + .channel = DC_TI_ADC_PMICTEMP,
> > > + .address = DC_TI_ADC_DATA_REG_CH(1),
> > > + .datasheet_name = "CH1",
> > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> > > + }, {
> > > + .indexed = 1,
> > > + .type = IIO_TEMP,
> > > + .channel = DC_TI_ADC_BATTEMP,
> > > + .address = DC_TI_ADC_DATA_REG_CH(2),
> > > + .datasheet_name = "CH2",
> > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> > > + }, {
> > > + .indexed = 1,
> > > + .type = IIO_TEMP,
> > > + .channel = DC_TI_ADC_SYSTEMP0,
> > > + .address = DC_TI_ADC_DATA_REG_CH(3),
> > > + .datasheet_name = "CH3",
> > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> > > + }
> > > +};
> >
> > Question for Jonathan: If we don't know the scale on these
> > temperature channels, should we implement IIO_CHAN_INFO_SCALE
> > and just always return an error? My understanding is that
> > by not implementing it, the scale is assumed to be 1, which
> > likely isn't correct in in this case.
>
> Good question. We have quite a bit of precedence of just
> not providing scale in these cases. The docs for the main block of _scale
> do say
>
> "If known for a device, scale to be applied to <type>Y[_name]_raw
> post addition of <type>[Y][_name]_offset in order to obtain the
> measured value in <type> units as specified in
> <type>[Y][_name]_raw documentation. "
>
> Rather than trying to fix all those, shall we tweak the docs
> to say that scale is assumed to be 1 if offset is provided?
>
> If neither is there, the channel can be _PROCESSED anyway.
>
> Your suggestion of returning an error would have been somewhat neat
> long ago, but now I think it will just be too confusing for userspace.
>
> Jonathan
>
Just a quick note. I looked through the driver again the other
day and other than the bug (and request for tests) that David had
on patch 1 this looks good to me.
I completely missed that bug so thanks David!
Jonathan
> >
> >
>
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 1/2] iio: Improve iio_read_channel_processed_scale() precision
2025-07-29 17:26 ` David Lechner
@ 2025-08-10 19:25 ` Hans de Goede
2025-08-10 21:12 ` Hans de Goede
0 siblings, 1 reply; 13+ messages in thread
From: Hans de Goede @ 2025-08-10 19:25 UTC (permalink / raw)
To: David Lechner, Jonathan Cameron; +Cc: Nuno Sá, Andy Shevchenko, linux-iio
Hi David,
On 29-Jul-25 7:26 PM, David Lechner wrote:
> On 7/27/25 4:06 PM, Hans de Goede wrote:
>> Before this change iio_read_channel_processed_scale() always assumes that
>> channels which advertise IIO_CHAN_INFO_PROCESSED capability return
>> IIO_VAL_INT on success.
>>
>> Ignoring any fractional values from drivers which return
>> IIO_VAL_INT_PLUS_MICRO / IIO_VAL_INT_PLUS_NANO. These fractional values
>> might become non fractional after scaling so these should be taken into
>> account.
>>
>> While at it also error out for IIO_VAL_* values which
>> iio_read_channel_processed_scale() does not know how to handle.
>>
>> Signed-off-by: Hans de Goede <hansg@kernel.org>
>> ---
>> Changes in v3:
>> - Use div_s64() instead of div_u64() to fix -1.0 - 0.0 range
>> - Directly return IIO_VAL_INT from valid cases and drop the final
>> return ret after the switch-case
>>
>> Changes in v2:
>> - New patch in v2 of this patch-series
>> ---
>> drivers/iio/inkern.c | 24 ++++++++++++++++++++----
>> 1 file changed, 20 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
>> index c174ebb7d5e6..46900be16ff8 100644
>> --- a/drivers/iio/inkern.c
>> +++ b/drivers/iio/inkern.c
>> @@ -714,20 +714,36 @@ int iio_read_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;
>> + int ret, val2;
>>
>> guard(mutex)(&iio_dev_opaque->info_exist_lock);
>> if (!chan->indio_dev->info)
>> return -ENODEV;
>>
>> if (iio_channel_has_info(chan->channel, IIO_CHAN_INFO_PROCESSED)) {
>> - ret = iio_channel_read(chan, val, NULL,
>> + ret = iio_channel_read(chan, val, &val2,
>> IIO_CHAN_INFO_PROCESSED);
>> if (ret < 0)
>> return ret;
>> - *val *= scale;
>>
>> - return ret;
>> + switch (ret) {
>> + case IIO_VAL_INT:
>> + *val *= scale;
>> + return IIO_VAL_INT;
>> + case IIO_VAL_INT_PLUS_MICRO:
>> + *val *= scale;
>> + *val += div_s64((s64)val2 * scale, 1000000LL);
>> + return IIO_VAL_INT;
>> + case IIO_VAL_INT_PLUS_NANO:
>> + *val *= scale;
>> + *val += div_s64((s64)val2 * scale, 1000000000LL);
>> + return IIO_VAL_INT;
>
> I would feel better if we had some kunit tests on this function since
> the negative values can be tricky. I.e. something similar to
> iio_test_iio_format_value_fixedpoint() that tests the 4 possible
> interesting cases for val and val2.
>
> I think that would find a bug here. For example, if the processed
> value is -1.5 with IIO_VAL_INT_PLUS_MICRO, then *val would be
> -1 and *val2 would be 500_000 (before applying scale). And suppose
> scale is 2. The expected result would be -1.5 * 2 = -3. But the math
> here is:
>
> -1 * 2 + 500_000 * 2 / 1_000_000 = -1 != -3
Ack, after looking at iio_format_value and the kunit test for this
I believe I know how this is supposed to work now.
Note that it seems that iio_convert_raw_to_processed_unlocked()
also seems to get this wrong when the channel scale attribute
is smaller then -1, e.g. your -1.5. Actually it seems that
the code in iio_convert_raw_to_processed_unlocked() is making
the exact same mistake you are highlighting in my code :)
I'll prepare a patch series to try and deal with this.
Regards,
Hans
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 1/2] iio: Improve iio_read_channel_processed_scale() precision
2025-08-10 19:25 ` Hans de Goede
@ 2025-08-10 21:12 ` Hans de Goede
2025-08-11 12:37 ` Andy Shevchenko
0 siblings, 1 reply; 13+ messages in thread
From: Hans de Goede @ 2025-08-10 21:12 UTC (permalink / raw)
To: David Lechner, Jonathan Cameron; +Cc: Nuno Sá, Andy Shevchenko, linux-iio
[-- Attachment #1: Type: text/plain, Size: 4108 bytes --]
Hi,
On 10-Aug-25 9:25 PM, Hans de Goede wrote:
> Hi David,
>
> On 29-Jul-25 7:26 PM, David Lechner wrote:
>> On 7/27/25 4:06 PM, Hans de Goede wrote:
>>> Before this change iio_read_channel_processed_scale() always assumes that
>>> channels which advertise IIO_CHAN_INFO_PROCESSED capability return
>>> IIO_VAL_INT on success.
>>>
>>> Ignoring any fractional values from drivers which return
>>> IIO_VAL_INT_PLUS_MICRO / IIO_VAL_INT_PLUS_NANO. These fractional values
>>> might become non fractional after scaling so these should be taken into
>>> account.
>>>
>>> While at it also error out for IIO_VAL_* values which
>>> iio_read_channel_processed_scale() does not know how to handle.
>>>
>>> Signed-off-by: Hans de Goede <hansg@kernel.org>
>>> ---
>>> Changes in v3:
>>> - Use div_s64() instead of div_u64() to fix -1.0 - 0.0 range
>>> - Directly return IIO_VAL_INT from valid cases and drop the final
>>> return ret after the switch-case
>>>
>>> Changes in v2:
>>> - New patch in v2 of this patch-series
>>> ---
>>> drivers/iio/inkern.c | 24 ++++++++++++++++++++----
>>> 1 file changed, 20 insertions(+), 4 deletions(-)
>>>
>>> diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
>>> index c174ebb7d5e6..46900be16ff8 100644
>>> --- a/drivers/iio/inkern.c
>>> +++ b/drivers/iio/inkern.c
>>> @@ -714,20 +714,36 @@ int iio_read_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;
>>> + int ret, val2;
>>>
>>> guard(mutex)(&iio_dev_opaque->info_exist_lock);
>>> if (!chan->indio_dev->info)
>>> return -ENODEV;
>>>
>>> if (iio_channel_has_info(chan->channel, IIO_CHAN_INFO_PROCESSED)) {
>>> - ret = iio_channel_read(chan, val, NULL,
>>> + ret = iio_channel_read(chan, val, &val2,
>>> IIO_CHAN_INFO_PROCESSED);
>>> if (ret < 0)
>>> return ret;
>>> - *val *= scale;
>>>
>>> - return ret;
>>> + switch (ret) {
>>> + case IIO_VAL_INT:
>>> + *val *= scale;
>>> + return IIO_VAL_INT;
>>> + case IIO_VAL_INT_PLUS_MICRO:
>>> + *val *= scale;
>>> + *val += div_s64((s64)val2 * scale, 1000000LL);
>>> + return IIO_VAL_INT;
>>> + case IIO_VAL_INT_PLUS_NANO:
>>> + *val *= scale;
>>> + *val += div_s64((s64)val2 * scale, 1000000000LL);
>>> + return IIO_VAL_INT;
>>
>> I would feel better if we had some kunit tests on this function since
>> the negative values can be tricky. I.e. something similar to
>> iio_test_iio_format_value_fixedpoint() that tests the 4 possible
>> interesting cases for val and val2.
>>
>> I think that would find a bug here. For example, if the processed
>> value is -1.5 with IIO_VAL_INT_PLUS_MICRO, then *val would be
>> -1 and *val2 would be 500_000 (before applying scale). And suppose
>> scale is 2. The expected result would be -1.5 * 2 = -3. But the math
>> here is:
>>
>> -1 * 2 + 500_000 * 2 / 1_000_000 = -1 != -3
>
> Ack, after looking at iio_format_value and the kunit test for this
> I believe I know how this is supposed to work now.
>
> Note that it seems that iio_convert_raw_to_processed_unlocked()
> also seems to get this wrong when the channel scale attribute
> is smaller then -1, e.g. your -1.5. Actually it seems that
> the code in iio_convert_raw_to_processed_unlocked() is making
> the exact same mistake you are highlighting in my code :)
>
> I'll prepare a patch series to try and deal with this.
Ok, attached is a teaser of the patch-series I'm working on.
The first 2 patches fix 2 bugs in iio_convert_raw_to_processed()
which I noticed while working on this.
The third patch factors multiplication of a type, val,
val2 integer triplet by some integer number and storing
the result in an integer out into a new iio_mutiply_value()
helper. For re-use and so that a kunit-test can be written.
Patch 4 is a new version of the patch improving
iio_read_channel_processed_scale() precision using this helper.
What is missing for posting a new version is a kunit test
for the new helper. I hope to be able to work on that
tomorrow.
Regards,
Hans
[-- Attachment #2: 0001-iio-consumers-Fix-handling-of-negative-channel-scale.patch --]
[-- Type: text/x-patch, Size: 3693 bytes --]
From 841797bc90e32f366a1ee4eaa5f3a26747546f7c Mon Sep 17 00:00:00 2001
From: Hans de Goede <hansg@kernel.org>
Date: Sun, 10 Aug 2025 21:49:35 +0200
Subject: [PATCH 1/4] iio: consumers: Fix handling of negative channel scale in
iio_convert_raw_to_processed()
There is an issue with the handling of negative channel scales
in iio_convert_raw_to_processed_unlocked() when the channel-scale
is of the IIO_VAL_INT_PLUS_[MICRO|NANO] type:
Things work for channel-scale values > -1.0 and < 0.0 because of
the use of signed values in:
*processed += div_s64(raw64 * (s64)scale_val2 * scale, 1000000LL);
Things will break however for scale values < -1.0. Lets for example say
that raw = 2, (caller-provided)scale = 10 and (channel)scale_val = -1.5.
The result should then be 2 * 10 * -1.5 = -30.
channel-scale = -1.5 means scale_val = -1 and scale_val2 = 500000,
now lets see what gets stored in processed:
1. *processed = raw64 * scale_val * scale;
2. *processed += raw64 * scale_val2 * scale / 1000000LL;
1. Sets processed to 2 * -1 * 10 = -20
2. Adds 2 * 500000 * 10 / 1000000 = 10 to processed
And the end result is processed = -20 + 10 = -10, which is not correct.
Fix this by always using the abs value of both scale_val and scale_val2
and if either is negative multiply the end-result by -1.
Note there seems to be an unwritten rule about negative
IIO_VAL_INT_PLUS_[MICRO|NANO] values that:
i. values > -1.0 and < 0.0 are written as val=0 val2=-xxx
ii. values <= -1.0 are written as val=-xxx val2=xxx
But iio_format_value() will also correctly display a third option:
iii. values <= -1.0 written as val=-xxx val2=-xxx
Since iio_format_value() uses abs(val) when val2 < 0.
This fix also makes iio_convert_raw_to_processed() properly handle
channel-scales using this third option.
Fixes: 48e44ce0f881 ("iio:inkern: Add function to read the processed value")
Cc: Matteo Martelli <matteomartelli3@gmail.com>
Signed-off-by: Hans de Goede <hansg@kernel.org>
---
Changes in v4:
- New patch in v4 of this patch-set
---
drivers/iio/inkern.c | 23 +++++++++--------------
1 file changed, 9 insertions(+), 14 deletions(-)
diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
index c174ebb7d5e6..6c16224619f4 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -604,7 +604,7 @@ static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan,
{
int scale_type, scale_val, scale_val2;
int offset_type, offset_val, offset_val2;
- s64 raw64 = raw;
+ s64 denominator, raw64 = raw;
offset_type = iio_channel_read(chan, &offset_val, &offset_val2,
IIO_CHAN_INFO_OFFSET);
@@ -648,20 +648,15 @@ static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan,
*processed = raw64 * scale_val * scale;
break;
case IIO_VAL_INT_PLUS_MICRO:
- if (scale_val2 < 0)
- *processed = -raw64 * scale_val * scale;
- else
- *processed = raw64 * scale_val * scale;
- *processed += div_s64(raw64 * (s64)scale_val2 * scale,
- 1000000LL);
- break;
case IIO_VAL_INT_PLUS_NANO:
- if (scale_val2 < 0)
- *processed = -raw64 * scale_val * scale;
- else
- *processed = raw64 * scale_val * scale;
- *processed += div_s64(raw64 * (s64)scale_val2 * scale,
- 1000000000LL);
+ switch (scale_type) {
+ case IIO_VAL_INT_PLUS_MICRO: denominator = 1000000LL; break;
+ case IIO_VAL_INT_PLUS_NANO: denominator = 1000000000LL; break;
+ }
+ *processed = raw64 * scale * abs(scale_val);
+ *processed += div_s64(raw64 * scale * abs(scale_val2), denominator);
+ if (scale_val < 0 || scale_val2 < 0)
+ *processed *= -1;
break;
case IIO_VAL_FRACTIONAL:
*processed = div_s64(raw64 * (s64)scale_val * scale,
--
2.49.0
[-- Attachment #3: 0002-iio-consumers-Fix-offset-handling-in-iio_convert_raw.patch --]
[-- Type: text/x-patch, Size: 1227 bytes --]
From 62e1c0dd18648ecf262bb2b5b7460c339e7521c6 Mon Sep 17 00:00:00 2001
From: Hans de Goede <hansg@kernel.org>
Date: Sun, 10 Aug 2025 22:48:01 +0200
Subject: [PATCH 2/4] iio: consumers: Fix offset handling in
iio_convert_raw_to_processed()
Fix iio_convert_raw_to_processed() offset handling for channels without
a scale attribute.
The offset has been applied to the raw64 value not to the original raw
value. Use the raw64 value so that the offset is taken into account.
Fixes: 14b457fdde38 ("iio: inkern: apply consumer scale when no channel scale is available")
Cc: Liam Beguin <liambeguin@gmail.com>
Signed-off-by: Hans de Goede <hansg@kernel.org>
---
Changes in v4:
- New patch in v4 of this patch-set
---
drivers/iio/inkern.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
index 6c16224619f4..9f43ae4b5bde 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -639,7 +639,7 @@ static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan,
* If no channel scaling is available apply consumer scale to
* raw value and return.
*/
- *processed = raw * scale;
+ *processed = raw64 * scale;
return 0;
}
--
2.49.0
[-- Attachment #4: 0003-iio-consumers-Add-an-iio_multiply_value-helper-funct.patch --]
[-- Type: text/x-patch, Size: 5140 bytes --]
From 3ecd9534379532b69fdb905807a56f231aa19135 Mon Sep 17 00:00:00 2001
From: Hans de Goede <hansg@kernel.org>
Date: Sun, 10 Aug 2025 22:40:45 +0200
Subject: [PATCH 3/4] iio: consumers: Add an iio_multiply_value() helper
function
The channel-scale handling in iio_convert_raw_to_processed() in essence
does the following:
processed = raw * caller-provided-scale * channel-scale
Which can also be written as:
multiplier = raw * caller-provided-scale
iio-val = channel-scale
processed = multiplier * iio-val
Where iioval is a set of IIO_VAL_* type + val + val2 integers, being
able to handle multiplication of iio-values like this is something
which is useful to have in general and as previous bugfixes to
iio_convert_raw_to_processed() have shown also tricky to implement.
Split the iio-value multiplication code from iio_convert_raw_to_processed()
out into a new iio_multiply_value() helper. This serves multiple purposes:
1. Having this split out allows writing a kunit test for this.
2. Having this split out allows re-use to get better precision
when scaling values in iio_read_channel_processed_scale().
Signed-off-by: Hans de Goede <hansg@kernel.org>
---
Changes in v4:
- New patch in v4 of this patch-set
---
drivers/iio/inkern.c | 64 +++++++++++++++++++++---------------
include/linux/iio/consumer.h | 18 ++++++++++
2 files changed, 56 insertions(+), 26 deletions(-)
diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
index 9f43ae4b5bde..e3f1d0944e28 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -598,13 +598,46 @@ int iio_read_channel_average_raw(struct iio_channel *chan, int *val)
}
EXPORT_SYMBOL_GPL(iio_read_channel_average_raw);
+int iio_multiply_value(int *result, s64 multiplier,
+ unsigned int type, int val, int val2)
+{
+ s64 denominator;
+
+ switch (type) {
+ case IIO_VAL_INT:
+ *result = multiplier * val;
+ return IIO_VAL_INT;
+ case IIO_VAL_INT_PLUS_MICRO:
+ case IIO_VAL_INT_PLUS_NANO:
+ switch (type) {
+ case IIO_VAL_INT_PLUS_MICRO: denominator = 1000000LL; break;
+ case IIO_VAL_INT_PLUS_NANO: denominator = 1000000000LL; break;
+ }
+ *result = multiplier * abs(val);
+ *result += div_s64(multiplier * abs(val2), denominator);
+ if (val < 0 || val2 < 0)
+ *result *= -1;
+ return IIO_VAL_INT;
+ case IIO_VAL_FRACTIONAL:
+ *result = div_s64(multiplier * val, val2);
+ return IIO_VAL_INT;
+ case IIO_VAL_FRACTIONAL_LOG2:
+ *result = (multiplier * val) >> val2;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL_GPL(iio_multiply_value);
+
static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan,
int raw, int *processed,
unsigned int scale)
{
int scale_type, scale_val, scale_val2;
int offset_type, offset_val, offset_val2;
- s64 denominator, raw64 = raw;
+ s64 raw64 = raw;
+ int ret;
offset_type = iio_channel_read(chan, &offset_val, &offset_val2,
IIO_CHAN_INFO_OFFSET);
@@ -643,31 +676,10 @@ static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan,
return 0;
}
- switch (scale_type) {
- case IIO_VAL_INT:
- *processed = raw64 * scale_val * scale;
- break;
- case IIO_VAL_INT_PLUS_MICRO:
- case IIO_VAL_INT_PLUS_NANO:
- switch (scale_type) {
- case IIO_VAL_INT_PLUS_MICRO: denominator = 1000000LL; break;
- case IIO_VAL_INT_PLUS_NANO: denominator = 1000000000LL; break;
- }
- *processed = raw64 * scale * abs(scale_val);
- *processed += div_s64(raw64 * scale * abs(scale_val2), denominator);
- if (scale_val < 0 || scale_val2 < 0)
- *processed *= -1;
- break;
- case IIO_VAL_FRACTIONAL:
- *processed = div_s64(raw64 * (s64)scale_val * scale,
- scale_val2);
- break;
- case IIO_VAL_FRACTIONAL_LOG2:
- *processed = (raw64 * (s64)scale_val * scale) >> scale_val2;
- break;
- default:
- return -EINVAL;
- }
+ ret = iio_multiply_value(processed, raw64 * scale,
+ scale_type, scale_val, scale_val2);
+ if (ret < 0)
+ return ret;
return 0;
}
diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
index 6a4479616479..c8c6261c81f9 100644
--- a/include/linux/iio/consumer.h
+++ b/include/linux/iio/consumer.h
@@ -381,6 +381,24 @@ int iio_read_channel_offset(struct iio_channel *chan, int *val,
int iio_read_channel_scale(struct iio_channel *chan, int *val,
int *val2);
+/**
+ * iio_multiply_value() - Multiply an iio value
+ * @result: Destination pointer for the multiplication result
+ * @multiplier: Multiplier.
+ * @type: One of the IIO_VAL_* constants. This decides how the val
+ * and val2 parameters are interpreted.
+ * @val: Value being multiplied.
+ * @val2: Value being multiplied. val2 use depends on type.
+ *
+ * Multiply an iio value with a s64 multiplier 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_multiply_value(int *result, s64 multiplier,
+ 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
--
2.49.0
[-- Attachment #5: 0004-iio-Improve-iio_read_channel_processed_scale-precisi.patch --]
[-- Type: text/x-patch, Size: 1861 bytes --]
From f8372957c6b51ae30e95015e151d42aec709c179 Mon Sep 17 00:00:00 2001
From: Hans de Goede <hansg@kernel.org>
Date: Sun, 10 Aug 2025 23:03:41 +0200
Subject: [PATCH 4/4] iio: Improve iio_read_channel_processed_scale() precision
Before this change iio_read_channel_processed_scale() always assumes that
channels which advertise IIO_CHAN_INFO_PROCESSED capability return
IIO_VAL_INT on success.
Ignoring any fractional values from drivers which return
IIO_VAL_INT_PLUS_MICRO / IIO_VAL_INT_PLUS_NANO. These fractional values
might become non fractional after scaling so these should be taken into
account for better precision.
Use the new iio_multiply_value() helper to do proper scaling taking
the fractionional values into account.
Signed-off-by: Hans de Goede <hansg@kernel.org>
---
Changes in v4
- Use the new iio_multiply_value() helper instead of DYI code
---
drivers/iio/inkern.c | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
index e3f1d0944e28..ad65a03e1684 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -721,20 +721,19 @@ int iio_read_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;
+ int ret, val2;
guard(mutex)(&iio_dev_opaque->info_exist_lock);
if (!chan->indio_dev->info)
return -ENODEV;
if (iio_channel_has_info(chan->channel, IIO_CHAN_INFO_PROCESSED)) {
- ret = iio_channel_read(chan, val, NULL,
+ ret = iio_channel_read(chan, val, &val2,
IIO_CHAN_INFO_PROCESSED);
if (ret < 0)
return ret;
- *val *= scale;
- return ret;
+ return iio_multiply_value(val, scale, ret, *val, val2);
} else {
ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW);
if (ret < 0)
--
2.49.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v3 1/2] iio: Improve iio_read_channel_processed_scale() precision
2025-08-10 21:12 ` Hans de Goede
@ 2025-08-11 12:37 ` Andy Shevchenko
2025-08-11 14:35 ` Hans de Goede
0 siblings, 1 reply; 13+ messages in thread
From: Andy Shevchenko @ 2025-08-11 12:37 UTC (permalink / raw)
To: Hans de Goede
Cc: David Lechner, Jonathan Cameron, Nuno Sá, Andy Shevchenko,
linux-iio
On Sun, Aug 10, 2025 at 11:12:24PM +0200, Hans de Goede wrote:
> On 10-Aug-25 9:25 PM, Hans de Goede wrote:
> > On 29-Jul-25 7:26 PM, David Lechner wrote:
> >> On 7/27/25 4:06 PM, Hans de Goede wrote:
...
> + switch (scale_type) {
> + case IIO_VAL_INT_PLUS_MICRO: denominator = 1000000LL; break;
> + case IIO_VAL_INT_PLUS_NANO: denominator = 1000000000LL; break;
(s64)MICRO
(s64)NANO
?
> + }
...
> + ret = iio_multiply_value(processed, raw64 * scale,
> + scale_type, scale_val, scale_val2);
> + if (ret < 0)
> + return ret;
>
> return 0;
Simply return iio_...(...); ?
...
> +int iio_multiply_value(int *result, s64 multiplier,
> + unsigned int type, int val, int val2);
There is room for type in the previous line.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 1/2] iio: Improve iio_read_channel_processed_scale() precision
2025-08-11 12:37 ` Andy Shevchenko
@ 2025-08-11 14:35 ` Hans de Goede
2025-08-11 14:50 ` Andy Shevchenko
0 siblings, 1 reply; 13+ messages in thread
From: Hans de Goede @ 2025-08-11 14:35 UTC (permalink / raw)
To: Andy Shevchenko
Cc: David Lechner, Jonathan Cameron, Nuno Sá, Andy Shevchenko,
linux-iio
Hi,
On 11-Aug-25 2:37 PM, Andy Shevchenko wrote:
> On Sun, Aug 10, 2025 at 11:12:24PM +0200, Hans de Goede wrote:
>> On 10-Aug-25 9:25 PM, Hans de Goede wrote:
>>> On 29-Jul-25 7:26 PM, David Lechner wrote:
>>>> On 7/27/25 4:06 PM, Hans de Goede wrote:
>
> ...
>
>> + switch (scale_type) {
>> + case IIO_VAL_INT_PLUS_MICRO: denominator = 1000000LL; break;
>> + case IIO_VAL_INT_PLUS_NANO: denominator = 1000000000LL; break;
>
> (s64)MICRO
> (s64)NANO
> ?
Ack.
> ...
>
>> + ret = iio_multiply_value(processed, raw64 * scale,
>> + scale_type, scale_val, scale_val2);
>> + if (ret < 0)
>> + return ret;
>>
>> return 0;
>
> Simply return iio_...(...); ?
That will change the return value on success from 0 to
IIO_VAL_INT, which is 1 not 0.
> ...
>
>> +int iio_multiply_value(int *result, s64 multiplier,
>> + unsigned int type, int val, int val2);
>
> There is room for type in the previous line.
type, val and val2 is a triplet which belongs together,
since it does not matter for the number of lines this
takes it seems better to keep the 3 together.
Regards,
Hans
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 1/2] iio: Improve iio_read_channel_processed_scale() precision
2025-08-11 14:35 ` Hans de Goede
@ 2025-08-11 14:50 ` Andy Shevchenko
0 siblings, 0 replies; 13+ messages in thread
From: Andy Shevchenko @ 2025-08-11 14:50 UTC (permalink / raw)
To: Hans de Goede
Cc: Andy Shevchenko, David Lechner, Jonathan Cameron, Nuno Sá,
Andy Shevchenko, linux-iio
On Mon, Aug 11, 2025 at 4:36 PM Hans de Goede <hansg@kernel.org> wrote:
> On 11-Aug-25 2:37 PM, Andy Shevchenko wrote:
> > On Sun, Aug 10, 2025 at 11:12:24PM +0200, Hans de Goede wrote:
> >> On 10-Aug-25 9:25 PM, Hans de Goede wrote:
> >>> On 29-Jul-25 7:26 PM, David Lechner wrote:
> >>>> On 7/27/25 4:06 PM, Hans de Goede wrote:
...
> >> + ret = iio_multiply_value(processed, raw64 * scale,
> >> + scale_type, scale_val, scale_val2);
> >> + if (ret < 0)
> >> + return ret;
> >>
> >> return 0;
> >
> > Simply return iio_...(...); ?
>
> That will change the return value on success from 0 to
> IIO_VAL_INT, which is 1 not 0.
Indeed, thanks for elaboration.
...
> >> +int iio_multiply_value(int *result, s64 multiplier,
> >> + unsigned int type, int val, int val2);
> >
> > There is room for type in the previous line.
>
> type, val and val2 is a triplet which belongs together,
> since it does not matter for the number of lines this
> takes it seems better to keep the 3 together.
I was thinking about the same, but I can't surely deduce this from the
names of parameters. I agree with your initial split.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2025-08-11 14:51 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-27 21:06 [PATCH v3 0/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver Hans de Goede
2025-07-27 21:06 ` [PATCH v3 1/2] iio: Improve iio_read_channel_processed_scale() precision Hans de Goede
2025-07-29 17:26 ` David Lechner
2025-08-10 19:25 ` Hans de Goede
2025-08-10 21:12 ` Hans de Goede
2025-08-11 12:37 ` Andy Shevchenko
2025-08-11 14:35 ` Hans de Goede
2025-08-11 14:50 ` Andy Shevchenko
2025-07-27 21:06 ` [PATCH v3 2/2] iio: adc: Add Intel Dollar Cove TI PMIC ADC driver Hans de Goede
2025-07-28 18:41 ` Jonathan Cameron
2025-07-29 17:50 ` David Lechner
2025-07-31 11:12 ` Jonathan Cameron
2025-08-02 11:46 ` Jonathan Cameron
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).