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