Linux IIO development
 help / color / mirror / Atom feed
From: Kurt Borja <kuurtb@gmail.com>
To: Kurt Borja <kuurtb@gmail.com>,
	Jonathan Cameron <jic23@kernel.org>,
	 Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	 Conor Dooley <conor+dt@kernel.org>,
	David Lechner <dlechner@baylibre.com>
Cc: "Nuno Sá" <nuno.sa@analog.com>,
	"Andy Shevchenko" <andy@kernel.org>,
	linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH v4 05/10] iio: adc: ti-ads1262: support per-channel reference and gain
Date: Fri, 28 Aug 2026 01:38:20 -0500	[thread overview]
Message-ID: <20260828-ads126x-v4-5-1dc27e9c0260@gmail.com> (raw)
In-Reply-To: <20260828-ads126x-v4-0-1dc27e9c0260@gmail.com>

Allow each channel to select its voltage reference through the
"reference-sources" firmware property. Then, use the reference voltage
to calculate available scales. The PGA bypass handling is intentionally
left out until more information about on its usual applications is
gathered.

The ADS1262 allows single-ended supply configurations or bipolar supply
configurations. In single ended configurations both the analog and
digital rails share the same ground, i.e. AVSS = DGND = 0 V. In bipolar
supply configurations, AVSS can go below ground, e.g. AVSS = -2.5 V. If
AVSS is below ground, the ADC can achieve true bipolar measurements and
the external references can also have voltage levels below ground.

This is currently an issue because the regulator subsystem doesn't
support reading negative voltages. The ad4170-4 driver faces this
problem too and the same workaround is used in this case: assume every
regulator reports magnitudes (absolute values). If the chip has a
bipolar supply configuration, then assume positive references are above
ground (>= 0 V) and negative references are below ground (<= 0 V). This
is not a hardware constraint, but it is the most common wiring.

Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 drivers/iio/adc/ti-ads1262.c | 493 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 468 insertions(+), 25 deletions(-)

diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 86ea9b6232d8..c87c0a66119a 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -8,6 +8,7 @@
 #include <linux/array_size.h>
 #include <linux/bitfield.h>
 #include <linux/bitops.h>
+#include <linux/bitmap.h>
 #include <linux/cleanup.h>
 #include <linux/clk.h>
 #include <linux/completion.h>
@@ -18,12 +19,14 @@
 #include <linux/interrupt.h>
 #include <linux/lockdep.h>
 #include <linux/math64.h>
+#include <linux/minmax.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/property.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
 #include <linux/spi/spi.h>
+#include <linux/string.h>
 #include <linux/types.h>
 #include <linux/units.h>
 
@@ -83,6 +86,7 @@
 #define ADS1262_MODE2_REG			0x05
 #define   ADS1262_MODE2_BYPASS_MASK		BIT(7)
 #define   ADS1262_MODE2_GAIN_MASK		GENMASK(6, 4)
+#define     ADS1262_GAIN_COUNT			6
 #define   ADS1262_MODE2_DR_MASK			GENMASK(3, 0)
 #define     ADS1262_DR_2_5_SPS			  0
 #define     ADS1262_DR_5_SPS			  1
@@ -123,7 +127,7 @@
 #define ADS1262_IDACMUX_REG			0x0D
 #define   ADS1262_IDACMUX_MUX2_MASK		GENMASK(7, 4)
 #define   ADS1262_IDACMUX_MUX1_MASK		GENMASK(3, 0)
-#define     ADS1262_IDACMUX_NO_CONN		0b1011
+#define     ADS1262_IDACMUX_NO_CONN		  11
 
 #define ADS1262_IDACMAG_REG			0x0E
 #define   ADS1262_IDACMAG_MAG2_MASK		GENMASK(7, 4)
@@ -131,7 +135,17 @@
 
 #define ADS1262_REFMUX_REG			0x0F
 #define   ADS1262_REFMUX_RMUXP_MASK		GENMASK(5, 3)
+#define     ADS1262_RMUXP_INTERNAL		  0
+#define     ADS1262_RMUXP_REFP1			  1
+#define     ADS1262_RMUXP_REFP2			  2
+#define     ADS1262_RMUXP_REFP3			  3
+#define     ADS1262_RMUXP_AVDD			  4
 #define   ADS1262_REFMUX_RMUXN_MASK		GENMASK(2, 0)
