Message ID | 20190322111108.19383-3-gregory.clement@bootlin.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Add support for the TI ADS8344 A/DC chips | expand |
On Fri, Mar 22, 2019 at 12:11:08PM +0100, Gregory CLEMENT wrote: > This adds support for the Texas Instruments ADS8344 ADC chip. This chip > has a 16-bit 8-Channel ADC and is access directly through SPI. > > Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com> > --- > drivers/iio/adc/Kconfig | 10 ++ > drivers/iio/adc/Makefile | 1 + > drivers/iio/adc/ti-ads8344.c | 200 +++++++++++++++++++++++++++++++++++ > 3 files changed, 211 insertions(+) > create mode 100644 drivers/iio/adc/ti-ads8344.c > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 76db6e5cc296..447d3a871746 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -967,6 +967,16 @@ config TI_ADS7950 > To compile this driver as a module, choose M here: the > module will be called ti-ads7950. > > +config TI_ADS8344 > + tristate "Texas Instruments ADS8344" > + depends on SPI && OF > + help > + If you say yes here you get support for Texas Instruments ADS8344 > + ADC chips > + > + This driver can also be built as a module. If so, the module will be > + called ti-ads8344. > + > config TI_ADS8688 > tristate "Texas Instruments ADS8688" > depends on SPI && OF > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index 6fcebd167524..1f3ae934111d 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -87,6 +87,7 @@ obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o > obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o > obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o > 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_AM335X_ADC) += ti_am335x_adc.o > diff --git a/drivers/iio/adc/ti-ads8344.c b/drivers/iio/adc/ti-ads8344.c > new file mode 100644 > index 000000000000..649ed05bf1c9 > --- /dev/null > +++ b/drivers/iio/adc/ti-ads8344.c > @@ -0,0 +1,200 @@ > +// SPDX-License-Identifier: GPL-2.0+ Inconsistency in License. > > + * ADS8344 16-bit 8-Channel ADC driver > + * > + * Author: Gregory CLEMENT <gregory.clement@bootlin.com> > + * > + * Datasheet: http://www.ti.com/lit/ds/symlink/ads8344.pdf > + */ > + > +#include <linux/delay.h> > +#include <linux/iio/buffer.h> > +#include <linux/iio/iio.h> > +#include <linux/module.h> > +#include <linux/regulator/consumer.h> > +#include <linux/spi/spi.h> > + > +#define ADS8344_START BIT(7) > +#define ADS8344_SINGLE_END BIT(2) > +#define ADS8344_CHANNEL(channel) ((channel) << 4) > +#define ADS8344_CLOCK_INTERNAL 0x2 /* PD1 = 1 and PD0 = 0 */ > + > +struct ads8344 { > + struct spi_device *spi; > + struct regulator *reg; > + struct mutex lock; This requires a comment explaining its purpose. checkpatch issues a warning IIRC. > + > + u8 tx_buf ____cacheline_aligned; > + u16 rx_buf; > +}; > + > +#define ADS8344_VOLTAGE_CHANNEL(chan, si) \ > + { \ > + .type = IIO_VOLTAGE, \ > + .indexed = 1, \ > + .channel = chan, \ > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ > + } > + > +#define ADS8344_VOLTAGE_CHANNEL_DIFF(chan1, chan2, si) \ > + { \ > + .type = IIO_VOLTAGE, \ > + .indexed = 1, \ > + .channel = (chan1), \ > + .channel2 = (chan2), \ > + .differential = 1, \ > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ > + } > + > +static const struct iio_chan_spec ads8344_channels[] = { > + ADS8344_VOLTAGE_CHANNEL(0, 0), > + ADS8344_VOLTAGE_CHANNEL(1, 4), > + ADS8344_VOLTAGE_CHANNEL(2, 1), > + ADS8344_VOLTAGE_CHANNEL(3, 5), > + ADS8344_VOLTAGE_CHANNEL(4, 2), > + ADS8344_VOLTAGE_CHANNEL(5, 6), > + ADS8344_VOLTAGE_CHANNEL(6, 3), > + ADS8344_VOLTAGE_CHANNEL(7, 7), > + ADS8344_VOLTAGE_CHANNEL_DIFF(0, 1, 8), > + ADS8344_VOLTAGE_CHANNEL_DIFF(2, 3, 9), > + ADS8344_VOLTAGE_CHANNEL_DIFF(4, 5, 10), > + ADS8344_VOLTAGE_CHANNEL_DIFF(6, 7, 11), > + ADS8344_VOLTAGE_CHANNEL_DIFF(1, 0, 12), > + ADS8344_VOLTAGE_CHANNEL_DIFF(3, 2, 13), > + ADS8344_VOLTAGE_CHANNEL_DIFF(5, 4, 14), > + ADS8344_VOLTAGE_CHANNEL_DIFF(7, 6, 15), > +}; > + > +static int ads8344_adc_conversion(struct ads8344 *adc, int channel, > + bool differential) > +{ > + struct spi_device *spi = adc->spi; > + int ret; > + > + adc->tx_buf = ADS8344_START; > + if (!differential) > + adc->tx_buf |= ADS8344_SINGLE_END; > + adc->tx_buf |= ADS8344_CHANNEL(channel); > + adc->tx_buf |= ADS8344_CLOCK_INTERNAL; > + > + ret = spi_write(spi, &adc->tx_buf, 1); > + if (ret) > + return ret; > + > + udelay(9); > + > + ret = spi_read(spi, &adc->rx_buf, 2); > + if (ret) > + return ret; > + > + return adc->rx_buf; > +} > + > +static int ads8344_read_raw(struct iio_dev *iio, > + struct iio_chan_spec const *channel, int *value, > + int *shift, long mask) > +{ > + struct ads8344 *adc = iio_priv(iio); > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + mutex_lock(&adc->lock); Just curious: What would happen if you don't lock ? I'm interested in looking for whatever happens in such a case and how to exploit it ? Also, bus transactions are often atomic using their locking/unlocking procedures. So, why do we need locking procedures in iio drivers itself ? > + *value = ads8344_adc_conversion(adc, channel->scan_index, > + channel->differential); > + mutex_unlock(&adc->lock); > + if (*value < 0) > + return *value; > + > + return IIO_VAL_INT; > + case IIO_CHAN_INFO_SCALE: > + *value = regulator_get_voltage(adc->reg); > + if (*value < 0) > + return *value; > + > + /* convert regulator output voltage to mV */ > + *value /= 1000; > + *shift = 16; > + > + return IIO_VAL_FRACTIONAL_LOG2; > + default: > + return -EINVAL; > + } > +} > + > +static const struct iio_info ads8344_info = { > + .read_raw = ads8344_read_raw, > +}; > + > +static int ads8344_probe(struct spi_device *spi) > +{ > + struct iio_dev *indio_dev; > + struct ads8344 *adc; > + int ret; > + > + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); > + if (!indio_dev) > + return -ENOMEM; > + > + adc = iio_priv(indio_dev); > + adc->spi = spi; > + mutex_init(&adc->lock); > + > + indio_dev->name = dev_name(&spi->dev); > + indio_dev->dev.parent = &spi->dev; > + indio_dev->dev.of_node = spi->dev.of_node; > + indio_dev->info = &ads8344_info; > + indio_dev->modes = INDIO_DIRECT_MODE; > + indio_dev->channels = ads8344_channels; > + indio_dev->num_channels = ARRAY_SIZE(ads8344_channels); > + > + adc->reg = devm_regulator_get(&spi->dev, "vref"); > + if (IS_ERR(adc->reg)) > + return PTR_ERR(adc->reg); > + > + ret = regulator_enable(adc->reg); > + if (ret) > + return ret; > + > + spi_set_drvdata(spi, indio_dev); > + > + ret = iio_device_register(indio_dev); > + if (ret) { > + regulator_disable(adc->reg); > + return ret; > + } IDK but it is advised not to mix devm_* with regular functions. If there is any possibilty to use `devm_add_action_or_reset` here ? This would help get rid of `ads8344_remove` and help smooth unwinding in failure w/o any race. > + return 0; > +} > + > +static int ads8344_remove(struct spi_device *spi) > +{ > + struct iio_dev *indio_dev = spi_get_drvdata(spi); > + struct ads8344 *adc = iio_priv(indio_dev); > + > + iio_device_unregister(indio_dev); > + regulator_disable(adc->reg); > + > + return 0; > +} > + > +static const struct of_device_id ads8344_of_match[] = { > + { .compatible = "ti,ads8344", }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, ads8344_dt_ids); > + > +static struct spi_driver ads8344_driver = { > + .driver = { > + .name = "ads8344", > + .of_match_table = ads8344_of_match, > + }, > + .probe = ads8344_probe, > + .remove = ads8344_remove, > +}; > +module_spi_driver(ads8344_driver); > + > +MODULE_AUTHOR("Gregory CLEMENT <gregory.clement@bootlin.com>"); > +MODULE_DESCRIPTION("ADS8344 driver"); > +MODULE_LICENSE("GPL v2"); this is a mismatch.
On Sun, 24 Mar 2019 20:46:25 +0530 Himanshu Jha <himanshujha199640@gmail.com> wrote: > On Fri, Mar 22, 2019 at 12:11:08PM +0100, Gregory CLEMENT wrote: > > This adds support for the Texas Instruments ADS8344 ADC chip. This chip > > has a 16-bit 8-Channel ADC and is access directly through SPI. > > > > Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com> Hi Gregory / Himanshu, Comments inline, though mostly addressing Himanshu's questions as I was here ;) I'm happy with the code, though up to you if you want to look at taking everything to device managed allocators. Just the license issue that is stopping me applying this. Jonathan > > --- > > drivers/iio/adc/Kconfig | 10 ++ > > drivers/iio/adc/Makefile | 1 + > > drivers/iio/adc/ti-ads8344.c | 200 +++++++++++++++++++++++++++++++++++ > > 3 files changed, 211 insertions(+) > > create mode 100644 drivers/iio/adc/ti-ads8344.c > > > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > > index 76db6e5cc296..447d3a871746 100644 > > --- a/drivers/iio/adc/Kconfig > > +++ b/drivers/iio/adc/Kconfig > > @@ -967,6 +967,16 @@ config TI_ADS7950 > > To compile this driver as a module, choose M here: the > > module will be called ti-ads7950. > > > > +config TI_ADS8344 > > + tristate "Texas Instruments ADS8344" > > + depends on SPI && OF > > + help > > + If you say yes here you get support for Texas Instruments ADS8344 > > + ADC chips > > + > > + This driver can also be built as a module. If so, the module will be > > + called ti-ads8344. > > + > > config TI_ADS8688 > > tristate "Texas Instruments ADS8688" > > depends on SPI && OF > > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > > index 6fcebd167524..1f3ae934111d 100644 > > --- a/drivers/iio/adc/Makefile > > +++ b/drivers/iio/adc/Makefile > > @@ -87,6 +87,7 @@ obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o > > obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o > > obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o > > 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_AM335X_ADC) += ti_am335x_adc.o > > diff --git a/drivers/iio/adc/ti-ads8344.c b/drivers/iio/adc/ti-ads8344.c > > new file mode 100644 > > index 000000000000..649ed05bf1c9 > > --- /dev/null > > +++ b/drivers/iio/adc/ti-ads8344.c > > @@ -0,0 +1,200 @@ > > +// SPDX-License-Identifier: GPL-2.0+ > > Inconsistency in License. > Good spot. That alone means we are going to need a v3. > > > > + * ADS8344 16-bit 8-Channel ADC driver > > + * > > + * Author: Gregory CLEMENT <gregory.clement@bootlin.com> > > + * > > + * Datasheet: http://www.ti.com/lit/ds/symlink/ads8344.pdf > > + */ > > + > > +#include <linux/delay.h> > > +#include <linux/iio/buffer.h> > > +#include <linux/iio/iio.h> > > +#include <linux/module.h> > > +#include <linux/regulator/consumer.h> > > +#include <linux/spi/spi.h> > > + > > +#define ADS8344_START BIT(7) > > +#define ADS8344_SINGLE_END BIT(2) > > +#define ADS8344_CHANNEL(channel) ((channel) << 4) > > +#define ADS8344_CLOCK_INTERNAL 0x2 /* PD1 = 1 and PD0 = 0 */ > > + > > +struct ads8344 { > > + struct spi_device *spi; > > + struct regulator *reg; > > + struct mutex lock; > > This requires a comment explaining its purpose. > checkpatch issues a warning IIRC. In this particular case that might have answered the question you have below! > > > + > > + u8 tx_buf ____cacheline_aligned; > > + u16 rx_buf; > > +}; > > + > > +#define ADS8344_VOLTAGE_CHANNEL(chan, si) \ > > + { \ > > + .type = IIO_VOLTAGE, \ > > + .indexed = 1, \ > > + .channel = chan, \ > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ > > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ > > + } > > + > > +#define ADS8344_VOLTAGE_CHANNEL_DIFF(chan1, chan2, si) \ > > + { \ > > + .type = IIO_VOLTAGE, \ > > + .indexed = 1, \ > > + .channel = (chan1), \ > > + .channel2 = (chan2), \ > > + .differential = 1, \ > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ > > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ > > + } > > + > > +static const struct iio_chan_spec ads8344_channels[] = { > > + ADS8344_VOLTAGE_CHANNEL(0, 0), > > + ADS8344_VOLTAGE_CHANNEL(1, 4), > > + ADS8344_VOLTAGE_CHANNEL(2, 1), > > + ADS8344_VOLTAGE_CHANNEL(3, 5), > > + ADS8344_VOLTAGE_CHANNEL(4, 2), > > + ADS8344_VOLTAGE_CHANNEL(5, 6), > > + ADS8344_VOLTAGE_CHANNEL(6, 3), > > + ADS8344_VOLTAGE_CHANNEL(7, 7), > > + ADS8344_VOLTAGE_CHANNEL_DIFF(0, 1, 8), > > + ADS8344_VOLTAGE_CHANNEL_DIFF(2, 3, 9), > > + ADS8344_VOLTAGE_CHANNEL_DIFF(4, 5, 10), > > + ADS8344_VOLTAGE_CHANNEL_DIFF(6, 7, 11), > > + ADS8344_VOLTAGE_CHANNEL_DIFF(1, 0, 12), > > + ADS8344_VOLTAGE_CHANNEL_DIFF(3, 2, 13), > > + ADS8344_VOLTAGE_CHANNEL_DIFF(5, 4, 14), > > + ADS8344_VOLTAGE_CHANNEL_DIFF(7, 6, 15), > > +}; > > + > > +static int ads8344_adc_conversion(struct ads8344 *adc, int channel, > > + bool differential) > > +{ > > + struct spi_device *spi = adc->spi; > > + int ret; > > + > > + adc->tx_buf = ADS8344_START; > > + if (!differential) > > + adc->tx_buf |= ADS8344_SINGLE_END; > > + adc->tx_buf |= ADS8344_CHANNEL(channel); > > + adc->tx_buf |= ADS8344_CLOCK_INTERNAL; > > + > > + ret = spi_write(spi, &adc->tx_buf, 1); > > + if (ret) > > + return ret; > > + > > + udelay(9); > > + > > + ret = spi_read(spi, &adc->rx_buf, 2); > > + if (ret) > > + return ret; > > + > > + return adc->rx_buf; > > +} > > + > > +static int ads8344_read_raw(struct iio_dev *iio, > > + struct iio_chan_spec const *channel, int *value, > > + int *shift, long mask) > > +{ > > + struct ads8344 *adc = iio_priv(iio); > > + > > + switch (mask) { > > + case IIO_CHAN_INFO_RAW: > > + mutex_lock(&adc->lock); > > Just curious: > What would happen if you don't lock ? Well as I'm here I'll answer. Key here is that there is nothing stopping two concurrent reads of a sysfs file. In this particular case the lock is protecting adc->tx_buff and rx_buff. As a simple example, imagine two calls are reading different channels. As they can race, it's possible both reads occur such that we either do two bus reads with the same channel set, or we end up doing one read after the other, but both functions end up using the same buffer contents. Upshot is that we end up reading the wrong channel. > > I'm interested in looking for whatever happens in such a case > and how to exploit it ? > > Also, bus transactions are often atomic using their locking/unlocking > procedures. So, why do we need locking procedures in iio drivers itself ? Yes, this could be avoided, but only at the cost of separate buffers, made tricky by the dma alignment requirements that mean we can't use them off the stack. One option would be to use spi_write_then_read, which uses the SPI bus locking + some local buffers thus avoiding the problem. However the need to have a sleep between them stops that option. You can think of it as the bus locks prevent multiple users of the bus (which may be different drivers) and the IIO locks prevent multiple users of IIO device instance specific data. Hence there is no nice way to do it with a single lock as they have different / overlapping scopes. > > > + *value = ads8344_adc_conversion(adc, channel->scan_index, > > + channel->differential); > > + mutex_unlock(&adc->lock); > > + if (*value < 0) > > + return *value; > > + > > + return IIO_VAL_INT; > > + case IIO_CHAN_INFO_SCALE: > > + *value = regulator_get_voltage(adc->reg); > > + if (*value < 0) > > + return *value; > > + > > + /* convert regulator output voltage to mV */ > > + *value /= 1000; > > + *shift = 16; > > + > > + return IIO_VAL_FRACTIONAL_LOG2; > > + default: > > + return -EINVAL; > > + } > > +} > > + > > +static const struct iio_info ads8344_info = { > > + .read_raw = ads8344_read_raw, > > +}; > > + > > +static int ads8344_probe(struct spi_device *spi) > > +{ > > + struct iio_dev *indio_dev; > > + struct ads8344 *adc; > > + int ret; > > + > > + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); > > + if (!indio_dev) > > + return -ENOMEM; > > + > > + adc = iio_priv(indio_dev); > > + adc->spi = spi; > > + mutex_init(&adc->lock); > > + > > + indio_dev->name = dev_name(&spi->dev); > > + indio_dev->dev.parent = &spi->dev; > > + indio_dev->dev.of_node = spi->dev.of_node; > > + indio_dev->info = &ads8344_info; > > + indio_dev->modes = INDIO_DIRECT_MODE; > > + indio_dev->channels = ads8344_channels; > > + indio_dev->num_channels = ARRAY_SIZE(ads8344_channels); > > + > > + adc->reg = devm_regulator_get(&spi->dev, "vref"); > > + if (IS_ERR(adc->reg)) > > + return PTR_ERR(adc->reg); > > + > > + ret = regulator_enable(adc->reg); > > + if (ret) > > + return ret; > > + > > + spi_set_drvdata(spi, indio_dev); > > + > > + ret = iio_device_register(indio_dev); > > + if (ret) { > > + regulator_disable(adc->reg); > > + return ret; > > + } > > IDK but it is advised not to mix devm_* with regular functions. > > If there is any possibilty to use `devm_add_action_or_reset` here ? > > This would help get rid of `ads8344_remove` and help smooth > unwinding in failure w/o any race. There is no problem as long as you keep in mind that devm_ unwinding occurs after remove occurs. That means that, to maintain ordering of remove being the opposite of probe, once you hit something that needs unwinding manually, you should use the none devm functions for everything after it. Hence I think what we have here is correct. The devm_add_action_or_reset might be worthwhile to avoid the need to manually unwind anything at all though. > > > + return 0; > > +} > > + > > +static int ads8344_remove(struct spi_device *spi) > > +{ > > + struct iio_dev *indio_dev = spi_get_drvdata(spi); > > + struct ads8344 *adc = iio_priv(indio_dev); > > + > > + iio_device_unregister(indio_dev); > > + regulator_disable(adc->reg); > > + > > + return 0; > > +} > > + > > +static const struct of_device_id ads8344_of_match[] = { > > + { .compatible = "ti,ads8344", }, > > + {} > > +}; > > +MODULE_DEVICE_TABLE(of, ads8344_dt_ids); > > + > > +static struct spi_driver ads8344_driver = { > > + .driver = { > > + .name = "ads8344", > > + .of_match_table = ads8344_of_match, > > + }, > > + .probe = ads8344_probe, > > + .remove = ads8344_remove, > > +}; > > +module_spi_driver(ads8344_driver); > > + > > +MODULE_AUTHOR("Gregory CLEMENT <gregory.clement@bootlin.com>"); > > +MODULE_DESCRIPTION("ADS8344 driver"); > > > > +MODULE_LICENSE("GPL v2"); > > this is a mismatch. > >
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 76db6e5cc296..447d3a871746 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -967,6 +967,16 @@ config TI_ADS7950 To compile this driver as a module, choose M here: the module will be called ti-ads7950. +config TI_ADS8344 + tristate "Texas Instruments ADS8344" + depends on SPI && OF + help + If you say yes here you get support for Texas Instruments ADS8344 + ADC chips + + This driver can also be built as a module. If so, the module will be + called ti-ads8344. + config TI_ADS8688 tristate "Texas Instruments ADS8688" depends on SPI && OF diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 6fcebd167524..1f3ae934111d 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -87,6 +87,7 @@ obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o 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_AM335X_ADC) += ti_am335x_adc.o diff --git a/drivers/iio/adc/ti-ads8344.c b/drivers/iio/adc/ti-ads8344.c new file mode 100644 index 000000000000..649ed05bf1c9 --- /dev/null +++ b/drivers/iio/adc/ti-ads8344.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ADS8344 16-bit 8-Channel ADC driver + * + * Author: Gregory CLEMENT <gregory.clement@bootlin.com> + * + * Datasheet: http://www.ti.com/lit/ds/symlink/ads8344.pdf + */ + +#include <linux/delay.h> +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#define ADS8344_START BIT(7) +#define ADS8344_SINGLE_END BIT(2) +#define ADS8344_CHANNEL(channel) ((channel) << 4) +#define ADS8344_CLOCK_INTERNAL 0x2 /* PD1 = 1 and PD0 = 0 */ + +struct ads8344 { + struct spi_device *spi; + struct regulator *reg; + struct mutex lock; + + u8 tx_buf ____cacheline_aligned; + u16 rx_buf; +}; + +#define ADS8344_VOLTAGE_CHANNEL(chan, si) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + } + +#define ADS8344_VOLTAGE_CHANNEL_DIFF(chan1, chan2, si) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (chan1), \ + .channel2 = (chan2), \ + .differential = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + } + +static const struct iio_chan_spec ads8344_channels[] = { + ADS8344_VOLTAGE_CHANNEL(0, 0), + ADS8344_VOLTAGE_CHANNEL(1, 4), + ADS8344_VOLTAGE_CHANNEL(2, 1), + ADS8344_VOLTAGE_CHANNEL(3, 5), + ADS8344_VOLTAGE_CHANNEL(4, 2), + ADS8344_VOLTAGE_CHANNEL(5, 6), + ADS8344_VOLTAGE_CHANNEL(6, 3), + ADS8344_VOLTAGE_CHANNEL(7, 7), + ADS8344_VOLTAGE_CHANNEL_DIFF(0, 1, 8), + ADS8344_VOLTAGE_CHANNEL_DIFF(2, 3, 9), + ADS8344_VOLTAGE_CHANNEL_DIFF(4, 5, 10), + ADS8344_VOLTAGE_CHANNEL_DIFF(6, 7, 11), + ADS8344_VOLTAGE_CHANNEL_DIFF(1, 0, 12), + ADS8344_VOLTAGE_CHANNEL_DIFF(3, 2, 13), + ADS8344_VOLTAGE_CHANNEL_DIFF(5, 4, 14), + ADS8344_VOLTAGE_CHANNEL_DIFF(7, 6, 15), +}; + +static int ads8344_adc_conversion(struct ads8344 *adc, int channel, + bool differential) +{ + struct spi_device *spi = adc->spi; + int ret; + + adc->tx_buf = ADS8344_START; + if (!differential) + adc->tx_buf |= ADS8344_SINGLE_END; + adc->tx_buf |= ADS8344_CHANNEL(channel); + adc->tx_buf |= ADS8344_CLOCK_INTERNAL; + + ret = spi_write(spi, &adc->tx_buf, 1); + if (ret) + return ret; + + udelay(9); + + ret = spi_read(spi, &adc->rx_buf, 2); + if (ret) + return ret; + + return adc->rx_buf; +} + +static int ads8344_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *channel, int *value, + int *shift, long mask) +{ + struct ads8344 *adc = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&adc->lock); + *value = ads8344_adc_conversion(adc, channel->scan_index, + channel->differential); + mutex_unlock(&adc->lock); + if (*value < 0) + return *value; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *value = regulator_get_voltage(adc->reg); + if (*value < 0) + return *value; + + /* convert regulator output voltage to mV */ + *value /= 1000; + *shift = 16; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info ads8344_info = { + .read_raw = ads8344_read_raw, +}; + +static int ads8344_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ads8344 *adc; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + mutex_init(&adc->lock); + + indio_dev->name = dev_name(&spi->dev); + indio_dev->dev.parent = &spi->dev; + indio_dev->dev.of_node = spi->dev.of_node; + indio_dev->info = &ads8344_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ads8344_channels; + indio_dev->num_channels = ARRAY_SIZE(ads8344_channels); + + adc->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(adc->reg)) + return PTR_ERR(adc->reg); + + ret = regulator_enable(adc->reg); + if (ret) + return ret; + + spi_set_drvdata(spi, indio_dev); + + ret = iio_device_register(indio_dev); + if (ret) { + regulator_disable(adc->reg); + return ret; + } + + return 0; +} + +static int ads8344_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ads8344 *adc = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(adc->reg); + + return 0; +} + +static const struct of_device_id ads8344_of_match[] = { + { .compatible = "ti,ads8344", }, + {} +}; +MODULE_DEVICE_TABLE(of, ads8344_dt_ids); + +static struct spi_driver ads8344_driver = { + .driver = { + .name = "ads8344", + .of_match_table = ads8344_of_match, + }, + .probe = ads8344_probe, + .remove = ads8344_remove, +}; +module_spi_driver(ads8344_driver); + +MODULE_AUTHOR("Gregory CLEMENT <gregory.clement@bootlin.com>"); +MODULE_DESCRIPTION("ADS8344 driver"); +MODULE_LICENSE("GPL v2");
This adds support for the Texas Instruments ADS8344 ADC chip. This chip has a 16-bit 8-Channel ADC and is access directly through SPI. Signed-off-by: Gregory CLEMENT <gregory.clement@bootlin.com> --- drivers/iio/adc/Kconfig | 10 ++ drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-ads8344.c | 200 +++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 drivers/iio/adc/ti-ads8344.c