devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] Add support for ADS131E0x ADC family
@ 2020-11-27 19:42 tomislav.denis
  2020-11-27 19:42 ` [PATCH 1/2] iio: adc: Add driver for Texas Instruments " tomislav.denis
  2020-11-27 19:42 ` [PATCH 2/2] bindings: iio: adc: Add documentation for ADS131E0x ADC driver tomislav.denis
  0 siblings, 2 replies; 9+ messages in thread
From: tomislav.denis @ 2020-11-27 19:42 UTC (permalink / raw)
  To: jic23; +Cc: linux-iio, devicetree, tomislav.denis

From: Tomislav Denis <tomislav.denis@avl.com>

This patchset adds support for Texas Instruments ADS131E0x
analog-to-digital converters family.

Datasheet: https://www.ti.com/lit/ds/symlink/ads131e08.pdf

Tomislav Denis (2):
  iio: adc: Add driver for Texas Instruments ADS131E0x ADC family
  bindings: iio: adc: Add documentation for ADS131E0x ADC driver

 .../devicetree/bindings/iio/adc/ti,ads131e08.yaml  | 145 ++++
 MAINTAINERS                                        |   7 +
 drivers/iio/adc/Kconfig                            |  12 +
 drivers/iio/adc/Makefile                           |   1 +
 drivers/iio/adc/ti-ads131e08.c                     | 826 +++++++++++++++++++++
 5 files changed, 991 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
 create mode 100644 drivers/iio/adc/ti-ads131e08.c

-- 
2.7.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/2] iio: adc: Add driver for Texas Instruments ADS131E0x ADC family
  2020-11-27 19:42 [PATCH 0/2] Add support for ADS131E0x ADC family tomislav.denis
@ 2020-11-27 19:42 ` tomislav.denis
  2020-11-28 13:02   ` Jonathan Cameron
  2020-11-28 23:58   ` kernel test robot
  2020-11-27 19:42 ` [PATCH 2/2] bindings: iio: adc: Add documentation for ADS131E0x ADC driver tomislav.denis
  1 sibling, 2 replies; 9+ messages in thread
From: tomislav.denis @ 2020-11-27 19:42 UTC (permalink / raw)
  To: jic23; +Cc: linux-iio, devicetree, tomislav.denis

From: Tomislav Denis <tomislav.denis@avl.com>

The ADS131E0x are a family of multichannel, simultaneous sampling,
24-bit, delta-sigma, analog-to-digital converters (ADCs) with a
built-in programmable gain amplifier (PGA), internal reference
and an onboard oscillator.

Signed-off-by: Tomislav Denis <tomislav.denis@avl.com>
---
 MAINTAINERS                    |   6 +
 drivers/iio/adc/Kconfig        |  12 +
 drivers/iio/adc/Makefile       |   1 +
 drivers/iio/adc/ti-ads131e08.c | 826 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 845 insertions(+)
 create mode 100644 drivers/iio/adc/ti-ads131e08.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 924616d..28bc5f9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17220,6 +17220,12 @@ M:	Robert Richter <rric@kernel.org>
 S:	Odd Fixes
 F:	drivers/gpio/gpio-thunderx.c
 
+TI ADS131E0X ADC SERIES DRIVER
+M:	Tomislav Denis <tomislav.denis@avl.com>
+L:	linux-iio@vger.kernel.org
+S:	Maintained
+F:	drivers/iio/adc/ti-ads131e08.c
+
 TI AM437X VPFE DRIVER
 M:	"Lad, Prabhakar" <prabhakar.csengg@gmail.com>
 L:	linux-media@vger.kernel.org
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 91ae905..b82b6e0 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1140,6 +1140,18 @@ config TI_ADS124S08
 	  This driver can also be built as a module. If so, the module will be
 	  called ti-ads124s08.
 
+config TI_ADS131E08
+	tristate "Texas Instruments ADS131E08"
+	depends on SPI && OF
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	  Say yes here you get support for Texas Instruments ADS131E08 and
+	  ADS131E04 chips.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called ti-ads131e08.
+
 config TI_AM335X_ADC
 	tristate "TI's AM335X ADC driver"
 	depends on MFD_TI_AM335X_TSCADC && HAS_DMA
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 90f94ad..b578315 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -102,6 +102,7 @@ obj-$(CONFIG_TI_ADS7950) += ti-ads7950.o
 obj-$(CONFIG_TI_ADS8344) += ti-ads8344.o
 obj-$(CONFIG_TI_ADS8688) += ti-ads8688.o
 obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
+obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o
 obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
 obj-$(CONFIG_TI_TLC4541) += ti-tlc4541.o
 obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o