+#define     ADS1262_RMUXN_INTERNAL		  0
+#define     ADS1262_RMUXN_REFN1			  1
+#define     ADS1262_RMUXN_REFN2			  2
+#define     ADS1262_RMUXN_REFN3			  3
+#define     ADS1262_RMUXN_AVSS			  4
 
 #define ADS1262_TDACP_REG			0x10
 #define   ADS1262_TDACP_OUTP_MASK		BIT(7)
@@ -169,9 +183,13 @@
 
 #define ADS1262_NOMINAL_CLK_RATE		7372800
 #define ADS1262_MODULATOR_DIV			8
+#define ADS1262_INTERNAL_REFERENCE_uV		2500000
+#define ADS1262_TEMP_SLOPE_uV_C			420ULL
+#define ADS1262_TEMP_ZERO_C			111900ULL
 
 #define ADS1262_FW_CHANNEL_COUNT		16
 #define ADS1262_MON_CHANNEL_COUNT		4
+#define ADS1262_EXT_REF_COUNT			3
 #define ADS1262_REGMAP_WRITE_SZ			8
 #define ADS1262_MONITOR_ADDR_OFFSET		100
 
@@ -180,12 +198,21 @@
 struct ads1262_channel {
 	u8 data_rate;
 	u8 filter;
+	u8 gain;
+	u8 ref_p;
+	u8 ref_n;
+	bool ref_reversal;
+	bool is_resistance;
+	size_t num_scales;
+	int scales[ADS1262_GAIN_COUNT][2];
 };
 
 struct ads1262 {
 	struct spi_device *spi;
 	struct regmap *regmap;
 	struct gpio_desc *start_gpiod;
+	struct regulator *avdd_supply;
+	struct regulator *avss_supply;
 	size_t num_channels;
 	struct ads1262_channel *channels;
 	/* protects concurrent SPI transfers */
@@ -195,8 +222,12 @@ struct ads1262 {
 	struct completion drdy;
 	unsigned long clk_rate;
 	u8 dev_id;
+	bool bipolar_supply;
 	int sampling_freq_table[ADS1262_DR_COUNT][2];
 	int sampling_freq_fir[ADS1262_DR_16_6_SPS + 1][2];
+	u32 rref_ohms[ADS1262_EXT_REF_COUNT][ADS1262_EXT_REF_COUNT];
+	int refp_uV[ADS1262_EXT_REF_COUNT];
+	int refn_uV[ADS1262_EXT_REF_COUNT];
 };
 
 static const char * const ads1262_device_id_to_name[] = {
@@ -217,8 +248,11 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
 			.endianness = IIO_BE,
 		},
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
-		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_OFFSET) |
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+						BIT(IIO_CHAN_INFO_SCALE),
 	},
 	{
 		.type = IIO_VOLTAGE,
@@ -233,8 +267,10 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
 			.endianness = IIO_BE,
 		},
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
-		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+						BIT(IIO_CHAN_INFO_SCALE),
 	},
 	{
 		.type = IIO_VOLTAGE,
@@ -249,8 +285,10 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
 			.endianness = IIO_BE,
 		},
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
-		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+						BIT(IIO_CHAN_INFO_SCALE),
 	},
 	{
 		.type = IIO_VOLTAGE,
@@ -266,8 +304,10 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
 			.endianness = IIO_BE,
 		},
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
-		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+						BIT(IIO_CHAN_INFO_SCALE),
 	},
 };
 
@@ -314,6 +354,24 @@ static const u32 ads1262_mod_decimator_b[] = {
 	[ADS1262_DR_38400_SPS]	= 1,
 };
 
+static const char * const ads1262_ref_sources_pos[] = {
+	[ADS1262_RMUXP_INTERNAL] = "internal-p",
+	[ADS1262_RMUXP_REFP1] = "refp1",
+	[ADS1262_RMUXP_REFP2] = "refp2",
+	[ADS1262_RMUXP_REFP3] = "refp3",
+	[ADS1262_RMUXP_AVDD] = "avdd",
+	NULL
+};
+
+static const char * const ads1262_ref_sources_neg[] = {
+	[ADS1262_RMUXN_INTERNAL] = "internal-n",
+	[ADS1262_RMUXN_REFN1] = "refn1",
+	[ADS1262_RMUXN_REFN2] = "refn2",
+	[ADS1262_RMUXN_REFN3] = "refn3",
+	[ADS1262_RMUXN_AVSS] = "avss",
+	NULL
+};
+
 static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
 			    int val2)
 {
@@ -325,6 +383,12 @@ static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
 	return -EINVAL;
 }
 
+static bool ads1262_reference_is_external(int ref_p, int ref_n)
+{
+	return in_range(ref_p, ADS1262_RMUXP_REFP1, ADS1262_EXT_REF_COUNT) &&
+	       in_range(ref_n, ADS1262_RMUXN_REFN1, ADS1262_EXT_REF_COUNT);
+}
+
 static int ads1262_dev_send_cmd(struct ads1262 *st, u8 opcode)
 {
 	guard(mutex)(&st->xfer_lock);
@@ -455,17 +519,33 @@ static int ads1262_channel_enable(struct ads1262 *st,
 	guard(mutex)(&st->xfer_lock);
 	guard(mutex)(&st->chan_lock);
 
-	ret = regmap_update_bits(st->regmap, ADS1262_MODE2_REG,
-				 ADS1262_MODE2_DR_MASK,
-				 FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate));
+	ret = regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
+				 ADS1262_MODE0_REFREV_MASK,
+				 FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal));
 	if (ret)
 		return ret;
 
-	return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
-				  ADS1262_INPMUX_MUXN_MASK |
-				  ADS1262_INPMUX_MUXP_MASK,
-				  FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
-				  FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel));
+	ret = regmap_update_bits(st->regmap, ADS1262_MODE2_REG,
+				 ADS1262_MODE2_DR_MASK |
+				 ADS1262_MODE2_GAIN_MASK,
+				 FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate) |
+				 FIELD_PREP(ADS1262_MODE2_GAIN_MASK, chan->gain));
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
+				 ADS1262_INPMUX_MUXN_MASK |
+				 ADS1262_INPMUX_MUXP_MASK,
+				 FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
+				 FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel));
+	if (ret)
+		return ret;
+
+	return regmap_update_bits(st->regmap, ADS1262_REFMUX_REG,
+				  ADS1262_REFMUX_RMUXN_MASK |
+				  ADS1262_REFMUX_RMUXP_MASK,
+				  FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, chan->ref_n) |
+				  FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, chan->ref_p));
 }
 
 static int ads1262_set_runmode(struct ads1262 *st, u8 runmode)
