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