* [PATCH v2 01/10] iio: adc: ti-ads112c14: add DRDY interrupt support
2026-09-04 22:09 [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
@ 2026-09-04 22:09 ` David Lechner (TI)
2026-09-04 22:09 ` [PATCH v2 02/10] iio: adc: ti-ads112c14: create data read helper functions David Lechner (TI)
` (9 subsequent siblings)
10 siblings, 0 replies; 16+ messages in thread
From: David Lechner (TI) @ 2026-09-04 22:09 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko
Cc: Chris Hall, Patrick Edwards, Kurt Borja, linux-iio, linux-kernel,
David Lechner (TI)
Add handling for the DRDY interrupt to wait for data ready events rather
than polling (only when it is wired up).
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
Small note: the hard-coded 100 ms timeout will be replaced in a future
series with a dynamic value, so I didn't bother with a macro or comments
to explain why the value was chosen.
And passing indio_dev instead of data to irq is intentional as it will
be used in the next patch.
---
drivers/iio/adc/ti-ads112c14.c | 100 ++++++++++++++++++++++++++++++++++++-----
1 file changed, 90 insertions(+), 10 deletions(-)
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index 60eab4852ba2..63b819339788 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -10,6 +10,7 @@
#include <linux/bitfield.h>
#include <linux/cleanup.h>
+#include <linux/completion.h>
#include <linux/crc8.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
@@ -19,6 +20,7 @@
#include <linux/iio/iio.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
+#include <linux/interrupt.h>
#include <linux/math64.h>
#include <linux/minmax.h>
#include <linux/module.h>
@@ -117,9 +119,15 @@
#define ADS112C14_GPIO_CFG_GPIO2_CFG GENMASK(5, 4)
#define ADS112C14_GPIO_CFG_GPIO1_CFG GENMASK(3, 2)
#define ADS112C14_GPIO_CFG_GPIO0_CFG GENMASK(1, 0)
+#define ADS112C14_GPIO_CFG_GPIO_CFG_DISABLED 0
+#define ADS112C14_GPIO_CFG_GPIO_CFG_INPUT 1
+#define ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_PUSH_PULL 2
+#define ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_OPEN_DRAIN 3
#define ADS112C14_REG_GPIO_DATA_OUTPUT 0x0C
#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC BIT(7)
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC_DAT_OUT 0
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC_DRDY 1
#define ADS112C14_GPIO_DATA_OUTPUT_GPIO2_SRC BIT(6)
#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_DAT_OUT BIT(3)
#define ADS112C14_GPIO_DATA_OUTPUT_GPIO2_DAT_OUT BIT(2)
@@ -251,6 +259,8 @@ struct ads112c14_data {
struct regmap *regmap;
/* Synchronizes access to register value fields. */
struct mutex lock;
+ int drdy_irq;
+ struct completion drdy_completion;
bool i2c_crc_enabled;
u32 avdd_uV;
u32 ext_ref_uV;
@@ -265,6 +275,16 @@ struct ads112c14_data {
ARRAY_SIZE(ads112c14_sys_mon_channels));
};
+static irqreturn_t ads112c14_drdy_irq_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct ads112c14_data *data = iio_priv(indio_dev);
+
+ complete(&data->drdy_completion);
+
+ return IRQ_HANDLED;
+}
+
static bool ads112c14_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
@@ -581,12 +601,45 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
return 0;
}
+static int ads112c14_wait_for_conversion_irq(struct ads112c14_data *data)
+{
+ unsigned long remaining;
+ int ret;
+
+ reinit_completion(&data->drdy_completion);
+
+ ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
+ ADS112C14_CONVERSION_CTRL_START);
+ if (ret)
+ return ret;
+
+ remaining = wait_for_completion_timeout(&data->drdy_completion,
+ msecs_to_jiffies(100));
+
+ return remaining ? 0 : -ETIMEDOUT;
+}
+
+static int ads112c14_wait_for_conversion_poll(struct ads112c14_data *data)
+{
+ u32 reg_val;
+ int ret;
+
+ ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
+ ADS112C14_CONVERSION_CTRL_START);
+ if (ret)
+ return ret;
+
+ return regmap_read_poll_timeout(data->regmap,
+ ADS112C14_REG_STATUS_MSB, reg_val,
+ FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val),
+ 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC);
+}
+
static int ads112c14_single_conversion(struct ads112c14_data *data,
const struct iio_chan_spec *chan,
u8 *buf, bool for_scan)
{
struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap));
- u32 reg_val;
int ret;
guard(mutex)(&data->lock);
@@ -601,15 +654,10 @@ static int ads112c14_single_conversion(struct ads112c14_data *data,
return ret;
}
- ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
- ADS112C14_CONVERSION_CTRL_START);
- if (ret)
- return ret;
-
- ret = regmap_read_poll_timeout(data->regmap,
- ADS112C14_REG_STATUS_MSB, reg_val,
- FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val),
- 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC);
+ if (data->drdy_irq)
+ ret = ads112c14_wait_for_conversion_irq(data);
+ else
+ ret = ads112c14_wait_for_conversion_poll(data);
if (ret)
return ret;
@@ -1391,6 +1439,38 @@ static int ads112c14_probe(struct i2c_client *client)
if (ret)
return ret;
+ if (device_property_present(dev, "interrupt-names")) {
+ data->drdy_irq = fwnode_irq_get_byname(dev_fwnode(dev), "drdy");
+ if (data->drdy_irq < 0)
+ return dev_err_probe(dev, data->drdy_irq,
+ "failed to get drdy interrupt\n");
+
+ /*
+ * REVISIT: would probably need to implement a pin controller in
+ * order to support open drain option here.
+ */
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_CFG,
+ ADS112C14_GPIO_CFG_GPIO3_CFG,
+ FIELD_PREP(ADS112C14_GPIO_CFG_GPIO3_CFG,
+ ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_PUSH_PULL));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_DATA_OUTPUT,
+ ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC,
+ FIELD_PREP(ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC,
+ ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC_DRDY));
+ if (ret)
+ return ret;
+
+ init_completion(&data->drdy_completion);
+
+ ret = devm_request_irq(dev, data->drdy_irq, ads112c14_drdy_irq_handler,
+ 0, dev_name(dev), indio_dev);
+ if (ret)
+ return ret;
+ }
+
ads112c14_populate_tables(data);
indio_dev->name = info->name;
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 02/10] iio: adc: ti-ads112c14: create data read helper functions
2026-09-04 22:09 [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
2026-09-04 22:09 ` [PATCH v2 01/10] iio: adc: ti-ads112c14: add DRDY interrupt support David Lechner (TI)
@ 2026-09-04 22:09 ` David Lechner (TI)
2026-09-04 22:09 ` [PATCH v2 03/10] iio: adc: ti-ads112c14: add continuous mode support David Lechner (TI)
` (8 subsequent siblings)
10 siblings, 0 replies; 16+ messages in thread
From: David Lechner (TI) @ 2026-09-04 22:09 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko
Cc: Chris Hall, Patrick Edwards, Kurt Borja, linux-iio, linux-kernel,
David Lechner (TI)
Refactor a few bits of code into helper functions. These will be reused
when continuous mode support is added in a later patch.
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
drivers/iio/adc/ti-ads112c14.c | 51 +++++++++++++++++++++++++-----------------
1 file changed, 31 insertions(+), 20 deletions(-)
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index 63b819339788..23f15be303fb 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -601,6 +601,32 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
return 0;
}
+static int ads112c14_prepare_channel(struct ads112c14_data *data,
+ const struct iio_chan_spec *chan)
+{
+ if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE)
+ return ads112c14_prepare_measurement_channel(data, chan);
+
+ return ads112c14_prepare_sys_mon_channel(data, chan);
+}
+
+static int ads112c14_scan_read(struct ads112c14_data *data, u8 *buf)
+{
+ struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap));
+ int ret;
+ u8 len;
+
+ len = BITS_TO_BYTES(data->chip_info->resolution_bits);
+ if (data->i2c_crc_enabled)
+ len += 1;
+
+ ret = i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA, len, buf);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
static int ads112c14_wait_for_conversion_irq(struct ads112c14_data *data)
{
unsigned long remaining;
@@ -644,15 +670,9 @@ static int ads112c14_single_conversion(struct ads112c14_data *data,
guard(mutex)(&data->lock);
- if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
- ret = ads112c14_prepare_measurement_channel(data, chan);
- if (ret)
- return ret;
- } else {
- ret = ads112c14_prepare_sys_mon_channel(data, chan);
- if (ret)
- return ret;
- }
+ ret = ads112c14_prepare_channel(data, chan);
+ if (ret)
+ return ret;
if (data->drdy_irq)
ret = ads112c14_wait_for_conversion_irq(data);
@@ -667,17 +687,8 @@ static int ads112c14_single_conversion(struct ads112c14_data *data,
* with CRC errors, but rather leave it to userspace to decide what to
* do.
*/
- if (for_scan) {
- u8 len = BITS_TO_BYTES(data->chip_info->resolution_bits) +
- (data->i2c_crc_enabled ? 1 : 0);
-
- ret = i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA,
- len, buf);
- if (ret < 0)
- return ret;
-
- return 0;
- }
+ if (for_scan)
+ return ads112c14_scan_read(data, buf);
return ads112c14_i2c_read_bytes(client, ADS112C14_CMD_RDATA, buf,
BITS_TO_BYTES(data->chip_info->resolution_bits),
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 03/10] iio: adc: ti-ads112c14: add continuous mode support
2026-09-04 22:09 [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
2026-09-04 22:09 ` [PATCH v2 01/10] iio: adc: ti-ads112c14: add DRDY interrupt support David Lechner (TI)
2026-09-04 22:09 ` [PATCH v2 02/10] iio: adc: ti-ads112c14: create data read helper functions David Lechner (TI)
@ 2026-09-04 22:09 ` David Lechner (TI)
2026-09-04 22:09 ` [PATCH v2 04/10] iio: adc: ti-ads112c14: add burnout current support David Lechner (TI)
` (7 subsequent siblings)
10 siblings, 0 replies; 16+ messages in thread
From: David Lechner (TI) @ 2026-09-04 22:09 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko
Cc: Chris Hall, Patrick Edwards, Kurt Borja, linux-iio, linux-kernel,
David Lechner (TI)
Add support for continuous mode in the TI ADS112C14 ADC driver. In this
mode the ADC itself is starting each conversion, so we add a trigger
based on the DRDY interrupt to read each sample. This mode is also
limited in that only one channel can be enabled at a time since the
chip does not have a sequencer or simultaneous sampling capability.
Continuous mode will only be used when this new trigger is the current
trigger.
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
drivers/iio/adc/ti-ads112c14.c | 153 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 151 insertions(+), 2 deletions(-)
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index 23f15be303fb..06b962fbde5e 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -9,6 +9,7 @@
*/
#include <linux/bitfield.h>
+#include <linux/bitmap.h>
#include <linux/cleanup.h>
#include <linux/completion.h>
#include <linux/crc8.h>
@@ -18,6 +19,7 @@
#include <linux/i2c.h>
#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/interrupt.h>
@@ -257,10 +259,12 @@ struct ads112c14_measurement {
struct ads112c14_data {
const struct ads112c14_chip_info *chip_info;
struct regmap *regmap;
+ struct iio_trigger *drdy_trig;
/* Synchronizes access to register value fields. */
struct mutex lock;
int drdy_irq;
struct completion drdy_completion;
+ bool continuous_mode;
bool i2c_crc_enabled;
u32 avdd_uV;
u32 ext_ref_uV;
@@ -280,11 +284,18 @@ static irqreturn_t ads112c14_drdy_irq_handler(int irq, void *private)
struct iio_dev *indio_dev = private;
struct ads112c14_data *data = iio_priv(indio_dev);
- complete(&data->drdy_completion);
+ if (READ_ONCE(data->continuous_mode))
+ iio_trigger_poll(data->drdy_trig);
+ else
+ complete(&data->drdy_completion);
return IRQ_HANDLED;
}
+static const struct iio_trigger_ops ads112c14_trigger_ops = {
+ .validate_device = iio_trigger_validate_own_device,
+};
+
static bool ads112c14_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
@@ -695,6 +706,13 @@ static int ads112c14_single_conversion(struct ads112c14_data *data,
data->i2c_crc_enabled);
}
+static bool ads112c14_using_drdy_trigger(struct iio_dev *indio_dev)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+
+ return data->drdy_trig && indio_dev->trig == data->drdy_trig;
+}
+
static int ads112c14_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -898,6 +916,19 @@ static int ads112c14_write_raw_get_fmt(struct iio_dev *indio_dev,
}
}
+static int ads112c14_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *scan_mask)
+{
+ /* Only continuous mode is limited to a single channel. */
+ if (!ads112c14_using_drdy_trigger(indio_dev))
+ return 0;
+
+ if (!iio_validate_scan_mask_onehot(indio_dev, scan_mask))
+ return -EINVAL;
+
+ return 0;
+}
+
static int ads112c14_debugfs_reg_access(struct iio_dev *indio_dev,
unsigned int reg,
unsigned int writeval,
@@ -952,6 +983,19 @@ static int ads112c14_read_label(struct iio_dev *indio_dev,
return sysfs_emit(label, "%s\n", label_source);
}
+static const struct iio_chan_spec *
+ads112c14_first_active_channel(struct iio_dev *indio_dev)
+{
+ unsigned int scan_mask_len = iio_get_masklength(indio_dev);
+ unsigned int i;
+
+ i = find_first_bit(indio_dev->active_scan_mask, scan_mask_len);
+ if (i == scan_mask_len)
+ return NULL;
+
+ return &indio_dev->channels[i];
+}
+
static irqreturn_t ads112c14_trigger_handler(int irq, void *private)
{
struct iio_poll_func *pf = private;
@@ -961,6 +1005,26 @@ static irqreturn_t ads112c14_trigger_handler(int irq, void *private)
u32 i;
int ret;
+ if (ads112c14_using_drdy_trigger(indio_dev)) {
+ const struct iio_chan_spec *chan;
+
+ chan = ads112c14_first_active_channel(indio_dev);
+ if (!chan)
+ goto out;
+
+ ret = ads112c14_scan_read(data, (u8 *)&data->scan[0]);
+ if (ret) {
+ dev_err_once(indio_dev->dev.parent,
+ "failed to read channel %d: %pe; additional errors will be suppressed\n",
+ chan->channel, ERR_PTR(ret));
+ goto out;
+ }
+
+ iio_push_to_buffers_with_ts(indio_dev, data->scan,
+ sizeof(data->scan), pf->timestamp);
+ goto out;
+ }
+
iio_for_each_active_channel(indio_dev, i) {
const struct iio_chan_spec *chan = &indio_dev->channels[i];
@@ -988,10 +1052,81 @@ static const struct iio_info ads112c14_info = {
.read_avail = ads112c14_read_avail,
.write_raw = ads112c14_write_raw,
.write_raw_get_fmt = ads112c14_write_raw_get_fmt,
+ .update_scan_mode = ads112c14_update_scan_mode,
.debugfs_reg_access = ads112c14_debugfs_reg_access,
.read_label = ads112c14_read_label,
};
+static int ads112c14_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ const struct iio_chan_spec *chan;
+ int ret;
+
+ if (!ads112c14_using_drdy_trigger(indio_dev))
+ return 0;
+
+ chan = ads112c14_first_active_channel(indio_dev);
+ if (!chan)
+ return -EINVAL;
+
+ guard(mutex)(&data->lock);
+
+ ret = ads112c14_prepare_channel(data, chan);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_CONV_MODE,
+ FIELD_PREP(ADS112C14_DEVICE_CFG_CONV_MODE,
+ ADS112C14_DEVICE_CFG_CONV_MODE_CONTINUOUS));
+ if (ret)
+ return ret;
+
+ WRITE_ONCE(data->continuous_mode, true);
+
+ ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
+ ADS112C14_CONVERSION_CTRL_START);
+ if (ret) {
+ WRITE_ONCE(data->continuous_mode, false);
+ regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_CONV_MODE,
+ FIELD_PREP(ADS112C14_DEVICE_CFG_CONV_MODE,
+ ADS112C14_DEVICE_CFG_CONV_MODE_SINGLE_SHOT));
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ads112c14_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (!ads112c14_using_drdy_trigger(indio_dev))
+ return 0;
+
+ guard(mutex)(&data->lock);
+
+ WRITE_ONCE(data->continuous_mode, false);
+
+ ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
+ ADS112C14_CONVERSION_CTRL_STOP);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_CONV_MODE,
+ FIELD_PREP(ADS112C14_DEVICE_CFG_CONV_MODE,
+ ADS112C14_DEVICE_CFG_CONV_MODE_SINGLE_SHOT));
+}
+
+static const struct iio_buffer_setup_ops ads112c14_buffer_setup_ops = {
+ .postenable = ads112c14_buffer_postenable,
+ .predisable = ads112c14_buffer_predisable,
+};
+
static int ads112c14_populate_idac_mag(u32 current_nA, u8 *idac_mag)
{
u32 current_uA = current_nA / (NANO / MICRO);
@@ -1480,6 +1615,19 @@ static int ads112c14_probe(struct i2c_client *client)
0, dev_name(dev), indio_dev);
if (ret)
return ret;
+
+ data->drdy_trig = devm_iio_trigger_alloc(dev, "%s-dev%d-drdy",
+ info->name,
+ iio_device_id(indio_dev));
+ if (!data->drdy_trig)
+ return -ENOMEM;
+
+ data->drdy_trig->ops = &ads112c14_trigger_ops;
+ iio_trigger_set_drvdata(data->drdy_trig, indio_dev);
+
+ ret = devm_iio_trigger_register(dev, data->drdy_trig);
+ if (ret)
+ return ret;
}
ads112c14_populate_tables(data);
@@ -1490,7 +1638,8 @@ static int ads112c14_probe(struct i2c_client *client)
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
iio_pollfunc_store_time,
- ads112c14_trigger_handler, NULL);
+ ads112c14_trigger_handler,
+ &ads112c14_buffer_setup_ops);
if (ret)
return ret;
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 04/10] iio: adc: ti-ads112c14: add burnout current support
2026-09-04 22:09 [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
` (2 preceding siblings ...)
2026-09-04 22:09 ` [PATCH v2 03/10] iio: adc: ti-ads112c14: add continuous mode support David Lechner (TI)
@ 2026-09-04 22:09 ` David Lechner (TI)
2026-09-04 22:09 ` [PATCH v2 05/10] iio: ABI: add sysfs attribute for _burnoutraw David Lechner (TI)
` (6 subsequent siblings)
10 siblings, 0 replies; 16+ messages in thread
From: David Lechner (TI) @ 2026-09-04 22:09 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko
Cc: Chris Hall, Patrick Edwards, Kurt Borja, linux-iio, linux-kernel,
David Lechner (TI)
Add a custom attribute via ext_info when a channel has a burnout current
specified in the devicetree. This adds an in_{voltageY,resistanceY,
voltageY-voltageX}_burnoutraw sysfs attribute for the channel that
performs a single conversion (same as _raw attribute) except that it
enables the burnout current. The chip also has a restriction that input
chopping cannot be enabled when burnout current is enabled, so we also
disable input chopping when burnout current is active.
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
drivers/iio/adc/ti-ads112c14.c | 125 ++++++++++++++++++++++++++++++++++++++---
1 file changed, 116 insertions(+), 9 deletions(-)
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index 06b962fbde5e..c03789c3d95e 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -76,6 +76,11 @@
#define ADS112C14_DEVICE_CFG_PWDN BIT(7)
#define ADS112C14_DEVICE_CFG_STBY_MODE BIT(6)
#define ADS112C14_DEVICE_CFG_BOCS GENMASK(5, 4)
+#define ADS112C14_DEVICE_CFG_BOCS_DISABLED 0
+#define ADS112C14_DEVICE_CFG_BOCS_200_nA 1
+#define ADS112C14_DEVICE_CFG_BOCS_1_uA 2
+#define ADS112C14_DEVICE_CFG_BOCS_10_uA 3
+
#define ADS112C14_DEVICE_CFG_CLK_SEL BIT(3)
#define ADS112C14_DEVICE_CFG_CONV_MODE BIT(2)
#define ADS112C14_DEVICE_CFG_CONV_MODE_CONTINUOUS 0
@@ -251,6 +256,7 @@ struct ads112c14_measurement {
u8 idac2_mux;
u8 iadc_count;
u8 gain_val;
+ u8 burnout;
bool global_chop;
bool bipolar;
int scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2];
@@ -461,7 +467,8 @@ static const struct regmap_config ads112c14_regmap_config = {
};
static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
- const struct iio_chan_spec *chan)
+ const struct iio_chan_spec *chan,
+ bool en_burnout)
{
struct ads112c14_measurement *measurement = &data->measurements[chan->scan_index];
u32 refp_buf_en, refn_buf_en, ref_val, ref_sel;
@@ -515,7 +522,8 @@ static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG,
ADS112C14_DATA_RATE_CFG_GC_EN,
FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN,
- measurement->global_chop));
+ (measurement->global_chop &&
+ !en_burnout) ? 1 : 0));
if (ret)
return ret;
@@ -613,10 +621,11 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
}
static int ads112c14_prepare_channel(struct ads112c14_data *data,
- const struct iio_chan_spec *chan)
+ const struct iio_chan_spec *chan,
+ bool en_burnout)
{
if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE)
- return ads112c14_prepare_measurement_channel(data, chan);
+ return ads112c14_prepare_measurement_channel(data, chan, en_burnout);
return ads112c14_prepare_sys_mon_channel(data, chan);
}
@@ -674,14 +683,14 @@ static int ads112c14_wait_for_conversion_poll(struct ads112c14_data *data)
static int ads112c14_single_conversion(struct ads112c14_data *data,
const struct iio_chan_spec *chan,
- u8 *buf, bool for_scan)
+ u8 *buf, bool en_burnout, bool for_scan)
{
struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap));
int ret;
guard(mutex)(&data->lock);
- ret = ads112c14_prepare_channel(data, chan);
+ ret = ads112c14_prepare_channel(data, chan, en_burnout);
if (ret)
return ret;
@@ -742,7 +751,7 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
if (IIO_DEV_ACQUIRE_FAILED(claim))
return -EBUSY;
- ret = ads112c14_single_conversion(data, chan, buf, false);
+ ret = ads112c14_single_conversion(data, chan, buf, false, false);
if (ret)
return ret;
@@ -1030,7 +1039,7 @@ static irqreturn_t ads112c14_trigger_handler(int irq, void *private)
ret = ads112c14_single_conversion(data, chan,
(u8 *)&data->scan[offset++],
- true);
+ false, true);
if (ret) {
dev_err_once(indio_dev->dev.parent,
"failed to read channel %d: %pe; additional errors will be suppressed\n",
@@ -1072,7 +1081,7 @@ static int ads112c14_buffer_postenable(struct iio_dev *indio_dev)
guard(mutex)(&data->lock);
- ret = ads112c14_prepare_channel(data, chan);
+ ret = ads112c14_prepare_channel(data, chan, false);
if (ret)
return ret;
@@ -1127,6 +1136,74 @@ static const struct iio_buffer_setup_ops ads112c14_buffer_setup_ops = {
.predisable = ads112c14_buffer_predisable,
};
+static ssize_t ads112c14_read_burnout_raw(struct iio_dev *indio_dev,
+ uintptr_t private,
+ struct iio_chan_spec const *chan,
+ char *buf)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_measurement *measurement;
+ int ret, ret2, val;
+ u8 raw_buf[3];
+
+ if (chan->channel >= ADS112C14_SYS_MON_CHANNEL_BASE)
+ return -EINVAL;
+
+ measurement = &data->measurements[chan->scan_index];
+ if (!measurement->burnout)
+ return -EINVAL;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_BOCS,
+ FIELD_PREP(ADS112C14_DEVICE_CFG_BOCS,
+ measurement->burnout));
+ if (ret)
+ return ret;
+
+ ret = ads112c14_single_conversion(data, chan, raw_buf, true, false);
+
+ /*
+ * Important to always turn off burnout current even if the conversion
+ * fails so that it does not affect subsequent measurements.
+ */
+ ret2 = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_BOCS,
+ FIELD_PREP(ADS112C14_DEVICE_CFG_BOCS,
+ ADS112C14_DEVICE_CFG_BOCS_DISABLED));
+ if (ret < 0)
+ return ret;
+ if (ret2)
+ return ret2;
+
+ switch (data->chip_info->resolution_bits) {
+ case 16:
+ val = get_unaligned_be16(raw_buf);
+ break;
+ case 24:
+ val = get_unaligned_be24(raw_buf);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (measurement->bipolar)
+ val = sign_extend32(val, data->chip_info->resolution_bits - 1);
+
+ return sysfs_emit(buf, "%d\n", val);
+}
+
+static const struct iio_chan_spec_ext_info ads112c14_ext_info_burnout[] = {
+ {
+ .name = "burnoutraw",
+ .read = ads112c14_read_burnout_raw,
+ },
+ { }
+};
+
static int ads112c14_populate_idac_mag(u32 current_nA, u8 *idac_mag)
{
u32 current_uA = current_nA / (NANO / MICRO);
@@ -1172,6 +1249,7 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
device_for_each_named_child_node_scoped(dev, child, "channel") {
struct ads112c14_measurement *measurement = &data->measurements[i];
struct iio_chan_spec *spec = &channels[i];
+ const char *propname;
spec->indexed = 1;
spec->scan_index = i;
@@ -1295,6 +1373,35 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
measurement->global_chop = fwnode_property_read_bool(child,
"input-chopping");
+ propname = "burn-out-current-nanoamp";
+ if (fwnode_property_present(child, propname)) {
+ u32 burnout_nA;
+
+ ret = fwnode_property_read_u32(child, propname, &burnout_nA);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read %s property\n",
+ propname);
+
+ switch (burnout_nA) {
+ case 200:
+ measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_200_nA;
+ break;
+ case 1000:
+ measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_1_uA;
+ break;
+ case 10000:
+ measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_10_uA;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "invalid %s value\n", propname);
+ }
+
+ if (measurement->burnout != ADS112C14_DEVICE_CFG_BOCS_DISABLED)
+ spec->ext_info = ads112c14_ext_info_burnout;
+ }
+
if (fwnode_property_present(child, "reference-sources")) {
ret = fwnode_property_match_property_string(child,
"reference-sources", ads112c14_vref_source_names,
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 05/10] iio: ABI: add sysfs attribute for _burnoutraw
2026-09-04 22:09 [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
` (3 preceding siblings ...)
2026-09-04 22:09 ` [PATCH v2 04/10] iio: adc: ti-ads112c14: add burnout current support David Lechner (TI)
@ 2026-09-04 22:09 ` David Lechner (TI)
2026-09-04 22:09 ` [PATCH v2 06/10] iio: adc: ti-ads112c14: support external clock David Lechner (TI)
` (5 subsequent siblings)
10 siblings, 0 replies; 16+ messages in thread
From: David Lechner (TI) @ 2026-09-04 22:09 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko
Cc: Chris Hall, Patrick Edwards, Kurt Borja, linux-iio, linux-kernel,
David Lechner (TI)
Add a new _burnoutraw attribute to the IIO ADC ABI. This is likely only
applicable to ADCs (but is seen on multiple chips and vendors) so it
gets its own file instead of being added to the main IIO ABI file.
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
Documentation/ABI/testing/sysfs-bus-iio-adc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-bus-iio-adc b/Documentation/ABI/testing/sysfs-bus-iio-adc
new file mode 100644
index 000000000000..e1309b371efb
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-adc
@@ -0,0 +1,9 @@
+What: /sys/bus/iio/devices/iio:deviceX/in_resistanceY_burnoutraw
+What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_burnoutraw
+What: /sys/bus/iio/devices/iio:deviceX/in_voltageY-voltageZ_burnoutraw
+KernelVersion: 7.4
+Contact: linux-iio@vger.kernel.org
+Description:
+ Raw value from channel Y read using a single conversion with
+ the channel burnout current enabled. This is typically used
+ for diagnostic purposes to detect an open or shorted input.
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 06/10] iio: adc: ti-ads112c14: support external clock
2026-09-04 22:09 [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
` (4 preceding siblings ...)
2026-09-04 22:09 ` [PATCH v2 05/10] iio: ABI: add sysfs attribute for _burnoutraw David Lechner (TI)
@ 2026-09-04 22:09 ` David Lechner (TI)
2026-09-06 1:41 ` Jonathan Cameron
2026-09-04 22:09 ` [PATCH v2 07/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
` (4 subsequent siblings)
10 siblings, 1 reply; 16+ messages in thread
From: David Lechner (TI) @ 2026-09-04 22:09 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko
Cc: Chris Hall, Patrick Edwards, Kurt Borja, linux-iio, linux-kernel,
David Lechner (TI)
Add support for an external clock source to the TI ADS112C14 ADC driver.
The unused fclk_Hz field is added in preparation for filter support.
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
drivers/iio/adc/ti-ads112c14.c | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index c03789c3d95e..55462fc57752 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -11,6 +11,7 @@
#include <linux/bitfield.h>
#include <linux/bitmap.h>
#include <linux/cleanup.h>
+#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/crc8.h>
#include <linux/delay.h>
@@ -178,6 +179,8 @@ static const u32 ads112c14_pga_gains_x10[] = {
200, 320, 500, 640, 1000, 1280, 2000, 2560, /* 8 - 15 */
};
+#define ADS112C14_INTERNAL_CLK_Hz 4096000
+
#define ADS112C14_I2C_CRC8_POLYNOMIAL 0x07
DECLARE_CRC8_TABLE(ads112c14_crc8_table);
@@ -268,6 +271,7 @@ struct ads112c14_data {
struct iio_trigger *drdy_trig;
/* Synchronizes access to register value fields. */
struct mutex lock;
+ long fclk_Hz;
int drdy_irq;
struct completion drdy_completion;
bool continuous_mode;
@@ -1536,6 +1540,7 @@ static int ads112c14_probe(struct i2c_client *client)
const struct ads112c14_chip_info *info;
struct iio_dev *indio_dev;
struct ads112c14_data *data;
+ struct clk *clk;
bool need_avdd_ref, need_ext_ref;
u32 refp_uV = 0;
u32 refn_uV = 0;
@@ -1628,6 +1633,12 @@ static int ads112c14_probe(struct i2c_client *client)
return dev_err_probe(dev, -EINVAL,
"external reference measurements require either refp-supply or ti,refp-refn-resistor-ohms property\n");
+ clk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "failed to get clk\n");
+
+ data->fclk_Hz = clk ? clk_get_rate(clk) : ADS112C14_INTERNAL_CLK_Hz;
+
/* It takes some time for the internal reference to stabilize. */
fsleep(10 * USEC_PER_MSEC);
@@ -1698,6 +1709,10 @@ static int ads112c14_probe(struct i2c_client *client)
return dev_err_probe(dev, data->drdy_irq,
"failed to get drdy interrupt\n");
+ if (clk)
+ return dev_err_probe(dev, -EINVAL,
+ "cannot use both DRDY and CLK - they share the same pin\n");
+
/*
* REVISIT: would probably need to implement a pin controller in
* order to support open drain option here.
@@ -1737,6 +1752,20 @@ static int ads112c14_probe(struct i2c_client *client)
return ret;
}
+ if (clk) {
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_CFG,
+ ADS112C14_GPIO_CFG_GPIO3_CFG,
+ FIELD_PREP(ADS112C14_GPIO_CFG_GPIO3_CFG,
+ ADS112C14_GPIO_CFG_GPIO_CFG_INPUT));
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_CLK_SEL);
+ if (ret)
+ return ret;
+ }
+
ads112c14_populate_tables(data);
indio_dev->name = info->name;
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH v2 06/10] iio: adc: ti-ads112c14: support external clock
2026-09-04 22:09 ` [PATCH v2 06/10] iio: adc: ti-ads112c14: support external clock David Lechner (TI)
@ 2026-09-06 1:41 ` Jonathan Cameron
0 siblings, 0 replies; 16+ messages in thread
From: Jonathan Cameron @ 2026-09-06 1:41 UTC (permalink / raw)
To: David Lechner (TI)
Cc: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Chris Hall,
Patrick Edwards, Kurt Borja, linux-iio, linux-kernel
> Add support for an external clock source to the TI ADS112C14 ADC driver.
> The unused fclk_Hz field is added in preparation for filter support.
>
> Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
One minor thing inline.
>
> diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
> index c03789c3d95e..55462fc57752 100644
> --- a/drivers/iio/adc/ti-ads112c14.c
> +++ b/drivers/iio/adc/ti-ads112c14.c
> @@ -1737,6 +1752,20 @@ static int ads112c14_probe(struct i2c_client *client)
> return ret;
> }
>
> + if (clk) {
> + ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_CFG,
> + ADS112C14_GPIO_CFG_GPIO3_CFG,
> + FIELD_PREP(ADS112C14_GPIO_CFG_GPIO3_CFG,
> + ADS112C14_GPIO_CFG_GPIO_CFG_INPUT));
> + if (ret)
> + return ret;
> +
> + ret = regmap_set_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
> + ADS112C14_DEVICE_CFG_CLK_SEL);
Given it is a pick between two things I'd rather see a FIELD_PREP()
for this one and a field value name that tells us what is being
picked. That means adding defines for values 0 and 1.
--
Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 07/10] iio: adc: ti-ads112c14: add filter support
2026-09-04 22:09 [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
` (5 preceding siblings ...)
2026-09-04 22:09 ` [PATCH v2 06/10] iio: adc: ti-ads112c14: support external clock David Lechner (TI)
@ 2026-09-04 22:09 ` David Lechner (TI)
2026-09-06 1:41 ` Jonathan Cameron
2026-09-04 22:09 ` [PATCH v2 08/10] iio: ABI: add sinc4+sinc1+pf1 filter_type David Lechner (TI)
` (3 subsequent siblings)
10 siblings, 1 reply; 16+ messages in thread
From: David Lechner (TI) @ 2026-09-04 22:09 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko
Cc: Chris Hall, Patrick Edwards, Kurt Borja, linux-iio, linux-kernel,
David Lechner (TI)
Add support for filter_type, oversampling_ratio and sampling_frequency
attributes to the ti-ads112c14 driver.
On these chips, these three controls are interdependent and the
SPEED_MODE register value has a different meaning depending on the
filter type, which makes the interactions a bit complex. As such, the
expectation is that the user will set the filter type first, then
depending on the filter type, either set the oversampling ratio or the
sampling frequency and finally the other of these two.
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
Note: I intend to sumbit a documentation patch later that explains the
"right way" to set these three attributes since it is a bit odd due to
the conditional interdependencies.
---
drivers/iio/adc/ti-ads112c14.c | 493 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 480 insertions(+), 13 deletions(-)
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index 55462fc57752..85e926184b6e 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -92,6 +92,14 @@
#define ADS112C14_DATA_RATE_CFG_DELAY GENMASK(7, 4)
#define ADS112C14_DATA_RATE_CFG_GC_EN BIT(3)
#define ADS112C14_DATA_RATE_CFG_FLTR_OSR GENMASK(2, 0)
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_16 0
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_32 1
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_128 2
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_256 3
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_512 4
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024 5
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS 6
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS 7
#define ADS112C14_REG_MUX_CFG 0x07
#define ADS112C14_MUX_CFG_AINP GENMASK(7, 4)
@@ -181,6 +189,43 @@ static const u32 ads112c14_pga_gains_x10[] = {
#define ADS112C14_INTERNAL_CLK_Hz 4096000
+/* Index corresponds to first 2 ADS112C14_DATA_RATE_CFG_FLTR_OSR values. */
+static const int ads112c14_sinc4_osr_available[] = {
+ 16, 32
+};
+
+/* Index corresponds to next 4 ADS112C14_DATA_RATE_CFG_FLTR_OSR values. */
+static const int ads112c14_sinc4_sinc1_osr_available[] = {
+ 128, 256, 512, 1024
+};
+
+/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */
+static const int ads112c14_sinc4_sinc1_pf1_20sps_osr_available[] = {
+ 1600, 12800, 25600, 51200
+};
+
+/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */
+static const int ads112c14_sinc4_sinc1_pf1_25sps_osr_available[] = {
+ 1280, 10240, 20480, 40960
+};
+
+/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */
+static const int ads112c14_fmod_div[] = {
+ 128, 16, 8, 4
+};
+
+enum ads112c14_filter_type {
+ ADS112C14_FILTER_TYPE_SINC4,
+ ADS112C14_FILTER_TYPE_SINC4_SINC1,
+ ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1,
+};
+
+static const char * const ads112c14_filter_type_names[] = {
+ [ADS112C14_FILTER_TYPE_SINC4] = "sinc4",
+ [ADS112C14_FILTER_TYPE_SINC4_SINC1] = "sinc4+sinc1",
+ [ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1] = "sinc4+sinc1+pf1",
+};
+
#define ADS112C14_I2C_CRC8_POLYNOMIAL 0x07
DECLARE_CRC8_TABLE(ads112c14_crc8_table);
@@ -202,6 +247,8 @@ enum {
ADS112C14_SYS_MON_CHANNEL_SHORT,
};
+static const struct iio_chan_spec_ext_info ads112c14_ext_info[];
+
static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
{
.type = IIO_TEMP,
@@ -210,7 +257,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
.address = 2,
.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_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .ext_info = ads112c14_ext_info,
},
{
.type = IIO_VOLTAGE,
@@ -218,7 +270,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
.channel = ADS112C14_SYS_MON_CHANNEL_EXT_REF,
.address = 3,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
- | BIT(IIO_CHAN_INFO_SCALE),
+ | BIT(IIO_CHAN_INFO_SCALE)
+ | BIT(IIO_CHAN_INFO_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .ext_info = ads112c14_ext_info,
},
{
.type = IIO_VOLTAGE,
@@ -226,7 +283,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
.channel = ADS112C14_SYS_MON_CHANNEL_AVDD,
.address = 4,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
- | BIT(IIO_CHAN_INFO_SCALE),
+ | BIT(IIO_CHAN_INFO_SCALE)
+ | BIT(IIO_CHAN_INFO_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .ext_info = ads112c14_ext_info,
},
{
.type = IIO_VOLTAGE,
@@ -234,7 +296,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
.channel = ADS112C14_SYS_MON_CHANNEL_DVDD,
.address = 5,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
- | BIT(IIO_CHAN_INFO_SCALE),
+ | BIT(IIO_CHAN_INFO_SCALE)
+ | BIT(IIO_CHAN_INFO_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .ext_info = ads112c14_ext_info,
},
{
.type = IIO_VOLTAGE,
@@ -244,8 +311,13 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
.differential = 1,
.address = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
- | BIT(IIO_CHAN_INFO_SCALE),
- .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
+ | BIT(IIO_CHAN_INFO_SCALE)
+ | BIT(IIO_CHAN_INFO_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE)
+ | BIT(IIO_CHAN_INFO_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .ext_info = ads112c14_ext_info,
},
};
@@ -265,6 +337,11 @@ struct ads112c14_measurement {
int scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2];
};
+struct ads112c14_channel_state {
+ u8 speed_mode;
+ u8 filter_osr;
+};
+
struct ads112c14_data {
const struct ads112c14_chip_info *chip_info;
struct regmap *regmap;
@@ -282,9 +359,13 @@ struct ads112c14_data {
bool refn_is_gnd;
u32 ext_ref_ohms;
struct ads112c14_measurement *measurements;
+ struct ads112c14_channel_state *channel_states;
u32 num_measurements;
u8 sys_mon_chan_short_gain_val;
int sys_mon_chan_short_scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2];
+ int sinc4_sample_rate_available[ARRAY_SIZE(ads112c14_sinc4_osr_available)][ARRAY_SIZE(ads112c14_fmod_div)][2];
+ int sinc4_sinc1_sample_rate_available[ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available)][ARRAY_SIZE(ads112c14_fmod_div)][2];
+ int sinc4_sinc1_pf1_sample_rate_available[2][2];
IIO_DECLARE_BUFFER_WITH_TS(__be32, scan, ADS112C14_MAX_MEASUREMENT_CHANNELS +
ARRAY_SIZE(ads112c14_sys_mon_channels));
};
@@ -470,14 +551,46 @@ static const struct regmap_config ads112c14_regmap_config = {
.cache_type = REGCACHE_MAPLE,
};
+static int ads112c14_get_osr(struct ads112c14_channel_state *channel_state)
+{
+ u8 i;
+
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
+ i = channel_state->filter_osr;
+ return ads112c14_sinc4_osr_available[i];
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ i = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128;
+ return ads112c14_sinc4_sinc1_osr_available[i];
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS:
+ i = channel_state->speed_mode;
+ return ads112c14_sinc4_sinc1_pf1_25sps_osr_available[i];
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
+ i = channel_state->speed_mode;
+ return ads112c14_sinc4_sinc1_pf1_20sps_osr_available[i];
+ default:
+ return -EINVAL;
+ }
+}
+
static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
const struct iio_chan_spec *chan,
bool en_burnout)
{
struct ads112c14_measurement *measurement = &data->measurements[chan->scan_index];
+ struct ads112c14_channel_state *channel_state;
u32 refp_buf_en, refn_buf_en, ref_val, ref_sel;
int ret;
+ channel_state = &data->channel_states[chan->scan_index];
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_SPEED_MODE,
+ FIELD_PREP(ADS112C14_DEVICE_CFG_SPEED_MODE,
+ channel_state->speed_mode));
+ if (ret)
+ return ret;
+
ret = regmap_update_bits(data->regmap, ADS112C14_REG_MUX_CFG,
ADS112C14_MUX_CFG_AINP | ADS112C14_MUX_CFG_AINN,
FIELD_PREP(ADS112C14_MUX_CFG_AINP, chan->channel) |
@@ -524,10 +637,13 @@ static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
return ret;
ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG,
- ADS112C14_DATA_RATE_CFG_GC_EN,
+ ADS112C14_DATA_RATE_CFG_GC_EN |
+ ADS112C14_DATA_RATE_CFG_FLTR_OSR,
FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN,
(measurement->global_chop &&
- !en_burnout) ? 1 : 0));
+ !en_burnout) ? 1 : 0) |
+ FIELD_PREP(ADS112C14_DATA_RATE_CFG_FLTR_OSR,
+ channel_state->filter_osr));
if (ret)
return ret;
@@ -570,9 +686,12 @@ static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
const struct iio_chan_spec *chan)
{
+ struct ads112c14_channel_state *channel_state;
u32 gain_val;
int ret;
+ channel_state = &data->channel_states[chan->scan_index];
+
/*
* NB: IDAC registers are left as-is in case they are generating current
* needed for the external reference measurement.
@@ -599,6 +718,22 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
if (ret)
return ret;
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_SPEED_MODE,
+ FIELD_PREP(ADS112C14_DEVICE_CFG_SPEED_MODE,
+ channel_state->speed_mode));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG,
+ ADS112C14_DATA_RATE_CFG_GC_EN |
+ ADS112C14_DATA_RATE_CFG_FLTR_OSR,
+ FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN, 0) |
+ FIELD_PREP(ADS112C14_DATA_RATE_CFG_FLTR_OSR,
+ channel_state->filter_osr));
+ if (ret)
+ return ret;
+
/*
* REVISIT: if we implement regulator support for the REFOUT pin, we
* might need to make this voltage match what is required by that. In
@@ -833,6 +968,45 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
*/
*val = div_s64((s64)(25 * 405 - 119500) * BIT(fsr_bits), vref_uV);
return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ struct ads112c14_channel_state *channel_state;
+ const int (*available)[2];
+ u8 i, j;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
+ j = channel_state->filter_osr;
+ available = data->sinc4_sample_rate_available[j];
+ i = channel_state->speed_mode;
+ break;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ j = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128;
+ available = data->sinc4_sinc1_sample_rate_available[j];
+ i = channel_state->speed_mode;
+ break;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS:
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
+ available = data->sinc4_sinc1_pf1_sample_rate_available;
+ i = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *val = available[i][0];
+ *val2 = available[i][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
+ guard(mutex)(&data->lock);
+
+ *val = ads112c14_get_osr(&data->channel_states[chan->scan_index]);
+ return IIO_VAL_INT;
+ }
default:
return -EINVAL;
}
@@ -843,6 +1017,9 @@ static int ads112c14_read_avail(struct iio_dev *indio_dev,
int *type, int *length, long mask)
{
struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_channel_state *channel_state;
+
+ channel_state = &data->channel_states[chan->scan_index];
switch (mask) {
case IIO_CHAN_INFO_SCALE:
@@ -868,6 +1045,58 @@ static int ads112c14_read_avail(struct iio_dev *indio_dev,
}
return -EINVAL;
+
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ guard(mutex)(&data->lock);
+
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
+ *vals = (const int *)data->sinc4_sample_rate_available[channel_state->filter_osr];
+ *length = 2 * ARRAY_SIZE(data->sinc4_sample_rate_available[0]);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ *vals = (const int *)data->sinc4_sinc1_sample_rate_available[channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128];
+ *length = 2 * ARRAY_SIZE(data->sinc4_sinc1_sample_rate_available[0]);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS...ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
+ *vals = (const int *)data->sinc4_sinc1_pf1_sample_rate_available;
+ *length = 2 * ARRAY_SIZE(data->sinc4_sinc1_pf1_sample_rate_available);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+ }
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
+ guard(mutex)(&data->lock);
+
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
+ *vals = ads112c14_sinc4_osr_available;
+ *length = ARRAY_SIZE(ads112c14_sinc4_osr_available);
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ *vals = ads112c14_sinc4_sinc1_osr_available;
+ *length = ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available);
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS:
+ *vals = ads112c14_sinc4_sinc1_pf1_25sps_osr_available;
+ *length = ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_25sps_osr_available);
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
+ *vals = ads112c14_sinc4_sinc1_pf1_20sps_osr_available;
+ *length = ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_20sps_osr_available);
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+ }
default:
return -EINVAL;
}
@@ -880,6 +1109,7 @@ static int ads112c14_write_raw(struct iio_dev *indio_dev,
struct ads112c14_data *data = iio_priv(indio_dev);
const int (*scale_avail)[2];
u8 *gain_val;
+ u32 i;
IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
if (IIO_DEV_ACQUIRE_FAILED(claim))
@@ -912,6 +1142,99 @@ static int ads112c14_write_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ struct ads112c14_channel_state *channel_state;
+ const int (*available)[2];
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ if (channel_state->filter_osr < ADS112C14_DATA_RATE_CFG_FLTR_OSR_128) {
+ u8 idx = channel_state->filter_osr;
+
+ available = data->sinc4_sample_rate_available[idx];
+ } else {
+ u8 idx = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128;
+
+ available = data->sinc4_sinc1_sample_rate_available[idx];
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ads112c14_fmod_div); i++) {
+ if (val == available[i][0] && val2 == available[i][1]) {
+ channel_state->speed_mode = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS:
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: {
+ available = data->sinc4_sinc1_pf1_sample_rate_available;
+
+ for (i = 0; i < ARRAY_SIZE(data->sinc4_sinc1_pf1_sample_rate_available); i++) {
+ if (val == available[i][0] && val2 == available[i][1]) {
+ channel_state->filter_osr = i + ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+ }
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
+ struct ads112c14_channel_state *channel_state;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
+ for (i = 0; i < ARRAY_SIZE(ads112c14_sinc4_osr_available); i++) {
+ if (val == ads112c14_sinc4_osr_available[i]) {
+ channel_state->filter_osr = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ for (i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available); i++) {
+ if (val == ads112c14_sinc4_sinc1_osr_available[i]) {
+ channel_state->filter_osr = i + ADS112C14_DATA_RATE_CFG_FLTR_OSR_128;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS:
+ for (i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_25sps_osr_available); i++) {
+ if (val == ads112c14_sinc4_sinc1_pf1_25sps_osr_available[i]) {
+ channel_state->speed_mode = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
+ for (i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_20sps_osr_available); i++) {
+ if (val == ads112c14_sinc4_sinc1_pf1_20sps_osr_available[i]) {
+ channel_state->speed_mode = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+ }
default:
return -EINVAL;
}
@@ -1200,11 +1523,98 @@ static ssize_t ads112c14_read_burnout_raw(struct iio_dev *indio_dev,
return sysfs_emit(buf, "%d\n", val);
}
+static int ads112c14_get_filter_type_from_state(struct ads112c14_channel_state *channel_state)
+{
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
+ return ADS112C14_FILTER_TYPE_SINC4;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ return ADS112C14_FILTER_TYPE_SINC4_SINC1;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS...ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
+ return ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ads112c14_set_filter_type(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ unsigned int val)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_channel_state *channel_state;
+ int ret;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ ret = ads112c14_get_filter_type_from_state(channel_state);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * channel_state->filter_osr affects multiple attributes, so don't modify
+ * it if the filter type is already set to the requested value.
+ */
+ if (ret == val)
+ return 0;
+
+ /* Otherwise, pick an arbitrary default for each type. */
+ switch (val) {
+ case ADS112C14_FILTER_TYPE_SINC4:
+ channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16;
+ break;
+ case ADS112C14_FILTER_TYPE_SINC4_SINC1:
+ channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_128;
+ break;
+ case ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1:
+ channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ads112c14_get_filter_type(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_channel_state *channel_state;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ return ads112c14_get_filter_type_from_state(channel_state);
+}
+
+static const struct iio_enum ads112c14_filter_type_enum = {
+ .items = ads112c14_filter_type_names,
+ .num_items = ARRAY_SIZE(ads112c14_filter_type_names),
+ .set = ads112c14_set_filter_type,
+ .get = ads112c14_get_filter_type,
+};
+
+static const struct iio_chan_spec_ext_info ads112c14_ext_info[] = {
+ IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
+ IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
+ { }
+};
+
static const struct iio_chan_spec_ext_info ads112c14_ext_info_burnout[] = {
{
.name = "burnoutraw",
.read = ads112c14_read_burnout_raw,
},
+ IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
+ IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
{ }
};
@@ -1230,7 +1640,7 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
struct ads112c14_data *data = iio_priv(indio_dev);
struct device *dev = indio_dev->dev.parent;
struct iio_chan_spec *channels;
- u32 num_child_nodes, i, pair[2];
+ u32 num_child_nodes, num_data_chans, i, pair[2];
int ret;
*need_avdd_ref = false;
@@ -1243,8 +1653,15 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
if (!data->measurements)
return -ENOMEM;
- channels = devm_kcalloc(dev, num_child_nodes +
- ARRAY_SIZE(ads112c14_sys_mon_channels) + 1,
+ num_data_chans = num_child_nodes + ARRAY_SIZE(ads112c14_sys_mon_channels);
+
+ data->channel_states = devm_kcalloc(dev, num_data_chans,
+ sizeof(*data->channel_states),
+ GFP_KERNEL);
+ if (!data->channel_states)
+ return -ENOMEM;
+
+ channels = devm_kcalloc(dev, num_data_chans + 1,
sizeof(*channels), GFP_KERNEL);
if (!channels)
return -ENOMEM;
@@ -1257,7 +1674,9 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
spec->indexed = 1;
spec->scan_index = i;
+ spec->ext_info = ads112c14_ext_info;
measurement->gain_val = 1;
+ data->channel_states[i].filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16;
if (fwnode_property_present(child, "label")) {
ret = fwnode_property_read_string(child, "label", &measurement->label);
@@ -1422,8 +1841,13 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
if (measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL)
*need_ext_ref = true;
- spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE);
- spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE);
+ spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO);
+ spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO);
/*
* If reference source is resistor rather than voltage supply,
@@ -1457,6 +1881,10 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
for (u32 j = 0; j < ARRAY_SIZE(ads112c14_sys_mon_channels); j++) {
struct iio_chan_spec *spec = &channels[i];
+ struct ads112c14_channel_state *channel_state;
+
+ channel_state = &data->channel_states[i];
+ channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16;
/* Update the template that was already copied with dynamic values. */
spec->scan_index = i;
@@ -1495,6 +1923,44 @@ static void ads112c14_populate_scale_available(int (*scale_avail)[2],
}
}
+static void ads112c14_populate_odr_tables(struct ads112c14_data *data)
+{
+ int *available;
+ u32 osr, fmod_Hz;
+ u64 odr_uHz;
+ u32 i, j;
+
+ for (i = 0; i < ARRAY_SIZE(ads112c14_sinc4_osr_available); i++) {
+ osr = ads112c14_sinc4_osr_available[i];
+
+ for (j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) {
+ fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j];
+ odr_uHz = div_u64((u64)fmod_Hz * MICRO, osr);
+ available = data->sinc4_sample_rate_available[i][j];
+ available[0] = div_u64_rem(odr_uHz, MICRO, &available[1]);
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available); i++) {
+ osr = ads112c14_sinc4_sinc1_osr_available[i];
+
+ for (j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) {
+ fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j];
+ odr_uHz = div_u64((u64)fmod_Hz * MICRO, osr);
+ available = data->sinc4_sinc1_sample_rate_available[i][j];
+ available[0] = div_u64_rem(odr_uHz, MICRO, &available[1]);
+ }
+ }
+
+ odr_uHz = div_u64((u64)25 * data->fclk_Hz * MICRO, ADS112C14_INTERNAL_CLK_Hz);
+ available = data->sinc4_sinc1_pf1_sample_rate_available[0];
+ available[0] = div_u64_rem(odr_uHz, MICRO, &available[1]);
+
+ odr_uHz = div_u64((u64)20 * data->fclk_Hz * MICRO, ADS112C14_INTERNAL_CLK_Hz);
+ available = data->sinc4_sinc1_pf1_sample_rate_available[1];
+ available[0] = div_u64_rem(odr_uHz, MICRO, &available[1]);
+}
+
static void ads112c14_populate_tables(struct ads112c14_data *data)
{
u32 full_scale, fsr_bits;
@@ -1532,6 +1998,7 @@ static void ads112c14_populate_tables(struct ads112c14_data *data)
ads112c14_populate_scale_available(data->sys_mon_chan_short_scale_available,
full_scale, fsr_bits);
+ ads112c14_populate_odr_tables(data);
}
static int ads112c14_probe(struct i2c_client *client)
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH v2 07/10] iio: adc: ti-ads112c14: add filter support
2026-09-04 22:09 ` [PATCH v2 07/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
@ 2026-09-06 1:41 ` Jonathan Cameron
0 siblings, 0 replies; 16+ messages in thread
From: Jonathan Cameron @ 2026-09-06 1:41 UTC (permalink / raw)
To: David Lechner (TI)
Cc: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Chris Hall,
Patrick Edwards, Kurt Borja, linux-iio, linux-kernel
> Add support for filter_type, oversampling_ratio and sampling_frequency
> attributes to the ti-ads112c14 driver.
>
> On these chips, these three controls are interdependent and the
> SPEED_MODE register value has a different meaning depending on the
> filter type, which makes the interactions a bit complex. As such, the
> expectation is that the user will set the filter type first, then
> depending on the filter type, either set the oversampling ratio or the
> sampling frequency and finally the other of these two.
>
> Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
As you probably saw already, sashiko had views.
A few other things from me.
Thanks
Jonathan
>
> diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
> index 55462fc57752..85e926184b6e 100644
> --- a/drivers/iio/adc/ti-ads112c14.c
> +++ b/drivers/iio/adc/ti-ads112c14.c
> @@ -92,6 +92,14 @@
> #define ADS112C14_DATA_RATE_CFG_DELAY GENMASK(7, 4)
> #define ADS112C14_DATA_RATE_CFG_GC_EN BIT(3)
> #define ADS112C14_DATA_RATE_CFG_FLTR_OSR GENMASK(2, 0)
> +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_16 0
> +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_32 1
> +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_128 2
> +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_256 3
> +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_512 4
> +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024 5
> +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS 6
> +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS 7
>
> #define ADS112C14_REG_MUX_CFG 0x07
> #define ADS112C14_MUX_CFG_AINP GENMASK(7, 4)
> @@ -181,6 +189,43 @@ static const u32 ads112c14_pga_gains_x10[] = {
>
> #define ADS112C14_INTERNAL_CLK_Hz 4096000
>
> +/* Index corresponds to first 2 ADS112C14_DATA_RATE_CFG_FLTR_OSR values. */
> +static const int ads112c14_sinc4_osr_available[] = {
> + 16, 32
> +};
> +
> +/* Index corresponds to next 4 ADS112C14_DATA_RATE_CFG_FLTR_OSR values. */
> +static const int ads112c14_sinc4_sinc1_osr_available[] = {
> + 128, 256, 512, 1024
> +};
You 'could' do the comment as maths in the [] =
but maybe it isn't worth it. Up to you.
> +
> +/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */
> +static const int ads112c14_sinc4_sinc1_pf1_20sps_osr_available[] = {
> + 1600, 12800, 25600, 51200
> +};
> +
> +/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */
> +static const int ads112c14_sinc4_sinc1_pf1_25sps_osr_available[] = {
> + 1280, 10240, 20480, 40960
> +};
> +
> +/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */
> +static const int ads112c14_fmod_div[] = {
> + 128, 16, 8, 4
> +};
Would be nice to index all these [] = ...
but given the 4 speed modes are called 0, 1, 2, 3
I'm not sure it would actually help much beyond maybe removing need
for the comments.
> +
> +enum ads112c14_filter_type {
> + ADS112C14_FILTER_TYPE_SINC4,
> + ADS112C14_FILTER_TYPE_SINC4_SINC1,
> + ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1,
> +};
> +
> +static const char * const ads112c14_filter_type_names[] = {
> + [ADS112C14_FILTER_TYPE_SINC4] = "sinc4",
> + [ADS112C14_FILTER_TYPE_SINC4_SINC1] = "sinc4+sinc1",
> + [ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1] = "sinc4+sinc1+pf1",
> +};
> +
> #define ADS112C14_I2C_CRC8_POLYNOMIAL 0x07
> DECLARE_CRC8_TABLE(ads112c14_crc8_table);
>
> @@ -880,6 +1109,7 @@ static int ads112c14_write_raw(struct iio_dev *indio_dev,
> struct ads112c14_data *data = iio_priv(indio_dev);
> const int (*scale_avail)[2];
> u8 *gain_val;
> + u32 i;
Similar to below, I'm not seeing a reason for this to be specifically
32 bits.
>
> IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> if (IIO_DEV_ACQUIRE_FAILED(claim))
> @@ -912,6 +1142,99 @@ static int ads112c14_write_raw(struct iio_dev *indio_dev,
>
> return -EINVAL;
> }
> + case IIO_CHAN_INFO_SAMP_FREQ: {
> + struct ads112c14_channel_state *channel_state;
> + const int (*available)[2];
> +
> + guard(mutex)(&data->lock);
> +
> + channel_state = &data->channel_states[chan->scan_index];
> +
> + switch (channel_state->filter_osr) {
> + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
> + if (channel_state->filter_osr < ADS112C14_DATA_RATE_CFG_FLTR_OSR_128) {
> + u8 idx = channel_state->filter_osr;
> +
> + available = data->sinc4_sample_rate_available[idx];
> + } else {
> + u8 idx = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128;
> +
> + available = data->sinc4_sinc1_sample_rate_available[idx];
Sashiko:
[Severity: Low]
Can this assignment cause a build failure when compiling with
-Werror=incompatible-pointer-types?
The variable available is declared as const int (*)[2], but the array indexing
of data->sinc4_sinc1_sample_rate_available[idx] yields an array that decays to
int (*)[2]. In C, assigning int (*)[2] to const int (*)[2] is an incompatible
pointer type mismatch without an explicit cast.
-
Seems correct that a cast is needed here or maybe drop the const
marking on the local variable?
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(ads112c14_fmod_div); i++) {
> + if (val == available[i][0] && val2 == available[i][1]) {
> + channel_state->speed_mode = i;
> + return 0;
> + }
> + }
> +
> + return -EINVAL;
> + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS:
> + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: {
> + available = data->sinc4_sinc1_pf1_sample_rate_available;
> +
> + for (i = 0; i < ARRAY_SIZE(data->sinc4_sinc1_pf1_sample_rate_available); i++) {
> + if (val == available[i][0] && val2 == available[i][1]) {
> + channel_state->filter_osr = i + ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS;
> + return 0;
> + }
> + }
> +
> + return -EINVAL;
> + }
> + default:
> + return -EINVAL;
> + }
> + }
> + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
> + struct ads112c14_channel_state *channel_state;
> +
> + guard(mutex)(&data->lock);
> +
> + channel_state = &data->channel_states[chan->scan_index];
> +
> + switch (channel_state->filter_osr) {
> + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
> + for (i = 0; i < ARRAY_SIZE(ads112c14_sinc4_osr_available); i++) {
> + if (val == ads112c14_sinc4_osr_available[i]) {
> + channel_state->filter_osr = i;
> + return 0;
> + }
> + }
> +
> + return -EINVAL;
> + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
> + for (i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available); i++) {
> + if (val == ads112c14_sinc4_sinc1_osr_available[i]) {
> + channel_state->filter_osr = i + ADS112C14_DATA_RATE_CFG_FLTR_OSR_128;
> + return 0;
> + }
> + }
> +
> + return -EINVAL;
> + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS:
> + for (i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_25sps_osr_available); i++) {
> + if (val == ads112c14_sinc4_sinc1_pf1_25sps_osr_available[i]) {
> + channel_state->speed_mode = i;
> + return 0;
> + }
> + }
> +
> + return -EINVAL;
> + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
> + for (i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_20sps_osr_available); i++) {
> + if (val == ads112c14_sinc4_sinc1_pf1_20sps_osr_available[i]) {
> + channel_state->speed_mode = i;
> + return 0;
> + }
> + }
> +
> + return -EINVAL;
> + default:
> + return -EINVAL;
> + }
> + }
> default:
> return -EINVAL;
> }
> @@ -1200,11 +1523,98 @@ static ssize_t ads112c14_read_burnout_raw(struct iio_dev *indio_dev,
> return sysfs_emit(buf, "%d\n", val);
> }
>
> +static int ads112c14_get_filter_type_from_state(struct ads112c14_channel_state *channel_state)
> +{
> + switch (channel_state->filter_osr) {
> + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
> + return ADS112C14_FILTER_TYPE_SINC4;
> + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
> + return ADS112C14_FILTER_TYPE_SINC4_SINC1;
> + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS...ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
> + return ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ads112c14_set_filter_type(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + unsigned int val)
> +{
> + struct ads112c14_data *data = iio_priv(indio_dev);
> + struct ads112c14_channel_state *channel_state;
> + int ret;
> +
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + guard(mutex)(&data->lock);
> +
> + channel_state = &data->channel_states[chan->scan_index];
> +
> + ret = ads112c14_get_filter_type_from_state(channel_state);
> + if (ret < 0)
> + return ret;
> +
> + /*
> + * channel_state->filter_osr affects multiple attributes, so don't modify
> + * it if the filter type is already set to the requested value.
> + */
> + if (ret == val)
> + return 0;
> +
> + /* Otherwise, pick an arbitrary default for each type. */
> + switch (val) {
> + case ADS112C14_FILTER_TYPE_SINC4:
> + channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16;
> + break;
> + case ADS112C14_FILTER_TYPE_SINC4_SINC1:
> + channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_128;
> + break;
> + case ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1:
> + channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int ads112c14_get_filter_type(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan)
> +{
> + struct ads112c14_data *data = iio_priv(indio_dev);
> + struct ads112c14_channel_state *channel_state;
> +
> + guard(mutex)(&data->lock);
> +
> + channel_state = &data->channel_states[chan->scan_index];
> +
> + return ads112c14_get_filter_type_from_state(channel_state);
> +}
> +
> +static const struct iio_enum ads112c14_filter_type_enum = {
> + .items = ads112c14_filter_type_names,
> + .num_items = ARRAY_SIZE(ads112c14_filter_type_names),
> + .set = ads112c14_set_filter_type,
> + .get = ads112c14_get_filter_type,
> +};
> +
> +static const struct iio_chan_spec_ext_info ads112c14_ext_info[] = {
> + IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
> + IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
> + { }
> +};
> +
> static const struct iio_chan_spec_ext_info ads112c14_ext_info_burnout[] = {
> {
> .name = "burnoutraw",
> .read = ads112c14_read_burnout_raw,
> },
> + IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
> + IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
> { }
> };
>
> @@ -1230,7 +1640,7 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
> struct ads112c14_data *data = iio_priv(indio_dev);
> struct device *dev = indio_dev->dev.parent;
> struct iio_chan_spec *channels;
> - u32 num_child_nodes, i, pair[2];
> + u32 num_child_nodes, num_data_chans, i, pair[2];
> int ret;
>
> *need_avdd_ref = false;
> @@ -1243,8 +1653,15 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
> if (!data->measurements)
> return -ENOMEM;
>
> - channels = devm_kcalloc(dev, num_child_nodes +
> - ARRAY_SIZE(ads112c14_sys_mon_channels) + 1,
> + num_data_chans = num_child_nodes + ARRAY_SIZE(ads112c14_sys_mon_channels);
> +
> + data->channel_states = devm_kcalloc(dev, num_data_chans,
> + sizeof(*data->channel_states),
> + GFP_KERNEL);
> + if (!data->channel_states)
> + return -ENOMEM;
> +
> + channels = devm_kcalloc(dev, num_data_chans + 1,
> sizeof(*channels), GFP_KERNEL);
> if (!channels)
> return -ENOMEM;
> @@ -1257,7 +1674,9 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
>
> spec->indexed = 1;
> spec->scan_index = i;
> + spec->ext_info = ads112c14_ext_info;
> measurement->gain_val = 1;
> + data->channel_states[i].filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16;
>
> if (fwnode_property_present(child, "label")) {
> ret = fwnode_property_read_string(child, "label", &measurement->label);
> @@ -1422,8 +1841,13 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
> if (measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL)
> *need_ext_ref = true;
>
> - spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE);
> - spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE);
> + spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> + BIT(IIO_CHAN_INFO_SCALE) |
> + BIT(IIO_CHAN_INFO_SAMP_FREQ) |
> + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO);
> + spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE) |
> + BIT(IIO_CHAN_INFO_SAMP_FREQ) |
> + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO);
>
> /*
> * If reference source is resistor rather than voltage supply,
> @@ -1457,6 +1881,10 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
>
> for (u32 j = 0; j < ARRAY_SIZE(ads112c14_sys_mon_channels); j++) {
> struct iio_chan_spec *spec = &channels[i];
> + struct ads112c14_channel_state *channel_state;
> +
> + channel_state = &data->channel_states[i];
> + channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16;
>
> /* Update the template that was already copied with dynamic values. */
> spec->scan_index = i;
> @@ -1495,6 +1923,44 @@ static void ads112c14_populate_scale_available(int (*scale_avail)[2],
> }
> }
>
> +static void ads112c14_populate_odr_tables(struct ads112c14_data *data)
> +{
> + int *available;
> + u32 osr, fmod_Hz;
> + u64 odr_uHz;
> + u32 i, j;
For these I'd use a bare unsigned int as no particular
reason I can see for forcing 32 bit nature. Even though it would be
duplication I'd probably declare the each time as local loop
iterators as well.
> +
> + for (i = 0; i < ARRAY_SIZE(ads112c14_sinc4_osr_available); i++) {
> + osr = ads112c14_sinc4_osr_available[i];
> +
> + for (j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) {
> + fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j];
> + odr_uHz = div_u64((u64)fmod_Hz * MICRO, osr);
> + available = data->sinc4_sample_rate_available[i][j];
> + available[0] = div_u64_rem(odr_uHz, MICRO, &available[1]);
Sashiko: (b4 review emacs stuff eats source of coments when I adopt
them - I should figure out how fix that and send a patch!)
[Severity: Low]
Will this call to div_u64_rem() cause a compiler warning or error for
incompatible pointer types?
The local variable available is an int pointer, so &available[1] is also of type
int pointer. However, the third argument of div_u64_rem() expects a u32 pointer
for the remainder. Passing an int pointer to a u32 pointer will trigger a
pointer type mismatch warning.
-
Use a local variable here and in all other places this applies.
--
Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 08/10] iio: ABI: add sinc4+sinc1+pf1 filter_type
2026-09-04 22:09 [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
` (6 preceding siblings ...)
2026-09-04 22:09 ` [PATCH v2 07/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
@ 2026-09-04 22:09 ` David Lechner (TI)
2026-09-04 22:09 ` [PATCH v2 09/10] iio: adc: ti-ads112c14: add settlingtime attribute David Lechner (TI)
` (2 subsequent siblings)
10 siblings, 0 replies; 16+ messages in thread
From: David Lechner (TI) @ 2026-09-04 22:09 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko
Cc: Chris Hall, Patrick Edwards, Kurt Borja, linux-iio, linux-kernel,
David Lechner (TI)
Document "sinc4+sinc1+pf1" as a valid value for the filter_type
attributes (used by the ti-ads112c14 driver).
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
Documentation/ABI/testing/sysfs-bus-iio | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio
index a4f5595722ad..a8e1cb8e7490 100644
--- a/Documentation/ABI/testing/sysfs-bus-iio
+++ b/Documentation/ABI/testing/sysfs-bus-iio
@@ -2380,6 +2380,7 @@ Description:
* "sinc4+lp" - Sinc4 + Low Pass Filter.
* "sinc4+sinc1" - Sinc4 + averaging by 8. Low 1st conversion
time.
+ * "sinc4+sinc1+pf1" - Sinc4 + Sinc1 + device specific Post Filter 1.
* "sinc4+rej60" - Sinc4 + 60Hz rejection.
* "sinc5" - The digital sinc5 filter. Excellent noise
performance
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 09/10] iio: adc: ti-ads112c14: add settlingtime attribute
2026-09-04 22:09 [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
` (7 preceding siblings ...)
2026-09-04 22:09 ` [PATCH v2 08/10] iio: ABI: add sinc4+sinc1+pf1 filter_type David Lechner (TI)
@ 2026-09-04 22:09 ` David Lechner (TI)
2026-09-06 1:41 ` Jonathan Cameron
2026-09-04 22:09 ` [PATCH v2 10/10] iio: ABI: add settlingtime attributes David Lechner (TI)
2026-09-06 0:36 ` [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support Jonathan Cameron
10 siblings, 1 reply; 16+ messages in thread
From: David Lechner (TI) @ 2026-09-04 22:09 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko
Cc: Chris Hall, Patrick Edwards, Kurt Borja, linux-iio, linux-kernel,
David Lechner (TI)
Add per-channel sysfs attributes for settlingtime and
settlingtime_available. These allow adjusting the total settling time
for each channel. The value consists of a fixed t_latency time (based
on the selected filter_type, oversampling_ratio and sampling_frequency)
plus a user-configurable t_delay that determines the value to write to
the DELAY field in the registers.
The allowable values are non-linear integer multiples, so the step size
is just the smallest step size. Writing the attribute will match the
closest matching value for the DELAY field with a time equal to or
greater than the requested settling time (unless the requested time is
larger than the maximum allowable settling time).
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
drivers/iio/adc/ti-ads112c14.c | 337 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 331 insertions(+), 6 deletions(-)
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index 85e926184b6e..cefbb7afe729 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -214,6 +214,24 @@ static const int ads112c14_fmod_div[] = {
128, 16, 8, 4
};
+#define ADS112C14_DELAY_MAX FIELD_MAX(ADS112C14_DATA_RATE_CFG_DELAY)
+
+/* Table 7-6 latency in t_MOD for OSR [16, 32, 128, 256, 512, 1024]. */
+static const int ads112c14_sinc_latency_tmod[][ARRAY_SIZE(ads112c14_fmod_div)] = {
+ { 80, 88, 88, 104 },
+ { 144, 152, 152, 168 },
+ { 240, 248, 248, 264 },
+ { 368, 376, 376, 392 },
+ { 624, 632, 632, 648 },
+ { 1136, 1144, 1144, 1160 },
+};
+
+/* Table 7-7 latency in t_MOD for output data rates [25SPS, 20SPS]. */
+static const int ads112c14_fir_latency_tmod[][ARRAY_SIZE(ads112c14_fmod_div)] = {
+ { 1416, 10384, 20624, 41120 },
+ { 1736, 12944, 25744, 51360 },
+};
+
enum ads112c14_filter_type {
ADS112C14_FILTER_TYPE_SINC4,
ADS112C14_FILTER_TYPE_SINC4_SINC1,
@@ -340,6 +358,7 @@ struct ads112c14_measurement {
struct ads112c14_channel_state {
u8 speed_mode;
u8 filter_osr;
+ u8 delay;
};
struct ads112c14_data {
@@ -366,6 +385,8 @@ struct ads112c14_data {
int sinc4_sample_rate_available[ARRAY_SIZE(ads112c14_sinc4_osr_available)][ARRAY_SIZE(ads112c14_fmod_div)][2];
int sinc4_sinc1_sample_rate_available[ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available)][ARRAY_SIZE(ads112c14_fmod_div)][2];
int sinc4_sinc1_pf1_sample_rate_available[2][2];
+ int sinc_settling_time_range_available[ARRAY_SIZE(ads112c14_sinc_latency_tmod)][ARRAY_SIZE(ads112c14_fmod_div)][3][2];
+ int fir_settling_time_range_available[ARRAY_SIZE(ads112c14_fir_latency_tmod)][ARRAY_SIZE(ads112c14_fmod_div)][3][2];
IIO_DECLARE_BUFFER_WITH_TS(__be32, scan, ADS112C14_MAX_MEASUREMENT_CHANNELS +
ARRAY_SIZE(ads112c14_sys_mon_channels));
};
@@ -573,6 +594,211 @@ static int ads112c14_get_osr(struct ads112c14_channel_state *channel_state)
}
}
+static int ads112c14_get_fmod_Hz(struct ads112c14_data *data,
+ struct ads112c14_channel_state *channel_state)
+{
+ return data->fclk_Hz / ads112c14_fmod_div[channel_state->speed_mode];
+}
+
+static int ads112c14_delay_to_tmod(u8 delay)
+{
+ if (!delay)
+ return 0;
+
+ return BIT(delay - 1);
+}
+
+static int ads112c14_get_latency_tmod(struct ads112c14_channel_state *channel_state)
+{
+ u8 speed_mode = channel_state->speed_mode;
+ u8 filter_osr = channel_state->filter_osr;
+
+ if (speed_mode >= ARRAY_SIZE(ads112c14_fmod_div))
+ return -EINVAL;
+
+ if (filter_osr <= ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024)
+ return ads112c14_sinc_latency_tmod[filter_osr][speed_mode];
+
+ if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS)
+ return ads112c14_fir_latency_tmod[0][speed_mode];
+
+ if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS)
+ return ads112c14_fir_latency_tmod[1][speed_mode];
+
+ return -EINVAL;
+}
+
+static int ads112c14_get_settling_time_us(struct ads112c14_data *data,
+ struct ads112c14_channel_state *channel_state,
+ u8 delay, u32 *settling_time_us)
+{
+ int fmod_Hz, latency_tmod;
+ u64 total_tmod;
+
+ fmod_Hz = ads112c14_get_fmod_Hz(data, channel_state);
+ if (fmod_Hz <= 0)
+ return -EINVAL;
+
+ latency_tmod = ads112c14_get_latency_tmod(channel_state);
+ if (latency_tmod < 0)
+ return latency_tmod;
+
+ total_tmod = latency_tmod + ads112c14_delay_to_tmod(delay);
+ *settling_time_us = div64_u64(total_tmod * USEC_PER_SEC, fmod_Hz);
+
+ return 0;
+}
+
+static int ads112c14_find_delay_for_settling_time_us(struct ads112c14_data *data,
+ struct ads112c14_channel_state *channel_state,
+ s64 settling_time_us, u8 *delay)
+{
+ u32 fixed_latency_us, delay_us;
+ u64 delay_tmod_needed;
+ int ret, fmod_Hz;
+ u8 i;
+
+ ret = ads112c14_get_settling_time_us(data, channel_state, 0, &fixed_latency_us);
+ if (ret)
+ return ret;
+
+ if (settling_time_us <= fixed_latency_us) {
+ *delay = 0;
+ return 0;
+ }
+
+ fmod_Hz = ads112c14_get_fmod_Hz(data, channel_state);
+ if (fmod_Hz <= 0)
+ return -EINVAL;
+
+ delay_us = settling_time_us - fixed_latency_us;
+ delay_tmod_needed = DIV_ROUND_UP_ULL((u64)delay_us * fmod_Hz,
+ USEC_PER_SEC);
+
+ for (i = 1; i < ADS112C14_DELAY_MAX; i++) {
+ if (ads112c14_delay_to_tmod(i) >= delay_tmod_needed)
+ break;
+ }
+
+ *delay = i;
+
+ return 0;
+}
+
+static ssize_t ads112c14_read_settling_time(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_channel_state *channel_state;
+ u32 settling_time_us;
+ int vals[2];
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ ret = ads112c14_get_settling_time_us(data, channel_state,
+ channel_state->delay,
+ &settling_time_us);
+ if (ret)
+ return ret;
+
+ iio_val_s64_decompose(settling_time_us, &vals[0], &vals[1]);
+
+ return iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(vals), vals);
+}
+
+static ssize_t ads112c14_write_settling_time(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_channel_state *channel_state;
+ s64 settling_time_us;
+ int integer;
+ int fract;
+ u8 delay;
+ int ret;
+
+ ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract);
+ if (ret)
+ return ret;
+
+ settling_time_us = integer * MICRO + fract;
+ if (settling_time_us < 0)
+ return -EINVAL;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ ret = ads112c14_find_delay_for_settling_time_us(data, channel_state,
+ settling_time_us, &delay);
+ if (ret)
+ return ret;
+
+ channel_state->delay = delay;
+
+ return len;
+}
+
+static ssize_t ads112c14_read_settling_time_available(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_channel_state *channel_state;
+ u8 filter_osr, speed_mode;
+ const int (*range)[2];
+ size_t len = 0;
+ int i;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ filter_osr = channel_state->filter_osr;
+ speed_mode = channel_state->speed_mode;
+
+ if (speed_mode >= ARRAY_SIZE(ads112c14_fmod_div))
+ return -EINVAL;
+
+ if (filter_osr <= ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024)
+ range = data->sinc_settling_time_range_available[filter_osr][speed_mode];
+ else if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS)
+ range = data->fir_settling_time_range_available[0][speed_mode];
+ else if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS)
+ range = data->fir_settling_time_range_available[1][speed_mode];
+ else
+ return -EINVAL;
+
+ len += sysfs_emit_at(buf, len, "[");
+ for (i = 0; i < 3; i++) {
+ s64 range_val;
+ s32 int_val, rem;
+
+ range_val = iio_val_s64_compose(range[i][0], range[i][1]);
+ int_val = div_s64_rem(range_val, MICRO, &rem);
+
+ if (i)
+ len += sysfs_emit_at(buf, len, " ");
+
+ len += sysfs_emit_at(buf, len, "%d.%06d", int_val, rem);
+ }
+ len += sysfs_emit_at(buf, len, "]\n");
+
+ return len;
+}
+
static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
const struct iio_chan_spec *chan,
bool en_burnout)
@@ -637,8 +863,11 @@ static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
return ret;
ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG,
+ ADS112C14_DATA_RATE_CFG_DELAY |
ADS112C14_DATA_RATE_CFG_GC_EN |
ADS112C14_DATA_RATE_CFG_FLTR_OSR,
+ FIELD_PREP(ADS112C14_DATA_RATE_CFG_DELAY,
+ channel_state->delay) |
FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN,
(measurement->global_chop &&
!en_burnout) ? 1 : 0) |
@@ -726,8 +955,11 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
return ret;
ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG,
+ ADS112C14_DATA_RATE_CFG_DELAY |
ADS112C14_DATA_RATE_CFG_GC_EN |
ADS112C14_DATA_RATE_CFG_FLTR_OSR,
+ FIELD_PREP(ADS112C14_DATA_RATE_CFG_DELAY,
+ channel_state->delay) |
FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN, 0) |
FIELD_PREP(ADS112C14_DATA_RATE_CFG_FLTR_OSR,
channel_state->filter_osr));
@@ -786,7 +1018,8 @@ static int ads112c14_scan_read(struct ads112c14_data *data, u8 *buf)
return 0;
}
-static int ads112c14_wait_for_conversion_irq(struct ads112c14_data *data)
+static int ads112c14_wait_for_conversion_irq(struct ads112c14_data *data,
+ u32 settle_time_us)
{
unsigned long remaining;
int ret;
@@ -798,13 +1031,16 @@ static int ads112c14_wait_for_conversion_irq(struct ads112c14_data *data)
if (ret)
return ret;
+ /* Give it 1ms more than calculated settling time. */
remaining = wait_for_completion_timeout(&data->drdy_completion,
- msecs_to_jiffies(100));
+ usecs_to_jiffies(settle_time_us +
+ 1 * USEC_PER_MSEC));
return remaining ? 0 : -ETIMEDOUT;
}
-static int ads112c14_wait_for_conversion_poll(struct ads112c14_data *data)
+static int ads112c14_wait_for_conversion_poll(struct ads112c14_data *data,
+ u32 settle_time_us)
{
u32 reg_val;
int ret;
@@ -814,10 +1050,12 @@ static int ads112c14_wait_for_conversion_poll(struct ads112c14_data *data)
if (ret)
return ret;
+ /* Give it 1ms more than calculated settling time. */
return regmap_read_poll_timeout(data->regmap,
ADS112C14_REG_STATUS_MSB, reg_val,
FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val),
- 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC);
+ 1 * USEC_PER_MSEC, settle_time_us +
+ 1 * USEC_PER_MSEC);
}
static int ads112c14_single_conversion(struct ads112c14_data *data,
@@ -825,6 +1063,8 @@ static int ads112c14_single_conversion(struct ads112c14_data *data,
u8 *buf, bool en_burnout, bool for_scan)
{
struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap));
+ struct ads112c14_channel_state *channel_state;
+ u32 settle_time_us;
int ret;
guard(mutex)(&data->lock);
@@ -833,10 +1073,26 @@ static int ads112c14_single_conversion(struct ads112c14_data *data,
if (ret)
return ret;
+ channel_state = &data->channel_states[chan->scan_index];
+ ret = ads112c14_get_settling_time_us(data, channel_state,
+ channel_state->delay,
+ &settle_time_us);
+ if (ret)
+ return ret;
+
+ ret = regmap_test_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG,
+ ADS112C14_DATA_RATE_CFG_GC_EN);
+ if (ret < 0)
+ return ret;
+
+ /* Input chopping doubles the settling time. */
+ if (ret)
+ settle_time_us *= 2;
+
if (data->drdy_irq)
- ret = ads112c14_wait_for_conversion_irq(data);
+ ret = ads112c14_wait_for_conversion_irq(data, settle_time_us);
else
- ret = ads112c14_wait_for_conversion_poll(data);
+ ret = ads112c14_wait_for_conversion_poll(data, settle_time_us);
if (ret)
return ret;
@@ -1603,6 +1859,17 @@ static const struct iio_enum ads112c14_filter_type_enum = {
};
static const struct iio_chan_spec_ext_info ads112c14_ext_info[] = {
+ {
+ .name = "settlingtime",
+ .read = ads112c14_read_settling_time,
+ .write = ads112c14_write_settling_time,
+ .shared = IIO_SEPARATE,
+ },
+ {
+ .name = "settlingtime_available",
+ .read = ads112c14_read_settling_time_available,
+ .shared = IIO_SEPARATE,
+ },
IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
{ }
@@ -1613,6 +1880,17 @@ static const struct iio_chan_spec_ext_info ads112c14_ext_info_burnout[] = {
.name = "burnoutraw",
.read = ads112c14_read_burnout_raw,
},
+ {
+ .name = "settlingtime",
+ .read = ads112c14_read_settling_time,
+ .write = ads112c14_write_settling_time,
+ .shared = IIO_SEPARATE,
+ },
+ {
+ .name = "settlingtime_available",
+ .read = ads112c14_read_settling_time_available,
+ .shared = IIO_SEPARATE,
+ },
IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
{ }
@@ -1961,6 +2239,52 @@ static void ads112c14_populate_odr_tables(struct ads112c14_data *data)
available[0] = div_u64_rem(odr_uHz, MICRO, &available[1]);
}
+static void ads112c14_populate_settling_range_tables(struct ads112c14_data *data)
+{
+ s32 (*avail)[2];
+ u32 i, j;
+
+ for (i = 0; i < ARRAY_SIZE(ads112c14_sinc_latency_tmod); i++) {
+ for (j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) {
+ u64 fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j];
+ u64 start_tmod = ads112c14_sinc_latency_tmod[i][j];
+ u64 step_tmod = ads112c14_delay_to_tmod(1);
+ u64 stop_tmod = start_tmod + ads112c14_delay_to_tmod(ADS112C14_DELAY_MAX);
+ s64 start_us, step_us, stop_us;
+
+ start_us = DIV_ROUND_CLOSEST_ULL(start_tmod * USEC_PER_SEC, fmod_Hz);
+ step_us = DIV_ROUND_CLOSEST_ULL(step_tmod * USEC_PER_SEC, fmod_Hz);
+ stop_us = DIV_ROUND_CLOSEST_ULL(stop_tmod * USEC_PER_SEC, fmod_Hz);
+
+ avail = data->sinc_settling_time_range_available[i][j];
+
+ iio_val_s64_decompose(start_us, &avail[0][0], &avail[0][1]);
+ iio_val_s64_decompose(step_us, &avail[1][0], &avail[1][1]);
+ iio_val_s64_decompose(stop_us, &avail[2][0], &avail[2][1]);
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ads112c14_fir_latency_tmod); i++) {
+ for (j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) {
+ u64 fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j];
+ u64 start_tmod = ads112c14_fir_latency_tmod[i][j];
+ u64 step_tmod = ads112c14_delay_to_tmod(1);
+ u64 stop_tmod = start_tmod + ads112c14_delay_to_tmod(ADS112C14_DELAY_MAX);
+ s64 start_us, step_us, stop_us;
+
+ start_us = DIV_ROUND_CLOSEST_ULL(start_tmod * USEC_PER_SEC, fmod_Hz);
+ step_us = DIV_ROUND_CLOSEST_ULL(step_tmod * USEC_PER_SEC, fmod_Hz);
+ stop_us = DIV_ROUND_CLOSEST_ULL(stop_tmod * USEC_PER_SEC, fmod_Hz);
+
+ avail = data->fir_settling_time_range_available[i][j];
+
+ iio_val_s64_decompose(start_us, &avail[0][0], &avail[0][1]);
+ iio_val_s64_decompose(step_us, &avail[1][0], &avail[1][1]);
+ iio_val_s64_decompose(stop_us, &avail[2][0], &avail[2][1]);
+ }
+ }
+}
+
static void ads112c14_populate_tables(struct ads112c14_data *data)
{
u32 full_scale, fsr_bits;
@@ -1999,6 +2323,7 @@ static void ads112c14_populate_tables(struct ads112c14_data *data)
ads112c14_populate_scale_available(data->sys_mon_chan_short_scale_available,
full_scale, fsr_bits);
ads112c14_populate_odr_tables(data);
+ ads112c14_populate_settling_range_tables(data);
}
static int ads112c14_probe(struct i2c_client *client)
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH v2 09/10] iio: adc: ti-ads112c14: add settlingtime attribute
2026-09-04 22:09 ` [PATCH v2 09/10] iio: adc: ti-ads112c14: add settlingtime attribute David Lechner (TI)
@ 2026-09-06 1:41 ` Jonathan Cameron
0 siblings, 0 replies; 16+ messages in thread
From: Jonathan Cameron @ 2026-09-06 1:41 UTC (permalink / raw)
To: David Lechner (TI)
Cc: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Chris Hall,
Patrick Edwards, Kurt Borja, linux-iio, linux-kernel
> Add per-channel sysfs attributes for settlingtime and
> settlingtime_available. These allow adjusting the total settling time
> for each channel. The value consists of a fixed t_latency time (based
> on the selected filter_type, oversampling_ratio and sampling_frequency)
> plus a user-configurable t_delay that determines the value to write to
> the DELAY field in the registers.
>
> The allowable values are non-linear integer multiples, so the step size
> is just the smallest step size. Writing the attribute will match the
> closest matching value for the DELAY field with a time equal to or
> greater than the requested settling time (unless the requested time is
> larger than the maximum allowable settling time).
>
> Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
A few things in here - mostly from sashiko rather than me.
>
> diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
> index 85e926184b6e..cefbb7afe729 100644
> --- a/drivers/iio/adc/ti-ads112c14.c
> +++ b/drivers/iio/adc/ti-ads112c14.c
...
> +static int ads112c14_get_settling_time_us(struct ads112c14_data *data,
> + struct ads112c14_channel_state *channel_state,
> + u8 delay, u32 *settling_time_us)
> +{
> + int fmod_Hz, latency_tmod;
> + u64 total_tmod;
> +
> + fmod_Hz = ads112c14_get_fmod_Hz(data, channel_state);
> + if (fmod_Hz <= 0)
> + return -EINVAL;
[Severity: Medium]
Is it possible for negative inputs to bypass this validation check on 32-bit
architectures? MICRO is typically defined as 1000000UL. If integer is
negative (e.g., -1), the calculation integer * MICRO evaluates as an
unsigned 32-bit operation, producing a huge unsigned 32-bit value.
When this is assigned to the 64-bit signed integer settling_time_us, it is
zero-extended to a large positive value, rendering the < 0 check useless
and resulting in incorrect timing delay configurations.
-
I'm not sure on this one (and too lazy to check properly).
My guess is not a problem due to bounds on values from elsewhere but
please take a look.
> +
> + latency_tmod = ads112c14_get_latency_tmod(channel_state);
> + if (latency_tmod < 0)
> + return latency_tmod;
> +
> + total_tmod = latency_tmod + ads112c14_delay_to_tmod(delay);
> + *settling_time_us = div64_u64(total_tmod * USEC_PER_SEC, fmod_Hz);
> +
> + return 0;
> +}
> +
> +static int ads112c14_find_delay_for_settling_time_us(struct ads112c14_data *data,
> + struct ads112c14_channel_state *channel_state,
> + s64 settling_time_us, u8 *delay)
> +{
> + u32 fixed_latency_us, delay_us;
> + u64 delay_tmod_needed;
> + int ret, fmod_Hz;
> + u8 i;
> +
> + ret = ads112c14_get_settling_time_us(data, channel_state, 0, &fixed_latency_us);
> + if (ret)
> + return ret;
> +
> + if (settling_time_us <= fixed_latency_us) {
> + *delay = 0;
> + return 0;
> + }
> +
> + fmod_Hz = ads112c14_get_fmod_Hz(data, channel_state);
> + if (fmod_Hz <= 0)
> + return -EINVAL;
> +
> + delay_us = settling_time_us - fixed_latency_us;
Sashiko:
[Severity: Medium]
Can this assignment truncate large settling times? delay_us is declared as
a 32-bit unsigned integer, while settling_time_us is a 64-bit signed
integer. If userspace writes a sufficiently large time, the subtraction will
wrap around the 32-bit boundary, resulting in a short, unintended delay
instead of saturating to the hardware's maximum delay limit.
-
The input comes from val * MICRO + val2 so seems like overflow is
plausible.
> + delay_tmod_needed = DIV_ROUND_UP_ULL((u64)delay_us * fmod_Hz,
> + USEC_PER_SEC);
> +
> + for (i = 1; i < ADS112C14_DELAY_MAX; i++) {
> + if (ads112c14_delay_to_tmod(i) >= delay_tmod_needed)
> + break;
> + }
> +
> + *delay = i;
> +
> + return 0;
> +}
> +
> @@ -814,10 +1050,12 @@ static int ads112c14_wait_for_conversion_poll(struct ads112c14_data *data)
> if (ret)
> return ret;
>
> + /* Give it 1ms more than calculated settling time. */
> return regmap_read_poll_timeout(data->regmap,
> ADS112C14_REG_STATUS_MSB, reg_val,
> FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val),
> - 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC);
> + 1 * USEC_PER_MSEC, settle_time_us +
> + 1 * USEC_PER_MSEC);
Rewrap this:
1 * USEC_PER_MSEC,
settle_time_us + 1 * USEC_PER_MSEC);
Is nicer.
> }
>
> @@ -1961,6 +2239,52 @@ static void ads112c14_populate_odr_tables(struct ads112c14_data *data)
> available[0] = div_u64_rem(odr_uHz, MICRO, &available[1]);
> }
>
> +static void ads112c14_populate_settling_range_tables(struct ads112c14_data *data)
> +{
> + s32 (*avail)[2];
> + u32 i, j;
> +
> + for (i = 0; i < ARRAY_SIZE(ads112c14_sinc_latency_tmod); i++) {
> + for (j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) {
> + u64 fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j];
> + u64 start_tmod = ads112c14_sinc_latency_tmod[i][j];
> + u64 step_tmod = ads112c14_delay_to_tmod(1);
> + u64 stop_tmod = start_tmod + ads112c14_delay_to_tmod(ADS112C14_DELAY_MAX);
> + s64 start_us, step_us, stop_us;
> +
> + start_us = DIV_ROUND_CLOSEST_ULL(start_tmod * USEC_PER_SEC, fmod_Hz);
Your friendly neighbourhood Sashiko:
[Severity: High]
Does this code risk a division by zero crash during probe?
The external clock rate data->fclk_Hz is fetched without asserting that the
rate is nonzero or sufficiently large. If clk_get_rate() returns 0 (e.g. from
a faulty, missing, or zero-initialized clock in the device tree), fmod_Hz
becomes 0.
The DIV_ROUND_CLOSEST_ULL macro will then invoke do_div with a zero divisor,
triggering a hardware exception and crashing the kernel.
-
Probably should defend against this but just checking it isn't zero
when we first get it.
--
Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 10/10] iio: ABI: add settlingtime attributes
2026-09-04 22:09 [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
` (8 preceding siblings ...)
2026-09-04 22:09 ` [PATCH v2 09/10] iio: adc: ti-ads112c14: add settlingtime attribute David Lechner (TI)
@ 2026-09-04 22:09 ` David Lechner (TI)
2026-09-06 1:41 ` Jonathan Cameron
2026-09-06 0:36 ` [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support Jonathan Cameron
10 siblings, 1 reply; 16+ messages in thread
From: David Lechner (TI) @ 2026-09-04 22:09 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko
Cc: Chris Hall, Patrick Edwards, Kurt Borja, linux-iio, linux-kernel,
David Lechner (TI)
Add new generic attributes for input settling time introduced in the
ti-ads122c14 driver.
Usually, in IIO the output data rate is 1 / sampling_frequency. There
are known devices (generally ones with filtering and oversampling) where
the first conversion takes longer than 1 / sampling_frequency to allow
the input to settle in order to have a valid conversion result. Then,
any subsequent conversions in a continuous sampling mode would be done
at the regular interval defined by 1 / sampling_frequency.
This attribute is intended to allow controlling that settling time
duration.
A device that has input chopping enabled would see this settling time on
every conversion since the device setting (i.e. the input mux) changes
on every conversion.
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
Documentation/ABI/testing/sysfs-bus-iio | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio
index a8e1cb8e7490..045589058655 100644
--- a/Documentation/ABI/testing/sysfs-bus-iio
+++ b/Documentation/ABI/testing/sysfs-bus-iio
@@ -2402,6 +2402,32 @@ Description:
Specifies which filter type apply to the channel. The possible
values are given by the filter_type_available attribute.
+What: /sys/bus/iio/devices/iio:deviceX/in_resistanceY_settlingtime
+What: /sys/bus/iio/devices/iio:deviceX/in_tempY_settlingtime
+What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_settlingtime
+What: /sys/bus/iio/devices/iio:deviceX/in_voltageY-voltageZ_settlingtime
+KernelVersion: 7.4
+Contact: linux-iio@vger.kernel.org
+Description:
+ Set/read total settling time in seconds for a device to settle
+ after starting the first conversion after any settings have
+ changed. The data for the conversion is outputted at the end of
+ this time.
+
+ Depending on the device and how it is used, this settling time
+ may apply to more than just the first conversion, e.g. to every
+ conversion in a single-shot conversion mode or when a device
+ setting changes after each conversion (input chopping).
+
+What: /sys/bus/iio/devices/iio:deviceX/in_resistanceY_settlingtime_available
+What: /sys/bus/iio/devices/iio:deviceX/in_tempY_settlingtime_available
+What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_settlingtime_available
+What: /sys/bus/iio/devices/iio:deviceX/in_voltageY-voltageZ_settlingtime_available
+KernelVersion: 7.4
+Contact: linux-iio@vger.kernel.org
+Description:
+ Available settlingtime values in IIO range or list format.
+
What: /sys/.../events/in_proximity_thresh_either_runningperiod
KernelVersion: 6.6
Contact: linux-iio@vger.kernel.org
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH v2 10/10] iio: ABI: add settlingtime attributes
2026-09-04 22:09 ` [PATCH v2 10/10] iio: ABI: add settlingtime attributes David Lechner (TI)
@ 2026-09-06 1:41 ` Jonathan Cameron
0 siblings, 0 replies; 16+ messages in thread
From: Jonathan Cameron @ 2026-09-06 1:41 UTC (permalink / raw)
To: David Lechner (TI)
Cc: Nuno Sá, Andy Shevchenko, Chris Hall, Patrick Edwards,
Kurt Borja, linux-iio, linux-kernel
On Fri, 04 Sep 2026 17:09:55 -0500
"David Lechner (TI)" <dlechner@baylibre.com> wrote:
> Add new generic attributes for input settling time introduced in the
> ti-ads122c14 driver.
>
> Usually, in IIO the output data rate is 1 / sampling_frequency. There
> are known devices (generally ones with filtering and oversampling) where
> the first conversion takes longer than 1 / sampling_frequency to allow
> the input to settle in order to have a valid conversion result. Then,
> any subsequent conversions in a continuous sampling mode would be done
> at the regular interval defined by 1 / sampling_frequency.
>
> This attribute is intended to allow controlling that settling time
> duration.
>
> A device that has input chopping enabled would see this settling time on
> every conversion since the device setting (i.e. the input mux) changes
> on every conversion.
>
> Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
One query inline. I'd like a bit in the commit description on that
mostly because I'm failing to remember the answer and there isn't
an obvious right answer.
Jonathan
> ---
> Documentation/ABI/testing/sysfs-bus-iio | 26 ++++++++++++++++++++++++++
> 1 file changed, 26 insertions(+)
>
> diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio
> index a8e1cb8e7490..045589058655 100644
> --- a/Documentation/ABI/testing/sysfs-bus-iio
> +++ b/Documentation/ABI/testing/sysfs-bus-iio
> @@ -2402,6 +2402,32 @@ Description:
> Specifies which filter type apply to the channel. The possible
> values are given by the filter_type_available attribute.
>
> +What: /sys/bus/iio/devices/iio:deviceX/in_resistanceY_settlingtime
> +What: /sys/bus/iio/devices/iio:deviceX/in_tempY_settlingtime
> +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_settlingtime
> +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY-voltageZ_settlingtime
> +KernelVersion: 7.4
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Set/read total settling time in seconds for a device to settle
> + after starting the first conversion after any settings have
> + changed. The data for the conversion is outputted at the end of
> + this time.
This probably came up in earlier discussions and I've forgotten it but why
is this not the difference between the time needed for first sample and
that needed for subsequent. That's the only bit that is settling related rather
than acquisition time.
> +
> + Depending on the device and how it is used, this settling time
> + may apply to more than just the first conversion, e.g. to every
> + conversion in a single-shot conversion mode or when a device
> + setting changes after each conversion (input chopping).
> +
> +What: /sys/bus/iio/devices/iio:deviceX/in_resistanceY_settlingtime_available
> +What: /sys/bus/iio/devices/iio:deviceX/in_tempY_settlingtime_available
> +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_settlingtime_available
> +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY-voltageZ_settlingtime_available
> +KernelVersion: 7.4
> +Contact: linux-iio@vger.kernel.org
> +Description:
> + Available settlingtime values in IIO range or list format.
> +
> What: /sys/.../events/in_proximity_thresh_either_runningperiod
> KernelVersion: 6.6
> Contact: linux-iio@vger.kernel.org
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support
2026-09-04 22:09 [PATCH v2 00/10] iio: adc: ti-ads112c14: add filter support David Lechner (TI)
` (9 preceding siblings ...)
2026-09-04 22:09 ` [PATCH v2 10/10] iio: ABI: add settlingtime attributes David Lechner (TI)
@ 2026-09-06 0:36 ` Jonathan Cameron
10 siblings, 0 replies; 16+ messages in thread
From: Jonathan Cameron @ 2026-09-06 0:36 UTC (permalink / raw)
To: David Lechner (TI)
Cc: Nuno Sá, Andy Shevchenko, Chris Hall, Patrick Edwards,
Kurt Borja, linux-iio, linux-kernel
On Fri, 04 Sep 2026 17:09:45 -0500
"David Lechner (TI)" <dlechner@baylibre.com> wrote:
> I was hoping to avoid this, but until [0] lands, sashiko doesn't know
> how to handle patch series with dependencies. So I have combined my
> three outstanding ti-ads112c14 series into a single one. It's really
> too much all at once, but this seems the best way to speed up the review
> process at the moment.
>
> [0]: https://github.com/sashiko-dev/sashiko/pull/389
Nice. I hope that lands. Not being in a sensible position to cherry-pick
off the start of a series is leading to a lot more patches outstanding
on list than I like. I was drawing up a list of asks but seems folk
are already on top of them!
As a temporary solution this is fine.
Given my goldfish brain I can't remember if I reviewed much of this
recently so I'll just start at the top again!
Thanks
Jonathan
>
> So we now have:
>
> * patches 1-3: "iio: adc: ti-ads112c14: continuous mode support", last
> posted as v5 [1]
> * patches 4-5: "iio: adc: ti-ads112c14: add burnout current support",
> last posted as v2 [2]
> * patches 6-10: "iio: adc: ti-ads112c14: add filter support", last
> posted as v1 [3]
>
> Apart from the changes listed below, the patches are the same as in
> those postings.
>
> [1]: https://patch.msgid.link/20260831-iio-adc-ti-ads112c14-continuous-mode-v5-0-76f80a04b94f@baylibre.com
> [2]: https://patch.msgid.link/20260827-iio-adc-ti-ads112c14-burnout-v2-0-00a1fab9e2d1@baylibre.com
> [3]: https://patch.msgid.link/20260807-iio-adc-ti-ads112c14-filter-support-v1-0-4d3ba00caf18@baylibre.com
>
> The rest of this cover letter describes the filter support portion.
>
> TI ADS112C14 has several features related to filtering that are all
> interconnected. And to make things more interesting, not of the register
> fields map directly to IIO attributes. So this is one of those cases
> where we need to bend the rules a bit and just document it (we've
> already discussed this a bit in the previous series for this driver and
> came to this conclusion).
>
> Here is the high-level overview:
>
> We are adding sampling_frequency, oversampling_ratio, filter_type, and
> a (new to IIO) settlingtime attribute.
>
> Since register fields have different meanings depending on filter type
> we have a quirky rule that if the filter type is sinc4 or sinc4+sinc1,
> then you need to set the oversampling ratio first in order to see the
> expected available values for the sampling frequency. For sinc4+sinc1+pf1
> it is the other way around, you have to set sampling frequency first
> in order to see the expected available oversampling ratios.
>
> In other drivers, we've opted to store the requested values for dependant
> attributes like this and pick the closest available one when actually
> starting sampling. I opted not to do that here as there is not much
> overlap between settings. And as we will see below, there are other
> reasons for being picky about sampling frequency.
>
> We also discussed in another series about a proposed settlingtime
> attribute. The conclusion was that it should be the total settling time
> delay (in seconds) before a chip takes the first sample (after any
> settings have changed). In this chip there is a DELAY field in a
> register that programs some extra delay in addition to an always present
> fixed delay. So the way the attribute will work for this chip is that
> the settlingtime_available attribute will list the range including a
> minimum value. This happens to be the always present fixed delay. So the
> difference between that and the current value of the settlingtime
> attribute will be programed as the DELAY value. It also seems that the
> fixed latency period includes the conversion time. We've just glossed
> over that for now and not subtracted that from the settlingtime
> attribute.
>
> Now, here is where things really get interesting/complicated. There are
> even more settings that affect the settling time. We defined the
> settling time as just a delay before the first sample. However, there
> are a couple of things that trigger the "first" sample. On this chip,
> the first sample only counts in continuous sampling mode. So only works
> as described when using the DRDY trigger in this driver. When using
> a generic trigger, e.g. a hrtimer trigger, single-shot sampling mode
> is used, so every sample is a "first" sample and has the settling delay
> added. This is mostly a non-issue other than it could throw people off
> that they cannot set the hrtimer frequency close to the sampling
> frequency attribute and actually get that sampling rate.
>
> And there a few other idiosyncrasies we haven't accounted for. To keep
> things simple, we've implemented settlingtime as tDELAY + tLATENCY
> (datasheet values). But this actually include the conversion time as
> well. Also, tLATENCY is longer if you are coming out of standby mode
> (this doesn't matter at this point since we didn't implement power
> management, but we wouldn't want to change it and break userspace
> later).
>
> Then there is also input chopping where the positive and negative input
> channels are swapped in the mux in the ADC on each sample. In these
> cases, every conversion requires the settling time because the mux is
> switched after every conversion. And the actual first sample has
> additional delay (presumably does two conversions). So the first sample
> takes tGC_LATENCY = 2 × (tDELAY + tLATENCY) – 12 tMOD and every sample
> after that takes tGC_DATA = tDELAY + tLATENCY – 12 tMOD. For this one,
> I have valued simplicity over accuracy in the implementation, so it is
> the same where settlingtime = tDELAY + tLATENCY and not worried about
> the 12 tMOD difference. I think it makes sense to keep settlingtime
> as a single tDELAY + tLATENCY in this case since the when the mux
> changes after each sample, the next sample is now the first sample after
> settings have changed.
>
> This sort of breaks the definition of sampling frequency though since
> in IIO, the 1 / sampling frequency is the time between each sample
> being sent over the bus. I don't really want to change how sampling
> frequency is implemented here though because the current values match
> the datasheet which can be used to infer information like where the
> notches in the filter are. The actual observed sampling rate will be
> 1 / tGC_DATA.
>
> Having written all of this out now though, I'm tempted to go back and
> change the settlingtime attribute implementation to be more accurate.
> In any case, tDELAY will always be easy to infer because it is the
> current value minus the minimum value (from the _available attribute).
> Then when using input chopping one could get a reasonably accurate
> sample period by taking 1 / sampling_frequency + settlingtime.
>
> We will follow this up later with a documentation patch that explains
> all of this too.
>
> Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
> ---
> Changes in v2:
> - Combined the continuous mode support and burnout current support
> series into this series (see above).
> - Rebased on the current iio/testing.
> - Bumped KernelVersion to 7.4 in the settlingtime ABI documentation so
> that it matches the rest of the new ABI in this series.
> - Patches 1-3 (continuous mode): no changes since v5 [1].
> - FWIW, I didn't think sashiko's comments on the IRQ were realistic.
> This could never be used with a level interrupt. And we haven't
> typically tried to handle spurious interrupts in the past either.
> Everything it suggested could only happen with broken hardware or
> excessive noise (which I suppose counts as broken hardware).
> - Patches 4-5 (burnout), changes since v2 [2]:
> - Pass an integer rather than a boolean to FIELD_PREP() for the global
> chop enable bit.
> - Drop the blank line after looking up the measurement.
> - Return the conversion error in preference to the error from turning
> the burnout current back off.
> - Use a local variable for the "burn-out-current-nanoamp" property
> name.
> - Compare against ADS112C14_DEVICE_CFG_BOCS_DISABLED explicitly
> instead of testing for non-zero.
> - Fix KernelVersion in the ABI docs.
> - Patches 6-10 (filter support), changes since v1 [3]:
> - Move the driver code that was accidentally squashed into the
> "iio: ABI: add sinc4+sinc1+pf1 filter_type" patch back to the
> "iio: adc: ti-ads112c14: add filter support" patch where it belongs.
> - Fixed typos in the external clock patch commit message.
> - Added a note to the settlingtime ABI documentation that the settling
> time can apply to more than just the first conversion.
> - Link to v1: https://patch.msgid.link/20260807-iio-adc-ti-ads112c14-filter-support-v1-0-4d3ba00caf18@baylibre.com
>
> ---
> David Lechner (TI) (10):
> iio: adc: ti-ads112c14: add DRDY interrupt support
> iio: adc: ti-ads112c14: create data read helper functions
> iio: adc: ti-ads112c14: add continuous mode support
> iio: adc: ti-ads112c14: add burnout current support
> iio: ABI: add sysfs attribute for _burnoutraw
> iio: adc: ti-ads112c14: support external clock
> iio: adc: ti-ads112c14: add filter support
> iio: ABI: add sinc4+sinc1+pf1 filter_type
> iio: adc: ti-ads112c14: add settlingtime attribute
> iio: ABI: add settlingtime attributes
>
> Documentation/ABI/testing/sysfs-bus-iio | 27 +
> Documentation/ABI/testing/sysfs-bus-iio-adc | 9 +
> drivers/iio/adc/ti-ads112c14.c | 1286 +++++++++++++++++++++++++--
> 3 files changed, 1263 insertions(+), 59 deletions(-)
> ---
> base-commit: e7c1d459e542bc4a9c57f558e8ca1b14df7a7eef
> change-id: 20260807-iio-adc-ti-ads112c14-filter-support-8a56850f590f
>
> Best regards,
> --
> David Lechner (TI) <dlechner@baylibre.com>
>
^ permalink raw reply [flat|nested] 16+ messages in thread