/* * 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 * * 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 #include #include #include #include #include #include #include #include #include #include #include #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);