diff options
author | Phil Reid <preid@electromag.com.au> | 2017-01-16 09:38:24 +0100 |
---|---|---|
committer | Jonathan Cameron <jic23@kernel.org> | 2017-01-22 14:21:28 +0100 |
commit | ac2bec9d587c6a423a00c7a2d21a8a5928dfedf5 (patch) | |
tree | 84fd27c6fb2daeb02b0485067bbd700e56c2a16f /drivers/iio/adc/ti-tlc4541.c | |
parent | dt-bindings: iio: document MAX11100 ADC (diff) | |
download | linux-ac2bec9d587c6a423a00c7a2d21a8a5928dfedf5.tar.xz linux-ac2bec9d587c6a423a00c7a2d21a8a5928dfedf5.zip |
iio: adc: tlc4541: add support for TI tlc4541 adc
This adds TI's tlc4541 16-bit ADC driver. Which is a single channel
ADC. Supports raw and trigger buffer access.
Also supports the tlc3541 14-bit device, which has not been tested.
Implementation of the tlc3541 is fairly straight forward thou.
Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Phil Reid <preid@electromag.com.au>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>
Diffstat (limited to 'drivers/iio/adc/ti-tlc4541.c')
-rw-r--r-- | drivers/iio/adc/ti-tlc4541.c | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/drivers/iio/adc/ti-tlc4541.c b/drivers/iio/adc/ti-tlc4541.c new file mode 100644 index 000000000000..78d91a069ea4 --- /dev/null +++ b/drivers/iio/adc/ti-tlc4541.c @@ -0,0 +1,271 @@ +/* + * TI tlc4541 ADC Driver + * + * Copyright (C) 2017 Phil Reid + * + * Datasheets can be found here: + * http://www.ti.com/lit/gpn/tlc3541 + * http://www.ti.com/lit/gpn/tlc4541 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The tlc4541 requires 24 clock cycles to start a transfer. + * Conversion then takes 2.94us to complete before data is ready + * Data is returned MSB first. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> + +struct tlc4541_state { + struct spi_device *spi; + struct regulator *reg; + struct spi_transfer scan_single_xfer[3]; + struct spi_message scan_single_msg; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + * 2 bytes data + 6 bytes padding + 8 bytes timestamp when + * call iio_push_to_buffers_with_timestamp. + */ + __be16 rx_buf[8] ____cacheline_aligned; +}; + +struct tlc4541_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +enum tlc4541_id { + TLC3541, + TLC4541, +}; + +#define TLC4541_V_CHAN(bits, bitshift) { \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = (bitshift), \ + .endianness = IIO_BE, \ + }, \ + } + +#define DECLARE_TLC4541_CHANNELS(name, bits, bitshift) \ +const struct iio_chan_spec name ## _channels[] = { \ + TLC4541_V_CHAN(bits, bitshift), \ + IIO_CHAN_SOFT_TIMESTAMP(1), \ +} + +static DECLARE_TLC4541_CHANNELS(tlc3541, 14, 2); +static DECLARE_TLC4541_CHANNELS(tlc4541, 16, 0); + +static const struct tlc4541_chip_info tlc4541_chip_info[] = { + [TLC3541] = { + .channels = tlc3541_channels, + .num_channels = ARRAY_SIZE(tlc3541_channels), + }, + [TLC4541] = { + .channels = tlc4541_channels, + .num_channels = ARRAY_SIZE(tlc4541_channels), + }, +}; + +static irqreturn_t tlc4541_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct tlc4541_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret < 0) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, st->rx_buf, + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int tlc4541_get_range(struct tlc4541_state *st) +{ + int vref; + + vref = regulator_get_voltage(st->reg); + if (vref < 0) + return vref; + + vref /= 1000; + + return vref; +} + +static int tlc4541_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret = 0; + struct tlc4541_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + ret = spi_sync(st->spi, &st->scan_single_msg); + iio_device_release_direct_mode(indio_dev); + if (ret < 0) + return ret; + *val = be16_to_cpu(st->rx_buf[0]); + *val = *val >> chan->scan_type.shift; + *val &= GENMASK(chan->scan_type.realbits - 1, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = tlc4541_get_range(st); + if (ret < 0) + return ret; + *val = ret; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static const struct iio_info tlc4541_info = { + .read_raw = &tlc4541_read_raw, + .driver_module = THIS_MODULE, +}; + +static int tlc4541_probe(struct spi_device *spi) +{ + struct tlc4541_state *st; + struct iio_dev *indio_dev; + const struct tlc4541_chip_info *info; + int ret; + int8_t device_init = 0; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + info = &tlc4541_chip_info[spi_get_device_id(spi)->driver_data]; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = info->channels; + indio_dev->num_channels = info->num_channels; + indio_dev->info = &tlc4541_info; + + /* perform reset */ + spi_write(spi, &device_init, 1); + + /* Setup default message */ + st->scan_single_xfer[0].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[0].len = 3; + st->scan_single_xfer[1].delay_usecs = 3; + st->scan_single_xfer[2].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[2].len = 2; + + spi_message_init_with_transfers(&st->scan_single_msg, + st->scan_single_xfer, 3); + + st->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &tlc4541_trigger_handler, NULL); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer; + + return 0; + +error_cleanup_buffer: + iio_triggered_buffer_cleanup(indio_dev); +error_disable_reg: + regulator_disable(st->reg); + + return ret; +} + +static int tlc4541_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct tlc4541_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + regulator_disable(st->reg); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id tlc4541_dt_ids[] = { + { .compatible = "ti,tlc3541", }, + { .compatible = "ti,tlc4541", }, + {} +}; +MODULE_DEVICE_TABLE(of, tlc4541_dt_ids); +#endif + +static const struct spi_device_id tlc4541_id[] = { + {"tlc3541", TLC3541}, + {"tlc4541", TLC4541}, + {} +}; +MODULE_DEVICE_TABLE(spi, tlc4541_id); + +static struct spi_driver tlc4541_driver = { + .driver = { + .name = "tlc4541", + .of_match_table = of_match_ptr(tlc4541_dt_ids), + }, + .probe = tlc4541_probe, + .remove = tlc4541_remove, + .id_table = tlc4541_id, +}; +module_spi_driver(tlc4541_driver); + +MODULE_AUTHOR("Phil Reid <preid@electromag.com.au>"); +MODULE_DESCRIPTION("Texas Instruments TLC4541 ADC"); +MODULE_LICENSE("GPL v2"); |