Add support for adl8113 10MHz to 12GHz Low Noise Amplifier with
10MHz to 14GHz bypass switches.
Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
Changes in v3:
- Major architecture refactor: separate gain mode and signal path controls
- Replace single "mode" enum with separate gain_mode and signal_path enums
- Add write_raw support for IIO_CHAN_INFO_HARDWAREGAIN to control amplifier (14dB) vs bypass (0dB)
- Replace mode attribute with signal_path attribute for controlling routing
- Automatically enforce constraints: external paths require bypass mode
- Remove linux/iio/sysfs.h include (no longer needed)
- Add comprehensive GPIO state validation in adl8113_update_gpio()
- Enhance error handling with proper -EINVAL returns for invalid combinations
---
drivers/iio/amplifiers/Kconfig | 12 ++
drivers/iio/amplifiers/Makefile | 1 +
drivers/iio/amplifiers/adl8113.c | 252 +++++++++++++++++++++++++++++++
3 files changed, 265 insertions(+)
create mode 100644 drivers/iio/amplifiers/adl8113.c
diff --git a/drivers/iio/amplifiers/Kconfig b/drivers/iio/amplifiers/Kconfig
index 55eb16b32f6c..a8a604863eed 100644
--- a/drivers/iio/amplifiers/Kconfig
+++ b/drivers/iio/amplifiers/Kconfig
@@ -36,6 +36,18 @@ config ADA4250
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
diff --git a/drivers/iio/amplifiers/Makefile b/drivers/iio/amplifiers/Makefile
index 2126331129cf..0a76443be1aa 100644
--- a/drivers/iio/amplifiers/Makefile
+++ b/drivers/iio/amplifiers/Makefile
@@ -6,4 +6,5 @@
# 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/adl8113.c b/drivers/iio/amplifiers/adl8113.c
new file mode 100644
index 000000000000..155291f4ec89
--- /dev/null
+++ b/drivers/iio/amplifiers/adl8113.c
@@ -0,0 +1,252 @@
+// 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/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+enum adl8113_gain_mode {
+ ADL8113_AMPLIFIER,
+ ADL8113_BYPASS,
+};
+
+enum adl8113_signal_path {
+ ADL8113_INTERNAL,
+ ADL8113_EXTERNAL_A,
+ ADL8113_EXTERNAL_B,
+};
+
+struct adl8113_state {
+ struct gpio_desc *gpio_va;
+ struct gpio_desc *gpio_vb;
+ enum adl8113_gain_mode gain_mode;
+ enum adl8113_signal_path signal_path;
+};
+
+static const char * const adl8113_supply_names[] = {
+ "vdd1",
+ "vdd2",
+ "vss2"
+};
+
+static const char * const adl8113_signal_path_names[] = {
+ [ADL8113_INTERNAL] = "internal",
+ [ADL8113_EXTERNAL_A] = "external_a",
+ [ADL8113_EXTERNAL_B] = "external_b",
+};
+
+static int adl8113_update_gpio(struct adl8113_state *st)
+{
+ int va, vb;
+
+ /* Determine GPIO values based on gain mode and signal path */
+ switch (st->gain_mode) {
+ case ADL8113_AMPLIFIER:
+ if (st->signal_path != ADL8113_INTERNAL)
+ return -EINVAL;
+ va = 0; vb = 0; /* Internal amplifier */
+ break;
+ case ADL8113_BYPASS:
+ switch (st->signal_path) {
+ case ADL8113_INTERNAL:
+ va = 1; vb = 1; /* Internal bypass */
+ break;
+ case ADL8113_EXTERNAL_A:
+ va = 0; vb = 1; /* External bypass A */
+ break;
+ case ADL8113_EXTERNAL_B:
+ va = 1; vb = 0; /* External bypass B */
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ gpiod_set_value(st->gpio_va, va);
+ gpiod_set_value(st->gpio_vb, vb);
+ return 0;
+}
+
+static int adl8113_get_signal_path(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct adl8113_state *st = iio_priv(indio_dev);
+
+ return st->signal_path;
+}
+
+static int adl8113_set_signal_path(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int path)
+{
+ struct adl8113_state *st = iio_priv(indio_dev);
+
+ if (path >= ARRAY_SIZE(adl8113_signal_path_names))
+ return -EINVAL;
+
+ /* External paths require bypass mode, so switch to it automatically */
+ if (path != ADL8113_INTERNAL)
+ st->gain_mode = ADL8113_BYPASS;
+
+ st->signal_path = path;
+ return adl8113_update_gpio(st);
+}
+
+static const struct iio_enum adl8113_signal_path_enum = {
+ .items = adl8113_signal_path_names,
+ .num_items = ARRAY_SIZE(adl8113_signal_path_names),
+ .get = adl8113_get_signal_path,
+ .set = adl8113_set_signal_path,
+};
+
+static const struct iio_chan_spec_ext_info adl8113_ext_info[] = {
+ IIO_ENUM("signal_path", IIO_SHARED_BY_ALL, &adl8113_signal_path_enum),
+ IIO_ENUM_AVAILABLE("signal_path", IIO_SHARED_BY_ALL,
+ &adl8113_signal_path_enum),
+ { },
+};
+
+static const struct iio_chan_spec adl8113_channels[] = {
+ {
+ .type = IIO_VOLTAGE,
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN),
+ .indexed = 1,
+ .channel = 0,
+ .ext_info = adl8113_ext_info,
+ },
+};
+
+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);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ if (st->gain_mode == ADL8113_AMPLIFIER) {
+ /* Amplifier mode: 14dB gain */
+ *val = 14;
+ *val2 = 0;
+ return IIO_VAL_INT_PLUS_MICRO_DB;
+ }
+
+ /* Bypass mode: 0dB gain */
+ *val = 0;
+ *val2 = 0;
+ return IIO_VAL_INT_PLUS_MICRO_DB;
+ 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);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ /* Accept dB values: 0dB (bypass) or 14dB (amplifier) */
+ if (val == 0 && val2 == 0) {
+ st->gain_mode = ADL8113_BYPASS;
+ } else if (val == 14 && val2 == 0) {
+ st->gain_mode = ADL8113_AMPLIFIER;
+ /* Force internal path for amplifier mode */
+ st->signal_path = ADL8113_INTERNAL;
+ } else {
+ return -EINVAL;
+ }
+ return adl8113_update_gpio(st);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info adl8113_info = {
+ .read_raw = adl8113_read_raw,
+ .write_raw = adl8113_write_raw,
+};
+
+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->gpio_va = devm_gpiod_get(dev, "va", GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpio_va))
+ return dev_err_probe(dev, PTR_ERR(st->gpio_va),
+ "failed to get VA GPIO\n");
+
+ st->gpio_vb = devm_gpiod_get(dev, "vb", GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpio_vb))
+ return dev_err_probe(dev, PTR_ERR(st->gpio_vb),
+ "failed to get VB GPIO\n");
+
+ 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 to amplifier mode with internal path */
+ st->gain_mode = ADL8113_AMPLIFIER;
+ st->signal_path = ADL8113_INTERNAL;
+ ret = adl8113_update_gpio(st);
+ 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);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ 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");
--
2.43.0
On Fri, 14 Nov 2025 11:57:24 +0000
Antoniu Miclaus <antoniu.miclaus@analog.com> wrote:
> Add support for adl8113 10MHz to 12GHz Low Noise Amplifier with
> 10MHz to 14GHz bypass switches.
>
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
Hi Antoniu
A few really minor things inline that are in the category of
things I'd just tidy up whilst applying.
However, I want to know more about real usecases. Probably best
place to discuss those is wrt to patch 3 so I'll reply to that.
> diff --git a/drivers/iio/amplifiers/adl8113.c b/drivers/iio/amplifiers/adl8113.c
> new file mode 100644
> index 000000000000..155291f4ec89
> --- /dev/null
> +++ b/drivers/iio/amplifiers/adl8113.c
> @@ -0,0 +1,252 @@
> +// 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/device.h>
> +#include <linux/err.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/iio/iio.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/slab.h>
> +#include <linux/sysfs.h>
Is this used?
> +static const char * const adl8113_supply_names[] = {
> + "vdd1",
> + "vdd2",
> + "vss2"
This one should have a trailing comma because maybe we'll have
another supply at some future point. At very least it's not obvious
from code alone that we never will so convention is to have that comma.
> +};
> +
> +static const struct iio_chan_spec_ext_info adl8113_ext_info[] = {
> + IIO_ENUM("signal_path", IIO_SHARED_BY_ALL, &adl8113_signal_path_enum),
> + IIO_ENUM_AVAILABLE("signal_path", IIO_SHARED_BY_ALL,
> + &adl8113_signal_path_enum),
> + { },
Trivial but no trailing comma given nothing can come after this.
> +};
© 2016 - 2026 Red Hat, Inc.