Merge branch 'irq/tegra-pmc' into irq/irqchip-next
Signed-off-by: Marc Zyngier <maz@kernel.org>
This commit is contained in:
Коммит
408f110ef6
|
@ -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)
|
||||
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче