18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Synopsys DW APB ICTL irqchip driver. 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * based on GPL'ed 2.6 kernel sources 78c2ecf20Sopenharmony_ci * (c) Marvell International Ltd. 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public 108c2ecf20Sopenharmony_ci * License version 2. This program is licensed "as is" without any 118c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied. 128c2ecf20Sopenharmony_ci */ 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <linux/io.h> 158c2ecf20Sopenharmony_ci#include <linux/irq.h> 168c2ecf20Sopenharmony_ci#include <linux/irqchip.h> 178c2ecf20Sopenharmony_ci#include <linux/irqchip/chained_irq.h> 188c2ecf20Sopenharmony_ci#include <linux/of_address.h> 198c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 208c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define APB_INT_ENABLE_L 0x00 238c2ecf20Sopenharmony_ci#define APB_INT_ENABLE_H 0x04 248c2ecf20Sopenharmony_ci#define APB_INT_MASK_L 0x08 258c2ecf20Sopenharmony_ci#define APB_INT_MASK_H 0x0c 268c2ecf20Sopenharmony_ci#define APB_INT_FINALSTATUS_L 0x30 278c2ecf20Sopenharmony_ci#define APB_INT_FINALSTATUS_H 0x34 288c2ecf20Sopenharmony_ci#define APB_INT_BASE_OFFSET 0x04 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* irq domain of the primary interrupt controller. */ 318c2ecf20Sopenharmony_cistatic struct irq_domain *dw_apb_ictl_irq_domain; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_cistatic void __irq_entry dw_apb_ictl_handle_irq(struct pt_regs *regs) 348c2ecf20Sopenharmony_ci{ 358c2ecf20Sopenharmony_ci struct irq_domain *d = dw_apb_ictl_irq_domain; 368c2ecf20Sopenharmony_ci int n; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci for (n = 0; n < d->revmap_size; n += 32) { 398c2ecf20Sopenharmony_ci struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, n); 408c2ecf20Sopenharmony_ci u32 stat = readl_relaxed(gc->reg_base + APB_INT_FINALSTATUS_L); 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci while (stat) { 438c2ecf20Sopenharmony_ci u32 hwirq = ffs(stat) - 1; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci handle_domain_irq(d, hwirq, regs); 468c2ecf20Sopenharmony_ci stat &= ~BIT(hwirq); 478c2ecf20Sopenharmony_ci } 488c2ecf20Sopenharmony_ci } 498c2ecf20Sopenharmony_ci} 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistatic void dw_apb_ictl_handle_irq_cascaded(struct irq_desc *desc) 528c2ecf20Sopenharmony_ci{ 538c2ecf20Sopenharmony_ci struct irq_domain *d = irq_desc_get_handler_data(desc); 548c2ecf20Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(desc); 558c2ecf20Sopenharmony_ci int n; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci chained_irq_enter(chip, desc); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci for (n = 0; n < d->revmap_size; n += 32) { 608c2ecf20Sopenharmony_ci struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, n); 618c2ecf20Sopenharmony_ci u32 stat = readl_relaxed(gc->reg_base + APB_INT_FINALSTATUS_L); 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci while (stat) { 648c2ecf20Sopenharmony_ci u32 hwirq = ffs(stat) - 1; 658c2ecf20Sopenharmony_ci u32 virq = irq_find_mapping(d, gc->irq_base + hwirq); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci generic_handle_irq(virq); 688c2ecf20Sopenharmony_ci stat &= ~BIT(hwirq); 698c2ecf20Sopenharmony_ci } 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci chained_irq_exit(chip, desc); 738c2ecf20Sopenharmony_ci} 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic int dw_apb_ictl_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, 768c2ecf20Sopenharmony_ci unsigned int nr_irqs, void *arg) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci int i, ret; 798c2ecf20Sopenharmony_ci irq_hw_number_t hwirq; 808c2ecf20Sopenharmony_ci unsigned int type = IRQ_TYPE_NONE; 818c2ecf20Sopenharmony_ci struct irq_fwspec *fwspec = arg; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci ret = irq_domain_translate_onecell(domain, fwspec, &hwirq, &type); 848c2ecf20Sopenharmony_ci if (ret) 858c2ecf20Sopenharmony_ci return ret; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci for (i = 0; i < nr_irqs; i++) 888c2ecf20Sopenharmony_ci irq_map_generic_chip(domain, virq + i, hwirq + i); 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci return 0; 918c2ecf20Sopenharmony_ci} 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistatic const struct irq_domain_ops dw_apb_ictl_irq_domain_ops = { 948c2ecf20Sopenharmony_ci .translate = irq_domain_translate_onecell, 958c2ecf20Sopenharmony_ci .alloc = dw_apb_ictl_irq_domain_alloc, 968c2ecf20Sopenharmony_ci .free = irq_domain_free_irqs_top, 978c2ecf20Sopenharmony_ci}; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 1008c2ecf20Sopenharmony_cistatic void dw_apb_ictl_resume(struct irq_data *d) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); 1038c2ecf20Sopenharmony_ci struct irq_chip_type *ct = irq_data_get_chip_type(d); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci irq_gc_lock(gc); 1068c2ecf20Sopenharmony_ci writel_relaxed(~0, gc->reg_base + ct->regs.enable); 1078c2ecf20Sopenharmony_ci writel_relaxed(*ct->mask_cache, gc->reg_base + ct->regs.mask); 1088c2ecf20Sopenharmony_ci irq_gc_unlock(gc); 1098c2ecf20Sopenharmony_ci} 1108c2ecf20Sopenharmony_ci#else 1118c2ecf20Sopenharmony_ci#define dw_apb_ictl_resume NULL 1128c2ecf20Sopenharmony_ci#endif /* CONFIG_PM */ 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic int __init dw_apb_ictl_init(struct device_node *np, 1158c2ecf20Sopenharmony_ci struct device_node *parent) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci const struct irq_domain_ops *domain_ops; 1188c2ecf20Sopenharmony_ci unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; 1198c2ecf20Sopenharmony_ci struct resource r; 1208c2ecf20Sopenharmony_ci struct irq_domain *domain; 1218c2ecf20Sopenharmony_ci struct irq_chip_generic *gc; 1228c2ecf20Sopenharmony_ci void __iomem *iobase; 1238c2ecf20Sopenharmony_ci int ret, nrirqs, parent_irq, i; 1248c2ecf20Sopenharmony_ci u32 reg; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci if (!parent) { 1278c2ecf20Sopenharmony_ci /* Used as the primary interrupt controller */ 1288c2ecf20Sopenharmony_ci parent_irq = 0; 1298c2ecf20Sopenharmony_ci domain_ops = &dw_apb_ictl_irq_domain_ops; 1308c2ecf20Sopenharmony_ci } else { 1318c2ecf20Sopenharmony_ci /* Map the parent interrupt for the chained handler */ 1328c2ecf20Sopenharmony_ci parent_irq = irq_of_parse_and_map(np, 0); 1338c2ecf20Sopenharmony_ci if (parent_irq <= 0) { 1348c2ecf20Sopenharmony_ci pr_err("%pOF: unable to parse irq\n", np); 1358c2ecf20Sopenharmony_ci return -EINVAL; 1368c2ecf20Sopenharmony_ci } 1378c2ecf20Sopenharmony_ci domain_ops = &irq_generic_chip_ops; 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci ret = of_address_to_resource(np, 0, &r); 1418c2ecf20Sopenharmony_ci if (ret) { 1428c2ecf20Sopenharmony_ci pr_err("%pOF: unable to get resource\n", np); 1438c2ecf20Sopenharmony_ci return ret; 1448c2ecf20Sopenharmony_ci } 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci if (!request_mem_region(r.start, resource_size(&r), np->full_name)) { 1478c2ecf20Sopenharmony_ci pr_err("%pOF: unable to request mem region\n", np); 1488c2ecf20Sopenharmony_ci return -ENOMEM; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci iobase = ioremap(r.start, resource_size(&r)); 1528c2ecf20Sopenharmony_ci if (!iobase) { 1538c2ecf20Sopenharmony_ci pr_err("%pOF: unable to map resource\n", np); 1548c2ecf20Sopenharmony_ci ret = -ENOMEM; 1558c2ecf20Sopenharmony_ci goto err_release; 1568c2ecf20Sopenharmony_ci } 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci /* 1598c2ecf20Sopenharmony_ci * DW IP can be configured to allow 2-64 irqs. We can determine 1608c2ecf20Sopenharmony_ci * the number of irqs supported by writing into enable register 1618c2ecf20Sopenharmony_ci * and look for bits not set, as corresponding flip-flops will 1628c2ecf20Sopenharmony_ci * have been removed by synthesis tool. 1638c2ecf20Sopenharmony_ci */ 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci /* mask and enable all interrupts */ 1668c2ecf20Sopenharmony_ci writel_relaxed(~0, iobase + APB_INT_MASK_L); 1678c2ecf20Sopenharmony_ci writel_relaxed(~0, iobase + APB_INT_MASK_H); 1688c2ecf20Sopenharmony_ci writel_relaxed(~0, iobase + APB_INT_ENABLE_L); 1698c2ecf20Sopenharmony_ci writel_relaxed(~0, iobase + APB_INT_ENABLE_H); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci reg = readl_relaxed(iobase + APB_INT_ENABLE_H); 1728c2ecf20Sopenharmony_ci if (reg) 1738c2ecf20Sopenharmony_ci nrirqs = 32 + fls(reg); 1748c2ecf20Sopenharmony_ci else 1758c2ecf20Sopenharmony_ci nrirqs = fls(readl_relaxed(iobase + APB_INT_ENABLE_L)); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci domain = irq_domain_add_linear(np, nrirqs, domain_ops, NULL); 1788c2ecf20Sopenharmony_ci if (!domain) { 1798c2ecf20Sopenharmony_ci pr_err("%pOF: unable to add irq domain\n", np); 1808c2ecf20Sopenharmony_ci ret = -ENOMEM; 1818c2ecf20Sopenharmony_ci goto err_unmap; 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci ret = irq_alloc_domain_generic_chips(domain, 32, 1, np->name, 1858c2ecf20Sopenharmony_ci handle_level_irq, clr, 0, 1868c2ecf20Sopenharmony_ci IRQ_GC_INIT_MASK_CACHE); 1878c2ecf20Sopenharmony_ci if (ret) { 1888c2ecf20Sopenharmony_ci pr_err("%pOF: unable to alloc irq domain gc\n", np); 1898c2ecf20Sopenharmony_ci goto err_unmap; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci for (i = 0; i < DIV_ROUND_UP(nrirqs, 32); i++) { 1938c2ecf20Sopenharmony_ci gc = irq_get_domain_generic_chip(domain, i * 32); 1948c2ecf20Sopenharmony_ci gc->reg_base = iobase + i * APB_INT_BASE_OFFSET; 1958c2ecf20Sopenharmony_ci gc->chip_types[0].regs.mask = APB_INT_MASK_L; 1968c2ecf20Sopenharmony_ci gc->chip_types[0].regs.enable = APB_INT_ENABLE_L; 1978c2ecf20Sopenharmony_ci gc->chip_types[0].chip.irq_mask = irq_gc_mask_set_bit; 1988c2ecf20Sopenharmony_ci gc->chip_types[0].chip.irq_unmask = irq_gc_mask_clr_bit; 1998c2ecf20Sopenharmony_ci gc->chip_types[0].chip.irq_resume = dw_apb_ictl_resume; 2008c2ecf20Sopenharmony_ci } 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci if (parent_irq) { 2038c2ecf20Sopenharmony_ci irq_set_chained_handler_and_data(parent_irq, 2048c2ecf20Sopenharmony_ci dw_apb_ictl_handle_irq_cascaded, domain); 2058c2ecf20Sopenharmony_ci } else { 2068c2ecf20Sopenharmony_ci dw_apb_ictl_irq_domain = domain; 2078c2ecf20Sopenharmony_ci set_handle_irq(dw_apb_ictl_handle_irq); 2088c2ecf20Sopenharmony_ci } 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci return 0; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_cierr_unmap: 2138c2ecf20Sopenharmony_ci iounmap(iobase); 2148c2ecf20Sopenharmony_cierr_release: 2158c2ecf20Sopenharmony_ci release_mem_region(r.start, resource_size(&r)); 2168c2ecf20Sopenharmony_ci return ret; 2178c2ecf20Sopenharmony_ci} 2188c2ecf20Sopenharmony_ciIRQCHIP_DECLARE(dw_apb_ictl, 2198c2ecf20Sopenharmony_ci "snps,dw-apb-ictl", dw_apb_ictl_init); 220