diff --git a/drivers/iio/adc/ti-ads131e08.c b/drivers/iio/adc/ti-ads131e08.c
new file mode 100644
index 0000000..5799ab3
--- /dev/null
+++ b/drivers/iio/adc/ti-ads131e08.c
@@ -0,0 +1,826 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Texas Instruments ADS131E0x 4-, 6-, and 8-Channel ADCs
+ *
+ * Copyright (c) 2020 AVL DiTEST GmbH
+ *   Tomislav Denis <tomislav.denis@avl.com>
+ *
+ * Datasheet: https://www.ti.com/lit/ds/symlink/ads131e08.pdf
+ */
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/bitfield.h>
+#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <asm/unaligned.h>
+
+/* Commands */
+#define ADS131E08_CMD_RESET		0x06
+#define ADS131E08_CMD_START		0x08
+#define ADS131E08_CMD_STOP		0x0A
+#define ADS131E08_CMD_OFFSETCAL		0x1A
+#define ADS131E08_CMD_SDATAC		0x11
+#define ADS131E08_CMD_RDATA		0x12
+#define ADS131E08_CMD_RREG(r)		(0x20 | (r & 0x1F))
+#define ADS131E08_CMD_WREG(r)		(0x40 | (r & 0x1F))
+
+/* Registers */
+#define ADS131E08_ADR_CFG1R		0x01
+#define ADS131E08_ADR_CFG3R		0x03
+#define ADS131E08_ADR_CH0R		0x05
+
+/* Configuration register 1 */
+#define ADS131E08_CFG1R_DR_MASK		GENMASK(2, 0)
+
+/* Configuration register 3 */
+#define ADS131E08_CFG3R_PDB_REFBUF_MASK	BIT(7)
+#define ADS131E08_CFG3R_VREF_4V_MASK	BIT(5)
+
+/* Channel settings register */
+#define ADS131E08_CHR_GAIN_MASK		GENMASK(6, 4)
+#define ADS131E08_CHR_PWD_MASK		BIT(7)
+
+/* ADC  misc */
+#define ADS131E08_DEFAULT_DATA_RATE	1
+#define ADS131E08_DEFAULT_PGA_GAIN	1
+
+#define ADS131E08_VREF_2V4_MV		2400
+#define ADS131E08_VREF_4V_MV		4000
+
+#define ADS131E08_WAIT_RESET_CYCLES	18
+#define ADS131E08_WAIT_SDECODE_CYCLES	4
+#define ADS131E08_WAIT_OFFSETCAL_MS	153
+#define ADS131E08_MAX_SETTLING_TIME_MS	6
+
+#define ADS131E08_NUM_CHN_MAX		8
+
+#define ADS131E08_NUM_STATUS_BYTES	3
+#define ADS131E08_NUM_DATA_BYTES_MAX	3
+#define ADS131E08_NUM_DATA_BYTES(dr)	(((dr) >= 32) ? 2 : 3)
+#define ADS131E08_NUM_DATA_BITS(dr)	(ADS131E08_NUM_DATA_BYTES(dr) * 8)
+#define ADS131E08_NUM_STORAGE_BYTES	4
+#define ADS131E08_NUM_STORAGE_BITS	(ADS131E08_NUM_STORAGE_BYTES * 8)
+
+enum ads131e08_ids {
+	ads131e04,
+	ads131e08,
+};
+
+struct ads131e08_info {
+	unsigned int max_channels;
+};
+
+struct ads131e08_state {
+	const struct ads131e08_info *info;
+	struct spi_device *spi;
+	struct iio_trigger *trig;
+	struct clk *adc_clk;
+	struct regulator *vref_reg;
+	unsigned int data_rate;
+	unsigned int pga_gain;
+	unsigned int vref_mv;
+	unsigned int sdecode_delay_us;
+	unsigned int reset_delay_us;
+	unsigned int readback_len;
+	struct completion completion;
+
+	/*
+	 * Add extra one padding byte to be able to access the last channel
+	 * value using u32 pointer
+	 */
+	u8 rx_buf[ADS131E08_NUM_STATUS_BYTES +
+		ADS131E08_NUM_CHN_MAX * ADS131E08_NUM_DATA_BYTES_MAX + 1];
+	u8 tmp_buf[ADS131E08_NUM_CHN_MAX * ADS131E08_NUM_DATA_BYTES_MAX];
+};
+
+static const struct ads131e08_info ads131e08_info_tbl[] = {
+	[ads131e04] = {
+		.max_channels = 4,
+	},
+	[ads131e08] = {
+		.max_channels = 8,
+	},
+};
+
+struct ads131e08_data_rate_desc {
+	unsigned int rate;  /* data rate in kSPS */
+	u8 reg;             /* reg value */
+};
+
+static const struct ads131e08_data_rate_desc ads131e08_data_rate_tbl[] = {
+	{ .rate = 64,   .reg = 0x00 },
+	{ .rate = 32,   .reg = 0x01 },
+	{ .rate = 16,   .reg = 0x02 },
+	{ .rate = 8,    .reg = 0x03 },
+	{ .rate = 4,    .reg = 0x04 },
+	{ .rate = 2,    .reg = 0x05 },
+	{ .rate = 1,    .reg = 0x06 },
+};
+
+struct ads131e08_pga_gain_desc {
+	unsigned int gain;  /* PGA gain value */
+	u8 reg;             /* reg value */
+};
+
+static const struct ads131e08_pga_gain_desc ads131e08_pga_gain_tbl[] = {
+	{ .gain = 1,   .reg = 0x01 },
+	{ .gain = 2,   .reg = 0x02 },
+	{ .gain = 4,   .reg = 0x04 },
+	{ .gain = 8,   .reg = 0x05 },
+	{ .gain = 12,  .reg = 0x06 },
+};
+
+static int ads131e08_exec_cmd(struct ads131e08_state *st, u8 cmd)
+{
+	int ret;
+
+	ret = spi_write(st->spi, &cmd, 1);
+	if (ret) {
+		dev_err(&st->spi->dev,
+			"%s: SPI write failed for cmd: %02x\n", __func__, cmd);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ads131e08_read_reg(struct ads131e08_state *st, u8 reg)
+{
+	u8 rx;
+	int ret;
+
+	u8 tx_buf[] = {
+		ADS131E08_CMD_RREG(reg),
+		0,
+	};
+
+	struct spi_transfer transfer[] = {
+		{
+			.tx_buf = tx_buf,
+			.len = ARRAY_SIZE(tx_buf),
+			.delay_usecs = st->sdecode_delay_us,
+		}, {
+			.rx_buf = &rx,
+			.len = 1,
+		},
+	};
+
+	ret = spi_sync_transfer(st->spi, transfer, ARRAY_SIZE(transfer));
+	if (ret) {
+		dev_err(&st->spi->dev, "%s: SPI transfer failed\n", __func__);
+		return ret;
+	}
+
+	return rx;
+}
+
+static int ads131e08_write_reg(struct ads131e08_state *st, u8 reg, u8 value)
+{
+	int ret;
+
+	u8 tx_buf[] = {
+		ADS131E08_CMD_WREG(reg),
+		0,
+		value,
+	};
+
+	struct spi_transfer transfer[] = {
+		{
+			.tx_buf = tx_buf,
+			.len = ARRAY_SIZE(tx_buf),
+			.delay_usecs = st->sdecode_delay_us,
+		}
+	};
+
+	ret = spi_sync_transfer(st->spi, transfer, ARRAY_SIZE(transfer));
+	if (ret) {
+		dev_err(&st->spi->dev, "%s: SPI transfer failed\n", __func__);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ads131e08_read_data(struct ads131e08_state *st, int rx_len)
+{
+	int ret;
+
+	u8 tx_buf[] = {
+		ADS131E08_CMD_RDATA,
+	};
+
+	struct spi_transfer transfer[] = {
+		{
+			.tx_buf = tx_buf,
+			.len = ARRAY_SIZE(tx_buf),
+		}, {
+			.rx_buf = &st->rx_buf,
+			.len = rx_len,
+		},
+	};
+
+	ret = spi_sync_transfer(st->spi, transfer, ARRAY_SIZE(transfer));
+	if (ret) {
+		dev_err(&st->spi->dev, "%s: SPI transfer failed\n", __func__);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ads131e08_set_data_rate(struct ads131e08_state *st, int data_rate)
+{
+	int ret, i, reg;
+
+	for (i = 0; i < ARRAY_SIZE(ads131e08_data_rate_tbl); ++i) {
+		if (ads131e08_data_rate_tbl[i].rate == data_rate)
+			break;
+	}
+
+	if (i == ARRAY_SIZE(ads131e08_data_rate_tbl)) {
+		dev_err(&st->spi->dev, "invalid data rate value\n");
+		return -EINVAL;
+	}
+
+	reg = ads131e08_read_reg(st, ADS131E08_ADR_CFG1R);
+	if (reg < 0)
+		return reg;
+
+	reg &= ~ADS131E08_CFG1R_DR_MASK;
+	reg |= FIELD_PREP(ADS131E08_CFG1R_DR_MASK,
+		ads131e08_data_rate_tbl[i].reg);
+
+	ret = ads131e08_write_reg(st, ADS131E08_ADR_CFG1R, reg);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ads131e08_set_pga_gain(struct ads131e08_state *st,
+	unsigned int channel, unsigned int pga_gain)
+{
+	int ret, i, reg;
+
+	for (i = 0; i < ARRAY_SIZE(ads131e08_pga_gain_tbl); ++i) {
+		if (ads131e08_pga_gain_tbl[i].gain == pga_gain)
+			break;
+	}
+
+	if (i == ARRAY_SIZE(ads131e08_pga_gain_tbl)) {
+		dev_err(&st->spi->dev, "invalid PGA gain value\n");
+		return -EINVAL;
+	}
+
+	reg = ads131e08_read_reg(st, ADS131E08_ADR_CH0R + channel);
+	if (reg < 0)
+		return reg;
+
+	reg &= ~ADS131E08_CHR_GAIN_MASK;
+	reg |= FIELD_PREP(ADS131E08_CHR_GAIN_MASK,
+		ads131e08_pga_gain_tbl[i].reg);
+
+	ret = ads131e08_write_reg(st, ADS131E08_ADR_CH0R + channel, reg);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ads131e08_power_down_channel(struct ads131e08_state *st,
+	unsigned int channel, bool value)
+{
+	int ret, reg;
+
+	reg = ads131e08_read_reg(st, ADS131E08_ADR_CH0R + channel);
+	if (reg < 0)
+		return reg;
+
+	reg &= ~ADS131E08_CHR_PWD_MASK;
+	reg |= FIELD_PREP(ADS131E08_CHR_PWD_MASK, value);
+
+	ret = ads131e08_write_reg(st, ADS131E08_ADR_CH0R + channel, reg);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ads131e08_config_reference_voltage(struct ads131e08_state *st)
+{
+	int ret, reg;
+
+	reg = ads131e08_read_reg(st, ADS131E08_ADR_CFG3R);
+	if (reg < 0)
+		return reg;
+
+	if (st->vref_reg) {
+		/* Use external reference voltage */
+		reg &= ~ADS131E08_CFG3R_PDB_REFBUF_MASK;
+	} else {
+		/* Use internal reference voltage */
+		reg &= ~ADS131E08_CFG3R_PDB_REFBUF_MASK |
+			~ADS131E08_CFG3R_VREF_4V_MASK;
+		reg |= FIELD_PREP(ADS131E08_CFG3R_PDB_REFBUF_MASK, 1);
+		reg |= FIELD_PREP(ADS131E08_CFG3R_VREF_4V_MASK,
+			st->vref_mv == ADS131E08_VREF_4V_MV);
+	}
+
+	ret = ads131e08_write_reg(st, ADS131E08_ADR_CFG3R, reg);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ads131e08_initial_config(struct iio_dev *indio_dev)
+{
+	struct ads131e08_state *st = iio_priv(indio_dev);
+	const struct iio_chan_spec *channel = indio_dev->channels;
+	int ret, i;
+	u8 active_channels = 0;
+
+	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_RESET);
+	if (ret)
+		return ret;
+
+	udelay(st->reset_delay_us);
+
+	/* Disable read data in continuous mode (enabled by default) */
+	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_SDATAC);
+	if (ret)
+		return ret;
+
+	ret = ads131e08_set_data_rate(st, st->data_rate);
+	if (ret)
+		return ret;
+
+	ret = ads131e08_config_reference_voltage(st);
+	if (ret)
+		return ret;
+
+	for (i = 0;  i < indio_dev->num_channels; ++i) {
+		ret = ads131e08_set_pga_gain(st, channel->channel,
+			st->pga_gain);
+		if (ret)
+			return ret;
+
+		active_channels |= BIT(channel->channel);
+		channel++;
+	}
+
+	/* Power down unused channels */
+	for (i = 0; i < st->info->max_channels; ++i) {
+		if (!(active_channels & BIT(i))) {
+			ret = ads131e08_power_down_channel(st, i, true);
+			if (ret)
+				return ret;
+		}
+	}
+
+	/* Request channel offset calibration */
+	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_OFFSETCAL);
+	if (ret)
+		return ret;
+
+	/*
+	 * Channel offset calibration is triggered with first START command.
+	 * Since calibration take more time than settling operation,
+	 * this causes timeout error when command START is sent first
+	 * time (e.g. first call of the ads131e08_read_direct method).
+	 * To avoid this problem offset calibration is triggered here.
+	 */
+	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_START);
+	if (ret)
+		return ret;
+
+	msleep(ADS131E08_WAIT_OFFSETCAL_MS);
+
+	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_STOP);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ads131e08_read_direct(struct iio_dev *indio_dev,
+	struct iio_chan_spec const *channel, int *value)
+{
+	struct ads131e08_state *st = iio_priv(indio_dev);
+	int ret;
+	u8 num_bits, *src;
+
+	reinit_completion(&st->completion);
+
+	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_START);
+	if (ret)
+		return ret;
+
+	ret = wait_for_completion_timeout(&st->completion,
+		msecs_to_jiffies(ADS131E08_MAX_SETTLING_TIME_MS));
+	if (!ret)
+		return -ETIMEDOUT;
+
+	ret = ads131e08_read_data(st, st->readback_len);
+	if (ret)
+		return ret;
+
+	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_STOP);
+	if (ret)
+		return ret;
+
+	num_bits = channel->scan_type.realbits;
+	src = st->rx_buf + ADS131E08_NUM_STATUS_BYTES +
+		channel->channel * ADS131E08_NUM_DATA_BYTES(st->data_rate);
+
+	*value = sign_extend32(
+		get_unaligned_be32(src) >> (32 - num_bits), num_bits - 1);
+
+	return ret;
+}
+
+static int ads131e08_read_raw(struct iio_dev *indio_dev,
+	struct iio_chan_spec const *channel, int *value,
+	int *value2, long mask)
+{
+	struct ads131e08_state *st = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = iio_device_claim_direct_mode(indio_dev);
+		if (ret)
+			return ret;
+
+		ret = ads131e08_read_direct(indio_dev, channel, value);
+		iio_device_release_direct_mode(indio_dev);
+		if (ret)
+			return ret;
+
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		*value = st->vref_mv / st->pga_gain;
+		*value2 = channel->scan_type.realbits - 1;
+
+		return IIO_VAL_FRACTIONAL_LOG2;
+	}
+
+	return -EINVAL;
+}
+
+static int ads131e08_validate_trigger(struct iio_dev *indio_dev,
+	struct iio_trigger *trig)
+{
+	struct ads131e08_state *st = iio_priv(indio_dev);
+
+	if (st->trig != trig)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int ads131e08_debugfs_reg_access(struct iio_dev *indio_dev,
+	unsigned int reg, unsigned int writeval, unsigned int *readval)
+{
+	struct ads131e08_state *st = iio_priv(indio_dev);
+
+	if (readval) {
+		int ret = ads131e08_read_reg(st, (u8)reg);
+		*readval = ret;
+		return ret;
+	}
+
+	return ads131e08_write_reg(st, (u8)reg, (u8)writeval);
+}
+
+static const struct iio_info ads131e08_iio_info = {
+	.read_raw = ads131e08_read_raw,
+	.validate_trigger = &ads131e08_validate_trigger,
+	.debugfs_reg_access = &ads131e08_debugfs_reg_access,
+};
+
+static int ads131e08_set_trigger_state(struct iio_trigger *trig, bool state)
+{
+	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+	struct ads131e08_state *st = iio_priv(indio_dev);
+	u8 cmd = state ? ADS131E08_CMD_START : ADS131E08_CMD_STOP;
+
+	return ads131e08_exec_cmd(st, cmd);
+}
+
+static const struct iio_trigger_ops ads131e08_trigger_ops = {
+	.validate_device = &iio_trigger_validate_own_device,
+	.set_trigger_state = &ads131e08_set_trigger_state,
+};
+
+static irqreturn_t ads131e08_trigger_handler(int irq, void *private)
+{
+	struct iio_poll_func *pf = private;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct ads131e08_state *st = iio_priv(indio_dev);
+	int ret;
+	unsigned int num_dbytes = ADS131E08_NUM_DATA_BYTES(st->data_rate);
+	unsigned int chn, i = 0;
+
+	ret = ads131e08_read_data(st, st->readback_len);
+	if (ret)
+		goto out;
+
+	for_each_set_bit(chn, indio_dev->active_scan_mask,
+		indio_dev->masklength) {
+		memcpy(st->tmp_buf + i * ADS131E08_NUM_STORAGE_BYTES,
+			st->rx_buf + ADS131E08_NUM_STATUS_BYTES + chn * num_dbytes,
+			num_dbytes);
+		i++;
+	}
+
+	iio_push_to_buffers(indio_dev, st->tmp_buf);
+
+out:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t ads131e08_interrupt(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct ads131e08_state *st = iio_priv(indio_dev);
+
+	if (iio_buffer_enabled(indio_dev))
+		iio_trigger_poll_chained(st->trig);
+	else
+		complete(&st->completion);
+
+	return IRQ_HANDLED;
+}
+
+static int ads131e08_parse_dt(struct iio_dev *indio_dev,
+	struct device_node *node)
+{
+	struct ads131e08_state *st = iio_priv(indio_dev);
+	struct iio_chan_spec *channels, *channel;
+	int vrefsel, num_channels;
+	struct property *prop;
+	const __be32 *cur;
+	u32 value;
+
+	if (of_property_read_u32(node, "ti,datarate", &st->data_rate))
+		st->data_rate = ADS131E08_DEFAULT_DATA_RATE;
+
+	if (of_property_read_u32(node, "ti,gain", &st->pga_gain))
+		st->pga_gain = ADS131E08_DEFAULT_PGA_GAIN;
+
+	if (of_property_read_u32(node, "ti,vref-sel", &vrefsel))
+		vrefsel = 0;
+
+	switch (vrefsel) {
+	case 0:
+		st->vref_mv = ADS131E08_VREF_2V4_MV;
+		break;
+	case 1:
+		st->vref_mv = ADS131E08_VREF_4V_MV;
+		break;
+	case 2:
+		st->vref_mv = 0;
+		break;
+	default:
+		st->vref_mv = ADS131E08_VREF_2V4_MV;
+	}
+
+	num_channels = of_property_count_elems_of_size(node,
+		"ti,adc-channels", sizeof(u32));
+	if (num_channels == 0) {
+		dev_err(&st->spi->dev, "no valid channels property\n");
+		return -ENODEV;
+	}
+
+	if (num_channels > st->info->max_channels) {
+		dev_err(&st->spi->dev, "num of channels out of range\n");
+		return -EINVAL;
+	}
+
+	channels = devm_kcalloc(&st->spi->dev, num_channels,
+		sizeof(struct iio_chan_spec), GFP_KERNEL);
+	if (!channels)
+		return -ENOMEM;
+
+	channel = channels;
+	of_property_for_each_u32(node, "ti,adc-channels", prop, cur, value) {
+		channel->type = IIO_VOLTAGE;
+		channel->indexed = 1;
+		channel->channel = value;
+		channel->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
+		channel->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
+		channel->scan_index = value;
+		channel->scan_type.sign = 's';
+		channel->scan_type.realbits =
+			ADS131E08_NUM_DATA_BITS(st->data_rate);
+		channel->scan_type.storagebits = ADS131E08_NUM_STORAGE_BITS;
+		channel->scan_type.shift = 8;
+		channel->scan_type.endianness = IIO_BE;
+		channel++;
+	}
+
+	indio_dev->channels = channels;
+	indio_dev->num_channels = num_channels;
+
+	return 0;
+}
+
+static int ads131e08_probe(struct spi_device *spi)
+{
+	struct iio_dev *indio_dev;
+	struct ads131e08_state *st;
+	int ret;
+	unsigned long adc_clk_hz, adc_clk_ns;
+
+	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+	if (!indio_dev) {
+		dev_err(&spi->dev, "failed to allocate IIO device\n");
+		return -ENOMEM;
+	}
+
+	spi_set_drvdata(spi, indio_dev);
+
+	st = iio_priv(indio_dev);
+	st->info = &ads131e08_info_tbl[spi_get_device_id(spi)->driver_data];
+	st->spi = spi;
+
+	ret = ads131e08_parse_dt(indio_dev, spi->dev.of_node);
+	if (ret)
+		return ret;
+
+	st->readback_len = ADS131E08_NUM_STATUS_BYTES +
+		ADS131E08_NUM_DATA_BYTES(st->data_rate) *
+		st->info->max_channels;
+
+	indio_dev->name = spi_get_device_id(spi)->name;
+	indio_dev->dev.parent = &spi->dev;
+	indio_dev->dev.of_node = spi->dev.of_node;
+	indio_dev->info = &ads131e08_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	init_completion(&st->completion);
+
+	if (spi->irq) {
+		ret = devm_request_threaded_irq(&spi->dev, spi->irq,
+			NULL, ads131e08_interrupt,
+			IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+			spi->dev.driver->name, indio_dev);
+		if (ret) {
+			dev_err(&spi->dev, "failed to allocate IRQ\n");
+			return ret;
+		}
+	} else {
+		dev_err(&spi->dev, "data ready IRQ missing\n");
+		return -ENODEV;
+	}
+
+	st->trig = devm_iio_trigger_alloc(&spi->dev, "%s-dev%d",
+		indio_dev->name, indio_dev->id);
+	if (!st->trig) {
+		dev_err(&spi->dev, "failed to allocate IIO trigger\n");
+		return -ENOMEM;
+	}
+
+	st->trig->ops = &ads131e08_trigger_ops;
+	st->trig->dev.parent = &spi->dev;
+	iio_trigger_set_drvdata(st->trig, indio_dev);
+	ret = devm_iio_trigger_register(&spi->dev, st->trig);
+	if (ret) {
+		dev_err(&spi->dev, "failed to register IIO trigger\n");
+		goto err_disable_reg;
+	}
+
+	indio_dev->trig = iio_trigger_get(st->trig);
+
+	ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev,
+		NULL, &ads131e08_trigger_handler, NULL);
+	if (ret) {
+		dev_err(&spi->dev, "failed to setup IIO buffer\n");
+		return ret;
+	}
+
+	st->adc_clk = devm_clk_get(&spi->dev, "adc-clk");
+	if (IS_ERR(st->adc_clk)) {
+		dev_err(&spi->dev, "failed to get the ADC clock\n");
+		return PTR_ERR(st->adc_clk);
+	}
+
+	ret = clk_prepare_enable(st->adc_clk);
+	if (ret) {
+		dev_err(&spi->dev, "failed to prepare/enable the ADC clock\n");
+		return ret;
+	}
+
+	adc_clk_hz = clk_get_rate(st->adc_clk);
+	if (!adc_clk_hz) {
+		dev_err(&spi->dev, "failed to get the ADC clock rate\n");
+		ret = -EINVAL;
+		goto err_disable_clk;
+	}
+
+	adc_clk_ns = NSEC_PER_SEC / adc_clk_hz;
+	st->sdecode_delay_us = DIV_ROUND_UP(
+		ADS131E08_WAIT_SDECODE_CYCLES * adc_clk_ns, 1000);
+	st->reset_delay_us = DIV_ROUND_UP(
+		ADS131E08_WAIT_RESET_CYCLES * adc_clk_ns, 1000);
+
+	if (st->vref_mv == 0) {
+		st->vref_reg = devm_regulator_get(&spi->dev, "vref");
+		if (IS_ERR(st->vref_reg)) {
+			dev_err(&spi->dev,
+				"failed to get vref regulator\n");
+			ret = PTR_ERR(st->vref_reg);
+			goto err_disable_clk;
+		}
+
+		ret = regulator_enable(st->vref_reg);
+		if (ret) {
+			dev_err(&spi->dev,
+				"failed to enable external vref supply\n");
+			goto err_disable_clk;
+		}
+
+		ret = regulator_get_voltage(st->vref_reg);
+		if (ret < 0) {
+			dev_err(&spi->dev, "failed to get vref voltage\n");
+			goto err_disable_reg;
+		}
+
+		st->vref_mv = ret / 1000;
+	}
+
+	ret = ads131e08_initial_config(indio_dev);
+	if (ret) {
+		dev_err(&spi->dev, "initial configuration failed\n");
+		goto err_disable_reg;
+	}
+
+	ret = devm_iio_device_register(&spi->dev, indio_dev);
+	if (ret) {
+		dev_err(&spi->dev, "failed to register IIO device\n");
+		goto err_disable_reg;
+	}
+
+	return 0;
+
+err_disable_reg:
+	regulator_disable(st->vref_reg);
+
+err_disable_clk:
+	clk_disable_unprepare(st->adc_clk);
+
+	return ret;
+}
+
+static int ads131e08_remove(struct spi_device *spi)
+{
+	struct iio_dev *indio_dev = spi_get_drvdata(spi);
+	struct ads131e08_state *st = iio_priv(indio_dev);
+
+	if (st->vref_reg)
+		regulator_disable(st->vref_reg);
+
+	clk_disable_unprepare(st->adc_clk);
+
+	return 0;
+}
+
+static const struct of_device_id ads131e08_of_match[] = {
+	{ .compatible = "ti,ads131e04", },
+	{ .compatible = "ti,ads131e08", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, ads131e08_matches);
+
+static const struct spi_device_id ads131e08_id[] = {
+	{ "ads131e04",      ads131e04},
+	{ "ads131e08",      ads131e08},
+	{}
+};
+MODULE_DEVICE_TABLE(spi, ads131e08_id);
+
+static struct spi_driver ads131e08_driver = {
+	.driver = {
+		.name = "ads131e08",
+		.of_match_table = ads131e08_of_match,
+	},
+	.probe = ads131e08_probe,
+	.remove = ads131e08_remove,
+	.id_table = ads131e08_id,
+};
+module_spi_driver(ads131e08_driver);
+
+MODULE_AUTHOR("Tomislav Denis <tomislav.denis@avl.com>");
+MODULE_DESCRIPTION("Driver for ADS131E0x ADC family");
+MODULE_LICENSE("GPL v2");
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 2/2] bindings: iio: adc: Add documentation for ADS131E0x ADC driver
  2020-11-27 19:42 [PATCH 0/2] Add support for ADS131E0x ADC family tomislav.denis
  2020-11-27 19:42 ` [PATCH 1/2] iio: adc: Add driver for Texas Instruments " tomislav.denis
@ 2020-11-27 19:42 ` tomislav.denis
  2020-11-28 12:34   ` Jonathan Cameron
  2020-11-30 17:36   ` Rob Herring
  1 sibling, 2 replies; 9+ messages in thread
From: tomislav.denis @ 2020-11-27 19:42 UTC (permalink / raw)
  To: jic23; +Cc: linux-iio, devicetree, tomislav.denis

From: Tomislav Denis <tomislav.denis@avl.com>

Add a device tree binding documentation for Texas Instruments
ADS131E0x ADC family driver.

Signed-off-by: Tomislav Denis <tomislav.denis@avl.com>
---
 .../devicetree/bindings/iio/adc/ti,ads131e08.yaml  | 145 +++++++++++++++++++++
 MAINTAINERS                                        |   1 +
 2 files changed, 146 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml

diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
new file mode 100644
index 0000000..92da193
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
@@ -0,0 +1,145 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/ti,ads131e08.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments ADS131E0x 4-, 6-, and 8-Channel ADCs
+
+maintainers:
+  - Tomislav Denis <tomislav.denis@avl.com>
+
+description: |
+  The ADS131E0x are a family of multichannel, simultaneous sampling,
+  24-bit, delta-sigma, analog-to-digital converters (ADCs) with a
+  built-in programmable gain amplifier (PGA), internal reference
+  and an onboard oscillator.
+  The communication with ADC chip is via the SPI bus (mode 1).
+
+  https://www.ti.com/lit/ds/symlink/ads131e08.pdf
+
+properties:
+  compatible:
+    enum:
+      - ti,ads131e04
+      - ti,ads131e08
+
+  reg:
+    description: |
+      SPI chip select number
+    maxItems: 1
+
+  spi-cpha: true
+
+  clocks:
+    description: |
+      Device tree identifier to the clock source (2.048 MHz)
+      Note: clock source is selected using CLKSEL pin
+    maxItems: 1
+
+  clock-names:
+    items:
+      - const: adc-clk
+
+  interrupts:
+    description: |
+      IRQ line for the ADC data ready
+    maxItems: 1
+
+  vref-supply:
+    description: |
+      Optional external voltage reference. Has to be supplied, if
+      ti,vref-sel equals 2
+
+  ti,vref-sel:
+    description: |
+      Select the voltage reference source
+      Valid values are:
+      0: Internal reference 2.4V
+      1: Internal reference 4V
+      2: External reference source (vref-supply is required)
+    $ref: /schemas/types.yaml#/definitions/uint32
+    enum: [0, 1, 2]
+    default: 0
+
+  ti,datarate:
+    description: |
+      ADC data rate in kSPS
+    $ref: /schemas/types.yaml#/definitions/uint32
+    enum: [1, 2, 4, 8, 16, 32, 64]
+    default: 1
+
+  ti,gain:
+    description: |
+      The gain value for the PGA function
+    $ref: /schemas/types.yaml#/definitions/uint32
+    enum: [1, 2, 4, 8, 12]
+    default: 1
+
+  ti,adc-channels:
+    description: |
+      List of single-ended channels muxed for this ADC
+      - 4 channels, numbered from 0 to 3 for ti,ads131e04
+      - 8 channels, numbered from 0 to 7 for ti,ads131e08
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+
+required:
+  - compatible
+  - reg
+  - spi-cpha
+  - clocks
+  - clock-names
+  - interrupts
+  - ti,adc-channels
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: ti,ads131e04
+
+  - then:
+      properties:
+        ti,adc-channels:
+          minItems: 1
+          maxItems: 4
+          items:
+            minimum: 0
+            maximum: 3
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: ti,ads131e08
+
+  - then:
+      properties:
+        ti,adc-channels:
+          minItems: 1
+          maxItems: 8
+          items:
+            minimum: 0
+            maximum: 7
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    spidev@0 {
+      compatible = "ti,ads131e08";
+      reg = <0>;
+      spi-max-frequency = <1000000>;
+      spi-cpha;
+      clocks = <&clk2048k>;
+      clock-names = "adc-clk";
+      interrupt-parent = <&gpio5>;
+      interrupts = <28 IRQ_TYPE_EDGE_FALLING>;
+      vref-supply = <&vref_reg>;
+      ti,vref-sel = <2>;
+      ti,datarate = <1>;
+      ti,gain = <1>;
+      ti,adc-channels = <0 1 2 3 4 5 6 7>;
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 28bc5f9..0c351c7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17224,6 +17224,7 @@ TI ADS131E0X ADC SERIES DRIVER
 M:	Tomislav Denis <tomislav.denis@avl.com>
 L:	linux-iio@vger.kernel.org
 S:	Maintained
+F:	Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
 F:	drivers/iio/adc/ti-ads131e08.c
 
 TI AM437X VPFE DRIVER
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] bindings: iio: adc: Add documentation for ADS131E0x ADC driver
  2020-11-27 19:42 ` [PATCH 2/2] bindings: iio: adc: Add documentation for ADS131E0x ADC driver tomislav.denis
@ 2020-11-28 12:34   ` Jonathan Cameron
  2021-01-01 22:41     ` Denis, Tomislav AVL DiTEST
  2020-11-30 17:36   ` Rob Herring
  1 sibling, 1 reply; 9+ messages in thread
From: Jonathan Cameron @ 2020-11-28 12:34 UTC (permalink / raw)
  To: tomislav.denis; +Cc: linux-iio, devicetree

On Fri, 27 Nov 2020 20:42:40 +0100
<tomislav.denis@avl.com> wrote:

> From: Tomislav Denis <tomislav.denis@avl.com>
> 
> Add a device tree binding documentation for Texas Instruments
> ADS131E0x ADC family driver.
> 
> Signed-off-by: Tomislav Denis <tomislav.denis@avl.com>
Hi Tomislav,

A few comments inline.

Thanks,

Jonathan

> ---
>  .../devicetree/bindings/iio/adc/ti,ads131e08.yaml  | 145 +++++++++++++++++++++
>  MAINTAINERS                                        |   1 +
>  2 files changed, 146 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
> new file mode 100644
> index 0000000..92da193
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
> @@ -0,0 +1,145 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/adc/ti,ads131e08.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Texas Instruments ADS131E0x 4-, 6-, and 8-Channel ADCs

Not currently supporting 6 channel variants?

> +
> +maintainers:
> +  - Tomislav Denis <tomislav.denis@avl.com>
> +
> +description: |
> +  The ADS131E0x are a family of multichannel, simultaneous sampling,
> +  24-bit, delta-sigma, analog-to-digital converters (ADCs) with a
> +  built-in programmable gain amplifier (PGA), internal reference
> +  and an onboard oscillator.
> +  The communication with ADC chip is via the SPI bus (mode 1).
> +
> +  https://www.ti.com/lit/ds/symlink/ads131e08.pdf
> +
> +properties:
> +  compatible:
> +    enum:
> +      - ti,ads131e04
> +      - ti,ads131e08
> +
> +  reg:
> +    description: |
> +      SPI chip select number

That is entirely standard so no real need to put a description of
reg for an spi device.

> +    maxItems: 1
> +
> +  spi-cpha: true
> +
> +  clocks:
> +    description: |
> +      Device tree identifier to the clock source (2.048 MHz)
> +      Note: clock source is selected using CLKSEL pin
> +    maxItems: 1
> +
> +  clock-names:
> +    items:
> +      - const: adc-clk
> +
> +  interrupts:
> +    description: |
> +      IRQ line for the ADC data ready
> +    maxItems: 1
> +
> +  vref-supply:
> +    description: |
> +      Optional external voltage reference. Has to be supplied, if
> +      ti,vref-sel equals 2
> +
> +  ti,vref-sel:
> +    description: |
> +      Select the voltage reference source
> +      Valid values are:
> +      0: Internal reference 2.4V
> +      1: Internal reference 4V
> +      2: External reference source (vref-supply is required)

With optional external references we normally just use their presense
to indicate that they should be used.

You'll still need a parameter to pick the internal reference though.

> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    enum: [0, 1, 2]
> +    default: 0
> +
> +  ti,datarate:
> +    description: |
> +      ADC data rate in kSPS
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    enum: [1, 2, 4, 8, 16, 32, 64]
> +    default: 1

Why is this a devicetree element rather than runtime controllable?

> +
> +  ti,gain:
> +    description: |
> +      The gain value for the PGA function
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    enum: [1, 2, 4, 8, 12]
> +    default: 1

Isn't this per channel?  Also this explanation should mention
why it is a board related characteristic rather than a runtime
tuneable (I'm fine with having it here, but good to add a bit
of info on that to the description).

> +
> +  ti,adc-channels:
> +    description: |
> +      List of single-ended channels muxed for this ADC
> +      - 4 channels, numbered from 0 to 3 for ti,ads131e04
> +      - 8 channels, numbered from 0 to 7 for ti,ads131e08
> +    $ref: /schemas/types.yaml#/definitions/uint32-array

We've fairly recently introduced a generic adc channel binding that I'd prefer
is used in drivers going forwards.

See Documentation/device-tree/bindings/iio/adc.txt (or yaml if I've applied that
patch before you get to this).

That adds a subnode per channel and gives us an easy way to then provide
per channel parameters.  It's heavier weight than what you have here, but
much more flexible.

> +
> +required:
> +  - compatible
> +  - reg
> +  - spi-cpha
> +  - clocks
> +  - clock-names
> +  - interrupts
> +  - ti,adc-channels
> +
> +allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: ti,ads131e04
> +
> +  - then:
> +      properties:
> +        ti,adc-channels:
> +          minItems: 1
> +          maxItems: 4
> +          items:
> +            minimum: 0
> +            maximum: 3
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: ti,ads131e08
> +
> +  - then:
> +      properties:
> +        ti,adc-channels:
> +          minItems: 1
> +          maxItems: 8
> +          items:
> +            minimum: 0
> +            maximum: 7
> +
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +
> +    spidev@0 {
> +      compatible = "ti,ads131e08";
> +      reg = <0>;
> +      spi-max-frequency = <1000000>;
> +      spi-cpha;
> +      clocks = <&clk2048k>;
> +      clock-names = "adc-clk";
> +      interrupt-parent = <&gpio5>;
> +      interrupts = <28 IRQ_TYPE_EDGE_FALLING>;
> +      vref-supply = <&vref_reg>;
> +      ti,vref-sel = <2>;
> +      ti,datarate = <1>;
> +      ti,gain = <1>;
> +      ti,adc-channels = <0 1 2 3 4 5 6 7>;
> +    };
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 28bc5f9..0c351c7 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17224,6 +17224,7 @@ TI ADS131E0X ADC SERIES DRIVER
>  M:	Tomislav Denis <tomislav.denis@avl.com>
>  L:	linux-iio@vger.kernel.org
>  S:	Maintained
> +F:	Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
>  F:	drivers/iio/adc/ti-ads131e08.c
>  
>  TI AM437X VPFE DRIVER


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/2] iio: adc: Add driver for Texas Instruments ADS131E0x ADC family
  2020-11-27 19:42 ` [PATCH 1/2] iio: adc: Add driver for Texas Instruments " tomislav.denis
@ 2020-11-28 13:02   ` Jonathan Cameron
  2020-11-28 23:58   ` kernel test robot
  1 sibling, 0 replies; 9+ messages in thread
From: Jonathan Cameron @ 2020-11-28 13:02 UTC (permalink / raw)
  To: tomislav.denis; +Cc: linux-iio, devicetree

On Fri, 27 Nov 2020 20:42:39 +0100
<tomislav.denis@avl.com> wrote:

> From: Tomislav Denis <tomislav.denis@avl.com>
> 
> The ADS131E0x are a family of multichannel, simultaneous sampling,
> 24-bit, delta-sigma, analog-to-digital converters (ADCs) with a
> built-in programmable gain amplifier (PGA), internal reference
> and an onboard oscillator.
> 
> Signed-off-by: Tomislav Denis <tomislav.denis@avl.com>
Hi Tomislav,

Various comments inline.

Thanks,

Jonathan

> ---
>  MAINTAINERS                    |   6 +
>  drivers/iio/adc/Kconfig        |  12 +
>  drivers/iio/adc/Makefile       |   1 +
>  drivers/iio/adc/ti-ads131e08.c | 826 +++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 845 insertions(+)
>  create mode 100644 drivers/iio/adc/ti-ads131e08.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 924616d..28bc5f9 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17220,6 +17220,12 @@ M:	Robert Richter <rric@kernel.org>
>  S:	Odd Fixes
>  F:	drivers/gpio/gpio-thunderx.c
>  
> +TI ADS131E0X ADC SERIES DRIVER
> +M:	Tomislav Denis <tomislav.denis@avl.com>
> +L:	linux-iio@vger.kernel.org
> +S:	Maintained
> +F:	drivers/iio/adc/ti-ads131e08.c
> +
>  TI AM437X VPFE DRIVER
>  M:	"Lad, Prabhakar" <prabhakar.csengg@gmail.com>
>  L:	linux-media@vger.kernel.org
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 91ae905..b82b6e0 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1140,6 +1140,18 @@ config TI_ADS124S08
>  	  This driver can also be built as a module. If so, the module will be
>  	  called ti-ads124s08.
>  
> +config TI_ADS131E08
> +	tristate "Texas Instruments ADS131E08"
> +	depends on SPI && OF
> +	select IIO_BUFFER
> +	select IIO_TRIGGERED_BUFFER
> +	help
> +	  Say yes here you get support for Texas Instruments ADS131E08 and
> +	  ADS131E04 chips.
> +
> +	  This driver can also be built as a module. If so, the module will be
> +	  called ti-ads131e08.
> +
>  config TI_AM335X_ADC
>  	tristate "TI's AM335X ADC driver"
>  	depends on MFD_TI_AM335X_TSCADC && HAS_DMA
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 90f94ad..b578315 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -102,6 +102,7 @@ obj-$(CONFIG_TI_ADS7950) += ti-ads7950.o
>  obj-$(CONFIG_TI_ADS8344) += ti-ads8344.o
>  obj-$(CONFIG_TI_ADS8688) += ti-ads8688.o
>  obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
> +obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o
>  obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
>  obj-$(CONFIG_TI_TLC4541) += ti-tlc4541.o
>  obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o
> diff --git a/drivers/iio/adc/ti-ads131e08.c b/drivers/iio/adc/ti-ads131e08.c
> new file mode 100644
> index 0000000..5799ab3
> --- /dev/null
> +++ b/drivers/iio/adc/ti-ads131e08.c
> @@ -0,0 +1,826 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Texas Instruments ADS131E0x 4-, 6-, and 8-Channel ADCs
> + *
> + * Copyright (c) 2020 AVL DiTEST GmbH
> + *   Tomislav Denis <tomislav.denis@avl.com>
> + *
> + * Datasheet: https://www.ti.com/lit/ds/symlink/ads131e08.pdf
> + */
> +
> +#include <linux/module.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/bitfield.h>
> +#include <linux/spi/spi.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/trigger.h>
> +#include <linux/iio/trigger_consumer.h>
> +#include <linux/iio/triggered_buffer.h>
> +#include <asm/unaligned.h>
> +
> +/* Commands */
> +#define ADS131E08_CMD_RESET		0x06
> +#define ADS131E08_CMD_START		0x08
> +#define ADS131E08_CMD_STOP		0x0A
> +#define ADS131E08_CMD_OFFSETCAL		0x1A
> +#define ADS131E08_CMD_SDATAC		0x11
> +#define ADS131E08_CMD_RDATA		0x12
> +#define ADS131E08_CMD_RREG(r)		(0x20 | (r & 0x1F))

0x1F looks like a mask, so please use GENMASK for that.

> +#define ADS131E08_CMD_WREG(r)		(0x40 | (r & 0x1F))
> +
> +/* Registers */
> +#define ADS131E08_ADR_CFG1R		0x01
> +#define ADS131E08_ADR_CFG3R		0x03
> +#define ADS131E08_ADR_CH0R		0x05
> +
> +/* Configuration register 1 */
> +#define ADS131E08_CFG1R_DR_MASK		GENMASK(2, 0)
> +
> +/* Configuration register 3 */
> +#define ADS131E08_CFG3R_PDB_REFBUF_MASK	BIT(7)
> +#define ADS131E08_CFG3R_VREF_4V_MASK	BIT(5)
> +
> +/* Channel settings register */
> +#define ADS131E08_CHR_GAIN_MASK		GENMASK(6, 4)
> +#define ADS131E08_CHR_PWD_MASK		BIT(7)
> +
> +/* ADC  misc */
> +#define ADS131E08_DEFAULT_DATA_RATE	1
> +#define ADS131E08_DEFAULT_PGA_GAIN	1
> +
> +#define ADS131E08_VREF_2V4_MV		2400
> +#define ADS131E08_VREF_4V_MV		4000
> +
> +#define ADS131E08_WAIT_RESET_CYCLES	18
> +#define ADS131E08_WAIT_SDECODE_CYCLES	4
> +#define ADS131E08_WAIT_OFFSETCAL_MS	153
> +#define ADS131E08_MAX_SETTLING_TIME_MS	6
> +
> +#define ADS131E08_NUM_CHN_MAX		8
> +
> +#define ADS131E08_NUM_STATUS_BYTES	3
> +#define ADS131E08_NUM_DATA_BYTES_MAX	3
> +#define ADS131E08_NUM_DATA_BYTES(dr)	(((dr) >= 32) ? 2 : 3)
> +#define ADS131E08_NUM_DATA_BITS(dr)	(ADS131E08_NUM_DATA_BYTES(dr) * 8)
> +#define ADS131E08_NUM_STORAGE_BYTES	4
> +#define ADS131E08_NUM_STORAGE_BITS	(ADS131E08_NUM_STORAGE_BYTES * 8)
> +
> +enum ads131e08_ids {
> +	ads131e04,
> +	ads131e08,
> +};
> +
> +struct ads131e08_info {
> +	unsigned int max_channels;
> +};
> +
> +struct ads131e08_state {
> +	const struct ads131e08_info *info;
> +	struct spi_device *spi;
> +	struct iio_trigger *trig;
> +	struct clk *adc_clk;
> +	struct regulator *vref_reg;
> +	unsigned int data_rate;
> +	unsigned int pga_gain;
> +	unsigned int vref_mv;
> +	unsigned int sdecode_delay_us;
> +	unsigned int reset_delay_us;
> +	unsigned int readback_len;
> +	struct completion completion;
> +
> +	/*
> +	 * Add extra one padding byte to be able to access the last channel
> +	 * value using u32 pointer
> +	 */
> +	u8 rx_buf[ADS131E08_NUM_STATUS_BYTES +
> +		ADS131E08_NUM_CHN_MAX * ADS131E08_NUM_DATA_BYTES_MAX + 1];

May be used for DMA, so you need to sure appropriate alignment.
____cache_line_aligned will do that as long as you are then careful about
an following elements to make sure they don't end up in the same cacheline
(here just move tmp_buf before rx_buf)

Note that I've suggested below that you pull tx in here as well. That can
share a cacheline with the rx_buf as it's always assumed no SPI controller
is going to be dumb enough to cause trouble with it's own buffers.

> +	u8 tmp_buf[ADS131E08_NUM_CHN_MAX * ADS131E08_NUM_DATA_BYTES_MAX];

> +};
> +
> +static const struct ads131e08_info ads131e08_info_tbl[] = {
> +	[ads131e04] = {
> +		.max_channels = 4,
> +	},
> +	[ads131e08] = {
> +		.max_channels = 8,
> +	},
> +};
> +
> +struct ads131e08_data_rate_desc {
> +	unsigned int rate;  /* data rate in kSPS */
> +	u8 reg;             /* reg value */
> +};
> +
> +static const struct ads131e08_data_rate_desc ads131e08_data_rate_tbl[] = {
> +	{ .rate = 64,   .reg = 0x00 },
> +	{ .rate = 32,   .reg = 0x01 },
> +	{ .rate = 16,   .reg = 0x02 },
> +	{ .rate = 8,    .reg = 0x03 },
> +	{ .rate = 4,    .reg = 0x04 },
> +	{ .rate = 2,    .reg = 0x05 },
> +	{ .rate = 1,    .reg = 0x06 },
> +};
> +
> +struct ads131e08_pga_gain_desc {
> +	unsigned int gain;  /* PGA gain value */
> +	u8 reg;             /* reg value */
> +};
> +
> +static const struct ads131e08_pga_gain_desc ads131e08_pga_gain_tbl[] = {
> +	{ .gain = 1,   .reg = 0x01 },
> +	{ .gain = 2,   .reg = 0x02 },
> +	{ .gain = 4,   .reg = 0x04 },
> +	{ .gain = 8,   .reg = 0x05 },
> +	{ .gain = 12,  .reg = 0x06 },
> +};
> +
> +static int ads131e08_exec_cmd(struct ads131e08_state *st, u8 cmd)
> +{
> +	int ret;
> +
> +	ret = spi_write(st->spi, &cmd, 1);
spi_write is a wrapper around spi_sync and spi_sync requires DMA safe buffers.
If you want to avoid that, use

spi_write_then_read() which uses a bounce buffer (but set the read size to 0)

> +	if (ret) {
> +		dev_err(&st->spi->dev,
> +			"%s: SPI write failed for cmd: %02x\n", __func__, cmd);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ads131e08_read_reg(struct ads131e08_state *st, u8 reg)
> +{
> +	u8 rx;
> +	int ret;
> +
> +	u8 tx_buf[] = {
> +		ADS131E08_CMD_RREG(reg),
> +		0,
> +	};

As below. DMA safety.

> +
> +	struct spi_transfer transfer[] = {
> +		{
> +			.tx_buf = tx_buf,
> +			.len = ARRAY_SIZE(tx_buf),
> +			.delay_usecs = st->sdecode_delay_us,
> +		}, {
> +			.rx_buf = &rx,
> +			.len = 1,
> +		},
> +	};
> +
> +	ret = spi_sync_transfer(st->spi, transfer, ARRAY_SIZE(transfer));
> +	if (ret) {
> +		dev_err(&st->spi->dev, "%s: SPI transfer failed\n", __func__);
> +		return ret;
> +	}
> +
> +	return rx;
> +}
> +
> +static int ads131e08_write_reg(struct ads131e08_state *st, u8 reg, u8 value)
> +{
> +	int ret;
> +
> +	u8 tx_buf[] = {
> +		ADS131E08_CMD_WREG(reg),
> +		0,
> +		value,
> +	};
> +
> +	struct spi_transfer transfer[] = {
> +		{
> +			.tx_buf = tx_buf,
> +			.len = ARRAY_SIZE(tx_buf),
> +			.delay_usecs = st->sdecode_delay_us,
> +		}
> +	};
> +
> +	ret = spi_sync_transfer(st->spi, transfer, ARRAY_SIZE(transfer));
> +	if (ret) {
> +		dev_err(&st->spi->dev, "%s: SPI transfer failed\n", __func__);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ads131e08_read_data(struct ads131e08_state *st, int rx_len)
> +{
> +	int ret;
> +
> +	u8 tx_buf[] = {
> +		ADS131E08_CMD_RDATA,
> +	};

SPI buffers generally need to DMA safe.  That one isn't.
I'd put it in the state structure like you have done for rx.

> +
> +	struct spi_transfer transfer[] = {
> +		{
> +			.tx_buf = tx_buf,
> +			.len = ARRAY_SIZE(tx_buf),
> +		}, {
> +			.rx_buf = &st->rx_buf,
> +			.len = rx_len,
> +		},
> +	};
> +
> +	ret = spi_sync_transfer(st->spi, transfer, ARRAY_SIZE(transfer));
> +	if (ret) {
> +		dev_err(&st->spi->dev, "%s: SPI transfer failed\n", __func__);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ads131e08_set_data_rate(struct ads131e08_state *st, int data_rate)
> +{
> +	int ret, i, reg;
> +
> +	for (i = 0; i < ARRAY_SIZE(ads131e08_data_rate_tbl); ++i) {
> +		if (ads131e08_data_rate_tbl[i].rate == data_rate)
> +			break;
> +	}
> +
> +	if (i == ARRAY_SIZE(ads131e08_data_rate_tbl)) {
> +		dev_err(&st->spi->dev, "invalid data rate value\n");
> +		return -EINVAL;
> +	}
> +
> +	reg = ads131e08_read_reg(st, ADS131E08_ADR_CFG1R);
> +	if (reg < 0)
> +		return reg;
> +
> +	reg &= ~ADS131E08_CFG1R_DR_MASK;
> +	reg |= FIELD_PREP(ADS131E08_CFG1R_DR_MASK,
> +		ads131e08_data_rate_tbl[i].reg);
> +
> +	ret = ads131e08_write_reg(st, ADS131E08_ADR_CFG1R, reg);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int ads131e08_set_pga_gain(struct ads131e08_state *st,
> +	unsigned int channel, unsigned int pga_gain)
> +{
> +	int ret, i, reg;
> +
> +	for (i = 0; i < ARRAY_SIZE(ads131e08_pga_gain_tbl); ++i) {
> +		if (ads131e08_pga_gain_tbl[i].gain == pga_gain)
> +			break;
> +	}
> +
> +	if (i == ARRAY_SIZE(ads131e08_pga_gain_tbl)) {
> +		dev_err(&st->spi->dev, "invalid PGA gain value\n");
> +		return -EINVAL;
> +	}
> +
> +	reg = ads131e08_read_reg(st, ADS131E08_ADR_CH0R + channel);
> +	if (reg < 0)
> +		return reg;
> +
> +	reg &= ~ADS131E08_CHR_GAIN_MASK;
> +	reg |= FIELD_PREP(ADS131E08_CHR_GAIN_MASK,
> +		ads131e08_pga_gain_tbl[i].reg);

Align with opening bracket - or just put it on one line as it'll only be jsut
over 80 characters and it will be easier to read.

> +
> +	ret = ads131e08_write_reg(st, ADS131E08_ADR_CH0R + channel, reg);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int ads131e08_power_down_channel(struct ads131e08_state *st,
> +	unsigned int channel, bool value)
> +{
> +	int ret, reg;
> +
> +	reg = ads131e08_read_reg(st, ADS131E08_ADR_CH0R + channel);
> +	if (reg < 0)
> +		return reg;
> +
> +	reg &= ~ADS131E08_CHR_PWD_MASK;
> +	reg |= FIELD_PREP(ADS131E08_CHR_PWD_MASK, value);
> +
> +	ret = ads131e08_write_reg(st, ADS131E08_ADR_CH0R + channel, reg);
> +	if (ret)
> +		return ret;

return ads131...
Same elsewhere.  We might as well compact the code a bit where it
doesn't hurt readability.


> +
> +	return 0;
> +}
> +
> +static int ads131e08_config_reference_voltage(struct ads131e08_state *st)
> +{
> +	int ret, reg;
> +
> +	reg = ads131e08_read_reg(st, ADS131E08_ADR_CFG3R);
> +	if (reg < 0)
> +		return reg;
> +
> +	if (st->vref_reg) {
> +		/* Use external reference voltage */
> +		reg &= ~ADS131E08_CFG3R_PDB_REFBUF_MASK;
> +	} else {
> +		/* Use internal reference voltage */
> +		reg &= ~ADS131E08_CFG3R_PDB_REFBUF_MASK |
> +			~ADS131E08_CFG3R_VREF_4V_MASK;
> +		reg |= FIELD_PREP(ADS131E08_CFG3R_PDB_REFBUF_MASK, 1);
> +		reg |= FIELD_PREP(ADS131E08_CFG3R_VREF_4V_MASK,
> +			st->vref_mv == ADS131E08_VREF_4V_MV);
> +	}
> +
> +	ret = ads131e08_write_reg(st, ADS131E08_ADR_CFG3R, reg);
> +	if (ret)
> +		return ret;
> +

return ads131...

> +	return 0;
> +}
> +
> +static int ads131e08_initial_config(struct iio_dev *indio_dev)
> +{
> +	struct ads131e08_state *st = iio_priv(indio_dev);
> +	const struct iio_chan_spec *channel = indio_dev->channels;
> +	int ret, i;
> +	u8 active_channels = 0;
> +
> +	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_RESET);
> +	if (ret)
> +		return ret;
> +
> +	udelay(st->reset_delay_us);
> +
> +	/* Disable read data in continuous mode (enabled by default) */
> +	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_SDATAC);
> +	if (ret)
> +		return ret;
> +
> +	ret = ads131e08_set_data_rate(st, st->data_rate);
> +	if (ret)
> +		return ret;
> +
> +	ret = ads131e08_config_reference_voltage(st);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0;  i < indio_dev->num_channels; ++i) {
> +		ret = ads131e08_set_pga_gain(st, channel->channel,
> +			st->pga_gain);
> +		if (ret)
> +			return ret;
> +
> +		active_channels |= BIT(channel->channel);
> +		channel++;
> +	}
> +
> +	/* Power down unused channels */
> +	for (i = 0; i < st->info->max_channels; ++i) {
> +		if (!(active_channels & BIT(i))) {
> +			ret = ads131e08_power_down_channel(st, i, true);
> +			if (ret)
> +				return ret;
> +		}
> +	}
> +
> +	/* Request channel offset calibration */
> +	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_OFFSETCAL);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Channel offset calibration is triggered with first START command.
> +	 * Since calibration take more time than settling operation,
> +	 * this causes timeout error when command START is sent first
> +	 * time (e.g. first call of the ads131e08_read_direct method).
> +	 * To avoid this problem offset calibration is triggered here.
> +	 */
> +	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_START);
> +	if (ret)
> +		return ret;
> +
> +	msleep(ADS131E08_WAIT_OFFSETCAL_MS);
> +
> +	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_STOP);

return ads13...

> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int ads131e08_read_direct(struct iio_dev *indio_dev,
> +	struct iio_chan_spec const *channel, int *value)
> +{
> +	struct ads131e08_state *st = iio_priv(indio_dev);
> +	int ret;
> +	u8 num_bits, *src;
> +
> +	reinit_completion(&st->completion);
> +
> +	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_START);
> +	if (ret)
> +		return ret;
> +
> +	ret = wait_for_completion_timeout(&st->completion,
> +		msecs_to_jiffies(ADS131E08_MAX_SETTLING_TIME_MS));
> +	if (!ret)
> +		return -ETIMEDOUT;
> +
> +	ret = ads131e08_read_data(st, st->readback_len);
> +	if (ret)
> +		return ret;
> +
> +	ret = ads131e08_exec_cmd(st, ADS131E08_CMD_STOP);
> +	if (ret)
> +		return ret;
> +
> +	num_bits = channel->scan_type.realbits;
> +	src = st->rx_buf + ADS131E08_NUM_STATUS_BYTES +
> +		channel->channel * ADS131E08_NUM_DATA_BYTES(st->data_rate);
> +
> +	*value = sign_extend32(
> +		get_unaligned_be32(src) >> (32 - num_bits), num_bits - 1);
> +
> +	return ret;
> +}
> +
> +static int ads131e08_read_raw(struct iio_dev *indio_dev,
> +	struct iio_chan_spec const *channel, int *value,
> +	int *value2, long mask)
> +{
> +	struct ads131e08_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		ret = iio_device_claim_direct_mode(indio_dev);
> +		if (ret)
> +			return ret;
> +
> +		ret = ads131e08_read_direct(indio_dev, channel, value);
> +		iio_device_release_direct_mode(indio_dev);
> +		if (ret)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +
> +	case IIO_CHAN_INFO_SCALE:
> +		*value = st->vref_mv / st->pga_gain;
> +		*value2 = channel->scan_type.realbits - 1;
> +
> +		return IIO_VAL_FRACTIONAL_LOG2;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int ads131e08_validate_trigger(struct iio_dev *indio_dev,
> +	struct iio_trigger *trig)
> +{
> +	struct ads131e08_state *st = iio_priv(indio_dev);
> +
> +	if (st->trig != trig)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static int ads131e08_debugfs_reg_access(struct iio_dev *indio_dev,
> +	unsigned int reg, unsigned int writeval, unsigned int *readval)
> +{
> +	struct ads131e08_state *st = iio_priv(indio_dev);
> +
> +	if (readval) {
> +		int ret = ads131e08_read_reg(st, (u8)reg);
> +		*readval = ret;
> +		return ret;
> +	}
> +
> +	return ads131e08_write_reg(st, (u8)reg, (u8)writeval);
> +}
> +
> +static const struct iio_info ads131e08_iio_info = {
> +	.read_raw = ads131e08_read_raw,
> +	.validate_trigger = &ads131e08_validate_trigger,
> +	.debugfs_reg_access = &ads131e08_debugfs_reg_access,
> +};
> +
> +static int ads131e08_set_trigger_state(struct iio_trigger *trig, bool state)
> +{
> +	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
> +	struct ads131e08_state *st = iio_priv(indio_dev);
> +	u8 cmd = state ? ADS131E08_CMD_START : ADS131E08_CMD_STOP;
> +
> +	return ads131e08_exec_cmd(st, cmd);
> +}
> +
> +static const struct iio_trigger_ops ads131e08_trigger_ops = {
> +	.validate_device = &iio_trigger_validate_own_device,

Why?  I can't immediately see why we couldn't let other devices
run in sync with this one by sharing the trigger.

> +	.set_trigger_state = &ads131e08_set_trigger_state,
> +};
> +
> +static irqreturn_t ads131e08_trigger_handler(int irq, void *private)
> +{
> +	struct iio_poll_func *pf = private;
> +	struct iio_dev *indio_dev = pf->indio_dev;
> +	struct ads131e08_state *st = iio_priv(indio_dev);
> +	int ret;
> +	unsigned int num_dbytes = ADS131E08_NUM_DATA_BYTES(st->data_rate);
> +	unsigned int chn, i = 0;
> +
> +	ret = ads131e08_read_data(st, st->readback_len);
> +	if (ret)
> +		goto out;
> +
> +	for_each_set_bit(chn, indio_dev->active_scan_mask,
> +		indio_dev->masklength) {
> +		memcpy(st->tmp_buf + i * ADS131E08_NUM_STORAGE_BYTES,
> +			st->rx_buf + ADS131E08_NUM_STATUS_BYTES + chn * num_dbytes,
> +			num_dbytes);
> +		i++;
> +	}
> +
> +	iio_push_to_buffers(indio_dev, st->tmp_buf);

Why no timestamp?

Note that if you do provide one you'll have to ensure st->tmp_buf is 8 byte
aligned.

> +
> +out:
> +	iio_trigger_notify_done(indio_dev->trig);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t ads131e08_interrupt(int irq, void *private)
> +{
> +	struct iio_dev *indio_dev = private;
> +	struct ads131e08_state *st = iio_priv(indio_dev);
> +
> +	if (iio_buffer_enabled(indio_dev))
> +		iio_trigger_poll_chained(st->trig);
> +	else
> +		complete(&st->completion);

We don't do anything that needs to sleep in here and it's trivial so
I'd be tempted to do it in a top half handler rather than the thread.
In particular the completion shouldn't require spinning up the
thread.

> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int ads131e08_parse_dt(struct iio_dev *indio_dev,
> +	struct device_node *node)
> +{
> +	struct ads131e08_state *st = iio_priv(indio_dev);
> +	struct iio_chan_spec *channels, *channel;
> +	int vrefsel, num_channels;
> +	struct property *prop;
> +	const __be32 *cur;
> +	u32 value;
> +
> +	if (of_property_read_u32(node, "ti,datarate", &st->data_rate))
> +		st->data_rate = ADS131E08_DEFAULT_DATA_RATE;
> +
> +	if (of_property_read_u32(node, "ti,gain", &st->pga_gain))
> +		st->pga_gain = ADS131E08_DEFAULT_PGA_GAIN;

I think this should be per channel (from a very quick glance at datasheet!)

> +
> +	if (of_property_read_u32(node, "ti,vref-sel", &vrefsel))
> +		vrefsel = 0;

I mention in the binding review that I'd expect default for the case
where an external regulator is specified to always be the external
regulator (someone bothered to put it down on the board!)
That will flip some of this logic around.

> +
> +	switch (vrefsel) {
> +	case 0:
> +		st->vref_mv = ADS131E08_VREF_2V4_MV;
> +		break;
> +	case 1:
> +		st->vref_mv = ADS131E08_VREF_4V_MV;
> +		break;
> +	case 2:
> +		st->vref_mv = 0;
> +		break;
> +	default:
> +		st->vref_mv = ADS131E08_VREF_2V4_MV;
> +	}
> +
> +	num_channels = of_property_count_elems_of_size(node,
> +		"ti,adc-channels", sizeof(u32));

In the binding review I request that you change to subnodes.  That
does make this bit of code more complex, but also allows per channel PGA
settings.

> +	if (num_channels == 0) {
> +		dev_err(&st->spi->dev, "no valid channels property\n");
> +		return -ENODEV;
> +	}
> +
> +	if (num_channels > st->info->max_channels) {
> +		dev_err(&st->spi->dev, "num of channels out of range\n");
> +		return -EINVAL;
> +	}
> +
> +	channels = devm_kcalloc(&st->spi->dev, num_channels,
> +		sizeof(struct iio_chan_spec), GFP_KERNEL);
> +	if (!channels)
> +		return -ENOMEM;
> +
> +	channel = channels;
> +	of_property_for_each_u32(node, "ti,adc-channels", prop, cur, value) {
> +		channel->type = IIO_VOLTAGE;
> +		channel->indexed = 1;
> +		channel->channel = value;
> +		channel->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> +		channel->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
> +		channel->scan_index = value;
> +		channel->scan_type.sign = 's';
> +		channel->scan_type.realbits =
> +			ADS131E08_NUM_DATA_BITS(st->data_rate);
> +		channel->scan_type.storagebits = ADS131E08_NUM_STORAGE_BITS;
> +		channel->scan_type.shift = 8;
It's a bit odd to mix defines and direct values here. I'd argue storeagebits
is a real number so doesn't make sense to hide it in a define.

> +		channel->scan_type.endianness = IIO_BE;
> +		channel++;
> +	}
> +
> +	indio_dev->channels = channels;
> +	indio_dev->num_channels = num_channels;
> +
> +	return 0;
> +}
> +
> +static int ads131e08_probe(struct spi_device *spi)
> +{
> +	struct iio_dev *indio_dev;
> +	struct ads131e08_state *st;
> +	int ret;
> +	unsigned long adc_clk_hz, adc_clk_ns;
> +
> +	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> +	if (!indio_dev) {
> +		dev_err(&spi->dev, "failed to allocate IIO device\n");
> +		return -ENOMEM;
> +	}
> +
> +	spi_set_drvdata(spi, indio_dev);
> +
> +	st = iio_priv(indio_dev);
> +	st->info = &ads131e08_info_tbl[spi_get_device_id(spi)->driver_data];
> +	st->spi = spi;
> +
> +	ret = ads131e08_parse_dt(indio_dev, spi->dev.of_node);
> +	if (ret)
> +		return ret;
> +
> +	st->readback_len = ADS131E08_NUM_STATUS_BYTES +
> +		ADS131E08_NUM_DATA_BYTES(st->data_rate) *
> +		st->info->max_channels;
> +
> +	indio_dev->name = spi_get_device_id(spi)->name;
> +	indio_dev->dev.parent = &spi->dev;
> +	indio_dev->dev.of_node = spi->dev.of_node;
> +	indio_dev->info = &ads131e08_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	init_completion(&st->completion);
> +
> +	if (spi->irq) {
> +		ret = devm_request_threaded_irq(&spi->dev, spi->irq,
> +			NULL, ads131e08_interrupt,
> +			IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
> +			spi->dev.driver->name, indio_dev);
> +		if (ret) {
> +			dev_err(&spi->dev, "failed to allocate IRQ\n");
> +			return ret;
> +		}
> +	} else {
> +		dev_err(&spi->dev, "data ready IRQ missing\n");
> +		return -ENODEV;
> +	}
> +
> +	st->trig = devm_iio_trigger_alloc(&spi->dev, "%s-dev%d",
> +		indio_dev->name, indio_dev->id);
> +	if (!st->trig) {
> +		dev_err(&spi->dev, "failed to allocate IIO trigger\n");
> +		return -ENOMEM;
> +	}
> +
> +	st->trig->ops = &ads131e08_trigger_ops;
> +	st->trig->dev.parent = &spi->dev;
> +	iio_trigger_set_drvdata(st->trig, indio_dev);
> +	ret = devm_iio_trigger_register(&spi->dev, st->trig);
> +	if (ret) {
> +		dev_err(&spi->dev, "failed to register IIO trigger\n");
> +		goto err_disable_reg;
> +	}
> +
> +	indio_dev->trig = iio_trigger_get(st->trig);
> +
> +	ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev,
> +		NULL, &ads131e08_trigger_handler, NULL);
> +	if (ret) {
> +		dev_err(&spi->dev, "failed to setup IIO buffer\n");
> +		return ret;
> +	}
> +
> +	st->adc_clk = devm_clk_get(&spi->dev, "adc-clk");
> +	if (IS_ERR(st->adc_clk)) {
> +		dev_err(&spi->dev, "failed to get the ADC clock\n");
> +		return PTR_ERR(st->adc_clk);
> +	}
> +
> +	ret = clk_prepare_enable(st->adc_clk);
> +	if (ret) {
> +		dev_err(&spi->dev, "failed to prepare/enable the ADC clock\n");
> +		return ret;
> +	}
> +
> +	adc_clk_hz = clk_get_rate(st->adc_clk);
> +	if (!adc_clk_hz) {
> +		dev_err(&spi->dev, "failed to get the ADC clock rate\n");
> +		ret = -EINVAL;
> +		goto err_disable_clk;
> +	}
> +
> +	adc_clk_ns = NSEC_PER_SEC / adc_clk_hz;
> +	st->sdecode_delay_us = DIV_ROUND_UP(
> +		ADS131E08_WAIT_SDECODE_CYCLES * adc_clk_ns, 1000);
> +	st->reset_delay_us = DIV_ROUND_UP(
> +		ADS131E08_WAIT_RESET_CYCLES * adc_clk_ns, 1000);
> +
> +	if (st->vref_mv == 0) {
> +		st->vref_reg = devm_regulator_get(&spi->dev, "vref");
> +		if (IS_ERR(st->vref_reg)) {
> +			dev_err(&spi->dev,
> +				"failed to get vref regulator\n");
> +			ret = PTR_ERR(st->vref_reg);
> +			goto err_disable_clk;
> +		}
> +
> +		ret = regulator_enable(st->vref_reg);
> +		if (ret) {
> +			dev_err(&spi->dev,
> +				"failed to enable external vref supply\n");
> +			goto err_disable_clk;
> +		}
> +
> +		ret = regulator_get_voltage(st->vref_reg);
> +		if (ret < 0) {
> +			dev_err(&spi->dev, "failed to get vref voltage\n");
> +			goto err_disable_reg;
> +		}
> +
> +		st->vref_mv = ret / 1000;
> +	}
> +
> +	ret = ads131e08_initial_config(indio_dev);
> +	if (ret) {
> +		dev_err(&spi->dev, "initial configuration failed\n");
> +		goto err_disable_reg;
> +	}
> +
> +	ret = devm_iio_device_register(&spi->dev, indio_dev);

See below about mixing devm and non devm.

> +	if (ret) {
> +		dev_err(&spi->dev, "failed to register IIO device\n");
> +		goto err_disable_reg;
> +	}
> +
> +	return 0;
> +
> +err_disable_reg:
> +	regulator_disable(st->vref_reg);
> +
> +err_disable_clk:
> +	clk_disable_unprepare(st->adc_clk);
> +
> +	return ret;
> +}
> +
> +static int ads131e08_remove(struct spi_device *spi)
> +{
> +	struct iio_dev *indio_dev = spi_get_drvdata(spi);
> +	struct ads131e08_state *st = iio_priv(indio_dev);
> +
> +	if (st->vref_reg)
> +		regulator_disable(st->vref_reg);
> +
> +	clk_disable_unprepare(st->adc_clk);
You shouldn't mix devm and unmanaged clean up.  
Here this results in a race where the userspace interfaces are still available 
after you've turned off the power and clocks.

2 solutions to this.
1) The first time you make a call in probe that is not a device managed
call, switch to only use the non managed forms.
2) Make everything device managed - devm_add_action_or_reset() can be used
for those elements that don't have existing managed forms.

> +
> +	return 0;
> +}
> +
> +static const struct of_device_id ads131e08_of_match[] = {
> +	{ .compatible = "ti,ads131e04", },
> +	{ .compatible = "ti,ads131e08", },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, ads131e08_matches);
> +
> +static const struct spi_device_id ads131e08_id[] = {
> +	{ "ads131e04",      ads131e04},
> +	{ "ads131e08",      ads131e08},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(spi, ads131e08_id);
> +
> +static struct spi_driver ads131e08_driver = {
> +	.driver = {
> +		.name = "ads131e08",
> +		.of_match_table = ads131e08_of_match,
> +	},
> +	.probe = ads131e08_probe,
> +	.remove = ads131e08_remove,
> +	.id_table = ads131e08_id,
> +};
> +module_spi_driver(ads131e08_driver);
> +
> +MODULE_AUTHOR("Tomislav Denis <tomislav.denis@avl.com>");
> +MODULE_DESCRIPTION("Driver for ADS131E0x ADC family");
> +MODULE_LICENSE("GPL v2");


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/2] iio: adc: Add driver for Texas Instruments ADS131E0x ADC family
  2020-11-27 19:42 ` [PATCH 1/2] iio: adc: Add driver for Texas Instruments " tomislav.denis
  2020-11-28 13:02   ` Jonathan Cameron
@ 2020-11-28 23:58   ` kernel test robot
  1 sibling, 0 replies; 9+ messages in thread
From: kernel test robot @ 2020-11-28 23:58 UTC (permalink / raw)
  To: tomislav.denis, jic23; +Cc: kbuild-all, linux-iio, devicetree, tomislav.denis

[-- Attachment #1: Type: text/plain, Size: 2782 bytes --]

Hi,

I love your patch! Yet something to improve:

[auto build test ERROR on iio/togreg]
[also build test ERROR on linus/master v5.10-rc5 next-20201127]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/tomislav-denis-avl-com/iio-adc-Add-driver-for-Texas-Instruments-ADS131E0x-ADC-family/20201129-062610
base:   https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg
config: sh-allmodconfig (attached as .config)
compiler: sh4-linux-gcc (GCC) 9.3.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/9a70466cfd655dcd40e9b57ff7deb4a7bf2b0110
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review tomislav-denis-avl-com/iio-adc-Add-driver-for-Texas-Instruments-ADS131E0x-ADC-family/20201129-062610
        git checkout 9a70466cfd655dcd40e9b57ff7deb4a7bf2b0110
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross ARCH=sh 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   In file included from drivers/iio/adc/ti-ads131e08.c:11:
>> drivers/iio/adc/ti-ads131e08.c:804:25: error: 'ads131e08_matches' undeclared here (not in a function); did you mean 'ads131e08_of_match'?
     804 | MODULE_DEVICE_TABLE(of, ads131e08_matches);
         |                         ^~~~~~~~~~~~~~~~~
   include/linux/module.h:240:15: note: in definition of macro 'MODULE_DEVICE_TABLE'
     240 | extern typeof(name) __mod_##type##__##name##_device_table  \
         |               ^~~~
>> include/linux/module.h:240:21: error: '__mod_of__ads131e08_matches_device_table' aliased to undefined symbol 'ads131e08_matches'
     240 | extern typeof(name) __mod_##type##__##name##_device_table  \
         |                     ^~~~~~
   drivers/iio/adc/ti-ads131e08.c:804:1: note: in expansion of macro 'MODULE_DEVICE_TABLE'
     804 | MODULE_DEVICE_TABLE(of, ads131e08_matches);
         | ^~~~~~~~~~~~~~~~~~~

vim +804 drivers/iio/adc/ti-ads131e08.c

   798	
   799	static const struct of_device_id ads131e08_of_match[] = {
   800		{ .compatible = "ti,ads131e04", },
   801		{ .compatible = "ti,ads131e08", },
   802		{}
   803	};
 > 804	MODULE_DEVICE_TABLE(of, ads131e08_matches);
   805	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 52871 bytes --]

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] bindings: iio: adc: Add documentation for ADS131E0x ADC driver
  2020-11-27 19:42 ` [PATCH 2/2] bindings: iio: adc: Add documentation for ADS131E0x ADC driver tomislav.denis
  2020-11-28 12:34   ` Jonathan Cameron
@ 2020-11-30 17:36   ` Rob Herring
  1 sibling, 0 replies; 9+ messages in thread
From: Rob Herring @ 2020-11-30 17:36 UTC (permalink / raw)
  To: tomislav.denis; +Cc: devicetree, jic23, linux-iio

On Fri, 27 Nov 2020 20:42:40 +0100, tomislav.denis@avl.com wrote:
> From: Tomislav Denis <tomislav.denis@avl.com>
> 
> Add a device tree binding documentation for Texas Instruments
> ADS131E0x ADC family driver.
> 
> Signed-off-by: Tomislav Denis <tomislav.denis@avl.com>
> ---
>  .../devicetree/bindings/iio/adc/ti,ads131e08.yaml  | 145 +++++++++++++++++++++
>  MAINTAINERS                                        |   1 +
>  2 files changed, 146 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
> 


My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml: 'additionalProperties' is a required property
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml: ignoring, error in schema: 
warning: no schema found in file: ./Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
Documentation/devicetree/bindings/iio/adc/ti,ads131e08.example.dts:23.11-21: Warning (reg_format): /example-0/spidev@0:reg: property has invalid length (4 bytes) (#address-cells == 1, #size-cells == 1)
Documentation/devicetree/bindings/iio/adc/ti,ads131e08.example.dt.yaml: Warning (pci_device_reg): Failed prerequisite 'reg_format'
Documentation/devicetree/bindings/iio/adc/ti,ads131e08.example.dt.yaml: Warning (pci_device_bus_num): Failed prerequisite 'reg_format'
Documentation/devicetree/bindings/iio/adc/ti,ads131e08.example.dt.yaml: Warning (simple_bus_reg): Failed prerequisite 'reg_format'
Documentation/devicetree/bindings/iio/adc/ti,ads131e08.example.dt.yaml: Warning (i2c_bus_reg): Failed prerequisite 'reg_format'
Documentation/devicetree/bindings/iio/adc/ti,ads131e08.example.dt.yaml: Warning (spi_bus_reg): Failed prerequisite 'reg_format'
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/iio/adc/ti,ads131e08.example.dt.yaml: example-0: spidev@0:reg:0: [0] is too short
	From schema: /usr/local/lib/python3.8/dist-packages/dtschema/schemas/reg.yaml


See https://patchwork.ozlabs.org/patch/1407724

The base for the patch is generally the last rc1. Any dependencies
should be noted.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit.


^ permalink raw reply	[flat|nested] 9+ messages in thread

* RE: [PATCH 2/2] bindings: iio: adc: Add documentation for ADS131E0x ADC driver
  2020-11-28 12:34   ` Jonathan Cameron
@ 2021-01-01 22:41     ` Denis, Tomislav AVL DiTEST
  2021-01-02 14:15       ` Jonathan Cameron
  0 siblings, 1 reply; 9+ messages in thread
From: Denis, Tomislav AVL DiTEST @ 2021-01-01 22:41 UTC (permalink / raw)
  To: Jonathan Cameron; +Cc: linux-iio, devicetree

Hi Jonathan,

Thanks for great review and hints that you gave me.
A few comments inline.

BR,

Tomislav

> -----Original Message-----
> From: Jonathan Cameron <jic23@kernel.org>
> Sent: 28 November 2020 13:34
> To: Denis, Tomislav AVL DiTEST <Tomislav.Denis@avl.com>
> Cc: linux-iio@vger.kernel.org; devicetree@vger.kernel.org
> Subject: Re: [PATCH 2/2] bindings: iio: adc: Add documentation for ADS131E0x
> ADC driver
> 
> On Fri, 27 Nov 2020 20:42:40 +0100
> <tomislav.denis@avl.com> wrote:
> 
> > From: Tomislav Denis <tomislav.denis@avl.com>
> >
> > Add a device tree binding documentation for Texas Instruments
> > ADS131E0x ADC family driver.
> >
> > Signed-off-by: Tomislav Denis <tomislav.denis@avl.com>
> Hi Tomislav,
> 
> A few comments inline.
> 
> Thanks,
> 
> Jonathan
> 
> > ---
> >  .../devicetree/bindings/iio/adc/ti,ads131e08.yaml  | 145
> +++++++++++++++++++++
> >  MAINTAINERS                                        |   1 +
> >  2 files changed, 146 insertions(+)
> >  create mode 100644
> > Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
> >
> > diff --git
> > a/Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
> > b/Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
> > new file mode 100644
> > index 0000000..92da193
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
> > @@ -0,0 +1,145 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) %YAML 1.2
> > +---
> > +$id:
> > +https://urldefense.com/v3/__http://devicetree.org/schemas/iio/adc/ti,
> > +ads131e08.yaml*__;Iw!!Oq50-tQ!_S4huPtQ7bwYwG-
> J3eOtmYscvX_TjlRfR7tjpa6
> > +d2dqUQ67RUNI9X1VKpsiphO1jsQ$
> > +$schema:
> > +https://urldefense.com/v3/__http://devicetree.org/meta-schemas/core.y
> > +aml*__;Iw!!Oq50-tQ!_S4huPtQ7bwYwG-
> J3eOtmYscvX_TjlRfR7tjpa6d2dqUQ67RUN
> > +I9X1VKpsi1XPH8nA$
> > +
> > +title: Texas Instruments ADS131E0x 4-, 6-, and 8-Channel ADCs
> 
> Not currently supporting 6 channel variants?

It should be working without problem! But since I don't have HW to test I've left it out. 

> 
> > +
> > +maintainers:
> > +  - Tomislav Denis <tomislav.denis@avl.com>
> > +
> > +description: |
> > +  The ADS131E0x are a family of multichannel, simultaneous sampling,
> > +  24-bit, delta-sigma, analog-to-digital converters (ADCs) with a
> > +  built-in programmable gain amplifier (PGA), internal reference
> > +  and an onboard oscillator.
> > +  The communication with ADC chip is via the SPI bus (mode 1).
> > +
> > +
> > + https://urldefense.com/v3/__https://www.ti.com/lit/ds/symlink/ads131
> > + e08.pdf__;!!Oq50-tQ!_S4huPtQ7bwYwG-
> J3eOtmYscvX_TjlRfR7tjpa6d2dqUQ67R
> > + UNI9X1VKpsjgTd5HaA$
> > +
> > +properties:
> > +  compatible:
> > +    enum:
> > +      - ti,ads131e04
> > +      - ti,ads131e08
> > +
> > +  reg:
> > +    description: |
> > +      SPI chip select number
> 
> That is entirely standard so no real need to put a description of reg for an spi
> device.
> 
> > +    maxItems: 1
> > +
> > +  spi-cpha: true
> > +
> > +  clocks:
> > +    description: |
> > +      Device tree identifier to the clock source (2.048 MHz)
> > +      Note: clock source is selected using CLKSEL pin
> > +    maxItems: 1
> > +
> > +  clock-names:
> > +    items:
> > +      - const: adc-clk
> > +
> > +  interrupts:
> > +    description: |
> > +      IRQ line for the ADC data ready
> > +    maxItems: 1
> > +
> > +  vref-supply:
> > +    description: |
> > +      Optional external voltage reference. Has to be supplied, if
> > +      ti,vref-sel equals 2
> > +
> > +  ti,vref-sel:
> > +    description: |
> > +      Select the voltage reference source
> > +      Valid values are:
> > +      0: Internal reference 2.4V
> > +      1: Internal reference 4V
> > +      2: External reference source (vref-supply is required)
> 
> With optional external references we normally just use their presense to indicate
> that they should be used.
> 
> You'll still need a parameter to pick the internal reference though.
> 
> > +    $ref: /schemas/types.yaml#/definitions/uint32
> > +    enum: [0, 1, 2]
> > +    default: 0
> > +
> > +  ti,datarate:
> > +    description: |
> > +      ADC data rate in kSPS
> > +    $ref: /schemas/types.yaml#/definitions/uint32
> > +    enum: [1, 2, 4, 8, 16, 32, 64]
> > +    default: 1
> 
> Why is this a devicetree element rather than runtime controllable?

Number of data bytes per channel depends on data rate. To be able to change data rate 
dynamically would require to change scan_type.realbits also dynamically! I am not sure
if that make sense and also if it is possible to do it?

> 
> > +
> > +  ti,gain:
> > +    description: |
> > +      The gain value for the PGA function
> > +    $ref: /schemas/types.yaml#/definitions/uint32
> > +    enum: [1, 2, 4, 8, 12]
> > +    default: 1
> 
> Isn't this per channel?  Also this explanation should mention why it is a board
> related characteristic rather than a runtime tuneable (I'm fine with having it here,
> but good to add a bit of info on that to the description).
> 
> > +
> > +  ti,adc-channels:
> > +    description: |
> > +      List of single-ended channels muxed for this ADC
> > +      - 4 channels, numbered from 0 to 3 for ti,ads131e04
> > +      - 8 channels, numbered from 0 to 7 for ti,ads131e08
> > +    $ref: /schemas/types.yaml#/definitions/uint32-array
> 
> We've fairly recently introduced a generic adc channel binding that I'd prefer is
> used in drivers going forwards.
> 
> See Documentation/device-tree/bindings/iio/adc.txt (or yaml if I've applied that
> patch before you get to this).
> 
> That adds a subnode per channel and gives us an easy way to then provide per
> channel parameters.  It's heavier weight than what you have here, but much
> more flexible.
> 
> > +
> > +required:
> > +  - compatible
> > +  - reg
> > +  - spi-cpha
> > +  - clocks
> > +  - clock-names
> > +  - interrupts
> > +  - ti,adc-channels
> > +
> > +allOf:
> > +  - if:
> > +      properties:
> > +        compatible:
> > +          contains:
> > +            const: ti,ads131e04
> > +
> > +  - then:
> > +      properties:
> > +        ti,adc-channels:
> > +          minItems: 1
> > +          maxItems: 4
> > +          items:
> > +            minimum: 0
> > +            maximum: 3
> > +
> > +  - if:
> > +      properties:
> > +        compatible:
> > +          contains:
> > +            const: ti,ads131e08
> > +
> > +  - then:
> > +      properties:
> > +        ti,adc-channels:
> > +          minItems: 1
> > +          maxItems: 8
> > +          items:
> > +            minimum: 0
> > +            maximum: 7
> > +
> > +examples:
> > +  - |
> > +    #include <dt-bindings/interrupt-controller/irq.h>
> > +
> > +    spidev@0 {
> > +      compatible = "ti,ads131e08";
> > +      reg = <0>;
> > +      spi-max-frequency = <1000000>;
> > +      spi-cpha;
> > +      clocks = <&clk2048k>;
> > +      clock-names = "adc-clk";
> > +      interrupt-parent = <&gpio5>;
> > +      interrupts = <28 IRQ_TYPE_EDGE_FALLING>;
> > +      vref-supply = <&vref_reg>;
> > +      ti,vref-sel = <2>;
> > +      ti,datarate = <1>;
> > +      ti,gain = <1>;
> > +      ti,adc-channels = <0 1 2 3 4 5 6 7>;
> > +    };
> > +...
> > diff --git a/MAINTAINERS b/MAINTAINERS index 28bc5f9..0c351c7 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -17224,6 +17224,7 @@ TI ADS131E0X ADC SERIES DRIVER
> >  M:	Tomislav Denis <tomislav.denis@avl.com>
> >  L:	linux-iio@vger.kernel.org
> >  S:	Maintained
> > +F:	Documentation/devicetree/bindings/iio/adc/ti,ads131e08.yaml
> >  F:	drivers/iio/adc/ti-ads131e08.c
> >
> >  TI AM437X VPFE DRIVER


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] bindings: iio: adc: Add documentation for ADS131E0x ADC driver
  2021-01-01 22:41     ` Denis, Tomislav AVL DiTEST
@ 2021-01-02 14:15       ` Jonathan Cameron
  0 siblings, 0 replies; 9+ messages in thread
