From: Alexander Sverdlin <alexander.sverd...@nokia.com>

There are several implementations of PL061 which lack GPIOINTR signal in
hardware and only have individual GPIOMIS[7:0] interrupts. Use the
hierarchical interrupt support of the gpiolib in these cases (if at least 8
IRQs are configured for the PL061).

One in-tree example is arch/arm/boot/dts/axm55xx.dtsi, PL061 instances have
8 IRQs defined, but current driver supports only the first one, so only one
pin would work as IRQ trigger.

Link: 
https://lore.kernel.org/linux-gpio/CACRpkdZpYzpMDWqJobSYH=jhgb74hbcqihotexs+svyo6sr...@mail.gmail.com/
Signed-off-by: Alexander Sverdlin <alexander.sverd...@nokia.com>
---
Changelog:
v2: Add pl061_populate_parent_fwspec()

 drivers/gpio/Kconfig      |  1 +
 drivers/gpio/gpio-pl061.c | 91 +++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 85 insertions(+), 7 deletions(-)

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index e3607ec..456c0a5 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -469,6 +469,7 @@ config GPIO_PL061
        depends on ARM_AMBA
        select IRQ_DOMAIN
        select GPIOLIB_IRQCHIP
+       select IRQ_DOMAIN_HIERARCHY
        help
          Say yes here to support the PrimeCell PL061 GPIO device
 
diff --git a/drivers/gpio/gpio-pl061.c b/drivers/gpio/gpio-pl061.c
index f1b53dd..e95714a 100644
--- a/drivers/gpio/gpio-pl061.c
+++ b/drivers/gpio/gpio-pl061.c
@@ -24,6 +24,7 @@
 #include <linux/slab.h>
 #include <linux/pinctrl/consumer.h>
 #include <linux/pm.h>
+#include <linux/of_irq.h>
 
 #define GPIODIR 0x400
 #define GPIOIS  0x404
@@ -283,6 +284,64 @@ static int pl061_irq_set_wake(struct irq_data *d, unsigned 
int state)
        return irq_set_irq_wake(pl061->parent_irq, state);
 }
 
+static int pl061_child_to_parent_hwirq(struct gpio_chip *gc, unsigned int 
child,
+                                      unsigned int child_type,
+                                      unsigned int *parent,
+                                      unsigned int *parent_type)
+{
+       struct amba_device *adev = to_amba_device(gc->parent);
+       unsigned int irq = adev->irq[child];
+       struct irq_data *d = irq_get_irq_data(irq);
+
+       if (!d)
+               return -EINVAL;
+
+       *parent_type = irqd_get_trigger_type(d);
+       *parent = irqd_to_hwirq(d);
+       return 0;
+}
+
+#ifdef CONFIG_OF
+void pl061_populate_parent_fwspec(struct gpio_chip *gc,
+                                 struct irq_fwspec *fwspec,
+                                 unsigned int parent_hwirq,
+                                 unsigned int parent_type)
+{
+       struct device_node *dn = to_of_node(gc->irq.fwnode);
+       struct of_phandle_args pha;
+       int i;
+
+       fwspec->param_count = 0;
+
+       if (WARN_ON(!dn))
+               return;
+
+       /*
+        * This brute-force here is because of the fact PL061 is often paired
+        * with GIC-v3, which has 3-cell IRQ specifier (SPI/PPI selection), and
+        * unexpected range shifts in hwirq mapping (SPI IRQs are shifted by
+        * 32). So this is about reversing of gic_irq_domain_translate().
+        */
+       for (i = 0; i < PL061_GPIO_NR; i++) {
+               unsigned int p, pt;
+
+               if (pl061_child_to_parent_hwirq(gc, i, parent_type, &p, &pt))
+                       continue;
+               if (p == parent_hwirq)
+                       break;
+       }
+       if (WARN_ON(i == PL061_GPIO_NR))
+               return;
+
+       if (WARN_ON(of_irq_parse_one(dn, i, &pha)))
+               return;
+
+       fwspec->param_count = pha.args_count;
+       for (i = 0; i < pha.args_count; i++)
+               fwspec->param[i] = pha.args[i];
+}
+#endif
+
 static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
 {
        struct device *dev = &adev->dev;
@@ -330,16 +389,34 @@ static int pl061_probe(struct amba_device *adev, const 
struct amba_id *id)
 
        girq = &pl061->gc.irq;
        girq->chip = &pl061->irq_chip;
-       girq->parent_handler = pl061_irq_handler;
-       girq->num_parents = 1;
-       girq->parents = devm_kcalloc(dev, 1, sizeof(*girq->parents),
-                                    GFP_KERNEL);
-       if (!girq->parents)
-               return -ENOMEM;
-       girq->parents[0] = irq;
        girq->default_type = IRQ_TYPE_NONE;
        girq->handler = handle_bad_irq;
 
+       /*
+        * There are some PL061 implementations which lack GPIOINTR in hardware
+        * and only have individual GPIOMIS[7:0] signals. We distinguish them by
+        * the number of IRQs assigned to the AMBA device.
+        */
+       if (!adev->irq[PL061_GPIO_NR - 1]) {
+               WARN_ON(adev->irq[1]);
+
+               girq->parent_handler = pl061_irq_handler;
+               girq->num_parents = 1;
+               girq->parents = devm_kcalloc(dev, 1, sizeof(*girq->parents),
+                                            GFP_KERNEL);
+               if (!girq->parents)
+                       return -ENOMEM;
+               girq->parents[0] = irq;
+       } else {
+               girq->fwnode = dev->fwnode;
+               girq->parent_domain =
+                       irq_get_irq_data(adev->irq[PL061_GPIO_NR - 1])->domain;
+               girq->child_to_parent_hwirq = pl061_child_to_parent_hwirq;
+#ifdef CONFIG_OF
+               girq->populate_parent_fwspec = pl061_populate_parent_fwspec;
+#endif
+       }
+
        ret = devm_gpiochip_add_data(dev, &pl061->gc, pl061);
        if (ret)
                return ret;
-- 
2.10.2

Reply via email to