summaryrefslogtreecommitdiffstats
path: root/drivers/iio/amplifiers/ad8366.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iio/amplifiers/ad8366.c')
-rw-r--r--drivers/iio/amplifiers/ad8366.c335
1 files changed, 335 insertions, 0 deletions
diff --git a/drivers/iio/amplifiers/ad8366.c b/drivers/iio/amplifiers/ad8366.c
new file mode 100644
index 000000000..f2c2ea79a
--- /dev/null
+++ b/drivers/iio/amplifiers/ad8366.c
@@ -0,0 +1,335 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AD8366 and similar Gain Amplifiers
+ * This driver supports the following gain amplifiers:
+ * AD8366 Dual-Digital Variable Gain Amplifier (VGA)
+ * ADA4961 BiCMOS RF Digital Gain Amplifier (DGA)
+ * ADL5240 Digitally controlled variable gain amplifier (VGA)
+ * HMC1119 0.25 dB LSB, 7-Bit, Silicon Digital Attenuator
+ *
+ * Copyright 2012-2019 Analog Devices Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio/consumer.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/bitrev.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+enum ad8366_type {
+ ID_AD8366,
+ ID_ADA4961,
+ ID_ADL5240,
+ ID_HMC1119,
+};
+
+struct ad8366_info {
+ int gain_min;
+ int gain_max;
+};
+
+struct ad8366_state {
+ struct spi_device *spi;
+ struct regulator *reg;
+ struct mutex lock; /* protect sensor state */
+ struct gpio_desc *reset_gpio;
+ unsigned char ch[2];
+ enum ad8366_type type;
+ struct ad8366_info *info;
+ /*
+ * DMA (thus cache coherency maintenance) may require the
+ * transfer buffers to live in their own cache lines.
+ */
+ unsigned char data[2] __aligned(IIO_DMA_MINALIGN);
+};
+
+static struct ad8366_info ad8366_infos[] = {
+ [ID_AD8366] = {
+ .gain_min = 4500,
+ .gain_max = 20500,
+ },
+ [ID_ADA4961] = {
+ .gain_min = -6000,
+ .gain_max = 15000,
+ },
+ [ID_ADL5240] = {
+ .gain_min = -11500,
+ .gain_max = 20000,
+ },
+ [ID_HMC1119] = {
+ .gain_min = -31750,
+ .gain_max = 0,
+ },
+};
+
+static int ad8366_write(struct iio_dev *indio_dev,
+ unsigned char ch_a, unsigned char ch_b)
+{
+ struct ad8366_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (st->type) {
+ case ID_AD8366:
+ ch_a = bitrev8(ch_a & 0x3F);
+ ch_b = bitrev8(ch_b & 0x3F);
+
+ st->data[0] = ch_b >> 4;
+ st->data[1] = (ch_b << 4) | (ch_a >> 2);
+ break;
+ case ID_ADA4961:
+ st->data[0] = ch_a & 0x1F;
+ break;
+ case ID_ADL5240:
+ st->data[0] = (ch_a & 0x3F);
+ break;
+ case ID_HMC1119:
+ st->data[0] = ch_a;
+ break;
+ }
+
+ ret = spi_write(st->spi, st->data, indio_dev->num_channels);
+ if (ret < 0)
+ dev_err(&indio_dev->dev, "write failed (%d)", ret);
+
+ return ret;
+}
+
+static int ad8366_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long m)
+{
+ struct ad8366_state *st = iio_priv(indio_dev);
+ int ret;
+ int code, gain = 0;
+
+ mutex_lock(&st->lock);
+ switch (m) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ code = st->ch[chan->channel];
+
+ switch (st->type) {
+ case ID_AD8366:
+ gain = code * 253 + 4500;
+ break;
+ case ID_ADA4961:
+ gain = 15000 - code * 1000;
+ break;
+ case ID_ADL5240:
+ gain = 20000 - 31500 + code * 500;
+ break;
+ case ID_HMC1119:
+ gain = -1 * code * 250;
+ break;
+ }
+
+ /* Values in dB */
+ *val = gain / 1000;
+ *val2 = (gain % 1000) * 1000;
+
+ ret = IIO_VAL_INT_PLUS_MICRO_DB;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ mutex_unlock(&st->lock);
+
+ return ret;
+};
+
+static int ad8366_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ struct ad8366_state *st = iio_priv(indio_dev);
+ struct ad8366_info *inf = st->info;
+ int code = 0, gain;
+ int ret;
+
+ /* Values in dB */
+ if (val < 0)
+ gain = (val * 1000) - (val2 / 1000);
+ else
+ gain = (val * 1000) + (val2 / 1000);
+
+ if (gain > inf->gain_max || gain < inf->gain_min)
+ return -EINVAL;
+
+ switch (st->type) {
+ case ID_AD8366:
+ code = (gain - 4500) / 253;
+ break;
+ case ID_ADA4961:
+ code = (15000 - gain) / 1000;
+ break;
+ case ID_ADL5240:
+ code = ((gain - 500 - 20000) / 500) & 0x3F;
+ break;
+ case ID_HMC1119:
+ code = (abs(gain) / 250) & 0x7F;
+ break;
+ }
+
+ mutex_lock(&st->lock);
+ switch (mask) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ st->ch[chan->channel] = code;
+ ret = ad8366_write(indio_dev, st->ch[0], st->ch[1]);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static int ad8366_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ return IIO_VAL_INT_PLUS_MICRO_DB;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info ad8366_info = {
+ .read_raw = &ad8366_read_raw,
+ .write_raw = &ad8366_write_raw,
+ .write_raw_get_fmt = &ad8366_write_raw_get_fmt,
+};
+
+#define AD8366_CHAN(_channel) { \
+ .type = IIO_VOLTAGE, \
+ .output = 1, \
+ .indexed = 1, \
+ .channel = _channel, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_HARDWAREGAIN),\
+}
+
+static const struct iio_chan_spec ad8366_channels[] = {
+ AD8366_CHAN(0),
+ AD8366_CHAN(1),
+};
+
+static const struct iio_chan_spec ada4961_channels[] = {
+ AD8366_CHAN(0),
+};
+
+static int ad8366_probe(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev;
+ struct ad8366_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (indio_dev == NULL)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ st->reg = devm_regulator_get(&spi->dev, "vcc");
+ if (!IS_ERR(st->reg)) {
+ ret = regulator_enable(st->reg);
+ if (ret)
+ return ret;
+ }
+
+ spi_set_drvdata(spi, indio_dev);
+ mutex_init(&st->lock);
+ st->spi = spi;
+ st->type = spi_get_device_id(spi)->driver_data;
+
+ switch (st->type) {
+ case ID_AD8366:
+ indio_dev->channels = ad8366_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ad8366_channels);
+ break;
+ case ID_ADA4961:
+ case ID_ADL5240:
+ case ID_HMC1119:
+ st->reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(st->reset_gpio)) {
+ ret = PTR_ERR(st->reset_gpio);
+ goto error_disable_reg;
+ }
+ indio_dev->channels = ada4961_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ada4961_channels);
+ break;
+ default:
+ dev_err(&spi->dev, "Invalid device ID\n");
+ ret = -EINVAL;
+ goto error_disable_reg;
+ }
+
+ st->info = &ad8366_infos[st->type];
+ indio_dev->name = spi_get_device_id(spi)->name;
+ indio_dev->info = &ad8366_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = ad8366_write(indio_dev, 0 , 0);
+ if (ret < 0)
+ goto error_disable_reg;
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto error_disable_reg;
+
+ return 0;
+
+error_disable_reg:
+ if (!IS_ERR(st->reg))
+ regulator_disable(st->reg);
+
+ return ret;
+}
+
+static void ad8366_remove(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+ struct ad8366_state *st = iio_priv(indio_dev);
+ struct regulator *reg = st->reg;
+
+ iio_device_unregister(indio_dev);
+
+ if (!IS_ERR(reg))
+ regulator_disable(reg);
+}
+
+static const struct spi_device_id ad8366_id[] = {
+ {"ad8366", ID_AD8366},
+ {"ada4961", ID_ADA4961},
+ {"adl5240", ID_ADL5240},
+ {"hmc1119", ID_HMC1119},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, ad8366_id);
+
+static struct spi_driver ad8366_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+ .probe = ad8366_probe,
+ .remove = ad8366_remove,
+ .id_table = ad8366_id,
+};
+
+module_spi_driver(ad8366_driver);
+
+MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD8366 and similar Gain Amplifiers");
+MODULE_LICENSE("GPL v2");