09e3bdfe49
This is a frequent minor comment in reviews, so start cleaning up existing drivers in the hope we get fewer cases of cut and paste. There are not kernel wide rules for these, but for IIO the style that I prefer (and hence most common) is: - Space after { and before } - No comma after terminator { } This may cause merge conflicts but they should be trivial to resolve hence I have not broken this into per driver patches. Link: https://patch.msgid.link/20240818180912.719399-1-jic23@kernel.org Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
265 lines
6.6 KiB
C
265 lines
6.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* TI tlc4541 ADC Driver
|
|
*
|
|
* Copyright (C) 2017 Phil Reid
|
|
*
|
|
* Datasheets can be found here:
|
|
* https://www.ti.com/lit/gpn/tlc3541
|
|
* https://www.ti.com/lit/gpn/tlc4541
|
|
*
|
|
* 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/mod_devicetable.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) may require 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] __aligned(IIO_DMA_MINALIGN);
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
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->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.value = 3;
|
|
st->scan_single_xfer[1].delay.unit = SPI_DELAY_UNIT_NSECS;
|
|
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 void 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);
|
|
}
|
|
|
|
static const struct of_device_id tlc4541_dt_ids[] = {
|
|
{ .compatible = "ti,tlc3541", },
|
|
{ .compatible = "ti,tlc4541", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, tlc4541_dt_ids);
|
|
|
|
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 = 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");
|