--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices LTC4283 GPIO driver
+ *
+ * Copyright 2025 Analog Devices Inc.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bitmap.h>
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#define LTC4283_PINS_MAX 8
+#define LTC4283_PGIOX_START_NR 4
+#define LTC4283_INPUT_STATUS 0x02
+#define LTC4283_PGIO_CONFIG 0x10
+#define LTC4283_PGIO_CFG_MASK(pin) \
+ GENMASK(((pin) - LTC4283_PGIOX_START_NR) * 2 + 1, (((pin) - LTC4283_PGIOX_START_NR) * 2))
+#define LTC4283_PGIO_CONFIG_2 0x11
+
+#define LTC4283_ADIO_CONFIG 0x12
+/* starts at bit 4 */
+#define LTC4283_ADIOX_CONFIG_MASK(pin) BIT((pin) + 4)
+#define LTC4283_PGIO_DIR_IN 3
+#define LTC4283_PGIO_DIR_OUT 2
+
+struct ltc4283_gpio {
+ struct gpio_chip gpio_chip;
+ struct regmap *regmap;
+};
+
+static int ltc4283_pgio_get_direction(const struct ltc4283_gpio *st, unsigned int off)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(st->regmap, LTC4283_PGIO_CONFIG, &val);
+ if (ret)
+ return ret;
+
+ val = field_get(LTC4283_PGIO_CFG_MASK(off), val);
+ if (val == LTC4283_PGIO_DIR_IN)
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int ltc4283_gpio_get_direction(struct gpio_chip *gc, unsigned int off)
+{
+ struct ltc4283_gpio *st = gpiochip_get_data(gc);
+ unsigned int val;
+ int ret;
+
+ if (off >= LTC4283_PGIOX_START_NR)
+ return ltc4283_pgio_get_direction(st, off);
+
+ ret = regmap_read(st->regmap, LTC4283_ADIO_CONFIG, &val);
+ if (ret)
+ return ret;
+
+ if (val & LTC4283_ADIOX_CONFIG_MASK(off))
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int ltc4283_gpio_direction_set(const struct ltc4283_gpio *st,
+ unsigned int off, bool input)
+{
+ if (off >= LTC4283_PGIOX_START_NR) {
+ unsigned int val = LTC4283_PGIO_DIR_OUT;
+
+ if (input)
+ val = LTC4283_PGIO_DIR_IN;
+
+ val = field_prep(LTC4283_PGIO_CFG_MASK(off), val);
+ return regmap_update_bits(st->regmap, LTC4283_PGIO_CONFIG,
+ LTC4283_PGIO_CFG_MASK(off), val);
+ }
+
+ return regmap_update_bits(st->regmap, LTC4283_ADIO_CONFIG,
+ LTC4283_ADIOX_CONFIG_MASK(off),
+ field_prep(LTC4283_ADIOX_CONFIG_MASK(off), input));
+}
+
+static int __ltc4283_gpio_set_value(const struct ltc4283_gpio *st,
+ unsigned int off, int val)
+{
+ u32 reg = off < LTC4283_PGIOX_START_NR ? LTC4283_ADIO_CONFIG : LTC4283_PGIO_CONFIG_2;
+
+ return regmap_update_bits(st->regmap, reg, BIT(off),
+ field_prep(BIT(off), !!val));
+}
+
+static int ltc4283_gpio_direction_input(struct gpio_chip *gc, unsigned int off)
+{
+ struct ltc4283_gpio *st = gpiochip_get_data(gc);
+
+ return ltc4283_gpio_direction_set(st, off, true);
+}
+
+static int ltc4283_gpio_direction_output(struct gpio_chip *gc, unsigned int off, int val)
+{
+ struct ltc4283_gpio *st = gpiochip_get_data(gc);
+ int ret;
+
+ ret = ltc4283_gpio_direction_set(st, off, false);
+ if (ret)
+ return ret;
+
+ return __ltc4283_gpio_set_value(st, off, val);
+}
+
+static int ltc4283_gpio_get_value(struct gpio_chip *gc, unsigned int off)
+{
+ struct ltc4283_gpio *st = gpiochip_get_data(gc);
+ unsigned int val, reg;
+ int ret, dir;
+
+ dir = ltc4283_gpio_get_direction(gc, off);
+ if (dir < 0)
+ return dir;
+
+ if (dir == GPIO_LINE_DIRECTION_IN) {
+ ret = regmap_read(st->regmap, LTC4283_INPUT_STATUS, &val);
+ if (ret)
+ return ret;
+
+ /* ADIO1 is at bit 3. */
+ if (off < LTC4283_PGIOX_START_NR)
+ return !!(val & BIT(3 - off));
+
+ /* PGIO1 is at bit 7. */
+ return !!(val & BIT(7 - (off - LTC4283_PGIOX_START_NR)));
+ }
+
+ if (off < LTC4283_PGIOX_START_NR)
+ reg = LTC4283_ADIO_CONFIG;
+ else
+ reg = LTC4283_PGIO_CONFIG_2;
+
+ ret = regmap_read(st->regmap, reg, &val);
+ if (ret)
+ return ret;
+
+ return !!(val & BIT(off));
+}
+
+static int ltc4283_gpio_set_value(struct gpio_chip *gc, unsigned int off, int val)
+{
+ struct ltc4283_gpio *st = gpiochip_get_data(gc);
+
+ return __ltc4283_gpio_set_value(st, off, val);
+}
+
+static int ltc4283_init_valid_mask(struct gpio_chip *gc, unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ unsigned long *mask = dev_get_platdata(gc->parent);
+
+ bitmap_copy(valid_mask, mask, ngpios);
+ return 0;
+}
+
+static int ltc4283_gpio_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct ltc4283_gpio *st;
+ struct gpio_chip *gc;
+
+ st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ st->regmap = dev_get_regmap(dev->parent, NULL);
+ if (!st->regmap)
+ return dev_err_probe(dev, -ENODEV,
+ "Failed to get regmap\n");
+
+ gc = &st->gpio_chip;
+ gc->parent = dev;
+ gc->get_direction = ltc4283_gpio_get_direction;
+ gc->direction_input = ltc4283_gpio_direction_input;
+ gc->direction_output = ltc4283_gpio_direction_output;
+ gc->get = ltc4283_gpio_get_value;
+ gc->set = ltc4283_gpio_set_value;
+ gc->init_valid_mask = ltc4283_init_valid_mask;
+ gc->can_sleep = true;
+
+ gc->base = -1;
+ gc->ngpio = LTC4283_PINS_MAX;
+ gc->label = adev->name;
+ gc->owner = THIS_MODULE;
+
+ return devm_gpiochip_add_data(dev, &st->gpio_chip, st);
+}
+
+static const struct auxiliary_device_id ltc4283_aux_id_table[] = {
+ { "ltc4283.gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(auxiliary, ltc4283_aux_id_table);
+
+static struct auxiliary_driver ltc4283_gpio_driver = {
+ .probe = ltc4283_gpio_probe,
+ .id_table = ltc4283_aux_id_table,
+};
+module_auxiliary_driver(ltc4283_gpio_driver);
+
+MODULE_AUTHOR("Nuno Sá <nuno.sa@analog.com>");
+MODULE_DESCRIPTION("GPIO LTC4283 Driver");
+MODULE_LICENSE("GPL");