In certain cases it makes sense to create cascaded GPIO which
are not real GPIOs, merely point to the real backend GPIO chip.

In order to support this, cascaded of_xlate lookup need to be
performed.

For example let's take a connector that we want to abstract
having two GPIO pins from different GPIO controllers, connector
pin #0 connected to gpioA controller with offset 10 and gpioB
with 4.

In pseudo DT form this is analogous to:

        gpioA: gpioa@80000 {
                compatible = "foocorp,gpio";
                ...
        };

        gpioB: gpiob@80800 {
                compatible = "foocorp,gpio";
                ....
        };

        gpioC: controller_gpio {
                compatible = "cascaded-gpio";
                gpios = <&gpioA 10>, <&gpioB 5>;
        };

For example a user of gpioC (let's take a led driver) will have a
reference to gpioC like this:

        leds {
                compatible = "gpio-leds";
                led@0 {
                        gpios = <&gpioC 0 GPIO_ACTIVE_HIGH>;
                        ...
                };
                led@1 {
                        gpios = <&gpioC 1 GPIO_ACTIVE_HIGH>;
                        ..
                };
        };

We want the matches for gpioC to instead refer to gpioA & gpioB.

This is accomplished by a new method of_gpiochip_find() which
is an extension of the standard gpiochip_find() method.

A cascaded GPIO controller can modify the gpiospec node pointer
and arg[0] to point to the next GPIO in sequence and return -EAGAIN.
of_gpiochip_find() will restart the match using the new data
until either the final real GPIO is found or a maximum iteration
limit is reached.

In our example the cascaded-gpio driver can have a of_xlate method that
will point to gpioA/10 for gpioC/0 and gpioB/5 for gpioC/1.

Note that the cascade-gpio driver needs not to define any other member
methods since no actual reference will ever be made to it.

Signed-off-by: Pantelis Antoniou <pantelis.anton...@konsulko.com>
---
 drivers/gpio/gpiolib-of.c | 16 ++++----------
 drivers/gpio/gpiolib.c    | 54 +++++++++++++++++++++++++++++++++++++++++++++++
 drivers/gpio/gpiolib.h    | 14 ++++++++++++
 3 files changed, 72 insertions(+), 12 deletions(-)

diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c
index 50cb787..771060f 100644
--- a/drivers/gpio/gpiolib-of.c
+++ b/drivers/gpio/gpiolib-of.c
@@ -26,18 +26,10 @@
 
 #include "gpiolib.h"
 
-/* Private data structure for of_gpiochip_find_and_xlate */
-struct gg_data {
-       enum of_gpio_flags *flags;
-       struct of_phandle_args gpiospec;
-
-       struct gpio_desc *out_gpio;
-};
-
 /* Private function for resolving node pointer to gpio_chip */
-static int of_gpiochip_find_and_xlate(struct gpio_chip *gc, void *data)
+static int of_gpiochip_find_and_xlate(struct gpio_chip *gc,
+               struct gg_data *gg_data)
 {
-       struct gg_data *gg_data = data;
        int ret;
 
        if ((gc->of_node != gg_data->gpiospec.np) ||
@@ -95,7 +87,7 @@ struct gpio_desc *of_get_named_gpiod_flags(struct device_node 
*np,
                return ERR_PTR(ret);
        }
 
-       gpiochip_find(&gg_data, of_gpiochip_find_and_xlate);
+       of_gpiochip_find(&gg_data, of_gpiochip_find_and_xlate);
 
        of_node_put(gg_data.gpiospec.np);
        pr_debug("%s: parsed '%s' property of node '%s[%d]' - status (%d)\n",
@@ -166,7 +158,7 @@ static struct gpio_desc *of_parse_own_gpio(struct 
device_node *np,
                        return ERR_PTR(ret);
        }
 
-       gpiochip_find(&gg_data, of_gpiochip_find_and_xlate);
+       of_gpiochip_find(&gg_data, of_gpiochip_find_and_xlate);
        if (!gg_data.out_gpio) {
                if (np->parent == np)
                        return ERR_PTR(-ENXIO);
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 24f60d2..e719499 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -884,6 +884,60 @@ struct gpio_chip *gpiochip_find(void *data,
 }
 EXPORT_SYMBOL_GPL(gpiochip_find);
 
+#ifdef CONFIG_OF_GPIO
+
+/* allow a maximum of 10 GPIO cascades (should be enough) */
+#define OF_GPIOCHIP_RETRY_COUNT_MAX    10
+
+/**
+ * of_gpiochip_find() - iterator for locating a specific gpio_chip
+ * @gg_data: data to pass to match function
+ * @callback: Callback function to check gpio_chip
+ *
+ * Similar to bus_find_device.  It returns a reference to a gpio_chip as
+ * determined by a user supplied @match callback.  The callback should return
+ * 0 if the device doesn't match and non-zero if it does.  If the callback is
+ * non-zero, this function will return to the caller and not iterate over any
+ * more gpio_chips.
+ */
+struct gpio_chip *of_gpiochip_find(struct gg_data *gg_data,
+               int (*match)(struct gpio_chip *chip, struct gg_data *gg_data))
+{
+       struct gpio_device *gdev;
+       struct gpio_chip *chip;
+       unsigned long flags;
+       int i;
+
+       spin_lock_irqsave(&gpio_lock, flags);
+       /* always start with defer */
+       gg_data->out_gpio = ERR_PTR(-EPROBE_DEFER);
+       for (i = 0; gg_data->out_gpio == ERR_PTR(-EPROBE_DEFER) &&
+                       i < OF_GPIOCHIP_RETRY_COUNT_MAX; i++) {
+
+               list_for_each_entry(gdev, &gpio_devices, list) {
+                       if (match(gdev->chip, gg_data))
+                               break;
+                       /* again? cascaded; try agan */
+                       if (gg_data->out_gpio == ERR_PTR(-EAGAIN)) {
+                               /* defer is the default again */
+                               gg_data->out_gpio = ERR_PTR(-EPROBE_DEFER);
+                               break;
+                       }
+               }
+       }
+
+       /* no match or maximum retry limit? */
+       if (&gdev->list == &gpio_devices || i >= OF_GPIOCHIP_RETRY_COUNT_MAX)
+               chip = NULL;
+       else
+               chip = gdev->chip;
+       spin_unlock_irqrestore(&gpio_lock, flags);
+
+       return chip;
+}
+EXPORT_SYMBOL_GPL(of_gpiochip_find);
+#endif
+
 static int gpiochip_match_name(struct gpio_chip *chip, void *data)
 {
        const char *name = data;
diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
index 2d9ea5e..58cbf75 100644
--- a/drivers/gpio/gpiolib.h
+++ b/drivers/gpio/gpiolib.h
@@ -236,4 +236,18 @@ static inline void gpiochip_sysfs_unregister(struct 
gpio_device *gdev)
 
 #endif /* CONFIG_GPIO_SYSFS */
 
+#ifdef CONFIG_OF_GPIO
+
+/* Private data structure for of_gpiochip_find_and_xlate */
+struct gg_data {
+       enum of_gpio_flags *flags;
+       struct of_phandle_args gpiospec;
+       struct gpio_desc *out_gpio;
+};
+
+extern struct gpio_chip *of_gpiochip_find(struct gg_data *gg_data,
+               int (*match)(struct gpio_chip *chip, struct gg_data *gg_data));
+
+#endif
+
 #endif /* GPIOLIB_H */
-- 
1.7.12

Reply via email to