1350d71b9SSebastian Hesselbarth /* 2350d71b9SSebastian Hesselbarth * Synopsys DW APB ICTL irqchip driver. 3350d71b9SSebastian Hesselbarth * 4350d71b9SSebastian Hesselbarth * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> 5350d71b9SSebastian Hesselbarth * 6350d71b9SSebastian Hesselbarth * based on GPL'ed 2.6 kernel sources 7350d71b9SSebastian Hesselbarth * (c) Marvell International Ltd. 8350d71b9SSebastian Hesselbarth * 9350d71b9SSebastian Hesselbarth * This file is licensed under the terms of the GNU General Public 10350d71b9SSebastian Hesselbarth * License version 2. This program is licensed "as is" without any 11350d71b9SSebastian Hesselbarth * warranty of any kind, whether express or implied. 12350d71b9SSebastian Hesselbarth */ 13350d71b9SSebastian Hesselbarth 14350d71b9SSebastian Hesselbarth #include <linux/io.h> 15350d71b9SSebastian Hesselbarth #include <linux/irq.h> 1641a83e06SJoel Porquet #include <linux/irqchip.h> 17350d71b9SSebastian Hesselbarth #include <linux/irqchip/chained_irq.h> 18350d71b9SSebastian Hesselbarth #include <linux/of_address.h> 19350d71b9SSebastian Hesselbarth #include <linux/of_irq.h> 2054a38440SZhen Lei #include <linux/interrupt.h> 21350d71b9SSebastian Hesselbarth 22350d71b9SSebastian Hesselbarth #define APB_INT_ENABLE_L 0x00 23350d71b9SSebastian Hesselbarth #define APB_INT_ENABLE_H 0x04 24350d71b9SSebastian Hesselbarth #define APB_INT_MASK_L 0x08 25350d71b9SSebastian Hesselbarth #define APB_INT_MASK_H 0x0c 26350d71b9SSebastian Hesselbarth #define APB_INT_FINALSTATUS_L 0x30 27350d71b9SSebastian Hesselbarth #define APB_INT_FINALSTATUS_H 0x34 28b6623118SThomas Gleixner #define APB_INT_BASE_OFFSET 0x04 29350d71b9SSebastian Hesselbarth 3054a38440SZhen Lei /* irq domain of the primary interrupt controller. */ 3154a38440SZhen Lei static struct irq_domain *dw_apb_ictl_irq_domain; 3254a38440SZhen Lei 3354a38440SZhen Lei static void __irq_entry dw_apb_ictl_handle_irq(struct pt_regs *regs) 3454a38440SZhen Lei { 3554a38440SZhen Lei struct irq_domain *d = dw_apb_ictl_irq_domain; 3654a38440SZhen Lei int n; 3754a38440SZhen Lei 3854a38440SZhen Lei for (n = 0; n < d->revmap_size; n += 32) { 3954a38440SZhen Lei struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, n); 4054a38440SZhen Lei u32 stat = readl_relaxed(gc->reg_base + APB_INT_FINALSTATUS_L); 4154a38440SZhen Lei 4254a38440SZhen Lei while (stat) { 4354a38440SZhen Lei u32 hwirq = ffs(stat) - 1; 4454a38440SZhen Lei 450953fb26SMark Rutland generic_handle_domain_irq(d, hwirq); 4654a38440SZhen Lei stat &= ~BIT(hwirq); 4754a38440SZhen Lei } 4854a38440SZhen Lei } 4954a38440SZhen Lei } 5054a38440SZhen Lei 51d59f7d15SZhen Lei static void dw_apb_ictl_handle_irq_cascaded(struct irq_desc *desc) 52350d71b9SSebastian Hesselbarth { 53b6623118SThomas Gleixner struct irq_domain *d = irq_desc_get_handler_data(desc); 54b6623118SThomas Gleixner struct irq_chip *chip = irq_desc_get_chip(desc); 55350d71b9SSebastian Hesselbarth int n; 56350d71b9SSebastian Hesselbarth 57350d71b9SSebastian Hesselbarth chained_irq_enter(chip, desc); 58350d71b9SSebastian Hesselbarth 59b6623118SThomas Gleixner for (n = 0; n < d->revmap_size; n += 32) { 60b6623118SThomas Gleixner struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, n); 61b6623118SThomas Gleixner u32 stat = readl_relaxed(gc->reg_base + APB_INT_FINALSTATUS_L); 62b6623118SThomas Gleixner 63350d71b9SSebastian Hesselbarth while (stat) { 64350d71b9SSebastian Hesselbarth u32 hwirq = ffs(stat) - 1; 65046a6ee2SMarc Zyngier generic_handle_domain_irq(d, gc->irq_base + hwirq); 66b6623118SThomas Gleixner 67d59f7d15SZhen Lei stat &= ~BIT(hwirq); 68350d71b9SSebastian Hesselbarth } 69350d71b9SSebastian Hesselbarth } 70350d71b9SSebastian Hesselbarth 71350d71b9SSebastian Hesselbarth chained_irq_exit(chip, desc); 72350d71b9SSebastian Hesselbarth } 73350d71b9SSebastian Hesselbarth 7454a38440SZhen Lei static int dw_apb_ictl_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, 7554a38440SZhen Lei unsigned int nr_irqs, void *arg) 7654a38440SZhen Lei { 7754a38440SZhen Lei int i, ret; 7854a38440SZhen Lei irq_hw_number_t hwirq; 7954a38440SZhen Lei unsigned int type = IRQ_TYPE_NONE; 8054a38440SZhen Lei struct irq_fwspec *fwspec = arg; 8154a38440SZhen Lei 8254a38440SZhen Lei ret = irq_domain_translate_onecell(domain, fwspec, &hwirq, &type); 8354a38440SZhen Lei if (ret) 8454a38440SZhen Lei return ret; 8554a38440SZhen Lei 8654a38440SZhen Lei for (i = 0; i < nr_irqs; i++) 8754a38440SZhen Lei irq_map_generic_chip(domain, virq + i, hwirq + i); 8854a38440SZhen Lei 8954a38440SZhen Lei return 0; 9054a38440SZhen Lei } 9154a38440SZhen Lei 9254a38440SZhen Lei static const struct irq_domain_ops dw_apb_ictl_irq_domain_ops = { 9354a38440SZhen Lei .translate = irq_domain_translate_onecell, 9454a38440SZhen Lei .alloc = dw_apb_ictl_irq_domain_alloc, 9554a38440SZhen Lei .free = irq_domain_free_irqs_top, 9654a38440SZhen Lei }; 9754a38440SZhen Lei 981655b053SJisheng Zhang #ifdef CONFIG_PM 991655b053SJisheng Zhang static void dw_apb_ictl_resume(struct irq_data *d) 1001655b053SJisheng Zhang { 1011655b053SJisheng Zhang struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); 1021655b053SJisheng Zhang struct irq_chip_type *ct = irq_data_get_chip_type(d); 1031655b053SJisheng Zhang 1041655b053SJisheng Zhang irq_gc_lock(gc); 1051655b053SJisheng Zhang writel_relaxed(~0, gc->reg_base + ct->regs.enable); 1061655b053SJisheng Zhang writel_relaxed(*ct->mask_cache, gc->reg_base + ct->regs.mask); 1071655b053SJisheng Zhang irq_gc_unlock(gc); 1081655b053SJisheng Zhang } 1091655b053SJisheng Zhang #else 1101655b053SJisheng Zhang #define dw_apb_ictl_resume NULL 1111655b053SJisheng Zhang #endif /* CONFIG_PM */ 1121655b053SJisheng Zhang 113350d71b9SSebastian Hesselbarth static int __init dw_apb_ictl_init(struct device_node *np, 114350d71b9SSebastian Hesselbarth struct device_node *parent) 115350d71b9SSebastian Hesselbarth { 116d59f7d15SZhen Lei const struct irq_domain_ops *domain_ops; 117350d71b9SSebastian Hesselbarth unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; 118350d71b9SSebastian Hesselbarth struct resource r; 119350d71b9SSebastian Hesselbarth struct irq_domain *domain; 120350d71b9SSebastian Hesselbarth struct irq_chip_generic *gc; 121350d71b9SSebastian Hesselbarth void __iomem *iobase; 122d59f7d15SZhen Lei int ret, nrirqs, parent_irq, i; 123350d71b9SSebastian Hesselbarth u32 reg; 124350d71b9SSebastian Hesselbarth 12554a38440SZhen Lei if (!parent) { 12654a38440SZhen Lei /* Used as the primary interrupt controller */ 12754a38440SZhen Lei parent_irq = 0; 12854a38440SZhen Lei domain_ops = &dw_apb_ictl_irq_domain_ops; 12954a38440SZhen Lei } else { 130350d71b9SSebastian Hesselbarth /* Map the parent interrupt for the chained handler */ 131d59f7d15SZhen Lei parent_irq = irq_of_parse_and_map(np, 0); 132d59f7d15SZhen Lei if (parent_irq <= 0) { 133e81f54c6SRob Herring pr_err("%pOF: unable to parse irq\n", np); 134350d71b9SSebastian Hesselbarth return -EINVAL; 135350d71b9SSebastian Hesselbarth } 13654a38440SZhen Lei domain_ops = &irq_generic_chip_ops; 13754a38440SZhen Lei } 138350d71b9SSebastian Hesselbarth 139350d71b9SSebastian Hesselbarth ret = of_address_to_resource(np, 0, &r); 140350d71b9SSebastian Hesselbarth if (ret) { 141e81f54c6SRob Herring pr_err("%pOF: unable to get resource\n", np); 142350d71b9SSebastian Hesselbarth return ret; 143350d71b9SSebastian Hesselbarth } 144350d71b9SSebastian Hesselbarth 145350d71b9SSebastian Hesselbarth if (!request_mem_region(r.start, resource_size(&r), np->full_name)) { 146e81f54c6SRob Herring pr_err("%pOF: unable to request mem region\n", np); 147350d71b9SSebastian Hesselbarth return -ENOMEM; 148350d71b9SSebastian Hesselbarth } 149350d71b9SSebastian Hesselbarth 150350d71b9SSebastian Hesselbarth iobase = ioremap(r.start, resource_size(&r)); 151350d71b9SSebastian Hesselbarth if (!iobase) { 152e81f54c6SRob Herring pr_err("%pOF: unable to map resource\n", np); 153350d71b9SSebastian Hesselbarth ret = -ENOMEM; 154350d71b9SSebastian Hesselbarth goto err_release; 155350d71b9SSebastian Hesselbarth } 156350d71b9SSebastian Hesselbarth 157350d71b9SSebastian Hesselbarth /* 158350d71b9SSebastian Hesselbarth * DW IP can be configured to allow 2-64 irqs. We can determine 159350d71b9SSebastian Hesselbarth * the number of irqs supported by writing into enable register 160350d71b9SSebastian Hesselbarth * and look for bits not set, as corresponding flip-flops will 161c5f48c0aSIngo Molnar * have been removed by synthesis tool. 162350d71b9SSebastian Hesselbarth */ 163350d71b9SSebastian Hesselbarth 164350d71b9SSebastian Hesselbarth /* mask and enable all interrupts */ 1658876ce7dSJisheng Zhang writel_relaxed(~0, iobase + APB_INT_MASK_L); 1668876ce7dSJisheng Zhang writel_relaxed(~0, iobase + APB_INT_MASK_H); 1678876ce7dSJisheng Zhang writel_relaxed(~0, iobase + APB_INT_ENABLE_L); 1688876ce7dSJisheng Zhang writel_relaxed(~0, iobase + APB_INT_ENABLE_H); 169350d71b9SSebastian Hesselbarth 1708876ce7dSJisheng Zhang reg = readl_relaxed(iobase + APB_INT_ENABLE_H); 171350d71b9SSebastian Hesselbarth if (reg) 172350d71b9SSebastian Hesselbarth nrirqs = 32 + fls(reg); 173350d71b9SSebastian Hesselbarth else 1748876ce7dSJisheng Zhang nrirqs = fls(readl_relaxed(iobase + APB_INT_ENABLE_L)); 175350d71b9SSebastian Hesselbarth 176d59f7d15SZhen Lei domain = irq_domain_add_linear(np, nrirqs, domain_ops, NULL); 177350d71b9SSebastian Hesselbarth if (!domain) { 178e81f54c6SRob Herring pr_err("%pOF: unable to add irq domain\n", np); 179350d71b9SSebastian Hesselbarth ret = -ENOMEM; 180350d71b9SSebastian Hesselbarth goto err_unmap; 181350d71b9SSebastian Hesselbarth } 182350d71b9SSebastian Hesselbarth 183b6623118SThomas Gleixner ret = irq_alloc_domain_generic_chips(domain, 32, 1, np->name, 184b6623118SThomas Gleixner handle_level_irq, clr, 0, 185350d71b9SSebastian Hesselbarth IRQ_GC_INIT_MASK_CACHE); 186350d71b9SSebastian Hesselbarth if (ret) { 187e81f54c6SRob Herring pr_err("%pOF: unable to alloc irq domain gc\n", np); 188350d71b9SSebastian Hesselbarth goto err_unmap; 189350d71b9SSebastian Hesselbarth } 190350d71b9SSebastian Hesselbarth 191b6623118SThomas Gleixner for (i = 0; i < DIV_ROUND_UP(nrirqs, 32); i++) { 192b6623118SThomas Gleixner gc = irq_get_domain_generic_chip(domain, i * 32); 193b6623118SThomas Gleixner gc->reg_base = iobase + i * APB_INT_BASE_OFFSET; 194350d71b9SSebastian Hesselbarth gc->chip_types[0].regs.mask = APB_INT_MASK_L; 1951655b053SJisheng Zhang gc->chip_types[0].regs.enable = APB_INT_ENABLE_L; 196350d71b9SSebastian Hesselbarth gc->chip_types[0].chip.irq_mask = irq_gc_mask_set_bit; 197350d71b9SSebastian Hesselbarth gc->chip_types[0].chip.irq_unmask = irq_gc_mask_clr_bit; 1981655b053SJisheng Zhang gc->chip_types[0].chip.irq_resume = dw_apb_ictl_resume; 199350d71b9SSebastian Hesselbarth } 200350d71b9SSebastian Hesselbarth 20154a38440SZhen Lei if (parent_irq) { 202d59f7d15SZhen Lei irq_set_chained_handler_and_data(parent_irq, 203d59f7d15SZhen Lei dw_apb_ictl_handle_irq_cascaded, domain); 20454a38440SZhen Lei } else { 20554a38440SZhen Lei dw_apb_ictl_irq_domain = domain; 20654a38440SZhen Lei set_handle_irq(dw_apb_ictl_handle_irq); 20754a38440SZhen Lei } 208350d71b9SSebastian Hesselbarth 209350d71b9SSebastian Hesselbarth return 0; 210350d71b9SSebastian Hesselbarth 211350d71b9SSebastian Hesselbarth err_unmap: 212350d71b9SSebastian Hesselbarth iounmap(iobase); 213350d71b9SSebastian Hesselbarth err_release: 214350d71b9SSebastian Hesselbarth release_mem_region(r.start, resource_size(&r)); 215350d71b9SSebastian Hesselbarth return ret; 216350d71b9SSebastian Hesselbarth } 217350d71b9SSebastian Hesselbarth IRQCHIP_DECLARE(dw_apb_ictl, 218350d71b9SSebastian Hesselbarth "snps,dw-apb-ictl", dw_apb_ictl_init); 219