root/drivers/gpio/gpio-regmap.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * regmap based generic GPIO driver
 *
 * Copyright 2020 Michael Walle <michael@walle.cc>
 */

#include <linux/bits.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/types.h>

#include <linux/gpio/driver.h>
#include <linux/gpio/regmap.h>

#include "gpiolib.h"

struct gpio_regmap {
        struct device *parent;
        struct regmap *regmap;
        struct gpio_chip gpio_chip;

        int reg_stride;
        int ngpio_per_reg;
        unsigned int reg_dat_base;
        unsigned int reg_set_base;
        unsigned int reg_clr_base;
        unsigned int reg_dir_in_base;
        unsigned int reg_dir_out_base;
        unsigned long *fixed_direction_output;

#ifdef CONFIG_REGMAP_IRQ
        int regmap_irq_line;
        struct regmap_irq_chip_data *irq_chip_data;
#endif

        int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base,
                              unsigned int offset, unsigned int *reg,
                              unsigned int *mask);

        void *driver_data;
};

static unsigned int gpio_regmap_addr(unsigned int addr)
{
        if (addr == GPIO_REGMAP_ADDR_ZERO)
                return 0;

        return addr;
}

static int gpio_regmap_simple_xlate(struct gpio_regmap *gpio,
                                    unsigned int base, unsigned int offset,
                                    unsigned int *reg, unsigned int *mask)
{
        unsigned int line = offset % gpio->ngpio_per_reg;
        unsigned int stride = offset / gpio->ngpio_per_reg;

        *reg = base + stride * gpio->reg_stride;
        *mask = BIT(line);

        return 0;
}

static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset)
{
        struct gpio_regmap *gpio = gpiochip_get_data(chip);
        unsigned int base, val, reg, mask;
        int ret;

        /* we might not have an output register if we are input only */
        if (gpio->reg_dat_base)
                base = gpio_regmap_addr(gpio->reg_dat_base);
        else
                base = gpio_regmap_addr(gpio->reg_set_base);

        ret = gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
        if (ret)
                return ret;

        /* 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);
        if (ret)
                return ret;

        return !!(val & mask);
}

static int gpio_regmap_set(struct gpio_chip *chip, unsigned int offset,
                           int val)
{
        struct gpio_regmap *gpio = gpiochip_get_data(chip);
        unsigned int base = gpio_regmap_addr(gpio->reg_set_base);
        unsigned int reg, mask, mask_val;
        int ret;

        ret = gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
        if (ret)
                return ret;

        if (val)
                mask_val = mask;
        else
                mask_val = 0;

        /* ignore input values which shadow the old output value */
        if (gpio->reg_dat_base == gpio->reg_set_base)
                ret = regmap_write_bits(gpio->regmap, reg, mask, mask_val);
        else
                ret = regmap_update_bits(gpio->regmap, reg, mask, mask_val);

        return ret;
}

static int gpio_regmap_set_with_clear(struct gpio_chip *chip,
                                      unsigned int offset, int val)
{
        struct gpio_regmap *gpio = gpiochip_get_data(chip);
        unsigned int base, reg, mask;
        int ret;

        if (val)
                base = gpio_regmap_addr(gpio->reg_set_base);
        else
                base = gpio_regmap_addr(gpio->reg_clr_base);

        ret = gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
        if (ret)
                return ret;

        return regmap_write(gpio->regmap, reg, mask);
}

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;
        int invert, ret;

        if (gpio->fixed_direction_output) {
                if (test_bit(offset, gpio->fixed_direction_output))
                        return GPIO_LINE_DIRECTION_OUT;
                else
                        return GPIO_LINE_DIRECTION_IN;
        }

        if (gpio->reg_dat_base && !gpio->reg_set_base)
                return GPIO_LINE_DIRECTION_IN;
        if (gpio->reg_set_base && !gpio->reg_dat_base)
                return GPIO_LINE_DIRECTION_OUT;

        if (gpio->reg_dir_out_base) {
                base = gpio_regmap_addr(gpio->reg_dir_out_base);
                invert = 0;
        } else if (gpio->reg_dir_in_base) {
                base = gpio_regmap_addr(gpio->reg_dir_in_base);
                invert = 1;
        } else {
                return -ENOTSUPP;
        }

        ret = gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
        if (ret)
                return ret;

        ret = regmap_read(gpio->regmap, reg, &val);
        if (ret)
                return ret;

        if (!!(val & mask) ^ invert)
                return GPIO_LINE_DIRECTION_OUT;
        else
                return GPIO_LINE_DIRECTION_IN;
}

static int gpio_regmap_set_direction(struct gpio_chip *chip,
                                     unsigned int offset, bool output)
{
        struct gpio_regmap *gpio = gpiochip_get_data(chip);
        unsigned int base, val, reg, mask;
        int invert, ret;

        if (gpio->reg_dir_out_base) {
                base = gpio_regmap_addr(gpio->reg_dir_out_base);
                invert = 0;
        } else if (gpio->reg_dir_in_base) {
                base = gpio_regmap_addr(gpio->reg_dir_in_base);
                invert = 1;
        } else {
                return -ENOTSUPP;
        }

        ret = gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
        if (ret)
                return ret;

        if (invert)
                val = output ? 0 : mask;
        else
                val = output ? mask : 0;

        return regmap_update_bits(gpio->regmap, reg, mask, val);
}

static int gpio_regmap_direction_input(struct gpio_chip *chip,
                                       unsigned int offset)
{
        return gpio_regmap_set_direction(chip, offset, false);
}

static int gpio_regmap_direction_output(struct gpio_chip *chip,
                                        unsigned int offset, int value)
{
        gpio_regmap_set(chip, offset, value);

        return gpio_regmap_set_direction(chip, offset, true);
}

void *gpio_regmap_get_drvdata(struct gpio_regmap *gpio)
{
        return gpio->driver_data;
}
EXPORT_SYMBOL_GPL(gpio_regmap_get_drvdata);

/**
 * gpio_regmap_register() - Register a generic regmap GPIO controller
 * @config: configuration for gpio_regmap
 *
 * Return: A pointer to the registered gpio_regmap or ERR_PTR error value.
 */
struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config)
{
        struct irq_domain *irq_domain;
        struct gpio_regmap *gpio;
        struct gpio_chip *chip;
        int ret;

        if (!config->parent)
                return ERR_PTR(-EINVAL);

        /* we need at least one */
        if (!config->reg_dat_base && !config->reg_set_base)
                return ERR_PTR(-EINVAL);

        /* if we have a direction register we need both input and output */
        if ((config->reg_dir_out_base || config->reg_dir_in_base) &&
            (!config->reg_dat_base || !config->reg_set_base))
                return ERR_PTR(-EINVAL);

        /* we don't support having both registers simultaneously for now */
        if (config->reg_dir_out_base && config->reg_dir_in_base)
                return ERR_PTR(-EINVAL);

        gpio = kzalloc_obj(*gpio);
        if (!gpio)
                return ERR_PTR(-ENOMEM);

        gpio->parent = config->parent;
        gpio->driver_data = config->drvdata;
        gpio->regmap = config->regmap;
        gpio->reg_dat_base = config->reg_dat_base;
        gpio->reg_set_base = config->reg_set_base;
        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;

        chip = &gpio->gpio_chip;
        chip->parent = config->parent;
        chip->fwnode = config->fwnode;
        chip->base = -1;
        chip->names = config->names;
        chip->label = config->label ?: dev_name(config->parent);
        chip->can_sleep = regmap_might_sleep(config->regmap);
        chip->init_valid_mask = config->init_valid_mask;

        chip->request = gpiochip_generic_request;
        chip->free = gpiochip_generic_free;
        chip->get = gpio_regmap_get;
        if (gpio->reg_set_base && gpio->reg_clr_base)
                chip->set = gpio_regmap_set_with_clear;
        else if (gpio->reg_set_base)
                chip->set = gpio_regmap_set;

        chip->get_direction = gpio_regmap_get_direction;
        if (gpio->reg_dir_in_base || gpio->reg_dir_out_base) {
                chip->direction_input = gpio_regmap_direction_input;
                chip->direction_output = gpio_regmap_direction_output;
        }

        chip->ngpio = config->ngpio;
        if (!chip->ngpio) {
                ret = gpiochip_get_ngpios(chip, chip->parent);
                if (ret)
                        goto err_free_gpio;
        }

        if (config->fixed_direction_output) {
                gpio->fixed_direction_output = bitmap_alloc(chip->ngpio,
                                                            GFP_KERNEL);
                if (!gpio->fixed_direction_output) {
                        ret = -ENOMEM;
                        goto err_free_gpio;
                }
                bitmap_copy(gpio->fixed_direction_output,
                            config->fixed_direction_output, chip->ngpio);
        }

        /* if not set, assume there is only one register */
        gpio->ngpio_per_reg = config->ngpio_per_reg;
        if (!gpio->ngpio_per_reg)
                gpio->ngpio_per_reg = config->ngpio;

        /* if not set, assume they are consecutive */
        gpio->reg_stride = config->reg_stride;
        if (!gpio->reg_stride)
                gpio->reg_stride = 1;

        gpio->reg_mask_xlate = config->reg_mask_xlate;
        if (!gpio->reg_mask_xlate)
                gpio->reg_mask_xlate = gpio_regmap_simple_xlate;

        ret = gpiochip_add_data(chip, gpio);
        if (ret < 0)
                goto err_free_bitmap;

#ifdef CONFIG_REGMAP_IRQ
        if (config->regmap_irq_chip) {
                gpio->regmap_irq_line = config->regmap_irq_line;
                ret = regmap_add_irq_chip_fwnode(dev_fwnode(config->parent), config->regmap,
                                                 config->regmap_irq_line, config->regmap_irq_flags,
                                                 0, config->regmap_irq_chip, &gpio->irq_chip_data);
                if (ret)
                        goto err_remove_gpiochip;

                irq_domain = regmap_irq_get_domain(gpio->irq_chip_data);
        } else
#endif
        irq_domain = config->irq_domain;

        if (irq_domain) {
                ret = gpiochip_irqchip_add_domain(chip, irq_domain);
                if (ret)
                        goto err_remove_gpiochip;
        }

        return gpio;

err_remove_gpiochip:
        gpiochip_remove(chip);
err_free_bitmap:
        bitmap_free(gpio->fixed_direction_output);
err_free_gpio:
        kfree(gpio);
        return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(gpio_regmap_register);

/**
 * gpio_regmap_unregister() - Unregister a generic regmap GPIO controller
 * @gpio: gpio_regmap device to unregister
 */
void gpio_regmap_unregister(struct gpio_regmap *gpio)
{
#ifdef CONFIG_REGMAP_IRQ
        if (gpio->irq_chip_data)
                regmap_del_irq_chip(gpio->regmap_irq_line, gpio->irq_chip_data);
#endif

        gpiochip_remove(&gpio->gpio_chip);
        bitmap_free(gpio->fixed_direction_output);
        kfree(gpio);
}
EXPORT_SYMBOL_GPL(gpio_regmap_unregister);

static void devm_gpio_regmap_unregister(void *res)
{
        gpio_regmap_unregister(res);
}

/**
 * devm_gpio_regmap_register() - resource managed gpio_regmap_register()
 * @dev: device that is registering this GPIO device
 * @config: configuration for gpio_regmap
 *
 * Managed gpio_regmap_register(). For generic regmap GPIO device registered by
 * this function, gpio_regmap_unregister() is automatically called on driver
 * detach. See gpio_regmap_register() for more information.
 *
 * Return: A pointer to the registered gpio_regmap or ERR_PTR error value.
 */
struct gpio_regmap *devm_gpio_regmap_register(struct device *dev,
                                              const struct gpio_regmap_config *config)
{
        struct gpio_regmap *gpio;
        int ret;

        gpio = gpio_regmap_register(config);

        if (IS_ERR(gpio))
                return gpio;

        ret = devm_add_action_or_reset(dev, devm_gpio_regmap_unregister, gpio);
        if (ret)
                return ERR_PTR(ret);

        return gpio;
}
EXPORT_SYMBOL_GPL(devm_gpio_regmap_register);

MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
MODULE_DESCRIPTION("GPIO generic regmap driver core");
MODULE_LICENSE("GPL");