| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (C) 2025 Linaro Ltd. | |
| */ | |
| #include <linux/auxiliary_bus.h> | |
| #include <linux/cleanup.h> | |
| #include <linux/device.h> | |
| #include <linux/err.h> | |
| #include <linux/gpio/consumer.h> | |
| #include <linux/gpio/driver.h> | |
| #include <linux/lockdep.h> | |
| #include <linux/module.h> | |
| #include <linux/mutex.h> | |
| #include <linux/string_choices.h> | |
| #include <linux/types.h> | |
| #include "gpiolib-shared.h" | |
| struct gpio_shared_proxy_data { | |
| struct gpio_chip gc; | |
| struct gpio_shared_desc *shared_desc; | |
| struct device *dev; | |
| bool voted_change; | |
| }; | |
| static int | |
| gpio_shared_proxy_set_unlocked(struct gpio_shared_proxy_data *proxy, int value) | |
| { | |
| struct gpio_shared_desc *shared_desc = proxy->shared_desc; | |
| struct gpio_desc *desc = shared_desc->desc; | |
| int ret = 0; | |
| lockdep_assert_held(&shared_desc->mutex); | |
| if (value != shared_desc->def_val) { | |
| /* User wants to vote for a value change. */ | |
| if (proxy->voted_change) | |
| /* Already voted for a change, nothing to do. */ | |
| goto out; | |
| /* Haven't voted for a value change yet. */ | |
| if (!shared_desc->votecnt) { | |
| /* | |
| * Current value is default, need to actually set value | |
| * to the opposite. | |
| */ | |
| ret = gpiod_set_value_cansleep(desc, value); | |
| if (ret) | |
| goto out; | |
| } | |
| shared_desc->votecnt++; | |
| proxy->voted_change = true; | |
| goto out; | |
| } | |
| /* Desired value is the default. */ | |
| if (!proxy->voted_change) | |
| /* We didn't vote for change previously, nothing to do. */ | |
| goto out; | |
| /* We previously voted for change. */ | |
| if (shared_desc->votecnt == 1) { | |
| /* This is the last remaining vote for change, set value to default. */ | |
| ret = gpiod_set_value_cansleep(desc, shared_desc->def_val); | |
| if (ret) | |
| goto out; | |
| } | |
| shared_desc->votecnt--; | |
| proxy->voted_change = false; | |
| out: | |
| if (shared_desc->votecnt) | |
| dev_dbg(proxy->dev, | |
| "Voted for value '%s', effective value is '%s', number of votes: %u\n", | |
| str_high_low(value), str_high_low(!shared_desc->def_val), | |
| shared_desc->votecnt); | |
| else | |
| dev_dbg(proxy->dev, "Voted for value '%s', effective value is '%s'\n", | |
| str_high_low(value), str_high_low(shared_desc->def_val)); | |
| return ret; | |
| } | |
| static int gpio_shared_proxy_request(struct gpio_chip *gc, unsigned int offset) | |
| { | |
| struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc); | |
| struct gpio_shared_desc *shared_desc = proxy->shared_desc; | |
| guard(mutex)(&shared_desc->mutex); | |
| proxy->shared_desc->usecnt++; | |
| dev_dbg(proxy->dev, "Shared GPIO requested, number of users: %u\n", | |
| proxy->shared_desc->usecnt); | |
| return 0; | |
| } | |
| static void gpio_shared_proxy_free(struct gpio_chip *gc, unsigned int offset) | |
| { | |
| struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc); | |
| struct gpio_shared_desc *shared_desc = proxy->shared_desc; | |
| int ret; | |
| guard(mutex)(&shared_desc->mutex); | |
| if (proxy->voted_change) { | |
| ret = gpio_shared_proxy_set_unlocked(proxy, shared_desc->def_val); | |
| if (ret) | |
| dev_err(proxy->dev, | |
| "Failed to unset the shared GPIO value on release: %d\n", ret); | |
| } | |
| proxy->shared_desc->usecnt--; | |
| dev_dbg(proxy->dev, "Shared GPIO freed, number of users: %u\n", | |
| proxy->shared_desc->usecnt); | |
| } | |
| static int gpio_shared_proxy_set_config(struct gpio_chip *gc, | |
| unsigned int offset, unsigned long cfg) | |
| { | |
| struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc); | |
| struct gpio_shared_desc *shared_desc = proxy->shared_desc; | |
| struct gpio_desc *desc = shared_desc->desc; | |
| int ret; | |
| guard(mutex)(&shared_desc->mutex); | |
| if (shared_desc->usecnt > 1) { | |
| if (shared_desc->cfg != cfg) { | |
| dev_dbg(proxy->dev, | |
| "Shared GPIO's configuration already set, accepting changes but users may conflict!!\n"); | |
| } else { | |
| dev_dbg(proxy->dev, "Equal config requested, nothing to do\n"); | |
| return 0; | |
| } | |
| } | |
| ret = gpiod_set_config(desc, cfg); | |
| if (ret && ret != -ENOTSUPP) | |
| return ret; | |
| shared_desc->cfg = cfg; | |
| return 0; | |
| } | |
| static int gpio_shared_proxy_direction_input(struct gpio_chip *gc, | |
| unsigned int offset) | |
| { | |
| struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc); | |
| struct gpio_shared_desc *shared_desc = proxy->shared_desc; | |
| struct gpio_desc *desc = shared_desc->desc; | |
| int dir; | |
| guard(mutex)(&shared_desc->mutex); | |
| if (shared_desc->usecnt == 1) { | |
| dev_dbg(proxy->dev, | |
| "Only one user of this shared GPIO, allowing to set direction to input\n"); | |
| return gpiod_direction_input(desc); | |
| } | |
| dir = gpiod_get_direction(desc); | |
| if (dir < 0) | |
| return dir; | |
| if (dir == GPIO_LINE_DIRECTION_OUT) { | |
| dev_dbg(proxy->dev, | |
| "Shared GPIO's direction already set to output, refusing to change\n"); | |
| return -EPERM; | |
| } | |
| return 0; | |
| } | |
| static int gpio_shared_proxy_direction_output(struct gpio_chip *gc, | |
| unsigned int offset, int value) | |
| { | |
| struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc); | |
| struct gpio_shared_desc *shared_desc = proxy->shared_desc; | |
| struct gpio_desc *desc = shared_desc->desc; | |
| int ret, dir; | |
| guard(mutex)(&shared_desc->mutex); | |
| if (shared_desc->usecnt == 1) { | |
| dev_dbg(proxy->dev, | |
| "Only one user of this shared GPIO, allowing to set direction to output with value '%s'\n", | |
| str_high_low(value)); | |
| ret = gpiod_direction_output(desc, value); | |
| if (ret) | |
| return ret; | |
| shared_desc->def_val = value; | |
| shared_desc->votecnt = 0; | |
| proxy->voted_change = false; | |
| return 0; | |
| } | |
| dir = gpiod_get_direction(desc); | |
| if (dir < 0) | |
| return dir; | |
| if (dir == GPIO_LINE_DIRECTION_IN) { | |
| dev_dbg(proxy->dev, | |
| "Shared GPIO's direction already set to input, refusing to change\n"); | |
| return -EPERM; | |
| } | |
| return gpio_shared_proxy_set_unlocked(proxy, value); | |
| } | |
| static int gpio_shared_proxy_get_cansleep(struct gpio_chip *gc, | |
| unsigned int offset) | |
| { | |
| struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc); | |
| return gpiod_get_value_cansleep(proxy->shared_desc->desc); | |
| } | |
| static int gpio_shared_proxy_set_cansleep(struct gpio_chip *gc, | |
| unsigned int offset, int value) | |
| { | |
| struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc); | |
| guard(mutex)(&proxy->shared_desc->mutex); | |
| return gpio_shared_proxy_set_unlocked(proxy, value); | |
| } | |
| static int gpio_shared_proxy_get_direction(struct gpio_chip *gc, | |
| unsigned int offset) | |
| { | |
| struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc); | |
| return gpiod_get_direction(proxy->shared_desc->desc); | |
| } | |
| static int gpio_shared_proxy_to_irq(struct gpio_chip *gc, unsigned int offset) | |
| { | |
| struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc); | |
| return gpiod_to_irq(proxy->shared_desc->desc); | |
| } | |
| static int gpio_shared_proxy_probe(struct auxiliary_device *adev, | |
| const struct auxiliary_device_id *id) | |
| { | |
| struct gpio_shared_proxy_data *proxy; | |
| struct gpio_shared_desc *shared_desc; | |
| struct device *dev = &adev->dev; | |
| struct gpio_chip *gc; | |
| shared_desc = devm_gpiod_shared_get(dev); | |
| if (IS_ERR(shared_desc)) | |
| return PTR_ERR(shared_desc); | |
| proxy = devm_kzalloc(dev, sizeof(*proxy), GFP_KERNEL); | |
| if (!proxy) | |
| return -ENOMEM; | |
| proxy->shared_desc = shared_desc; | |
| proxy->dev = dev; | |
| gc = &proxy->gc; | |
| gc->base = -1; | |
| gc->ngpio = 1; | |
| gc->label = dev_name(dev); | |
| gc->parent = dev; | |
| gc->owner = THIS_MODULE; | |
| /* | |
| * Under the descriptor mutex the proxy may call | |
| * gpiod_set_config()/gpiod_direction_*(), which can reach pinctrl | |
| * paths that take a mutex (e.g. gpiod_set_config() -> | |
| * gpiochip_generic_config() -> pinctrl_gpio_set_config()), independent | |
| * of the underlying chip's can_sleep. So the descriptor lock must be a | |
| * mutex and the proxy gpiochip is therefore always sleeping; drive the | |
| * underlying GPIO through the cansleep value accessors, which are valid | |
| * for both sleeping and non-sleeping chips. | |
| */ | |
| gc->can_sleep = true; | |
| gc->request = gpio_shared_proxy_request; | |
| gc->free = gpio_shared_proxy_free; | |
| gc->set_config = gpio_shared_proxy_set_config; | |
| gc->direction_input = gpio_shared_proxy_direction_input; | |
| gc->direction_output = gpio_shared_proxy_direction_output; | |
| gc->set = gpio_shared_proxy_set_cansleep; | |
| gc->get = gpio_shared_proxy_get_cansleep; | |
| gc->get_direction = gpio_shared_proxy_get_direction; | |
| gc->to_irq = gpio_shared_proxy_to_irq; | |
| return devm_gpiochip_add_data(dev, &proxy->gc, proxy); | |
| } | |
| static const struct auxiliary_device_id gpio_shared_proxy_id_table[] = { | |
| { .name = "gpiolib_shared.proxy" }, | |
| {}, | |
| }; | |
| MODULE_DEVICE_TABLE(auxiliary, gpio_shared_proxy_id_table); | |
| static struct auxiliary_driver gpio_shared_proxy_driver = { | |
| .driver = { | |
| .name = "gpio-shared-proxy", | |
| .suppress_bind_attrs = true, | |
| }, | |
| .probe = gpio_shared_proxy_probe, | |
| .id_table = gpio_shared_proxy_id_table, | |
| }; | |
| module_auxiliary_driver(gpio_shared_proxy_driver); | |
| MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>"); | |
| MODULE_DESCRIPTION("Shared GPIO mux driver."); | |
| MODULE_LICENSE("GPL"); |