Loading drivers/gpio/gpio-tegra186.c +13 −2 Original line number Diff line number Diff line Loading @@ -430,7 +430,18 @@ static int tegra186_irq_set_type(struct irq_data *data, unsigned int type) else irq_set_handler_locked(data, handle_edge_irq); if (data->parent_data) return irq_chip_set_type_parent(data, type); return 0; } static int tegra186_irq_set_wake(struct irq_data *data, unsigned int on) { if (data->parent_data) return irq_chip_set_wake_parent(data, on); return 0; } static void tegra186_gpio_irq(struct irq_desc *desc) Loading Loading @@ -678,7 +689,7 @@ static int tegra186_gpio_probe(struct platform_device *pdev) gpio->intc.irq_mask = tegra186_irq_mask; gpio->intc.irq_unmask = tegra186_irq_unmask; gpio->intc.irq_set_type = tegra186_irq_set_type; gpio->intc.irq_set_wake = irq_chip_set_wake_parent; gpio->intc.irq_set_wake = tegra186_irq_set_wake; irq = &gpio->gpio.irq; irq->chip = &gpio->intc; Loading drivers/soc/tegra/pmc.c +39 −52 Original line number Diff line number Diff line Loading @@ -1990,44 +1990,17 @@ static int tegra_pmc_irq_alloc(struct irq_domain *domain, unsigned int virq, event->id, &pmc->irq, pmc); /* * GPIOs don't have an equivalent interrupt in the * parent controller (GIC). However some code, such * as the one in irq_get_irqchip_state(), require a * valid IRQ chip to be set. Make sure that's the * case by passing NULL here, which will install a * dummy IRQ chip for the interrupt in the parent * domain. */ if (domain->parent) irq_domain_set_hwirq_and_chip(domain->parent, virq, 0, NULL, NULL); /* GPIO hierarchies stop at the PMC level */ if (!err && domain->parent) err = irq_domain_disconnect_hierarchy(domain->parent, virq); break; } } /* * For interrupts that don't have associated wake events, assign a * dummy hardware IRQ number. This is used in the ->irq_set_type() * and ->irq_set_wake() callbacks to return early for these IRQs. */ if (i == soc->num_wake_events) { err = irq_domain_set_hwirq_and_chip(domain, virq, ULONG_MAX, &pmc->irq, pmc); /* * Interrupts without a wake event don't have a corresponding * interrupt in the parent controller (GIC). Pass NULL for the * chip here, which causes a dummy IRQ chip to be installed * for the interrupt in the parent domain, to make this * explicit. */ if (domain->parent) irq_domain_set_hwirq_and_chip(domain->parent, virq, 0, NULL, NULL); } /* If there is no wake-up event, there is no PMC mapping */ if (i == soc->num_wake_events) err = irq_domain_disconnect_hierarchy(domain, virq); return err; } Loading @@ -2043,9 +2016,6 @@ static int tegra210_pmc_irq_set_wake(struct irq_data *data, unsigned int on) unsigned int offset, bit; u32 value; if (data->hwirq == ULONG_MAX) return 0; offset = data->hwirq / 32; bit = data->hwirq % 32; Loading Loading @@ -2080,9 +2050,6 @@ static int tegra210_pmc_irq_set_type(struct irq_data *data, unsigned int type) unsigned int offset, bit; u32 value; if (data->hwirq == ULONG_MAX) return 0; offset = data->hwirq / 32; bit = data->hwirq % 32; Loading Loading @@ -2123,10 +2090,6 @@ static int tegra186_pmc_irq_set_wake(struct irq_data *data, unsigned int on) unsigned int offset, bit; u32 value; /* nothing to do if there's no associated wake event */ if (WARN_ON(data->hwirq == ULONG_MAX)) return 0; offset = data->hwirq / 32; bit = data->hwirq % 32; Loading Loading @@ -2154,10 +2117,6 @@ static int tegra186_pmc_irq_set_type(struct irq_data *data, unsigned int type) struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data); u32 value; /* nothing to do if there's no associated wake event */ if (data->hwirq == ULONG_MAX) return 0; value = readl(pmc->wake + WAKE_AOWAKE_CNTRL(data->hwirq)); switch (type) { Loading @@ -2184,6 +2143,34 @@ static int tegra186_pmc_irq_set_type(struct irq_data *data, unsigned int type) return 0; } static void tegra_irq_mask_parent(struct irq_data *data) { if (data->parent_data) irq_chip_mask_parent(data); } static void tegra_irq_unmask_parent(struct irq_data *data) { if (data->parent_data) irq_chip_unmask_parent(data); } static void tegra_irq_eoi_parent(struct irq_data *data) { if (data->parent_data) irq_chip_eoi_parent(data); } static int tegra_irq_set_affinity_parent(struct irq_data *data, const struct cpumask *dest, bool force) { if (data->parent_data) return irq_chip_set_affinity_parent(data, dest, force); return -EINVAL; } static int tegra_pmc_irq_init(struct tegra_pmc *pmc) { struct irq_domain *parent = NULL; Loading @@ -2199,10 +2186,10 @@ static int tegra_pmc_irq_init(struct tegra_pmc *pmc) return 0; pmc->irq.name = dev_name(pmc->dev); pmc->irq.irq_mask = irq_chip_mask_parent; pmc->irq.irq_unmask = irq_chip_unmask_parent; pmc->irq.irq_eoi = irq_chip_eoi_parent; pmc->irq.irq_set_affinity = irq_chip_set_affinity_parent; pmc->irq.irq_mask = tegra_irq_mask_parent; pmc->irq.irq_unmask = tegra_irq_unmask_parent; pmc->irq.irq_eoi = tegra_irq_eoi_parent; pmc->irq.irq_set_affinity = tegra_irq_set_affinity_parent; pmc->irq.irq_set_type = pmc->soc->irq_set_type; pmc->irq.irq_set_wake = pmc->soc->irq_set_wake; Loading include/linux/irqdomain.h +3 −0 Original line number Diff line number Diff line Loading @@ -509,6 +509,9 @@ extern void irq_domain_free_irqs_parent(struct irq_domain *domain, unsigned int irq_base, unsigned int nr_irqs); extern int irq_domain_disconnect_hierarchy(struct irq_domain *domain, unsigned int virq); static inline bool irq_domain_is_hierarchy(struct irq_domain *domain) { return domain->flags & IRQ_DOMAIN_FLAG_HIERARCHY; Loading kernel/irq/irqdomain.c +95 −4 Original line number Diff line number Diff line Loading @@ -1136,6 +1136,17 @@ static struct irq_data *irq_domain_insert_irq_data(struct irq_domain *domain, return irq_data; } static void __irq_domain_free_hierarchy(struct irq_data *irq_data) { struct irq_data *tmp; while (irq_data) { tmp = irq_data; irq_data = irq_data->parent_data; kfree(tmp); } } static void irq_domain_free_irq_data(unsigned int virq, unsigned int nr_irqs) { struct irq_data *irq_data, *tmp; Loading @@ -1147,12 +1158,83 @@ static void irq_domain_free_irq_data(unsigned int virq, unsigned int nr_irqs) irq_data->parent_data = NULL; irq_data->domain = NULL; while (tmp) { irq_data = tmp; tmp = tmp->parent_data; kfree(irq_data); __irq_domain_free_hierarchy(tmp); } } /** * irq_domain_disconnect_hierarchy - Mark the first unused level of a hierarchy * @domain: IRQ domain from which the hierarchy is to be disconnected * @virq: IRQ number where the hierarchy is to be trimmed * * Marks the @virq level belonging to @domain as disconnected. * Returns -EINVAL if @virq doesn't have a valid irq_data pointing * to @domain. * * Its only use is to be able to trim levels of hierarchy that do not * have any real meaning for this interrupt, and that the driver marks * as such from its .alloc() callback. */ int irq_domain_disconnect_hierarchy(struct irq_domain *domain, unsigned int virq) { struct irq_data *irqd; irqd = irq_domain_get_irq_data(domain, virq); if (!irqd) return -EINVAL; irqd->chip = ERR_PTR(-ENOTCONN); return 0; } static int irq_domain_trim_hierarchy(unsigned int virq) { struct irq_data *tail, *irqd, *irq_data; irq_data = irq_get_irq_data(virq); tail = NULL; /* The first entry must have a valid irqchip */ if (!irq_data->chip || IS_ERR(irq_data->chip)) return -EINVAL; /* * Validate that the irq_data chain is sane in the presence of * a hierarchy trimming marker. */ for (irqd = irq_data->parent_data; irqd; irq_data = irqd, irqd = irqd->parent_data) { /* Can't have a valid irqchip after a trim marker */ if (irqd->chip && tail) return -EINVAL; /* Can't have an empty irqchip before a trim marker */ if (!irqd->chip && !tail) return -EINVAL; if (IS_ERR(irqd->chip)) { /* Only -ENOTCONN is a valid trim marker */ if (PTR_ERR(irqd->chip) != -ENOTCONN) return -EINVAL; tail = irq_data; } } /* No trim marker, nothing to do */ if (!tail) return 0; pr_info("IRQ%d: trimming hierarchy from %s\n", virq, tail->parent_data->domain->name); /* Sever the inner part of the hierarchy... */ irqd = tail; tail = tail->parent_data; irqd->parent_data = NULL; __irq_domain_free_hierarchy(tail); return 0; } static int irq_domain_alloc_irq_data(struct irq_domain *domain, Loading Loading @@ -1362,6 +1444,15 @@ int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base, mutex_unlock(&irq_domain_mutex); goto out_free_irq_data; } for (i = 0; i < nr_irqs; i++) { ret = irq_domain_trim_hierarchy(virq + i); if (ret) { mutex_unlock(&irq_domain_mutex); goto out_free_irq_data; } } for (i = 0; i < nr_irqs; i++) irq_domain_insert_irq(virq + i); mutex_unlock(&irq_domain_mutex); Loading Loading
drivers/gpio/gpio-tegra186.c +13 −2 Original line number Diff line number Diff line Loading @@ -430,7 +430,18 @@ static int tegra186_irq_set_type(struct irq_data *data, unsigned int type) else irq_set_handler_locked(data, handle_edge_irq); if (data->parent_data) return irq_chip_set_type_parent(data, type); return 0; } static int tegra186_irq_set_wake(struct irq_data *data, unsigned int on) { if (data->parent_data) return irq_chip_set_wake_parent(data, on); return 0; } static void tegra186_gpio_irq(struct irq_desc *desc) Loading Loading @@ -678,7 +689,7 @@ static int tegra186_gpio_probe(struct platform_device *pdev) gpio->intc.irq_mask = tegra186_irq_mask; gpio->intc.irq_unmask = tegra186_irq_unmask; gpio->intc.irq_set_type = tegra186_irq_set_type; gpio->intc.irq_set_wake = irq_chip_set_wake_parent; gpio->intc.irq_set_wake = tegra186_irq_set_wake; irq = &gpio->gpio.irq; irq->chip = &gpio->intc; Loading
drivers/soc/tegra/pmc.c +39 −52 Original line number Diff line number Diff line Loading @@ -1990,44 +1990,17 @@ static int tegra_pmc_irq_alloc(struct irq_domain *domain, unsigned int virq, event->id, &pmc->irq, pmc); /* * GPIOs don't have an equivalent interrupt in the * parent controller (GIC). However some code, such * as the one in irq_get_irqchip_state(), require a * valid IRQ chip to be set. Make sure that's the * case by passing NULL here, which will install a * dummy IRQ chip for the interrupt in the parent * domain. */ if (domain->parent) irq_domain_set_hwirq_and_chip(domain->parent, virq, 0, NULL, NULL); /* GPIO hierarchies stop at the PMC level */ if (!err && domain->parent) err = irq_domain_disconnect_hierarchy(domain->parent, virq); break; } } /* * For interrupts that don't have associated wake events, assign a * dummy hardware IRQ number. This is used in the ->irq_set_type() * and ->irq_set_wake() callbacks to return early for these IRQs. */ if (i == soc->num_wake_events) { err = irq_domain_set_hwirq_and_chip(domain, virq, ULONG_MAX, &pmc->irq, pmc); /* * Interrupts without a wake event don't have a corresponding * interrupt in the parent controller (GIC). Pass NULL for the * chip here, which causes a dummy IRQ chip to be installed * for the interrupt in the parent domain, to make this * explicit. */ if (domain->parent) irq_domain_set_hwirq_and_chip(domain->parent, virq, 0, NULL, NULL); } /* If there is no wake-up event, there is no PMC mapping */ if (i == soc->num_wake_events) err = irq_domain_disconnect_hierarchy(domain, virq); return err; } Loading @@ -2043,9 +2016,6 @@ static int tegra210_pmc_irq_set_wake(struct irq_data *data, unsigned int on) unsigned int offset, bit; u32 value; if (data->hwirq == ULONG_MAX) return 0; offset = data->hwirq / 32; bit = data->hwirq % 32; Loading Loading @@ -2080,9 +2050,6 @@ static int tegra210_pmc_irq_set_type(struct irq_data *data, unsigned int type) unsigned int offset, bit; u32 value; if (data->hwirq == ULONG_MAX) return 0; offset = data->hwirq / 32; bit = data->hwirq % 32; Loading Loading @@ -2123,10 +2090,6 @@ static int tegra186_pmc_irq_set_wake(struct irq_data *data, unsigned int on) unsigned int offset, bit; u32 value; /* nothing to do if there's no associated wake event */ if (WARN_ON(data->hwirq == ULONG_MAX)) return 0; offset = data->hwirq / 32; bit = data->hwirq % 32; Loading Loading @@ -2154,10 +2117,6 @@ static int tegra186_pmc_irq_set_type(struct irq_data *data, unsigned int type) struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data); u32 value; /* nothing to do if there's no associated wake event */ if (data->hwirq == ULONG_MAX) return 0; value = readl(pmc->wake + WAKE_AOWAKE_CNTRL(data->hwirq)); switch (type) { Loading @@ -2184,6 +2143,34 @@ static int tegra186_pmc_irq_set_type(struct irq_data *data, unsigned int type) return 0; } static void tegra_irq_mask_parent(struct irq_data *data) { if (data->parent_data) irq_chip_mask_parent(data); } static void tegra_irq_unmask_parent(struct irq_data *data) { if (data->parent_data) irq_chip_unmask_parent(data); } static void tegra_irq_eoi_parent(struct irq_data *data) { if (data->parent_data) irq_chip_eoi_parent(data); } static int tegra_irq_set_affinity_parent(struct irq_data *data, const struct cpumask *dest, bool force) { if (data->parent_data) return irq_chip_set_affinity_parent(data, dest, force); return -EINVAL; } static int tegra_pmc_irq_init(struct tegra_pmc *pmc) { struct irq_domain *parent = NULL; Loading @@ -2199,10 +2186,10 @@ static int tegra_pmc_irq_init(struct tegra_pmc *pmc) return 0; pmc->irq.name = dev_name(pmc->dev); pmc->irq.irq_mask = irq_chip_mask_parent; pmc->irq.irq_unmask = irq_chip_unmask_parent; pmc->irq.irq_eoi = irq_chip_eoi_parent; pmc->irq.irq_set_affinity = irq_chip_set_affinity_parent; pmc->irq.irq_mask = tegra_irq_mask_parent; pmc->irq.irq_unmask = tegra_irq_unmask_parent; pmc->irq.irq_eoi = tegra_irq_eoi_parent; pmc->irq.irq_set_affinity = tegra_irq_set_affinity_parent; pmc->irq.irq_set_type = pmc->soc->irq_set_type; pmc->irq.irq_set_wake = pmc->soc->irq_set_wake; Loading
include/linux/irqdomain.h +3 −0 Original line number Diff line number Diff line Loading @@ -509,6 +509,9 @@ extern void irq_domain_free_irqs_parent(struct irq_domain *domain, unsigned int irq_base, unsigned int nr_irqs); extern int irq_domain_disconnect_hierarchy(struct irq_domain *domain, unsigned int virq); static inline bool irq_domain_is_hierarchy(struct irq_domain *domain) { return domain->flags & IRQ_DOMAIN_FLAG_HIERARCHY; Loading
kernel/irq/irqdomain.c +95 −4 Original line number Diff line number Diff line Loading @@ -1136,6 +1136,17 @@ static struct irq_data *irq_domain_insert_irq_data(struct irq_domain *domain, return irq_data; } static void __irq_domain_free_hierarchy(struct irq_data *irq_data) { struct irq_data *tmp; while (irq_data) { tmp = irq_data; irq_data = irq_data->parent_data; kfree(tmp); } } static void irq_domain_free_irq_data(unsigned int virq, unsigned int nr_irqs) { struct irq_data *irq_data, *tmp; Loading @@ -1147,12 +1158,83 @@ static void irq_domain_free_irq_data(unsigned int virq, unsigned int nr_irqs) irq_data->parent_data = NULL; irq_data->domain = NULL; while (tmp) { irq_data = tmp; tmp = tmp->parent_data; kfree(irq_data); __irq_domain_free_hierarchy(tmp); } } /** * irq_domain_disconnect_hierarchy - Mark the first unused level of a hierarchy * @domain: IRQ domain from which the hierarchy is to be disconnected * @virq: IRQ number where the hierarchy is to be trimmed * * Marks the @virq level belonging to @domain as disconnected. * Returns -EINVAL if @virq doesn't have a valid irq_data pointing * to @domain. * * Its only use is to be able to trim levels of hierarchy that do not * have any real meaning for this interrupt, and that the driver marks * as such from its .alloc() callback. */ int irq_domain_disconnect_hierarchy(struct irq_domain *domain, unsigned int virq) { struct irq_data *irqd; irqd = irq_domain_get_irq_data(domain, virq); if (!irqd) return -EINVAL; irqd->chip = ERR_PTR(-ENOTCONN); return 0; } static int irq_domain_trim_hierarchy(unsigned int virq) { struct irq_data *tail, *irqd, *irq_data; irq_data = irq_get_irq_data(virq); tail = NULL; /* The first entry must have a valid irqchip */ if (!irq_data->chip || IS_ERR(irq_data->chip)) return -EINVAL; /* * Validate that the irq_data chain is sane in the presence of * a hierarchy trimming marker. */ for (irqd = irq_data->parent_data; irqd; irq_data = irqd, irqd = irqd->parent_data) { /* Can't have a valid irqchip after a trim marker */ if (irqd->chip && tail) return -EINVAL; /* Can't have an empty irqchip before a trim marker */ if (!irqd->chip && !tail) return -EINVAL; if (IS_ERR(irqd->chip)) { /* Only -ENOTCONN is a valid trim marker */ if (PTR_ERR(irqd->chip) != -ENOTCONN) return -EINVAL; tail = irq_data; } } /* No trim marker, nothing to do */ if (!tail) return 0; pr_info("IRQ%d: trimming hierarchy from %s\n", virq, tail->parent_data->domain->name); /* Sever the inner part of the hierarchy... */ irqd = tail; tail = tail->parent_data; irqd->parent_data = NULL; __irq_domain_free_hierarchy(tail); return 0; } static int irq_domain_alloc_irq_data(struct irq_domain *domain, Loading Loading @@ -1362,6 +1444,15 @@ int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base, mutex_unlock(&irq_domain_mutex); goto out_free_irq_data; } for (i = 0; i < nr_irqs; i++) { ret = irq_domain_trim_hierarchy(virq + i); if (ret) { mutex_unlock(&irq_domain_mutex); goto out_free_irq_data; } } for (i = 0; i < nr_irqs; i++) irq_domain_insert_irq(virq + i); mutex_unlock(&irq_domain_mutex); Loading