* [PATCH v6 1/3] gpio: regmap: Use regmap_test_bits() for single bit reads
2026-09-03 12:01 [PATCH v6 0/3] gpio: Add AD7768 GPIO support Janani Sunil
@ 2026-09-03 12:01 ` Janani Sunil
2026-09-03 12:01 ` [PATCH v6 2/3] gpio: regmap: Add optional runtime PM support Janani Sunil
2026-09-03 12:01 ` [PATCH v6 3/3] gpio: ad7768: Add AD7768 GPIO auxiliary driver Janani Sunil
2 siblings, 0 replies; 6+ messages in thread
From: Janani Sunil @ 2026-09-03 12:01 UTC (permalink / raw)
To: Michael Walle, Linus Walleij, Bartosz Golaszewski
Cc: linux-gpio, linux-kernel, Nuno Sá, Jonathan Cameron,
Andy Shevchenko, Janani Sunil, Janani Sunil
Use regmap_test_bits() when reading a single GPIO value from a normal
register and when reading the direction bit.
Reviewed-by: Linus Walleij <linusw@kernel.org>
Reviewed-by: Michael Walle <mwalle@kernel.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: Janani Sunil <janani.sunil@analog.com>
---
drivers/gpio/gpio-regmap.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
index fb21396e1d02..ae6fa42b00a4 100644
--- a/drivers/gpio/gpio-regmap.c
+++ b/drivers/gpio/gpio-regmap.c
@@ -95,11 +95,11 @@ static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset)
if (ret)
return ret;
+ if (gpio->reg_dat_base != gpio->reg_set_base)
+ return regmap_test_bits(gpio->regmap, reg, mask);
+
/* ensure we don't spoil any register cache with pin input values */
- if (gpio->reg_dat_base == gpio->reg_set_base)
- ret = regmap_read_bypassed(gpio->regmap, reg, &val);
- else
- ret = regmap_read(gpio->regmap, reg, &val);
+ ret = regmap_read_bypassed(gpio->regmap, reg, &val);
if (ret)
return ret;
@@ -183,7 +183,7 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip,
unsigned int offset)
{
struct gpio_regmap *gpio = gpiochip_get_data(chip);
- unsigned int base, val, reg, mask;
+ unsigned int base, reg, mask;
int invert, ret;
if (gpio_regmap_fixed_direction(gpio, offset)) {
@@ -212,14 +212,14 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip,
if (ret)
return ret;
- ret = regmap_read(gpio->regmap, reg, &val);
- if (ret)
+ ret = regmap_test_bits(gpio->regmap, reg, mask);
+ if (ret < 0)
return ret;
- if (!!(val & mask) ^ invert)
+ if (ret ^ invert)
return GPIO_LINE_DIRECTION_OUT;
- else
- return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_IN;
}
static int gpio_regmap_try_direction_fixed(struct gpio_regmap *gpio,
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v6 2/3] gpio: regmap: Add optional runtime PM support
2026-09-03 12:01 [PATCH v6 0/3] gpio: Add AD7768 GPIO support Janani Sunil
2026-09-03 12:01 ` [PATCH v6 1/3] gpio: regmap: Use regmap_test_bits() for single bit reads Janani Sunil
@ 2026-09-03 12:01 ` Janani Sunil
2026-09-04 6:44 ` Michael Walle
2026-09-03 12:01 ` [PATCH v6 3/3] gpio: ad7768: Add AD7768 GPIO auxiliary driver Janani Sunil
2 siblings, 1 reply; 6+ messages in thread
From: Janani Sunil @ 2026-09-03 12:01 UTC (permalink / raw)
To: Michael Walle, Linus Walleij, Bartosz Golaszewski
Cc: linux-gpio, linux-kernel, Nuno Sá, Jonathan Cameron,
Andy Shevchenko, Janani Sunil, Janani Sunil
Some gpio-regmap consumers share their regmap with a parent device that
may be runtime suspended. GPIO register accesses must resume that device
first.
Add an optional pm_dev field and acquire it before register translation
or access. Release it using runtime autosuspend after each operation.
Keep the device active across the complete direction-output sequence and
propagate failure when setting the initial output value.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: Janani Sunil <janani.sunil@analog.com>
---
drivers/gpio/gpio-regmap.c | 80 +++++++++++++++++++++++++++++++++++++++++----
include/linux/gpio/regmap.h | 2 ++
2 files changed, 76 insertions(+), 6 deletions(-)
diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
index ae6fa42b00a4..2f5f45b84cc9 100644
--- a/drivers/gpio/gpio-regmap.c
+++ b/drivers/gpio/gpio-regmap.c
@@ -6,11 +6,13 @@
*/
#include <linux/bits.h>
+#include <linux/cleanup.h>
#include <linux/compiler_attributes.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
+#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/types.h>
@@ -23,6 +25,8 @@
struct gpio_regmap {
struct device *parent;
struct regmap *regmap;
+ struct device *pm_dev;
+
struct gpio_chip gpio_chip;
int reg_stride;
@@ -79,6 +83,33 @@ static int gpio_regmap_simple_xlate(struct gpio_regmap *gpio,
return 0;
}
+static int gpio_regmap_runtime_get(struct gpio_regmap *gpio)
+{
+ if (!gpio->pm_dev)
+ return 0;
+
+ return pm_runtime_get_active(gpio->pm_dev, RPM_TRANSPARENT);
+}
+
+static void gpio_regmap_runtime_put(struct gpio_regmap *gpio)
+{
+ if (!gpio->pm_dev)
+ return;
+
+ pm_runtime_put_autosuspend(gpio->pm_dev);
+}
+
+DEFINE_GUARD(gpio_regmap_runtime, struct gpio_regmap *,
+ gpio_regmap_runtime_get(_T), gpio_regmap_runtime_put(_T))
+DEFINE_GUARD_COND(gpio_regmap_runtime, _try,
+ gpio_regmap_runtime_get(_T), _RET == 0)
+
+#define GPIO_REGMAP_RUNTIME_ACQUIRE(_gpio, _var) \
+ ACQUIRE(gpio_regmap_runtime_try, _var)(_gpio)
+
+#define GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(_var_ptr) \
+ ACQUIRE_ERR(gpio_regmap_runtime, _var_ptr)
+
static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset)
{
struct gpio_regmap *gpio = gpiochip_get_data(chip);
@@ -91,7 +122,13 @@ static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset)
else
base = gpio_regmap_addr(gpio->reg_set_base);
- ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_GET_OP, base, offset, ®, &mask);
+ GPIO_REGMAP_RUNTIME_ACQUIRE(gpio, pm);
+ ret = GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_GET_OP, base, offset,
+ ®, &mask);
if (ret)
return ret;
@@ -114,7 +151,13 @@ static int gpio_regmap_set(struct gpio_chip *chip, unsigned int offset,
unsigned int reg, mask, mask_val;
int ret;
- ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_OP, base, offset, ®, &mask);
+ GPIO_REGMAP_RUNTIME_ACQUIRE(gpio, pm);
+ ret = GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_OP, base, offset,
+ ®, &mask);
if (ret)
return ret;
@@ -146,6 +189,11 @@ static int gpio_regmap_set_with_clear(struct gpio_chip *chip,
unsigned int base, reg, mask, value = 0;
int ret;
+ GPIO_REGMAP_RUNTIME_ACQUIRE(gpio, pm);
+ ret = GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
if (val)
base = gpio_regmap_addr(gpio->reg_set_base);
else
@@ -208,7 +256,13 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip,
return -ENOTSUPP;
}
- ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_GET_DIR_OP, base, offset, ®, &mask);
+ GPIO_REGMAP_RUNTIME_ACQUIRE(gpio, pm);
+ ret = GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_GET_DIR_OP, base, offset,
+ ®, &mask);
if (ret)
return ret;
@@ -262,7 +316,13 @@ static int gpio_regmap_set_direction(struct gpio_chip *chip,
return -ENOTSUPP;
}
- ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_DIR_OP, base, offset, ®, &mask);
+ GPIO_REGMAP_RUNTIME_ACQUIRE(gpio, pm);
+ ret = GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_DIR_OP, base, offset,
+ ®, &mask);
if (ret)
return ret;
@@ -293,6 +353,11 @@ static int gpio_regmap_direction_output(struct gpio_chip *chip,
struct gpio_regmap *gpio = gpiochip_get_data(chip);
int ret;
+ GPIO_REGMAP_RUNTIME_ACQUIRE(gpio, pm);
+ ret = GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
/*
* First check if this is gonna work on a fixed direction line,
* if it doesn't (i.e. this is a fixed input line), then do not
@@ -304,7 +369,9 @@ static int gpio_regmap_direction_output(struct gpio_chip *chip,
return ret;
}
- gpio_regmap_set(chip, offset, value);
+ ret = gpio_regmap_set(chip, offset, value);
+ if (ret)
+ return ret;
return gpio_regmap_set_direction(chip, offset, true);
}
@@ -399,6 +466,7 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
gpio->reg_clr_base = config->reg_clr_base;
gpio->reg_dir_in_base = config->reg_dir_in_base;
gpio->reg_dir_out_base = config->reg_dir_out_base;
+ gpio->pm_dev = config->pm_dev;
chip = &gpio->gpio_chip;
chip->parent = config->parent;
@@ -406,7 +474,7 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
chip->base = -1;
chip->names = config->names;
chip->label = config->label ?: dev_name(config->parent);
- chip->can_sleep = regmap_might_sleep(config->regmap);
+ chip->can_sleep = config->pm_dev || regmap_might_sleep(config->regmap);
chip->init_valid_mask = config->init_valid_mask;
chip->request = gpiochip_generic_request;
diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h
index 6f52c140e50d..6fe0877717cb 100644
--- a/include/linux/gpio/regmap.h
+++ b/include/linux/gpio/regmap.h
@@ -46,6 +46,7 @@ enum gpio_regmap_operation {
* @parent: The parent device
* @regmap: The regmap used to access the registers
* given, the name of the device is used
+ * @pm_dev: (Optional) Device to use for runtime power management.
* @fwnode: (Optional) The firmware node.
* If not given, the fwnode of the parent is used.
* @label: (Optional) Descriptive name for GPIO controller.
@@ -117,6 +118,7 @@ enum gpio_regmap_operation {
struct gpio_regmap_config {
struct device *parent;
struct regmap *regmap;
+ struct device *pm_dev;
struct fwnode_handle *fwnode;
const char *label;
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v6 3/3] gpio: ad7768: Add AD7768 GPIO auxiliary driver
2026-09-03 12:01 [PATCH v6 0/3] gpio: Add AD7768 GPIO support Janani Sunil
2026-09-03 12:01 ` [PATCH v6 1/3] gpio: regmap: Use regmap_test_bits() for single bit reads Janani Sunil
2026-09-03 12:01 ` [PATCH v6 2/3] gpio: regmap: Add optional runtime PM support Janani Sunil
@ 2026-09-03 12:01 ` Janani Sunil
2026-09-03 15:21 ` Andy Shevchenko
2 siblings, 1 reply; 6+ messages in thread
From: Janani Sunil @ 2026-09-03 12:01 UTC (permalink / raw)
To: Michael Walle, Linus Walleij, Bartosz Golaszewski
Cc: linux-gpio, linux-kernel, Nuno Sá, Jonathan Cameron,
Andy Shevchenko, Janani Sunil, Janani Sunil
The AD7768 provides five GPIOs controlled through registers shared
with the parent IIO device. Register an auxiliary gpio-regmap driver
and use the parent device for runtime PM.
The device has separate input-state and output-latch registers. Add a
reg_mask_xlate() callback that checks the line direction and reads the
programmed output latch for output lines while retaining input-state
reads for input lines.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Signed-off-by: Janani Sunil <janani.sunil@analog.com>
---
drivers/gpio/Kconfig | 12 +++++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-ad7768.c | 122 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 135 insertions(+)
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index a48586bb8edb..07b7f8ab0ccb 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1818,6 +1818,18 @@ endmenu
menu "Auxiliary Bus GPIO drivers"
depends on AUXILIARY_BUS
+config GPIO_AD7768
+ tristate "Analog Devices AD7768 GPIO support"
+ depends on AD7768
+ depends on GPIOLIB
+ select GPIO_REGMAP
+ help
+ Say yes here to expose the AD7768 utility pins as GPIOs when the
+ device tree node is marked as a GPIO controller.
+
+ To compile this driver as a module, choose M here: the module will be
+ called gpio-ad7768.
+
config GPIO_LTC4283
tristate "Analog Devices LTC4283 GPIO support"
depends on SENSORS_LTC4283
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index dc9e6d643b5b..ce56e9b3f55d 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_GPIO_104_IDI_48) += gpio-104-idi-48.o
obj-$(CONFIG_GPIO_104_IDIO_16) += gpio-104-idio-16.o
obj-$(CONFIG_GPIO_74X164) += gpio-74x164.o
obj-$(CONFIG_GPIO_74XX_MMIO) += gpio-74xx-mmio.o
+obj-$(CONFIG_GPIO_AD7768) += gpio-ad7768.o
obj-$(CONFIG_GPIO_ADNP) += gpio-adnp.o
obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o
obj-$(CONFIG_GPIO_ADP5585) += gpio-adp5585.o
diff --git a/drivers/gpio/gpio-ad7768.c b/drivers/gpio/gpio-ad7768.c
new file mode 100644
index 000000000000..cac3a55e738d
--- /dev/null
+++ b/drivers/gpio/gpio-ad7768.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Analog Devices AD7768 GPIO auxiliary driver
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitmap.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#define AD7768_REG_GPIO_CONTROL 0x0E
+#define AD7768_GPIO_UGPIO_ENABLE BIT(7)
+
+#define AD7768_REG_GPIO_WRITE 0x0F
+#define AD7768_REG_GPIO_READ 0x10
+
+#define AD7768_NUM_GPIOS 5
+#define AD7768_FILTER_GPIO 4
+
+static int ad7768_gpio_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ if (ngpios > AD7768_FILTER_GPIO &&
+ device_property_match_string(gc->parent->parent, "clock-names",
+ "mclk") < 0)
+ bitmap_clear(valid_mask, AD7768_FILTER_GPIO, 1);
+
+ return 0;
+}
+
+static int ad7768_gpio_reg_mask_xlate(struct gpio_regmap *gpio,
+ enum gpio_regmap_operation op,
+ unsigned int base, unsigned int offset,
+ unsigned int *reg, unsigned int *mask)
+{
+ struct regmap *regmap = gpio_regmap_get_drvdata(gpio);
+ int ret;
+
+ *reg = base;
+ *mask = BIT(offset);
+
+ if (op != GPIO_REGMAP_GET_OP)
+ return 0;
+
+ /*
+ * AD7768 has separate input-state and output-latch registers. For an
+ * output line, report the programmed value from the output latch;
+ * input lines continue to use the input-state register.
+ */
+ ret = regmap_test_bits(regmap, AD7768_REG_GPIO_CONTROL, *mask);
+ if (ret < 0)
+ return ret;
+ if (ret)
+ *reg = AD7768_REG_GPIO_WRITE;
+
+ return 0;
+}
+
+static int ad7768_gpio_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct gpio_regmap_config config;
+ struct device *dev = &adev->dev;
+ struct device *parent;
+ struct regmap *map;
+ int ret;
+
+ parent = dev->parent;
+ map = dev_get_regmap(parent, NULL);
+ if (!map)
+ return -ENODEV;
+
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(parent, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(map, AD7768_REG_GPIO_CONTROL, AD7768_GPIO_UGPIO_ENABLE);
+ if (ret)
+ return ret;
+
+ config = (struct gpio_regmap_config) {
+ .parent = dev,
+ .regmap = map,
+ .label = dev_name(parent),
+ .ngpio = AD7768_NUM_GPIOS,
+ .reg_dat_base = AD7768_REG_GPIO_READ,
+ .reg_set_base = AD7768_REG_GPIO_WRITE,
+ .reg_dir_out_base = AD7768_REG_GPIO_CONTROL,
+ .pm_dev = parent,
+ .reg_mask_xlate = ad7768_gpio_reg_mask_xlate,
+ .init_valid_mask = ad7768_gpio_init_valid_mask,
+ .drvdata = map,
+ };
+
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &config));
+}
+
+static const struct auxiliary_device_id ad7768_gpio_ids[] = {
+ { .name = "ad7768.gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(auxiliary, ad7768_gpio_ids);
+
+static struct auxiliary_driver ad7768_gpio_driver = {
+ .probe = ad7768_gpio_probe,
+ .id_table = ad7768_gpio_ids,
+};
+module_auxiliary_driver(ad7768_gpio_driver);
+
+MODULE_AUTHOR("Janani Sunil <janani.sunil@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD7768 GPIO auxiliary driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread