From: David Lechner <dlechner@baylibre.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>,
Linus Walleij <linusw@kernel.org>,
Bartosz Golaszewski <brgl@kernel.org>
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, linux-gpio@vger.kernel.org
Subject: Re: [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver
Date: Sat, 8 Aug 2026 13:39:04 -0500 [thread overview]
Message-ID: <ad95afb4-e667-4290-8878-fdd7067f20fe@baylibre.com> (raw)
In-Reply-To: <20260807-ads126x-v3-2-f89925d72792@gmail.com>
On 8/7/26 10:58 PM, Kurt Borja wrote:
> Add the ti-ads1262 driver with initial support for the primary ADC
> (ADC1). The ADS1263 auxiliary ADC (ADC2) is handled by a separate driver
> and interoperability considerations were taken into account.
Should probably mention here that IIO_CHAN_INFO_SCALE is intentionally
left out here. (Or just implement it using internal reference voltage
to start with.)
>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> MAINTAINERS | 1 +
> drivers/iio/adc/Kconfig | 11 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/ti-ads1262.c | 847 +++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 860 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e9248979b801..3ee4a2f80733 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -27006,6 +27006,7 @@ M: Kurt Borja <kuurtb@gmail.com>
> L: linux-iio@vger.kernel.org
> S: Maintained
> F: Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
> +F: drivers/iio/adc/ti-ads1262.c
>
> TI ADS7924 ADC DRIVER
> M: Hugo Villeneuve <hvilleneuve@dimonoff.com>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 990e7b3e7212..dbf76427912b 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1841,6 +1841,17 @@ config TI_ADS124S08
> This driver can also be built as a module. If so, the module will be
> called ti-ads124s08.
>
> +config TI_ADS1262
> + tristate "Texas Instruments ADS1262"
> + depends on SPI
> + select REGMAP
> + help
> + If you say yes here you get support for Texas Instruments ADS1262 and
> + ADS1263 ADC chips.
> +
> + This driver can also be built as a module. If so, the module will be
> + called ti-ads1262.
> +
> config TI_ADS1298
> tristate "Texas Instruments ADS1298"
> depends on SPI
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index dcec0abb03b7..f85b89859fe9 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -157,6 +157,7 @@ obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o
> obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o
> obj-$(CONFIG_TI_ADS112C14) += ti-ads112c14.o
> obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
> +obj-$(CONFIG_TI_ADS1262) += ti-ads1262.o
> obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o
> obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o
> obj-$(CONFIG_TI_ADS131M02) += ti-ads131m02.o
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> new file mode 100644
> index 000000000000..d78e5e3ae13e
> --- /dev/null
> +++ b/drivers/iio/adc/ti-ads1262.c
> @@ -0,0 +1,847 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Texas Instruments ADS1262 ADC driver
> + *
> + * Copyright (C) 2026 Kurt Borja <kuurtb@gmail.com>
> + */
> +
> +#include <linux/array_size.h>
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/compiler_attributes.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/interrupt.h>
> +#include <linux/lockdep.h>
> +#include <linux/math64.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.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/types.h>
> +
> +#include <asm/byteorder.h>
> +
> +#include <linux/iio/iio.h>
> +
> +#define ADS1262_OPCODE_NOP 0x00
> +#define ADS1262_OPCODE_RESET 0x06
> +#define ADS1262_OPCODE_START1 0x08
> +#define ADS1262_OPCODE_STOP1 0x0A
> +#define ADS1262_OPCODE_START2 0x0C
> +#define ADS1262_OPCODE_STOP2 0x0E
> +#define ADS1262_OPCODE_RDATA1 0x12
> +#define ADS1262_OPCODE_RDATA2 0x14
> +#define ADS1262_OPCODE_SYOCAL1 0x16
> +#define ADS1262_OPCODE_SYGCAL1 0x17
> +#define ADS1262_OPCODE_SFOCAL1 0x19
> +#define ADS1262_OPCODE_SYOCAL2 0x1B
> +#define ADS1262_OPCODE_SYGCAL2 0x1C
> +#define ADS1262_OPCODE_SFOCAL2 0x1E
> +#define ADS1262_OPCODE_RREG 0x20
> +#define ADS1262_OPCODE_WREG 0x40
> +
> +#define ADS1262_ID_REG 0x00
> +#define ADS1262_DEV_ID_MASK GENMASK(7, 5)
> +#define ADS1262_REV_ID_MASK GENMASK(4, 0)
> +
> +#define ADS1262_POWER_REG 0x01
> +#define ADS1262_POWER_RESET_MASK BIT(4)
> +#define ADS1262_POWER_VBIAS_MASK BIT(1)
> +#define ADS1262_POWER_INTREF_MASK BIT(0)
> +
> +#define ADS1262_INTERFACE_REG 0x02
> +#define ADS1262_INTERFACE_TIMEOUT_MASK BIT(3)
> +#define ADS1262_INTERFACE_STATUS_MASK BIT(2)
> +#define ADS1262_INTERFACE_CRC_MASK GENMASK(1, 0)
> +
> +#define ADS1262_MODE0_REG 0x03
> +#define ADS1262_MODE0_REFREV_MASK BIT(7)
> +#define ADS1262_MODE0_RUNMODE_MASK BIT(6)
> +#define ADS1262_MODE0_IDAC_CHOP_MASK BIT(5)
> +#define ADS1262_MODE0_INPUT_CHOP_MASK BIT(4)
> +#define ADS1262_MODE0_DELAY_MASK GENMASK(3, 0)
> +
> +#define ADS1262_MODE1_REG 0x04
> +#define ADS1262_MODE1_FILTER_MASK GENMASK(7, 5)
> +
> +#define ADS1262_MODE2_REG 0x05
> +#define ADS1262_MODE2_BYPASS_MASK BIT(7)
> +#define ADS1262_MODE2_GAIN_MASK GENMASK(6, 4)
> +#define ADS1262_MODE2_DR_MASK GENMASK(3, 0)
> +
> +#define ADS1262_INPMUX_REG 0x06
> +#define ADS1262_INPMUX_MUXP_MASK GENMASK(7, 4)
> +#define ADS1262_INPMUX_MUXN_MASK GENMASK(3, 0)
> +
> +#define ADS1262_OFCAL0_REG 0x07
> +#define ADS1262_OFCAL1_REG 0x08
> +#define ADS1262_OFCAL2_REG 0x09
> +#define ADS1262_FSCAL0_REG 0x0A
> +#define ADS1262_FSCAL1_REG 0x0B
> +#define ADS1262_FSCAL2_REG 0x0C
> +
> +#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 0xB
> +
> +#define ADS1262_IDACMAG_REG 0x0E
> +
> +#define ADS1262_REFMUX_REG 0x0F
> +#define ADS1262_TDACP_REG 0x10
> +#define ADS1262_TDACN_REG 0x11
> +#define ADS1262_GPIOCON_REG 0x12
> +#define ADS1262_GPIODIR_REG 0x13
> +#define ADS1262_GPIODAT_REG 0x14
> +#define ADS1262_ADC2CFG_REG 0x15
> +
> +#define ADS1262_ADC2MUX_REG 0x16
> +#define ADS1262_ADC2MUX_MUXP2_MASK GENMASK(7, 4)
> +#define ADS1262_ADC2MUX_MUXN2_MASK GENMASK(3, 0)
> +
> +#define ADS1262_ADC2OFC0_REG 0x17
> +#define ADS1262_ADC2OFC1_REG 0x18
> +#define ADS1262_ADC2FSC0_REG 0x19
> +#define ADS1262_ADC2FSC1_REG 0x1A
> +
> +#define ADS1262_REG_COUNT 0x1B
> +
> +#define ADS1262_MAX_CHANNEL_COUNT 16
> +#define ADS1262_MAX_REGMAP_WRITE 8
> +#define ADS1262_ADC1_RESOLUTION 32
> +
> +enum {
> + ADS1262_RUNMODE_CONTINUOUS,
> + ADS1262_RUNMODE_PULSE,
> +};
> +
> +enum {
> + ADS1262_FILTER_SINC1,
> + ADS1262_FILTER_SINC2,
> + ADS1262_FILTER_SINC3,
> + ADS1262_FILTER_SINC4,
> + ADS1262_FILTER_FIR,
> +};
> +
> +enum {
> + ADS1262_DR_2_5_SPS,
> + ADS1262_DR_5_SPS,
> + ADS1262_DR_10_SPS,
> + ADS1262_DR_16_6_SPS,
> + ADS1262_DR_20_SPS,
> + ADS1262_DR_50_SPS,
> + ADS1262_DR_60_SPS,
> + ADS1262_DR_100_SPS,
> + ADS1262_DR_400_SPS,
> + ADS1262_DR_1200_SPS,
> + ADS1262_DR_2400_SPS,
> + ADS1262_DR_4800_SPS,
> + ADS1262_DR_7200_SPS,
> + ADS1262_DR_14400_SPS,
> + ADS1262_DR_19200_SPS,
> + ADS1262_DR_38400_SPS,
> +};
> +
> +enum {
> + ADS1262_INPMUX_AIN0,
> + ADS1262_INPMUX_AIN1,
> + ADS1262_INPMUX_AIN2,
> + ADS1262_INPMUX_AIN3,
> + ADS1262_INPMUX_AIN4,
> + ADS1262_INPMUX_AIN5,
> + ADS1262_INPMUX_AIN6,
> + ADS1262_INPMUX_AIN7,
> + ADS1262_INPMUX_AIN8,
> + ADS1262_INPMUX_AIN9,
> + ADS1262_INPMUX_AINCOM,
> + ADS1262_INPMUX_TEMP,
> + ADS1262_INPMUX_AVDD,
> + ADS1262_INPMUX_DVDD,
> + ADS1262_INPMUX_TDAC,
> + ADS1262_INPMUX_FLOAT,
> +};
> +
> +struct ads1262_chip_info {
> + const char *name;
> +};
> +
> +struct ads1262 {
> + struct spi_device *spi;
> + struct regmap *regmap;
> + struct gpio_desc *reset_gpiod;
> + struct gpio_desc *start_gpiod;
> + unsigned long clk_rate;
> +
> + /* Protects channel state */
> + struct mutex chan_lock;
> + unsigned int num_channels;
> + struct completion drdy;
> +
> + /* Protects transfer buffers and concurrent SPI transfers */
> + struct mutex xfer_lock;
> +};
> +
> +static int ads1262_dev_cmd(struct ads1262 *st, u8 opcode)
I would add an action to the name, like write_cmd or send_cmd.
> +{
> + guard(mutex)(&st->xfer_lock);
> +
> + return spi_write_then_read(st->spi, &opcode, sizeof(opcode), NULL, 0);
> +}
> +
> +static int ads1262_dev_read_by_cmd(struct ads1262 *st, u8 cmd, __be32 *val)
> +{
> + guard(mutex)(&st->xfer_lock);
> +
> + return spi_write_then_read(st->spi, &cmd, sizeof(cmd), val, sizeof(*val));
> +}
> +
> +static int ads1262_dev_reset(struct ads1262 *st)
> +{
> + int ret;
> +
> + if (st->reset_gpiod) {
> + ret = gpiod_set_value_cansleep(st->reset_gpiod, 1);
> + if (ret)
> + return ret;
> +
> + /*
> + * The RESET pulse timing requirement is 4 clock cycles, at the
> + * minimum clock rate this is 4 microseconds.
> + */
> + fsleep(4);
How long do we have to hold reset before the chip powers down?
> +
> + ret = gpiod_set_value_cansleep(st->reset_gpiod, 0);
> + if (ret)
> + return ret;
> +
> + /*
> + * The RESET timing requirement is 8 clock cycles, at the
> + * minimum clock rate this is 8 microseconds
> + */
> + fsleep(8);
> + } else {
> + ret = ads1262_dev_cmd(st, ADS1262_OPCODE_RESET);
> + if (ret)
> + return ret;
> +
> + /*
> + * The RESET timing requirement is 8 clock cycles, at the
> + * minimum clock rate this is 8 microseconds
> + */
> + fsleep(8);
> + }
Sleep after reset is the same in both branches, so can be moved here.
> +
> + return 0;
> +}
> +
> +static int ads1262_dev_start(struct ads1262 *st)
> +{
> + int ret;
> +
> + if (st->start_gpiod)
> + ret = gpiod_set_value_cansleep(st->start_gpiod, 1);
> + else
> + ret = ads1262_dev_cmd(st, ADS1262_OPCODE_START1);
> +
> + return ret;
> +}
> +
> +static int ads1262_dev_stop(struct ads1262 *st)
> +{
> + int ret;
> +
> + if (st->start_gpiod)
> + ret = gpiod_set_value_cansleep(st->start_gpiod, 0);
> + else
> + ret = ads1262_dev_cmd(st, ADS1262_OPCODE_STOP1);
> +
> + return ret;
> +}
> +
> +static int ads1262_dev_start_one(struct ads1262 *st)
> +{
> + int ret;
> +
> + ret = ads1262_dev_start(st);
> + if (ret)
> + return ret;
> +
> + if (st->start_gpiod) {
> + /*
> + * The START pulse timing requirement is 4 clock cycles, at the
> + * minimum clock rate this is 4 microseconds.
> + */
> + fsleep(4);
> + return ads1262_dev_stop(st);
> + }
> +
> + return 0;
> +}
> +
> +static int ads1262_wait_for_conversion(struct ads1262 *st)
> +{
> + u64 max_lat_ms;
> + long ret;
> +
> + /*
> + * The first conversion latency is affected by the channel's data rate,
> + * filter, the configurable conversion delay and whether chop mode
> + * and/or IDAC rotation mode are enabled.
> + *
> + * The worst possible latency is calculated by taking the lowest data
> + * rate (2.5 SPS) and the sinc4 filter. This gives a latency of 1600 ms
> + * (Table 9-13). Then we scale it by the actual clock rate and multiply
> + * by 4 to account for chop and IDAC rotation modes (Equation 20).
> + */
> + max_lat_ms = 4 * div_u64(mul_u32_u32(1600, 7372800), st->clk_rate);
These are constant values, so don't need mul_u32_u32(). Also, given the wide
range of possible sampling rates, I would include the current sampling rate
in the calculation. No need to wate 1.6 seconds for something that should
take a few 10s of microseconds.
> +
> + ret = wait_for_completion_interruptible_timeout(&st->drdy,
> + msecs_to_jiffies(max_lat_ms));
> + if (ret < 0)
> + return ret;
> + if (!ret)
> + return -ETIMEDOUT;
> +
> + return 0;
> +}
> +
> +static int ads1262_channel_enable(struct ads1262 *st,
> + const struct iio_chan_spec *spec)
> +{
> + u8 val;
> +
> + guard(mutex)(&st->xfer_lock);
> + guard(mutex)(&st->chan_lock);
> +
> + val = FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
> + FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel);
> + return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
> + ADS1262_INPMUX_MUXN_MASK |
> + ADS1262_INPMUX_MUXP_MASK, val);
> +}
> +
> +static int ads1262_set_runmode(struct ads1262 *st, u8 runmode)
> +{
> + guard(mutex)(&st->xfer_lock);
> +
> + return regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
> + ADS1262_MODE0_RUNMODE_MASK,
> + FIELD_PREP(ADS1262_MODE0_RUNMODE_MASK, runmode));
> +}
> +
> +static int ads1262_channel_read(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *spec, __be32 *val)
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> + int ret;
> +
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + ret = ads1262_set_runmode(st, ADS1262_RUNMODE_PULSE);
> + if (ret)
> + return ret;
> +
> + ret = ads1262_channel_enable(st, spec);
> + if (ret)
> + return ret;
> +
> + reinit_completion(&st->drdy);
> +
> + ret = ads1262_dev_start_one(st);
> + if (ret)
> + return ret;
> +
> + ret = ads1262_wait_for_conversion(st);
> + if (ret)
> + return ret;
> +
> + return ads1262_dev_read_by_cmd(st, ADS1262_OPCODE_RDATA1, val);
> +}
> +
> +static int ads1262_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val,
> + int *val2, long mask)
> +{
> + __be32 raw;
> + int ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + ret = ads1262_channel_read(indio_dev, chan, &raw);
> + if (ret)
> + return ret;
> + *val = sign_extend32(be32_to_cpu(raw), ADS1262_ADC1_RESOLUTION - 1);
It is already a 32-bit value, so sign extend doesn't make sense.
> +
> + return IIO_VAL_INT;
> +
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg,
> + unsigned int writeval, unsigned int *readval)
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> +
> + guard(mutex)(&st->xfer_lock);
> +
> + if (readval)
> + return regmap_read_bypassed(st->regmap, reg, readval);
Don't trust the cache? :-)
> +
> + return regmap_write(st->regmap, reg, writeval);
> +}
> +
> +static const struct iio_info ads1262_iio_info = {
> + .read_raw = ads1262_read_raw,
> + .debugfs_reg_access = ads1262_debugfs_reg_access,
> +};
> +
> +static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
> +{
> + struct ads1262 *st = dev_id;
> +
> + complete(&st->drdy);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int ads1262_dev_configure(struct ads1262 *st)
> +{
> + struct device *dev = &st->spi->dev;
> + int ret;
> +
> + ret = ads1262_dev_reset(st);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to reset device\n");
> +
> + guard(mutex)(&st->xfer_lock);
> +
> + ret = regmap_clear_bits(st->regmap, ADS1262_POWER_REG,
> + ADS1262_POWER_RESET_MASK);
> + if (ret)
> + return ret;
> +
> + ret = regmap_clear_bits(st->regmap, ADS1262_INTERFACE_REG,
> + ADS1262_INTERFACE_STATUS_MASK |
> + ADS1262_INTERFACE_CRC_MASK);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static bool ads1262_readable_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case ADS1262_ID_REG ... ADS1262_ADC2FSC1_REG:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static bool ads1262_writeable_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case ADS1262_POWER_REG ... ADS1262_ADC2FSC1_REG:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static bool ads1262_volatile_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case ADS1262_POWER_REG:
> + case ADS1262_OFCAL0_REG ... ADS1262_FSCAL2_REG:
> + case ADS1262_GPIODAT_REG:
> + case ADS1262_ADC2OFC0_REG ... ADS1262_ADC2FSC1_REG:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static const struct reg_default ads1262_reg_defaults[] = {
> + { ADS1262_INTERFACE_REG,
> + FIELD_PREP_CONST(ADS1262_INTERFACE_STATUS_MASK, true) |
> + FIELD_PREP_CONST(ADS1262_INTERFACE_CRC_MASK, true) },
> + { ADS1262_MODE0_REG, 0x00 },
> + { ADS1262_MODE1_REG,
> + FIELD_PREP_CONST(ADS1262_MODE1_FILTER_MASK, ADS1262_FILTER_FIR) },
> + { ADS1262_MODE2_REG,
> + FIELD_PREP_CONST(ADS1262_MODE2_DR_MASK, ADS1262_DR_20_SPS) },
> + { ADS1262_INPMUX_REG,
> + FIELD_PREP_CONST(ADS1262_INPMUX_MUXN_MASK, ADS1262_INPMUX_AIN1) },
> + { ADS1262_IDACMUX_REG,
> + FIELD_PREP_CONST(ADS1262_IDACMUX_MUX2_MASK, ADS1262_IDACMUX_NO_CONN) |
> + FIELD_PREP_CONST(ADS1262_IDACMUX_MUX1_MASK, ADS1262_IDACMUX_NO_CONN) },
> + { ADS1262_IDACMAG_REG, 0x00 },
> + { ADS1262_REFMUX_REG, 0x00 },
> + { ADS1262_TDACP_REG, 0x00 },
> + { ADS1262_TDACN_REG, 0x00 },
> + { ADS1262_GPIOCON_REG, 0x00 },
> + { ADS1262_GPIODIR_REG, 0x00 },
> + { ADS1262_ADC2CFG_REG, 0x00 },
> + { ADS1262_ADC2MUX_REG,
> + FIELD_PREP_CONST(ADS1262_ADC2MUX_MUXN2_MASK, ADS1262_INPMUX_AIN1) },
> +};
> +
> +static const struct regmap_config ads1262_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .writeable_reg = ads1262_writeable_reg,
> + .readable_reg = ads1262_readable_reg,
> + .volatile_reg = ads1262_volatile_reg,
> + .reg_defaults = ads1262_reg_defaults,
> + .num_reg_defaults = ARRAY_SIZE(ads1262_reg_defaults),
> + .max_register = ADS1262_ADC2FSC1_REG,
> + .can_sleep = true,
> + .cache_type = REGCACHE_MAPLE,
> +};
> +
> +static int ads1262_regmap_read(void *context, const void *reg_buf,
> + size_t reg_size, void *val_buf, size_t val_size)
> +{
> + struct ads1262 *st = context;
> + u8 tx[2];
> +
> + lockdep_assert_held(&st->xfer_lock);
> +
> + /*
> + * The register read operation uses a two byte command header followed
> + * by the register data:
> + *
> + * byte 0: RREG opcode | register address
> + * byte 1: number of registers to transfer, minus one
> + * byte 2..: register data
> + */
> + memcpy(tx, reg_buf, 1);
> + tx[0] |= ADS1262_OPCODE_RREG;
> + tx[1] = val_size - 1;
> +
> + return spi_write_then_read(st->spi, tx, sizeof(tx), val_buf, val_size);
> +}
> +
> +static int ads1262_regmap_gather_write(void *context, const void *reg_buf,
> + size_t reg_size, const void *val_buf,
> + size_t val_size)
> +{
> + struct ads1262 *st = context;
> + u8 tx[ADS1262_MAX_REGMAP_WRITE + 2];
> +
> + lockdep_assert_held(&st->xfer_lock);
> +
> + /*
> + * The register write operation uses a two byte command header followed
> + * by the register data:
> + *
> + * byte 0: WREG opcode | register address
> + * byte 1: number of registers to transfer, minus one
> + * byte 2..: register data
> + */
> + memcpy(tx, reg_buf, 1);
> + tx[0] |= ADS1262_OPCODE_WREG;
> + tx[1] = val_size - 1;
> + memcpy(&tx[2], val_buf, val_size);
> +
> + return spi_write_then_read(st->spi, tx, 2 + val_size, NULL, 0);
> +}
> +
> +static int ads1262_regmap_write(void *context, const void *data, size_t count)
> +{
> + return ads1262_regmap_gather_write(context, data, 1, data + 1,
> + count - 1);
> +}
> +
> +static const struct regmap_bus ads1262_regmap_bus = {
> + .read = ads1262_regmap_read,
> + .gather_write = ads1262_regmap_gather_write,
> + .write = ads1262_regmap_write,
> + .reg_format_endian_default = REGMAP_ENDIAN_BIG,
> + .val_format_endian_default = REGMAP_ENDIAN_BIG,
> + .max_raw_write = ADS1262_MAX_REGMAP_WRITE,
> +};
> +
> +static int ads1262_gpio_setup(struct ads1262 *st)
> +{
> + struct device *dev = &st->spi->dev;
> +
> + st->start_gpiod = devm_gpiod_get_optional(dev, "start", GPIOD_OUT_LOW);
> + if (IS_ERR(st->start_gpiod))
> + return dev_err_probe(dev, PTR_ERR(st->start_gpiod),
> + "failed to get start GPIO\n");
> +
> + st->reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
Usually, we would just request this with GPIOD_OUT_HIGH in the reset function
(assuming reset only runs during probe, which is how it is usually done).
Then we don't need to keep a handle the gpiod in struct ads1262 and it saves
us a function call to set it high to perform the reset.
> + if (IS_ERR(st->reset_gpiod))
> + return dev_err_probe(dev, PTR_ERR(st->reset_gpiod),
> + "failed to get reset GPIO\n");
> +
> + /*
> + * The power transition timing requirement is 65536 clock cycles, at the
> + * minimum clock frequency this is 65536 microseconds.
> + */
> + fsleep(65536);
Sleep seems out of place here. Should be right after regulator enables in
ads1262_supply_setup().
> +
> + return 0;
> +}
> +
> +static int ads1262_parse_channel_node(struct ads1262 *st,
> + struct iio_chan_spec *spec,
> + struct fwnode_handle *node)
> +{
> + struct device *dev = &st->spi->dev;
> + u32 pins[2];
> + int ret;
> +
> + if (fwnode_property_present(node, "single-channel")) {
> + ret = fwnode_property_read_u32(node, "single-channel", &pins[0]);
> + if (ret)
> + return dev_err_probe(dev, ret, "%s: failed to read single-channel\n",
> + fwnode_get_name(node));
> +
> + pins[1] = ADS1262_INPMUX_AINCOM;
> + fwnode_property_read_u32(node, "common-mode-channel", &pins[1]);
Why ignoring error?
> + } else if (fwnode_property_present(node, "diff-channels")) {
> + ret = fwnode_property_read_u32_array(node, "diff-channels", pins,
> + ARRAY_SIZE(pins));
> + if (ret)
> + return dev_err_probe(dev, ret, "%s: failed to read diff-channels\n",
> + fwnode_get_name(node));
> +
> + if (pins[0] <= ADS1262_INPMUX_AINCOM || pins[1] <= ADS1262_INPMUX_AINCOM)
> + spec->differential = true;
TDAC input would be differential too. (But I don't think it should be requried to
be declared in the devicetree.)
> + } else {
> + return dev_err_probe(dev, -ENXIO,
Usually we just return -EINVAL and rely on the error message. This isn't a "no
such device or address" problem. Same applies to other ENXIO in this driver.
> + "%s: one of single-channel or diff-channels is required\n",
> + fwnode_get_name(node));
> + }
> +
> + if (pins[0] >= ADS1262_INPMUX_FLOAT || pins[1] >= ADS1262_INPMUX_FLOAT)
> + return dev_err_probe(dev, -EINVAL, "%s: input channels not in range\n",
> + fwnode_get_name(node));
> +
> + if ((pins[0] >= ADS1262_INPMUX_TEMP ||
> + pins[1] >= ADS1262_INPMUX_TEMP) && pins[0] != pins[1])
> + return dev_err_probe(dev, -EINVAL,
> + "%s: monitor channels must be selected symmetrically\n",
> + fwnode_get_name(node));
> +
> + spec->channel = pins[0];
> + spec->channel2 = pins[1];
> +
> + return 0;
> +}
> +
> +static int ads1262_parse_channels(struct iio_dev *indio_dev)
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> + struct device *dev = &st->spi->dev;
> + struct iio_chan_spec *specs;
> + unsigned long used_regs = 0;
> + int num_specs;
> + u32 reg;
> + int ret;
> +
> + st->num_channels = device_get_named_child_node_count(dev, "channel");
> + if (!st->num_channels)
> + return dev_err_probe(dev, -ENXIO, "no 'channel' nodes configured\n");
> + if (st->num_channels > ADS1262_MAX_CHANNEL_COUNT)
> + return dev_err_probe(dev, -EINVAL, "too many channels\n");
> +
> + /* Account for the timestamp channel */
> + num_specs = st->num_channels + 1;
> + specs = devm_kcalloc(dev, num_specs, sizeof(*specs), GFP_KERNEL);
> + if (!specs)
> + return -ENOMEM;
> +
> + device_for_each_named_child_node_scoped(dev, node, "channel") {
> + ret = fwnode_property_read_u32(node, "reg", ®);
> + if (ret)
> + return dev_err_probe(dev, ret, "%s: failed to read channel reg\n",
> + fwnode_get_name(node));
> + if (reg >= st->num_channels)
> + return dev_err_probe(dev, -EINVAL, "%s: reg out of range\n",
> + fwnode_get_name(node));
> +
> + static_assert(ADS1262_MAX_CHANNEL_COUNT < BITS_PER_LONG);
> + if (__test_and_set_bit(reg, &used_regs))
> + return dev_err_probe(dev, -EINVAL, "%s: duplicated channel reg\n",
> + fwnode_get_name(node));
> +
> + specs[reg].scan_index = reg;
> + specs[reg].scan_type = (struct iio_scan_type) {
> + .format = IIO_SCAN_FORMAT_SIGNED_INT,
> + .realbits = ADS1262_ADC1_RESOLUTION,
> + .storagebits = 32,
> + .endianness = IIO_BE,
> + };
> +
> + ret = ads1262_parse_channel_node(st, &specs[reg], node);
> + if (ret)
> + return ret;
> +
> + if (specs[reg].channel == ADS1262_INPMUX_TEMP)
> + specs[reg].type = IIO_TEMP;
> + else
> + specs[reg].type = IIO_VOLTAGE;
> +
> + if (specs[reg].channel != ADS1262_INPMUX_TEMP)
> + specs[reg].indexed = true;
> +
> + specs[reg].info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> + }
If we are going to use reg to determine the scan index, we need to
make sure there are no holes in specs that didn't get filled in.
device_for_each_named_child_node_scoped() will skip `status = "disabled"`
channels, so this could be a possibility.
> +
> + specs[num_specs - 1] = IIO_CHAN_SOFT_TIMESTAMP(num_specs - 1);
> +
> + indio_dev->channels = specs;
> + indio_dev->num_channels = num_specs;
> +
> + return 0;
> +}
> +
> +static int ads1262_supply_setup(struct ads1262 *st)
> +{
> + struct device *dev = &st->spi->dev;
> + int ret;
> +
> + ret = devm_regulator_get_enable(dev, "dvdd");
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
> +
> + ret = devm_regulator_get_enable(dev, "avdd");
> + if (ret < 0)
> + 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)
> + return dev_err_probe(dev, ret, "failed to get avss regulator\n");
> +
> + return 0;
> +}
> +
> +static int ads1262_spi_probe(struct spi_device *spi)
> +{
> + const struct ads1262_chip_info *info;
> + struct device *dev = &spi->dev;
> + struct iio_dev *indio_dev;
> + struct ads1262 *st;
> + unsigned long rate;
> + struct clk *clk;
> + int irq;
> + int ret;
> +
> + info = spi_get_device_match_data(spi);
> + if (!info)
> + return -EINVAL;
> +
> + indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> + if (!indio_dev)
> + return -ENOMEM;
> + indio_dev->name = info->name;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> + indio_dev->info = &ads1262_iio_info;
> +
> + st = iio_priv(indio_dev);
> + st->spi = spi;
> + init_completion(&st->drdy);
> +
> + ret = devm_mutex_init(dev, &st->chan_lock);
> + if (ret)
> + return ret;
> + ret = devm_mutex_init(dev, &st->xfer_lock);
> + if (ret)
> + return ret;
> +
> + ret = ads1262_parse_channels(indio_dev);
> + if (ret)
> + return ret;
> +
> + clk = devm_clk_get_optional_enabled(dev, NULL);
> + if (IS_ERR(clk))
> + return dev_err_probe(dev, PTR_ERR(clk), "failed to get external clock\n");
> +
> + rate = clk_get_rate(clk);
> + if (clk && !rate)
> + return dev_err_probe(dev, -ENXIO, "failed to get clock rate\n");
> + st->clk_rate = rate ? rate : 7372800;
I would use a macro to self-document that 7372800 is the internal clock rate.
> +
> + ret = ads1262_supply_setup(st);
> + if (ret)
> + return ret;
Should probably apply power before enabling clock since most chips
don't like voltage applied to I/O pins before powering on.
> +
> + ret = ads1262_gpio_setup(st);
> + if (ret)
> + return ret;
> +
> + st->regmap = devm_regmap_init(dev, &ads1262_regmap_bus, st,
> + &ads1262_regmap_config);
> + if (IS_ERR(st->regmap))
> + return PTR_ERR(st->regmap);
> +
> + ret = ads1262_dev_configure(st);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to configure device\n");
> +
> + /*
> + * REVISIT: This chip has software polling capabilities, which could be
> + * used to stop depending on the 'drdy' IRQ.
> + *
> + * Additionally, the MISO pin also can be used as a DRDY IRQ, in which
> + * case the interrupt would be named 'dout-drdy', but requires a lot of
> + * timing and synchronization considerations to be reliable.
> + */
> + irq = fwnode_irq_get_byname(dev_fwnode(dev), "drdy");
> + if (irq < 0)
> + return dev_err_probe(dev, irq,
> + "the 'drdy' IRQ is currently required for operation\n");
> +
> + ret = devm_request_irq(dev, irq, ads1262_irq_handler, IRQF_NO_THREAD,
> + info->name, st);
> + if (ret)
> + return ret;
> +
> + return devm_iio_device_register(dev, indio_dev);
> +}
> +
next prev parent reply other threads:[~2026-08-08 18:39 UTC|newest]
Thread overview: 42+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-08 3:58 [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support Kurt Borja
2026-08-08 3:58 ` [PATCH v3 1/9] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
2026-08-08 4:08 ` sashiko-bot
2026-08-08 18:38 ` David Lechner
2026-08-09 8:26 ` Kurt Borja
2026-08-10 16:42 ` David Lechner
2026-08-10 8:46 ` Bartosz Golaszewski
2026-08-08 3:58 ` [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver Kurt Borja
2026-08-08 4:11 ` sashiko-bot
2026-08-08 18:39 ` David Lechner [this message]
2026-08-09 8:26 ` Kurt Borja
2026-08-10 16:42 ` David Lechner
2026-08-10 18:48 ` Andy Shevchenko
2026-08-08 22:28 ` Uwe Kleine-König
2026-08-09 16:24 ` Kurt Borja
2026-08-08 3:58 ` [PATCH v3 3/9] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
2026-08-08 4:11 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
2026-08-09 8:27 ` Kurt Borja
2026-08-08 3:58 ` [PATCH v3 4/9] iio: adc: ti-ads1262: support per-channel reference and gain Kurt Borja
2026-08-08 18:39 ` David Lechner
2026-08-09 8:28 ` Kurt Borja
2026-08-08 3:58 ` [PATCH v3 5/9] iio: adc: ti-ads1262: support input chopping Kurt Borja
2026-08-08 18:39 ` David Lechner
2026-08-08 3:58 ` [PATCH v3 6/9] iio: adc: ti-ads1262: support excitation currents Kurt Borja
2026-08-08 4:13 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
2026-08-08 3:58 ` [PATCH v3 7/9] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
2026-08-08 4:09 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
2026-08-09 8:28 ` Kurt Borja
2026-08-10 16:31 ` David Lechner
2026-08-08 3:58 ` [PATCH v3 8/9] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
2026-08-08 4:11 ` sashiko-bot
2026-08-08 18:40 ` David Lechner
2026-08-09 8:28 ` Kurt Borja
2026-08-08 3:58 ` [PATCH v3 9/9] iio: adc: ti-ads1262: support common mode supplies Kurt Borja
2026-08-08 18:40 ` David Lechner
2026-08-09 8:29 ` Kurt Borja
2026-08-08 18:37 ` [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support David Lechner
2026-08-09 8:29 ` Kurt Borja
2026-08-10 16:42 ` David Lechner
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=ad95afb4-e667-4290-8878-fdd7067f20fe@baylibre.com \
--to=dlechner@baylibre.com \
--cc=andy@kernel.org \
--cc=brgl@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=jic23@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=kuurtb@gmail.com \
--cc=linusw@kernel.org \
--cc=linux-gpio@vger.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.