xref: /openbmc/linux/drivers/irqchip/irq-mchp-eic.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
100fa3461SClaudiu Beznea // SPDX-License-Identifier: GPL-2.0-only
200fa3461SClaudiu Beznea /*
300fa3461SClaudiu Beznea  * Microchip External Interrupt Controller driver
400fa3461SClaudiu Beznea  *
500fa3461SClaudiu Beznea  * Copyright (C) 2021 Microchip Technology Inc. and its subsidiaries
600fa3461SClaudiu Beznea  *
700fa3461SClaudiu Beznea  * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
800fa3461SClaudiu Beznea  */
900fa3461SClaudiu Beznea #include <linux/clk.h>
1000fa3461SClaudiu Beznea #include <linux/delay.h>
1100fa3461SClaudiu Beznea #include <linux/interrupt.h>
1200fa3461SClaudiu Beznea #include <linux/irqchip.h>
1300fa3461SClaudiu Beznea #include <linux/of_address.h>
1400fa3461SClaudiu Beznea #include <linux/of_irq.h>
1500fa3461SClaudiu Beznea #include <linux/syscore_ops.h>
1600fa3461SClaudiu Beznea 
1700fa3461SClaudiu Beznea #include <dt-bindings/interrupt-controller/arm-gic.h>
1800fa3461SClaudiu Beznea 
1900fa3461SClaudiu Beznea #define MCHP_EIC_GFCS			(0x0)
2000fa3461SClaudiu Beznea #define MCHP_EIC_SCFG(x)		(0x4 + (x) * 0x4)
2100fa3461SClaudiu Beznea #define MCHP_EIC_SCFG_EN		BIT(16)
2200fa3461SClaudiu Beznea #define MCHP_EIC_SCFG_LVL		BIT(9)
2300fa3461SClaudiu Beznea #define MCHP_EIC_SCFG_POL		BIT(8)
2400fa3461SClaudiu Beznea 
2500fa3461SClaudiu Beznea #define MCHP_EIC_NIRQ			(2)
2600fa3461SClaudiu Beznea 
2700fa3461SClaudiu Beznea /*
2800fa3461SClaudiu Beznea  * struct mchp_eic - EIC private data structure
2900fa3461SClaudiu Beznea  * @base: base address
3000fa3461SClaudiu Beznea  * @clk: peripheral clock
3100fa3461SClaudiu Beznea  * @domain: irq domain
3200fa3461SClaudiu Beznea  * @irqs: irqs b/w eic and gic
3300fa3461SClaudiu Beznea  * @scfg: backup for scfg registers (necessary for backup and self-refresh mode)
3400fa3461SClaudiu Beznea  * @wakeup_source: wakeup source mask
3500fa3461SClaudiu Beznea  */
3600fa3461SClaudiu Beznea struct mchp_eic {
3700fa3461SClaudiu Beznea 	void __iomem *base;
3800fa3461SClaudiu Beznea 	struct clk *clk;
3900fa3461SClaudiu Beznea 	struct irq_domain *domain;
4000fa3461SClaudiu Beznea 	u32 irqs[MCHP_EIC_NIRQ];
4100fa3461SClaudiu Beznea 	u32 scfg[MCHP_EIC_NIRQ];
4200fa3461SClaudiu Beznea 	u32 wakeup_source;
4300fa3461SClaudiu Beznea };
4400fa3461SClaudiu Beznea 
4500fa3461SClaudiu Beznea static struct mchp_eic *eic;
4600fa3461SClaudiu Beznea 
mchp_eic_irq_mask(struct irq_data * d)4700fa3461SClaudiu Beznea static void mchp_eic_irq_mask(struct irq_data *d)
4800fa3461SClaudiu Beznea {
4900fa3461SClaudiu Beznea 	unsigned int tmp;
5000fa3461SClaudiu Beznea 
5100fa3461SClaudiu Beznea 	tmp = readl_relaxed(eic->base + MCHP_EIC_SCFG(d->hwirq));
5200fa3461SClaudiu Beznea 	tmp &= ~MCHP_EIC_SCFG_EN;
5300fa3461SClaudiu Beznea 	writel_relaxed(tmp, eic->base + MCHP_EIC_SCFG(d->hwirq));
5400fa3461SClaudiu Beznea 
5500fa3461SClaudiu Beznea 	irq_chip_mask_parent(d);
5600fa3461SClaudiu Beznea }
5700fa3461SClaudiu Beznea 
mchp_eic_irq_unmask(struct irq_data * d)5800fa3461SClaudiu Beznea static void mchp_eic_irq_unmask(struct irq_data *d)
5900fa3461SClaudiu Beznea {
6000fa3461SClaudiu Beznea 	unsigned int tmp;
6100fa3461SClaudiu Beznea 
6200fa3461SClaudiu Beznea 	tmp = readl_relaxed(eic->base + MCHP_EIC_SCFG(d->hwirq));
6300fa3461SClaudiu Beznea 	tmp |= MCHP_EIC_SCFG_EN;
6400fa3461SClaudiu Beznea 	writel_relaxed(tmp, eic->base + MCHP_EIC_SCFG(d->hwirq));
6500fa3461SClaudiu Beznea 
6600fa3461SClaudiu Beznea 	irq_chip_unmask_parent(d);
6700fa3461SClaudiu Beznea }
6800fa3461SClaudiu Beznea 
mchp_eic_irq_set_type(struct irq_data * d,unsigned int type)6900fa3461SClaudiu Beznea static int mchp_eic_irq_set_type(struct irq_data *d, unsigned int type)
7000fa3461SClaudiu Beznea {
7100fa3461SClaudiu Beznea 	unsigned int parent_irq_type;
7200fa3461SClaudiu Beznea 	unsigned int tmp;
7300fa3461SClaudiu Beznea 
7400fa3461SClaudiu Beznea 	tmp = readl_relaxed(eic->base + MCHP_EIC_SCFG(d->hwirq));
7500fa3461SClaudiu Beznea 	tmp &= ~(MCHP_EIC_SCFG_POL | MCHP_EIC_SCFG_LVL);
7600fa3461SClaudiu Beznea 	switch (type) {
7700fa3461SClaudiu Beznea 	case IRQ_TYPE_LEVEL_HIGH:
7800fa3461SClaudiu Beznea 		tmp |= MCHP_EIC_SCFG_POL | MCHP_EIC_SCFG_LVL;
7900fa3461SClaudiu Beznea 		parent_irq_type = IRQ_TYPE_LEVEL_HIGH;
8000fa3461SClaudiu Beznea 		break;
8100fa3461SClaudiu Beznea 	case IRQ_TYPE_LEVEL_LOW:
8200fa3461SClaudiu Beznea 		tmp |= MCHP_EIC_SCFG_LVL;
8300fa3461SClaudiu Beznea 		parent_irq_type = IRQ_TYPE_LEVEL_HIGH;
8400fa3461SClaudiu Beznea 		break;
8500fa3461SClaudiu Beznea 	case IRQ_TYPE_EDGE_RISING:
8600fa3461SClaudiu Beznea 		parent_irq_type = IRQ_TYPE_EDGE_RISING;
8700fa3461SClaudiu Beznea 		break;
8800fa3461SClaudiu Beznea 	case IRQ_TYPE_EDGE_FALLING:
8900fa3461SClaudiu Beznea 		tmp |= MCHP_EIC_SCFG_POL;
9000fa3461SClaudiu Beznea 		parent_irq_type = IRQ_TYPE_EDGE_RISING;
9100fa3461SClaudiu Beznea 		break;
9200fa3461SClaudiu Beznea 	default:
9300fa3461SClaudiu Beznea 		return -EINVAL;
9400fa3461SClaudiu Beznea 	}
9500fa3461SClaudiu Beznea 
9600fa3461SClaudiu Beznea 	writel_relaxed(tmp, eic->base + MCHP_EIC_SCFG(d->hwirq));
9700fa3461SClaudiu Beznea 
9800fa3461SClaudiu Beznea 	return irq_chip_set_type_parent(d, parent_irq_type);
9900fa3461SClaudiu Beznea }
10000fa3461SClaudiu Beznea 
mchp_eic_irq_set_wake(struct irq_data * d,unsigned int on)10100fa3461SClaudiu Beznea static int mchp_eic_irq_set_wake(struct irq_data *d, unsigned int on)
10200fa3461SClaudiu Beznea {
10300fa3461SClaudiu Beznea 	irq_set_irq_wake(eic->irqs[d->hwirq], on);
10400fa3461SClaudiu Beznea 	if (on)
10500fa3461SClaudiu Beznea 		eic->wakeup_source |= BIT(d->hwirq);
10600fa3461SClaudiu Beznea 	else
10700fa3461SClaudiu Beznea 		eic->wakeup_source &= ~BIT(d->hwirq);
10800fa3461SClaudiu Beznea 
10900fa3461SClaudiu Beznea 	return 0;
11000fa3461SClaudiu Beznea }
11100fa3461SClaudiu Beznea 
mchp_eic_irq_suspend(void)11200fa3461SClaudiu Beznea static int mchp_eic_irq_suspend(void)
11300fa3461SClaudiu Beznea {
11400fa3461SClaudiu Beznea 	unsigned int hwirq;
11500fa3461SClaudiu Beznea 
11600fa3461SClaudiu Beznea 	for (hwirq = 0; hwirq < MCHP_EIC_NIRQ; hwirq++)
11700fa3461SClaudiu Beznea 		eic->scfg[hwirq] = readl_relaxed(eic->base +
11800fa3461SClaudiu Beznea 						 MCHP_EIC_SCFG(hwirq));
11900fa3461SClaudiu Beznea 
12000fa3461SClaudiu Beznea 	if (!eic->wakeup_source)
12100fa3461SClaudiu Beznea 		clk_disable_unprepare(eic->clk);
12200fa3461SClaudiu Beznea 
12300fa3461SClaudiu Beznea 	return 0;
12400fa3461SClaudiu Beznea }
12500fa3461SClaudiu Beznea 
mchp_eic_irq_resume(void)12600fa3461SClaudiu Beznea static void mchp_eic_irq_resume(void)
12700fa3461SClaudiu Beznea {
12800fa3461SClaudiu Beznea 	unsigned int hwirq;
12900fa3461SClaudiu Beznea 
13000fa3461SClaudiu Beznea 	if (!eic->wakeup_source)
13100fa3461SClaudiu Beznea 		clk_prepare_enable(eic->clk);
13200fa3461SClaudiu Beznea 
13300fa3461SClaudiu Beznea 	for (hwirq = 0; hwirq < MCHP_EIC_NIRQ; hwirq++)
13400fa3461SClaudiu Beznea 		writel_relaxed(eic->scfg[hwirq], eic->base +
13500fa3461SClaudiu Beznea 			       MCHP_EIC_SCFG(hwirq));
13600fa3461SClaudiu Beznea }
13700fa3461SClaudiu Beznea 
13800fa3461SClaudiu Beznea static struct syscore_ops mchp_eic_syscore_ops = {
13900fa3461SClaudiu Beznea 	.suspend = mchp_eic_irq_suspend,
14000fa3461SClaudiu Beznea 	.resume = mchp_eic_irq_resume,
14100fa3461SClaudiu Beznea };
14200fa3461SClaudiu Beznea 
14300fa3461SClaudiu Beznea static struct irq_chip mchp_eic_chip = {
14400fa3461SClaudiu Beznea 	.name		= "eic",
14500fa3461SClaudiu Beznea 	.flags		= IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SET_TYPE_MASKED,
14600fa3461SClaudiu Beznea 	.irq_mask	= mchp_eic_irq_mask,
14700fa3461SClaudiu Beznea 	.irq_unmask	= mchp_eic_irq_unmask,
14800fa3461SClaudiu Beznea 	.irq_set_type	= mchp_eic_irq_set_type,
14900fa3461SClaudiu Beznea 	.irq_ack	= irq_chip_ack_parent,
15000fa3461SClaudiu Beznea 	.irq_eoi	= irq_chip_eoi_parent,
15100fa3461SClaudiu Beznea 	.irq_retrigger	= irq_chip_retrigger_hierarchy,
15200fa3461SClaudiu Beznea 	.irq_set_wake	= mchp_eic_irq_set_wake,
15300fa3461SClaudiu Beznea };
15400fa3461SClaudiu Beznea 
mchp_eic_domain_alloc(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs,void * data)15500fa3461SClaudiu Beznea static int mchp_eic_domain_alloc(struct irq_domain *domain, unsigned int virq,
15600fa3461SClaudiu Beznea 				 unsigned int nr_irqs, void *data)
15700fa3461SClaudiu Beznea {
15800fa3461SClaudiu Beznea 	struct irq_fwspec *fwspec = data;
15900fa3461SClaudiu Beznea 	struct irq_fwspec parent_fwspec;
16000fa3461SClaudiu Beznea 	irq_hw_number_t hwirq;
16100fa3461SClaudiu Beznea 	unsigned int type;
16200fa3461SClaudiu Beznea 	int ret;
16300fa3461SClaudiu Beznea 
16400fa3461SClaudiu Beznea 	if (WARN_ON(nr_irqs != 1))
16500fa3461SClaudiu Beznea 		return -EINVAL;
16600fa3461SClaudiu Beznea 
16700fa3461SClaudiu Beznea 	ret = irq_domain_translate_twocell(domain, fwspec, &hwirq, &type);
16800fa3461SClaudiu Beznea 	if (ret || hwirq >= MCHP_EIC_NIRQ)
16900fa3461SClaudiu Beznea 		return ret;
17000fa3461SClaudiu Beznea 
17100fa3461SClaudiu Beznea 	switch (type) {
17200fa3461SClaudiu Beznea 	case IRQ_TYPE_EDGE_RISING:
17300fa3461SClaudiu Beznea 	case IRQ_TYPE_LEVEL_HIGH:
17400fa3461SClaudiu Beznea 		break;
17500fa3461SClaudiu Beznea 	case IRQ_TYPE_EDGE_FALLING:
17600fa3461SClaudiu Beznea 		type = IRQ_TYPE_EDGE_RISING;
17700fa3461SClaudiu Beznea 		break;
17800fa3461SClaudiu Beznea 	case IRQ_TYPE_LEVEL_LOW:
17900fa3461SClaudiu Beznea 		type = IRQ_TYPE_LEVEL_HIGH;
18000fa3461SClaudiu Beznea 		break;
18100fa3461SClaudiu Beznea 	default:
18200fa3461SClaudiu Beznea 		return -EINVAL;
18300fa3461SClaudiu Beznea 	}
18400fa3461SClaudiu Beznea 
18500fa3461SClaudiu Beznea 	irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &mchp_eic_chip, eic);
18600fa3461SClaudiu Beznea 
18700fa3461SClaudiu Beznea 	parent_fwspec.fwnode = domain->parent->fwnode;
18800fa3461SClaudiu Beznea 	parent_fwspec.param_count = 3;
18900fa3461SClaudiu Beznea 	parent_fwspec.param[0] = GIC_SPI;
19000fa3461SClaudiu Beznea 	parent_fwspec.param[1] = eic->irqs[hwirq];
19100fa3461SClaudiu Beznea 	parent_fwspec.param[2] = type;
19200fa3461SClaudiu Beznea 
19300fa3461SClaudiu Beznea 	return irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec);
19400fa3461SClaudiu Beznea }
19500fa3461SClaudiu Beznea 
19600fa3461SClaudiu Beznea static const struct irq_domain_ops mchp_eic_domain_ops = {
19700fa3461SClaudiu Beznea 	.translate	= irq_domain_translate_twocell,
19800fa3461SClaudiu Beznea 	.alloc		= mchp_eic_domain_alloc,
19900fa3461SClaudiu Beznea 	.free		= irq_domain_free_irqs_common,
20000fa3461SClaudiu Beznea };
20100fa3461SClaudiu Beznea 
mchp_eic_init(struct device_node * node,struct device_node * parent)20200fa3461SClaudiu Beznea static int mchp_eic_init(struct device_node *node, struct device_node *parent)
20300fa3461SClaudiu Beznea {
20400fa3461SClaudiu Beznea 	struct irq_domain *parent_domain = NULL;
20500fa3461SClaudiu Beznea 	int ret, i;
20600fa3461SClaudiu Beznea 
20700fa3461SClaudiu Beznea 	eic = kzalloc(sizeof(*eic), GFP_KERNEL);
20800fa3461SClaudiu Beznea 	if (!eic)
20900fa3461SClaudiu Beznea 		return -ENOMEM;
21000fa3461SClaudiu Beznea 
21100fa3461SClaudiu Beznea 	eic->base = of_iomap(node, 0);
212*68a6e0c6SYang Yingliang 	if (!eic->base) {
21300fa3461SClaudiu Beznea 		ret = -ENOMEM;
21400fa3461SClaudiu Beznea 		goto free;
21500fa3461SClaudiu Beznea 	}
21600fa3461SClaudiu Beznea 
21700fa3461SClaudiu Beznea 	parent_domain = irq_find_host(parent);
21800fa3461SClaudiu Beznea 	if (!parent_domain) {
21900fa3461SClaudiu Beznea 		ret = -ENODEV;
22000fa3461SClaudiu Beznea 		goto unmap;
22100fa3461SClaudiu Beznea 	}
22200fa3461SClaudiu Beznea 
22300fa3461SClaudiu Beznea 	eic->clk = of_clk_get_by_name(node, "pclk");
22400fa3461SClaudiu Beznea 	if (IS_ERR(eic->clk)) {
22500fa3461SClaudiu Beznea 		ret = PTR_ERR(eic->clk);
22600fa3461SClaudiu Beznea 		goto unmap;
22700fa3461SClaudiu Beznea 	}
22800fa3461SClaudiu Beznea 
22900fa3461SClaudiu Beznea 	ret = clk_prepare_enable(eic->clk);
23000fa3461SClaudiu Beznea 	if (ret)
23100fa3461SClaudiu Beznea 		goto unmap;
23200fa3461SClaudiu Beznea 
23300fa3461SClaudiu Beznea 	for (i = 0; i < MCHP_EIC_NIRQ; i++) {
23400fa3461SClaudiu Beznea 		struct of_phandle_args irq;
23500fa3461SClaudiu Beznea 
23600fa3461SClaudiu Beznea 		/* Disable it, if any. */
23700fa3461SClaudiu Beznea 		writel_relaxed(0UL, eic->base + MCHP_EIC_SCFG(i));
23800fa3461SClaudiu Beznea 
23900fa3461SClaudiu Beznea 		ret = of_irq_parse_one(node, i, &irq);
24000fa3461SClaudiu Beznea 		if (ret)
24100fa3461SClaudiu Beznea 			goto clk_unprepare;
24200fa3461SClaudiu Beznea 
24300fa3461SClaudiu Beznea 		if (WARN_ON(irq.args_count != 3)) {
24400fa3461SClaudiu Beznea 			ret = -EINVAL;
24500fa3461SClaudiu Beznea 			goto clk_unprepare;
24600fa3461SClaudiu Beznea 		}
24700fa3461SClaudiu Beznea 
24800fa3461SClaudiu Beznea 		eic->irqs[i] = irq.args[1];
24900fa3461SClaudiu Beznea 	}
25000fa3461SClaudiu Beznea 
25100fa3461SClaudiu Beznea 	eic->domain = irq_domain_add_hierarchy(parent_domain, 0, MCHP_EIC_NIRQ,
25200fa3461SClaudiu Beznea 					       node, &mchp_eic_domain_ops, eic);
25300fa3461SClaudiu Beznea 	if (!eic->domain) {
25400fa3461SClaudiu Beznea 		pr_err("%pOF: Failed to add domain\n", node);
25500fa3461SClaudiu Beznea 		ret = -ENODEV;
25600fa3461SClaudiu Beznea 		goto clk_unprepare;
25700fa3461SClaudiu Beznea 	}
25800fa3461SClaudiu Beznea 
25900fa3461SClaudiu Beznea 	register_syscore_ops(&mchp_eic_syscore_ops);
26000fa3461SClaudiu Beznea 
26100fa3461SClaudiu Beznea 	pr_info("%pOF: EIC registered, nr_irqs %u\n", node, MCHP_EIC_NIRQ);
26200fa3461SClaudiu Beznea 
26300fa3461SClaudiu Beznea 	return 0;
26400fa3461SClaudiu Beznea 
26500fa3461SClaudiu Beznea clk_unprepare:
26600fa3461SClaudiu Beznea 	clk_disable_unprepare(eic->clk);
26700fa3461SClaudiu Beznea unmap:
26800fa3461SClaudiu Beznea 	iounmap(eic->base);
26900fa3461SClaudiu Beznea free:
27000fa3461SClaudiu Beznea 	kfree(eic);
27100fa3461SClaudiu Beznea 	return ret;
27200fa3461SClaudiu Beznea }
27300fa3461SClaudiu Beznea 
27400fa3461SClaudiu Beznea IRQCHIP_PLATFORM_DRIVER_BEGIN(mchp_eic)
27500fa3461SClaudiu Beznea IRQCHIP_MATCH("microchip,sama7g5-eic", mchp_eic_init)
27600fa3461SClaudiu Beznea IRQCHIP_PLATFORM_DRIVER_END(mchp_eic)
27700fa3461SClaudiu Beznea 
27800fa3461SClaudiu Beznea MODULE_DESCRIPTION("Microchip External Interrupt Controller");
27900fa3461SClaudiu Beznea MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>");
280