162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2015-2016 Vladimir Zapolskiy <vz@mleia.com>
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#define pr_fmt(fmt) "%s: " fmt, __func__
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/io.h>
962306a36Sopenharmony_ci#include <linux/irqchip.h>
1062306a36Sopenharmony_ci#include <linux/irqchip/chained_irq.h>
1162306a36Sopenharmony_ci#include <linux/of_address.h>
1262306a36Sopenharmony_ci#include <linux/of_irq.h>
1362306a36Sopenharmony_ci#include <linux/of_platform.h>
1462306a36Sopenharmony_ci#include <linux/seq_file.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci#include <asm/exception.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define LPC32XX_INTC_MASK		0x00
1962306a36Sopenharmony_ci#define LPC32XX_INTC_RAW		0x04
2062306a36Sopenharmony_ci#define LPC32XX_INTC_STAT		0x08
2162306a36Sopenharmony_ci#define LPC32XX_INTC_POL		0x0C
2262306a36Sopenharmony_ci#define LPC32XX_INTC_TYPE		0x10
2362306a36Sopenharmony_ci#define LPC32XX_INTC_FIQ		0x14
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define NR_LPC32XX_IC_IRQS		32
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistruct lpc32xx_irq_chip {
2862306a36Sopenharmony_ci	void __iomem *base;
2962306a36Sopenharmony_ci	phys_addr_t addr;
3062306a36Sopenharmony_ci	struct irq_domain *domain;
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic struct lpc32xx_irq_chip *lpc32xx_mic_irqc;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic inline u32 lpc32xx_ic_read(struct lpc32xx_irq_chip *ic, u32 reg)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	return readl_relaxed(ic->base + reg);
3862306a36Sopenharmony_ci}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic inline void lpc32xx_ic_write(struct lpc32xx_irq_chip *ic,
4162306a36Sopenharmony_ci				    u32 reg, u32 val)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	writel_relaxed(val, ic->base + reg);
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic void lpc32xx_irq_mask(struct irq_data *d)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	struct lpc32xx_irq_chip *ic = irq_data_get_irq_chip_data(d);
4962306a36Sopenharmony_ci	u32 val, mask = BIT(d->hwirq);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	val = lpc32xx_ic_read(ic, LPC32XX_INTC_MASK) & ~mask;
5262306a36Sopenharmony_ci	lpc32xx_ic_write(ic, LPC32XX_INTC_MASK, val);
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic void lpc32xx_irq_unmask(struct irq_data *d)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	struct lpc32xx_irq_chip *ic = irq_data_get_irq_chip_data(d);
5862306a36Sopenharmony_ci	u32 val, mask = BIT(d->hwirq);
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	val = lpc32xx_ic_read(ic, LPC32XX_INTC_MASK) | mask;
6162306a36Sopenharmony_ci	lpc32xx_ic_write(ic, LPC32XX_INTC_MASK, val);
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic void lpc32xx_irq_ack(struct irq_data *d)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	struct lpc32xx_irq_chip *ic = irq_data_get_irq_chip_data(d);
6762306a36Sopenharmony_ci	u32 mask = BIT(d->hwirq);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	lpc32xx_ic_write(ic, LPC32XX_INTC_RAW, mask);
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic int lpc32xx_irq_set_type(struct irq_data *d, unsigned int type)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	struct lpc32xx_irq_chip *ic = irq_data_get_irq_chip_data(d);
7562306a36Sopenharmony_ci	u32 val, mask = BIT(d->hwirq);
7662306a36Sopenharmony_ci	bool high, edge;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	switch (type) {
7962306a36Sopenharmony_ci	case IRQ_TYPE_EDGE_RISING:
8062306a36Sopenharmony_ci		edge = true;
8162306a36Sopenharmony_ci		high = true;
8262306a36Sopenharmony_ci		break;
8362306a36Sopenharmony_ci	case IRQ_TYPE_EDGE_FALLING:
8462306a36Sopenharmony_ci		edge = true;
8562306a36Sopenharmony_ci		high = false;
8662306a36Sopenharmony_ci		break;
8762306a36Sopenharmony_ci	case IRQ_TYPE_LEVEL_HIGH:
8862306a36Sopenharmony_ci		edge = false;
8962306a36Sopenharmony_ci		high = true;
9062306a36Sopenharmony_ci		break;
9162306a36Sopenharmony_ci	case IRQ_TYPE_LEVEL_LOW:
9262306a36Sopenharmony_ci		edge = false;
9362306a36Sopenharmony_ci		high = false;
9462306a36Sopenharmony_ci		break;
9562306a36Sopenharmony_ci	default:
9662306a36Sopenharmony_ci		pr_info("unsupported irq type %d\n", type);
9762306a36Sopenharmony_ci		return -EINVAL;
9862306a36Sopenharmony_ci	}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	irqd_set_trigger_type(d, type);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	val = lpc32xx_ic_read(ic, LPC32XX_INTC_POL);
10362306a36Sopenharmony_ci	if (high)
10462306a36Sopenharmony_ci		val |= mask;
10562306a36Sopenharmony_ci	else
10662306a36Sopenharmony_ci		val &= ~mask;
10762306a36Sopenharmony_ci	lpc32xx_ic_write(ic, LPC32XX_INTC_POL, val);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	val = lpc32xx_ic_read(ic, LPC32XX_INTC_TYPE);
11062306a36Sopenharmony_ci	if (edge) {
11162306a36Sopenharmony_ci		val |= mask;
11262306a36Sopenharmony_ci		irq_set_handler_locked(d, handle_edge_irq);
11362306a36Sopenharmony_ci	} else {
11462306a36Sopenharmony_ci		val &= ~mask;
11562306a36Sopenharmony_ci		irq_set_handler_locked(d, handle_level_irq);
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci	lpc32xx_ic_write(ic, LPC32XX_INTC_TYPE, val);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	return 0;
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic void lpc32xx_irq_print_chip(struct irq_data *d, struct seq_file *p)
12362306a36Sopenharmony_ci{
12462306a36Sopenharmony_ci	struct lpc32xx_irq_chip *ic = irq_data_get_irq_chip_data(d);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	if (ic == lpc32xx_mic_irqc)
12762306a36Sopenharmony_ci		seq_printf(p, "%08x.mic", ic->addr);
12862306a36Sopenharmony_ci	else
12962306a36Sopenharmony_ci		seq_printf(p, "%08x.sic", ic->addr);
13062306a36Sopenharmony_ci}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic const struct irq_chip lpc32xx_chip = {
13362306a36Sopenharmony_ci	.irq_ack	= lpc32xx_irq_ack,
13462306a36Sopenharmony_ci	.irq_mask	= lpc32xx_irq_mask,
13562306a36Sopenharmony_ci	.irq_unmask	= lpc32xx_irq_unmask,
13662306a36Sopenharmony_ci	.irq_set_type	= lpc32xx_irq_set_type,
13762306a36Sopenharmony_ci	.irq_print_chip	= lpc32xx_irq_print_chip,
13862306a36Sopenharmony_ci};
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic void __exception_irq_entry lpc32xx_handle_irq(struct pt_regs *regs)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	struct lpc32xx_irq_chip *ic = lpc32xx_mic_irqc;
14362306a36Sopenharmony_ci	u32 hwirq = lpc32xx_ic_read(ic, LPC32XX_INTC_STAT), irq;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	while (hwirq) {
14662306a36Sopenharmony_ci		irq = __ffs(hwirq);
14762306a36Sopenharmony_ci		hwirq &= ~BIT(irq);
14862306a36Sopenharmony_ci		generic_handle_domain_irq(lpc32xx_mic_irqc->domain, irq);
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic void lpc32xx_sic_handler(struct irq_desc *desc)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	struct lpc32xx_irq_chip *ic = irq_desc_get_handler_data(desc);
15562306a36Sopenharmony_ci	struct irq_chip *chip = irq_desc_get_chip(desc);
15662306a36Sopenharmony_ci	u32 hwirq = lpc32xx_ic_read(ic, LPC32XX_INTC_STAT), irq;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	chained_irq_enter(chip, desc);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	while (hwirq) {
16162306a36Sopenharmony_ci		irq = __ffs(hwirq);
16262306a36Sopenharmony_ci		hwirq &= ~BIT(irq);
16362306a36Sopenharmony_ci		generic_handle_domain_irq(ic->domain, irq);
16462306a36Sopenharmony_ci	}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	chained_irq_exit(chip, desc);
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_cistatic int lpc32xx_irq_domain_map(struct irq_domain *id, unsigned int virq,
17062306a36Sopenharmony_ci				  irq_hw_number_t hw)
17162306a36Sopenharmony_ci{
17262306a36Sopenharmony_ci	struct lpc32xx_irq_chip *ic = id->host_data;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	irq_set_chip_data(virq, ic);
17562306a36Sopenharmony_ci	irq_set_chip_and_handler(virq, &lpc32xx_chip, handle_level_irq);
17662306a36Sopenharmony_ci	irq_set_status_flags(virq, IRQ_LEVEL);
17762306a36Sopenharmony_ci	irq_set_noprobe(virq);
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	return 0;
18062306a36Sopenharmony_ci}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_cistatic void lpc32xx_irq_domain_unmap(struct irq_domain *id, unsigned int virq)
18362306a36Sopenharmony_ci{
18462306a36Sopenharmony_ci	irq_set_chip_and_handler(virq, NULL, NULL);
18562306a36Sopenharmony_ci}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_cistatic const struct irq_domain_ops lpc32xx_irq_domain_ops = {
18862306a36Sopenharmony_ci	.map    = lpc32xx_irq_domain_map,
18962306a36Sopenharmony_ci	.unmap	= lpc32xx_irq_domain_unmap,
19062306a36Sopenharmony_ci	.xlate  = irq_domain_xlate_twocell,
19162306a36Sopenharmony_ci};
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_cistatic int __init lpc32xx_of_ic_init(struct device_node *node,
19462306a36Sopenharmony_ci				     struct device_node *parent)
19562306a36Sopenharmony_ci{
19662306a36Sopenharmony_ci	struct lpc32xx_irq_chip *irqc;
19762306a36Sopenharmony_ci	bool is_mic = of_device_is_compatible(node, "nxp,lpc3220-mic");
19862306a36Sopenharmony_ci	const __be32 *reg = of_get_property(node, "reg", NULL);
19962306a36Sopenharmony_ci	u32 parent_irq, i, addr = reg ? be32_to_cpu(*reg) : 0;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	irqc = kzalloc(sizeof(*irqc), GFP_KERNEL);
20262306a36Sopenharmony_ci	if (!irqc)
20362306a36Sopenharmony_ci		return -ENOMEM;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	irqc->addr = addr;
20662306a36Sopenharmony_ci	irqc->base = of_iomap(node, 0);
20762306a36Sopenharmony_ci	if (!irqc->base) {
20862306a36Sopenharmony_ci		pr_err("%pOF: unable to map registers\n", node);
20962306a36Sopenharmony_ci		kfree(irqc);
21062306a36Sopenharmony_ci		return -EINVAL;
21162306a36Sopenharmony_ci	}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	irqc->domain = irq_domain_add_linear(node, NR_LPC32XX_IC_IRQS,
21462306a36Sopenharmony_ci					     &lpc32xx_irq_domain_ops, irqc);
21562306a36Sopenharmony_ci	if (!irqc->domain) {
21662306a36Sopenharmony_ci		pr_err("unable to add irq domain\n");
21762306a36Sopenharmony_ci		iounmap(irqc->base);
21862306a36Sopenharmony_ci		kfree(irqc);
21962306a36Sopenharmony_ci		return -ENODEV;
22062306a36Sopenharmony_ci	}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	if (is_mic) {
22362306a36Sopenharmony_ci		lpc32xx_mic_irqc = irqc;
22462306a36Sopenharmony_ci		set_handle_irq(lpc32xx_handle_irq);
22562306a36Sopenharmony_ci	} else {
22662306a36Sopenharmony_ci		for (i = 0; i < of_irq_count(node); i++) {
22762306a36Sopenharmony_ci			parent_irq = irq_of_parse_and_map(node, i);
22862306a36Sopenharmony_ci			if (parent_irq)
22962306a36Sopenharmony_ci				irq_set_chained_handler_and_data(parent_irq,
23062306a36Sopenharmony_ci						 lpc32xx_sic_handler, irqc);
23162306a36Sopenharmony_ci		}
23262306a36Sopenharmony_ci	}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	lpc32xx_ic_write(irqc, LPC32XX_INTC_MASK, 0x00);
23562306a36Sopenharmony_ci	lpc32xx_ic_write(irqc, LPC32XX_INTC_POL,  0x00);
23662306a36Sopenharmony_ci	lpc32xx_ic_write(irqc, LPC32XX_INTC_TYPE, 0x00);
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	return 0;
23962306a36Sopenharmony_ci}
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ciIRQCHIP_DECLARE(nxp_lpc32xx_mic, "nxp,lpc3220-mic", lpc32xx_of_ic_init);
24262306a36Sopenharmony_ciIRQCHIP_DECLARE(nxp_lpc32xx_sic, "nxp,lpc3220-sic", lpc32xx_of_ic_init);
243