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 
dw_apb_ictl_handle_irq(struct pt_regs * regs)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 
45*0953fb26SMark Rutland 			generic_handle_domain_irq(d, hwirq);
4654a38440SZhen Lei 			stat &= ~BIT(hwirq);
4754a38440SZhen Lei 		}
4854a38440SZhen Lei 	}
4954a38440SZhen Lei }
5054a38440SZhen Lei 
dw_apb_ictl_handle_irq_cascaded(struct irq_desc * desc)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 
dw_apb_ictl_irq_domain_alloc(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs,void * arg)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
dw_apb_ictl_resume(struct irq_data * d)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 
dw_apb_ictl_init(struct device_node * np,struct device_node * parent)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