mirror of
https://github.com/torvalds/linux.git
synced 2026-09-23 05:04:02 +02:00
iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs
Support for LTC2378-20 and similar analog-to-digital converters. Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com> Reviewed-by: David Lechner <dlechner@baylibre.com> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com> Signed-off-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
This commit is contained in:
parent
6c0e726821
commit
469eafdc69
|
|
@ -15349,6 +15349,7 @@ L: linux-iio@vger.kernel.org
|
|||
S: Supported
|
||||
W: https://ez.analog.com/linux-software-drivers
|
||||
F: Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
|
||||
F: drivers/iio/adc/ltc2378.c
|
||||
|
||||
LTC2664 IIO DAC DRIVER
|
||||
M: Michael Hennerich <michael.hennerich@analog.com>
|
||||
|
|
|
|||
|
|
@ -943,6 +943,18 @@ config LTC2309
|
|||
This driver can also be built as a module. If so, the module will
|
||||
be called ltc2309.
|
||||
|
||||
config LTC2378
|
||||
tristate "Analog Devices LTC2378 ADC driver"
|
||||
depends on SPI
|
||||
depends on REGULATOR || COMPILE_TEST
|
||||
depends on GPIOLIB
|
||||
help
|
||||
Say yes here to build support for Analog Devices LTC2378-20 and
|
||||
similar analog to digital converters.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called ltc2378.
|
||||
|
||||
config LTC2471
|
||||
tristate "Linear Technology LTC2471 and LTC2473 ADC driver"
|
||||
depends on I2C
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
|
|||
obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
|
||||
obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
|
||||
obj-$(CONFIG_LTC2309) += ltc2309.o
|
||||
obj-$(CONFIG_LTC2378) += ltc2378.o
|
||||
obj-$(CONFIG_LTC2471) += ltc2471.o
|
||||
obj-$(CONFIG_LTC2485) += ltc2485.o
|
||||
obj-$(CONFIG_LTC2496) += ltc2496.o ltc2497-core.o
|
||||
|
|
|
|||
387
drivers/iio/adc/ltc2378.c
Normal file
387
drivers/iio/adc/ltc2378.c
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Analog Devices LTC2378 ADC series driver
|
||||
*
|
||||
* Copyright (C) 2026 Analog Devices Inc.
|
||||
* Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/byteorder/generic.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device-id/spi.h>
|
||||
#include <linux/device-id/of.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/types.h>
|
||||
|
||||
#define LTC2378_MAX_DATA_WAIT_US 4 /* max(TBUSYLH + TCONV + TDSDOBUSYL) */
|
||||
|
||||
#define LTC2378_CHANNEL(_sign, _real_bits, _storage_bits) \
|
||||
{ \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.indexed = 1, \
|
||||
.differential = _sign, \
|
||||
.channel = 0, \
|
||||
.channel2 = _sign ? 1 : 0, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.scan_index = 0, \
|
||||
.scan_type = { \
|
||||
.format = _sign ? IIO_SCAN_FORMAT_SIGNED_INT : \
|
||||
IIO_SCAN_FORMAT_UNSIGNED_INT, \
|
||||
.realbits = _real_bits, \
|
||||
.storagebits = _storage_bits, \
|
||||
.shift = _storage_bits - _real_bits, \
|
||||
.endianness = IIO_BE, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define LTC2378_DIFF_CHANNEL(_real_bits) \
|
||||
LTC2378_CHANNEL(1, _real_bits, (((_real_bits) > 16) ? 32 : 16))
|
||||
|
||||
#define LTC2378_PSEUDO_DIFF_CHANNEL(_real_bits) \
|
||||
LTC2378_CHANNEL(0, _real_bits, (((_real_bits) > 16) ? 32 : 16))
|
||||
|
||||
struct ltc2378_chip_info {
|
||||
const char *name;
|
||||
struct iio_chan_spec chan;
|
||||
};
|
||||
|
||||
struct ltc2378_state {
|
||||
const struct ltc2378_chip_info *info;
|
||||
struct gpio_desc *cnv_gpio;
|
||||
struct spi_device *spi;
|
||||
struct mutex lock; /* Protect data acquisition cycle */
|
||||
int ref_uV;
|
||||
struct spi_transfer xfer;
|
||||
|
||||
/*
|
||||
* DMA (thus cache coherency maintenance) requires the transfer buffers
|
||||
* to live in their own cache lines.
|
||||
*/
|
||||
struct {
|
||||
union {
|
||||
__be16 sample_buf16_be;
|
||||
__be32 sample_buf32_be;
|
||||
u16 sample_buf16;
|
||||
u32 sample_buf32;
|
||||
} data;
|
||||
aligned_s64 timestamp;
|
||||
} scan __aligned(IIO_DMA_MINALIGN);
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2364_16_chip_info = {
|
||||
.name = "ltc2364-16",
|
||||
.chan = LTC2378_PSEUDO_DIFF_CHANNEL(16),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2364_18_chip_info = {
|
||||
.name = "ltc2364-18",
|
||||
.chan = LTC2378_PSEUDO_DIFF_CHANNEL(18),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2367_16_chip_info = {
|
||||
.name = "ltc2367-16",
|
||||
.chan = LTC2378_PSEUDO_DIFF_CHANNEL(16),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2367_18_chip_info = {
|
||||
.name = "ltc2367-18",
|
||||
.chan = LTC2378_PSEUDO_DIFF_CHANNEL(18),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2368_16_chip_info = {
|
||||
.name = "ltc2368-16",
|
||||
.chan = LTC2378_PSEUDO_DIFF_CHANNEL(16),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2368_18_chip_info = {
|
||||
.name = "ltc2368-18",
|
||||
.chan = LTC2378_PSEUDO_DIFF_CHANNEL(18),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2369_18_chip_info = {
|
||||
.name = "ltc2369-18",
|
||||
.chan = LTC2378_PSEUDO_DIFF_CHANNEL(18),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2370_16_chip_info = {
|
||||
.name = "ltc2370-16",
|
||||
.chan = LTC2378_PSEUDO_DIFF_CHANNEL(16),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2376_16_chip_info = {
|
||||
.name = "ltc2376-16",
|
||||
.chan = LTC2378_DIFF_CHANNEL(16),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2376_18_chip_info = {
|
||||
.name = "ltc2376-18",
|
||||
.chan = LTC2378_DIFF_CHANNEL(18),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2376_20_chip_info = {
|
||||
.name = "ltc2376-20",
|
||||
.chan = LTC2378_DIFF_CHANNEL(20),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2377_16_chip_info = {
|
||||
.name = "ltc2377-16",
|
||||
.chan = LTC2378_DIFF_CHANNEL(16),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2377_18_chip_info = {
|
||||
.name = "ltc2377-18",
|
||||
.chan = LTC2378_DIFF_CHANNEL(18),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2377_20_chip_info = {
|
||||
.name = "ltc2377-20",
|
||||
.chan = LTC2378_DIFF_CHANNEL(20),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2378_16_chip_info = {
|
||||
.name = "ltc2378-16",
|
||||
.chan = LTC2378_DIFF_CHANNEL(16),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2378_18_chip_info = {
|
||||
.name = "ltc2378-18",
|
||||
.chan = LTC2378_DIFF_CHANNEL(18),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2378_20_chip_info = {
|
||||
.name = "ltc2378-20",
|
||||
.chan = LTC2378_DIFF_CHANNEL(20),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2379_18_chip_info = {
|
||||
.name = "ltc2379-18",
|
||||
.chan = LTC2378_DIFF_CHANNEL(18),
|
||||
};
|
||||
|
||||
static const struct ltc2378_chip_info ltc2380_16_chip_info = {
|
||||
.name = "ltc2380-16",
|
||||
.chan = LTC2378_DIFF_CHANNEL(16),
|
||||
};
|
||||
|
||||
static int ltc2378_convert_and_acquire(struct ltc2378_state *st)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Cause a rising edge of CNV to initiate a new ADC conversion */
|
||||
gpiod_set_value_cansleep(st->cnv_gpio, 1);
|
||||
fsleep(LTC2378_MAX_DATA_WAIT_US);
|
||||
ret = spi_sync_transfer(st->spi, &st->xfer, 1);
|
||||
gpiod_set_value_cansleep(st->cnv_gpio, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ltc2378_channel_single_read(const struct iio_chan_spec *chan,
|
||||
struct ltc2378_state *st, int *val)
|
||||
{
|
||||
const struct iio_scan_type *scan_type = &chan->scan_type;
|
||||
u32 sample;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&st->lock);
|
||||
ret = ltc2378_convert_and_acquire(st);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (chan->scan_type.endianness == IIO_BE) {
|
||||
if (chan->scan_type.realbits > 16)
|
||||
sample = be32_to_cpu(st->scan.data.sample_buf32_be);
|
||||
else
|
||||
sample = be16_to_cpu(st->scan.data.sample_buf16_be);
|
||||
} else { /* IIO_CPU */
|
||||
if (chan->scan_type.realbits > 16)
|
||||
sample = st->scan.data.sample_buf32;
|
||||
else
|
||||
sample = st->scan.data.sample_buf16;
|
||||
}
|
||||
|
||||
sample >>= chan->scan_type.shift;
|
||||
|
||||
if (scan_type->format == IIO_SCAN_FORMAT_SIGNED_INT)
|
||||
*val = sign_extend32(sample, scan_type->realbits - 1);
|
||||
else
|
||||
*val = sample;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ltc2378_read_raw(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct ltc2378_state *st = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW: {
|
||||
IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
|
||||
if (IIO_DEV_ACQUIRE_FAILED(claim))
|
||||
return -EBUSY;
|
||||
|
||||
ret = ltc2378_channel_single_read(chan, st, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = st->ref_uV / MILLI;
|
||||
/*
|
||||
* For all LTC2378-like devices, the amount of bits that express
|
||||
* voltage magnitude depend on the polarity / output code format:
|
||||
* - straight binary: All precision/resolution bits are used.
|
||||
* - 2's complement: One of the precision bits is used for sign.
|
||||
*/
|
||||
if (chan->scan_type.format == IIO_SCAN_FORMAT_SIGNED_INT)
|
||||
*val2 = chan->scan_type.realbits - 1;
|
||||
else
|
||||
*val2 = chan->scan_type.realbits;
|
||||
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct iio_info ltc2378_iio_info = {
|
||||
.read_raw = <c2378_read_raw,
|
||||
};
|
||||
|
||||
static int ltc2378_ref_setup(struct device *dev, struct ltc2378_state *st)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = devm_regulator_get_enable_read_voltage(dev, "ref");
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "failed to read ref regulator\n");
|
||||
|
||||
st->ref_uV = ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ltc2378_probe(struct spi_device *spi)
|
||||
{
|
||||
struct device *dev = &spi->dev;
|
||||
struct iio_dev *indio_dev;
|
||||
struct ltc2378_state *st;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
st = iio_priv(indio_dev);
|
||||
st->spi = spi;
|
||||
|
||||
ret = devm_mutex_init(dev, &st->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
st->info = spi_get_device_match_data(spi);
|
||||
if (!st->info)
|
||||
return -EINVAL;
|
||||
|
||||
ret = ltc2378_ref_setup(dev, st);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
indio_dev->name = st->info->name;
|
||||
indio_dev->info = <c2378_iio_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
st->cnv_gpio = devm_gpiod_get(dev, "cnv", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(st->cnv_gpio))
|
||||
return dev_err_probe(dev, PTR_ERR(st->cnv_gpio),
|
||||
"failed to get CNV GPIO");
|
||||
|
||||
indio_dev->channels = &st->info->chan;
|
||||
indio_dev->num_channels = 1;
|
||||
|
||||
st->xfer.rx_buf = &st->scan.data;
|
||||
st->xfer.len = spi_bpw_to_bytes(indio_dev->channels[0].scan_type.realbits);
|
||||
|
||||
return devm_iio_device_register(&spi->dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id ltc2378_of_match[] = {
|
||||
{ .compatible = "adi,ltc2364-16", .data = <c2364_16_chip_info },
|
||||
{ .compatible = "adi,ltc2364-18", .data = <c2364_18_chip_info },
|
||||
{ .compatible = "adi,ltc2367-16", .data = <c2367_16_chip_info },
|
||||
{ .compatible = "adi,ltc2367-18", .data = <c2367_18_chip_info },
|
||||
{ .compatible = "adi,ltc2368-16", .data = <c2368_16_chip_info },
|
||||
{ .compatible = "adi,ltc2368-18", .data = <c2368_18_chip_info },
|
||||
{ .compatible = "adi,ltc2369-18", .data = <c2369_18_chip_info },
|
||||
{ .compatible = "adi,ltc2370-16", .data = <c2370_16_chip_info },
|
||||
{ .compatible = "adi,ltc2376-16", .data = <c2376_16_chip_info },
|
||||
{ .compatible = "adi,ltc2376-18", .data = <c2376_18_chip_info },
|
||||
{ .compatible = "adi,ltc2376-20", .data = <c2376_20_chip_info },
|
||||
{ .compatible = "adi,ltc2377-16", .data = <c2377_16_chip_info },
|
||||
{ .compatible = "adi,ltc2377-18", .data = <c2377_18_chip_info },
|
||||
{ .compatible = "adi,ltc2377-20", .data = <c2377_20_chip_info },
|
||||
{ .compatible = "adi,ltc2378-16", .data = <c2378_16_chip_info },
|
||||
{ .compatible = "adi,ltc2378-18", .data = <c2378_18_chip_info },
|
||||
{ .compatible = "adi,ltc2378-20", .data = <c2378_20_chip_info },
|
||||
{ .compatible = "adi,ltc2379-18", .data = <c2379_18_chip_info },
|
||||
{ .compatible = "adi,ltc2380-16", .data = <c2380_16_chip_info },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ltc2378_of_match);
|
||||
|
||||
static const struct spi_device_id ltc2378_spi_id[] = {
|
||||
{ .name = "ltc2364-16", .driver_data = (kernel_ulong_t)<c2364_16_chip_info },
|
||||
{ .name = "ltc2364-18", .driver_data = (kernel_ulong_t)<c2364_18_chip_info },
|
||||
{ .name = "ltc2367-16", .driver_data = (kernel_ulong_t)<c2367_16_chip_info },
|
||||
{ .name = "ltc2367-18", .driver_data = (kernel_ulong_t)<c2367_18_chip_info },
|
||||
{ .name = "ltc2368-16", .driver_data = (kernel_ulong_t)<c2368_16_chip_info },
|
||||
{ .name = "ltc2368-18", .driver_data = (kernel_ulong_t)<c2368_18_chip_info },
|
||||
{ .name = "ltc2369-18", .driver_data = (kernel_ulong_t)<c2369_18_chip_info },
|
||||
{ .name = "ltc2370-16", .driver_data = (kernel_ulong_t)<c2370_16_chip_info },
|
||||
{ .name = "ltc2376-16", .driver_data = (kernel_ulong_t)<c2376_16_chip_info },
|
||||
{ .name = "ltc2376-18", .driver_data = (kernel_ulong_t)<c2376_18_chip_info },
|
||||
{ .name = "ltc2376-20", .driver_data = (kernel_ulong_t)<c2376_20_chip_info },
|
||||
{ .name = "ltc2377-16", .driver_data = (kernel_ulong_t)<c2377_16_chip_info },
|
||||
{ .name = "ltc2377-18", .driver_data = (kernel_ulong_t)<c2377_18_chip_info },
|
||||
{ .name = "ltc2377-20", .driver_data = (kernel_ulong_t)<c2377_20_chip_info },
|
||||
{ .name = "ltc2378-16", .driver_data = (kernel_ulong_t)<c2378_16_chip_info },
|
||||
{ .name = "ltc2378-18", .driver_data = (kernel_ulong_t)<c2378_18_chip_info },
|
||||
{ .name = "ltc2378-20", .driver_data = (kernel_ulong_t)<c2378_20_chip_info },
|
||||
{ .name = "ltc2379-18", .driver_data = (kernel_ulong_t)<c2379_18_chip_info },
|
||||
{ .name = "ltc2380-16", .driver_data = (kernel_ulong_t)<c2380_16_chip_info },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, ltc2378_spi_id);
|
||||
|
||||
static struct spi_driver ltc2378_driver = {
|
||||
.driver = {
|
||||
.name = "ltc2378",
|
||||
.of_match_table = ltc2378_of_match
|
||||
},
|
||||
.probe = ltc2378_probe,
|
||||
.id_table = ltc2378_spi_id,
|
||||
};
|
||||
module_spi_driver(ltc2378_driver);
|
||||
|
||||
MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>");
|
||||
MODULE_DESCRIPTION("Analog Devices LTC2378 ADC series driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
Loading…
Reference in New Issue
Block a user