[PATCH 2/3] iio: amplifiers: adl8113: add driver support

Antoniu Miclaus posted 3 patches 3 months, 1 week ago
There is a newer version of this series
[PATCH 2/3] iio: amplifiers: adl8113: add driver support
Posted by Antoniu Miclaus 3 months, 1 week ago
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>
---
 drivers/iio/amplifiers/Kconfig   |  12 ++
 drivers/iio/amplifiers/Makefile  |   1 +
 drivers/iio/amplifiers/adl8113.c | 235 +++++++++++++++++++++++++++++++
 3 files changed, 248 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..4a05c1497913
--- /dev/null
+++ b/drivers/iio/amplifiers/adl8113.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ADL8113 Low Noise Amplifier with integrated bypass switches
+ *
+ * Copyright 2025 Analog Devices Inc.
+ */
+
+#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/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.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_mode {
+	ADL8113_INTERNAL_AMPLIFIER,
+	ADL8113_INTERNAL_BYPASS,
+	ADL8113_EXTERNAL_BYPASS_A,
+	ADL8113_EXTERNAL_BYPASS_B
+};
+
+struct adl8113_state {
+	struct mutex lock; /* protect sensor state */
+	struct gpio_desc *gpio_va;
+	struct gpio_desc *gpio_vb;
+	enum adl8113_mode current_mode;
+};
+
+static const char * const adl8113_supply_names[] = {
+	"vdd1",
+	"vdd2",
+	"vss2"
+};
+
+static const char * const adl8113_mode_names[] = {
+	[ADL8113_INTERNAL_AMPLIFIER] = "internal_amplifier",
+	[ADL8113_INTERNAL_BYPASS] = "internal_bypass",
+	[ADL8113_EXTERNAL_BYPASS_A] = "external_bypass_a",
+	[ADL8113_EXTERNAL_BYPASS_B] = "external_bypass_b",
+};
+
+static int adl8113_set_mode(struct adl8113_state *st, enum adl8113_mode mode)
+{
+	switch (mode) {
+	case ADL8113_INTERNAL_AMPLIFIER:
+		gpiod_set_value(st->gpio_va, 0);
+		gpiod_set_value(st->gpio_vb, 0);
+		break;
+	case ADL8113_INTERNAL_BYPASS:
+		gpiod_set_value(st->gpio_va, 1);
+		gpiod_set_value(st->gpio_vb, 1);
+		break;
+	case ADL8113_EXTERNAL_BYPASS_A:
+		gpiod_set_value(st->gpio_va, 0);
+		gpiod_set_value(st->gpio_vb, 1);
+		break;
+	case ADL8113_EXTERNAL_BYPASS_B:
+		gpiod_set_value(st->gpio_va, 1);
+		gpiod_set_value(st->gpio_vb, 0);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	st->current_mode = mode;
+	return 0;
+}
+
+static int adl8113_get_mode(struct iio_dev *indio_dev,
+			    const struct iio_chan_spec *chan)
+{
+	struct adl8113_state *st = iio_priv(indio_dev);
+
+	return st->current_mode;
+}
+
+static int adl8113_set_mode_enum(struct iio_dev *indio_dev,
+				 const struct iio_chan_spec *chan,
+				 unsigned int mode)
+{
+	struct adl8113_state *st = iio_priv(indio_dev);
+	int ret;
+
+	if (mode >= ARRAY_SIZE(adl8113_mode_names))
+		return -EINVAL;
+
+	mutex_lock(&st->lock);
+	ret = adl8113_set_mode(st, mode);
+	mutex_unlock(&st->lock);
+
+	return ret;
+}
+
+static const struct iio_enum adl8113_mode_enum = {
+	.items = adl8113_mode_names,
+	.num_items = ARRAY_SIZE(adl8113_mode_names),
+	.get = adl8113_get_mode,
+	.set = adl8113_set_mode_enum,
+};
+
+static const struct iio_chan_spec_ext_info adl8113_ext_info[] = {
+	IIO_ENUM("mode", IIO_SHARED_BY_ALL, &adl8113_mode_enum),
+	IIO_ENUM_AVAILABLE("mode", IIO_SHARED_BY_ALL, &adl8113_mode_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);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		mutex_lock(&st->lock);
+		switch (st->current_mode) {
+		case ADL8113_INTERNAL_AMPLIFIER:
+			*val = 14;
+			*val2 = 0;
+			ret = IIO_VAL_INT_PLUS_MICRO_DB;
+			break;
+		case ADL8113_INTERNAL_BYPASS:
+		case ADL8113_EXTERNAL_BYPASS_A:
+		case ADL8113_EXTERNAL_BYPASS_B:
+			*val = 0;
+			*val2 = 0;
+			ret = IIO_VAL_INT_PLUS_MICRO_DB;
+			break;
+		default:
+			ret = -EINVAL;
+		}
+		mutex_unlock(&st->lock);
+		return ret;
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info adl8113_info = {
+	.read_raw = adl8113_read_raw,
+};
+
+static int adl8113_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct adl8113_state *st;
+	struct iio_dev *indio_dev;
+	u32 initial_mode = ADL8113_INTERNAL_AMPLIFIER;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	st = iio_priv(indio_dev);
+	mutex_init(&st->lock);
+
+	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");
+
+	device_property_read_u32(dev, "adi,initial-mode", &initial_mode);
+	if (initial_mode >= ARRAY_SIZE(adl8113_mode_names))
+		return -EINVAL;
+
+	ret = adl8113_set_mode(st, initial_mode);
+	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;
+
+	ret = devm_iio_device_register(dev, indio_dev);
+	if (ret)
+		return ret;
+
+	dev_info(dev, "ADL8113 registered, initial mode: %s\n",
+		 adl8113_mode_names[initial_mode]);
+
+	return 0;
+}
+
+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
Re: [PATCH 2/3] iio: amplifiers: adl8113: add driver support
Posted by Jonathan Cameron 3 months, 1 week ago
On Fri, 31 Oct 2025 16:04:04 +0000
Antoniu Miclaus <antoniu.miclaus@analog.com> wrote:

> Add support for adl8113 10MHz to 12GHz Low Noise Amplifier with
> 10MHz to 14GHz bypass switches.

This is an unusual device which makes things interesting
It is kind of a hybrid of a MUX and an amplifier.

As such most of the comments are around how we represent that.
I'd definitely like to get some more opinions on this.

Jonathan

> 
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>

> diff --git a/drivers/iio/amplifiers/adl8113.c b/drivers/iio/amplifiers/adl8113.c
> new file mode 100644
> index 000000000000..4a05c1497913
> --- /dev/null
> +++ b/drivers/iio/amplifiers/adl8113.c
> @@ -0,0 +1,235 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ADL8113 Low Noise Amplifier with integrated bypass switches
> + *
> + * Copyright 2025 Analog Devices Inc.
> + */
> +
> +#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>

Check closely what you actually use from kernel.h.  Mostly we are moving
to including more specific headers instead.  It is very rare it makes sense
to include this from a driver.

> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/mutex.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_mode {
> +	ADL8113_INTERNAL_AMPLIFIER,
> +	ADL8113_INTERNAL_BYPASS,
> +	ADL8113_EXTERNAL_BYPASS_A,
> +	ADL8113_EXTERNAL_BYPASS_B

Add a trailing comma.  Whilst there are only 4 modes there is nothing
that absolutely says there will never be more, perhaps in a chip variant.

> +};
> +
> +struct adl8113_state {
> +	struct mutex lock; /* protect sensor state */
Be more specific on what that is.  I think all it actually
protects is current mode read back racing with setting
GPIOS before writing it.  That's not a useful race to protect
as it corresponds to exactly what happens if the read back wins
the mutex race and then the pins are immediately updated.

If you were reading the gpios back it would make more sense as
you might see an intermediate state where one gpio had updated
and not the other..

> +	struct gpio_desc *gpio_va;
> +	struct gpio_desc *gpio_vb;
> +	enum adl8113_mode current_mode;
> +};

> +
> +static int adl8113_get_mode(struct iio_dev *indio_dev,
> +			    const struct iio_chan_spec *chan)
> +{
> +	struct adl8113_state *st = iio_priv(indio_dev);
> +
> +	return st->current_mode;
> +}
> +
> +static int adl8113_set_mode_enum(struct iio_dev *indio_dev,
> +				 const struct iio_chan_spec *chan,
> +				 unsigned int mode)
> +{
> +	struct adl8113_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	if (mode >= ARRAY_SIZE(adl8113_mode_names))
> +		return -EINVAL;
> +
> +	mutex_lock(&st->lock);
	guard(mutex)(&st->lock);
	return adl...

> +	ret = adl8113_set_mode(st, mode);
> +	mutex_unlock(&st->lock);
> +
> +	return ret;
> +}

> +
> +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);
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_HARDWAREGAIN:
> +		mutex_lock(&st->lock);

Add scope with {} and use guard so you can do early returns rather
than having to manually unlock.



> +		switch (st->current_mode) {
> +		case ADL8113_INTERNAL_AMPLIFIER:
> +			*val = 14;
> +			*val2 = 0;
> +			ret = IIO_VAL_INT_PLUS_MICRO_DB;
> +			break;
> +		case ADL8113_INTERNAL_BYPASS:
> +		case ADL8113_EXTERNAL_BYPASS_A:
> +		case ADL8113_EXTERNAL_BYPASS_B:
> +			*val = 0;
> +			*val2 = 0;
> +			ret = IIO_VAL_INT_PLUS_MICRO_DB;

Internal bypass is fine, but not the other two. We currently have no way
to know anything about those paths.

> +			break;
> +		default:
> +			ret = -EINVAL;
> +		}
> +		mutex_unlock(&st->lock);
> +		return ret;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static const struct iio_info adl8113_info = {
> +	.read_raw = adl8113_read_raw,
> +};
> +
> +static int adl8113_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct adl8113_state *st;
> +	struct iio_dev *indio_dev;
> +	u32 initial_mode = ADL8113_INTERNAL_AMPLIFIER;
> +	int ret;
> +
> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	st = iio_priv(indio_dev);
> +	mutex_init(&st->lock);

