sdk-hwV1.3/lichee/linux-4.9/drivers/irqchip/irq-sunxi.c

144 lines
3.8 KiB
C

/*
* SUNXI WakeupGen Source file
*
* SUNXI WakeupGen is the interrupt controller extension used along
* with ARM GIC to wake the CPU out from low power states on
* external interrupts. It is responsible for generating wakeup
* event from the incoming interrupts and enable bits. It is
* implemented in PMU always ON power domain. During normal operation,
* WakeupGen delivers external interrupts directly to the GIC.
*
* Copyright (C) 2017 Allwinner Technology, Inc.
* fanqinghua <fanqinghua@allwinnertech.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqdomain.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/cpu.h>
#include <linux/notifier.h>
#include <linux/cpu_pm.h>
#include <linux/arisc/arisc.h>
#define GIC_SUPPORT_IRQS 1024
static int sunxi_irq_set_wake(struct irq_data *d, unsigned int on)
{
if (on)
arisc_set_wakeup_source(SET_ROOT_WAKEUP_SOURCE(d->hwirq));
else
arisc_clear_wakeup_source(SET_ROOT_WAKEUP_SOURCE(d->hwirq));
/*
* Do *not* call into the parent, as the GIC doesn't have any
* wake-up facility...
*/
return 0;
}
static struct irq_chip wakeupgen_chip = {
.name = "wakeupgen",
.irq_enable = irq_chip_enable_parent,
.irq_disable = irq_chip_disable_parent,
.irq_eoi = irq_chip_eoi_parent,
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_retrigger = irq_chip_retrigger_hierarchy,
.irq_set_wake = sunxi_irq_set_wake,
.irq_set_type = irq_chip_set_type_parent,
#ifdef CONFIG_SMP
.irq_set_affinity = irq_chip_set_affinity_parent,
#endif
};
static int sunxi_domain_translate(struct irq_domain *d,
struct irq_fwspec *fwspec,
unsigned long *hwirq,
unsigned int *type)
{
if (is_of_node(fwspec->fwnode)) {
if (fwspec->param_count != 3)
return -EINVAL;
/* No PPI should point to this domain */
if (fwspec->param[0] != 0)
return -EINVAL;
*hwirq = fwspec->param[1];
*type = fwspec->param[2];
return 0;
}
return -EINVAL;
}
static int sunxi_domain_alloc(struct irq_domain *domain,
unsigned int irq,
unsigned int nr_irqs, void *data)
{
struct irq_fwspec *fwspec = data;
struct irq_fwspec parent_fwspec;
irq_hw_number_t hwirq;
int i;
if (fwspec->param_count != 3)
return -EINVAL; /* Not GIC compliant */
if (fwspec->param[0] != 0)
return -EINVAL; /* No PPI should point to this domain */
hwirq = fwspec->param[1];
if (hwirq >= GIC_SUPPORT_IRQS)
return -EINVAL; /* Can't deal with this */
for (i = 0; i < nr_irqs; i++)
irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i,
&wakeupgen_chip, NULL);
parent_fwspec = *fwspec;
parent_fwspec.fwnode = domain->parent->fwnode;
return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs,
&parent_fwspec);
}
static const struct irq_domain_ops sunxi_domain_ops = {
.translate = sunxi_domain_translate,
.alloc = sunxi_domain_alloc,
.free = irq_domain_free_irqs_common,
};
static int __init wakeupgen_init(struct device_node *node,
struct device_node *parent)
{
struct irq_domain *parent_domain, *domain;
if (!parent) {
pr_err("%s: no parent, giving up\n", node->full_name);
return -ENODEV;
}
parent_domain = irq_find_host(parent);
if (!parent_domain) {
pr_err("%s: unable to obtain parent domain\n", node->full_name);
return -ENXIO;
}
domain = irq_domain_add_hierarchy(parent_domain, 0, GIC_SUPPORT_IRQS,
node, &sunxi_domain_ops,
NULL);
if (!domain) {
pr_err("%s: failed to allocated domain\n", node->full_name);
return -ENOMEM;
}
return 0;
}
IRQCHIP_DECLARE(sunxi_wakeupgen, "allwinner,sunxi-wakeupgen", wakeupgen_init);