From: Jonathan Cameron @ 2021-01-02 14:15 UTC (permalink / raw)
  To: Denis, Tomislav AVL DiTEST; +Cc: linux-iio, devicetree

On Fri, 1 Jan 2021 22:41:35 +0000
"Denis, Tomislav AVL DiTEST" <Tomislav.Denis@avl.com> wrote:

> Hi Jonathan,
> 
> Thanks for great review and hints that you gave me.
> A few comments inline.
> 
> BR,
> 
> Tomislav
> 
Replies inline.

> > > +title: Texas Instruments ADS131E0x 4-, 6-, and 8-Channel ADCs  
> > 
> > Not currently supporting 6 channel variants?  
> 
> It should be working without problem! But since I don't have HW to test I've left it out. 
> 

Personally I'd just be an optimist and put it in.  Seems very unlikely
it won't work given you have variants on either side of it.


> > > +    $ref: /schemas/types.yaml#/definitions/uint32
> > > +    enum: [0, 1, 2]
> > > +    default: 0
> > > +
> > > +  ti,datarate:
> > > +    description: |
> > > +      ADC data rate in kSPS
> > > +    $ref: /schemas/types.yaml#/definitions/uint32
> > > +    enum: [1, 2, 4, 8, 16, 32, 64]
> > > +    default: 1  
> > 
> > Why is this a devicetree element rather than runtime controllable?  
> 
> Number of data bytes per channel depends on data rate. To be able to change data rate 
> dynamically would require to change scan_type.realbits also dynamically! I am not sure
> if that make sense and also if it is possible to do it?