For new code, prefer
	ret = devm_mutex_init(dev, &st->lock);
	if (ret)
		return ret;
It's cheap to add and maybe is useful to someone debugging locks.

> +
> +	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");

I'm not keen on the initial mode thing below, but if we are going to do that
then we definitely should not transition through another mode on the way there.
With that gone though this default setting when getting the pins is fine. 
Or leave them to whatever some earlier setting was by using GPIOD_ASIS
and querying what that is.


> +
> +	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");
> +
> +	device_property_read_u32(dev, "adi,initial-mode", &initial_mode);
> +	if (initial_mode >= ARRAY_SIZE(adl8113_mode_names))
> +		return -EINVAL;
> +
> +	ret = adl8113_set_mode(st, initial_mode);
> +	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;
> +
> +	ret = devm_iio_device_register(dev, indio_dev);
> +	if (ret)
> +		return ret;
> +
> +	dev_info(dev, "ADL8113 registered, initial mode: %s\n",
> +		 adl8113_mode_names[initial_mode]);

As per the binding patch, I'm not seeing the reason for a default
mode.  It wasn't true prior to driver load, so might as well make
it a userspace problem to set after driver load.

With that gone I'd drop this print to dev_dbg() at most as it's
noise we don't need.


> +
> +	return 0;
> +}
> +
> +static const struct of_device_id adl8113_of_match[] = {
> +	{ .compatible = "adi,adl8113" },
> +	{}
Trivial but for IIO I'm trying to get consistent style where possible and
that for this stuff is
	{ }

> +};
> +MODULE_DEVICE_TABLE(of, adl8113_of_match);