blob: 88aa6216006a90663253350d9411286ee2168bf3 [file] [edit]
// 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");