162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * Synopsys DW APB ICTL irqchip driver. 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * based on GPL'ed 2.6 kernel sources 762306a36Sopenharmony_ci * (c) Marvell International Ltd. 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * This file is licensed under the terms of the GNU General Public 1062306a36Sopenharmony_ci * License version 2. This program is licensed "as is" without any 1162306a36Sopenharmony_ci * warranty of any kind, whether express or implied. 1262306a36Sopenharmony_ci */ 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <linux/io.h> 1562306a36Sopenharmony_ci#include <linux/irq.h> 1662306a36Sopenharmony_ci#include <linux/irqchip.h> 1762306a36Sopenharmony_ci#include <linux/irqchip/chained_irq.h> 1862306a36Sopenharmony_ci#include <linux/of_address.h> 1962306a36Sopenharmony_ci#include <linux/of_irq.h> 2062306a36Sopenharmony_ci#include <linux/interrupt.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define APB_INT_ENABLE_L 0x00 2362306a36Sopenharmony_ci#define APB_INT_ENABLE_H 0x04 2462306a36Sopenharmony_ci#define APB_INT_MASK_L 0x08 2562306a36Sopenharmony_ci#define APB_INT_MASK_H 0x0c 2662306a36Sopenharmony_ci#define APB_INT_FINALSTATUS_L 0x30 2762306a36Sopenharmony_ci#define APB_INT_FINALSTATUS_H 0x34 2862306a36Sopenharmony_ci#define APB_INT_BASE_OFFSET 0x04 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci/* irq domain of the primary interrupt controller. */ 3162306a36Sopenharmony_cistatic struct irq_domain *dw_apb_ictl_irq_domain; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic void __irq_entry dw_apb_ictl_handle_irq(struct pt_regs *regs) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci struct irq_domain *d = dw_apb_ictl_irq_domain; 3662306a36Sopenharmony_ci int n; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci for (n = 0; n < d->revmap_size; n += 32) { 3962306a36Sopenharmony_ci struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, n); 4062306a36Sopenharmony_ci u32 stat = readl_relaxed(gc->reg_base + APB_INT_FINALSTATUS_L); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci while (stat) { 4362306a36Sopenharmony_ci u32 hwirq = ffs(stat) - 1; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci generic_handle_domain_irq(d, hwirq); 4662306a36Sopenharmony_ci stat &= ~BIT(hwirq); 4762306a36Sopenharmony_ci } 4862306a36Sopenharmony_ci } 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic void dw_apb_ictl_handle_irq_cascaded(struct irq_desc *desc) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci struct irq_domain *d = irq_desc_get_handler_data(desc); 5462306a36Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(desc); 5562306a36Sopenharmony_ci int n; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci chained_irq_enter(chip, desc); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci for (n = 0; n < d->revmap_size; n += 32) { 6062306a36Sopenharmony_ci struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, n); 6162306a36Sopenharmony_ci u32 stat = readl_relaxed(gc->reg_base + APB_INT_FINALSTATUS_L); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci while (stat) { 6462306a36Sopenharmony_ci u32 hwirq = ffs(stat) - 1; 6562306a36Sopenharmony_ci generic_handle_domain_irq(d, gc->irq_base + hwirq); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci stat &= ~BIT(hwirq); 6862306a36Sopenharmony_ci } 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci chained_irq_exit(chip, desc); 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic int dw_apb_ictl_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, 7562306a36Sopenharmony_ci unsigned int nr_irqs, void *arg) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci int i, ret; 7862306a36Sopenharmony_ci irq_hw_number_t hwirq; 7962306a36Sopenharmony_ci unsigned int type = IRQ_TYPE_NONE; 8062306a36Sopenharmony_ci struct irq_fwspec *fwspec = arg; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci ret = irq_domain_translate_onecell(domain, fwspec, &hwirq, &type); 8362306a36Sopenharmony_ci if (ret) 8462306a36Sopenharmony_ci return ret; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci for (i = 0; i < nr_irqs; i++) 8762306a36Sopenharmony_ci irq_map_generic_chip(domain, virq + i, hwirq + i); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci return 0; 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistatic const struct irq_domain_ops dw_apb_ictl_irq_domain_ops = { 9362306a36Sopenharmony_ci .translate = irq_domain_translate_onecell, 9462306a36Sopenharmony_ci .alloc = dw_apb_ictl_irq_domain_alloc, 9562306a36Sopenharmony_ci .free = irq_domain_free_irqs_top, 9662306a36Sopenharmony_ci}; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci#ifdef CONFIG_PM 9962306a36Sopenharmony_cistatic void dw_apb_ictl_resume(struct irq_data *d) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); 10262306a36Sopenharmony_ci struct irq_chip_type *ct = irq_data_get_chip_type(d); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci irq_gc_lock(gc); 10562306a36Sopenharmony_ci writel_relaxed(~0, gc->reg_base + ct->regs.enable); 10662306a36Sopenharmony_ci writel_relaxed(*ct->mask_cache, gc->reg_base + ct->regs.mask); 10762306a36Sopenharmony_ci irq_gc_unlock(gc); 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci#else 11062306a36Sopenharmony_ci#define dw_apb_ictl_resume NULL 11162306a36Sopenharmony_ci#endif /* CONFIG_PM */ 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistatic int __init dw_apb_ictl_init(struct device_node *np, 11462306a36Sopenharmony_ci struct device_node *parent) 11562306a36Sopenharmony_ci{ 11662306a36Sopenharmony_ci const struct irq_domain_ops *domain_ops; 11762306a36Sopenharmony_ci unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; 11862306a36Sopenharmony_ci struct resource r; 11962306a36Sopenharmony_ci struct irq_domain *domain; 12062306a36Sopenharmony_ci struct irq_chip_generic *gc; 12162306a36Sopenharmony_ci void __iomem *iobase; 12262306a36Sopenharmony_ci int ret, nrirqs, parent_irq, i; 12362306a36Sopenharmony_ci u32 reg; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (!parent) { 12662306a36Sopenharmony_ci /* Used as the primary interrupt controller */ 12762306a36Sopenharmony_ci parent_irq = 0; 12862306a36Sopenharmony_ci domain_ops = &dw_apb_ictl_irq_domain_ops; 12962306a36Sopenharmony_ci } else { 13062306a36Sopenharmony_ci /* Map the parent interrupt for the chained handler */ 13162306a36Sopenharmony_ci parent_irq = irq_of_parse_and_map(np, 0); 13262306a36Sopenharmony_ci if (parent_irq <= 0) { 13362306a36Sopenharmony_ci pr_err("%pOF: unable to parse irq\n", np); 13462306a36Sopenharmony_ci return -EINVAL; 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci domain_ops = &irq_generic_chip_ops; 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci ret = of_address_to_resource(np, 0, &r); 14062306a36Sopenharmony_ci if (ret) { 14162306a36Sopenharmony_ci pr_err("%pOF: unable to get resource\n", np); 14262306a36Sopenharmony_ci return ret; 14362306a36Sopenharmony_ci } 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci if (!request_mem_region(r.start, resource_size(&r), np->full_name)) { 14662306a36Sopenharmony_ci pr_err("%pOF: unable to request mem region\n", np); 14762306a36Sopenharmony_ci return -ENOMEM; 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci iobase = ioremap(r.start, resource_size(&r)); 15162306a36Sopenharmony_ci if (!iobase) { 15262306a36Sopenharmony_ci pr_err("%pOF: unable to map resource\n", np); 15362306a36Sopenharmony_ci ret = -ENOMEM; 15462306a36Sopenharmony_ci goto err_release; 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci /* 15862306a36Sopenharmony_ci * DW IP can be configured to allow 2-64 irqs. We can determine 15962306a36Sopenharmony_ci * the number of irqs supported by writing into enable register 16062306a36Sopenharmony_ci * and look for bits not set, as corresponding flip-flops will 16162306a36Sopenharmony_ci * have been removed by synthesis tool. 16262306a36Sopenharmony_ci */ 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci /* mask and enable all interrupts */ 16562306a36Sopenharmony_ci writel_relaxed(~0, iobase + APB_INT_MASK_L); 16662306a36Sopenharmony_ci writel_relaxed(~0, iobase + APB_INT_MASK_H); 16762306a36Sopenharmony_ci writel_relaxed(~0, iobase + APB_INT_ENABLE_L); 16862306a36Sopenharmony_ci writel_relaxed(~0, iobase + APB_INT_ENABLE_H); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci reg = readl_relaxed(iobase + APB_INT_ENABLE_H); 17162306a36Sopenharmony_ci if (reg) 17262306a36Sopenharmony_ci nrirqs = 32 + fls(reg); 17362306a36Sopenharmony_ci else 17462306a36Sopenharmony_ci nrirqs = fls(readl_relaxed(iobase + APB_INT_ENABLE_L)); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci domain = irq_domain_add_linear(np, nrirqs, domain_ops, NULL); 17762306a36Sopenharmony_ci if (!domain) { 17862306a36Sopenharmony_ci pr_err("%pOF: unable to add irq domain\n", np); 17962306a36Sopenharmony_ci ret = -ENOMEM; 18062306a36Sopenharmony_ci goto err_unmap; 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci ret = irq_alloc_domain_generic_chips(domain, 32, 1, np->name, 18462306a36Sopenharmony_ci handle_level_irq, clr, 0, 18562306a36Sopenharmony_ci IRQ_GC_INIT_MASK_CACHE); 18662306a36Sopenharmony_ci if (ret) { 18762306a36Sopenharmony_ci pr_err("%pOF: unable to alloc irq domain gc\n", np); 18862306a36Sopenharmony_ci goto err_unmap; 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci for (i = 0; i < DIV_ROUND_UP(nrirqs, 32); i++) { 19262306a36Sopenharmony_ci gc = irq_get_domain_generic_chip(domain, i * 32); 19362306a36Sopenharmony_ci gc->reg_base = iobase + i * APB_INT_BASE_OFFSET; 19462306a36Sopenharmony_ci gc->chip_types[0].regs.mask = APB_INT_MASK_L; 19562306a36Sopenharmony_ci gc->chip_types[0].regs.enable = APB_INT_ENABLE_L; 19662306a36Sopenharmony_ci gc->chip_types[0].chip.irq_mask = irq_gc_mask_set_bit; 19762306a36Sopenharmony_ci gc->chip_types[0].chip.irq_unmask = irq_gc_mask_clr_bit; 19862306a36Sopenharmony_ci gc->chip_types[0].chip.irq_resume = dw_apb_ictl_resume; 19962306a36Sopenharmony_ci } 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci if (parent_irq) { 20262306a36Sopenharmony_ci irq_set_chained_handler_and_data(parent_irq, 20362306a36Sopenharmony_ci dw_apb_ictl_handle_irq_cascaded, domain); 20462306a36Sopenharmony_ci } else { 20562306a36Sopenharmony_ci dw_apb_ictl_irq_domain = domain; 20662306a36Sopenharmony_ci set_handle_irq(dw_apb_ictl_handle_irq); 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci return 0; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_cierr_unmap: 21262306a36Sopenharmony_ci iounmap(iobase); 21362306a36Sopenharmony_cierr_release: 21462306a36Sopenharmony_ci release_mem_region(r.start, resource_size(&r)); 21562306a36Sopenharmony_ci return ret; 21662306a36Sopenharmony_ci} 21762306a36Sopenharmony_ciIRQCHIP_DECLARE(dw_apb_ictl, 21862306a36Sopenharmony_ci "snps,dw-apb-ictl", dw_apb_ictl_init); 219