summaryrefslogtreecommitdiffstats
path: root/drivers/iio/amplifiers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-02 12:17:24 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-02 12:17:24 -0700
commit3f1fe48a36b0b6722dc3fd421d93512bac138e9a (patch)
treeb767d7f6e26bc64334d3f14f8422d187239dc45d /drivers/iio/amplifiers
downloadlinux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.tar.gz
linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.zip
Merge tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linuxgrafted
Pull io_uring fixes from Jens Axboe: - Fix a task_work add use-after-free with SQPOLL. The sqpoll thread could pop and complete the last request while io_req_normal_work_add() was still looking at them after the mpscq push. Use the same approach as DEFER_TASKRUN to protect from that, holding an RCU read lock across the add, and have exit wait for an RCU grace period for SQPOLL rings as well. - CQE32 ring fixes: correct the free entry check for 32b CQEs, zero the big_cqe for aux CQEs, and only post the dummy skip CQE on CQE_MIXED rings - Mark the source filter table as COW when cloning bpf filters, so registering another filter on the source doesn't modify the shared table in place - Initialize the task context before running the BPF loop - Requeue zcrx multishot receives stopped by a local resource - End a TX_TIMESTAMP multishot cmd when the CQ is full (lollipopkit) * tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux: io_uring: fix task_work add use-after-free with SQPOLL io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full io_uring/zcrx: requeue multishot receives stopped by a local resource io_uring: initialize task context before running the BPF loop io_uring: zero big_cqe for aux CQEs on CQE32 rings io_uring: fix free entry check for 32b CQEs on CQE32 rings io_uring: only post the dummy skip CQE on CQE_MIXED rings io_uring/bpf_filter: mark source as COW when cloning filters
Diffstat (limited to 'drivers/iio/amplifiers')
-rw-r--r--drivers/iio/amplifiers/Kconfig69
-rw-r--r--drivers/iio/amplifiers/Makefile10
-rw-r--r--drivers/iio/amplifiers/ad8366.c402
-rw-r--r--drivers/iio/amplifiers/ada4250.c384
-rw-r--r--drivers/iio/amplifiers/adl8113.c268
-rw-r--r--drivers/iio/amplifiers/hmc425a.c424
6 files changed, 1557 insertions, 0 deletions
diff --git a/drivers/iio/amplifiers/Kconfig b/drivers/iio/amplifiers/Kconfig
new file mode 100644
index 000000000..9e24421b5
--- /dev/null
+++ b/drivers/iio/amplifiers/Kconfig
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Gain Amplifiers, etc.
+#
+# When adding new entries keep the list in alphabetical order
+
+menu "Amplifiers"
+
+config AD8366
+ tristate "Analog Devices AD8366 and similar Gain Amplifiers"
+ depends on SPI
+ depends on GPIOLIB
+ select BITREVERSE
+ help
+ Say yes here to build support for Analog Devices AD8366 and similar
+ gain amplifiers. This driver supports the following gain amplifiers
+ from Analog Devices:
+ AD8366 Dual-Digital Variable Gain Amplifier (VGA)
+ ADA4961 BiCMOS RF Digital Gain Amplifier (DGA)
+ ADL5240 Digitally controlled variable gain amplifier (VGA)
+ ADRF5702: 0.125 dB LSB, 8-Bit, Silicon Digital Attenuator
+ ADRF5703: 0.25 dB LSB, 7-Bit, Silicon Digital Attenuator
+ ADRF5720: 0.5 dB LSB, 6-Bit, Silicon Digital Attenuator
+ ADRF5730: 0.5 dB LSB, 6-Bit, Silicon Digital Attenuator
+ ADRF5731: 2 dB LSB, 4-Bit, Silicon Digital Attenuator
+ HMC271A: 1dB LSB 5-Bit Digital Attenuator SMT
+ HMC792A 0.25 dB LSB GaAs MMIC 6-Bit Digital Attenuator
+ HMC1018A: 1.0 dB LSB GaAs MMIC 5-BIT Digital Attenuator
+ HMC1019A: 0.5 dB LSB GaAs MMIC 5-BIT Digital Attenuator
+ HMC1119 0.25 dB LSB, 7-Bit, Silicon Digital Attenuator
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad8366.
+
+config ADA4250
+ tristate "Analog Devices ADA4250 Instrumentation Amplifier"
+ depends on SPI
+ select REGMAP_SPI
+ help
+ Say yes here to build support for Analog Devices ADA4250
+ SPI Amplifier's support. The driver provides direct access via
+ sysfs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ada4250.
+
+config ADL8113
+ tristate "Analog Devices ADL8113 Low Noise Amplifier"
+ depends on GPIOLIB
+ help
+ Say yes here to build support for Analog Devices ADL8113 Low Noise
+ Amplifier with integrated bypass switches. The device supports four
+ operation modes controlled by GPIO pins: internal amplifier,
+ internal bypass, and two external bypass modes.
+
+ To compile this driver as a module, choose M here: the
+ module will be called adl8113.
+
+config HMC425
+ tristate "Analog Devices HMC425A and similar GPIO Gain Amplifiers"
+ depends on GPIOLIB
+ help
+ Say yes here to build support for Analog Devices HMC425A and similar
+ gain amplifiers or step attenuators.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hmc425a.
+
+endmenu
diff --git a/drivers/iio/amplifiers/Makefile b/drivers/iio/amplifiers/Makefile
new file mode 100644
index 000000000..0a76443be
--- /dev/null
+++ b/drivers/iio/amplifiers/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile iio/amplifiers
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_AD8366) += ad8366.o
+obj-$(CONFIG_ADA4250) += ada4250.o
+obj-$(CONFIG_ADL8113) += adl8113.o
+obj-$(CONFIG_HMC425) += hmc425a.o
diff --git a/drivers/iio/amplifiers/ad8366.c b/drivers/iio/amplifiers/ad8366.c
new file mode 100644
index 000000000..e12f8604f
--- /dev/null
+++ b/drivers/iio/amplifiers/ad8366.c
@@ -0,0 +1,402 @@
+// 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)
+ * ADRF5702: 0.125 dB LSB, 8-Bit, Silicon Digital Attenuator, 50 MHz to 20 GHz
+ * ADRF5703: 0.25 dB LSB, 7-Bit, Silicon Digital Attenuator, 9 kHz to 20 GHz
+ * ADRF5720: 0.5 dB LSB, 6-Bit, Silicon Digital Attenuator, 9 kHz to 40 GHz
+ * ADRF5730: 0.5 dB LSB, 6-Bit, Silicon Digital Attenuator, 100 MHz to 40 GHz
+ * ADRF5731: 2 dB LSB, 4-Bit, Silicon Digital Attenuator, 100 MHz to 40 GHz
+ * HMC271A: 1dB LSB 5-Bit Digital Attenuator SMT, 0.7 - 3.7 GHz
+ * HMC792A 0.25 dB LSB GaAs MMIC 6-Bit Digital Attenuator
+ * HMC1018A: 1.0 dB LSB GaAs MMIC 5-BIT DIGITAL ATTENUATOR, 0.1 - 30 GHz
+ * HMC1019A: 0.5 dB LSB GaAs MMIC 5-BIT DIGITAL ATTENUATOR, 0.1 - 30 GHz
+ * HMC1119 0.25 dB LSB, 7-Bit, Silicon Digital Attenuator
+ *
+ * Copyright 2012-2026 Analog Devices Inc.
+ */
+
+#include <linux/bitrev.h>
+#include <linux/bits.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#include <linux/iio/iio.h>
+
+struct ad8366_info {
+ const char *name;
+ int gain_min;
+ int gain_max;
+ int gain_step;
+ size_t num_channels;
+ size_t (*pack_code)(const unsigned char *code, size_t num_channels,
+ unsigned char *data);
+};
+
+struct ad8366_state {
+ struct spi_device *spi;
+ struct mutex lock; /* protect sensor state */
+ unsigned char ch[2];
+ const 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 size_t ad8366_pack_code(const unsigned char *code, size_t num_channels,
+ unsigned char *data)
+{
+ u8 ch_a = bitrev8(code[0]) >> 2;
+ u8 ch_b = bitrev8(code[1]) >> 2;
+
+ put_unaligned_be16((ch_b << 6) | ch_a, &data[0]);
+ return sizeof(__be16);
+}
+
+static size_t adrf5731_pack_code(const unsigned char *code, size_t num_channels,
+ unsigned char *data)
+{
+ data[0] = code[0] << 2;
+ return 1;
+}
+
+static size_t hmc271_pack_code(const unsigned char *code, size_t num_channels,
+ unsigned char *data)
+{
+ data[0] = bitrev8(code[0]) >> 3;
+ return 1;
+}
+
+static const struct ad8366_info ad8366_chip_info = {
+ .name = "ad8366",
+ .gain_min = 4500,
+ .gain_max = 20500,
+ .gain_step = 253,
+ .num_channels = 2,
+ .pack_code = ad8366_pack_code,
+};
+
+static const struct ad8366_info ada4961_chip_info = {
+ .name = "ada4961",
+ .gain_min = -6000,
+ .gain_max = 15000,
+ .gain_step = -1000,
+ .num_channels = 1,
+};
+
+static const struct ad8366_info adl5240_chip_info = {
+ .name = "adl5240",
+ .gain_min = -11500,
+ .gain_max = 20000,
+ .gain_step = 500,
+ .num_channels = 1,
+};
+
+static const struct ad8366_info adrf5702_chip_info = {
+ .name = "adrf5702",
+ .gain_min = -31875,
+ .gain_max = 0,
+ .gain_step = -125,
+ .num_channels = 1,
+};
+
+static const struct ad8366_info adrf5703_chip_info = {
+ .name = "adrf5703",
+ .gain_min = -31750,
+ .gain_max = 0,
+ .gain_step = -250,
+ .num_channels = 1,
+};
+
+static const struct ad8366_info adrf5720_chip_info = {
+ .name = "adrf5720",
+ .gain_min = -31500,
+ .gain_max = 0,
+ .gain_step = -500,
+ .num_channels = 1,
+};
+
+static const struct ad8366_info adrf5730_chip_info = {
+ .name = "adrf5730",
+ .gain_min = -31500,
+ .gain_max = 0,
+ .gain_step = -500,
+ .num_channels = 1,
+};
+
+static const struct ad8366_info adrf5731_chip_info = {
+ .name = "adrf5731",
+ .gain_min = -30000,
+ .gain_max = 0,
+ .gain_step = -2000,
+ .num_channels = 1,
+ .pack_code = adrf5731_pack_code,
+};
+
+static const struct ad8366_info hmc271_chip_info = {
+ .name = "hmc271a",
+ .gain_min = -31000,
+ .gain_max = 0,
+ .gain_step = 1000,
+ .num_channels = 1,
+ .pack_code = hmc271_pack_code,
+};
+
+static const struct ad8366_info hmc792_chip_info = {
+ .name = "hmc792a",
+ .gain_min = -15750,
+ .gain_max = 0,
+ .gain_step = 250,
+ .num_channels = 1,
+};
+
+static const struct ad8366_info hmc1018_chip_info = {
+ .name = "hmc1018a",
+ .gain_min = -31000,
+ .gain_max = 0,
+ .gain_step = 1000,
+ .num_channels = 1,
+};
+
+static const struct ad8366_info hmc1019_chip_info = {
+ .name = "hmc1019a",
+ .gain_min = -15500,
+ .gain_max = 0,
+ .gain_step = 500,
+ .num_channels = 1,
+};
+
+static const struct ad8366_info hmc1119_chip_info = {
+ .name = "hmc1119",
+ .gain_min = -31750,
+ .gain_max = 0,
+ .gain_step = -250,
+ .num_channels = 1,
+};
+
+static int ad8366_write_code(struct ad8366_state *st)
+{
+ const struct ad8366_info *inf = st->info;
+ size_t len = 1;
+
+ if (inf->pack_code)
+ len = inf->pack_code(st->ch, inf->num_channels, st->data);
+ else
+ st->data[0] = st->ch[0];
+
+ return spi_write(st->spi, st->data, len);
+}
+
+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);
+ const struct ad8366_info *inf = st->info;
+ int ret;
+ int code, gain = 0;
+
+ mutex_lock(&st->lock);
+ switch (m) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ code = st->ch[chan->channel];
+ gain = inf->gain_step > 0 ? inf->gain_min : inf->gain_max;
+ gain += inf->gain_step * code;
+ /* 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);
+ const 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;
+
+ gain -= inf->gain_step > 0 ? inf->gain_min : inf->gain_max;
+ code = DIV_ROUND_CLOSEST(gain, inf->gain_step);
+
+ mutex_lock(&st->lock);
+ switch (mask) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ st->ch[chan->channel] = code;
+ ret = ad8366_write_code(st);
+ 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 int ad8366_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct gpio_desc *enable_gpio;
+ struct reset_control *rstc;
+ struct iio_dev *indio_dev;
+ struct ad8366_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (indio_dev == NULL)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ ret = devm_regulator_get_enable(dev, "vcc");
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get regulator\n");
+
+ st->spi = spi;
+ st->info = spi_get_device_match_data(spi);
+
+ enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH);
+ if (IS_ERR(enable_gpio))
+ return dev_err_probe(dev, PTR_ERR(enable_gpio),
+ "Failed to get enable GPIO\n");
+
+ rstc = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL);
+ if (IS_ERR(rstc))
+ return dev_err_probe(dev, PTR_ERR(rstc),
+ "Failed to get reset controller\n");
+
+ indio_dev->name = st->info->name;
+ indio_dev->info = &ad8366_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = ad8366_channels;
+ indio_dev->num_channels = st->info->num_channels;
+
+ ret = ad8366_write_code(st);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to write initial gain\n");
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct spi_device_id ad8366_id[] = {
+ { .name = "ad8366", .driver_data = (kernel_ulong_t)&ad8366_chip_info },
+ { .name = "ada4961", .driver_data = (kernel_ulong_t)&ada4961_chip_info },
+ { .name = "adl5240", .driver_data = (kernel_ulong_t)&adl5240_chip_info },
+ { .name = "adrf5702", .driver_data = (kernel_ulong_t)&adrf5702_chip_info },
+ { .name = "adrf5703", .driver_data = (kernel_ulong_t)&adrf5703_chip_info },
+ { .name = "adrf5720", .driver_data = (kernel_ulong_t)&adrf5720_chip_info },
+ { .name = "adrf5730", .driver_data = (kernel_ulong_t)&adrf5730_chip_info },
+ { .name = "adrf5731", .driver_data = (kernel_ulong_t)&adrf5731_chip_info },
+ { .name = "hmc271a", .driver_data = (kernel_ulong_t)&hmc271_chip_info },
+ { .name = "hmc792a", .driver_data = (kernel_ulong_t)&hmc792_chip_info },
+ { .name = "hmc1018a", .driver_data = (kernel_ulong_t)&hmc1018_chip_info },
+ { .name = "hmc1019a", .driver_data = (kernel_ulong_t)&hmc1019_chip_info },
+ { .name = "hmc1119", .driver_data = (kernel_ulong_t)&hmc1119_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ad8366_id);
+
+static const struct of_device_id ad8366_of_match[] = {
+ { .compatible = "adi,ad8366", .data = &ad8366_chip_info },
+ { .compatible = "adi,ada4961", .data = &ada4961_chip_info },
+ { .compatible = "adi,adl5240", .data = &adl5240_chip_info },
+ { .compatible = "adi,adrf5702", .data = &adrf5702_chip_info },
+ { .compatible = "adi,adrf5703", .data = &adrf5703_chip_info },
+ { .compatible = "adi,adrf5720", .data = &adrf5720_chip_info },
+ { .compatible = "adi,adrf5730", .data = &adrf5730_chip_info },
+ { .compatible = "adi,adrf5731", .data = &adrf5731_chip_info },
+ { .compatible = "adi,hmc271a", .data = &hmc271_chip_info },
+ { .compatible = "adi,hmc792a", .data = &hmc792_chip_info },
+ { .compatible = "adi,hmc1018a", .data = &hmc1018_chip_info },
+ { .compatible = "adi,hmc1019a", .data = &hmc1019_chip_info },
+ { .compatible = "adi,hmc1119", .data = &hmc1119_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad8366_of_match);
+
+static struct spi_driver ad8366_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = ad8366_of_match,
+ },
+ .probe = ad8366_probe,
+ .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");
diff --git a/drivers/iio/amplifiers/ada4250.c b/drivers/iio/amplifiers/ada4250.c
new file mode 100644
index 000000000..9702de3a5
--- /dev/null
+++ b/drivers/iio/amplifiers/ada4250.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ADA4250 driver
+ *
+ * Copyright 2022 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+/* ADA4250 Register Map */
+#define ADA4250_REG_GAIN_MUX 0x00
+#define ADA4250_REG_REFBUF_EN 0x01
+#define ADA4250_REG_RESET 0x02
+#define ADA4250_REG_SNSR_CAL_VAL 0x04
+#define ADA4250_REG_SNSR_CAL_CNFG 0x05
+#define ADA4250_REG_DIE_REV 0x18
+#define ADA4250_REG_CHIP_ID 0x19
+
+/* ADA4250_REG_GAIN_MUX Map */
+#define ADA4250_GAIN_MUX_MSK GENMASK(2, 0)
+
+/* ADA4250_REG_REFBUF Map */
+#define ADA4250_REFBUF_MSK BIT(0)
+
+/* ADA4250_REG_RESET Map */
+#define ADA4250_RESET_MSK BIT(0)
+
+/* ADA4250_REG_SNSR_CAL_VAL Map */
+#define ADA4250_CAL_CFG_BIAS_MSK GENMASK(7, 0)
+
+/* ADA4250_REG_SNSR_CAL_CNFG Bit Definition */
+#define ADA4250_BIAS_SET_MSK GENMASK(3, 2)
+#define ADA4250_RANGE_SET_MSK GENMASK(1, 0)
+
+/* Miscellaneous definitions */
+#define ADA4250_CHIP_ID 0x4250
+#define ADA4250_RANGE1 0
+#define ADA4250_RANGE4 3
+
+/* ADA4250 current bias set */
+enum ada4250_current_bias {
+ ADA4250_BIAS_DISABLED,
+ ADA4250_BIAS_BANDGAP,
+ ADA4250_BIAS_AVDD,
+};
+
+struct ada4250_state {
+ struct spi_device *spi;
+ struct regmap *regmap;
+ /* Protect against concurrent accesses to the device and data content */
+ struct mutex lock;
+ int avdd_uv;
+ int offset_uv;
+ u8 bias;
+ u8 gain;
+ bool refbuf_en;
+ __le16 reg_val_16 __aligned(IIO_DMA_MINALIGN);
+};
+
+/* ADA4250 Current Bias Source Settings: Disabled, Bandgap Reference, AVDD */
+static const int calibbias_table[] = {0, 1, 2};
+
+/* ADA4250 Gain (V/V) values: 1, 2, 4, 8, 16, 32, 64, 128 */
+static const int hwgain_table[] = {1, 2, 4, 8, 16, 32, 64, 128};
+
+static const struct regmap_config ada4250_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .read_flag_mask = BIT(7),
+ .max_register = 0x1A,
+};
+
+static int ada4250_set_offset_uv(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int offset_uv)
+{
+ struct ada4250_state *st = iio_priv(indio_dev);
+
+ int i, ret, x[8], max_vos, min_vos, voltage_v, vlsb = 0;
+ u8 offset_raw, range = ADA4250_RANGE1;
+ u32 lsb_coeff[6] = {1333, 2301, 4283, 8289, 16311, 31599};
+
+ if (st->bias == 0 || st->bias == 3)
+ return -EINVAL;
+
+ voltage_v = DIV_ROUND_CLOSEST(st->avdd_uv, MICRO);
+
+ if (st->bias == ADA4250_BIAS_AVDD)
+ x[0] = voltage_v;
+ else
+ x[0] = 5;
+
+ x[1] = 126 * (x[0] - 1);
+
+ for (i = 0; i < 6; i++)
+ x[i + 2] = DIV_ROUND_CLOSEST(x[1] * 1000, lsb_coeff[i]);
+
+ if (st->gain == 0)
+ return -EINVAL;
+
+ /*
+ * Compute Range and Voltage per LSB for the Sensor Offset Calibration
+ * Example of computation for Range 1 and Range 2 (Current Bias Set = AVDD):
+ * Range 1 Range 2
+ * Gain | Max Vos(mV) | LSB(mV) | Max Vos(mV) | LSB(mV) |
+ * 2 | X1*127 | X1=0.126(AVDD-1) | X1*3*127 | X1*3 |
+ * 4 | X2*127 | X2=X1/1.3333 | X2*3*127 | X2*3 |
+ * 8 | X3*127 | X3=X1/2.301 | X3*3*127 | X3*3 |
+ * 16 | X4*127 | X4=X1/4.283 | X4*3*127 | X4*3 |
+ * 32 | X5*127 | X5=X1/8.289 | X5*3*127 | X5*3 |
+ * 64 | X6*127 | X6=X1/16.311 | X6*3*127 | X6*3 |
+ * 128 | X7*127 | X7=X1/31.599 | X7*3*127 | X7*3 |
+ */
+ for (i = ADA4250_RANGE1; i <= ADA4250_RANGE4; i++) {
+ max_vos = x[st->gain] * 127 * ((1 << (i + 1)) - 1);
+ min_vos = -1 * max_vos;
+ if (offset_uv > min_vos && offset_uv < max_vos) {
+ range = i;
+ vlsb = x[st->gain] * ((1 << (i + 1)) - 1);
+ break;
+ }
+ }
+
+ if (vlsb <= 0)
+ return -EINVAL;
+
+ offset_raw = DIV_ROUND_CLOSEST(abs(offset_uv), vlsb);
+
+ mutex_lock(&st->lock);
+ ret = regmap_update_bits(st->regmap, ADA4250_REG_SNSR_CAL_CNFG,
+ ADA4250_RANGE_SET_MSK,
+ FIELD_PREP(ADA4250_RANGE_SET_MSK, range));
+ if (ret)
+ goto exit;
+
+ st->offset_uv = offset_raw * vlsb;
+
+ /*
+ * To set the offset calibration value, use bits [6:0] and bit 7 as the
+ * polarity bit (set to "0" for a negative offset and "1" for a positive
+ * offset).
+ */
+ if (offset_uv < 0) {
+ offset_raw |= BIT(7);
+ st->offset_uv *= (-1);
+ }
+
+ ret = regmap_write(st->regmap, ADA4250_REG_SNSR_CAL_VAL, offset_raw);
+
+exit:
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static int ada4250_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct ada4250_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ ret = regmap_read(st->regmap, ADA4250_REG_GAIN_MUX, val);
+ if (ret)
+ return ret;
+
+ *val = BIT(*val);
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_OFFSET:
+ *val = st->offset_uv;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ ret = regmap_read(st->regmap, ADA4250_REG_SNSR_CAL_CNFG, val);
+ if (ret)
+ return ret;
+
+ *val = FIELD_GET(ADA4250_BIAS_SET_MSK, *val);
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1;
+ *val2 = 1000000;
+
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ada4250_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct ada4250_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ ret = regmap_write(st->regmap, ADA4250_REG_GAIN_MUX,
+ FIELD_PREP(ADA4250_GAIN_MUX_MSK, ilog2(val)));
+ if (ret)
+ return ret;
+
+ st->gain = ilog2(val);
+
+ return ret;
+ case IIO_CHAN_INFO_OFFSET:
+ return ada4250_set_offset_uv(indio_dev, chan, val);
+ case IIO_CHAN_INFO_CALIBBIAS:
+ ret = regmap_update_bits(st->regmap, ADA4250_REG_SNSR_CAL_CNFG,
+ ADA4250_BIAS_SET_MSK,
+ FIELD_PREP(ADA4250_BIAS_SET_MSK, val));
+ if (ret)
+ return ret;
+
+ st->bias = val;
+
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ada4250_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_CALIBBIAS:
+ *vals = calibbias_table;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(calibbias_table);
+
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ *vals = hwgain_table;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(hwgain_table);
+
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ada4250_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg,
+ unsigned int write_val,
+ unsigned int *read_val)
+{
+ struct ada4250_state *st = iio_priv(indio_dev);
+
+ if (read_val)
+ return regmap_read(st->regmap, reg, read_val);
+ else
+ return regmap_write(st->regmap, reg, write_val);
+}
+
+static const struct iio_info ada4250_info = {
+ .read_raw = ada4250_read_raw,
+ .write_raw = ada4250_write_raw,
+ .read_avail = &ada4250_read_avail,
+ .debugfs_reg_access = &ada4250_reg_access,
+};
+
+static const struct iio_chan_spec ada4250_channels[] = {
+ {
+ .type = IIO_VOLTAGE,
+ .output = 1,
+ .indexed = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_HARDWAREGAIN) |
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_CALIBBIAS) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBBIAS) |
+ BIT(IIO_CHAN_INFO_HARDWAREGAIN),
+ }
+};
+
+static int ada4250_init(struct ada4250_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ int ret;
+ u16 chip_id;
+
+ st->refbuf_en = device_property_read_bool(dev, "adi,refbuf-enable");
+
+ st->avdd_uv = devm_regulator_get_enable_read_voltage(dev, "avdd");
+ if (st->avdd_uv < 0)
+ return dev_err_probe(dev, st->avdd_uv,
+ "failed to get the AVDD voltage\n");
+
+ ret = regmap_write(st->regmap, ADA4250_REG_RESET,
+ FIELD_PREP(ADA4250_RESET_MSK, 1));
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_read(st->regmap, ADA4250_REG_CHIP_ID, &st->reg_val_16,
+ sizeof(st->reg_val_16));
+ if (ret)
+ return ret;
+
+ chip_id = le16_to_cpu(st->reg_val_16);
+
+ if (chip_id != ADA4250_CHIP_ID)
+ dev_info(dev, "Invalid chip ID: 0x%02X.\n", chip_id);
+
+ return regmap_write(st->regmap, ADA4250_REG_REFBUF_EN,
+ FIELD_PREP(ADA4250_REFBUF_MSK, st->refbuf_en));
+}
+
+static int ada4250_probe(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev;
+ struct regmap *regmap;
+ struct ada4250_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ regmap = devm_regmap_init_spi(spi, &ada4250_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ st = iio_priv(indio_dev);
+ st->regmap = regmap;
+ st->spi = spi;
+
+ indio_dev->info = &ada4250_info;
+ indio_dev->name = "ada4250";
+ indio_dev->channels = ada4250_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ada4250_channels);
+
+ mutex_init(&st->lock);
+
+ ret = ada4250_init(st);
+ if (ret)
+ return dev_err_probe(&spi->dev, ret, "ADA4250 init failed\n");
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct spi_device_id ada4250_id[] = {
+ { .name = "ada4250" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ada4250_id);
+
+static const struct of_device_id ada4250_of_match[] = {
+ { .compatible = "adi,ada4250" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ada4250_of_match);
+
+static struct spi_driver ada4250_driver = {
+ .driver = {
+ .name = "ada4250",
+ .of_match_table = ada4250_of_match,
+ },
+ .probe = ada4250_probe,
+ .id_table = ada4250_id,
+};
+module_spi_driver(ada4250_driver);
+
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com");
+MODULE_DESCRIPTION("Analog Devices ADA4250");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/amplifiers/adl8113.c b/drivers/iio/amplifiers/adl8113.c
new file mode 100644
index 000000000..1f1cfca98
--- /dev/null
+++ b/drivers/iio/amplifiers/adl8113.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ADL8113 Low Noise Amplifier with integrated bypass switches
+ *
+ * Copyright 2025 Analog Devices Inc.
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitmap.h>
+#include <linux/device/driver.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+
+enum adl8113_signal_path {
+ ADL8113_INTERNAL_AMP,
+ ADL8113_INTERNAL_BYPASS,
+ ADL8113_EXTERNAL_A,
+ ADL8113_EXTERNAL_B,
+};
+
+struct adl8113_gain_config {
+ enum adl8113_signal_path path;
+ int gain_db;
+};
+
+struct adl8113_state {
+ struct gpio_descs *gpios;
+ struct adl8113_gain_config *gain_configs;
+ unsigned int num_gain_configs;
+ enum adl8113_signal_path current_path;
+};
+
+static const char * const adl8113_supply_names[] = {
+ "vdd1",
+ "vss2",
+ "vdd2",
+};
+
+static int adl8113_set_path(struct adl8113_state *st,
+ enum adl8113_signal_path path)
+{
+ DECLARE_BITMAP(values, 2);
+ int ret;
+
+ /*
+ * Determine GPIO values based on signal path.
+ * Va: bit 0, Vb: bit 1.
+ */
+ switch (path) {
+ case ADL8113_INTERNAL_AMP:
+ bitmap_write(values, 0x00, 0, 2);
+ break;
+ case ADL8113_INTERNAL_BYPASS:
+ bitmap_write(values, 0x03, 0, 2);
+ break;
+ case ADL8113_EXTERNAL_A:
+ bitmap_write(values, 0x02, 0, 2);
+ break;
+ case ADL8113_EXTERNAL_B:
+ bitmap_write(values, 0x01, 0, 2);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = gpiod_set_array_value_cansleep(st->gpios->ndescs, st->gpios->desc,
+ st->gpios->info, values);
+ if (ret)
+ return ret;
+
+ st->current_path = path;
+ return 0;
+}
+
+static int adl8113_find_gain_config(struct adl8113_state *st, int gain_db)
+{
+ unsigned int i;
+
+ for (i = 0; i < st->num_gain_configs; i++) {
+ if (st->gain_configs[i].gain_db == gain_db)
+ return i;
+ }
+ return -EINVAL;
+}
+
+static const struct iio_chan_spec adl8113_channels[] = {
+ {
+ .type = IIO_VOLTAGE,
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN),
+ },
+};
+
+static int adl8113_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct adl8113_state *st = iio_priv(indio_dev);
+ unsigned int i;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ /* Find current gain configuration */
+ for (i = 0; i < st->num_gain_configs; i++) {
+ if (st->gain_configs[i].path == st->current_path) {
+ *val = st->gain_configs[i].gain_db;
+ *val2 = 0;
+ return IIO_VAL_INT_PLUS_MICRO_DB;
+ }
+ }
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adl8113_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct adl8113_state *st = iio_priv(indio_dev);
+ int config_idx;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ if (val2 != 0)
+ return -EINVAL;
+
+ config_idx = adl8113_find_gain_config(st, val);
+ if (config_idx < 0)
+ return config_idx;
+
+ return adl8113_set_path(st, st->gain_configs[config_idx].path);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info adl8113_info = {
+ .read_raw = adl8113_read_raw,
+ .write_raw = adl8113_write_raw,
+};
+
+static int adl8113_init_gain_configs(struct device *dev, struct adl8113_state *st)
+{
+ int external_a_gain, external_b_gain;
+ unsigned int i;
+
+ /*
+ * Allocate for all 4 possible paths:
+ * - Internal amp and bypass (always present)
+ * - External bypass A and B (optional if configured)
+ */
+ st->gain_configs = devm_kcalloc(dev, 4, sizeof(*st->gain_configs),
+ GFP_KERNEL);
+ if (!st->gain_configs)
+ return -ENOMEM;
+
+ /* Start filling the gain configurations with data */
+ i = 0;
+
+ /* Always include internal amplifier (14dB) */
+ st->gain_configs[i++] = (struct adl8113_gain_config) {
+ .path = ADL8113_INTERNAL_AMP,
+ .gain_db = 14,
+ };
+
+ /* Always include internal bypass (-2dB insertion loss) */
+ st->gain_configs[i++] = (struct adl8113_gain_config) {
+ .path = ADL8113_INTERNAL_BYPASS,
+ .gain_db = -2,
+ };
+
+ /* Add external bypass A if configured */
+ if (!device_property_read_u32(dev, "adi,external-bypass-a-gain-db",
+ &external_a_gain)) {
+ st->gain_configs[i++] = (struct adl8113_gain_config) {
+ .path = ADL8113_EXTERNAL_A,
+ .gain_db = external_a_gain,
+ };
+ }
+
+ /* Add external bypass B if configured */
+ if (!device_property_read_u32(dev, "adi,external-bypass-b-gain-db",
+ &external_b_gain)) {
+ st->gain_configs[i++] = (struct adl8113_gain_config) {
+ .path = ADL8113_EXTERNAL_B,
+ .gain_db = external_b_gain,
+ };
+ }
+
+ st->num_gain_configs = i;
+
+ return 0;
+}
+
+static int adl8113_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct adl8113_state *st;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ st->gpios = devm_gpiod_get_array(dev, "ctrl", GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpios))
+ return dev_err_probe(dev, PTR_ERR(st->gpios),
+ "failed to get control GPIOs\n");
+
+ if (st->gpios->ndescs != 2)
+ return dev_err_probe(dev, -EINVAL,
+ "expected 2 control GPIOs, got %u\n",
+ st->gpios->ndescs);
+
+ ret = devm_regulator_bulk_get_enable(dev,
+ ARRAY_SIZE(adl8113_supply_names),
+ adl8113_supply_names);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to get and enable supplies\n");
+
+ /* Initialize gain configurations from devicetree */
+ ret = adl8113_init_gain_configs(dev, st);
+ if (ret)
+ return ret;
+
+ /* Initialize to internal amplifier path (14dB) */
+ ret = adl8113_set_path(st, ADL8113_INTERNAL_AMP);
+ if (ret)
+ return ret;
+
+ indio_dev->info = &adl8113_info;
+ indio_dev->name = "adl8113";
+ indio_dev->channels = adl8113_channels;
+ indio_dev->num_channels = ARRAY_SIZE(adl8113_channels);
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id adl8113_of_match[] = {
+ { .compatible = "adi,adl8113" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adl8113_of_match);
+
+static struct platform_driver adl8113_driver = {
+ .driver = {
+ .name = "adl8113",
+ .of_match_table = adl8113_of_match,
+ },
+ .probe = adl8113_probe,
+};
+module_platform_driver(adl8113_driver);
+
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADL8113 Low Noise Amplifier");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/amplifiers/hmc425a.c b/drivers/iio/amplifiers/hmc425a.c
new file mode 100644
index 000000000..85bfc8dcc
--- /dev/null
+++ b/drivers/iio/amplifiers/hmc425a.c
@@ -0,0 +1,424 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HMC425A and similar Gain Amplifiers
+ *
+ * Copyright 2020, 2024 Analog Devices Inc.
+ */
+
+#include <linux/bits.h>
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/kernel.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sysfs.h>
+
+/*
+ * The LTC6373 amplifier supports configuring gain using GPIO's with the following
+ * values (OUTPUT_V / INPUT_V): 0(shutdown), 0.25, 0.5, 1, 2, 4, 8, 16
+ *
+ * Except for the shutdown value, all can be converted to dB using 20 * log10(x)
+ * From here, it is observed that all values are multiples of the '2' gain setting,
+ * with the correspondent of 6.020dB.
+ */
+#define LTC6373_CONVERSION_CONSTANT 6020
+#define LTC6373_MIN_GAIN_CODE 0x6
+#define LTC6373_CONVERSION_MASK GENMASK(2, 0)
+#define LTC6373_SHUTDOWN GENMASK(2, 0)
+
+enum hmc425a_type {
+ ID_HMC425A,
+ ID_HMC540S,
+ ID_ADRF5740,
+ ID_LTC6373,
+};
+
+struct hmc425a_chip_info {
+ const char *name;
+ const struct iio_chan_spec *channels;
+ unsigned int num_channels;
+ unsigned int num_gpios;
+ int gain_min;
+ int gain_max;
+ int default_gain;
+ int powerdown_val;
+ bool has_powerdown;
+
+ int (*gain_dB_to_code)(int gain, int *code);
+ int (*code_to_gain_dB)(int code, int *val, int *val2);
+};
+
+struct hmc425a_state {
+ struct mutex lock; /* protect sensor state */
+ const struct hmc425a_chip_info *chip_info;
+ struct gpio_descs *gpios;
+ u32 gain;
+ bool powerdown;
+};
+
+static int gain_dB_to_code(struct hmc425a_state *st, int val, int val2, int *code)
+{
+ const struct hmc425a_chip_info *inf = st->chip_info;
+ int gain;
+
+ 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;
+ if (st->powerdown)
+ return -EPERM;
+
+ return st->chip_info->gain_dB_to_code(gain, code);
+}
+
+static int hmc425a_gain_dB_to_code(int gain, int *code)
+{
+ *code = ~((abs(gain) / 500) & 0x3F);
+ return 0;
+}
+
+static int hmc540s_gain_dB_to_code(int gain, int *code)
+{
+ *code = ~((abs(gain) / 1000) & 0xF);
+ return 0;
+}
+
+static int adrf5740_gain_dB_to_code(int gain, int *code)
+{
+ int temp = (abs(gain) / 2000) & 0xF;
+
+ /* Bit [0-3]: 2dB 4dB 8dB 8dB */
+ *code = temp & BIT(3) ? temp | BIT(2) : temp;
+ return 0;
+}
+
+static int ltc6373_gain_dB_to_code(int gain, int *code)
+{
+ *code = ~(DIV_ROUND_CLOSEST(gain, LTC6373_CONVERSION_CONSTANT) + 3)
+ & LTC6373_CONVERSION_MASK;
+ return 0;
+}
+
+static int code_to_gain_dB(struct hmc425a_state *st, int *val, int *val2)
+{
+ if (st->powerdown)
+ return -EPERM;
+ return st->chip_info->code_to_gain_dB(st->gain, val, val2);
+}
+
+static int hmc425a_code_to_gain_dB(int code, int *val, int *val2)
+{
+ *val = (~code * -500) / 1000;
+ *val2 = ((~code * -500) % 1000) * 1000;
+ return 0;
+}
+
+static int hmc540s_code_to_gain_dB(int code, int *val, int *val2)
+{
+ *val = (~code * -1000) / 1000;
+ *val2 = ((~code * -1000) % 1000) * 1000;
+ return 0;
+}
+
+static int adrf5740_code_to_gain_dB(int code, int *val, int *val2)
+{
+ /*
+ * Bit [0-3]: 2dB 4dB 8dB 8dB
+ * When BIT(3) is set, unset BIT(2) and use 3 as double the place value
+ */
+ code = code & BIT(3) ? code & ~BIT(2) : code;
+ *val = (code * -2000) / 1000;
+ *val2 = ((code * -2000) % 1000) * 1000;
+ return 0;
+}
+
+static int ltc6373_code_to_gain_dB(int code, int *val, int *val2)
+{
+ int gain = ((~code & LTC6373_CONVERSION_MASK) - 3) *
+ LTC6373_CONVERSION_CONSTANT;
+
+ *val = gain / 1000;
+ *val2 = (gain % 1000) * 1000;
+ return 0;
+}
+
+static int hmc425a_write(struct iio_dev *indio_dev, u32 value)
+{
+ struct hmc425a_state *st = iio_priv(indio_dev);
+ DECLARE_BITMAP(values, BITS_PER_TYPE(value));
+
+ values[0] = value;
+
+ gpiod_multi_set_value_cansleep(st->gpios, values);
+ return 0;
+}
+
+static int hmc425a_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long m)
+{
+ struct hmc425a_state *st = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&st->lock);
+ switch (m) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ ret = code_to_gain_dB(st, val, val2);
+ if (ret)
+ break;
+ ret = IIO_VAL_INT_PLUS_MICRO_DB;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ mutex_unlock(&st->lock);
+
+ return ret;
+};
+
+static int hmc425a_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct hmc425a_state *st = iio_priv(indio_dev);
+ int code = 0, ret;
+
+ mutex_lock(&st->lock);
+ switch (mask) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ ret = gain_dB_to_code(st, val, val2, &code);
+ if (ret)
+ break;
+ st->gain = code;
+
+ ret = hmc425a_write(indio_dev, st->gain);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static int hmc425a_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 hmc425a_info = {
+ .read_raw = &hmc425a_read_raw,
+ .write_raw = &hmc425a_write_raw,
+ .write_raw_get_fmt = &hmc425a_write_raw_get_fmt,
+};
+
+static ssize_t ltc6373_read_powerdown(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct hmc425a_state *st = iio_priv(indio_dev);
+
+ return sysfs_emit(buf, "%d\n", st->powerdown);
+}
+
+static ssize_t ltc6373_write_powerdown(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf,
+ size_t len)
+{
+ struct hmc425a_state *st = iio_priv(indio_dev);
+ bool powerdown;
+ int code, ret;
+
+ ret = kstrtobool(buf, &powerdown);
+ if (ret)
+ return ret;
+
+ mutex_lock(&st->lock);
+ st->powerdown = powerdown;
+ code = (powerdown) ? LTC6373_SHUTDOWN : st->gain;
+ hmc425a_write(indio_dev, code);
+ mutex_unlock(&st->lock);
+ return len;
+}
+
+static const struct iio_chan_spec_ext_info ltc6373_ext_info[] = {
+ {
+ .name = "powerdown",
+ .read = ltc6373_read_powerdown,
+ .write = ltc6373_write_powerdown,
+ .shared = IIO_SEPARATE,
+ },
+ { }
+};
+
+#define HMC425A_CHAN(_channel) \
+{ \
+ .type = IIO_VOLTAGE, \
+ .output = 1, \
+ .indexed = 1, \
+ .channel = _channel, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_HARDWAREGAIN), \
+}
+
+#define LTC6373_CHAN(_channel) \
+{ \
+ .type = IIO_VOLTAGE, \
+ .output = 1, \
+ .indexed = 1, \
+ .channel = _channel, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_HARDWAREGAIN), \
+ .ext_info = ltc6373_ext_info, \
+}
+
+static const struct iio_chan_spec hmc425a_channels[] = {
+ HMC425A_CHAN(0),
+};
+
+static const struct iio_chan_spec ltc6373_channels[] = {
+ LTC6373_CHAN(0),
+};
+
+static const struct hmc425a_chip_info hmc425a_chip_info_tbl[] = {
+ [ID_HMC425A] = {
+ .name = "hmc425a",
+ .channels = hmc425a_channels,
+ .num_channels = ARRAY_SIZE(hmc425a_channels),
+ .num_gpios = 6,
+ .gain_min = -31500,
+ .gain_max = 0,
+ .default_gain = -0x40, /* set default gain -31.5db*/
+ .gain_dB_to_code = hmc425a_gain_dB_to_code,
+ .code_to_gain_dB = hmc425a_code_to_gain_dB,
+ },
+ [ID_HMC540S] = {
+ .name = "hmc540s",
+ .channels = hmc425a_channels,
+ .num_channels = ARRAY_SIZE(hmc425a_channels),
+ .num_gpios = 4,
+ .gain_min = -15000,
+ .gain_max = 0,
+ .default_gain = -0x10, /* set default gain -15.0db*/
+ .gain_dB_to_code = hmc540s_gain_dB_to_code,
+ .code_to_gain_dB = hmc540s_code_to_gain_dB,
+ },
+ [ID_ADRF5740] = {
+ .name = "adrf5740",
+ .channels = hmc425a_channels,
+ .num_channels = ARRAY_SIZE(hmc425a_channels),
+ .num_gpios = 4,
+ .gain_min = -22000,
+ .gain_max = 0,
+ .default_gain = 0xF, /* set default gain -22.0db*/
+ .gain_dB_to_code = adrf5740_gain_dB_to_code,
+ .code_to_gain_dB = adrf5740_code_to_gain_dB,
+ },
+ [ID_LTC6373] = {
+ .name = "ltc6373",
+ .channels = ltc6373_channels,
+ .num_channels = ARRAY_SIZE(ltc6373_channels),
+ .num_gpios = 3,
+ .gain_min = -12041, /* gain setting x0.25*/
+ .gain_max = 24082, /* gain setting x16 */
+ .default_gain = LTC6373_MIN_GAIN_CODE,
+ .powerdown_val = LTC6373_SHUTDOWN,
+ .has_powerdown = true,
+ .gain_dB_to_code = ltc6373_gain_dB_to_code,
+ .code_to_gain_dB = ltc6373_code_to_gain_dB,
+ },
+};
+
+static int hmc425a_probe(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev;
+ struct hmc425a_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ st->chip_info = device_get_match_data(&pdev->dev);
+ indio_dev->num_channels = st->chip_info->num_channels;
+ indio_dev->channels = st->chip_info->channels;
+ indio_dev->name = st->chip_info->name;
+ st->gain = st->chip_info->default_gain;
+
+ st->gpios = devm_gpiod_get_array(&pdev->dev, "ctrl", GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpios))
+ return dev_err_probe(&pdev->dev, PTR_ERR(st->gpios),
+ "failed to get gpios\n");
+
+ if (st->gpios->ndescs != st->chip_info->num_gpios) {
+ dev_err(&pdev->dev, "%d GPIOs needed to operate\n",
+ st->chip_info->num_gpios);
+ return -ENODEV;
+ }
+
+ ret = devm_regulator_get_enable(&pdev->dev, "vcc-supply");
+ if (ret)
+ return ret;
+
+ mutex_init(&st->lock);
+
+ indio_dev->info = &hmc425a_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ if (st->chip_info->has_powerdown) {
+ st->powerdown = true;
+ hmc425a_write(indio_dev, st->chip_info->powerdown_val);
+ } else {
+ /* Set default gain */
+ hmc425a_write(indio_dev, st->gain);
+ }
+
+ return devm_iio_device_register(&pdev->dev, indio_dev);
+}
+
+static const struct of_device_id hmc425a_of_match[] = {
+ { .compatible = "adi,hmc425a",
+ .data = &hmc425a_chip_info_tbl[ID_HMC425A]},
+ { .compatible = "adi,hmc540s",
+ .data = &hmc425a_chip_info_tbl[ID_HMC540S]},
+ { .compatible = "adi,adrf5740",
+ .data = &hmc425a_chip_info_tbl[ID_ADRF5740]},
+ { .compatible = "adi,ltc6373",
+ .data = &hmc425a_chip_info_tbl[ID_LTC6373]},
+ { }
+};
+MODULE_DEVICE_TABLE(of, hmc425a_of_match);
+
+static struct platform_driver hmc425a_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = hmc425a_of_match,
+ },
+ .probe = hmc425a_probe,
+};
+module_platform_driver(hmc425a_driver);
+
+MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
+MODULE_DESCRIPTION("Analog Devices HMC425A and similar GPIO control Gain Amplifiers");
+MODULE_LICENSE("GPL v2");