[PATCH 3/4] iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel

From: David Lechner (TI)

Date: Mon Jun 15 2026 - 18:03:41 EST


Implement support for the programmable gain amplifier on the internal
short SYS_MON channel. This channel is used for calibration, so it is
useful to be able to set the PGA to the same gain as the external
channels. The gain setting is implemented via the `_scale` attribute.

In the future, we may want to support different reference voltages for
this channel, so the scale_available table is populated during probe
rather than being a static table.

Signed-off-by: David Lechner (TI) <dlechner@xxxxxxxxxxxx>
---
drivers/iio/adc/ti-ads112c14.c | 131 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 128 insertions(+), 3 deletions(-)

diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index 97097ae2a487..202a449b4234 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -122,6 +122,26 @@

#define ADS112C14_REG_REG_MAP_CRC 0x0F

+/* Available gains as tenths (x10) */
+static const u32 ads112c14_pga_gains_x10[] = {
+ 5, /* 0.5 */
+ 10, /* 1 */
+ 20, /* 2 */
+ 40, /* 4 */
+ 50, /* 5 */
+ 80, /* 8 */
+ 100, /* 10 */
+ 160, /* 16 */
+ 200, /* 20 */
+ 320, /* 32 */
+ 500, /* 50 */
+ 640, /* 64 */
+ 1000, /* 100 */
+ 1280, /* 128 */
+ 2000, /* 200 */
+ 2560, /* 256 */
+};
+
static bool ads112c14_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
@@ -181,6 +201,8 @@ struct ads112c14_data {
const struct ads112c14_chip_info *chip_info;
struct i2c_client *client;
struct regmap *regmap;
+ u8 sys_mon_chan_short_gain_val;
+ int sys_mon_chan_short_scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2];
};

/* Maps REF_VAL to microvolts. */
@@ -239,21 +261,28 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
.address = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
| BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
},
};

static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
const struct iio_chan_spec *chan)
{
+ u32 gain_val;
int ret;

- /* TODO: GAIN is useful for shorted PGA inputs. */
- /* All SYS_MON channels use GAIN of 1 to keep it simple. */
+ /*
+ * All SYS_MON channels use GAIN of 1 to keep it simple. Other than
+ * the internal short channel, where it is useful in practice.
+ */
+ gain_val = chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT ?
+ data->sys_mon_chan_short_gain_val : 1;
+
ret = regmap_update_bits(data->regmap, ADS112C14_REG_GAIN_CFG,
ADS112C14_GAIN_CFG_SYS_MON |
ADS112C14_GAIN_CFG_GAIN,
FIELD_PREP(ADS112C14_GAIN_CFG_SYS_MON, chan->address) |
- FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, 1));
+ FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, gain_val));
if (ret)
return ret;

@@ -366,6 +395,17 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_FRACTIONAL_LOG2;
}

+ if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
+ int *scale_avail = data->sys_mon_chan_short_scale_available[
+ data->sys_mon_chan_short_gain_val
+ ];
+
+ *val = scale_avail[0];
+ *val2 = scale_avail[1];
+
+ return IIO_VAL_INT_PLUS_NANO;
+ }
+
*val = vref_uV / (MICRO / MILLI);
/*
* Last 3 SYS_MON channels (ext ref, AVDD, DVDD) need to be
@@ -387,6 +427,61 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
}
}

+static int ads112c14_read_avail(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan, const int **vals,
+ int *type, int *length, long mask)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+
+ switch (chan->channel) {
+ case ADS112C14_SYS_MON_CHANNEL_SHORT:
+ *vals = (const int *)data->sys_mon_chan_short_scale_available;
+ *length = 2 * ARRAY_SIZE(data->sys_mon_chan_short_scale_available);
+ *type = IIO_VAL_INT_PLUS_NANO;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ads112c14_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+
+ switch (chan->channel) {
+ case ADS112C14_SYS_MON_CHANNEL_SHORT: {
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ for (u32 i = 0; i < ARRAY_SIZE(data->sys_mon_chan_short_scale_available); i++) {
+ if (val == data->sys_mon_chan_short_scale_available[i][0] &&
+ val2 == data->sys_mon_chan_short_scale_available[i][1]) {
+ data->sys_mon_chan_short_gain_val = i;
+ return 0;
+ }
+ }
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ads112c14_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+}
+
static int ads112c14_read_label(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, char *label)
{
@@ -418,9 +513,34 @@ static int ads112c14_read_label(struct iio_dev *indio_dev,

static const struct iio_info ads112c14_info = {
.read_raw = ads112c14_read_raw,
+ .read_avail = ads112c14_read_avail,
+ .write_raw = ads112c14_write_raw,
+ .write_raw_get_fmt = ads112c14_write_raw_get_fmt,
.read_label = ads112c14_read_label,
};

+static void ads112c14_populate_tables(struct ads112c14_data *data)
+{
+ u32 vref_uV, fsr_bits;
+ int i;
+
+ /* For now, assuming we are using 2.5V reference. */
+ vref_uV = ads112c14_internal_ref_uV[ADS112C14_REFERENCE_CFG_REF_VAL_2_5V];
+ fsr_bits = data->chip_info->resolution_bits - 1;
+
+ for (i = 0; i < ARRAY_SIZE(ads112c14_pga_gains_x10); i++) {
+ int *scale_avail = &data->sys_mon_chan_short_scale_available[i][0];
+ u32 gain_x10 = ads112c14_pga_gains_x10[i];
+
+ /* NB: slightly odd arrangement to avoid overflow. */
+ scale_avail[0] = div_u64_rem(div_u64((u64)NANO * 10 /
+ (MICRO / MILLI) * vref_uV /
+ gain_x10,
+ BIT(fsr_bits)),
+ NANO, &scale_avail[1]);
+ }
+}
+
static int ads112c14_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -472,6 +592,9 @@ static int ads112c14_probe(struct i2c_client *client)
if (FIELD_GET(ADS112C14_STATUS_MSB_RESETN, reg_val))
return dev_err_probe(dev, -EIO, "reset failed\n");

+ /* Default gain after reset is 1. */
+ data->sys_mon_chan_short_gain_val = 1;
+
/*
* Clear reset bit to prepare for next probe. And clear AVDD fault since
* that happens on every reset.
@@ -488,6 +611,8 @@ static int ads112c14_probe(struct i2c_client *client)
if (ret)
return ret;

+ ads112c14_populate_tables(data);
+
indio_dev->name = info->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ads112c14_sys_mon_channels;

--
2.43.0