[PATCH 2/2] gpio: Support cascaded GPIO chip lookup for OF

From: Pantelis Antoniou
Date: Fri Jun 03 2016 - 16:27:25 EST


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.antoniou@xxxxxxxxxxxx>
---
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