// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2025 Linaro Ltd. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; struct auxiliary_device adev; struct gpiod_lookup_table *lookup; }; /* 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; struct mutex lock; struct gpio_shared_desc *shared_desc; struct kref ref; struct list_head refs; }; static LIST_HEAD(gpio_shared_list); static DEFINE_MUTEX(gpio_shared_lock); 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; } /* Handle all special nodes that we should ignore. */ static bool gpio_shared_of_node_ignore(struct device_node *node) { /* * __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 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; 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(sizeof(*entry), GFP_KERNEL); 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); } struct gpio_shared_ref *ref __free(kfree) = kzalloc(sizeof(*ref), GFP_KERNEL); if (!ref) return -ENOMEM; ref->fwnode = fwnode_handle_get(of_fwnode_handle(curr)); ref->flags = args.args[1]; 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")) { ref->con_id = kstrdup(prop->name, GFP_KERNEL); if (!ref->con_id) return -ENOMEM; con_id_len = strlen(ref->con_id); suffix_len = strlen(suffix); ref->con_id[con_id_len - suffix_len] = '\0'; } ref->dev_id = ida_alloc(&gpio_shared_ida, GFP_KERNEL); if (ref->dev_id < 0) { kfree(ref->con_id); 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(&no_free_ptr(ref)->list, &entry->refs); } } 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; lockdep_assert_held(&gpio_shared_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; bool match; int ret; /* The reset-gpio device must have a parent AND a firmware node. */ if (!parent || !reset_fwnode) return false; /* * FIXME: use device_is_compatible() once the reset-gpio drivers gains * a compatible string which it currently does not have. */ if (!strstarts(dev_name(consumer), "reset.gpio.")) 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; /* * 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(ref->fwnode, "reset-gpios", NULL, 2, 0, &ref_args); if (ret) return false; ret = fwnode_property_get_reference_args(reset_fwnode, "reset-gpios", NULL, 2, 0, &aux_args); if (ret) { fwnode_handle_put(ref_args.fwnode); return false; } 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); return match; } #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, unsigned long lflags) { const char *dev_id = dev_name(consumer); struct gpio_shared_entry *entry; struct gpio_shared_ref *ref; struct gpiod_lookup_table *lookup __free(kfree) = kzalloc(struct_size(lookup, table, 2), GFP_KERNEL); if (!lookup) return -ENOMEM; guard(mutex)(&gpio_shared_lock); list_for_each_entry(entry, &gpio_shared_list, list) { list_for_each_entry(ref, &entry->refs, list) { if (!device_match_fwnode(consumer, ref->fwnode) && !gpio_shared_dev_is_reset_gpio(consumer, entry, ref)) 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; 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); gpiod_add_lookup_table(no_free_ptr(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) { lockdep_assert_held(&gpio_shared_lock); auxiliary_device_uninit(adev); auxiliary_device_delete(adev); } int gpio_device_setup_shared(struct gpio_device *gdev) { struct gpio_shared_entry *entry; struct gpio_shared_ref *ref; unsigned long *flags; int ret; guard(mutex)(&gpio_shared_lock); 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; flags = &gdev->descs[entry->offset].flags; __set_bit(GPIOD_FLAG_SHARED, flags); /* * Shared GPIOs are not requested via the normal path. Make * them inaccessible to anyone even before we register the * chip. */ __set_bit(GPIOD_FLAG_REQUESTED, flags); 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\n", fwnode_get_name(ref->fwnode)); ret = gpio_shared_make_adev(gdev, entry, ref); if (ret) return ret; } } return 0; } void gpio_device_teardown_shared(struct gpio_device *gdev) { struct gpio_shared_entry *entry; struct gpio_shared_ref *ref; guard(mutex)(&gpio_shared_lock); list_for_each_entry(entry, &gpio_shared_list, list) { if (!device_match_fwnode(&gdev->dev, entry->fwnode)) continue; list_for_each_entry(ref, &entry->refs, list) { 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); if (shared_desc->can_sleep) 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; lockdep_assert_not_held(&gpio_shared_lock); 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(sizeof(*shared_desc), GFP_KERNEL); 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]; shared_desc->can_sleep = gpiod_cansleep(shared_desc->desc); if (shared_desc->can_sleep) mutex_init(&shared_desc->mutex); else spin_lock_init(&shared_desc->spinlock); 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; lockdep_assert_not_held(&gpio_shared_lock); 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); 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 void gpio_shared_free_exclusive(void) { struct gpio_shared_entry *entry, *epos; list_for_each_entry_safe(entry, epos, &gpio_shared_list, list) { if (list_count_nodes(&entry->refs) > 1) continue; gpio_shared_drop_ref(list_first_entry(&entry->refs, struct gpio_shared_ref, list)); 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);