From: Alexander Sverdlin <alexander.sverdlin@xxxxxxxxx> 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+sVyo6SRJdA@xxxxxxxxxxxxxx/ Signed-off-by: Alexander Sverdlin <alexander.sverdlin@xxxxxxxxx> --- Changelog: v3: pl061_populate_parent_fwspec() -> pl061_populate_parent_alloc_arg() v2: Add pl061_populate_parent_fwspec() drivers/gpio/Kconfig | 1 + drivers/gpio/gpio-pl061.c | 97 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 91 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..5bfb5f6 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,69 @@ 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 +static void *pl061_populate_parent_alloc_arg(struct gpio_chip *gc, + unsigned int parent_hwirq, + unsigned int parent_type) +{ + struct device_node *dn = to_of_node(gc->irq.fwnode); + struct of_phandle_args pha; + struct irq_fwspec *fwspec; + int i; + + if (WARN_ON(!dn)) + return NULL; + + fwspec = kmalloc(sizeof(*fwspec), GFP_KERNEL); + if (!fwspec) + return NULL; + + /* + * 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 NULL; + + if (WARN_ON(of_irq_parse_one(dn, i, &pha))) + return NULL; + + fwspec->fwnode = gc->irq.parent_domain->fwnode; + fwspec->param_count = pha.args_count; + for (i = 0; i < pha.args_count; i++) + fwspec->param[i] = pha.args[i]; + + return fwspec; +} +#endif + static int pl061_probe(struct amba_device *adev, const struct amba_id *id) { struct device *dev = &adev->dev; @@ -330,16 +394,35 @@ 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]) { + 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_alloc_arg = + pl061_populate_parent_alloc_arg; +#endif + } else { + 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; + } + ret = devm_gpiochip_add_data(dev, &pl061->gc, pl061); if (ret) return ret; -- 2.10.2