]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
gpiolib: allow multiple lookup tables per consumer
authorBartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Mon, 22 Dec 2025 10:01:26 +0000 (11:01 +0100)
committerBartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Fri, 2 Jan 2026 09:34:31 +0000 (10:34 +0100)
The GPIO machine lookup mechanism dates back to old ARM board files
where lookup tables would all be defined in a single place. Since then,
lookup tables have also been used to address various DT and ACPI quirks
like missing GPIOs and - as of recently - setting up shared GPIO proxy
devices.

The lookup itself stops when we find the first matching table for a
consumer and - if it doesn't contain the right entry - the lookup fails.

Since the tables can now be defined in multiple places and we can't know
how many there are, effectively limiting a consumer to only a single
lookup table is no longer viable.

Rework the code to always look through all tables matching the consumer.

Link: https://lore.kernel.org/r/20251222-gpio-shared-reset-gpio-proxy-v1-1-8d4bba7d8c14@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
drivers/gpio/gpiolib.c

index 91e0c384f34ae5c0ed61ccd3a978685d4f72e867..6d583b3b07bb5c3e5886ced53deca7cd02460890 100644 (file)
@@ -4508,45 +4508,41 @@ void gpiod_remove_hogs(struct gpiod_hog *hogs)
 }
 EXPORT_SYMBOL_GPL(gpiod_remove_hogs);
 
-static struct gpiod_lookup_table *gpiod_find_lookup_table(struct device *dev)
+static bool gpiod_match_lookup_table(struct device *dev,
+                                    const struct gpiod_lookup_table *table)
 {
        const char *dev_id = dev ? dev_name(dev) : NULL;
-       struct gpiod_lookup_table *table;
 
-       list_for_each_entry(table, &gpio_lookup_list, list) {
-               if (table->dev_id && dev_id) {
-                       /*
-                        * Valid strings on both ends, must be identical to have
-                        * a match
-                        */
-                       if (!strcmp(table->dev_id, dev_id))
-                               return table;
-               } else {
-                       /*
-                        * One of the pointers is NULL, so both must be to have
-                        * a match
-                        */
-                       if (dev_id == table->dev_id)
-                               return table;
-               }
+       lockdep_assert_held(&gpio_lookup_lock);
+
+       if (table->dev_id && dev_id) {
+               /*
+                * Valid strings on both ends, must be identical to have
+                * a match
+                */
+               if (!strcmp(table->dev_id, dev_id))
+                       return true;
+       } else {
+               /*
+                * One of the pointers is NULL, so both must be to have
+                * a match
+                */
+               if (dev_id == table->dev_id)
+                       return true;
        }
 
-       return NULL;
+       return false;
 }
 
-static struct gpio_desc *gpiod_find(struct device *dev, const char *con_id,
-                                   unsigned int idx, unsigned long *flags)
+static struct gpio_desc *gpio_desc_table_match(struct device *dev, const char *con_id,
+                                              unsigned int idx, unsigned long *flags,
+                                              struct gpiod_lookup_table *table)
 {
-       struct gpio_desc *desc = ERR_PTR(-ENOENT);
-       struct gpiod_lookup_table *table;
+       struct gpio_desc *desc;
        struct gpiod_lookup *p;
        struct gpio_chip *gc;
 
-       guard(mutex)(&gpio_lookup_lock);
-
-       table = gpiod_find_lookup_table(dev);
-       if (!table)
-               return desc;
+       lockdep_assert_held(&gpio_lookup_lock);
 
        for (p = &table->table[0]; p->key; p++) {
                /* idx must always match exactly */
@@ -4600,6 +4596,29 @@ static struct gpio_desc *gpiod_find(struct device *dev, const char *con_id,
                return desc;
        }
 
+       return NULL;
+}
+
+static struct gpio_desc *gpiod_find(struct device *dev, const char *con_id,
+                                   unsigned int idx, unsigned long *flags)
+{
+       struct gpio_desc *desc = ERR_PTR(-ENOENT);
+       struct gpiod_lookup_table *table;
+
+       guard(mutex)(&gpio_lookup_lock);
+
+       list_for_each_entry(table, &gpio_lookup_list, list) {
+               if (!gpiod_match_lookup_table(dev, table))
+                       continue;
+
+               desc = gpio_desc_table_match(dev, con_id, idx, flags, table);
+               if (!desc)
+                       continue;
+
+               /* On IS_ERR() or match. */
+               return desc;
+       }
+
        return desc;
 }
 
@@ -4610,14 +4629,16 @@ static int platform_gpio_count(struct device *dev, const char *con_id)
        unsigned int count = 0;
 
        scoped_guard(mutex, &gpio_lookup_lock) {
-               table = gpiod_find_lookup_table(dev);
-               if (!table)
-                       return -ENOENT;
+               list_for_each_entry(table, &gpio_lookup_list, list) {
+                       if (!gpiod_match_lookup_table(dev, table))
+                               continue;
 
-               for (p = &table->table[0]; p->key; p++) {
-                       if ((con_id && p->con_id && !strcmp(con_id, p->con_id)) ||
-                           (!con_id && !p->con_id))
-                               count++;
+                       for (p = &table->table[0]; p->key; p++) {
+                               if ((con_id && p->con_id &&
+                                   !strcmp(con_id, p->con_id)) ||
+                                   (!con_id && !p->con_id))
+                                       count++;
+                       }
                }
        }