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