Blame view
drivers/irqchip/irq-brcmstb-l2.c
7.7 KB
1802d0bee treewide: Replace... |
1 |
// SPDX-License-Identifier: GPL-2.0-only |
7f646e927 irqchip: brcmstb-... |
2 3 4 |
/* * Generic Broadcom Set Top Box Level 2 Interrupt controller driver * |
49aa6ef0b irqchip/brcmstb-l... |
5 |
* Copyright (C) 2014-2017 Broadcom |
7f646e927 irqchip: brcmstb-... |
6 7 8 9 10 11 12 13 |
*/ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/init.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/platform_device.h> |
05f127574 irqchip: brcmstb-... |
14 |
#include <linux/spinlock.h> |
7f646e927 irqchip: brcmstb-... |
15 16 17 18 19 20 21 22 23 24 |
#include <linux/of.h> #include <linux/of_irq.h> #include <linux/of_address.h> #include <linux/of_platform.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/io.h> #include <linux/irqdomain.h> #include <linux/irqchip.h> #include <linux/irqchip/chained_irq.h> |
c0ca72620 irqchip/brcmstb-l... |
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
struct brcmstb_intc_init_params { irq_flow_handler_t handler; int cpu_status; int cpu_clear; int cpu_mask_status; int cpu_mask_set; int cpu_mask_clear; }; /* Register offsets in the L2 latched interrupt controller */ static const struct brcmstb_intc_init_params l2_edge_intc_init = { .handler = handle_edge_irq, .cpu_status = 0x00, .cpu_clear = 0x08, .cpu_mask_status = 0x0c, .cpu_mask_set = 0x10, .cpu_mask_clear = 0x14 }; /* Register offsets in the L2 level interrupt controller */ static const struct brcmstb_intc_init_params l2_lvl_intc_init = { .handler = handle_level_irq, .cpu_status = 0x00, .cpu_clear = -1, /* Register not present */ .cpu_mask_status = 0x04, .cpu_mask_set = 0x08, .cpu_mask_clear = 0x0C }; |
7f646e927 irqchip: brcmstb-... |
53 54 55 |
/* L2 intc private data structure */ struct brcmstb_l2_intc_data { |
7f646e927 irqchip: brcmstb-... |
56 |
struct irq_domain *domain; |
49aa6ef0b irqchip/brcmstb-l... |
57 |
struct irq_chip_generic *gc; |
8480ca477 irqchip/brcmstb-l... |
58 59 |
int status_offset; int mask_offset; |
7f646e927 irqchip: brcmstb-... |
60 61 62 |
bool can_wake; u32 saved_mask; /* for suspend/resume */ }; |
49aa6ef0b irqchip/brcmstb-l... |
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
/** * brcmstb_l2_mask_and_ack - Mask and ack pending interrupt * @d: irq_data * * Chip has separate enable/disable registers instead of a single mask * register and pending interrupt is acknowledged by setting a bit. * * Note: This function is generic and could easily be added to the * generic irqchip implementation if there ever becomes a will to do so. * Perhaps with a name like irq_gc_mask_disable_and_ack_set(). * * e.g.: https://patchwork.kernel.org/patch/9831047/ */ static void brcmstb_l2_mask_and_ack(struct irq_data *d) { struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); struct irq_chip_type *ct = irq_data_get_chip_type(d); u32 mask = d->mask; irq_gc_lock(gc); irq_reg_writel(gc, mask, ct->regs.disable); *ct->mask_cache &= ~mask; irq_reg_writel(gc, mask, ct->regs.ack); irq_gc_unlock(gc); } |
bd0b9ac40 genirq: Remove ir... |
88 |
static void brcmstb_l2_intc_irq_handle(struct irq_desc *desc) |
7f646e927 irqchip: brcmstb-... |
89 90 91 |
{ struct brcmstb_l2_intc_data *b = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); |
bd0b9ac40 genirq: Remove ir... |
92 |
unsigned int irq; |
7f646e927 irqchip: brcmstb-... |
93 94 95 |
u32 status; chained_irq_enter(chip, desc); |
8480ca477 irqchip/brcmstb-l... |
96 97 |
status = irq_reg_readl(b->gc, b->status_offset) & ~(irq_reg_readl(b->gc, b->mask_offset)); |
7f646e927 irqchip: brcmstb-... |
98 99 |
if (status == 0) { |
05f127574 irqchip: brcmstb-... |
100 |
raw_spin_lock(&desc->lock); |
bd0b9ac40 genirq: Remove ir... |
101 |
handle_bad_irq(desc); |
05f127574 irqchip: brcmstb-... |
102 |
raw_spin_unlock(&desc->lock); |
7f646e927 irqchip: brcmstb-... |
103 104 105 106 107 |
goto out; } do { irq = ffs(status) - 1; |
7f646e927 irqchip: brcmstb-... |
108 |
status &= ~(1 << irq); |
49aa6ef0b irqchip/brcmstb-l... |
109 |
generic_handle_irq(irq_linear_revmap(b->domain, irq)); |
7f646e927 irqchip: brcmstb-... |
110 111 112 113 114 115 116 117 |
} while (status); out: chained_irq_exit(chip, desc); } static void brcmstb_l2_intc_suspend(struct irq_data *d) { struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); |
8480ca477 irqchip/brcmstb-l... |
118 |
struct irq_chip_type *ct = irq_data_get_chip_type(d); |
7f646e927 irqchip: brcmstb-... |
119 |
struct brcmstb_l2_intc_data *b = gc->private; |
33517881e irqchip/brcmstb-l... |
120 |
unsigned long flags; |
7f646e927 irqchip: brcmstb-... |
121 |
|
33517881e irqchip/brcmstb-l... |
122 |
irq_gc_lock_irqsave(gc, flags); |
7f646e927 irqchip: brcmstb-... |
123 |
/* Save the current mask */ |
8480ca477 irqchip/brcmstb-l... |
124 |
b->saved_mask = irq_reg_readl(gc, ct->regs.mask); |
7f646e927 irqchip: brcmstb-... |
125 126 127 |
if (b->can_wake) { /* Program the wakeup mask */ |
8480ca477 irqchip/brcmstb-l... |
128 129 |
irq_reg_writel(gc, ~gc->wake_active, ct->regs.disable); irq_reg_writel(gc, gc->wake_active, ct->regs.enable); |
7f646e927 irqchip: brcmstb-... |
130 |
} |
33517881e irqchip/brcmstb-l... |
131 |
irq_gc_unlock_irqrestore(gc, flags); |
7f646e927 irqchip: brcmstb-... |
132 133 134 135 136 |
} static void brcmstb_l2_intc_resume(struct irq_data *d) { struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); |
8480ca477 irqchip/brcmstb-l... |
137 |
struct irq_chip_type *ct = irq_data_get_chip_type(d); |
7f646e927 irqchip: brcmstb-... |
138 |
struct brcmstb_l2_intc_data *b = gc->private; |
33517881e irqchip/brcmstb-l... |
139 |
unsigned long flags; |
7f646e927 irqchip: brcmstb-... |
140 |
|
33517881e irqchip/brcmstb-l... |
141 |
irq_gc_lock_irqsave(gc, flags); |
c0ca72620 irqchip/brcmstb-l... |
142 |
if (ct->chip.irq_ack) { |
8480ca477 irqchip/brcmstb-l... |
143 144 145 146 |
/* Clear unmasked non-wakeup interrupts */ irq_reg_writel(gc, ~b->saved_mask & ~gc->wake_active, ct->regs.ack); } |
7f646e927 irqchip: brcmstb-... |
147 148 |
/* Restore the saved mask */ |
8480ca477 irqchip/brcmstb-l... |
149 150 |
irq_reg_writel(gc, b->saved_mask, ct->regs.disable); irq_reg_writel(gc, ~b->saved_mask, ct->regs.enable); |
33517881e irqchip/brcmstb-l... |
151 |
irq_gc_unlock_irqrestore(gc, flags); |
7f646e927 irqchip: brcmstb-... |
152 |
} |
2ae9add9d irqchip/brcmstb-l... |
153 |
static int __init brcmstb_l2_intc_of_init(struct device_node *np, |
c0ca72620 irqchip/brcmstb-l... |
154 155 156 |
struct device_node *parent, const struct brcmstb_intc_init_params *init_params) |
7f646e927 irqchip: brcmstb-... |
157 158 159 |
{ unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; struct brcmstb_l2_intc_data *data; |
7f646e927 irqchip: brcmstb-... |
160 161 |
struct irq_chip_type *ct; int ret; |
1abbdbac3 irqchip: brcmstb-... |
162 |
unsigned int flags; |
49aa6ef0b irqchip/brcmstb-l... |
163 164 |
int parent_irq; void __iomem *base; |
7f646e927 irqchip: brcmstb-... |
165 166 167 168 |
data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; |
49aa6ef0b irqchip/brcmstb-l... |
169 170 |
base = of_iomap(np, 0); if (!base) { |
7f646e927 irqchip: brcmstb-... |
171 172 173 174 175 176 177 |
pr_err("failed to remap intc L2 registers "); ret = -ENOMEM; goto out_free; } /* Disable all interrupts by default */ |
c0ca72620 irqchip/brcmstb-l... |
178 |
writel(0xffffffff, base + init_params->cpu_mask_set); |
c9ae71e0f IRQCHIP: brcmstb-... |
179 180 181 |
/* Wakeup interrupts may be retained from S5 (cold boot) */ data->can_wake = of_property_read_bool(np, "brcm,irq-can-wake"); |
c0ca72620 irqchip/brcmstb-l... |
182 183 |
if (!data->can_wake && (init_params->cpu_clear >= 0)) writel(0xffffffff, base + init_params->cpu_clear); |
7f646e927 irqchip: brcmstb-... |
184 |
|
49aa6ef0b irqchip/brcmstb-l... |
185 186 |
parent_irq = irq_of_parse_and_map(np, 0); if (!parent_irq) { |
7f646e927 irqchip: brcmstb-... |
187 188 |
pr_err("failed to find parent interrupt "); |
d99ba4465 irqchip: brcmstb-... |
189 |
ret = -EINVAL; |
7f646e927 irqchip: brcmstb-... |
190 191 192 193 194 195 196 197 198 |
goto out_unmap; } data->domain = irq_domain_add_linear(np, 32, &irq_generic_chip_ops, NULL); if (!data->domain) { ret = -ENOMEM; goto out_unmap; } |
1abbdbac3 irqchip: brcmstb-... |
199 200 201 202 203 204 |
/* MIPS chips strapped for BE will automagically configure the * peripheral registers for CPU-native byte order. */ flags = 0; if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) flags |= IRQ_GC_BE_IO; |
7f646e927 irqchip: brcmstb-... |
205 206 |
/* Allocate a single Generic IRQ chip for this node */ ret = irq_alloc_domain_generic_chips(data->domain, 32, 1, |
c0ca72620 irqchip/brcmstb-l... |
207 |
np->full_name, init_params->handler, clr, 0, flags); |
7f646e927 irqchip: brcmstb-... |
208 209 210 211 212 213 214 |
if (ret) { pr_err("failed to allocate generic irq chip "); goto out_free_domain; } /* Set the IRQ chaining logic */ |
49aa6ef0b irqchip/brcmstb-l... |
215 |
irq_set_chained_handler_and_data(parent_irq, |
f286c1735 irqchip/brcmstb-l... |
216 |
brcmstb_l2_intc_irq_handle, data); |
7f646e927 irqchip: brcmstb-... |
217 |
|
49aa6ef0b irqchip/brcmstb-l... |
218 219 220 |
data->gc = irq_get_domain_generic_chip(data->domain, 0); data->gc->reg_base = base; data->gc->private = data; |
c0ca72620 irqchip/brcmstb-l... |
221 222 |
data->status_offset = init_params->cpu_status; data->mask_offset = init_params->cpu_mask_status; |
8480ca477 irqchip/brcmstb-l... |
223 |
|
49aa6ef0b irqchip/brcmstb-l... |
224 |
ct = data->gc->chip_types; |
7f646e927 irqchip: brcmstb-... |
225 |
|
c0ca72620 irqchip/brcmstb-l... |
226 227 228 229 230 231 232 233 |
if (init_params->cpu_clear >= 0) { ct->regs.ack = init_params->cpu_clear; ct->chip.irq_ack = irq_gc_ack_set_bit; ct->chip.irq_mask_ack = brcmstb_l2_mask_and_ack; } else { /* No Ack - but still slightly more efficient to define this */ ct->chip.irq_mask_ack = irq_gc_mask_disable_reg; } |
7f646e927 irqchip: brcmstb-... |
234 235 |
ct->chip.irq_mask = irq_gc_mask_disable_reg; |
c0ca72620 irqchip/brcmstb-l... |
236 237 |
ct->regs.disable = init_params->cpu_mask_set; ct->regs.mask = init_params->cpu_mask_status; |
7f646e927 irqchip: brcmstb-... |
238 239 |
ct->chip.irq_unmask = irq_gc_unmask_enable_reg; |
c0ca72620 irqchip/brcmstb-l... |
240 |
ct->regs.enable = init_params->cpu_mask_clear; |
7f646e927 irqchip: brcmstb-... |
241 242 243 |
ct->chip.irq_suspend = brcmstb_l2_intc_suspend; ct->chip.irq_resume = brcmstb_l2_intc_resume; |
c017d2114 irqchip: brcmstb-... |
244 |
ct->chip.irq_pm_shutdown = brcmstb_l2_intc_suspend; |
7f646e927 irqchip: brcmstb-... |
245 |
|
c9ae71e0f IRQCHIP: brcmstb-... |
246 |
if (data->can_wake) { |
7f646e927 irqchip: brcmstb-... |
247 248 249 |
/* This IRQ chip can wake the system, set all child interrupts * in wake_enabled mask */ |
49aa6ef0b irqchip/brcmstb-l... |
250 |
data->gc->wake_enabled = 0xffffffff; |
7f646e927 irqchip: brcmstb-... |
251 252 |
ct->chip.irq_set_wake = irq_gc_set_wake; } |
082ce27ff irqchip/bcm: Rest... |
253 254 |
pr_info("registered L2 intc (%pOF, parent irq: %d) ", np, parent_irq); |
7f646e927 irqchip: brcmstb-... |
255 256 257 258 259 |
return 0; out_free_domain: irq_domain_remove(data->domain); out_unmap: |
49aa6ef0b irqchip/brcmstb-l... |
260 |
iounmap(base); |
7f646e927 irqchip: brcmstb-... |
261 262 263 264 |
out_free: kfree(data); return ret; } |
c0ca72620 irqchip/brcmstb-l... |
265 |
|
dc3173c70 irqchip/brcmstb-l... |
266 |
static int __init brcmstb_l2_edge_intc_of_init(struct device_node *np, |
c0ca72620 irqchip/brcmstb-l... |
267 268 269 270 271 |
struct device_node *parent) { return brcmstb_l2_intc_of_init(np, parent, &l2_edge_intc_init); } IRQCHIP_DECLARE(brcmstb_l2_intc, "brcm,l2-intc", brcmstb_l2_edge_intc_of_init); |
dc3173c70 irqchip/brcmstb-l... |
272 |
static int __init brcmstb_l2_lvl_intc_of_init(struct device_node *np, |
c0ca72620 irqchip/brcmstb-l... |
273 274 275 276 277 278 |
struct device_node *parent) { return brcmstb_l2_intc_of_init(np, parent, &l2_lvl_intc_init); } IRQCHIP_DECLARE(bcm7271_l2_intc, "brcm,bcm7271-l2-intc", brcmstb_l2_lvl_intc_of_init); |