xref: /linux/drivers/irqchip/irq-brcmstb-l2.c (revision 0ea8a56de21be24cb79abb03dee79aabcd60a316)
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  *
549aa6ef0SDoug Berger  * Copyright (C) 2014-2017 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/of_platform.h>
197f646e92SFlorian Fainelli #include <linux/interrupt.h>
207f646e92SFlorian Fainelli #include <linux/irq.h>
217f646e92SFlorian Fainelli #include <linux/io.h>
227f646e92SFlorian Fainelli #include <linux/irqdomain.h>
237f646e92SFlorian Fainelli #include <linux/irqchip.h>
247f646e92SFlorian Fainelli #include <linux/irqchip/chained_irq.h>
257f646e92SFlorian Fainelli 
26c0ca7262SDoug Berger struct brcmstb_intc_init_params {
27c0ca7262SDoug Berger 	irq_flow_handler_t handler;
28c0ca7262SDoug Berger 	int cpu_status;
29c0ca7262SDoug Berger 	int cpu_clear;
30c0ca7262SDoug Berger 	int cpu_mask_status;
31c0ca7262SDoug Berger 	int cpu_mask_set;
32c0ca7262SDoug Berger 	int cpu_mask_clear;
33c0ca7262SDoug Berger };
34c0ca7262SDoug Berger 
35c0ca7262SDoug Berger /* Register offsets in the L2 latched interrupt controller */
36c0ca7262SDoug Berger static const struct brcmstb_intc_init_params l2_edge_intc_init = {
37c0ca7262SDoug Berger 	.handler		= handle_edge_irq,
38c0ca7262SDoug Berger 	.cpu_status		= 0x00,
39c0ca7262SDoug Berger 	.cpu_clear		= 0x08,
40c0ca7262SDoug Berger 	.cpu_mask_status	= 0x0c,
41c0ca7262SDoug Berger 	.cpu_mask_set		= 0x10,
42c0ca7262SDoug Berger 	.cpu_mask_clear		= 0x14
43c0ca7262SDoug Berger };
44c0ca7262SDoug Berger 
45c0ca7262SDoug Berger /* Register offsets in the L2 level interrupt controller */
46c0ca7262SDoug Berger static const struct brcmstb_intc_init_params l2_lvl_intc_init = {
47c0ca7262SDoug Berger 	.handler		= handle_level_irq,
48c0ca7262SDoug Berger 	.cpu_status		= 0x00,
49c0ca7262SDoug Berger 	.cpu_clear		= -1, /* Register not present */
50c0ca7262SDoug Berger 	.cpu_mask_status	= 0x04,
51c0ca7262SDoug Berger 	.cpu_mask_set		= 0x08,
52c0ca7262SDoug Berger 	.cpu_mask_clear		= 0x0C
53c0ca7262SDoug Berger };
547f646e92SFlorian Fainelli 
557f646e92SFlorian Fainelli /* L2 intc private data structure */
567f646e92SFlorian Fainelli struct brcmstb_l2_intc_data {
577f646e92SFlorian Fainelli 	struct irq_domain *domain;
5849aa6ef0SDoug Berger 	struct irq_chip_generic *gc;
598480ca47SDoug Berger 	int status_offset;
608480ca47SDoug Berger 	int mask_offset;
617f646e92SFlorian Fainelli 	bool can_wake;
627f646e92SFlorian Fainelli 	u32 saved_mask; /* for suspend/resume */
637f646e92SFlorian Fainelli };
647f646e92SFlorian Fainelli 
6549aa6ef0SDoug Berger /**
6649aa6ef0SDoug Berger  * brcmstb_l2_mask_and_ack - Mask and ack pending interrupt
6749aa6ef0SDoug Berger  * @d: irq_data
6849aa6ef0SDoug Berger  *
6949aa6ef0SDoug Berger  * Chip has separate enable/disable registers instead of a single mask
7049aa6ef0SDoug Berger  * register and pending interrupt is acknowledged by setting a bit.
7149aa6ef0SDoug Berger  *
7249aa6ef0SDoug Berger  * Note: This function is generic and could easily be added to the
7349aa6ef0SDoug Berger  * generic irqchip implementation if there ever becomes a will to do so.
7449aa6ef0SDoug Berger  * Perhaps with a name like irq_gc_mask_disable_and_ack_set().
7549aa6ef0SDoug Berger  *
7649aa6ef0SDoug Berger  * e.g.: https://patchwork.kernel.org/patch/9831047/
7749aa6ef0SDoug Berger  */
7849aa6ef0SDoug Berger static void brcmstb_l2_mask_and_ack(struct irq_data *d)
7949aa6ef0SDoug Berger {
8049aa6ef0SDoug Berger 	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
8149aa6ef0SDoug Berger 	struct irq_chip_type *ct = irq_data_get_chip_type(d);
8249aa6ef0SDoug Berger 	u32 mask = d->mask;
8349aa6ef0SDoug Berger 
8449aa6ef0SDoug Berger 	irq_gc_lock(gc);
8549aa6ef0SDoug Berger 	irq_reg_writel(gc, mask, ct->regs.disable);
8649aa6ef0SDoug Berger 	*ct->mask_cache &= ~mask;
8749aa6ef0SDoug Berger 	irq_reg_writel(gc, mask, ct->regs.ack);
8849aa6ef0SDoug Berger 	irq_gc_unlock(gc);
8949aa6ef0SDoug Berger }
9049aa6ef0SDoug Berger 
91bd0b9ac4SThomas Gleixner static void brcmstb_l2_intc_irq_handle(struct irq_desc *desc)
927f646e92SFlorian Fainelli {
937f646e92SFlorian Fainelli 	struct brcmstb_l2_intc_data *b = irq_desc_get_handler_data(desc);
947f646e92SFlorian Fainelli 	struct irq_chip *chip = irq_desc_get_chip(desc);
95bd0b9ac4SThomas Gleixner 	unsigned int irq;
967f646e92SFlorian Fainelli 	u32 status;
977f646e92SFlorian Fainelli 
987f646e92SFlorian Fainelli 	chained_irq_enter(chip, desc);
997f646e92SFlorian Fainelli 
1008480ca47SDoug Berger 	status = irq_reg_readl(b->gc, b->status_offset) &
1018480ca47SDoug Berger 		~(irq_reg_readl(b->gc, b->mask_offset));
1027f646e92SFlorian Fainelli 
1037f646e92SFlorian Fainelli 	if (status == 0) {
10405f12757SKevin Cernekee 		raw_spin_lock(&desc->lock);
105bd0b9ac4SThomas Gleixner 		handle_bad_irq(desc);
10605f12757SKevin Cernekee 		raw_spin_unlock(&desc->lock);
1077f646e92SFlorian Fainelli 		goto out;
1087f646e92SFlorian Fainelli 	}
1097f646e92SFlorian Fainelli 
1107f646e92SFlorian Fainelli 	do {
1117f646e92SFlorian Fainelli 		irq = ffs(status) - 1;
1127f646e92SFlorian Fainelli 		status &= ~(1 << irq);
11349aa6ef0SDoug Berger 		generic_handle_irq(irq_linear_revmap(b->domain, irq));
1147f646e92SFlorian Fainelli 	} while (status);
1157f646e92SFlorian Fainelli out:
1167f646e92SFlorian Fainelli 	chained_irq_exit(chip, desc);
1177f646e92SFlorian Fainelli }
1187f646e92SFlorian Fainelli 
1197f646e92SFlorian Fainelli static void brcmstb_l2_intc_suspend(struct irq_data *d)
1207f646e92SFlorian Fainelli {
1217f646e92SFlorian Fainelli 	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
1228480ca47SDoug Berger 	struct irq_chip_type *ct = irq_data_get_chip_type(d);
1237f646e92SFlorian Fainelli 	struct brcmstb_l2_intc_data *b = gc->private;
12433517881SDoug Berger 	unsigned long flags;
1257f646e92SFlorian Fainelli 
12633517881SDoug Berger 	irq_gc_lock_irqsave(gc, flags);
1277f646e92SFlorian Fainelli 	/* Save the current mask */
1288480ca47SDoug Berger 	b->saved_mask = irq_reg_readl(gc, ct->regs.mask);
1297f646e92SFlorian Fainelli 
1307f646e92SFlorian Fainelli 	if (b->can_wake) {
1317f646e92SFlorian Fainelli 		/* Program the wakeup mask */
1328480ca47SDoug Berger 		irq_reg_writel(gc, ~gc->wake_active, ct->regs.disable);
1338480ca47SDoug Berger 		irq_reg_writel(gc, gc->wake_active, ct->regs.enable);
1347f646e92SFlorian Fainelli 	}
13533517881SDoug Berger 	irq_gc_unlock_irqrestore(gc, flags);
1367f646e92SFlorian Fainelli }
1377f646e92SFlorian Fainelli 
1387f646e92SFlorian Fainelli static void brcmstb_l2_intc_resume(struct irq_data *d)
1397f646e92SFlorian Fainelli {
1407f646e92SFlorian Fainelli 	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
1418480ca47SDoug Berger 	struct irq_chip_type *ct = irq_data_get_chip_type(d);
1427f646e92SFlorian Fainelli 	struct brcmstb_l2_intc_data *b = gc->private;
14333517881SDoug Berger 	unsigned long flags;
1447f646e92SFlorian Fainelli 
14533517881SDoug Berger 	irq_gc_lock_irqsave(gc, flags);
146c0ca7262SDoug Berger 	if (ct->chip.irq_ack) {
1477f646e92SFlorian Fainelli 		/* Clear unmasked non-wakeup interrupts */
1488480ca47SDoug Berger 		irq_reg_writel(gc, ~b->saved_mask & ~gc->wake_active,
1498480ca47SDoug Berger 				ct->regs.ack);
1508480ca47SDoug Berger 	}
1517f646e92SFlorian Fainelli 
1527f646e92SFlorian Fainelli 	/* Restore the saved mask */
1538480ca47SDoug Berger 	irq_reg_writel(gc, b->saved_mask, ct->regs.disable);
1548480ca47SDoug Berger 	irq_reg_writel(gc, ~b->saved_mask, ct->regs.enable);
15533517881SDoug Berger 	irq_gc_unlock_irqrestore(gc, flags);
1567f646e92SFlorian Fainelli }
1577f646e92SFlorian Fainelli 
1582ae9add9SBen Dooks static int __init brcmstb_l2_intc_of_init(struct device_node *np,
159c0ca7262SDoug Berger 					  struct device_node *parent,
160c0ca7262SDoug Berger 					  const struct brcmstb_intc_init_params
161c0ca7262SDoug Berger 					  *init_params)
1627f646e92SFlorian Fainelli {
1637f646e92SFlorian Fainelli 	unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
1647f646e92SFlorian Fainelli 	struct brcmstb_l2_intc_data *data;
1657f646e92SFlorian Fainelli 	struct irq_chip_type *ct;
1667f646e92SFlorian Fainelli 	int ret;
1671abbdbacSKevin Cernekee 	unsigned int flags;
16849aa6ef0SDoug Berger 	int parent_irq;
16949aa6ef0SDoug Berger 	void __iomem *base;
1707f646e92SFlorian Fainelli 
1717f646e92SFlorian Fainelli 	data = kzalloc(sizeof(*data), GFP_KERNEL);
1727f646e92SFlorian Fainelli 	if (!data)
1737f646e92SFlorian Fainelli 		return -ENOMEM;
1747f646e92SFlorian Fainelli 
17549aa6ef0SDoug Berger 	base = of_iomap(np, 0);
17649aa6ef0SDoug Berger 	if (!base) {
1777f646e92SFlorian Fainelli 		pr_err("failed to remap intc L2 registers\n");
1787f646e92SFlorian Fainelli 		ret = -ENOMEM;
1797f646e92SFlorian Fainelli 		goto out_free;
1807f646e92SFlorian Fainelli 	}
1817f646e92SFlorian Fainelli 
1827f646e92SFlorian Fainelli 	/* Disable all interrupts by default */
183c0ca7262SDoug Berger 	writel(0xffffffff, base + init_params->cpu_mask_set);
184c9ae71e0SBrian Norris 
185c9ae71e0SBrian Norris 	/* Wakeup interrupts may be retained from S5 (cold boot) */
186c9ae71e0SBrian Norris 	data->can_wake = of_property_read_bool(np, "brcm,irq-can-wake");
187c0ca7262SDoug Berger 	if (!data->can_wake && (init_params->cpu_clear >= 0))
188c0ca7262SDoug Berger 		writel(0xffffffff, base + init_params->cpu_clear);
1897f646e92SFlorian Fainelli 
19049aa6ef0SDoug Berger 	parent_irq = irq_of_parse_and_map(np, 0);
19149aa6ef0SDoug Berger 	if (!parent_irq) {
1927f646e92SFlorian Fainelli 		pr_err("failed to find parent interrupt\n");
193d99ba446SDmitry Torokhov 		ret = -EINVAL;
1947f646e92SFlorian Fainelli 		goto out_unmap;
1957f646e92SFlorian Fainelli 	}
1967f646e92SFlorian Fainelli 
1977f646e92SFlorian Fainelli 	data->domain = irq_domain_add_linear(np, 32,
1987f646e92SFlorian Fainelli 				&irq_generic_chip_ops, NULL);
1997f646e92SFlorian Fainelli 	if (!data->domain) {
2007f646e92SFlorian Fainelli 		ret = -ENOMEM;
2017f646e92SFlorian Fainelli 		goto out_unmap;
2027f646e92SFlorian Fainelli 	}
2037f646e92SFlorian Fainelli 
2041abbdbacSKevin Cernekee 	/* MIPS chips strapped for BE will automagically configure the
2051abbdbacSKevin Cernekee 	 * peripheral registers for CPU-native byte order.
2061abbdbacSKevin Cernekee 	 */
2071abbdbacSKevin Cernekee 	flags = 0;
2081abbdbacSKevin Cernekee 	if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
2091abbdbacSKevin Cernekee 		flags |= IRQ_GC_BE_IO;
2101abbdbacSKevin Cernekee 
2117f646e92SFlorian Fainelli 	/* Allocate a single Generic IRQ chip for this node */
2127f646e92SFlorian Fainelli 	ret = irq_alloc_domain_generic_chips(data->domain, 32, 1,
213c0ca7262SDoug Berger 			np->full_name, init_params->handler, clr, 0, flags);
2147f646e92SFlorian Fainelli 	if (ret) {
2157f646e92SFlorian Fainelli 		pr_err("failed to allocate generic irq chip\n");
2167f646e92SFlorian Fainelli 		goto out_free_domain;
2177f646e92SFlorian Fainelli 	}
2187f646e92SFlorian Fainelli 
2197f646e92SFlorian Fainelli 	/* Set the IRQ chaining logic */
22049aa6ef0SDoug Berger 	irq_set_chained_handler_and_data(parent_irq,
221f286c173SThomas Gleixner 					 brcmstb_l2_intc_irq_handle, data);
2227f646e92SFlorian Fainelli 
22349aa6ef0SDoug Berger 	data->gc = irq_get_domain_generic_chip(data->domain, 0);
22449aa6ef0SDoug Berger 	data->gc->reg_base = base;
22549aa6ef0SDoug Berger 	data->gc->private = data;
226c0ca7262SDoug Berger 	data->status_offset = init_params->cpu_status;
227c0ca7262SDoug Berger 	data->mask_offset = init_params->cpu_mask_status;
2288480ca47SDoug Berger 
22949aa6ef0SDoug Berger 	ct = data->gc->chip_types;
2307f646e92SFlorian Fainelli 
231c0ca7262SDoug Berger 	if (init_params->cpu_clear >= 0) {
232c0ca7262SDoug Berger 		ct->regs.ack = init_params->cpu_clear;
2337f646e92SFlorian Fainelli 		ct->chip.irq_ack = irq_gc_ack_set_bit;
234c0ca7262SDoug Berger 		ct->chip.irq_mask_ack = brcmstb_l2_mask_and_ack;
235c0ca7262SDoug Berger 	} else {
236c0ca7262SDoug Berger 		/* No Ack - but still slightly more efficient to define this */
237c0ca7262SDoug Berger 		ct->chip.irq_mask_ack = irq_gc_mask_disable_reg;
238c0ca7262SDoug Berger 	}
2397f646e92SFlorian Fainelli 
2407f646e92SFlorian Fainelli 	ct->chip.irq_mask = irq_gc_mask_disable_reg;
241c0ca7262SDoug Berger 	ct->regs.disable = init_params->cpu_mask_set;
242c0ca7262SDoug Berger 	ct->regs.mask = init_params->cpu_mask_status;
2437f646e92SFlorian Fainelli 
2447f646e92SFlorian Fainelli 	ct->chip.irq_unmask = irq_gc_unmask_enable_reg;
245c0ca7262SDoug Berger 	ct->regs.enable = init_params->cpu_mask_clear;
2467f646e92SFlorian Fainelli 
2477f646e92SFlorian Fainelli 	ct->chip.irq_suspend = brcmstb_l2_intc_suspend;
2487f646e92SFlorian Fainelli 	ct->chip.irq_resume = brcmstb_l2_intc_resume;
249c017d211SFlorian Fainelli 	ct->chip.irq_pm_shutdown = brcmstb_l2_intc_suspend;
2507f646e92SFlorian Fainelli 
251c9ae71e0SBrian Norris 	if (data->can_wake) {
2527f646e92SFlorian Fainelli 		/* This IRQ chip can wake the system, set all child interrupts
2537f646e92SFlorian Fainelli 		 * in wake_enabled mask
2547f646e92SFlorian Fainelli 		 */
25549aa6ef0SDoug Berger 		data->gc->wake_enabled = 0xffffffff;
2567f646e92SFlorian Fainelli 		ct->chip.irq_set_wake = irq_gc_set_wake;
257c8d8d6fcSJustin Chen 		enable_irq_wake(parent_irq);
2587f646e92SFlorian Fainelli 	}
2597f646e92SFlorian Fainelli 
260082ce27fSFlorian Fainelli 	pr_info("registered L2 intc (%pOF, parent irq: %d)\n", np, parent_irq);
261082ce27fSFlorian Fainelli 
2627f646e92SFlorian Fainelli 	return 0;
2637f646e92SFlorian Fainelli 
2647f646e92SFlorian Fainelli out_free_domain:
2657f646e92SFlorian Fainelli 	irq_domain_remove(data->domain);
2667f646e92SFlorian Fainelli out_unmap:
26749aa6ef0SDoug Berger 	iounmap(base);
2687f646e92SFlorian Fainelli out_free:
2697f646e92SFlorian Fainelli 	kfree(data);
2707f646e92SFlorian Fainelli 	return ret;
2717f646e92SFlorian Fainelli }
272c0ca7262SDoug Berger 
273dc3173c7SYueHaibing static int __init brcmstb_l2_edge_intc_of_init(struct device_node *np,
274c0ca7262SDoug Berger 	struct device_node *parent)
275c0ca7262SDoug Berger {
276c0ca7262SDoug Berger 	return brcmstb_l2_intc_of_init(np, parent, &l2_edge_intc_init);
277c0ca7262SDoug Berger }
278c0ca7262SDoug Berger IRQCHIP_DECLARE(brcmstb_l2_intc, "brcm,l2-intc", brcmstb_l2_edge_intc_of_init);
2799ac793dcSKamal Dasu IRQCHIP_DECLARE(brcmstb_hif_spi_l2_intc, "brcm,hif-spi-l2-intc",
2809ac793dcSKamal Dasu 		brcmstb_l2_edge_intc_of_init);
281*240e176aSFlorian Fainelli IRQCHIP_DECLARE(brcmstb_upg_aux_aon_l2_intc, "brcm,upg-aux-aon-l2-intc",
282*240e176aSFlorian Fainelli 		brcmstb_l2_edge_intc_of_init);
283c0ca7262SDoug Berger 
284dc3173c7SYueHaibing static int __init brcmstb_l2_lvl_intc_of_init(struct device_node *np,
285c0ca7262SDoug Berger 	struct device_node *parent)
286c0ca7262SDoug Berger {
287c0ca7262SDoug Berger 	return brcmstb_l2_intc_of_init(np, parent, &l2_lvl_intc_init);
288c0ca7262SDoug Berger }
289c0ca7262SDoug Berger IRQCHIP_DECLARE(bcm7271_l2_intc, "brcm,bcm7271-l2-intc",
290c0ca7262SDoug Berger 	brcmstb_l2_lvl_intc_of_init);
291