| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (C) 2025 Linaro Ltd. | |
| */ | |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
| #include <linux/auxiliary_bus.h> | |
| #include <linux/cleanup.h> | |
| #include <linux/device.h> | |
| #include <linux/fwnode.h> | |
| #include <linux/gpio/consumer.h> | |
| #include <linux/gpio/machine.h> | |
| #include <linux/idr.h> | |
| #include <linux/kref.h> | |
| #include <linux/list.h> | |
| #include <linux/lockdep.h> | |
| #include <linux/module.h> | |
| #include <linux/mutex.h> | |
| #include <linux/of.h> | |
| #include <linux/overflow.h> | |
| #include <linux/printk.h> | |
| #include <linux/property.h> | |
| #include <linux/slab.h> | |
| #include <linux/string.h> | |
| #include "gpiolib.h" | |
| #include "gpiolib-shared.h" | |
| /* Represents a single reference to a GPIO pin. */ | |
| struct gpio_shared_ref { | |
| struct list_head list; | |
| /* Firmware node associated with this GPIO's consumer. */ | |
| struct fwnode_handle *fwnode; | |
| /* GPIO flags this consumer uses for the request. */ | |
| enum gpiod_flags flags; | |
| char *con_id; | |
| int dev_id; | |
| /* Protects the auxiliary device struct and the lookup table. */ | |
| struct mutex lock; | |
| struct lock_class_key lock_key; | |
| struct auxiliary_device adev; | |
| struct gpiod_lookup_table *lookup; | |
| bool is_reset_gpio; | |
| }; | |
| /* Represents a single GPIO pin. */ | |
| struct gpio_shared_entry { | |
| struct list_head list; | |
| /* Firmware node associated with the GPIO controller. */ | |
| struct fwnode_handle *fwnode; | |
| /* Hardware offset of the GPIO within its chip. */ | |
| unsigned int offset; | |
| /* Index in the property value array. */ | |
| size_t index; | |
| /* Synchronizes the modification of shared_desc and offset. */ | |
| struct mutex lock; | |
| struct gpio_shared_desc *shared_desc; | |
| struct kref ref; | |
| struct list_head refs; | |
| }; | |
| static LIST_HEAD(gpio_shared_list); | |
| static DEFINE_IDA(gpio_shared_ida); | |
| #if IS_ENABLED(CONFIG_OF) | |
| static struct gpio_shared_entry * | |
| gpio_shared_find_entry(struct fwnode_handle *controller_node, | |
| unsigned int offset) | |
| { | |
| struct gpio_shared_entry *entry; | |
| list_for_each_entry(entry, &gpio_shared_list, list) { | |
| if (entry->fwnode == controller_node && entry->offset == offset) | |
| return entry; | |
| } | |
| return NULL; | |
| } | |
| static struct gpio_shared_ref *gpio_shared_make_ref(struct fwnode_handle *fwnode, | |
| const char *con_id, | |
| enum gpiod_flags flags) | |
| { | |
| char *con_id_cpy __free(kfree) = NULL; | |
| struct gpio_shared_ref *ref __free(kfree) = kzalloc_obj(*ref); | |
| if (!ref) | |
| return NULL; | |
| if (con_id) { | |
| con_id_cpy = kstrdup(con_id, GFP_KERNEL); | |
| if (!con_id_cpy) | |
| return NULL; | |
| } | |
| ref->dev_id = ida_alloc(&gpio_shared_ida, GFP_KERNEL); | |
| if (ref->dev_id < 0) | |
| return NULL; | |
| ref->flags = flags; | |
| ref->con_id = no_free_ptr(con_id_cpy); | |
| ref->fwnode = fwnode; | |
| lockdep_register_key(&ref->lock_key); | |
| mutex_init_with_key(&ref->lock, &ref->lock_key); | |
| return no_free_ptr(ref); | |
| } | |
| static int gpio_shared_setup_reset_proxy(struct gpio_shared_entry *entry, | |
| enum gpiod_flags flags) | |
| { | |
| struct gpio_shared_ref *ref; | |
| list_for_each_entry(ref, &entry->refs, list) { | |
| if (ref->is_reset_gpio) | |
| /* Already set-up. */ | |
| return 0; | |
| } | |
| ref = gpio_shared_make_ref(NULL, "reset", flags); | |
| if (!ref) | |
| return -ENOMEM; | |
| ref->is_reset_gpio = true; | |
| list_add_tail(&ref->list, &entry->refs); | |
| pr_debug("Created a secondary shared GPIO reference for potential reset-gpio device for GPIO %u at %s\n", | |
| entry->offset, fwnode_get_name(entry->fwnode)); | |
| return 0; | |
| } | |
| /* Handle all special nodes that we should ignore. */ | |
| static bool gpio_shared_of_node_ignore(struct device_node *node) | |
| { | |
| /* Ignore disabled devices. */ | |
| if (!of_device_is_available(node)) | |
| return true; | |
| /* | |
| * __symbols__ is a special, internal node and should not be considered | |
| * when scanning for shared GPIOs. | |
| */ | |
| if (of_node_name_eq(node, "__symbols__")) | |
| return true; | |
| /* | |
| * GPIO hogs have a "gpios" property which is not a phandle and can't | |
| * possibly refer to a shared GPIO. | |
| */ | |
| if (of_property_present(node, "gpio-hog")) | |
| return true; | |
| return false; | |
| } | |
| static int gpio_shared_of_traverse(struct device_node *curr) | |
| { | |
| struct gpio_shared_entry *entry; | |
| size_t con_id_len, suffix_len; | |
| struct fwnode_handle *fwnode; | |
| struct of_phandle_args args; | |
| struct gpio_shared_ref *ref; | |
| struct property *prop; | |
| unsigned int offset; | |
| const char *suffix; | |
| int ret, count, i; | |
| if (gpio_shared_of_node_ignore(curr)) | |
| return 0; | |
| for_each_property_of_node(curr, prop) { | |
| /* | |
| * The standard name for a GPIO property is "foo-gpios" | |
| * or "foo-gpio". Some bindings also use "gpios" or "gpio". | |
| * There are some legacy device-trees which have a different | |
| * naming convention and for which we have rename quirks in | |
| * place in gpiolib-of.c. I don't think any of them require | |
| * support for shared GPIOs so for now let's just ignore | |
| * them. We can always just export the quirk list and | |
| * iterate over it here. | |
| */ | |
| if (!strends(prop->name, "-gpios") && | |
| !strends(prop->name, "-gpio") && | |
| strcmp(prop->name, "gpios") != 0 && | |
| strcmp(prop->name, "gpio") != 0) | |
| continue; | |
| count = of_count_phandle_with_args(curr, prop->name, | |
| "#gpio-cells"); | |
| if (count <= 0) | |
| continue; | |
| for (i = 0; i < count; i++) { | |
| struct device_node *np __free(device_node) = NULL; | |
| char *con_id __free(kfree) = NULL; | |
| ret = of_parse_phandle_with_args(curr, prop->name, | |
| "#gpio-cells", i, | |
| &args); | |
| if (ret) | |
| continue; | |
| np = args.np; | |
| if (!of_property_present(np, "gpio-controller")) | |
| continue; | |
| /* | |
| * We support 1, 2 and 3 cell GPIO bindings in the | |
| * kernel currently. There's only one old MIPS dts that | |
| * has a one-cell binding but there's no associated | |
| * consumer so it may as well be an error. There don't | |
| * seem to be any 3-cell users of non-exclusive GPIOs, | |
| * so we can skip this as well. Let's occupy ourselves | |
| * with the predominant 2-cell binding with the first | |
| * cell indicating the hardware offset of the GPIO and | |
| * the second defining the GPIO flags of the request. | |
| */ | |
| if (args.args_count != 2) | |
| continue; | |
| fwnode = of_fwnode_handle(args.np); | |
| offset = args.args[0]; | |
| entry = gpio_shared_find_entry(fwnode, offset); | |
| if (!entry) { | |
| entry = kzalloc_obj(*entry); | |
| if (!entry) | |
| return -ENOMEM; | |
| entry->fwnode = fwnode_handle_get(fwnode); | |
| entry->offset = offset; | |
| entry->index = count; | |
| INIT_LIST_HEAD(&entry->refs); | |
| mutex_init(&entry->lock); | |
| list_add_tail(&entry->list, &gpio_shared_list); | |
| } | |
| if (strends(prop->name, "gpios")) | |
| suffix = "-gpios"; | |
| else if (strends(prop->name, "gpio")) | |
| suffix = "-gpio"; | |
| else | |
| suffix = NULL; | |
| if (!suffix) | |
| continue; | |
| /* We only set con_id if there's actually one. */ | |
| if (strcmp(prop->name, "gpios") && strcmp(prop->name, "gpio")) { | |
| con_id = kstrdup(prop->name, GFP_KERNEL); | |
| if (!con_id) | |
| return -ENOMEM; | |
| con_id_len = strlen(con_id); | |
| suffix_len = strlen(suffix); | |
| con_id[con_id_len - suffix_len] = '\0'; | |
| } | |
| ref = gpio_shared_make_ref(fwnode_handle_get(of_fwnode_handle(curr)), | |
| con_id, args.args[1]); | |
| if (!ref) | |
| return -ENOMEM; | |
| if (!list_empty(&entry->refs)) | |
| pr_debug("GPIO %u at %s is shared by multiple firmware nodes\n", | |
| entry->offset, fwnode_get_name(entry->fwnode)); | |
| list_add_tail(&ref->list, &entry->refs); | |
| if (strcmp(prop->name, "reset-gpios") == 0) { | |
| ret = gpio_shared_setup_reset_proxy(entry, args.args[1]); | |
| if (ret) | |
| return ret; | |
| } | |
| } | |
| } | |
| for_each_child_of_node_scoped(curr, child) { | |
| ret = gpio_shared_of_traverse(child); | |
| if (ret) | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static int gpio_shared_of_scan(void) | |
| { | |
| if (of_root) | |
| return gpio_shared_of_traverse(of_root); | |
| return 0; | |
| } | |
| #else | |
| static int gpio_shared_of_scan(void) | |
| { | |
| return 0; | |
| } | |
| #endif /* CONFIG_OF */ | |
| static void gpio_shared_adev_release(struct device *dev) | |
| { | |
| } | |
| static int gpio_shared_make_adev(struct gpio_device *gdev, | |
| struct gpio_shared_entry *entry, | |
| struct gpio_shared_ref *ref) | |
| { | |
| struct auxiliary_device *adev = &ref->adev; | |
| int ret; | |
| guard(mutex)(&ref->lock); | |
| memset(adev, 0, sizeof(*adev)); | |
| adev->id = ref->dev_id; | |
| adev->name = "proxy"; | |
| adev->dev.parent = gdev->dev.parent; | |
| adev->dev.platform_data = entry; | |
| adev->dev.release = gpio_shared_adev_release; | |
| ret = auxiliary_device_init(adev); | |
| if (ret) | |
| return ret; | |
| ret = auxiliary_device_add(adev); | |
| if (ret) { | |
| auxiliary_device_uninit(adev); | |
| return ret; | |
| } | |
| pr_debug("Created an auxiliary GPIO proxy %s for GPIO device %s\n", | |
| dev_name(&adev->dev), gpio_device_get_label(gdev)); | |
| return 0; | |
| } | |
| #if IS_ENABLED(CONFIG_RESET_GPIO) | |
| /* | |
| * Special case: reset-gpio is an auxiliary device that's created dynamically | |
| * and put in between the GPIO controller and consumers of shared GPIOs | |
| * referred to by the "reset-gpios" property. | |
| * | |
| * If the supposed consumer of a shared GPIO didn't match any of the mappings | |
| * we created when scanning the firmware nodes, it's still possible that it's | |
| * the reset-gpio device which didn't exist at the time of the scan. | |
| * | |
| * This function verifies it an return true if it's the case. | |
| */ | |
| static bool gpio_shared_dev_is_reset_gpio(struct device *consumer, | |
| struct gpio_shared_entry *entry, | |
| struct gpio_shared_ref *ref) | |
| { | |
| struct fwnode_handle *reset_fwnode = dev_fwnode(consumer); | |
| struct fwnode_reference_args ref_args, aux_args; | |
| struct device *parent = consumer->parent; | |
| struct gpio_shared_ref *real_ref; | |
| bool match; | |
| int ret; | |
| lockdep_assert_held(&ref->lock); | |
| /* The reset-gpio device must have a parent AND a firmware node. */ | |
| if (!parent || !reset_fwnode) | |
| return false; | |
| /* | |
| * Parent of the reset-gpio auxiliary device is the GPIO chip whose | |
| * fwnode we stored in the entry structure. | |
| */ | |
| if (!device_match_fwnode(parent, entry->fwnode)) | |
| return false; | |
| /* | |
| * Now we need to find the actual pin we want to assign to this | |
| * reset-gpio device. To that end: iterate over the list of references | |
| * of this entry and see if there's one, whose reset-gpios property's | |
| * arguments match the ones from this consumer's node. | |
| */ | |
| list_for_each_entry(real_ref, &entry->refs, list) { | |
| if (real_ref == ref) | |
| continue; | |
| guard(mutex)(&real_ref->lock); | |
| if (!real_ref->fwnode) | |
| continue; | |
| /* | |
| * The device associated with the shared reference's firmware | |
| * node is the consumer of the reset control exposed by the | |
| * reset-gpio device. It must have a "reset-gpios" property | |
| * that's referencing the entry's firmware node. | |
| * | |
| * The reference args must agree between the real consumer and | |
| * the auxiliary reset-gpio device. | |
| */ | |
| ret = fwnode_property_get_reference_args(real_ref->fwnode, | |
| "reset-gpios", | |
| NULL, 2, 0, &ref_args); | |
| if (ret) | |
| continue; | |
| ret = fwnode_property_get_reference_args(reset_fwnode, "reset-gpios", | |
| NULL, 2, 0, &aux_args); | |
| if (ret) { | |
| fwnode_handle_put(ref_args.fwnode); | |
| continue; | |
| } | |
| match = ((ref_args.fwnode == entry->fwnode) && | |
| (aux_args.fwnode == entry->fwnode) && | |
| (ref_args.args[0] == aux_args.args[0])); | |
| fwnode_handle_put(ref_args.fwnode); | |
| fwnode_handle_put(aux_args.fwnode); | |
| if (!match) | |
| continue; | |
| /* | |
| * Reuse the fwnode of the real device, next time we'll use it | |
| * in the normal path. | |
| */ | |
| ref->fwnode = fwnode_handle_get(reset_fwnode); | |
| return true; | |
| } | |
| return false; | |
| } | |
| #else | |
| static bool gpio_shared_dev_is_reset_gpio(struct device *consumer, | |
| struct gpio_shared_entry *entry, | |
| struct gpio_shared_ref *ref) | |
| { | |
| return false; | |
| } | |
| #endif /* CONFIG_RESET_GPIO */ | |
| int gpio_shared_add_proxy_lookup(struct device *consumer, struct fwnode_handle *fwnode, | |
| const char *con_id, unsigned long lflags) | |
| { | |
| const char *dev_id = dev_name(consumer); | |
| struct gpiod_lookup_table *lookup; | |
| struct gpio_shared_entry *entry; | |
| struct gpio_shared_ref *ref; | |
| list_for_each_entry(entry, &gpio_shared_list, list) { | |
| list_for_each_entry(ref, &entry->refs, list) { | |
| guard(mutex)(&ref->lock); | |
| if (!ref->fwnode && device_is_compatible(consumer, "reset-gpio")) { | |
| if (!gpio_shared_dev_is_reset_gpio(consumer, entry, ref)) | |
| continue; | |
| } else if (fwnode != ref->fwnode) { | |
| continue; | |
| } | |
| if ((!con_id && ref->con_id) || (con_id && !ref->con_id) || | |
| (con_id && ref->con_id && strcmp(con_id, ref->con_id) != 0)) | |
| continue; | |
| /* We've already done that on a previous request. */ | |
| if (ref->lookup) | |
| return 0; | |
| char *key __free(kfree) = | |
| kasprintf(GFP_KERNEL, | |
| KBUILD_MODNAME ".proxy.%u", | |
| ref->adev.id); | |
| if (!key) | |
| return -ENOMEM; | |
| lookup = kzalloc_flex(*lookup, table, 2); | |
| if (!lookup) | |
| return -ENOMEM; | |
| pr_debug("Adding machine lookup entry for a shared GPIO for consumer %s, with key '%s' and con_id '%s'\n", | |
| dev_id, key, ref->con_id ?: "none"); | |
| lookup->dev_id = dev_id; | |
| lookup->table[0] = GPIO_LOOKUP(no_free_ptr(key), 0, | |
| ref->con_id, lflags); | |
| ref->lookup = lookup; | |
| gpiod_add_lookup_table(ref->lookup); | |
| return 0; | |
| } | |
| } | |
| /* We warn here because this can only happen if the programmer borked. */ | |
| WARN_ON(1); | |
| return -ENOENT; | |
| } | |
| static void gpio_shared_remove_adev(struct auxiliary_device *adev) | |
| { | |
| auxiliary_device_delete(adev); | |
| auxiliary_device_uninit(adev); | |
| } | |
| int gpiochip_setup_shared(struct gpio_chip *gc) | |
| { | |
| struct gpio_device *gdev = gc->gpiodev; | |
| struct gpio_shared_entry *entry; | |
| struct gpio_shared_ref *ref; | |
| struct gpio_desc *desc; | |
| int ret; | |
| list_for_each_entry(entry, &gpio_shared_list, list) { | |
| list_for_each_entry(ref, &entry->refs, list) { | |
| if (gdev->dev.parent == &ref->adev.dev) { | |
| /* | |
| * This is a shared GPIO proxy. Mark its | |
| * descriptor as such and return here. | |
| */ | |
| __set_bit(GPIOD_FLAG_SHARED_PROXY, | |
| &gdev->descs[0].flags); | |
| return 0; | |
| } | |
| } | |
| } | |
| /* | |
| * This is not a shared GPIO proxy but it still may be the device | |
| * exposing shared pins. Find them and create the proxy devices. | |
| */ | |
| list_for_each_entry(entry, &gpio_shared_list, list) { | |
| if (!device_match_fwnode(&gdev->dev, entry->fwnode)) | |
| continue; | |
| if (list_count_nodes(&entry->refs) <= 1) | |
| continue; | |
| scoped_guard(mutex, &entry->lock) { | |
| #if IS_ENABLED(CONFIG_OF) | |
| if (is_of_node(entry->fwnode) && gc->of_xlate) { | |
| /* | |
| * This is the earliest that we can tranlate the | |
| * devicetree offset to the chip offset. | |
| */ | |
| struct of_phandle_args gpiospec = { }; | |
| gpiospec.np = to_of_node(entry->fwnode); | |
| gpiospec.args_count = 2; | |
| gpiospec.args[0] = entry->offset; | |
| ret = gc->of_xlate(gc, &gpiospec, NULL); | |
| if (ret < 0) | |
| return ret; | |
| entry->offset = ret; | |
| } | |
| #endif /* CONFIG_OF */ | |
| desc = &gdev->descs[entry->offset]; | |
| __set_bit(GPIOD_FLAG_SHARED, &desc->flags); | |
| /* | |
| * Shared GPIOs are not requested via the normal path. Make | |
| * them inaccessible to anyone even before we register the | |
| * chip. | |
| */ | |
| ret = gpiod_request_commit(desc, "shared"); | |
| if (ret) | |
| return ret; | |
| pr_debug("GPIO %u owned by %s is shared by multiple consumers\n", | |
| entry->offset, gpio_device_get_label(gdev)); | |
| } | |
| list_for_each_entry(ref, &entry->refs, list) { | |
| pr_debug("Setting up a shared GPIO entry for %s (con_id: '%s')\n", | |
| fwnode_get_name(ref->fwnode) ?: "(no fwnode)", | |
| ref->con_id ?: "(none)"); | |
| ret = gpio_shared_make_adev(gdev, entry, ref); | |
| if (ret) { | |
| gpiod_free_commit(desc); | |
| return ret; | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| void gpio_device_teardown_shared(struct gpio_device *gdev) | |
| { | |
| struct gpio_shared_entry *entry; | |
| struct gpio_shared_ref *ref; | |
| list_for_each_entry(entry, &gpio_shared_list, list) { | |
| if (!device_match_fwnode(&gdev->dev, entry->fwnode)) | |
| continue; | |
| scoped_guard(mutex, &entry->lock) | |
| gpiod_free_commit(&gdev->descs[entry->offset]); | |
| list_for_each_entry(ref, &entry->refs, list) { | |
| if (ref->lookup) { | |
| gpiod_remove_lookup_table(ref->lookup); | |
| kfree(ref->lookup->table[0].key); | |
| kfree(ref->lookup); | |
| ref->lookup = NULL; | |
| } | |
| gpio_shared_remove_adev(&ref->adev); | |
| } | |
| } | |
| } | |
| static void gpio_shared_release(struct kref *kref) | |
| { | |
| struct gpio_shared_entry *entry = | |
| container_of(kref, struct gpio_shared_entry, ref); | |
| struct gpio_shared_desc *shared_desc; | |
| guard(mutex)(&entry->lock); | |
| shared_desc = entry->shared_desc; | |
| gpio_device_put(shared_desc->desc->gdev); | |
| mutex_destroy(&shared_desc->mutex); | |
| kfree(shared_desc); | |
| entry->shared_desc = NULL; | |
| } | |
| static void gpiod_shared_put(void *data) | |
| { | |
| struct gpio_shared_entry *entry = data; | |
| kref_put(&entry->ref, gpio_shared_release); | |
| } | |
| static struct gpio_shared_desc * | |
| gpiod_shared_desc_create(struct gpio_shared_entry *entry) | |
| { | |
| struct gpio_shared_desc *shared_desc; | |
| struct gpio_device *gdev; | |
| lockdep_assert_held(&entry->lock); | |
| shared_desc = kzalloc_obj(*shared_desc); | |
| if (!shared_desc) | |
| return ERR_PTR(-ENOMEM); | |
| gdev = gpio_device_find_by_fwnode(entry->fwnode); | |
| if (!gdev) { | |
| kfree(shared_desc); | |
| return ERR_PTR(-EPROBE_DEFER); | |
| } | |
| shared_desc->desc = &gdev->descs[entry->offset]; | |
| mutex_init(&shared_desc->mutex); | |
| return shared_desc; | |
| } | |
| struct gpio_shared_desc *devm_gpiod_shared_get(struct device *dev) | |
| { | |
| struct gpio_shared_desc *shared_desc; | |
| struct gpio_shared_entry *entry; | |
| int ret; | |
| entry = dev_get_platdata(dev); | |
| if (WARN_ON(!entry)) | |
| /* Programmer bug */ | |
| return ERR_PTR(-ENOENT); | |
| scoped_guard(mutex, &entry->lock) { | |
| if (entry->shared_desc) { | |
| kref_get(&entry->ref); | |
| shared_desc = entry->shared_desc; | |
| } else { | |
| shared_desc = gpiod_shared_desc_create(entry); | |
| if (IS_ERR(shared_desc)) | |
| return ERR_CAST(shared_desc); | |
| kref_init(&entry->ref); | |
| entry->shared_desc = shared_desc; | |
| } | |
| pr_debug("Device %s acquired a reference to the shared GPIO %u owned by %s\n", | |
| dev_name(dev), gpiod_hwgpio(shared_desc->desc), | |
| gpio_device_get_label(shared_desc->desc->gdev)); | |
| } | |
| ret = devm_add_action_or_reset(dev, gpiod_shared_put, entry); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| return shared_desc; | |
| } | |
| EXPORT_SYMBOL_GPL(devm_gpiod_shared_get); | |
| static void gpio_shared_drop_ref(struct gpio_shared_ref *ref) | |
| { | |
| list_del(&ref->list); | |
| mutex_destroy(&ref->lock); | |
| lockdep_unregister_key(&ref->lock_key); | |
| kfree(ref->con_id); | |
| ida_free(&gpio_shared_ida, ref->dev_id); | |
| fwnode_handle_put(ref->fwnode); | |
| kfree(ref); | |
| } | |
| static void gpio_shared_drop_entry(struct gpio_shared_entry *entry) | |
| { | |
| list_del(&entry->list); | |
| mutex_destroy(&entry->lock); | |
| fwnode_handle_put(entry->fwnode); | |
| kfree(entry); | |
| } | |
| /* | |
| * This is only called if gpio_shared_init() fails so it's in fact __init and | |
| * not __exit. | |
| */ | |
| static void __init gpio_shared_teardown(void) | |
| { | |
| struct gpio_shared_entry *entry, *epos; | |
| struct gpio_shared_ref *ref, *rpos; | |
| list_for_each_entry_safe(entry, epos, &gpio_shared_list, list) { | |
| list_for_each_entry_safe(ref, rpos, &entry->refs, list) | |
| gpio_shared_drop_ref(ref); | |
| gpio_shared_drop_entry(entry); | |
| } | |
| } | |
| static bool gpio_shared_entry_is_really_shared(struct gpio_shared_entry *entry) | |
| { | |
| size_t num_nodes = list_count_nodes(&entry->refs); | |
| struct gpio_shared_ref *ref; | |
| if (num_nodes <= 1) | |
| return false; | |
| if (num_nodes > 2) | |
| return true; | |
| /* Exactly two references: */ | |
| list_for_each_entry(ref, &entry->refs, list) { | |
| /* | |
| * Corner-case: the second reference comes from the potential | |
| * reset-gpio instance. However, this pin is not really shared | |
| * as it would have three references in this case. Avoid | |
| * creating unnecessary proxies. | |
| */ | |
| if (ref->is_reset_gpio) | |
| return false; | |
| } | |
| return true; | |
| } | |
| static void gpio_shared_free_exclusive(void) | |
| { | |
| struct gpio_shared_entry *entry, *epos; | |
| struct gpio_shared_ref *ref, *rpos; | |
| list_for_each_entry_safe(entry, epos, &gpio_shared_list, list) { | |
| if (gpio_shared_entry_is_really_shared(entry)) | |
| continue; | |
| list_for_each_entry_safe(ref, rpos, &entry->refs, list) | |
| gpio_shared_drop_ref(ref); | |
| gpio_shared_drop_entry(entry); | |
| } | |
| } | |
| static int __init gpio_shared_init(void) | |
| { | |
| int ret; | |
| /* Right now, we only support OF-based systems. */ | |
| ret = gpio_shared_of_scan(); | |
| if (ret) { | |
| gpio_shared_teardown(); | |
| pr_err("Failed to scan OF nodes for shared GPIOs: %d\n", ret); | |
| return ret; | |
| } | |
| gpio_shared_free_exclusive(); | |
| pr_debug("Finished scanning firmware nodes for shared GPIOs\n"); | |
| return 0; | |
| } | |
| postcore_initcall(gpio_shared_init); |