Indeed not possible to do runtime variable resolution currently.  We have talked
about implementing it a few times, but it's rather fiddly to do hence hasn't happened
yet.

hmm. It might be better to control the channel resolution in the device
tree as that feels a bit less like something that ought to be runtime controllable.

From a quick look at the datasheet it looks like you can have the same data width
for 32 and 64 ksps (16 bits) and the same for all the other rates (24 bits)

However, given you are using the same number of storage bits anyway, you could
be more cynical and claim 24 bits for all channels and just rely on the upper
bits being 0 for the 16 bit cases.  That way this would just become a
userspace sampling frequency control with slightly missleading apparent
precision + the need to stash the realbits value you are using for scale
somewhere else. If we do this we end up with entirely standard userspace
interface and no need for this control in DT.

Jonathan

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2021-01-02 14:16 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-11-27 19:42 [PATCH 0/2] Add support for ADS131E0x ADC family tomislav.denis
2020-11-27 19:42 ` [PATCH 1/2] iio: adc: Add driver for Texas Instruments " tomislav.denis
2020-11-28 13:02   ` Jonathan Cameron
2020-11-28 23:58   ` kernel test robot
2020-11-27 19:42 ` [PATCH 2/2] bindings: iio: adc: Add documentation for ADS131E0x ADC driver tomislav.denis
2020-11-28 12:34   ` Jonathan Cameron
2021-01-01 22:41     ` Denis, Tomislav AVL DiTEST
2021-01-02 14:15       ` Jonathan Cameron
2020-11-30 17:36   ` Rob Herring

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).