mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-24 04:34:08 +08:00
4b34410892
[ Upstream commit 9e9c4666ab
]
Controllers, supported by this driver, have two sets of registers:
* (main) interrupt registers control peripheral interrupt sources.
* device interrupt registers configure per-device (network interface)
interrupts and act as an extra stage before the main interrupt
registers.
In the driver unmask code, device trigger registers are used in the mask
calculation of the main interrupt sticky register, mixing two kinds of
registers.
Use the main interrupt trigger register instead.
Signed-off-by: Sergey Matsievskiy <matsievskiysv@gmail.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/all/20240925184416.54204-2-matsievskiysv@gmail.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
219 lines
5.5 KiB
C
219 lines
5.5 KiB
C
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
|
|
/*
|
|
* Microsemi Ocelot IRQ controller driver
|
|
*
|
|
* Copyright (c) 2017 Microsemi Corporation
|
|
*/
|
|
#include <linux/bitops.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/irqchip.h>
|
|
#include <linux/irqchip/chained_irq.h>
|
|
#include <linux/interrupt.h>
|
|
|
|
#define ICPU_CFG_INTR_DST_INTR_IDENT(_p, x) ((_p)->reg_off_ident + 0x4 * (x))
|
|
#define ICPU_CFG_INTR_INTR_TRIGGER(_p, x) ((_p)->reg_off_trigger + 0x4 * (x))
|
|
|
|
#define FLAGS_HAS_TRIGGER BIT(0)
|
|
#define FLAGS_NEED_INIT_ENABLE BIT(1)
|
|
|
|
struct chip_props {
|
|
u8 flags;
|
|
u8 reg_off_sticky;
|
|
u8 reg_off_ena;
|
|
u8 reg_off_ena_clr;
|
|
u8 reg_off_ena_set;
|
|
u8 reg_off_ident;
|
|
u8 reg_off_trigger;
|
|
u8 reg_off_ena_irq0;
|
|
u8 n_irq;
|
|
};
|
|
|
|
static struct chip_props ocelot_props = {
|
|
.flags = FLAGS_HAS_TRIGGER,
|
|
.reg_off_sticky = 0x10,
|
|
.reg_off_ena = 0x18,
|
|
.reg_off_ena_clr = 0x1c,
|
|
.reg_off_ena_set = 0x20,
|
|
.reg_off_ident = 0x38,
|
|
.reg_off_trigger = 0x4,
|
|
.n_irq = 24,
|
|
};
|
|
|
|
static struct chip_props serval_props = {
|
|
.flags = FLAGS_HAS_TRIGGER,
|
|
.reg_off_sticky = 0xc,
|
|
.reg_off_ena = 0x14,
|
|
.reg_off_ena_clr = 0x18,
|
|
.reg_off_ena_set = 0x1c,
|
|
.reg_off_ident = 0x20,
|
|
.reg_off_trigger = 0x4,
|
|
.n_irq = 24,
|
|
};
|
|
|
|
static struct chip_props luton_props = {
|
|
.flags = FLAGS_NEED_INIT_ENABLE,
|
|
.reg_off_sticky = 0,
|
|
.reg_off_ena = 0x4,
|
|
.reg_off_ena_clr = 0x8,
|
|
.reg_off_ena_set = 0xc,
|
|
.reg_off_ident = 0x18,
|
|
.reg_off_ena_irq0 = 0x14,
|
|
.n_irq = 28,
|
|
};
|
|
|
|
static struct chip_props jaguar2_props = {
|
|
.flags = FLAGS_HAS_TRIGGER,
|
|
.reg_off_sticky = 0x10,
|
|
.reg_off_ena = 0x18,
|
|
.reg_off_ena_clr = 0x1c,
|
|
.reg_off_ena_set = 0x20,
|
|
.reg_off_ident = 0x38,
|
|
.reg_off_trigger = 0x4,
|
|
.n_irq = 29,
|
|
};
|
|
|
|
static void ocelot_irq_unmask(struct irq_data *data)
|
|
{
|
|
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
|
|
struct irq_domain *d = data->domain;
|
|
struct chip_props *p = d->host_data;
|
|
struct irq_chip_type *ct = irq_data_get_chip_type(data);
|
|
unsigned int mask = data->mask;
|
|
u32 val;
|
|
|
|
irq_gc_lock(gc);
|
|
val = irq_reg_readl(gc, ICPU_CFG_INTR_INTR_TRIGGER(p, 0)) |
|
|
irq_reg_readl(gc, ICPU_CFG_INTR_INTR_TRIGGER(p, 1));
|
|
if (!(val & mask))
|
|
irq_reg_writel(gc, mask, p->reg_off_sticky);
|
|
|
|
*ct->mask_cache &= ~mask;
|
|
irq_reg_writel(gc, mask, p->reg_off_ena_set);
|
|
irq_gc_unlock(gc);
|
|
}
|
|
|
|
static void ocelot_irq_handler(struct irq_desc *desc)
|
|
{
|
|
struct irq_chip *chip = irq_desc_get_chip(desc);
|
|
struct irq_domain *d = irq_desc_get_handler_data(desc);
|
|
struct chip_props *p = d->host_data;
|
|
struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, 0);
|
|
u32 reg = irq_reg_readl(gc, ICPU_CFG_INTR_DST_INTR_IDENT(p, 0));
|
|
|
|
chained_irq_enter(chip, desc);
|
|
|
|
while (reg) {
|
|
u32 hwirq = __fls(reg);
|
|
|
|
generic_handle_domain_irq(d, hwirq);
|
|
reg &= ~(BIT(hwirq));
|
|
}
|
|
|
|
chained_irq_exit(chip, desc);
|
|
}
|
|
|
|
static int __init vcoreiii_irq_init(struct device_node *node,
|
|
struct device_node *parent,
|
|
struct chip_props *p)
|
|
{
|
|
struct irq_domain *domain;
|
|
struct irq_chip_generic *gc;
|
|
int parent_irq, ret;
|
|
|
|
parent_irq = irq_of_parse_and_map(node, 0);
|
|
if (!parent_irq)
|
|
return -EINVAL;
|
|
|
|
domain = irq_domain_add_linear(node, p->n_irq,
|
|
&irq_generic_chip_ops, NULL);
|
|
if (!domain) {
|
|
pr_err("%pOFn: unable to add irq domain\n", node);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = irq_alloc_domain_generic_chips(domain, p->n_irq, 1,
|
|
"icpu", handle_level_irq,
|
|
0, 0, 0);
|
|
if (ret) {
|
|
pr_err("%pOFn: unable to alloc irq domain gc\n", node);
|
|
goto err_domain_remove;
|
|
}
|
|
|
|
gc = irq_get_domain_generic_chip(domain, 0);
|
|
gc->reg_base = of_iomap(node, 0);
|
|
if (!gc->reg_base) {
|
|
pr_err("%pOFn: unable to map resource\n", node);
|
|
ret = -ENOMEM;
|
|
goto err_gc_free;
|
|
}
|
|
|
|
gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit;
|
|
gc->chip_types[0].regs.ack = p->reg_off_sticky;
|
|
if (p->flags & FLAGS_HAS_TRIGGER) {
|
|
gc->chip_types[0].regs.mask = p->reg_off_ena_clr;
|
|
gc->chip_types[0].chip.irq_unmask = ocelot_irq_unmask;
|
|
gc->chip_types[0].chip.irq_mask = irq_gc_mask_set_bit;
|
|
} else {
|
|
gc->chip_types[0].regs.enable = p->reg_off_ena_set;
|
|
gc->chip_types[0].regs.disable = p->reg_off_ena_clr;
|
|
gc->chip_types[0].chip.irq_mask = irq_gc_mask_disable_reg;
|
|
gc->chip_types[0].chip.irq_unmask = irq_gc_unmask_enable_reg;
|
|
}
|
|
|
|
/* Mask and ack all interrupts */
|
|
irq_reg_writel(gc, 0, p->reg_off_ena);
|
|
irq_reg_writel(gc, 0xffffffff, p->reg_off_sticky);
|
|
|
|
/* Overall init */
|
|
if (p->flags & FLAGS_NEED_INIT_ENABLE)
|
|
irq_reg_writel(gc, BIT(0), p->reg_off_ena_irq0);
|
|
|
|
domain->host_data = p;
|
|
irq_set_chained_handler_and_data(parent_irq, ocelot_irq_handler,
|
|
domain);
|
|
|
|
return 0;
|
|
|
|
err_gc_free:
|
|
irq_free_generic_chip(gc);
|
|
|
|
err_domain_remove:
|
|
irq_domain_remove(domain);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __init ocelot_irq_init(struct device_node *node,
|
|
struct device_node *parent)
|
|
{
|
|
return vcoreiii_irq_init(node, parent, &ocelot_props);
|
|
}
|
|
|
|
IRQCHIP_DECLARE(ocelot_icpu, "mscc,ocelot-icpu-intr", ocelot_irq_init);
|
|
|
|
static int __init serval_irq_init(struct device_node *node,
|
|
struct device_node *parent)
|
|
{
|
|
return vcoreiii_irq_init(node, parent, &serval_props);
|
|
}
|
|
|
|
IRQCHIP_DECLARE(serval_icpu, "mscc,serval-icpu-intr", serval_irq_init);
|
|
|
|
static int __init luton_irq_init(struct device_node *node,
|
|
struct device_node *parent)
|
|
{
|
|
return vcoreiii_irq_init(node, parent, &luton_props);
|
|
}
|
|
|
|
IRQCHIP_DECLARE(luton_icpu, "mscc,luton-icpu-intr", luton_irq_init);
|
|
|
|
static int __init jaguar2_irq_init(struct device_node *node,
|
|
struct device_node *parent)
|
|
{
|
|
return vcoreiii_irq_init(node, parent, &jaguar2_props);
|
|
}
|
|
|
|
IRQCHIP_DECLARE(jaguar2_icpu, "mscc,jaguar2-icpu-intr", jaguar2_irq_init);
|