[PATCH 1/2] iio: adc: Add driver for Texas Instruments ADS131E0x ADC family

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Tomislav Denis <tomislav.denis@xxxxxxx>

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@xxxxxxx>
---
 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@xxxxxxxxxx>
 S:	Odd Fixes
 F:	drivers/gpio/gpio-thunderx.c
 
+TI ADS131E0X ADC SERIES DRIVER
+M:	Tomislav Denis <tomislav.denis@xxxxxxx>
+L:	linux-iio@xxxxxxxxxxxxxxx
+S:	Maintained
+F:	drivers/iio/adc/ti-ads131e08.c
+
 TI AM437X VPFE DRIVER
 M:	"Lad, Prabhakar" <prabhakar.csengg@xxxxxxxxx>
 L:	linux-media@xxxxxxxxxxxxxxx
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@xxxxxxx>
+ *
+ * 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@xxxxxxx>");
+MODULE_DESCRIPTION("Driver for ADS131E0x ADC family");
+MODULE_LICENSE("GPL v2");
-- 
2.7.4




[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux