forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
272 lines
7.1 KiB
272 lines
7.1 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/** |
|
* Copyright (C) 2017 Axis Communications AB |
|
* |
|
* Driver for Texas Instruments' ADC084S021 ADC chip. |
|
* Datasheets can be found here: |
|
* https://www.ti.com/lit/ds/symlink/adc084s021.pdf |
|
*/ |
|
|
|
#include <linux/err.h> |
|
#include <linux/spi/spi.h> |
|
#include <linux/module.h> |
|
#include <linux/mod_devicetable.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/iio/iio.h> |
|
#include <linux/iio/buffer.h> |
|
#include <linux/iio/triggered_buffer.h> |
|
#include <linux/iio/trigger_consumer.h> |
|
#include <linux/regulator/consumer.h> |
|
|
|
#define ADC084S021_DRIVER_NAME "adc084s021" |
|
|
|
struct adc084s021 { |
|
struct spi_device *spi; |
|
struct spi_message message; |
|
struct spi_transfer spi_trans; |
|
struct regulator *reg; |
|
struct mutex lock; |
|
/* Buffer used to align data */ |
|
struct { |
|
__be16 channels[4]; |
|
s64 ts __aligned(8); |
|
} scan; |
|
/* |
|
* DMA (thus cache coherency maintenance) requires the |
|
* transfer buffers to live in their own cache line. |
|
*/ |
|
u16 tx_buf[4] ____cacheline_aligned; |
|
__be16 rx_buf[5]; /* First 16-bits are trash */ |
|
}; |
|
|
|
#define ADC084S021_VOLTAGE_CHANNEL(num) \ |
|
{ \ |
|
.type = IIO_VOLTAGE, \ |
|
.channel = (num), \ |
|
.indexed = 1, \ |
|
.scan_index = (num), \ |
|
.scan_type = { \ |
|
.sign = 'u', \ |
|
.realbits = 8, \ |
|
.storagebits = 16, \ |
|
.shift = 4, \ |
|
.endianness = IIO_BE, \ |
|
}, \ |
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
|
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\ |
|
} |
|
|
|
static const struct iio_chan_spec adc084s021_channels[] = { |
|
ADC084S021_VOLTAGE_CHANNEL(0), |
|
ADC084S021_VOLTAGE_CHANNEL(1), |
|
ADC084S021_VOLTAGE_CHANNEL(2), |
|
ADC084S021_VOLTAGE_CHANNEL(3), |
|
IIO_CHAN_SOFT_TIMESTAMP(4), |
|
}; |
|
|
|
/** |
|
* Read an ADC channel and return its value. |
|
* |
|
* @adc: The ADC SPI data. |
|
* @data: Buffer for converted data. |
|
*/ |
|
static int adc084s021_adc_conversion(struct adc084s021 *adc, __be16 *data) |
|
{ |
|
int n_words = (adc->spi_trans.len >> 1) - 1; /* Discard first word */ |
|
int ret, i = 0; |
|
|
|
/* Do the transfer */ |
|
ret = spi_sync(adc->spi, &adc->message); |
|
if (ret < 0) |
|
return ret; |
|
|
|
for (; i < n_words; i++) |
|
*(data + i) = adc->rx_buf[i + 1]; |
|
|
|
return ret; |
|
} |
|
|
|
static int adc084s021_read_raw(struct iio_dev *indio_dev, |
|
struct iio_chan_spec const *channel, int *val, |
|
int *val2, long mask) |
|
{ |
|
struct adc084s021 *adc = iio_priv(indio_dev); |
|
int ret; |
|
__be16 be_val; |
|
|
|
switch (mask) { |
|
case IIO_CHAN_INFO_RAW: |
|
ret = iio_device_claim_direct_mode(indio_dev); |
|
if (ret < 0) |
|
return ret; |
|
|
|
ret = regulator_enable(adc->reg); |
|
if (ret) { |
|
iio_device_release_direct_mode(indio_dev); |
|
return ret; |
|
} |
|
|
|
adc->tx_buf[0] = channel->channel << 3; |
|
ret = adc084s021_adc_conversion(adc, &be_val); |
|
iio_device_release_direct_mode(indio_dev); |
|
regulator_disable(adc->reg); |
|
if (ret < 0) |
|
return ret; |
|
|
|
*val = be16_to_cpu(be_val); |
|
*val = (*val >> channel->scan_type.shift) & 0xff; |
|
|
|
return IIO_VAL_INT; |
|
case IIO_CHAN_INFO_SCALE: |
|
ret = regulator_enable(adc->reg); |
|
if (ret) |
|
return ret; |
|
|
|
ret = regulator_get_voltage(adc->reg); |
|
regulator_disable(adc->reg); |
|
if (ret < 0) |
|
return ret; |
|
|
|
*val = ret / 1000; |
|
|
|
return IIO_VAL_INT; |
|
default: |
|
return -EINVAL; |
|
} |
|
} |
|
|
|
/** |
|
* Read enabled ADC channels and push data to the buffer. |
|
* |
|
* @irq: The interrupt number (not used). |
|
* @pollfunc: Pointer to the poll func. |
|
*/ |
|
static irqreturn_t adc084s021_buffer_trigger_handler(int irq, void *pollfunc) |
|
{ |
|
struct iio_poll_func *pf = pollfunc; |
|
struct iio_dev *indio_dev = pf->indio_dev; |
|
struct adc084s021 *adc = iio_priv(indio_dev); |
|
|
|
mutex_lock(&adc->lock); |
|
|
|
if (adc084s021_adc_conversion(adc, adc->scan.channels) < 0) |
|
dev_err(&adc->spi->dev, "Failed to read data\n"); |
|
|
|
iio_push_to_buffers_with_timestamp(indio_dev, &adc->scan, |
|
iio_get_time_ns(indio_dev)); |
|
mutex_unlock(&adc->lock); |
|
iio_trigger_notify_done(indio_dev->trig); |
|
|
|
return IRQ_HANDLED; |
|
} |
|
|
|
static int adc084s021_buffer_preenable(struct iio_dev *indio_dev) |
|
{ |
|
struct adc084s021 *adc = iio_priv(indio_dev); |
|
int scan_index; |
|
int i = 0; |
|
|
|
for_each_set_bit(scan_index, indio_dev->active_scan_mask, |
|
indio_dev->masklength) { |
|
const struct iio_chan_spec *channel = |
|
&indio_dev->channels[scan_index]; |
|
adc->tx_buf[i++] = channel->channel << 3; |
|
} |
|
adc->spi_trans.len = 2 + (i * sizeof(__be16)); /* Trash + channels */ |
|
|
|
return regulator_enable(adc->reg); |
|
} |
|
|
|
static int adc084s021_buffer_postdisable(struct iio_dev *indio_dev) |
|
{ |
|
struct adc084s021 *adc = iio_priv(indio_dev); |
|
|
|
adc->spi_trans.len = 4; /* Trash + single channel */ |
|
|
|
return regulator_disable(adc->reg); |
|
} |
|
|
|
static const struct iio_info adc084s021_info = { |
|
.read_raw = adc084s021_read_raw, |
|
}; |
|
|
|
static const struct iio_buffer_setup_ops adc084s021_buffer_setup_ops = { |
|
.preenable = adc084s021_buffer_preenable, |
|
.postdisable = adc084s021_buffer_postdisable, |
|
}; |
|
|
|
static int adc084s021_probe(struct spi_device *spi) |
|
{ |
|
struct iio_dev *indio_dev; |
|
struct adc084s021 *adc; |
|
int ret; |
|
|
|
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); |
|
if (!indio_dev) { |
|
dev_err(&spi->dev, "Failed to allocate IIO device\n"); |
|
return -ENOMEM; |
|
} |
|
|
|
adc = iio_priv(indio_dev); |
|
adc->spi = spi; |
|
|
|
/* Connect the SPI device and the iio dev */ |
|
spi_set_drvdata(spi, indio_dev); |
|
|
|
/* Initiate the Industrial I/O device */ |
|
indio_dev->name = spi_get_device_id(spi)->name; |
|
indio_dev->modes = INDIO_DIRECT_MODE; |
|
indio_dev->info = &adc084s021_info; |
|
indio_dev->channels = adc084s021_channels; |
|
indio_dev->num_channels = ARRAY_SIZE(adc084s021_channels); |
|
|
|
/* Create SPI transfer for channel reads */ |
|
adc->spi_trans.tx_buf = adc->tx_buf; |
|
adc->spi_trans.rx_buf = adc->rx_buf; |
|
adc->spi_trans.len = 4; /* Trash + single channel */ |
|
spi_message_init_with_transfers(&adc->message, &adc->spi_trans, 1); |
|
|
|
adc->reg = devm_regulator_get(&spi->dev, "vref"); |
|
if (IS_ERR(adc->reg)) |
|
return PTR_ERR(adc->reg); |
|
|
|
mutex_init(&adc->lock); |
|
|
|
/* Setup triggered buffer with pollfunction */ |
|
ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL, |
|
adc084s021_buffer_trigger_handler, |
|
&adc084s021_buffer_setup_ops); |
|
if (ret) { |
|
dev_err(&spi->dev, "Failed to setup triggered buffer\n"); |
|
return ret; |
|
} |
|
|
|
return devm_iio_device_register(&spi->dev, indio_dev); |
|
} |
|
|
|
static const struct of_device_id adc084s021_of_match[] = { |
|
{ .compatible = "ti,adc084s021", }, |
|
{}, |
|
}; |
|
MODULE_DEVICE_TABLE(of, adc084s021_of_match); |
|
|
|
static const struct spi_device_id adc084s021_id[] = { |
|
{ ADC084S021_DRIVER_NAME, 0}, |
|
{} |
|
}; |
|
MODULE_DEVICE_TABLE(spi, adc084s021_id); |
|
|
|
static struct spi_driver adc084s021_driver = { |
|
.driver = { |
|
.name = ADC084S021_DRIVER_NAME, |
|
.of_match_table = adc084s021_of_match, |
|
}, |
|
.probe = adc084s021_probe, |
|
.id_table = adc084s021_id, |
|
}; |
|
module_spi_driver(adc084s021_driver); |
|
|
|
MODULE_AUTHOR("Mårten Lindahl <[email protected]>"); |
|
MODULE_DESCRIPTION("Texas Instruments ADC084S021"); |
|
MODULE_LICENSE("GPL v2"); |
|
MODULE_VERSION("1.0");
|
|
|