@@ -514,6 +594,7 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
 {
 	struct ads1262 *st = iio_priv(indio_dev);
 	struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
+	u64 scale, offset;
 	__be32 raw;
 	int ret;
 
@@ -526,6 +607,34 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
 
 		return IIO_VAL_INT;
 
+	case IIO_CHAN_INFO_SCALE: {
+		guard(mutex)(&st->chan_lock);
+
+		*val = chan_data->scales[chan_data->gain][0];
+		*val2 = chan_data->scales[chan_data->gain][1];
+
+		return IIO_VAL_DECIMAL64_PICO;
+	}
+
+	case IIO_CHAN_INFO_OFFSET: {
+		guard(mutex)(&st->chan_lock);
+
+		scale = iio_val_s64_compose(chan_data->scales[chan_data->gain][0],
+					    chan_data->scales[chan_data->gain][1]);
+
+		switch (chan->type) {
+		case IIO_TEMP:
+			offset = -mul_u64_u64_div_u64(ADS1262_TEMP_ZERO_C,
+						      PICO * MILLIDEGREE_PER_DEGREE,
+						      scale * ADS1262_TEMP_SLOPE_uV_C);
+			iio_val_s64_decompose(offset, val, val2);
+
+			return IIO_VAL_INT_64;
+		default:
+			return -EOPNOTSUPP;
+		}
+	}
+
 	case IIO_CHAN_INFO_SAMP_FREQ: {
 		guard(mutex)(&st->chan_lock);
 
@@ -548,6 +657,12 @@ static int ads1262_read_avail(struct iio_dev *indio_dev,
 	struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
 
 	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		*type = IIO_VAL_DECIMAL64_PICO;
+		*vals = (const int *)chan_data->scales;
+		*length = chan_data->num_scales * 2;
+		return IIO_AVAIL_LIST;
+
 	case IIO_CHAN_INFO_SAMP_FREQ:
 		*type = IIO_VAL_INT_PLUS_MICRO;
 
@@ -580,6 +695,16 @@ static int ads1262_write_raw(struct iio_dev *indio_dev,
 	guard(mutex)(&st->chan_lock);
 
 	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		ret = ads1262_find_two(chan_data->scales, chan_data->num_scales,
+				       val, val2);
+		if (ret < 0)
+			return ret;
+
+		chan_data->gain = ret;
+
+		return 0;
+
 	case IIO_CHAN_INFO_SAMP_FREQ:
 		switch (chan_data->filter) {
 		case ADS1262_FILTER_FIR:
@@ -621,6 +746,17 @@ static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int re
 	return regmap_write(st->regmap, reg, writeval);
 }
 
+static int ads1262_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_DECIMAL64_PICO;
+	default:
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+}
+
 static int ads1262_fwnode_xlate(struct iio_dev *indio_dev,
 				const struct fwnode_reference_args *iiospec)
 {
@@ -643,6 +779,7 @@ static const struct iio_info ads1262_iio_info = {
 	.read_raw = ads1262_read_raw,
 	.read_avail = ads1262_read_avail,
 	.write_raw = ads1262_write_raw,
+	.write_raw_get_fmt = ads1262_write_raw_get_fmt,
 	.debugfs_reg_access = ads1262_debugfs_reg_access,
 	.fwnode_xlate = ads1262_fwnode_xlate,
 };
@@ -895,20 +1032,246 @@ static void ads1262_populate_samp_freqs(struct ads1262 *st)
 	}
 }
 
+static void ads1262_calculate_scales(int (*scales)[2], size_t num_scales,
+				     u32 full_scale, u64 mult,
+				     u32 resolution)
+{
+	unsigned int i;
+	s64 val;
+
+	/*
+	 * Each scale in the table corresponds to a PGA gain configuration,
+	 * which are given in powers of 2.
+	 */
+	for (i = 0; i < num_scales; i++) {
+		val = mul_u64_u64_shr(full_scale, mult, resolution - 1 + i);
+		iio_val_s64_decompose(val, &scales[i][0], &scales[i][1]);
+	}
+}
+
+static int ads1262_populate_scales_resistance(struct ads1262 *st,
+					      const struct iio_chan_spec *spec)
+{
+	struct ads1262_channel *chan = &st->channels[spec->scan_index];
+	u32 full_scale;
+
+	if (!ads1262_reference_is_external(chan->ref_p, chan->ref_n))
+		return -EINVAL;
+
+	full_scale = st->rref_ohms[chan->ref_p - 1][chan->ref_n - 1];
+
+	chan->num_scales = ARRAY_SIZE(chan->scales);
+
+	ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale,
+				 PICO, ADS1262_ADC1_RESOLUTION);
+
+	return 0;
+}
+
+static int ads1262_populate_scales_temp(struct ads1262 *st,
+					const struct iio_chan_spec *spec)
+{
+	struct ads1262_channel *chan = &st->channels[spec->scan_index];
+	u32 full_scale;
+	u64 mult;
+
+	full_scale = ADS1262_INTERNAL_REFERENCE_uV;
+	mult = PICO * MILLIDEGREE_PER_DEGREE / ADS1262_TEMP_SLOPE_uV_C;
+
+	chan->num_scales = 1;
+
+	ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale,
+				 mult, ADS1262_ADC1_RESOLUTION);
+
+	return 0;
+}
+
+static int ads1262_populate_scales_voltage(struct ads1262 *st,
+					   const struct iio_chan_spec *spec)
+{
+	struct device *dev = &st->spi->dev;
+	struct ads1262_channel *chan = &st->channels[spec->scan_index];
+	int refp_uV, refn_uV;
+	u32 full_scale_uV;
+	u64 mult;
+	int ret;
+
+	switch (chan->ref_p) {
+	case ADS1262_RMUXP_INTERNAL:
+		ret = st->avss_supply ? regulator_get_voltage(st->avss_supply) : 0;
+		if (ret < 0)
+			return dev_err_probe(dev, ret, "channel@%u: failed to get avss voltage\n",
+					     spec->scan_index);
+
+		/* The internal reference negative is AVSS */
+		refp_uV = -ret + ADS1262_INTERNAL_REFERENCE_uV;
+		break;
+
+	case ADS1262_RMUXP_REFP1 ... ADS1262_RMUXP_REFP3:
+		refp_uV = st->refp_uV[chan->ref_p - 1];
+		break;
+
+	case ADS1262_RMUXP_AVDD:
+		ret = regulator_get_voltage(st->avdd_supply);
+		if (ret < 0)
+			return dev_err_probe(dev, ret, "channel@%u: failed to get avdd voltage\n",
+					     spec->scan_index);
+		refp_uV = ret;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	switch (chan->ref_n) {
+	case ADS1262_RMUXN_INTERNAL:
+	case ADS1262_RMUXN_AVSS:
+		ret = st->avss_supply ? regulator_get_voltage(st->avss_supply) : 0;
+		if (ret < 0)
+			return dev_err_probe(dev, ret, "channel@%u: failed to get avss voltage\n",
+					     spec->scan_index);
+		refn_uV = -ret;
+		break;
+
+	case ADS1262_RMUXN_REFN1 ... ADS1262_RMUXN_REFN3:
+		refn_uV = st->refn_uV[chan->ref_n - 1];
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	full_scale_uV = abs(refp_uV - refn_uV);
+	if (full_scale_uV < 900000)
+		return dev_err_probe(dev, -EINVAL, "channel@%u: reference voltage below 0.9V\n",
+				     spec->scan_index);
+
+	if (spec->channel >= ADS1262_INPMUX_AVDD &&
+	    spec->channel <= ADS1262_INPMUX_DVDD) {
+		/* The power supply monitors are scaled down by a factor of 4 */
+		mult = 4;
+		chan->num_scales = 1;
+	} else {
+		mult = 1;
+		chan->num_scales = ARRAY_SIZE(chan->scales);
+	}
+
+	ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale_uV,
+				 NANO * mult, ADS1262_ADC1_RESOLUTION);
+
+	return 0;
+}
+
+static int ads1262_parse_references(struct ads1262 *st)
+{
+	struct device *dev = &st->spi->dev;
+	unsigned int i, j;
+	char name[sizeof("ti,refpN-refnM-resistor-ohms")];
+	u32 ohms;
+	int ret;
+
+	for (i = ADS1262_RMUXP_REFP1; i <= ADS1262_RMUXP_REFP3; i++) {
+		scnprintf(name, sizeof(name), "refp%u", i);
+		ret = devm_regulator_get_enable_read_voltage(dev, name);
+		if (ret < 0 && ret != -ENODEV)
+			return dev_err_probe(dev, ret, "failed to read reference voltage: %s\n",
+					     name);
+
+		st->refp_uV[i - 1] = ret == -ENODEV ? 0 : ret;
+	}
+
+	for (i = ADS1262_RMUXN_REFN1; i <= ADS1262_RMUXN_REFN3; i++) {
+		scnprintf(name, sizeof(name), "refn%u", i);
+		ret = devm_regulator_get_enable_read_voltage(dev, name);
+		if (ret < 0 && ret != -ENODEV)
+			return dev_err_probe(dev, ret, "failed to read reference voltage: %s\n",
+					     name);
+
+		/*
+		 * REVISIT: Currently the regulator subsystem doesn't support
+		 * reading negative voltages. If we have a bipolar supply
+		 * configuration (AVSS < 0), then we are forced to assume that
+		 * negative references are either 0V (no regulator) or below
+		 * ground magnitudes.
+		 */
+		if (st->bipolar_supply)
+			st->refn_uV[i - 1] = ret == -ENODEV ? 0 : -ret;
+		else
+			st->refn_uV[i - 1] = ret == -ENODEV ? 0 : ret;
+	}
+
+	for (i = ADS1262_RMUXP_REFP1; i <= ADS1262_RMUXP_REFP3; i++) {
+		for (j = ADS1262_RMUXN_REFN1; j <= ADS1262_RMUXN_REFN3; j++) {
+			scnprintf(name, sizeof(name),
+				  "ti,refp%u-refn%u-resistor-ohms", i, j);
+
+			if (!device_property_present(dev, name))
+				continue;
+
+			ret = device_property_read_u32(dev, name, &ohms);
+			if (ret)
+				return dev_err_probe(dev, ret,
+						     "failed to read reference resistor: %s\n",
+						     name);
+			if (!ohms)
+				return dev_err_probe(dev, -EINVAL,
+						     "reference resistor can't be 0 ohms: %s\n",
+						     name);
+
+			st->rref_ohms[i - 1][j - 1] = ohms;
+		}
+	}
+
+	return 0;
+}
+
 static int ads1262_populate_tables(struct iio_dev *indio_dev)
 {
 	struct ads1262 *st = iio_priv(indio_dev);
+	const struct iio_chan_spec *spec;
+	int ret;
 
 	ads1262_populate_samp_freqs(st);
 
+	ret = ads1262_parse_references(st);
+	if (ret)
+		return ret;
+
+	for (unsigned int i = 0; i < st->num_channels; i++) {
+		spec = &indio_dev->channels[i];
+
+		switch (spec->type) {
+		case IIO_VOLTAGE:
+			ret = ads1262_populate_scales_voltage(st, spec);
+			if (ret)
+				return ret;
+			break;
+		case IIO_TEMP:
+			ret = ads1262_populate_scales_temp(st, spec);
+			if (ret)
+				return ret;
+			break;
+		case IIO_RESISTANCE:
+			ret = ads1262_populate_scales_resistance(st, spec);
+			if (ret)
+				return ret;
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+	}
+
 	return 0;
 }
 
 static int ads1262_parse_channel_node(struct ads1262 *st,
 				      struct iio_chan_spec *spec,
+				      struct ads1262_channel *chan,
 				      struct fwnode_handle *node)
 {
 	struct device *dev = &st->spi->dev;
+	const char *sources[2];
+	char name[sizeof("ti,refpN-refnM-resistor-ohms")];
 	u32 pins[2];
 	int ret;
 
@@ -947,6 +1310,38 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
 	spec->channel = pins[0];
 	spec->channel2 = pins[1];
 
+	if (fwnode_property_present(node, "reference-sources")) {
+		ret = fwnode_property_read_string_array(node, "reference-sources",
+							sources, ARRAY_SIZE(sources));
+		if (ret < 0)
+			return dev_err_probe(dev, ret, "%pfwP: failed to read reference-sources\n",
+					     node);
+		if (ret < 2)
+			return dev_err_probe(dev, -EINVAL, "%pfwP: missing reference-sources\n",
+					     node);
+
+		ret = match_string(ads1262_ref_sources_pos, -1, sources[0]);
+		if (ret < 0)
+			return dev_err_probe(dev, ret, "%pfwP: invalid positive reference source\n",
+					     node);
+		chan->ref_p = ret;
+
+		ret = match_string(ads1262_ref_sources_neg, -1, sources[1]);
+		if (ret < 0)
+			return dev_err_probe(dev, ret, "%pfwP: invalid negative reference source\n",
+					     node);
+		chan->ref_n = ret;
+
+		if (ads1262_reference_is_external(chan->ref_p, chan->ref_n)) {
+			scnprintf(name, sizeof(name), "ti,refp%u-refn%u-resistor-ohms",
+				  chan->ref_p, chan->ref_n);
+			if (device_property_present(dev, name))
+				chan->is_resistance = true;
+		}
+	}
+
+	chan->ref_reversal = fwnode_property_read_bool(node, "ti,reference-reversal");
+
 	return 0;
 }
 
@@ -988,11 +1383,11 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
 
 		*chan = (struct ads1262_channel)ads1262_default_channel;
 
-		ret = ads1262_parse_channel_node(st, spec, node);
+		ret = ads1262_parse_channel_node(st, spec, chan, node);
 		if (ret)
 			return ret;
 
-		spec->type = IIO_VOLTAGE;
+		spec->type = chan->is_resistance ? IIO_RESISTANCE : IIO_VOLTAGE;
 		spec->indexed = true;
 		spec->scan_index = i;
 		spec->address = reg;
@@ -1003,8 +1398,10 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
 			.endianness = IIO_BE,
 		};
 		spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-					   BIT(IIO_CHAN_INFO_SAMP_FREQ);
-		spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ);
+					   BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+					   BIT(IIO_CHAN_INFO_SCALE);
+		spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+						     BIT(IIO_CHAN_INFO_SCALE);
 
 		i++;
 	}
