xref: /openbmc/linux/drivers/irqchip/irq-brcmstb-l2.c (revision c595db6d7c8bcf87ef42204391fa890e5950e566)
11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
27f646e92SFlorian Fainelli /*
37f646e92SFlorian Fainelli  * Generic Broadcom Set Top Box Level 2 Interrupt controller driver
47f646e92SFlorian Fainelli  *
5*5fcc75e4SDoug Berger  * Copyright (C) 2014-2024 Broadcom
67f646e92SFlorian Fainelli  */
77f646e92SFlorian Fainelli 
87f646e92SFlorian Fainelli #define pr_fmt(fmt)	KBUILD_MODNAME	": " fmt
97f646e92SFlorian Fainelli 
107f646e92SFlorian Fainelli #include <linux/init.h>
117f646e92SFlorian Fainelli #include <linux/slab.h>
127f646e92SFlorian Fainelli #include <linux/module.h>
137f646e92SFlorian Fainelli #include <linux/platform_device.h>
1405f12757SKevin Cernekee #include <linux/spinlock.h>
157f646e92SFlorian Fainelli #include <linux/of.h>
167f646e92SFlorian Fainelli #include <linux/of_irq.h>
177f646e92SFlorian Fainelli #include <linux/of_address.h>
187f646e92SFlorian Fainelli #include <linux/interrupt.h>
197f646e92SFlorian Fainelli #include <linux/irq.h>
207f646e92SFlorian Fainelli #include <linux/io.h>
217f646e92SFlorian Fainelli #include <linux/irqdomain.h>
227f646e92SFlorian Fainelli #include <linux/irqchip.h>
237f646e92SFlorian Fainelli #include <linux/irqchip/chained_irq.h>
247f646e92SFlorian Fainelli 
25c0ca7262SDoug Berger struct brcmstb_intc_init_params {
26c0ca7262SDoug Berger 	irq_flow_handler_t handler;
27c0ca7262SDoug Berger 	int cpu_status;
28c0ca7262SDoug Berger 	int cpu_clear;
29c0ca7262SDoug Berger 	int cpu_mask_status;
30c0ca7262SDoug Berger 	int cpu_mask_set;
31c0ca7262SDoug Berger 	int cpu_mask_clear;
32c0ca7262SDoug Berger };
33c0ca7262SDoug Berger 
34c0ca7262SDoug Berger /* Register offsets in the L2 latched interrupt controller */
35c0ca7262SDoug Berger static const struct brcmstb_intc_init_params l2_edge_intc_init = {
36c0ca7262SDoug Berger 	.handler		= handle_edge_irq,
37c0ca7262SDoug Berger 	.cpu_status		= 0x00,
38c0ca7262SDoug Berger 	.cpu_clear		= 0x08,
39c0ca7262SDoug Berger 	.cpu_mask_status	= 0x0c,
40c0ca7262SDoug Berger 	.cpu_mask_set		= 0x10,
41c0ca7262SDoug Berger 	.cpu_mask_clear		= 0x14
42c0ca7262SDoug Berger };
43c0ca7262SDoug Berger 
44c0ca7262SDoug Berger /* Register offsets in the L2 level interrupt controller */
45c0ca7262SDoug Berger static const struct brcmstb_intc_init_params l2_lvl_intc_init = {
46c0ca7262SDoug Berger 	.handler		= handle_level_irq,
47c0ca7262SDoug Berger 	.cpu_status		= 0x00,
48c0ca7262SDoug Berger 	.cpu_clear		= -1, /* Register not present */
49c0ca7262SDoug Berger 	.cpu_mask_status	= 0x04,
50c0ca7262SDoug Berger 	.cpu_mask_set		= 0x08,
51c0ca7262SDoug Berger 	.cpu_mask_clear		= 0x0C
52c0ca7262SDoug Berger };
537f646e92SFlorian Fainelli 
547f646e92SFlorian Fainelli /* L2 intc private data structure */
557f646e92SFlorian Fainelli struct brcmstb_l2_intc_data {
567f646e92SFlorian Fainelli 	struct irq_domain *domain;
5749aa6ef0SDoug Berger 	struct irq_chip_generic *gc;
588480ca47SDoug Berger 	int status_offset;
598480ca47SDoug Berger 	int mask_offset;
607f646e92SFlorian Fainelli 	bool can_wake;
617f646e92SFlorian Fainelli 	u32 saved_mask; /* for suspend/resume */
627f646e92SFlorian Fainelli };
637f646e92SFlorian Fainelli 
6449aa6ef0SDoug Berger /**
6549aa6ef0SDoug Berger  * brcmstb_l2_mask_and_ack - Mask and ack pending interrupt
6649aa6ef0SDoug Berger  * @d: irq_data
6749aa6ef0SDoug Berger  *
6849aa6ef0SDoug Berger  * Chip has separate enable/disable registers instead of a single mask
6949aa6ef0SDoug Berger  * register and pending interrupt is acknowledged by setting a bit.
7049aa6ef0SDoug Berger  *
7149aa6ef0SDoug Berger  * Note: This function is generic and could easily be added to the
7249aa6ef0SDoug Berger  * generic irqchip implementation if there ever becomes a will to do so.
7349aa6ef0SDoug Berger  * Perhaps with a name like irq_gc_mask_disable_and_ack_set().
7449aa6ef0SDoug Berger  *
7549aa6ef0SDoug Berger  * e.g.: https://patchwork.kernel.org/patch/9831047/
7649aa6ef0SDoug Berger  */
brcmstb_l2_mask_and_ack(struct irq_data * d)7749aa6ef0SDoug Berger static void brcmstb_l2_mask_and_ack(struct irq_data *d)
7849aa6ef0SDoug Berger {
7949aa6ef0SDoug Berger 	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
8049aa6ef0SDoug Berger 	struct irq_chip_type *ct = irq_data_get_chip_type(d);
8149aa6ef0SDoug Berger 	u32 mask = d->mask;
8249aa6ef0SDoug Berger 
8349aa6ef0SDoug Berger 	irq_gc_lock(gc);
8449aa6ef0SDoug Berger 	irq_reg_writel(gc, mask, ct->regs.disable);
8549aa6ef0SDoug Berger 	*ct->mask_cache &= ~mask;
8649aa6ef0SDoug Berger 	irq_reg_writel(gc, mask, ct->regs.ack);
8749aa6ef0SDoug Berger 	irq_gc_unlock(gc);
8849aa6ef0SDoug Berger }
8949aa6ef0SDoug Berger 
brcmstb_l2_intc_irq_handle(struct irq_desc * desc)90bd0b9ac4SThomas Gleixner static void brcmstb_l2_intc_irq_handle(struct irq_desc *desc)
917f646e92SFlorian Fainelli {
927f646e92SFlorian Fainelli 	struct brcmstb_l2_intc_data *b = irq_desc_get_handler_data(desc);
937f646e92SFlorian Fainelli 	struct irq_chip *chip = irq_desc_get_chip(desc);
94bd0b9ac4SThomas Gleixner 	unsigned int irq;
957f646e92SFlorian Fainelli 	u32 status;
967f646e92SFlorian Fainelli 
977f646e92SFlorian Fainelli 	chained_irq_enter(chip, desc);
987f646e92SFlorian Fainelli 
998480ca47SDoug Berger 	status = irq_reg_readl(b->gc, b->status_offset) &
1008480ca47SDoug Berger 		~(irq_reg_readl(b->gc, b->mask_offset));
1017f646e92SFlorian Fainelli 
1027f646e92SFlorian Fainelli 	if (status == 0) {
10305f12757SKevin Cernekee 		raw_spin_lock(&desc->lock);
104bd0b9ac4SThomas Gleixner 		handle_bad_irq(desc);
10505f12757SKevin Cernekee 		raw_spin_unlock(&desc->lock);
1067f646e92SFlorian Fainelli 		goto out;
1077f646e92SFlorian Fainelli 	}
1087f646e92SFlorian Fainelli 
1097f646e92SFlorian Fainelli 	do {
1107f646e92SFlorian Fainelli 		irq = ffs(status) - 1;
1117f646e92SFlorian Fainelli 		status &= ~(1 << irq);
112046a6ee2SMarc Zyngier 		generic_handle_domain_irq(b->domain, irq);
1137f646e92SFlorian Fainelli 	} while (status);
1147f646e92SFlorian Fainelli out:
115*5fcc75e4SDoug Berger 	/* Don't ack parent before all device writes are done */
116*5fcc75e4SDoug Berger 	wmb();
117*5fcc75e4SDoug Berger 
1187f646e92SFlorian Fainelli 	chained_irq_exit(chip, desc);
1197f646e92SFlorian Fainelli }
1207f646e92SFlorian Fainelli 
brcmstb_l2_intc_suspend(struct irq_data * d)1217f646e92SFlorian Fainelli static void brcmstb_l2_intc_suspend(struct irq_data *d)
1227f646e92SFlorian Fainelli {
1237f646e92SFlorian Fainelli 	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
1248480ca47SDoug Berger 	struct irq_chip_type *ct = irq_data_get_chip_type(d);
1257f646e92SFlorian Fainelli 	struct brcmstb_l2_intc_data *b = gc->private;
12633517881SDoug Berger 	unsigned long flags;
1277f646e92SFlorian Fainelli 
12833517881SDoug Berger 	irq_gc_lock_irqsave(gc, flags);
1297f646e92SFlorian Fainelli 	/* Save the current mask */
1308480ca47SDoug Berger 	b->saved_mask = irq_reg_readl(gc, ct->regs.mask);
1317f646e92SFlorian Fainelli 
1327f646e92SFlorian Fainelli 	if (b->can_wake) {
1337f646e92SFlorian Fainelli 		/* Program the wakeup mask */
1348480ca47SDoug Berger 		irq_reg_writel(gc, ~gc->wake_active, ct->regs.disable);
1358480ca47SDoug Berger 		irq_reg_writel(gc, gc->wake_active, ct->regs.enable);
1367f646e92SFlorian Fainelli 	}
13733517881SDoug Berger 	irq_gc_unlock_irqrestore(gc, flags);
1387f646e92SFlorian Fainelli }
1397f646e92SFlorian Fainelli 
brcmstb_l2_intc_resume(struct irq_data * d)1407f646e92SFlorian Fainelli static void brcmstb_l2_intc_resume(struct irq_data *d)
1417f646e92SFlorian Fainelli {
1427f646e92SFlorian Fainelli 	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
1438480ca47SDoug Berger 	struct irq_chip_type *ct = irq_data_get_chip_type(d);
1447f646e92SFlorian Fainelli 	struct brcmstb_l2_intc_data *b = gc->private;
14533517881SDoug Berger 	unsigned long flags;
1467f646e92SFlorian Fainelli 
14733517881SDoug Berger 	irq_gc_lock_irqsave(gc, flags);
148c0ca7262SDoug Berger 	if (ct->chip.irq_ack) {
1497f646e92SFlorian Fainelli 		/* Clear unmasked non-wakeup interrupts */
1508480ca47SDoug Berger 		irq_reg_writel(gc, ~b->saved_mask & ~gc->wake_active,
1518480ca47SDoug Berger 				ct->regs.ack);
1528480ca47SDoug Berger 	}
1537f646e92SFlorian Fainelli 
1547f646e92SFlorian Fainelli 	/* Restore the saved mask */
1558480ca47SDoug Berger 	irq_reg_writel(gc, b->saved_mask, ct->regs.disable);
1568480ca47SDoug Berger 	irq_reg_writel(gc, ~b->saved_mask, ct->regs.enable);
15733517881SDoug Berger 	irq_gc_unlock_irqrestore(gc, flags);
1587f646e92SFlorian Fainelli }
1597f646e92SFlorian Fainelli 
brcmstb_l2_intc_of_init(struct device_node * np,struct device_node * parent,const struct brcmstb_intc_init_params * init_params)1602ae9add9SBen Dooks static int __init brcmstb_l2_intc_of_init(struct device_node *np,
161c0ca7262SDoug Berger 					  struct device_node *parent,
162c0ca7262SDoug Berger 					  const struct brcmstb_intc_init_params
163c0ca7262SDoug Berger 					  *init_params)
1647f646e92SFlorian Fainelli {
1657f646e92SFlorian Fainelli 	unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
16694debe03SFlorian Fainelli 	unsigned int set = 0;
1677f646e92SFlorian Fainelli 	struct brcmstb_l2_intc_data *data;
1687f646e92SFlorian Fainelli 	struct irq_chip_type *ct;
1697f646e92SFlorian Fainelli 	int ret;
1701abbdbacSKevin Cernekee 	unsigned int flags;
17149aa6ef0SDoug Berger 	int parent_irq;
17249aa6ef0SDoug Berger 	void __iomem *base;
1737f646e92SFlorian Fainelli 
1747f646e92SFlorian Fainelli 	data = kzalloc(sizeof(*data), GFP_KERNEL);
1757f646e92SFlorian Fainelli 	if (!data)
1767f646e92SFlorian Fainelli 		return -ENOMEM;
1777f646e92SFlorian Fainelli 
17849aa6ef0SDoug Berger 	base = of_iomap(np, 0);
17949aa6ef0SDoug Berger 	if (!base) {
1807f646e92SFlorian Fainelli 		pr_err("failed to remap intc L2 registers\n");
1817f646e92SFlorian Fainelli 		ret = -ENOMEM;
1827f646e92SFlorian Fainelli 		goto out_free;
1837f646e92SFlorian Fainelli 	}
1847f646e92SFlorian Fainelli 
1857f646e92SFlorian Fainelli 	/* Disable all interrupts by default */
186c0ca7262SDoug Berger 	writel(0xffffffff, base + init_params->cpu_mask_set);
187c9ae71e0SBrian Norris 
188c9ae71e0SBrian Norris 	/* Wakeup interrupts may be retained from S5 (cold boot) */
189c9ae71e0SBrian Norris 	data->can_wake = of_property_read_bool(np, "brcm,irq-can-wake");
190c0ca7262SDoug Berger 	if (!data->can_wake && (init_params->cpu_clear >= 0))
191c0ca7262SDoug Berger 		writel(0xffffffff, base + init_params->cpu_clear);
1927f646e92SFlorian Fainelli 
19349aa6ef0SDoug Berger 	parent_irq = irq_of_parse_and_map(np, 0);
19449aa6ef0SDoug Berger 	if (!parent_irq) {
1957f646e92SFlorian Fainelli 		pr_err("failed to find parent interrupt\n");
196d99ba446SDmitry Torokhov 		ret = -EINVAL;
1977f646e92SFlorian Fainelli 		goto out_unmap;
1987f646e92SFlorian Fainelli 	}
1997f646e92SFlorian Fainelli 
2007f646e92SFlorian Fainelli 	data->domain = irq_domain_add_linear(np, 32,
2017f646e92SFlorian Fainelli 				&irq_generic_chip_ops, NULL);
2027f646e92SFlorian Fainelli 	if (!data->domain) {
2037f646e92SFlorian Fainelli 		ret = -ENOMEM;
2047f646e92SFlorian Fainelli 		goto out_unmap;
2057f646e92SFlorian Fainelli 	}
2067f646e92SFlorian Fainelli 
2071abbdbacSKevin Cernekee 	/* MIPS chips strapped for BE will automagically configure the
2081abbdbacSKevin Cernekee 	 * peripheral registers for CPU-native byte order.
2091abbdbacSKevin Cernekee 	 */
2101abbdbacSKevin Cernekee 	flags = 0;
2111abbdbacSKevin Cernekee 	if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
2121abbdbacSKevin Cernekee 		flags |= IRQ_GC_BE_IO;
2131abbdbacSKevin Cernekee 
21494debe03SFlorian Fainelli 	if (init_params->handler == handle_level_irq)
21594debe03SFlorian Fainelli 		set |= IRQ_LEVEL;
21694debe03SFlorian Fainelli 
2177f646e92SFlorian Fainelli 	/* Allocate a single Generic IRQ chip for this node */
2187f646e92SFlorian Fainelli 	ret = irq_alloc_domain_generic_chips(data->domain, 32, 1,
21994debe03SFlorian Fainelli 			np->full_name, init_params->handler, clr, set, flags);
2207f646e92SFlorian Fainelli 	if (ret) {
2217f646e92SFlorian Fainelli 		pr_err("failed to allocate generic irq chip\n");
2227f646e92SFlorian Fainelli 		goto out_free_domain;
2237f646e92SFlorian Fainelli 	}
2247f646e92SFlorian Fainelli 
2257f646e92SFlorian Fainelli 	/* Set the IRQ chaining logic */
22649aa6ef0SDoug Berger 	irq_set_chained_handler_and_data(parent_irq,
227f286c173SThomas Gleixner 					 brcmstb_l2_intc_irq_handle, data);
2287f646e92SFlorian Fainelli 
22949aa6ef0SDoug Berger 	data->gc = irq_get_domain_generic_chip(data->domain, 0);
23049aa6ef0SDoug Berger 	data->gc->reg_base = base;
23149aa6ef0SDoug Berger 	data->gc->private = data;
232c0ca7262SDoug Berger 	data->status_offset = init_params->cpu_status;
233c0ca7262SDoug Berger 	data->mask_offset = init_params->cpu_mask_status;
2348480ca47SDoug Berger 
23549aa6ef0SDoug Berger 	ct = data->gc->chip_types;
2367f646e92SFlorian Fainelli 
237c0ca7262SDoug Berger 	if (init_params->cpu_clear >= 0) {
238c0ca7262SDoug Berger 		ct->regs.ack = init_params->cpu_clear;
2397f646e92SFlorian Fainelli 		ct->chip.irq_ack = irq_gc_ack_set_bit;
240c0ca7262SDoug Berger 		ct->chip.irq_mask_ack = brcmstb_l2_mask_and_ack;
241c0ca7262SDoug Berger 	} else {
242c0ca7262SDoug Berger 		/* No Ack - but still slightly more efficient to define this */
243c0ca7262SDoug Berger 		ct->chip.irq_mask_ack = irq_gc_mask_disable_reg;
244c0ca7262SDoug Berger 	}
2457f646e92SFlorian Fainelli 
2467f646e92SFlorian Fainelli 	ct->chip.irq_mask = irq_gc_mask_disable_reg;
247c0ca7262SDoug Berger 	ct->regs.disable = init_params->cpu_mask_set;
248c0ca7262SDoug Berger 	ct->regs.mask = init_params->cpu_mask_status;
2497f646e92SFlorian Fainelli 
2507f646e92SFlorian Fainelli 	ct->chip.irq_unmask = irq_gc_unmask_enable_reg;
251c0ca7262SDoug Berger 	ct->regs.enable = init_params->cpu_mask_clear;
2527f646e92SFlorian Fainelli 
2537f646e92SFlorian Fainelli 	ct->chip.irq_suspend = brcmstb_l2_intc_suspend;
2547f646e92SFlorian Fainelli 	ct->chip.irq_resume = brcmstb_l2_intc_resume;
255c017d211SFlorian Fainelli 	ct->chip.irq_pm_shutdown = brcmstb_l2_intc_suspend;
2567f646e92SFlorian Fainelli 
257c9ae71e0SBrian Norris 	if (data->can_wake) {
2587f646e92SFlorian Fainelli 		/* This IRQ chip can wake the system, set all child interrupts
2597f646e92SFlorian Fainelli 		 * in wake_enabled mask
2607f646e92SFlorian Fainelli 		 */
26149aa6ef0SDoug Berger 		data->gc->wake_enabled = 0xffffffff;
2627f646e92SFlorian Fainelli 		ct->chip.irq_set_wake = irq_gc_set_wake;
263c8d8d6fcSJustin Chen 		enable_irq_wake(parent_irq);
2647f646e92SFlorian Fainelli 	}
2657f646e92SFlorian Fainelli 
266082ce27fSFlorian Fainelli 	pr_info("registered L2 intc (%pOF, parent irq: %d)\n", np, parent_irq);
267082ce27fSFlorian Fainelli 
2687f646e92SFlorian Fainelli 	return 0;
2697f646e92SFlorian Fainelli 
2707f646e92SFlorian Fainelli out_free_domain:
2717f646e92SFlorian Fainelli 	irq_domain_remove(data->domain);
2727f646e92SFlorian Fainelli out_unmap:
27349aa6ef0SDoug Berger 	iounmap(base);
2747f646e92SFlorian Fainelli out_free:
2757f646e92SFlorian Fainelli 	kfree(data);
2767f646e92SFlorian Fainelli 	return ret;
2777f646e92SFlorian Fainelli }
278c0ca7262SDoug Berger 
brcmstb_l2_edge_intc_of_init(struct device_node * np,struct device_node * parent)279dc3173c7SYueHaibing static int __init brcmstb_l2_edge_intc_of_init(struct device_node *np,
280c0ca7262SDoug Berger 	struct device_node *parent)
281c0ca7262SDoug Berger {
282c0ca7262SDoug Berger 	return brcmstb_l2_intc_of_init(np, parent, &l2_edge_intc_init);
283c0ca7262SDoug Berger }
284c0ca7262SDoug Berger 
brcmstb_l2_lvl_intc_of_init(struct device_node * np,struct device_node * parent)285dc3173c7SYueHaibing static int __init brcmstb_l2_lvl_intc_of_init(struct device_node *np,
286c0ca7262SDoug Berger 	struct device_node *parent)
287c0ca7262SDoug Berger {
288c0ca7262SDoug Berger 	return brcmstb_l2_intc_of_init(np, parent, &l2_lvl_intc_init);
289c0ca7262SDoug Berger }
29051d9db5cSFlorian Fainelli 
29151d9db5cSFlorian Fainelli IRQCHIP_PLATFORM_DRIVER_BEGIN(brcmstb_l2)
29251d9db5cSFlorian Fainelli IRQCHIP_MATCH("brcm,l2-intc", brcmstb_l2_edge_intc_of_init)
29351d9db5cSFlorian Fainelli IRQCHIP_MATCH("brcm,hif-spi-l2-intc", brcmstb_l2_edge_intc_of_init)
29451d9db5cSFlorian Fainelli IRQCHIP_MATCH("brcm,upg-aux-aon-l2-intc", brcmstb_l2_edge_intc_of_init)
29551d9db5cSFlorian Fainelli IRQCHIP_MATCH("brcm,bcm7271-l2-intc", brcmstb_l2_lvl_intc_of_init)
29651d9db5cSFlorian Fainelli IRQCHIP_PLATFORM_DRIVER_END(brcmstb_l2)
29751d9db5cSFlorian Fainelli MODULE_DESCRIPTION("Broadcom STB generic L2 interrupt controller");
29851d9db5cSFlorian Fainelli MODULE_LICENSE("GPL v2");
299