Currently, gpiolib creates irq_domain compatible with hierarchical API only when interrupts provided by gpiochip lay on top of existing hierarchy. Otherwise, legacy API is used. With this patch, as soon as - irq_domain hierarchical API is enabled in the kernel config, - chip driver does not request preallocated interrupt numbers, - chip driver does not provide it's own irq_domain_ops, - chip driver provides fwnode, either explicitly or via it's struct device, irq_domain created by gpiolib will use hierarchical API even without parent. This allows other irqchips to lay on top of this irq_domain. Example target use case is irq_inverter [1]. [1] https://lore.kernel.org/lkml/87fsqbznc2.wl-maz@xxxxxxxxxx/ Signed-off-by: Nikita Yushchenko <nikita.yoush@xxxxxxxxxxxxxxxxxx> --- drivers/gpio/gpiolib.c | 99 +++++++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 39 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index abfbf546d159..db8eee07a8d7 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1095,14 +1095,6 @@ static int gpiochip_hierarchy_irq_domain_alloc(struct irq_domain *d, chip_dbg(gc, "allocate IRQ %d, hwirq %lu\n", irq, hwirq); - ret = girq->child_to_parent_hwirq(gc, hwirq, type, - &parent_hwirq, &parent_type); - if (ret) { - chip_err(gc, "can't look up hwirq %lu\n", hwirq); - return ret; - } - chip_dbg(gc, "found parent hwirq %u\n", parent_hwirq); - /* * We set handle_bad_irq because the .set_type() should * always be invoked and set the right type of handler. @@ -1116,27 +1108,40 @@ static int gpiochip_hierarchy_irq_domain_alloc(struct irq_domain *d, NULL, NULL); irq_set_probe(irq); - /* This parent only handles asserted level IRQs */ - parent_arg = girq->populate_parent_alloc_arg(gc, parent_hwirq, parent_type); - if (!parent_arg) - return -ENOMEM; - - chip_dbg(gc, "alloc_irqs_parent for %d parent hwirq %d\n", - irq, parent_hwirq); irq_set_lockdep_class(irq, gc->irq.lock_key, gc->irq.request_key); - ret = irq_domain_alloc_irqs_parent(d, irq, 1, parent_arg); - /* - * If the parent irqdomain is msi, the interrupts have already - * been allocated, so the EEXIST is good. - */ - if (irq_domain_is_msi(d->parent) && (ret == -EEXIST)) - ret = 0; - if (ret) - chip_err(gc, - "failed to allocate parent hwirq %d for hwirq %lu\n", - parent_hwirq, hwirq); - kfree(parent_arg); + if (d->parent) { + ret = girq->child_to_parent_hwirq(gc, hwirq, type, + &parent_hwirq, &parent_type); + if (ret) { + chip_err(gc, "can't look up hwirq %lu\n", hwirq); + return ret; + } + chip_dbg(gc, "found parent hwirq %u\n", parent_hwirq); + + /* This parent only handles asserted level IRQs */ + parent_arg = girq->populate_parent_alloc_arg(gc, parent_hwirq, + parent_type); + if (!parent_arg) + return -ENOMEM; + + chip_dbg(gc, "alloc_irqs_parent for %d parent hwirq %d\n", + irq, parent_hwirq); + ret = irq_domain_alloc_irqs_parent(d, irq, 1, parent_arg); + /* + * If the parent irqdomain is msi, the interrupts have already + * been allocated, so the EEXIST is good. + */ + if (irq_domain_is_msi(d->parent) && (ret == -EEXIST)) + ret = 0; + if (ret) + chip_err(gc, + "failed to allocate parent hwirq %d for hwirq %lu\n", + parent_hwirq, hwirq); + + kfree(parent_arg); + } + return ret; } @@ -1164,8 +1169,8 @@ static void gpiochip_hierarchy_setup_domain_ops(struct irq_domain_ops *ops) static int gpiochip_hierarchy_add_domain(struct gpio_chip *gc) { - if (!gc->irq.child_to_parent_hwirq || - !gc->irq.fwnode) { + if (gc->irq.parent_domain && + (!gc->irq.child_to_parent_hwirq || !gc->irq.fwnode)) { chip_err(gc, "missing irqdomain vital data\n"); return -EINVAL; } @@ -1179,25 +1184,41 @@ static int gpiochip_hierarchy_add_domain(struct gpio_chip *gc) gpiochip_hierarchy_setup_domain_ops(&gc->irq.child_irq_domain_ops); - gc->irq.domain = irq_domain_create_hierarchy( - gc->irq.parent_domain, - 0, - gc->ngpio, - gc->irq.fwnode, - &gc->irq.child_irq_domain_ops, - gc); + if (gc->irq.parent_domain) { + gc->irq.domain = irq_domain_create_hierarchy( + gc->irq.parent_domain, + 0, + gc->ngpio, + gc->irq.fwnode, + &gc->irq.child_irq_domain_ops, + gc); + + if (gc->irq.domain) + gpiochip_set_hierarchical_irqchip(gc, gc->irq.chip); + } else { + gc->irq.domain = irq_domain_create_linear( + gc->irq.fwnode ?: dev_fwnode(&gc->gpiodev->dev), + gc->ngpio, + &gc->irq.child_irq_domain_ops, + gc); + } if (!gc->irq.domain) return -ENOMEM; - gpiochip_set_hierarchical_irqchip(gc, gc->irq.chip); - return 0; } static bool gpiochip_hierarchy_is_hierarchical(struct gpio_chip *gc) { - return !!gc->irq.parent_domain; + if (gc->irq.parent_domain) + return true; /* will add to existing hierarchy */ + + if (!gc->irq.first && !gc->irq.domain_ops && + (gc->irq.fwnode || dev_fwnode(&gc->gpiodev->dev))) + return true; /* will create hierarchy bottom */ + + return false; } void *gpiochip_populate_parent_fwspec_twocell(struct gpio_chip *gc, -- 2.30.2