@@ -1027,6 +1424,14 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
 	return 0;
 }
 
+static void ads1262_regulator_disable(void *data)
+{
+	struct regulator *supply = data;
+
+	if (supply)
+		regulator_disable(supply);
+}
+
 static int ads1262_supply_setup(struct ads1262 *st)
 {
 	struct device *dev = &st->spi->dev;
@@ -1036,14 +1441,52 @@ static int ads1262_supply_setup(struct ads1262 *st)
 	if (ret)
 		return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
 
-	ret = devm_regulator_get_enable(dev, "avdd");
-	if (ret < 0)
+	st->avdd_supply = devm_regulator_get(dev, "avdd");
+	ret = PTR_ERR_OR_ZERO(st->avdd_supply);
+	if (ret)
 		return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
 
-	ret = devm_regulator_get_enable_optional(dev, "avss");
-	if (ret < 0 && ret != -ENODEV)
+	ret = regulator_enable(st->avdd_supply);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action_or_reset(dev, ads1262_regulator_disable,
+				       st->avdd_supply);
+	if (ret)
+		return ret;
+
+	/*
+	 * REVISIT: The AVSS supply has a minimum of -2.5V and maximum of 0V.
+	 * Currently the regulator subsystem doesn't support negative voltages,
+	 * so we assume the value returned here is actually the magnitude
+	 * (absolute value).
+	 *
+	 * This limitation forces us to assume that, if we have a bipolar supply
+	 * (AVSS < 0V), all negative references are below ground (REFN <= 0V)
+	 * and positive references are above ground (REFP >= 0V), as this is the
+	 * most common configuration.
+	 */
+	st->avss_supply = devm_regulator_get_optional(dev, "avss");
+	ret = PTR_ERR_OR_ZERO(st->avss_supply);
+	if (ret && ret != -ENODEV)
 		return dev_err_probe(dev, ret, "failed to get avss regulator\n");
 
+	if (ret != -ENODEV) {
+		ret = regulator_enable(st->avss_supply);
+		if (ret)
+			return ret;
+
+		ret = devm_add_action_or_reset(dev, ads1262_regulator_disable,
+					       st->avss_supply);
+		if (ret)
+			return ret;
+
+		st->bipolar_supply = true;
+	} else  {
+		st->avss_supply = NULL;
+		st->bipolar_supply = false;
+	}
+
 	fsleep(ADS1262_POWER_TRANS_USECS);
 
 	return 0;

-- 
2.55.0


  parent reply	other threads:[~2026-08-28  6:38 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-28  6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
2026-08-28  6:38 ` [PATCH v4 01/10] dt-bindings: adc: add excitation-current-chopping property Kurt Borja
2026-08-28 16:33   ` Conor Dooley
2026-08-28  6:38 ` [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
2026-08-28 16:39   ` Conor Dooley
2026-08-28  6:38 ` [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver Kurt Borja
2026-08-28  8:09   ` Andy Shevchenko
2026-08-28  6:38 ` [PATCH v4 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
2026-08-28  6:38 ` Kurt Borja [this message]
2026-08-28  6:38 ` [PATCH v4 06/10] iio: adc: ti-ads1262: support input chopping Kurt Borja
2026-08-28  6:38 ` [PATCH v4 07/10] iio: adc: ti-ads1262: support excitation currents Kurt Borja
2026-08-28  6:38 ` [PATCH v4 08/10] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
2026-08-28  6:38 ` [PATCH v4 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
2026-08-28  6:38 ` [PATCH v4 10/10] iio: adc: ti-ads1262: support common mode supplies Kurt Borja

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260828-ads126x-v4-5-1dc27e9c0260@gmail.com \
    --to=kuurtb@gmail.com \
    --cc=andy@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dlechner@baylibre.com \
    --cc=jic23@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=nuno.sa@analog.com \
    --cc=robh@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox