|  | // 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> | 
|  |  | 
|  | 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; | 
|  |  | 
|  | 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, ®, &mask); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_read(gpio->regmap, reg, &val); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | return !!(val & mask); | 
|  | } | 
|  |  | 
|  | static void 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; | 
|  |  | 
|  | gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); | 
|  | if (val) | 
|  | regmap_update_bits(gpio->regmap, reg, mask, mask); | 
|  | else | 
|  | regmap_update_bits(gpio->regmap, reg, mask, 0); | 
|  | } | 
|  |  | 
|  | static void 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; | 
|  |  | 
|  | if (val) | 
|  | base = gpio_regmap_addr(gpio->reg_set_base); | 
|  | else | 
|  | base = gpio_regmap_addr(gpio->reg_clr_base); | 
|  |  | 
|  | gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); | 
|  | 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->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, ®, &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, ®, &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 gpio_regmap *gpio; | 
|  | struct gpio_chip *chip; | 
|  | int ret; | 
|  |  | 
|  | if (!config->parent) | 
|  | return ERR_PTR(-EINVAL); | 
|  |  | 
|  | if (!config->ngpio) | 
|  | 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(sizeof(*gpio), GFP_KERNEL); | 
|  | if (!gpio) | 
|  | return ERR_PTR(-ENOMEM); | 
|  |  | 
|  | gpio->parent = config->parent; | 
|  | gpio->driver_data = config->drvdata; | 
|  | gpio->regmap = config->regmap; | 
|  | gpio->ngpio_per_reg = config->ngpio_per_reg; | 
|  | gpio->reg_stride = config->reg_stride; | 
|  | gpio->reg_mask_xlate = config->reg_mask_xlate; | 
|  | 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; | 
|  |  | 
|  | /* if not set, assume there is only one register */ | 
|  | if (!gpio->ngpio_per_reg) | 
|  | gpio->ngpio_per_reg = config->ngpio; | 
|  |  | 
|  | /* if not set, assume they are consecutive */ | 
|  | if (!gpio->reg_stride) | 
|  | gpio->reg_stride = 1; | 
|  |  | 
|  | if (!gpio->reg_mask_xlate) | 
|  | gpio->reg_mask_xlate = gpio_regmap_simple_xlate; | 
|  |  | 
|  | chip = &gpio->gpio_chip; | 
|  | chip->parent = config->parent; | 
|  | chip->fwnode = config->fwnode; | 
|  | chip->base = -1; | 
|  | chip->ngpio = config->ngpio; | 
|  | chip->names = config->names; | 
|  | chip->label = config->label ?: dev_name(config->parent); | 
|  | chip->can_sleep = regmap_might_sleep(config->regmap); | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | ret = gpiochip_add_data(chip, gpio); | 
|  | if (ret < 0) | 
|  | goto err_free_gpio; | 
|  |  | 
|  | if (config->irq_domain) { | 
|  | ret = gpiochip_irqchip_add_domain(chip, config->irq_domain); | 
|  | if (ret) | 
|  | goto err_remove_gpiochip; | 
|  | } | 
|  |  | 
|  | return gpio; | 
|  |  | 
|  | err_remove_gpiochip: | 
|  | gpiochip_remove(chip); | 
|  | 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) | 
|  | { | 
|  | gpiochip_remove(&gpio->gpio_chip); | 
|  | 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"); |