Merge branch 'irq/tegra-pmc' into irq/irqchip-next

Signed-off-by: Marc Zyngier <maz@kernel.org>
This commit is contained in:
Marc Zyngier 2020-10-10 12:16:24 +01:00
commit 408f110ef6
4 changed files with 150 additions and 58 deletions

View File

@ -430,7 +430,18 @@ static int tegra186_irq_set_type(struct irq_data *data, unsigned int type)
else else
irq_set_handler_locked(data, handle_edge_irq); irq_set_handler_locked(data, handle_edge_irq);
return irq_chip_set_type_parent(data, type); 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) static void tegra186_gpio_irq(struct irq_desc *desc)
@ -678,7 +689,7 @@ static int tegra186_gpio_probe(struct platform_device *pdev)
gpio->intc.irq_mask = tegra186_irq_mask; gpio->intc.irq_mask = tegra186_irq_mask;
gpio->intc.irq_unmask = tegra186_irq_unmask; gpio->intc.irq_unmask = tegra186_irq_unmask;
gpio->intc.irq_set_type = tegra186_irq_set_type; 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 = &gpio->gpio.irq;
irq->chip = &gpio->intc; irq->chip = &gpio->intc;

View File

@ -1990,44 +1990,17 @@ static int tegra_pmc_irq_alloc(struct irq_domain *domain, unsigned int virq,
event->id, event->id,
&pmc->irq, pmc); &pmc->irq, pmc);
/* /* GPIO hierarchies stop at the PMC level */
* GPIOs don't have an equivalent interrupt in the if (!err && domain->parent)
* parent controller (GIC). However some code, such err = irq_domain_disconnect_hierarchy(domain->parent,
* as the one in irq_get_irqchip_state(), require a virq);
* 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);
break; break;
} }
} }
/* /* If there is no wake-up event, there is no PMC mapping */
* For interrupts that don't have associated wake events, assign a if (i == soc->num_wake_events)
* dummy hardware IRQ number. This is used in the ->irq_set_type() err = irq_domain_disconnect_hierarchy(domain, virq);
* 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);
}
return err; return err;
} }
@ -2043,9 +2016,6 @@ static int tegra210_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
unsigned int offset, bit; unsigned int offset, bit;
u32 value; u32 value;
if (data->hwirq == ULONG_MAX)
return 0;
offset = data->hwirq / 32; offset = data->hwirq / 32;
bit = data->hwirq % 32; bit = data->hwirq % 32;
@ -2080,9 +2050,6 @@ static int tegra210_pmc_irq_set_type(struct irq_data *data, unsigned int type)
unsigned int offset, bit; unsigned int offset, bit;
u32 value; u32 value;
if (data->hwirq == ULONG_MAX)
return 0;
offset = data->hwirq / 32; offset = data->hwirq / 32;
bit = data->hwirq % 32; bit = data->hwirq % 32;
@ -2123,10 +2090,6 @@ static int tegra186_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
unsigned int offset, bit; unsigned int offset, bit;
u32 value; 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; offset = data->hwirq / 32;
bit = data->hwirq % 32; bit = data->hwirq % 32;
@ -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); struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
u32 value; 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)); value = readl(pmc->wake + WAKE_AOWAKE_CNTRL(data->hwirq));
switch (type) { switch (type) {
@ -2184,6 +2143,34 @@ static int tegra186_pmc_irq_set_type(struct irq_data *data, unsigned int type)
return 0; 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) static int tegra_pmc_irq_init(struct tegra_pmc *pmc)
{ {
struct irq_domain *parent = NULL; struct irq_domain *parent = NULL;
@ -2199,10 +2186,10 @@ static int tegra_pmc_irq_init(struct tegra_pmc *pmc)
return 0; return 0;
pmc->irq.name = dev_name(pmc->dev); pmc->irq.name = dev_name(pmc->dev);
pmc->irq.irq_mask = irq_chip_mask_parent; pmc->irq.irq_mask = tegra_irq_mask_parent;
pmc->irq.irq_unmask = irq_chip_unmask_parent; pmc->irq.irq_unmask = tegra_irq_unmask_parent;
pmc->irq.irq_eoi = irq_chip_eoi_parent; pmc->irq.irq_eoi = tegra_irq_eoi_parent;
pmc->irq.irq_set_affinity = irq_chip_set_affinity_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_type = pmc->soc->irq_set_type;
pmc->irq.irq_set_wake = pmc->soc->irq_set_wake; pmc->irq.irq_set_wake = pmc->soc->irq_set_wake;

View File

@ -509,6 +509,9 @@ extern void irq_domain_free_irqs_parent(struct irq_domain *domain,
unsigned int irq_base, unsigned int irq_base,
unsigned int nr_irqs); 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) static inline bool irq_domain_is_hierarchy(struct irq_domain *domain)
{ {
return domain->flags & IRQ_DOMAIN_FLAG_HIERARCHY; return domain->flags & IRQ_DOMAIN_FLAG_HIERARCHY;

View File

@ -1136,6 +1136,17 @@ static struct irq_data *irq_domain_insert_irq_data(struct irq_domain *domain,
return irq_data; 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) static void irq_domain_free_irq_data(unsigned int virq, unsigned int nr_irqs)
{ {
struct irq_data *irq_data, *tmp; struct irq_data *irq_data, *tmp;
@ -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->parent_data = NULL;
irq_data->domain = NULL; irq_data->domain = NULL;
while (tmp) { __irq_domain_free_hierarchy(tmp);
irq_data = tmp; }
tmp = tmp->parent_data; }
kfree(irq_data);
/**
* 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, static int irq_domain_alloc_irq_data(struct irq_domain *domain,
@ -1362,6 +1444,15 @@ int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,
mutex_unlock(&irq_domain_mutex); mutex_unlock(&irq_domain_mutex);
goto out_free_irq_data; 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++) for (i = 0; i < nr_irqs; i++)
irq_domain_insert_irq(virq + i); irq_domain_insert_irq(virq + i);
mutex_unlock(&irq_domain_mutex); mutex_unlock(&irq_domain_mutex);