From: Jonathan Cameron <jic23@kernel.org>
To: Sayyad Abid <sayyad.abid16@gmail.com>
Cc: linux-iio@vger.kernel.org, lars@metafoo.de, robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org, dlechner@baylibre.com,
nuno.sa@analog.com, javier.carrasco.cruz@gmail.com,
olivier.moysan@foss.st.com, gstols@baylibre.com,
tgamblin@baylibre.com, alisadariana@gmail.com,
eblanc@baylibre.com, antoniu.miclaus@analog.com,
andriy.shevchenko@linux.intel.com, stefan.popa@analog.com,
ramona.gradinariu@analog.com, herve.codina@bootlin.com,
tobias.sperling@softing.com, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: Re: [RFC PATCH 1/5] iio: adc: ti-ads1262.c: add initial driver for TI ADS1262 ADC
Date: Sun, 4 May 2025 17:20:14 +0100 [thread overview]
Message-ID: <20250504172014.2c807456@jic23-huawei> (raw)
In-Reply-To: <20250501100043.325423-2-sayyad.abid16@gmail.com>
On Thu, 1 May 2025 15:30:39 +0530
Sayyad Abid <sayyad.abid16@gmail.com> wrote:
> Add the core driver file `ti-ads1262.c` for the TI ADS1262 ADC.
> This initial version implements basic IIO functionality for device
> probe via SPI and reading raw voltage samples from input channels.
>
> Signed-off-by: Sayyad Abid <sayyad.abid16@gmail.com>
A few additional comments from me (some probably overlap!)
Jonathan
> ---
> drivers/iio/adc/ti-ads1262.c | 438 +++++++++++++++++++++++++++++++++++
> 1 file changed, 438 insertions(+)
> create mode 100644 drivers/iio/adc/ti-ads1262.c
>
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> new file mode 100644
> index 000000000000..ef34c528ffeb
> --- /dev/null
> +++ b/drivers/iio/adc/ti-ads1262.c
> @@ -0,0 +1,438 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * IIO driver for Texas Instruments ADS1662 32-bit ADC
> + *
> + * Datasheet: https://www.ti.com/product/ADS1262
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/delay.h>
> +#include <linux/spi/spi.h>
> +#include <linux/unaligned.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/triggered_buffer.h>
I'm not seeing these being used yet.
> +/**
> + * struct ads1262_private - ADS1262 ADC private data structure
> + * @spi: SPI device structure
> + * @reset_gpio: GPIO descriptor for reset pin
> + * @prev_channel: Previously selected channel for MUX configuration
> + * @cmd_buffer: Buffer for SPI command transfers
> + * @rx_buffer: Buffer for SPI data reception
> + */
> +struct ads1262_private {
> + struct spi_device *spi;
> + struct gpio_desc *reset_gpio;
> + u8 prev_channel;
> + u8 cmd_buffer[ADS1262_SPI_CMD_BUFFER_SIZE];
You seem to use cmd_buffer directly in spi calls so that needs the __aligned(IIO_DMA_MINALIGN)
marking. You can probably just have one on that as long as you don't ever edit that
whilst rx_buffer is still in use.
> + u8 rx_buffer[ADS1262_SPI_RDATA_BUFFER_SIZE] __aligned(IIO_DMA_MINALIGN);
> +};
> +
> +#define ADS1262_CHAN(index) \
> +{ \
> + .type = IIO_VOLTAGE, \
> + .indexed = 1, \
> + .channel = index, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> + .scan_index = index, \
> + .scan_type = { \
> + .sign = 's', \
> + .realbits = ADS1262_BITS_PER_SAMPLE, \
> + .storagebits = 32, \
> + .endianness = IIO_CPU \
> + }, \
> +}
> +
> +#define ADS1262_DIFF_CHAN(index, pos_channel, neg_channel) \
> +{ \
> + .type = IIO_VOLTAGE, \
> + .indexed = 1, \
> + .channel = pos_channel, \
> + .channel2 = neg_channel, \
> + .differential = 1, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> + .scan_index = index, \
> + .scan_type = { \
> + .sign = 's', \
> + .realbits = ADS1262_BITS_PER_SAMPLE, \
> + .storagebits = 32, \
> + .endianness = IIO_CPU \
> + }, \
> +}
> +
> +#define ADS1262_TEMP_CHAN(index) \
> +{ \
> + .type = IIO_TEMP, \
> + .indexed = 1, \
> + .channel = index, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> + BIT(IIO_CHAN_INFO_SCALE) | \
> + BIT(IIO_CHAN_INFO_SAMP_FREQ), \
Is the sampling frequency independent for each channel?
Or is this a case where we want them separate as the overall sampling frequency
is directly related to how many channels are enabled? Odd to just have
this for the temp channel.
> + .scan_index = index, \
> + .scan_type = { \
> + .sign = 's', \
> + .realbits = ADS1262_BITS_PER_SAMPLE, \
> + .storagebits = 32, \
> + .endianness = IIO_BE, \
> + }, \
> +}
> +
> +static const struct iio_chan_spec ads1262_channels[] = {
> + /* Single ended channels*/
> + ADS1262_CHAN(0),
> + ADS1262_CHAN(1),
> + ADS1262_CHAN(2),
> + ADS1262_CHAN(3),
> + ADS1262_CHAN(4),
> + ADS1262_CHAN(5),
> + ADS1262_CHAN(6),
> + ADS1262_CHAN(7),
> + ADS1262_CHAN(8),
> + ADS1262_CHAN(9),
> + /* The channel at index 10 is AINCOM, which is the common ground
Comment syntax
/*
* The channel
> + * of the ADC. It is not a valid channel for the user.
> + */
> +
> + /* Temperature and Monitor channels */
> + ADS1262_TEMP_CHAN(11), /* TEMP SENSOR */
> + ADS1262_CHAN(12), /* AVDD MON */
> + ADS1262_CHAN(13), /* DVDD MON */
> + ADS1262_CHAN(14), /* TDAC TEST */
> + /* Differential channels */
> + ADS1262_DIFF_CHAN(15, 0, 1), /* AIN0 - AIN1 */
> + ADS1262_DIFF_CHAN(16, 2, 3), /* AIN2 - AIN3 */
> + ADS1262_DIFF_CHAN(17, 4, 5), /* AIN4 - AIN5 */
> + ADS1262_DIFF_CHAN(18, 6, 7), /* AIN6 - AIN7 */
> + ADS1262_DIFF_CHAN(19, 8, 9), /* AIN8 - AIN9 */
> +};
> +
> +static int ads1262_write_cmd(struct ads1262_private *priv, u8 command)
> +{
> + struct spi_transfer xfer = {
> + .tx_buf = priv->cmd_buffer,
> + .rx_buf = priv->rx_buffer,
> + .len = ADS1262_SPI_RDATA_BUFFER_SIZE,
> + .speed_hz = ADS1262_CLK_RATE_HZ,
> + };
> +
> + priv->cmd_buffer[0] = command;
> +
> + return spi_sync_transfer(priv->spi, &xfer, 1);
> +}
> +
> +static int ads1262_reg_write(void *context, unsigned int reg, unsigned int val)
> +{
> + struct ads1262_private *priv = context;
> +
> + priv->cmd_buffer[0] = ADS1262_CMD_WREG | reg;
> + priv->cmd_buffer[1] = 0;
> + priv->cmd_buffer[2] = val;
> +
> + return spi_write(priv->spi, &priv->cmd_buffer[0], 3);
> +}
> +
> +static int ads1262_reg_read(void *context, unsigned int reg)
> +{
> + struct ads1262_private *priv = context;
> + struct spi_transfer reg_read_xfer = {
> + .tx_buf = priv->cmd_buffer,
> + .rx_buf = priv->cmd_buffer,
> + .len = 3,
> + .speed_hz = ADS1262_CLK_RATE_HZ,
> + };
> + int ret;
> +
> + priv->cmd_buffer[0] = ADS1262_CMD_RREG | reg;
> + priv->cmd_buffer[1] = 0;
> + priv->cmd_buffer[2] = 0;
> +
> + ret = spi_sync_transfer(priv->spi, ®_read_xfer, 1);
> + if (ret)
> + return ret;
> +
> + return 0;
return spi_sync_transfer()
> +}
> +
> +static int ads1262_reset(struct iio_dev *indio_dev)
> +{
> + struct ads1262_private *priv = iio_priv(indio_dev);
> +
> + if (priv->reset_gpio) {
> + gpiod_set_value(priv->reset_gpio, 0);
> + usleep_range(200, 300);
> + gpiod_set_value(priv->reset_gpio, 1);
return 0;
}
return ads1262_write_cmd();
> + } else {
> + return ads1262_write_cmd(priv, ADS1262_CMD_RESET);
> + }
> + return 0;
> +}
> +
> +static int ads1262_init(struct iio_dev *indio_dev)
> +{
> + struct ads1262_private *priv = iio_priv(indio_dev);
> + int ret = 0;
> +
> + ret = ads1262_reset(indio_dev);
> + if (ret)
> + return ret;
> +
> + /* 10 milliseconds settling time for the ADC to stabilize */
> + fsleep(ADS1262_SETTLE_TIME_USECS);
This is probably better placed in the reset function as it is part
of that overall action. I added a comment asking why there wasn't one there
before seeing this.
> +
> + /* Clearing the RESET bit in the power register to detect ADC reset */
> + ret = ads1262_reg_write(priv, ADS1262_REG_POWER, ADS1262_INTREF_ENABLE);
I'd be tempted to do this in the _reset() function as well given
it is really just another part of that.
> + if (ret)
> + return ret;
> +
> + /* Setting the ADC to one-shot conversion mode */
> + ret = ads1262_reg_write(priv, ADS1262_REG_MODE0, ADS1262_MODE0_ONE_SHOT);
> + if (ret)
> + return ret;
> +
> + ret = ads1262_reg_read(priv, ADS1262_REG_INPMUX);
> + if (ret)
> + return ret;
> +
> + priv->prev_channel = priv->cmd_buffer[2];
> +
> + return ret;
> +}
> +
> +static int ads1262_get_samp_freq(struct ads1262_private *priv, int *val)
> +{
> + unsigned long samp_freq;
> + int ret;
> +
> + ret = ads1262_reg_read(priv, ADS1262_REG_MODE2);
> + if (ret)
> + return ret;
> +
> + samp_freq = priv->cmd_buffer[2];
There are multiple calls that use this structure. I'd kind of
expect to see some locking to prevent them racing against each other.
Note sysfs etc provide no prevention against such races.
> +
> + *val = (samp_freq & ADS1262_MASK_MODE2_DR);
No brackets needed.
> +
> + return IIO_VAL_INT;
> +}
> +
> +/**
> + * ads1262_read - Read a single sample from the ADC
> + * @priv: Pointer to the ADS1262 private data structure
> + * @chan: Pointer to the IIO channel specification
> + * @val: Pointer to store the read value
> + *
> + * Reads a single sample from the specified ADC channel. For differential
> + * channels, it sets up the MUX with both channels. For single-ended channels,
> + * it uses the channel number and AINCOM (0x0A).
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +static int ads1262_read(struct ads1262_private *priv,
> + struct iio_chan_spec const *chan, int *val)
> +{
> + u8 mux_value;
> + int ret;
> +
> + if (chan->differential) {
> + mux_value = (chan->channel << 4) | chan->channel2;
FIELD_PREP() and appropriate masks would make it easier to see what is
going on here.
> + } else {
> + /* For single-ended channels, use the channel number on one end
/*
* For single-ended channels...
> + * and AINCOM (0x0A) on the other end
> + */
> + mux_value = (chan->channel << 4) | 0x0A;
> + }
> +
> + if (mux_value != priv->prev_channel) {
Noting stops this racing with another read of a different channel.
Given you are trying to keep state consistent you need
a mutex in your priv structure to serialize them.
> + ret = ads1262_write_cmd(priv, ADS1262_CMD_STOP1);
> + if (ret)
> + return ret;
> +
> + ret = ads1262_reg_write(priv, ADS1262_REG_INPMUX, mux_value);
> + if (ret)
> + return ret;
> +
> + priv->prev_channel = mux_value;
> + }
> +
> + ret = ads1262_write_cmd(priv, ADS1262_CMD_START1);
> + if (ret)
> + return ret;
> +
> + usleep_range(1000, 2000);
> +
> + *val = sign_extend64(get_unaligned_be32(priv->rx_buffer + 1),
> + ADS1262_BITS_PER_SAMPLE - 1);
> +
> + return 0;
> +}
> +
> +static int ads1262_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val, int *val2, long mask)
> +{
> + struct ads1262_private *spi = iio_priv(indio_dev);
> + s64 temp;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + ads1262_read(spi, chan, val);
> + return IIO_VAL_INT;
> + case IIO_CHAN_INFO_SCALE:
> + switch (chan->type) {
> + case IIO_VOLTAGE:
> + *val = ADS1262_VOLTAGE_INT_REF_uV;
> + *val2 = chan->scan_type.realbits;
> + return IIO_VAL_FRACTIONAL_LOG2;
> + case IIO_TEMP:
> + temp = (s64)ADS1262_VOLTAGE_INT_REF_uV * MILLI;
> + temp /= ADS1262_TEMP_SENSITIVITY_uV_per_C;
> + *val = temp;
> + *val2 = chan->scan_type.realbits;
> + return IIO_VAL_FRACTIONAL_LOG2;
> + default:
> + return -EINVAL;
> + }
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + return ads1262_get_samp_freq(spi, val);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static const struct iio_info ads1262_info = {
> + .read_raw = ads1262_read_raw,
> +};
> +
> +static void ads1262_stop(void *ptr)
> +{
> + struct ads1262_private *adc = ptr;
> +
> + ads1262_write_cmd(adc, ADS1262_CMD_STOP1);
> +}
> +
> +static int ads1262_probe(struct spi_device *spi)
> +{
> + struct ads1262_private *adc;
> + struct iio_dev *indio_dev;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + adc = iio_priv(indio_dev);
> + adc->spi = spi;
> +
> + spi->mode = SPI_MODE_1;
> + spi->max_speed_hz = ADS1262_SPI_BUS_SPEED_SLOW;
David covered these...
> + spi_set_drvdata(spi, indio_dev);
> +
> + indio_dev->dev.parent = &spi->dev;
> + indio_dev->name = spi_get_device_id(spi)->name;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> + indio_dev->channels = ads1262_channels;
> + indio_dev->num_channels = ARRAY_SIZE(ads1262_channels);
> + indio_dev->info = &ads1262_info;
> +
> + ret = ads1262_reg_read(adc, ADS1262_REG_ID);
> + if (ret)
> + return ret;
> +
> + if (adc->rx_buffer[2] != ADS1262_REG_ID)
> + dev_err_probe(&spi->dev, -EINVAL, "Wrong device ID 0x%x\n",
> + adc->rx_buffer[2]);
> +
> + ret = devm_add_action_or_reset(&spi->dev, ads1262_stop, adc);
Which call is this 'undoing'. Generally I'd expect to only see
a devm callback register after what it is setting up.
Here I'm assuming that stop is undoing something in init?
If so register this after ads1262_init()
> + if (ret)
> + return ret;
> +
> + ret = ads1262_init(indio_dev);
> + if (ret)
> + return ret;
> +
> + return devm_iio_device_register(&spi->dev, indio_dev);
> +}
> +
> +static struct spi_device_id ads1262_id_table[] = {
> + { "ads1262" },
> + {}
{ }
> +};
> +MODULE_DEVICE_TABLE(spi, ads1262_id_table);
> +
> +static const struct of_device_id ads1262_of_match[] = {
> + { .compatible = "ti,ads1262" },
> + {},
{ }
Random choice I made for IIO as I'd like consistency.
> +};
> +MODULE_DEVICE_TABLE(of, ads1262_of_match);
next prev parent reply other threads:[~2025-05-04 16:20 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-05-01 10:00 [RFC PATCH 0/5] iio: adc: Add initial support for TI ADS1262 Sayyad Abid
2025-05-01 10:00 ` [RFC PATCH 1/5] iio: adc: ti-ads1262.c: add initial driver for TI ADS1262 ADC Sayyad Abid
2025-05-01 17:37 ` David Lechner
2025-05-02 10:07 ` Andy Shevchenko
2025-05-04 16:20 ` Jonathan Cameron [this message]
2025-05-01 10:00 ` [RFC PATCH 2/5] iio: adc: Kconfig: add Kconfig entry for TI ADS1262 driver Sayyad Abid
2025-05-01 17:37 ` David Lechner
2025-05-04 15:55 ` Jonathan Cameron
2025-05-01 10:00 ` [RFC PATCH 3/5] iio: adc: Makefile: add compile support " Sayyad Abid
2025-05-01 10:00 ` [RFC PATCH 4/5] MAINTAINERS: add entry for TI ADS1262 ADC driver Sayyad Abid
2025-05-04 16:02 ` Jonathan Cameron
2025-05-01 10:00 ` [RFC PATCH 5/5] dt-bindings: iio: adc: add bindings for TI ADS1262 Sayyad Abid
2025-05-01 14:51 ` Conor Dooley
2025-05-01 17:37 ` David Lechner
2025-05-04 16:01 ` Jonathan Cameron
2025-05-01 18:20 ` [RFC PATCH 0/5] iio: adc: Add initial support " David Lechner
2025-05-02 9:52 ` Andy Shevchenko
2025-05-04 16:20 ` Jonathan Cameron
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=20250504172014.2c807456@jic23-huawei \
--to=jic23@kernel.org \
--cc=alisadariana@gmail.com \
--cc=andriy.shevchenko@linux.intel.com \
--cc=antoniu.miclaus@analog.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=dlechner@baylibre.com \
--cc=eblanc@baylibre.com \
--cc=gstols@baylibre.com \
--cc=herve.codina@bootlin.com \
--cc=javier.carrasco.cruz@gmail.com \
--cc=krzk+dt@kernel.org \
--cc=lars@metafoo.de \
--cc=linux-iio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=nuno.sa@analog.com \
--cc=olivier.moysan@foss.st.com \
--cc=ramona.gradinariu@analog.com \
--cc=robh@kernel.org \
--cc=sayyad.abid16@gmail.com \
--cc=stefan.popa@analog.com \
--cc=tgamblin@baylibre.com \
--cc=tobias.sperling@softing.com \
/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