diff options
Diffstat (limited to 'drivers/gpio/gpiolib-shared.c')
| -rw-r--r-- | drivers/gpio/gpiolib-shared.c | 174 |
1 files changed, 136 insertions, 38 deletions
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c index fa1d16635ea7..8bdd107b1ad1 100644 --- a/drivers/gpio/gpiolib-shared.c +++ b/drivers/gpio/gpiolib-shared.c @@ -49,6 +49,7 @@ struct gpio_shared_entry { 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; @@ -58,6 +59,7 @@ 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) @@ -72,7 +74,26 @@ gpio_shared_find_entry(struct fwnode_handle *controller_node, return NULL; } -#if IS_ENABLED(CONFIG_OF) +/* 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; @@ -84,6 +105,9 @@ static int gpio_shared_of_traverse(struct device_node *curr) 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" @@ -147,6 +171,7 @@ static int gpio_shared_of_traverse(struct device_node *curr) entry->offset = offset; entry->index = count; INIT_LIST_HEAD(&entry->refs); + mutex_init(&entry->lock); list_add_tail(&entry->list, &gpio_shared_list); } @@ -205,7 +230,10 @@ static int gpio_shared_of_traverse(struct device_node *curr) static int gpio_shared_of_scan(void) { - return gpio_shared_of_traverse(of_root); + if (of_root) + return gpio_shared_of_traverse(of_root); + + return 0; } #else static int gpio_shared_of_scan(void) @@ -220,6 +248,7 @@ 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; @@ -232,6 +261,7 @@ static int gpio_shared_make_adev(struct gpio_device *gdev, 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); @@ -250,6 +280,84 @@ static int gpio_shared_make_adev(struct gpio_device *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); @@ -265,7 +373,8 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, unsigned long lflags) list_for_each_entry(entry, &gpio_shared_list, list) { list_for_each_entry(ref, &entry->refs, list) { - if (!device_match_fwnode(consumer, ref->fwnode)) + 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. */ @@ -356,7 +465,7 @@ int gpio_device_setup_shared(struct gpio_device *gdev) pr_debug("Setting up a shared GPIO entry for %s\n", fwnode_get_name(ref->fwnode)); - ret = gpio_shared_make_adev(gdev, ref); + ret = gpio_shared_make_adev(gdev, entry, ref); if (ret) return ret; } @@ -390,10 +499,11 @@ 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 = entry->shared_desc; + struct gpio_shared_desc *shared_desc; - guard(mutex)(&gpio_shared_lock); + 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); @@ -416,6 +526,8 @@ 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); @@ -436,57 +548,42 @@ gpiod_shared_desc_create(struct gpio_shared_entry *entry) return shared_desc; } -static struct gpio_shared_entry *gpiod_shared_find(struct auxiliary_device *adev) +struct gpio_shared_desc *devm_gpiod_shared_get(struct device *dev) { struct gpio_shared_desc *shared_desc; struct gpio_shared_entry *entry; - struct gpio_shared_ref *ref; - - guard(mutex)(&gpio_shared_lock); + int ret; - list_for_each_entry(entry, &gpio_shared_list, list) { - list_for_each_entry(ref, &entry->refs, list) { - if (adev != &ref->adev) - continue; + lockdep_assert_not_held(&gpio_shared_lock); - if (entry->shared_desc) { - kref_get(&entry->ref); - return entry; - } + 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(&adev->dev), gpio_chip_hwgpio(shared_desc->desc), - gpio_device_get_label(shared_desc->desc->gdev)); - - - return entry; } - } - - return ERR_PTR(-ENOENT); -} - -struct gpio_shared_desc *devm_gpiod_shared_get(struct device *dev) -{ - struct gpio_shared_entry *entry; - int ret; - entry = gpiod_shared_find(to_auxiliary_dev(dev)); - if (IS_ERR(entry)) - return ERR_CAST(entry); + 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 entry->shared_desc; + return shared_desc; } EXPORT_SYMBOL_GPL(devm_gpiod_shared_get); @@ -502,6 +599,7 @@ static void gpio_shared_drop_ref(struct gpio_shared_ref *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); } |
