diff mbox series

[6/6] MIPS: Loongson: Add support for 7A1000 interrupt controller

Message ID 1583742206-29163-7-git-send-email-yangtiezhu@loongson.cn (mailing list archive)
State Not Applicable, archived
Headers show
Series Add basic support for Loongson 7A1000 bridge chip | expand

Commit Message

Tiezhu Yang March 9, 2020, 8:23 a.m. UTC
Use interrupt model to support interrupt controller of Loongson
7A1000 bridge chip.

Co-developed-by: Jianmin Lv <lvjianmin@loongson.cn>
Signed-off-by: Jianmin Lv <lvjianmin@loongson.cn>
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
---
 arch/mips/include/asm/mach-loongson64/ioaicu.h | 166 ++++++++++++++
 arch/mips/include/asm/mach-loongson64/irq.h    |   2 +
 arch/mips/loongson64/Makefile                  |   2 +-
 arch/mips/loongson64/ioaicu.c                  | 305 +++++++++++++++++++++++++
 arch/mips/loongson64/irq.c                     |  20 +-
 arch/mips/loongson64/smp.c                     |  11 +-
 6 files changed, 497 insertions(+), 9 deletions(-)
 create mode 100644 arch/mips/include/asm/mach-loongson64/ioaicu.h
 create mode 100644 arch/mips/loongson64/ioaicu.c

Comments

Jiaxun Yang March 9, 2020, 9:07 a.m. UTC | #1
---- 在 星期一, 2020-03-09 16:23:26 Tiezhu Yang <yangtiezhu@loongson.cn> 撰写 ----
 > Use interrupt model to support interrupt controller of Loongson
 > 7A1000 bridge chip.

This patch seems clueless for me.
Partly beacuse you're hijacking what should be done by irq domain and irqchip.
Variuous of abbreviations makes me frustrated, so I didn't dig in to your logic deeper. 
See my review comments below.

 > 
 > Co-developed-by: Jianmin Lv <lvjianmin@loongson.cn>
 > Signed-off-by: Jianmin Lv <lvjianmin@loongson.cn>
 > Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
 > ---
 >  arch/mips/include/asm/mach-loongson64/ioaicu.h | 166 ++++++++++++++
 >  arch/mips/include/asm/mach-loongson64/irq.h    |   2 +
 >  arch/mips/loongson64/Makefile                  |   2 +-
 >  arch/mips/loongson64/ioaicu.c                  | 305 +++++++++++++++++++++++++
 >  arch/mips/loongson64/irq.c                     |  20 +-
 >  arch/mips/loongson64/smp.c                     |  11 +-
 >  6 files changed, 497 insertions(+), 9 deletions(-)
 >  create mode 100644 arch/mips/include/asm/mach-loongson64/ioaicu.h
 >  create mode 100644 arch/mips/loongson64/ioaicu.c
 > 
 > diff --git a/arch/mips/include/asm/mach-loongson64/ioaicu.h b/arch/mips/include/asm/mach-loongson64/ioaicu.h
 > new file mode 100644
 > index 0000000..e55de0d
 > --- /dev/null
 > +++ b/arch/mips/include/asm/mach-loongson64/ioaicu.h
 > @@ -0,0 +1,166 @@
 > +/* SPDX-License-Identifier: GPL-2.0 */
 > +/*
 > + * Copyright (C) 2020 Loongson Technology Corporation Limited
 > + *
 > + * Author: Jianmin Lv <lvjianmin@loongson.cn>
 > + * Author: Tiezhu Yang <yangtiezhu@loongson.cn>
 > + */
 > +
 > +#ifndef _ASM_LOONGSON_IOAICU_H
 > +#define _ASM_LOONGSON_IOAICU_H
 > +
 > +#define LS_CFG_BASE    0x3ff00000

Here your "_BASE" is using phys addr.

 > +#define LS_CFG_OFF(x)    ((void *)TO_UNCAC(LS_CFG_BASE) + (x))
 > +
 > +#define LS_IRC_BASE    LS_CFG_OFF(0x1400)
 > +#define LS_IRC_OFF(x)    (LS_IRC_BASE + x)
 > +
 > +#define LS_IRC_ENT(x)    LS_IRC_OFF(x)
 > +#define LS_IRC_EN    LS_IRC_OFF(0x24)
 > +#define LS_IRC_ENSET    LS_IRC_OFF(0x28)
 > +
 > +#define MAX_IOAICUS        16
 > +#define LS3A_MAX_IPI_IRQ    26
 > +#define LS3A_MAX_IO_VECTOR    64
 > +#define LS3A_MAX_IO_IRQ        256
 > +
 > +#define LS7A_PCH_REG_BASE    0x10000000
 > +#define LS7A_IOAICU_IRQ_BASE    64
 > +
 > +#define IOAICU_BASE    0x90000e0000000000ULL

But here your "_BASE" is using virt addr.

 > +#define IOAICU_IMR    0x20
 > +#define IOAICU_IER    0x60
 > +#define IOAICU_ICR    0x80
 > +#define IOAICU_IRR    0x100
 > +#define IOAICU_ISR    0x3a0
 > +#define IOAICU_IPR    0x3e0
 > +
 > +#define USE_HTH_INT0    0x1

What's that?

 > +
 > +#define LS3A_IOIRQ2VECTOR(irq) (irq - LS3A_MAX_IO_VECTOR)
 > +#define LS3A_VECTOR2IOIRQ(vector) (vector + LS3A_MAX_IO_VECTOR)
 > +
 > +struct ioaicu_config {
 > +    unsigned char aicuid;
 > +    unsigned char aicuver;
 > +    unsigned int aicuaddr;
 > +};
 > +
 > +struct ioaicu_gsi {
 > +    u32 gsi_base;
 > +    u32 gsi_end;
 > +};

What's meant by GSI?

 > +
 > +struct ioaicu {
 > +    int nr_registers;
 > +    struct ioaicu_config config;
 > +    struct ioaicu_gsi gsi_config;
 > +};
 > +
 > +struct loongson_irq_dispatch_ops {
 > +    void (*irq_dispatch)(void);
 > +};
 > +
 > +extern void setup_ioaicu(void);
 > +extern struct ioaicu ioaicus[MAX_IOAICUS];
 > +extern struct loongson_irq_dispatch_ops loongson_pch;
 > +extern unsigned short ls3a_ipi_pos2irq[LS3A_MAX_IO_VECTOR];
 > +
 > +extern void loongson3_send_irq_by_ipi(int cpu, int irqs);
 > +extern int plat_set_irq_affinity(struct irq_data *d,
 > +                 const struct cpumask *affinity, bool force);
 > +
 > +static inline unsigned long ioaicu_read64(unsigned int aicu, unsigned int reg)
 > +{
 > +    unsigned long addr = ioaicus[aicu].config.aicuaddr;
 > +
 > +    return *(unsigned long *)(IOAICU_BASE + addr + reg);

Please avoid raw pointer. Also applied to rest of the patch. 

 > +}
 > +
 > +static inline void ioaicu_write64(unsigned int aicu,
 > +                  unsigned int reg, unsigned long value)
 > +{
 > +    unsigned long addr = ioaicus[aicu].config.aicuaddr;
 > +    *(unsigned long *)(IOAICU_BASE + addr + reg) = value; 
> +}
 > +
 > +static inline unsigned char ioaicu_read8(unsigned int aicu, unsigned int reg)
 > +{
 > +    unsigned long addr = ioaicus[aicu].config.aicuaddr;
 > +
 > +    return *(unsigned char *)(IOAICU_BASE + addr + reg);
 > +}
 > +
 > +static inline void ioaicu_write8(unsigned int aicu,
 > +                 unsigned int reg, unsigned char value)
 > +{
 > +    unsigned long addr = ioaicus[aicu].config.aicuaddr;
 > +    *(unsigned char *)(IOAICU_BASE + addr + reg) = value;
 > +}
 > +
 > +static inline int ioaicu_get_redir_entries(int ioaicu)
 > +{
 > +    return 64;

Why not a macro?

 > +}
 > +
 > +static inline struct ioaicu_gsi *ioaicu_gsi_routing(int ioaicu_idx)
 > +{
 > +    return &ioaicus[ioaicu_idx].gsi_config;
 > +}
 > +
 > +static inline int ioaicu_id(int ioaicu_idx)
 > +{
 > +    return ioaicus[ioaicu_idx].config.aicuid;
 > +}
 > +
 > +static inline unsigned int ioaicu_addr(int ioaicu_idx)
 > +{
 > +    return ioaicus[ioaicu_idx].config.aicuaddr;
 > +}
 > +
 > +#define ioaicu_ver(ioaicu_idx) ioaicus[ioaicu_idx].config.aicuver
 > +
 > +static inline void ls64_conf_write64(u64 val64, void __iomem *addr)
 > +{
 > +    asm volatile (
 > +    "    .set push\n"
 > +    "    .set noreorder\n"
 > +    "    sd    %[v], (%[hw])\n"
 > +    "    lb    $0, (%[hw])\n"
 > +    "    .set pop\n"
 > +    :
 > +    : [hw] "r" (addr),  [v] "r" (val64)
 > +    );
 > +}

What are you doing here? I assume you're trying to workaround hardware bug?
Is it an io barrier? If so, please make it as a part of general io.h.

 > +
 > +static inline void ls64_conf_write32(u32 val, void __iomem *addr)
 > +{
 > +    asm volatile (
 > +    "    .set push\n"
 > +    "    .set noreorder\n"
 > +    "    sw    %[v], (%[hw])\n"
 > +    "    lb    $0, (%[hw])\n"
 > +    "    .set pop\n"
 > +    :
 > +    : [hw] "r" (addr), [v] "r" (val)
 > +    );
 > +}
 > +
 > +static inline void ls64_conf_write8(u8 val, void __iomem *addr)
 > +{
 > +    asm volatile (
 > +    "    .set push\n"
 > +    "    .set noreorder\n"
 > +    "    sb    %[v], (%[hw])\n"
 > +    "    lb    $0, (%[hw])\n"
 > +    "    .set pop\n"
 > +    :
 > +    : [hw] "r" (addr), [v] "r" (val)
 > +    );
 > +}
 > +
 > +#define ls64_conf_read64(addr) readq(addr)
 > +#define ls64_conf_read32(addr) readl(addr)
 > +#define ls64_conf_read8(addr) readb(addr)
 > +
 > +#endif /* _ASM_LOONGSON_IOAICU_H */
 > diff --git a/arch/mips/include/asm/mach-loongson64/irq.h b/arch/mips/include/asm/mach-loongson64/irq.h
 > index 73a8991..10568be 100644
 > --- a/arch/mips/include/asm/mach-loongson64/irq.h
 > +++ b/arch/mips/include/asm/mach-loongson64/irq.h
 > @@ -4,6 +4,8 @@
 >  
 >  #include <boot_param.h>
 >  
 > +#define NR_IRQS (64 + 256)
 > +
 >  /* cpu core interrupt numbers */
 >  #define MIPS_CPU_IRQ_BASE 56
 >  
 > diff --git a/arch/mips/loongson64/Makefile b/arch/mips/loongson64/Makefile
 > index 7821891..3db50c6 100644
 > --- a/arch/mips/loongson64/Makefile
 > +++ b/arch/mips/loongson64/Makefile
 > @@ -3,7 +3,7 @@
 >  # Makefile for Loongson-3 family machines
 >  #
 >  obj-$(CONFIG_MACH_LOONGSON64) += irq.o cop2-ex.o platform.o acpi_init.o dma.o \
 > -                setup.o init.o env.o time.o reset.o \
 > +                setup.o init.o env.o time.o reset.o ioaicu.o \
 >  
 >  obj-$(CONFIG_SMP)    += smp.o
 >  obj-$(CONFIG_NUMA)    += numa.o
 > diff --git a/arch/mips/loongson64/ioaicu.c b/arch/mips/loongson64/ioaicu.c
 > new file mode 100644
 > index 0000000..904ac75
 > --- /dev/null
 > +++ b/arch/mips/loongson64/ioaicu.c
 > @@ -0,0 +1,305 @@
 > +// SPDX-License-Identifier: GPL-2.0
 > +/*
 > + * Copyright (C) 2020 Loongson Technology Corporation Limited
 > + *
 > + * Author: Jianmin Lv <lvjianmin@loongson.cn>
 > + * Author: Tiezhu Yang <yangtiezhu@loongson.cn>
 > + */
 > +
 > +#include <loongson.h>
 > +#include <ioaicu.h>
 > +
 > +static DEFINE_SPINLOCK(pch_irq_lock);
 > +DECLARE_BITMAP(ls3a_ipi_in_use, LS3A_MAX_IPI_IRQ);
 > +struct loongson_irq_dispatch_ops loongson_pch;
 > +struct ioaicu ioaicus[MAX_IOAICUS];
 > +
 > +unsigned short ls3a_ipi_pos2irq[LS3A_MAX_IO_VECTOR] = {
 > +                    [0 ... LS3A_MAX_IO_VECTOR-1] = -1 };
 > +unsigned char ls3a_ipi_irq2pos[LS3A_MAX_IO_IRQ] = {
 > +                    [0 ... LS3A_MAX_IO_IRQ-1] = -1 };
 > +unsigned int cpu_for_irq[LS3A_MAX_IO_IRQ] = {
 > +                    [0 ... LS3A_MAX_IO_IRQ-1] = -1};
 > +
 > +u32 gsi_top;
 > +int nr_ioaicus;
 > +#define for_each_ioaicu(idx)    \
 > +    for ((idx) = 0; (idx) < nr_ioaicus; (idx)++)
 > +#define for_each_pin(idx, pin)    \
 > +    for ((pin) = 0; (pin) < ioaicus[(idx)].nr_registers; (pin)++)
 > +
 > +static int bad_ioaicu(unsigned long address)
 > +{
 > +    if (nr_ioaicus >= MAX_IOAICUS) {
 > +        pr_warn("WARNING: Max # of I/O AICUs (%d) exceeded (found %d), skipping\n",
 > +            MAX_IOAICUS, nr_ioaicus);
 > +        return 1;
 > +    }
 > +
 > +    if (!address) {
 > +        pr_warn("WARNING: Bogus (zero) I/O AICU address found in table, skipping!\n");
 > +        return 1;
 > +    }
 > +
 > +    return 0;
 > +}
 > +
 > +static void register_ioaicu(int id, u32 address, u32 gsi_base)
 > +{
 > +    int idx = 0;
 > +    int entries;
 > +    struct ioaicu_gsi *gsi_cfg;
 > +
 > +    if (bad_ioaicu(address))
 > +        return;
 > +
 > +    idx = nr_ioaicus;
 > +    ioaicus[idx].config.aicuaddr = address;
 > +    ioaicus[idx].config.aicuid = id;
 > +    ioaicus[idx].config.aicuver = 0;
 > +
 > +    entries = ioaicu_get_redir_entries(idx);
 > +    gsi_cfg = ioaicu_gsi_routing(idx);
 > +    gsi_cfg->gsi_base = gsi_base;
 > +    gsi_cfg->gsi_end = gsi_base + entries - 1;
 > +
 > +    ioaicus[idx].nr_registers = entries;
 > +
 > +    if (gsi_cfg->gsi_end >= gsi_top)
 > +        gsi_top = gsi_cfg->gsi_end + 1;
 > +
 > +    pr_info("IOAICU[%d]: aicu_id %d, version %d, address 0x%x, GSI %d-%d\n",
 > +        idx, ioaicu_id(idx), ioaicu_ver(idx), ioaicu_addr(idx),
 > +        gsi_cfg->gsi_base, gsi_cfg->gsi_end);
 > +
 > +    nr_ioaicus++;
 > +}
 > +
 > +static int find_ioaicu(u32 gsi)
 > +{
 > +    int i;
 > +
 > +    if (nr_ioaicus == 0)
 > +        return -1;
 > +
 > +    for_each_ioaicu(i) {
 > +        struct ioaicu_gsi *gsi_cfg = ioaicu_gsi_routing(i);
 > +
 > +        if (gsi >= gsi_cfg->gsi_base && gsi <= gsi_cfg->gsi_end)
 > +            return i;
 > +    }
 > +
 > +    pr_err("ERROR: Unable to locate IOAICU for GSI %d\n", gsi);
 > +
 > +    return -1;
 > +}
 > +
 > +static int pch_create_dirq(unsigned int irq)
 > +{
 > +    unsigned long flags;
 > +    int pos;
 > +
 > +    spin_lock_irqsave(&pch_irq_lock, flags);
 > +again:
 > +    pos = find_first_zero_bit(ls3a_ipi_in_use, LS3A_MAX_IPI_IRQ);
 > +    if (pos == LS3A_MAX_IPI_IRQ) {
 > +        spin_unlock_irqrestore(&pch_irq_lock, flags);
 > +        return -ENOSPC;
 > +    }

See Hierarchy IRQ domain, that's what you need.


 > +
 > +    if (test_and_set_bit(pos, ls3a_ipi_in_use))
 > +        goto again;
 > +
 > +    ls3a_ipi_pos2irq[pos] = irq;
 > +    ls3a_ipi_irq2pos[LS3A_IOIRQ2VECTOR(irq)] = pos;
 > +    spin_unlock_irqrestore(&pch_irq_lock, flags);
 > +
 > +    return 0;
 > +}
 > +
 > +static void pch_destroy_dirq(unsigned int irq)
 > +{
 > +    unsigned long flags;
 > +    int pos;
 > +
 > +    spin_lock_irqsave(&pch_irq_lock, flags);
 > +    pos = ls3a_ipi_irq2pos[LS3A_IOIRQ2VECTOR(irq)];
 > +    if (pos >= 0) {
 > +        clear_bit(pos, ls3a_ipi_in_use);
 > +        ls3a_ipi_irq2pos[LS3A_IOIRQ2VECTOR(irq)] = -1;
 > +        ls3a_ipi_pos2irq[pos] = -1;
 > +    }
 > +    spin_unlock_irqrestore(&pch_irq_lock, flags);
 > +}
 > +
 > +static void line_mask_pch_irq(struct irq_data *d)
 > +{
 > +    unsigned long flags, data;
 > +    unsigned int ioaicu;
 > +    unsigned long irq_nr = d->irq;
 > +
 > +    ioaicu = (unsigned int)find_ioaicu((u32)irq_nr);
 > +
 > +    spin_lock_irqsave(&pch_irq_lock, flags);
 > +    data = ioaicu_read64(ioaicu, IOAICU_IMR);
 > +    data |= (1ULL << LS3A_IOIRQ2VECTOR(irq_nr));
 > +    ioaicu_write64(ioaicu, IOAICU_IMR, data);
 > +    spin_unlock_irqrestore(&pch_irq_lock, flags);
 > +}
 > +
 > +static void line_unmask_pch_irq(struct irq_data *d)
 > +{
 > +    unsigned long flags, data;
 > +    unsigned int ioaicu;
 > +    unsigned long irq_nr = d->irq;
 > +
 > +    ioaicu = (unsigned int)find_ioaicu((u32)irq_nr);
 > +
 > +    spin_lock_irqsave(&pch_irq_lock, flags);
 > +    data = ioaicu_read64(ioaicu, IOAICU_IMR);
 > +    data &= ~(1ULL << LS3A_IOIRQ2VECTOR(irq_nr));
 > +    ioaicu_write64(ioaicu, IOAICU_IMR, data);
 > +    spin_unlock_irqrestore(&pch_irq_lock, flags);
 > +}
 > +
 > +static unsigned int line_startup_pch_irq(struct irq_data *d)
 > +{
 > +    pch_create_dirq(d->irq);
 > +    line_unmask_pch_irq(d);
 > +
 > +    return 0;
 > +}
 > +
 > +static void line_shutdown_pch_irq(struct irq_data *d)
 > +{
 > +    line_mask_pch_irq(d);
 > +    pch_destroy_dirq(d->irq);
 > +}
 > +
 > +static struct irq_chip ioaicu_line_chip = {
 > +    .name            = "IOAICU-LINE",
 > +    .irq_mask        = line_mask_pch_irq,
 > +    .irq_unmask        = line_unmask_pch_irq,
 > +    .irq_startup        = line_startup_pch_irq,
 > +    .irq_shutdown        = line_shutdown_pch_irq,
 > +    .irq_set_affinity    = plat_set_irq_affinity,
 > +};
 > +
 > +static void line_route_init(void)
 > +{
 > +    unsigned int dummy;
 > +
 > +    /* route 3A CPU0 INT0 to node0 core0 INT1(IP3) */
 > +    dummy = LOONGSON_INT_COREx_INTy(loongson_sysconf.boot_cpu_id, 1);
 > +    ls64_conf_write8(dummy, LS_IRC_ENT(0));
 > +
 > +    dummy = ls64_conf_read32(LS_IRC_EN);
 > +    dummy |= 0x1;
 > +    ls64_conf_write32(dummy, LS_IRC_ENSET);
 > +}
 > +
 > +static void init_pin_route(unsigned int ioaicu, int vec)
 > +{
 > +    ioaicu_write8(ioaicu, IOAICU_IRR + vec, USE_HTH_INT0);
 > +}
 > +
 > +static void init_ioaicu_pin(unsigned int ioaicu, int pin,
 > +                struct irq_chip *pirq_chip)
 > +{
 > +    init_pin_route(ioaicu, LS3A_IOIRQ2VECTOR(pin));
 > +    irq_set_chip_and_handler(pin, pirq_chip, handle_level_irq);
 > +}
 > +
 > +static void setup_ioaicu_irqs(struct irq_chip *pirq_chip)
 > +{
 > +    unsigned int ioaicu, pin;
 > +    struct ioaicu_gsi *gsi_cfg;
 > +
 > +    for_each_ioaicu(ioaicu) {
 > +        ioaicu_write64(ioaicu, IOAICU_IER, 0);
 > +        ioaicu_write64(ioaicu, IOAICU_IPR, (1ULL << 59));
 > +        ioaicu_write64(ioaicu, IOAICU_ISR, 0);
 > +        ioaicu_write64(ioaicu, IOAICU_IMR, -1ULL);
 > +        ioaicu_write64(ioaicu, IOAICU_ICR, -1ULL);
 > +    }
 > +
 > +    for (ioaicu = 0; ioaicu < nr_ioaicus; ioaicu++) {
 > +        for (pin = 0; pin < ioaicus[ioaicu].nr_registers; pin++) {
 > +            gsi_cfg = ioaicu_gsi_routing(ioaicu);
 > +            init_ioaicu_pin(ioaicu,
 > +                    pin + gsi_cfg->gsi_base, pirq_chip);
 > +        }
 > +    }
 > +}
 > +
 > +static void handle_irqs(unsigned long long irqs, int i)
 > +{
 > +    unsigned int irq;
 > +    struct irq_data *irqd;
 > +    struct cpumask affinity;
 > +    int cpu = smp_processor_id();
 > +
 > +    while (irqs) {
 > +        irq = __ffs(irqs);
 > +        irqs &= ~(1ULL<<irq);
 > +        irq += (i << 6);
 > +
 > +        /* handled by local core */
 > +        if (ls3a_ipi_irq2pos[irq] == (unsigned char)-1) {
 > +            do_IRQ(LS3A_VECTOR2IOIRQ(irq));
 > +            continue;
 > +        }
 > +
 > +        irqd = irq_get_irq_data(LS3A_VECTOR2IOIRQ(irq));
 > +        cpumask_and(&affinity, irqd->common->affinity, cpu_active_mask);
 > +        if (cpumask_empty(&affinity)) {
 > +            do_IRQ(LS3A_VECTOR2IOIRQ(irq));
 > +            continue;
 > +        }
 > +
 > +        cpu_for_irq[irq] = cpumask_next(cpu_for_irq[irq], &affinity);
 > +        if (cpu_for_irq[irq] >= nr_cpu_ids)
 > +            cpu_for_irq[irq] = cpumask_first(&affinity);
 > +
 > +        if (cpu_for_irq[irq] == cpu) {
 > +            do_IRQ(LS3A_VECTOR2IOIRQ(irq));
 > +            continue;
 > +        }
 > +
 > +        /* balanced by other cores */
 > +        loongson3_send_irq_by_ipi(cpu_for_irq[irq],
 > +                      (0x1 << (ls3a_ipi_irq2pos[irq])));
 > +    }
 > +}
 > +
 > +static void ioaicu_line_dispatch(void)
 > +{
 > +    unsigned long flags;
 > +    unsigned int ioaicu;
 > +    unsigned long long intstatus;
 > +    unsigned long long intmask;
 > +
 > +    for_each_ioaicu(ioaicu) {
 > +        /* read irq status register */
 > +        intstatus = ioaicu_read64(ioaicu, IOAICU_ISR);
 > +
 > +        spin_lock_irqsave(&pch_irq_lock, flags);
 > +        intmask = ioaicu_read64(ioaicu, IOAICU_IMR);
 > +        intmask |= intstatus;
 > +        ioaicu_write64(ioaicu, IOAICU_IMR, intmask);
 > +        intmask = ioaicu_read64(ioaicu, IOAICU_IMR);
 > +        spin_unlock_irqrestore(&pch_irq_lock, flags);
 > +
 > +        handle_irqs(intstatus, 0);
 > +    }
 > +}
 > +
 > +/* ioaicu: input/output advanced interrupt control unit */
 > +void __init setup_ioaicu(void)
 > +{
 > +    register_ioaicu(0, LS7A_PCH_REG_BASE, LS7A_IOAICU_IRQ_BASE);
 > +
 > +    line_route_init();
 > +    setup_ioaicu_irqs(&ioaicu_line_chip);
 > +    loongson_pch.irq_dispatch = ioaicu_line_dispatch;
 > +}
 > diff --git a/arch/mips/loongson64/irq.c b/arch/mips/loongson64/irq.c
 > index 79ad797..988f21f 100644
 > --- a/arch/mips/loongson64/irq.c
 > +++ b/arch/mips/loongson64/irq.c
 > @@ -1,6 +1,7 @@
 >  // SPDX-License-Identifier: GPL-2.0
 >  #include <loongson.h>
 >  #include <irq.h>
 > +#include <ioaicu.h>
 >  #include <linux/interrupt.h>
 >  #include <linux/init.h>
 >  
 > @@ -71,6 +72,8 @@ static void ht_irqdispatch(void)
 >              continue;
 >          }
 >  
 > +        ls3a_ipi_pos2irq[ht_irq[i]] = ht_irq[i];
 > +
 >          /* balanced by other cores */
 >          loongson3_send_irq_by_ipi(irq_cpu[ht_irq[i]], (0x1 << ht_irq[i]));
 >      }
 > @@ -91,7 +94,7 @@ asmlinkage void plat_irq_dispatch(void)
 >          loongson3_ipi_interrupt(NULL);
 >  #endif
 >      if (pending & CAUSEF_IP3)
 > -        ht_irqdispatch();
 > +        loongson_pch.irq_dispatch();
 >      if (pending & CAUSEF_IP2)
 >          do_IRQ(LOONGSON_UART_IRQ);
 >      if (pending & UNUSED_IPS) {
 > @@ -137,11 +140,18 @@ void __init arch_init_irq(void)
 >  
 >      clear_c0_status(ST0_IM | ST0_BEV);
 >  
 > -    irq_router_init();
 >      mips_cpu_irq_init();
 > -    init_i8259_irqs();
 > -    chip = irq_get_chip(I8259A_IRQ_BASE);
 > -    chip->irq_set_affinity = plat_set_irq_affinity;
 > +
 > +    if (strstr(eboard->name, "780E")) {
 > +        irq_router_init();
 > +        init_i8259_irqs();
 > +        loongson_pch.irq_dispatch = ht_irqdispatch;
 > +        chip = irq_get_chip(I8259A_IRQ_BASE);
 > +        chip->irq_set_affinity = plat_set_irq_affinity;
 > +    }
 > +
 > +    if (strstr(eboard->name, "7A1000"))
 > +        setup_ioaicu();

Ahh... strstr again.
Forgot to mention, you'd better make RS780E as fallback when no PCH type string
found in eboard name. That's to Loongson's crappy specification, 2/5 of my Loongson
boards didn't set their board name correctly.

 >  
 >      irq_set_chip_and_handler(LOONGSON_UART_IRQ,
 >              &loongson_irq_chip, handle_percpu_irq);
 > diff --git a/arch/mips/loongson64/smp.c b/arch/mips/loongson64/smp.c
 > index de8e074..0720df7 100644
 > --- a/arch/mips/loongson64/smp.c
 > +++ b/arch/mips/loongson64/smp.c
 > @@ -20,6 +20,7 @@
 >  #include <loongson.h>
 >  #include <loongson_regs.h>
 >  #include <workarounds.h>
 > +#include <ioaicu.h>
 >  
 >  #include "smp.h"
 >  
 > @@ -336,10 +337,14 @@ void loongson3_ipi_interrupt(struct pt_regs *regs)
 >      }
 >  
 >      if (irqs) {
 > -        int irq;
 > +        int irq, irq1;
 > +
 >          while ((irq = ffs(irqs))) {
 > -            do_IRQ(irq-1);
 > -            irqs &= ~(1<<(irq-1));
 > +            irq1 = ls3a_ipi_pos2irq[irq - 1];
 > +            if (likely(irq1 != (unsigned short)-1)) {
 > +                do_IRQ(irq1);
 > +                irqs &= ~(1 << (irq - 1));
 > +            }
 >          }
 >      }
 >  }
 > -- 
 > 2.1.0
 > 
 >
Tiezhu Yang March 10, 2020, 4:28 a.m. UTC | #2
On 03/09/2020 05:07 PM, Jiaxun Yang wrote:
>   ---- 在 星期一, 2020-03-09 16:23:26 Tiezhu Yang <yangtiezhu@loongson.cn> 撰写 ----
>   > Use interrupt model to support interrupt controller of Loongson
>   > 7A1000 bridge chip.
>
> This patch seems clueless for me.
> Partly beacuse you're hijacking what should be done by irq domain and irqchip.
> Variuous of abbreviations makes me frustrated, so I didn't dig in to your logic deeper.
> See my review comments below.

This implementation is an abstract interrupt model, it is
easily extended to support the new Loongson bridge chip
7A2000, 7A3000, ...

I will modify the code to make it more clear about the
following comments you mentioned.

Thanks,

Tiezhu Yang

>
>   >
>   > Co-developed-by: Jianmin Lv <lvjianmin@loongson.cn>
>   > Signed-off-by: Jianmin Lv <lvjianmin@loongson.cn>
>   > Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
>   > ---
>   >  arch/mips/include/asm/mach-loongson64/ioaicu.h | 166 ++++++++++++++
>   >  arch/mips/include/asm/mach-loongson64/irq.h    |   2 +
>   >  arch/mips/loongson64/Makefile                  |   2 +-
>   >  arch/mips/loongson64/ioaicu.c                  | 305 +++++++++++++++++++++++++
>   >  arch/mips/loongson64/irq.c                     |  20 +-
>   >  arch/mips/loongson64/smp.c                     |  11 +-
>   >  6 files changed, 497 insertions(+), 9 deletions(-)
>   >  create mode 100644 arch/mips/include/asm/mach-loongson64/ioaicu.h
>   >  create mode 100644 arch/mips/loongson64/ioaicu.c
>   >
>   > diff --git a/arch/mips/include/asm/mach-loongson64/ioaicu.h b/arch/mips/include/asm/mach-loongson64/ioaicu.h
>   > new file mode 100644
>   > index 0000000..e55de0d
>   > --- /dev/null
>   > +++ b/arch/mips/include/asm/mach-loongson64/ioaicu.h
>   > @@ -0,0 +1,166 @@
>   > +/* SPDX-License-Identifier: GPL-2.0 */
>   > +/*
>   > + * Copyright (C) 2020 Loongson Technology Corporation Limited
>   > + *
>   > + * Author: Jianmin Lv <lvjianmin@loongson.cn>
>   > + * Author: Tiezhu Yang <yangtiezhu@loongson.cn>
>   > + */
>   > +
>   > +#ifndef _ASM_LOONGSON_IOAICU_H
>   > +#define _ASM_LOONGSON_IOAICU_H
>   > +
>   > +#define LS_CFG_BASE    0x3ff00000
>
> Here your "_BASE" is using phys addr.
>
>   > +#define LS_CFG_OFF(x)    ((void *)TO_UNCAC(LS_CFG_BASE) + (x))
>   > +
>   > +#define LS_IRC_BASE    LS_CFG_OFF(0x1400)
>   > +#define LS_IRC_OFF(x)    (LS_IRC_BASE + x)
>   > +
>   > +#define LS_IRC_ENT(x)    LS_IRC_OFF(x)
>   > +#define LS_IRC_EN    LS_IRC_OFF(0x24)
>   > +#define LS_IRC_ENSET    LS_IRC_OFF(0x28)
>   > +
>   > +#define MAX_IOAICUS        16
>   > +#define LS3A_MAX_IPI_IRQ    26
>   > +#define LS3A_MAX_IO_VECTOR    64
>   > +#define LS3A_MAX_IO_IRQ        256
>   > +
>   > +#define LS7A_PCH_REG_BASE    0x10000000
>   > +#define LS7A_IOAICU_IRQ_BASE    64
>   > +
>   > +#define IOAICU_BASE    0x90000e0000000000ULL
>
> But here your "_BASE" is using virt addr.
>
>   > +#define IOAICU_IMR    0x20
>   > +#define IOAICU_IER    0x60
>   > +#define IOAICU_ICR    0x80
>   > +#define IOAICU_IRR    0x100
>   > +#define IOAICU_ISR    0x3a0
>   > +#define IOAICU_IPR    0x3e0
>   > +
>   > +#define USE_HTH_INT0    0x1
>
> What's that?
>
>   > +
>   > +#define LS3A_IOIRQ2VECTOR(irq) (irq - LS3A_MAX_IO_VECTOR)
>   > +#define LS3A_VECTOR2IOIRQ(vector) (vector + LS3A_MAX_IO_VECTOR)
>   > +
>   > +struct ioaicu_config {
>   > +    unsigned char aicuid;
>   > +    unsigned char aicuver;
>   > +    unsigned int aicuaddr;
>   > +};
>   > +
>   > +struct ioaicu_gsi {
>   > +    u32 gsi_base;
>   > +    u32 gsi_end;
>   > +};
>
> What's meant by GSI?
>
>   > +
>   > +struct ioaicu {
>   > +    int nr_registers;
>   > +    struct ioaicu_config config;
>   > +    struct ioaicu_gsi gsi_config;
>   > +};
>   > +
>   > +struct loongson_irq_dispatch_ops {
>   > +    void (*irq_dispatch)(void);
>   > +};
>   > +
>   > +extern void setup_ioaicu(void);
>   > +extern struct ioaicu ioaicus[MAX_IOAICUS];
>   > +extern struct loongson_irq_dispatch_ops loongson_pch;
>   > +extern unsigned short ls3a_ipi_pos2irq[LS3A_MAX_IO_VECTOR];
>   > +
>   > +extern void loongson3_send_irq_by_ipi(int cpu, int irqs);
>   > +extern int plat_set_irq_affinity(struct irq_data *d,
>   > +                 const struct cpumask *affinity, bool force);
>   > +
>   > +static inline unsigned long ioaicu_read64(unsigned int aicu, unsigned int reg)
>   > +{
>   > +    unsigned long addr = ioaicus[aicu].config.aicuaddr;
>   > +
>   > +    return *(unsigned long *)(IOAICU_BASE + addr + reg);
>
> Please avoid raw pointer. Also applied to rest of the patch.
>
>   > +}
>   > +
>   > +static inline void ioaicu_write64(unsigned int aicu,
>   > +                  unsigned int reg, unsigned long value)
>   > +{
>   > +    unsigned long addr = ioaicus[aicu].config.aicuaddr;
>   > +    *(unsigned long *)(IOAICU_BASE + addr + reg) = value;
>> +}
>   > +
>   > +static inline unsigned char ioaicu_read8(unsigned int aicu, unsigned int reg)
>   > +{
>   > +    unsigned long addr = ioaicus[aicu].config.aicuaddr;
>   > +
>   > +    return *(unsigned char *)(IOAICU_BASE + addr + reg);
>   > +}
>   > +
>   > +static inline void ioaicu_write8(unsigned int aicu,
>   > +                 unsigned int reg, unsigned char value)
>   > +{
>   > +    unsigned long addr = ioaicus[aicu].config.aicuaddr;
>   > +    *(unsigned char *)(IOAICU_BASE + addr + reg) = value;
>   > +}
>   > +
>   > +static inline int ioaicu_get_redir_entries(int ioaicu)
>   > +{
>   > +    return 64;
>
> Why not a macro?
>
>   > +}
>   > +
>   > +static inline struct ioaicu_gsi *ioaicu_gsi_routing(int ioaicu_idx)
>   > +{
>   > +    return &ioaicus[ioaicu_idx].gsi_config;
>   > +}
>   > +
>   > +static inline int ioaicu_id(int ioaicu_idx)
>   > +{
>   > +    return ioaicus[ioaicu_idx].config.aicuid;
>   > +}
>   > +
>   > +static inline unsigned int ioaicu_addr(int ioaicu_idx)
>   > +{
>   > +    return ioaicus[ioaicu_idx].config.aicuaddr;
>   > +}
>   > +
>   > +#define ioaicu_ver(ioaicu_idx) ioaicus[ioaicu_idx].config.aicuver
>   > +
>   > +static inline void ls64_conf_write64(u64 val64, void __iomem *addr)
>   > +{
>   > +    asm volatile (
>   > +    "    .set push\n"
>   > +    "    .set noreorder\n"
>   > +    "    sd    %[v], (%[hw])\n"
>   > +    "    lb    $0, (%[hw])\n"
>   > +    "    .set pop\n"
>   > +    :
>   > +    : [hw] "r" (addr),  [v] "r" (val64)
>   > +    );
>   > +}
>
> What are you doing here? I assume you're trying to workaround hardware bug?
> Is it an io barrier? If so, please make it as a part of general io.h.
>
>   > +
>   > +static inline void ls64_conf_write32(u32 val, void __iomem *addr)
>   > +{
>   > +    asm volatile (
>   > +    "    .set push\n"
>   > +    "    .set noreorder\n"
>   > +    "    sw    %[v], (%[hw])\n"
>   > +    "    lb    $0, (%[hw])\n"
>   > +    "    .set pop\n"
>   > +    :
>   > +    : [hw] "r" (addr), [v] "r" (val)
>   > +    );
>   > +}
>   > +
>   > +static inline void ls64_conf_write8(u8 val, void __iomem *addr)
>   > +{
>   > +    asm volatile (
>   > +    "    .set push\n"
>   > +    "    .set noreorder\n"
>   > +    "    sb    %[v], (%[hw])\n"
>   > +    "    lb    $0, (%[hw])\n"
>   > +    "    .set pop\n"
>   > +    :
>   > +    : [hw] "r" (addr), [v] "r" (val)
>   > +    );
>   > +}
>   > +
>   > +#define ls64_conf_read64(addr) readq(addr)
>   > +#define ls64_conf_read32(addr) readl(addr)
>   > +#define ls64_conf_read8(addr) readb(addr)
>   > +
>   > +#endif /* _ASM_LOONGSON_IOAICU_H */
>   > diff --git a/arch/mips/include/asm/mach-loongson64/irq.h b/arch/mips/include/asm/mach-loongson64/irq.h
>   > index 73a8991..10568be 100644
>   > --- a/arch/mips/include/asm/mach-loongson64/irq.h
>   > +++ b/arch/mips/include/asm/mach-loongson64/irq.h
>   > @@ -4,6 +4,8 @@
>   >
>   >  #include <boot_param.h>
>   >
>   > +#define NR_IRQS (64 + 256)
>   > +
>   >  /* cpu core interrupt numbers */
>   >  #define MIPS_CPU_IRQ_BASE 56
>   >
>   > diff --git a/arch/mips/loongson64/Makefile b/arch/mips/loongson64/Makefile
>   > index 7821891..3db50c6 100644
>   > --- a/arch/mips/loongson64/Makefile
>   > +++ b/arch/mips/loongson64/Makefile
>   > @@ -3,7 +3,7 @@
>   >  # Makefile for Loongson-3 family machines
>   >  #
>   >  obj-$(CONFIG_MACH_LOONGSON64) += irq.o cop2-ex.o platform.o acpi_init.o dma.o \
>   > -                setup.o init.o env.o time.o reset.o \
>   > +                setup.o init.o env.o time.o reset.o ioaicu.o \
>   >
>   >  obj-$(CONFIG_SMP)    += smp.o
>   >  obj-$(CONFIG_NUMA)    += numa.o
>   > diff --git a/arch/mips/loongson64/ioaicu.c b/arch/mips/loongson64/ioaicu.c
>   > new file mode 100644
>   > index 0000000..904ac75
>   > --- /dev/null
>   > +++ b/arch/mips/loongson64/ioaicu.c
>   > @@ -0,0 +1,305 @@
>   > +// SPDX-License-Identifier: GPL-2.0
>   > +/*
>   > + * Copyright (C) 2020 Loongson Technology Corporation Limited
>   > + *
>   > + * Author: Jianmin Lv <lvjianmin@loongson.cn>
>   > + * Author: Tiezhu Yang <yangtiezhu@loongson.cn>
>   > + */
>   > +
>   > +#include <loongson.h>
>   > +#include <ioaicu.h>
>   > +
>   > +static DEFINE_SPINLOCK(pch_irq_lock);
>   > +DECLARE_BITMAP(ls3a_ipi_in_use, LS3A_MAX_IPI_IRQ);
>   > +struct loongson_irq_dispatch_ops loongson_pch;
>   > +struct ioaicu ioaicus[MAX_IOAICUS];
>   > +
>   > +unsigned short ls3a_ipi_pos2irq[LS3A_MAX_IO_VECTOR] = {
>   > +                    [0 ... LS3A_MAX_IO_VECTOR-1] = -1 };
>   > +unsigned char ls3a_ipi_irq2pos[LS3A_MAX_IO_IRQ] = {
>   > +                    [0 ... LS3A_MAX_IO_IRQ-1] = -1 };
>   > +unsigned int cpu_for_irq[LS3A_MAX_IO_IRQ] = {
>   > +                    [0 ... LS3A_MAX_IO_IRQ-1] = -1};
>   > +
>   > +u32 gsi_top;
>   > +int nr_ioaicus;
>   > +#define for_each_ioaicu(idx)    \
>   > +    for ((idx) = 0; (idx) < nr_ioaicus; (idx)++)
>   > +#define for_each_pin(idx, pin)    \
>   > +    for ((pin) = 0; (pin) < ioaicus[(idx)].nr_registers; (pin)++)
>   > +
>   > +static int bad_ioaicu(unsigned long address)
>   > +{
>   > +    if (nr_ioaicus >= MAX_IOAICUS) {
>   > +        pr_warn("WARNING: Max # of I/O AICUs (%d) exceeded (found %d), skipping\n",
>   > +            MAX_IOAICUS, nr_ioaicus);
>   > +        return 1;
>   > +    }
>   > +
>   > +    if (!address) {
>   > +        pr_warn("WARNING: Bogus (zero) I/O AICU address found in table, skipping!\n");
>   > +        return 1;
>   > +    }
>   > +
>   > +    return 0;
>   > +}
>   > +
>   > +static void register_ioaicu(int id, u32 address, u32 gsi_base)
>   > +{
>   > +    int idx = 0;
>   > +    int entries;
>   > +    struct ioaicu_gsi *gsi_cfg;
>   > +
>   > +    if (bad_ioaicu(address))
>   > +        return;
>   > +
>   > +    idx = nr_ioaicus;
>   > +    ioaicus[idx].config.aicuaddr = address;
>   > +    ioaicus[idx].config.aicuid = id;
>   > +    ioaicus[idx].config.aicuver = 0;
>   > +
>   > +    entries = ioaicu_get_redir_entries(idx);
>   > +    gsi_cfg = ioaicu_gsi_routing(idx);
>   > +    gsi_cfg->gsi_base = gsi_base;
>   > +    gsi_cfg->gsi_end = gsi_base + entries - 1;
>   > +
>   > +    ioaicus[idx].nr_registers = entries;
>   > +
>   > +    if (gsi_cfg->gsi_end >= gsi_top)
>   > +        gsi_top = gsi_cfg->gsi_end + 1;
>   > +
>   > +    pr_info("IOAICU[%d]: aicu_id %d, version %d, address 0x%x, GSI %d-%d\n",
>   > +        idx, ioaicu_id(idx), ioaicu_ver(idx), ioaicu_addr(idx),
>   > +        gsi_cfg->gsi_base, gsi_cfg->gsi_end);
>   > +
>   > +    nr_ioaicus++;
>   > +}
>   > +
>   > +static int find_ioaicu(u32 gsi)
>   > +{
>   > +    int i;
>   > +
>   > +    if (nr_ioaicus == 0)
>   > +        return -1;
>   > +
>   > +    for_each_ioaicu(i) {
>   > +        struct ioaicu_gsi *gsi_cfg = ioaicu_gsi_routing(i);
>   > +
>   > +        if (gsi >= gsi_cfg->gsi_base && gsi <= gsi_cfg->gsi_end)
>   > +            return i;
>   > +    }
>   > +
>   > +    pr_err("ERROR: Unable to locate IOAICU for GSI %d\n", gsi);
>   > +
>   > +    return -1;
>   > +}
>   > +
>   > +static int pch_create_dirq(unsigned int irq)
>   > +{
>   > +    unsigned long flags;
>   > +    int pos;
>   > +
>   > +    spin_lock_irqsave(&pch_irq_lock, flags);
>   > +again:
>   > +    pos = find_first_zero_bit(ls3a_ipi_in_use, LS3A_MAX_IPI_IRQ);
>   > +    if (pos == LS3A_MAX_IPI_IRQ) {
>   > +        spin_unlock_irqrestore(&pch_irq_lock, flags);
>   > +        return -ENOSPC;
>   > +    }
>
> See Hierarchy IRQ domain, that's what you need.
>
>
>   > +
>   > +    if (test_and_set_bit(pos, ls3a_ipi_in_use))
>   > +        goto again;
>   > +
>   > +    ls3a_ipi_pos2irq[pos] = irq;
>   > +    ls3a_ipi_irq2pos[LS3A_IOIRQ2VECTOR(irq)] = pos;
>   > +    spin_unlock_irqrestore(&pch_irq_lock, flags);
>   > +
>   > +    return 0;
>   > +}
>   > +
>   > +static void pch_destroy_dirq(unsigned int irq)
>   > +{
>   > +    unsigned long flags;
>   > +    int pos;
>   > +
>   > +    spin_lock_irqsave(&pch_irq_lock, flags);
>   > +    pos = ls3a_ipi_irq2pos[LS3A_IOIRQ2VECTOR(irq)];
>   > +    if (pos >= 0) {
>   > +        clear_bit(pos, ls3a_ipi_in_use);
>   > +        ls3a_ipi_irq2pos[LS3A_IOIRQ2VECTOR(irq)] = -1;
>   > +        ls3a_ipi_pos2irq[pos] = -1;
>   > +    }
>   > +    spin_unlock_irqrestore(&pch_irq_lock, flags);
>   > +}
>   > +
>   > +static void line_mask_pch_irq(struct irq_data *d)
>   > +{
>   > +    unsigned long flags, data;
>   > +    unsigned int ioaicu;
>   > +    unsigned long irq_nr = d->irq;
>   > +
>   > +    ioaicu = (unsigned int)find_ioaicu((u32)irq_nr);
>   > +
>   > +    spin_lock_irqsave(&pch_irq_lock, flags);
>   > +    data = ioaicu_read64(ioaicu, IOAICU_IMR);
>   > +    data |= (1ULL << LS3A_IOIRQ2VECTOR(irq_nr));
>   > +    ioaicu_write64(ioaicu, IOAICU_IMR, data);
>   > +    spin_unlock_irqrestore(&pch_irq_lock, flags);
>   > +}
>   > +
>   > +static void line_unmask_pch_irq(struct irq_data *d)
>   > +{
>   > +    unsigned long flags, data;
>   > +    unsigned int ioaicu;
>   > +    unsigned long irq_nr = d->irq;
>   > +
>   > +    ioaicu = (unsigned int)find_ioaicu((u32)irq_nr);
>   > +
>   > +    spin_lock_irqsave(&pch_irq_lock, flags);
>   > +    data = ioaicu_read64(ioaicu, IOAICU_IMR);
>   > +    data &= ~(1ULL << LS3A_IOIRQ2VECTOR(irq_nr));
>   > +    ioaicu_write64(ioaicu, IOAICU_IMR, data);
>   > +    spin_unlock_irqrestore(&pch_irq_lock, flags);
>   > +}
>   > +
>   > +static unsigned int line_startup_pch_irq(struct irq_data *d)
>   > +{
>   > +    pch_create_dirq(d->irq);
>   > +    line_unmask_pch_irq(d);
>   > +
>   > +    return 0;
>   > +}
>   > +
>   > +static void line_shutdown_pch_irq(struct irq_data *d)
>   > +{
>   > +    line_mask_pch_irq(d);
>   > +    pch_destroy_dirq(d->irq);
>   > +}
>   > +
>   > +static struct irq_chip ioaicu_line_chip = {
>   > +    .name            = "IOAICU-LINE",
>   > +    .irq_mask        = line_mask_pch_irq,
>   > +    .irq_unmask        = line_unmask_pch_irq,
>   > +    .irq_startup        = line_startup_pch_irq,
>   > +    .irq_shutdown        = line_shutdown_pch_irq,
>   > +    .irq_set_affinity    = plat_set_irq_affinity,
>   > +};
>   > +
>   > +static void line_route_init(void)
>   > +{
>   > +    unsigned int dummy;
>   > +
>   > +    /* route 3A CPU0 INT0 to node0 core0 INT1(IP3) */
>   > +    dummy = LOONGSON_INT_COREx_INTy(loongson_sysconf.boot_cpu_id, 1);
>   > +    ls64_conf_write8(dummy, LS_IRC_ENT(0));
>   > +
>   > +    dummy = ls64_conf_read32(LS_IRC_EN);
>   > +    dummy |= 0x1;
>   > +    ls64_conf_write32(dummy, LS_IRC_ENSET);
>   > +}
>   > +
>   > +static void init_pin_route(unsigned int ioaicu, int vec)
>   > +{
>   > +    ioaicu_write8(ioaicu, IOAICU_IRR + vec, USE_HTH_INT0);
>   > +}
>   > +
>   > +static void init_ioaicu_pin(unsigned int ioaicu, int pin,
>   > +                struct irq_chip *pirq_chip)
>   > +{
>   > +    init_pin_route(ioaicu, LS3A_IOIRQ2VECTOR(pin));
>   > +    irq_set_chip_and_handler(pin, pirq_chip, handle_level_irq);
>   > +}
>   > +
>   > +static void setup_ioaicu_irqs(struct irq_chip *pirq_chip)
>   > +{
>   > +    unsigned int ioaicu, pin;
>   > +    struct ioaicu_gsi *gsi_cfg;
>   > +
>   > +    for_each_ioaicu(ioaicu) {
>   > +        ioaicu_write64(ioaicu, IOAICU_IER, 0);
>   > +        ioaicu_write64(ioaicu, IOAICU_IPR, (1ULL << 59));
>   > +        ioaicu_write64(ioaicu, IOAICU_ISR, 0);
>   > +        ioaicu_write64(ioaicu, IOAICU_IMR, -1ULL);
>   > +        ioaicu_write64(ioaicu, IOAICU_ICR, -1ULL);
>   > +    }
>   > +
>   > +    for (ioaicu = 0; ioaicu < nr_ioaicus; ioaicu++) {
>   > +        for (pin = 0; pin < ioaicus[ioaicu].nr_registers; pin++) {
>   > +            gsi_cfg = ioaicu_gsi_routing(ioaicu);
>   > +            init_ioaicu_pin(ioaicu,
>   > +                    pin + gsi_cfg->gsi_base, pirq_chip);
>   > +        }
>   > +    }
>   > +}
>   > +
>   > +static void handle_irqs(unsigned long long irqs, int i)
>   > +{
>   > +    unsigned int irq;
>   > +    struct irq_data *irqd;
>   > +    struct cpumask affinity;
>   > +    int cpu = smp_processor_id();
>   > +
>   > +    while (irqs) {
>   > +        irq = __ffs(irqs);
>   > +        irqs &= ~(1ULL<<irq);
>   > +        irq += (i << 6);
>   > +
>   > +        /* handled by local core */
>   > +        if (ls3a_ipi_irq2pos[irq] == (unsigned char)-1) {
>   > +            do_IRQ(LS3A_VECTOR2IOIRQ(irq));
>   > +            continue;
>   > +        }
>   > +
>   > +        irqd = irq_get_irq_data(LS3A_VECTOR2IOIRQ(irq));
>   > +        cpumask_and(&affinity, irqd->common->affinity, cpu_active_mask);
>   > +        if (cpumask_empty(&affinity)) {
>   > +            do_IRQ(LS3A_VECTOR2IOIRQ(irq));
>   > +            continue;
>   > +        }
>   > +
>   > +        cpu_for_irq[irq] = cpumask_next(cpu_for_irq[irq], &affinity);
>   > +        if (cpu_for_irq[irq] >= nr_cpu_ids)
>   > +            cpu_for_irq[irq] = cpumask_first(&affinity);
>   > +
>   > +        if (cpu_for_irq[irq] == cpu) {
>   > +            do_IRQ(LS3A_VECTOR2IOIRQ(irq));
>   > +            continue;
>   > +        }
>   > +
>   > +        /* balanced by other cores */
>   > +        loongson3_send_irq_by_ipi(cpu_for_irq[irq],
>   > +                      (0x1 << (ls3a_ipi_irq2pos[irq])));
>   > +    }
>   > +}
>   > +
>   > +static void ioaicu_line_dispatch(void)
>   > +{
>   > +    unsigned long flags;
>   > +    unsigned int ioaicu;
>   > +    unsigned long long intstatus;
>   > +    unsigned long long intmask;
>   > +
>   > +    for_each_ioaicu(ioaicu) {
>   > +        /* read irq status register */
>   > +        intstatus = ioaicu_read64(ioaicu, IOAICU_ISR);
>   > +
>   > +        spin_lock_irqsave(&pch_irq_lock, flags);
>   > +        intmask = ioaicu_read64(ioaicu, IOAICU_IMR);
>   > +        intmask |= intstatus;
>   > +        ioaicu_write64(ioaicu, IOAICU_IMR, intmask);
>   > +        intmask = ioaicu_read64(ioaicu, IOAICU_IMR);
>   > +        spin_unlock_irqrestore(&pch_irq_lock, flags);
>   > +
>   > +        handle_irqs(intstatus, 0);
>   > +    }
>   > +}
>   > +
>   > +/* ioaicu: input/output advanced interrupt control unit */
>   > +void __init setup_ioaicu(void)
>   > +{
>   > +    register_ioaicu(0, LS7A_PCH_REG_BASE, LS7A_IOAICU_IRQ_BASE);
>   > +
>   > +    line_route_init();
>   > +    setup_ioaicu_irqs(&ioaicu_line_chip);
>   > +    loongson_pch.irq_dispatch = ioaicu_line_dispatch;
>   > +}
>   > diff --git a/arch/mips/loongson64/irq.c b/arch/mips/loongson64/irq.c
>   > index 79ad797..988f21f 100644
>   > --- a/arch/mips/loongson64/irq.c
>   > +++ b/arch/mips/loongson64/irq.c
>   > @@ -1,6 +1,7 @@
>   >  // SPDX-License-Identifier: GPL-2.0
>   >  #include <loongson.h>
>   >  #include <irq.h>
>   > +#include <ioaicu.h>
>   >  #include <linux/interrupt.h>
>   >  #include <linux/init.h>
>   >
>   > @@ -71,6 +72,8 @@ static void ht_irqdispatch(void)
>   >              continue;
>   >          }
>   >
>   > +        ls3a_ipi_pos2irq[ht_irq[i]] = ht_irq[i];
>   > +
>   >          /* balanced by other cores */
>   >          loongson3_send_irq_by_ipi(irq_cpu[ht_irq[i]], (0x1 << ht_irq[i]));
>   >      }
>   > @@ -91,7 +94,7 @@ asmlinkage void plat_irq_dispatch(void)
>   >          loongson3_ipi_interrupt(NULL);
>   >  #endif
>   >      if (pending & CAUSEF_IP3)
>   > -        ht_irqdispatch();
>   > +        loongson_pch.irq_dispatch();
>   >      if (pending & CAUSEF_IP2)
>   >          do_IRQ(LOONGSON_UART_IRQ);
>   >      if (pending & UNUSED_IPS) {
>   > @@ -137,11 +140,18 @@ void __init arch_init_irq(void)
>   >
>   >      clear_c0_status(ST0_IM | ST0_BEV);
>   >
>   > -    irq_router_init();
>   >      mips_cpu_irq_init();
>   > -    init_i8259_irqs();
>   > -    chip = irq_get_chip(I8259A_IRQ_BASE);
>   > -    chip->irq_set_affinity = plat_set_irq_affinity;
>   > +
>   > +    if (strstr(eboard->name, "780E")) {
>   > +        irq_router_init();
>   > +        init_i8259_irqs();
>   > +        loongson_pch.irq_dispatch = ht_irqdispatch;
>   > +        chip = irq_get_chip(I8259A_IRQ_BASE);
>   > +        chip->irq_set_affinity = plat_set_irq_affinity;
>   > +    }
>   > +
>   > +    if (strstr(eboard->name, "7A1000"))
>   > +        setup_ioaicu();
>
> Ahh... strstr again.
> Forgot to mention, you'd better make RS780E as fallback when no PCH type string
> found in eboard name. That's to Loongson's crappy specification, 2/5 of my Loongson
> boards didn't set their board name correctly.
>
>   >
>   >      irq_set_chip_and_handler(LOONGSON_UART_IRQ,
>   >              &loongson_irq_chip, handle_percpu_irq);
>   > diff --git a/arch/mips/loongson64/smp.c b/arch/mips/loongson64/smp.c
>   > index de8e074..0720df7 100644
>   > --- a/arch/mips/loongson64/smp.c
>   > +++ b/arch/mips/loongson64/smp.c
>   > @@ -20,6 +20,7 @@
>   >  #include <loongson.h>
>   >  #include <loongson_regs.h>
>   >  #include <workarounds.h>
>   > +#include <ioaicu.h>
>   >
>   >  #include "smp.h"
>   >
>   > @@ -336,10 +337,14 @@ void loongson3_ipi_interrupt(struct pt_regs *regs)
>   >      }
>   >
>   >      if (irqs) {
>   > -        int irq;
>   > +        int irq, irq1;
>   > +
>   >          while ((irq = ffs(irqs))) {
>   > -            do_IRQ(irq-1);
>   > -            irqs &= ~(1<<(irq-1));
>   > +            irq1 = ls3a_ipi_pos2irq[irq - 1];
>   > +            if (likely(irq1 != (unsigned short)-1)) {
>   > +                do_IRQ(irq1);
>   > +                irqs &= ~(1 << (irq - 1));
>   > +            }
>   >          }
>   >      }
>   >  }
>   > --
>   > 2.1.0
>   >
>   >
diff mbox series

Patch

diff --git a/arch/mips/include/asm/mach-loongson64/ioaicu.h b/arch/mips/include/asm/mach-loongson64/ioaicu.h
new file mode 100644
index 0000000..e55de0d
--- /dev/null
+++ b/arch/mips/include/asm/mach-loongson64/ioaicu.h
@@ -0,0 +1,166 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 Loongson Technology Corporation Limited
+ *
+ * Author: Jianmin Lv <lvjianmin@loongson.cn>
+ * Author: Tiezhu Yang <yangtiezhu@loongson.cn>
+ */
+
+#ifndef _ASM_LOONGSON_IOAICU_H
+#define _ASM_LOONGSON_IOAICU_H
+
+#define LS_CFG_BASE	0x3ff00000
+#define LS_CFG_OFF(x)	((void *)TO_UNCAC(LS_CFG_BASE) + (x))
+
+#define LS_IRC_BASE	LS_CFG_OFF(0x1400)
+#define LS_IRC_OFF(x)	(LS_IRC_BASE + x)
+
+#define LS_IRC_ENT(x)	LS_IRC_OFF(x)
+#define LS_IRC_EN	LS_IRC_OFF(0x24)
+#define LS_IRC_ENSET	LS_IRC_OFF(0x28)
+
+#define MAX_IOAICUS		16
+#define LS3A_MAX_IPI_IRQ	26
+#define LS3A_MAX_IO_VECTOR	64
+#define LS3A_MAX_IO_IRQ		256
+
+#define LS7A_PCH_REG_BASE	0x10000000
+#define LS7A_IOAICU_IRQ_BASE	64
+
+#define IOAICU_BASE	0x90000e0000000000ULL
+#define IOAICU_IMR	0x20
+#define IOAICU_IER	0x60
+#define IOAICU_ICR	0x80
+#define IOAICU_IRR	0x100
+#define IOAICU_ISR	0x3a0
+#define IOAICU_IPR	0x3e0
+
+#define USE_HTH_INT0	0x1
+
+#define LS3A_IOIRQ2VECTOR(irq) (irq - LS3A_MAX_IO_VECTOR)
+#define LS3A_VECTOR2IOIRQ(vector) (vector + LS3A_MAX_IO_VECTOR)
+
+struct ioaicu_config {
+	unsigned char aicuid;
+	unsigned char aicuver;
+	unsigned int aicuaddr;
+};
+
+struct ioaicu_gsi {
+	u32 gsi_base;
+	u32 gsi_end;
+};
+
+struct ioaicu {
+	int nr_registers;
+	struct ioaicu_config config;
+	struct ioaicu_gsi gsi_config;
+};
+
+struct loongson_irq_dispatch_ops {
+	void (*irq_dispatch)(void);
+};
+
+extern void setup_ioaicu(void);
+extern struct ioaicu ioaicus[MAX_IOAICUS];
+extern struct loongson_irq_dispatch_ops loongson_pch;
+extern unsigned short ls3a_ipi_pos2irq[LS3A_MAX_IO_VECTOR];
+
+extern void loongson3_send_irq_by_ipi(int cpu, int irqs);
+extern int plat_set_irq_affinity(struct irq_data *d,
+				 const struct cpumask *affinity, bool force);
+
+static inline unsigned long ioaicu_read64(unsigned int aicu, unsigned int reg)
+{
+	unsigned long addr = ioaicus[aicu].config.aicuaddr;
+
+	return *(unsigned long *)(IOAICU_BASE + addr + reg);
+}
+
+static inline void ioaicu_write64(unsigned int aicu,
+				  unsigned int reg, unsigned long value)
+{
+	unsigned long addr = ioaicus[aicu].config.aicuaddr;
+	*(unsigned long *)(IOAICU_BASE + addr + reg) = value;
+}
+
+static inline unsigned char ioaicu_read8(unsigned int aicu, unsigned int reg)
+{
+	unsigned long addr = ioaicus[aicu].config.aicuaddr;
+
+	return *(unsigned char *)(IOAICU_BASE + addr + reg);
+}
+
+static inline void ioaicu_write8(unsigned int aicu,
+				 unsigned int reg, unsigned char value)
+{
+	unsigned long addr = ioaicus[aicu].config.aicuaddr;
+	*(unsigned char *)(IOAICU_BASE + addr + reg) = value;
+}
+
+static inline int ioaicu_get_redir_entries(int ioaicu)
+{
+	return 64;
+}
+
+static inline struct ioaicu_gsi *ioaicu_gsi_routing(int ioaicu_idx)
+{
+	return &ioaicus[ioaicu_idx].gsi_config;
+}
+
+static inline int ioaicu_id(int ioaicu_idx)
+{
+	return ioaicus[ioaicu_idx].config.aicuid;
+}
+
+static inline unsigned int ioaicu_addr(int ioaicu_idx)
+{
+	return ioaicus[ioaicu_idx].config.aicuaddr;
+}
+
+#define ioaicu_ver(ioaicu_idx) ioaicus[ioaicu_idx].config.aicuver
+
+static inline void ls64_conf_write64(u64 val64, void __iomem *addr)
+{
+	asm volatile (
+	"	.set push\n"
+	"	.set noreorder\n"
+	"	sd	%[v], (%[hw])\n"
+	"	lb	$0, (%[hw])\n"
+	"	.set pop\n"
+	:
+	: [hw] "r" (addr),  [v] "r" (val64)
+	);
+}
+
+static inline void ls64_conf_write32(u32 val, void __iomem *addr)
+{
+	asm volatile (
+	"	.set push\n"
+	"	.set noreorder\n"
+	"	sw	%[v], (%[hw])\n"
+	"	lb	$0, (%[hw])\n"
+	"	.set pop\n"
+	:
+	: [hw] "r" (addr), [v] "r" (val)
+	);
+}
+
+static inline void ls64_conf_write8(u8 val, void __iomem *addr)
+{
+	asm volatile (
+	"	.set push\n"
+	"	.set noreorder\n"
+	"	sb	%[v], (%[hw])\n"
+	"	lb	$0, (%[hw])\n"
+	"	.set pop\n"
+	:
+	: [hw] "r" (addr), [v] "r" (val)
+	);
+}
+
+#define ls64_conf_read64(addr) readq(addr)
+#define ls64_conf_read32(addr) readl(addr)
+#define ls64_conf_read8(addr) readb(addr)
+
+#endif /* _ASM_LOONGSON_IOAICU_H */
diff --git a/arch/mips/include/asm/mach-loongson64/irq.h b/arch/mips/include/asm/mach-loongson64/irq.h
index 73a8991..10568be 100644
--- a/arch/mips/include/asm/mach-loongson64/irq.h
+++ b/arch/mips/include/asm/mach-loongson64/irq.h
@@ -4,6 +4,8 @@ 
 
 #include <boot_param.h>
 
+#define NR_IRQS (64 + 256)
+
 /* cpu core interrupt numbers */
 #define MIPS_CPU_IRQ_BASE 56
 
diff --git a/arch/mips/loongson64/Makefile b/arch/mips/loongson64/Makefile
index 7821891..3db50c6 100644
--- a/arch/mips/loongson64/Makefile
+++ b/arch/mips/loongson64/Makefile
@@ -3,7 +3,7 @@ 
 # Makefile for Loongson-3 family machines
 #
 obj-$(CONFIG_MACH_LOONGSON64) += irq.o cop2-ex.o platform.o acpi_init.o dma.o \
-				setup.o init.o env.o time.o reset.o \
+				setup.o init.o env.o time.o reset.o ioaicu.o \
 
 obj-$(CONFIG_SMP)	+= smp.o
 obj-$(CONFIG_NUMA)	+= numa.o
diff --git a/arch/mips/loongson64/ioaicu.c b/arch/mips/loongson64/ioaicu.c
new file mode 100644
index 0000000..904ac75
--- /dev/null
+++ b/arch/mips/loongson64/ioaicu.c
@@ -0,0 +1,305 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Loongson Technology Corporation Limited
+ *
+ * Author: Jianmin Lv <lvjianmin@loongson.cn>
+ * Author: Tiezhu Yang <yangtiezhu@loongson.cn>
+ */
+
+#include <loongson.h>
+#include <ioaicu.h>
+
+static DEFINE_SPINLOCK(pch_irq_lock);
+DECLARE_BITMAP(ls3a_ipi_in_use, LS3A_MAX_IPI_IRQ);
+struct loongson_irq_dispatch_ops loongson_pch;
+struct ioaicu ioaicus[MAX_IOAICUS];
+
+unsigned short ls3a_ipi_pos2irq[LS3A_MAX_IO_VECTOR] = {
+					[0 ... LS3A_MAX_IO_VECTOR-1] = -1 };
+unsigned char ls3a_ipi_irq2pos[LS3A_MAX_IO_IRQ] = {
+					[0 ... LS3A_MAX_IO_IRQ-1] = -1 };
+unsigned int cpu_for_irq[LS3A_MAX_IO_IRQ] = {
+					[0 ... LS3A_MAX_IO_IRQ-1] = -1};
+
+u32 gsi_top;
+int nr_ioaicus;
+#define for_each_ioaicu(idx)	\
+	for ((idx) = 0; (idx) < nr_ioaicus; (idx)++)
+#define for_each_pin(idx, pin)	\
+	for ((pin) = 0; (pin) < ioaicus[(idx)].nr_registers; (pin)++)
+
+static int bad_ioaicu(unsigned long address)
+{
+	if (nr_ioaicus >= MAX_IOAICUS) {
+		pr_warn("WARNING: Max # of I/O AICUs (%d) exceeded (found %d), skipping\n",
+			MAX_IOAICUS, nr_ioaicus);
+		return 1;
+	}
+
+	if (!address) {
+		pr_warn("WARNING: Bogus (zero) I/O AICU address found in table, skipping!\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+static void register_ioaicu(int id, u32 address, u32 gsi_base)
+{
+	int idx = 0;
+	int entries;
+	struct ioaicu_gsi *gsi_cfg;
+
+	if (bad_ioaicu(address))
+		return;
+
+	idx = nr_ioaicus;
+	ioaicus[idx].config.aicuaddr = address;
+	ioaicus[idx].config.aicuid = id;
+	ioaicus[idx].config.aicuver = 0;
+
+	entries = ioaicu_get_redir_entries(idx);
+	gsi_cfg = ioaicu_gsi_routing(idx);
+	gsi_cfg->gsi_base = gsi_base;
+	gsi_cfg->gsi_end = gsi_base + entries - 1;
+
+	ioaicus[idx].nr_registers = entries;
+
+	if (gsi_cfg->gsi_end >= gsi_top)
+		gsi_top = gsi_cfg->gsi_end + 1;
+
+	pr_info("IOAICU[%d]: aicu_id %d, version %d, address 0x%x, GSI %d-%d\n",
+		idx, ioaicu_id(idx), ioaicu_ver(idx), ioaicu_addr(idx),
+		gsi_cfg->gsi_base, gsi_cfg->gsi_end);
+
+	nr_ioaicus++;
+}
+
+static int find_ioaicu(u32 gsi)
+{
+	int i;
+
+	if (nr_ioaicus == 0)
+		return -1;
+
+	for_each_ioaicu(i) {
+		struct ioaicu_gsi *gsi_cfg = ioaicu_gsi_routing(i);
+
+		if (gsi >= gsi_cfg->gsi_base && gsi <= gsi_cfg->gsi_end)
+			return i;
+	}
+
+	pr_err("ERROR: Unable to locate IOAICU for GSI %d\n", gsi);
+
+	return -1;
+}
+
+static int pch_create_dirq(unsigned int irq)
+{
+	unsigned long flags;
+	int pos;
+
+	spin_lock_irqsave(&pch_irq_lock, flags);
+again:
+	pos = find_first_zero_bit(ls3a_ipi_in_use, LS3A_MAX_IPI_IRQ);
+	if (pos == LS3A_MAX_IPI_IRQ) {
+		spin_unlock_irqrestore(&pch_irq_lock, flags);
+		return -ENOSPC;
+	}
+
+	if (test_and_set_bit(pos, ls3a_ipi_in_use))
+		goto again;
+
+	ls3a_ipi_pos2irq[pos] = irq;
+	ls3a_ipi_irq2pos[LS3A_IOIRQ2VECTOR(irq)] = pos;
+	spin_unlock_irqrestore(&pch_irq_lock, flags);
+
+	return 0;
+}
+
+static void pch_destroy_dirq(unsigned int irq)
+{
+	unsigned long flags;
+	int pos;
+
+	spin_lock_irqsave(&pch_irq_lock, flags);
+	pos = ls3a_ipi_irq2pos[LS3A_IOIRQ2VECTOR(irq)];
+	if (pos >= 0) {
+		clear_bit(pos, ls3a_ipi_in_use);
+		ls3a_ipi_irq2pos[LS3A_IOIRQ2VECTOR(irq)] = -1;
+		ls3a_ipi_pos2irq[pos] = -1;
+	}
+	spin_unlock_irqrestore(&pch_irq_lock, flags);
+}
+
+static void line_mask_pch_irq(struct irq_data *d)
+{
+	unsigned long flags, data;
+	unsigned int ioaicu;
+	unsigned long irq_nr = d->irq;
+
+	ioaicu = (unsigned int)find_ioaicu((u32)irq_nr);
+
+	spin_lock_irqsave(&pch_irq_lock, flags);
+	data = ioaicu_read64(ioaicu, IOAICU_IMR);
+	data |= (1ULL << LS3A_IOIRQ2VECTOR(irq_nr));
+	ioaicu_write64(ioaicu, IOAICU_IMR, data);
+	spin_unlock_irqrestore(&pch_irq_lock, flags);
+}
+
+static void line_unmask_pch_irq(struct irq_data *d)
+{
+	unsigned long flags, data;
+	unsigned int ioaicu;
+	unsigned long irq_nr = d->irq;
+
+	ioaicu = (unsigned int)find_ioaicu((u32)irq_nr);
+
+	spin_lock_irqsave(&pch_irq_lock, flags);
+	data = ioaicu_read64(ioaicu, IOAICU_IMR);
+	data &= ~(1ULL << LS3A_IOIRQ2VECTOR(irq_nr));
+	ioaicu_write64(ioaicu, IOAICU_IMR, data);
+	spin_unlock_irqrestore(&pch_irq_lock, flags);
+}
+
+static unsigned int line_startup_pch_irq(struct irq_data *d)
+{
+	pch_create_dirq(d->irq);
+	line_unmask_pch_irq(d);
+
+	return 0;
+}
+
+static void line_shutdown_pch_irq(struct irq_data *d)
+{
+	line_mask_pch_irq(d);
+	pch_destroy_dirq(d->irq);
+}
+
+static struct irq_chip ioaicu_line_chip = {
+	.name			= "IOAICU-LINE",
+	.irq_mask		= line_mask_pch_irq,
+	.irq_unmask		= line_unmask_pch_irq,
+	.irq_startup		= line_startup_pch_irq,
+	.irq_shutdown		= line_shutdown_pch_irq,
+	.irq_set_affinity	= plat_set_irq_affinity,
+};
+
+static void line_route_init(void)
+{
+	unsigned int dummy;
+
+	/* route 3A CPU0 INT0 to node0 core0 INT1(IP3) */
+	dummy = LOONGSON_INT_COREx_INTy(loongson_sysconf.boot_cpu_id, 1);
+	ls64_conf_write8(dummy, LS_IRC_ENT(0));
+
+	dummy = ls64_conf_read32(LS_IRC_EN);
+	dummy |= 0x1;
+	ls64_conf_write32(dummy, LS_IRC_ENSET);
+}
+
+static void init_pin_route(unsigned int ioaicu, int vec)
+{
+	ioaicu_write8(ioaicu, IOAICU_IRR + vec, USE_HTH_INT0);
+}
+
+static void init_ioaicu_pin(unsigned int ioaicu, int pin,
+			    struct irq_chip *pirq_chip)
+{
+	init_pin_route(ioaicu, LS3A_IOIRQ2VECTOR(pin));
+	irq_set_chip_and_handler(pin, pirq_chip, handle_level_irq);
+}
+
+static void setup_ioaicu_irqs(struct irq_chip *pirq_chip)
+{
+	unsigned int ioaicu, pin;
+	struct ioaicu_gsi *gsi_cfg;
+
+	for_each_ioaicu(ioaicu) {
+		ioaicu_write64(ioaicu, IOAICU_IER, 0);
+		ioaicu_write64(ioaicu, IOAICU_IPR, (1ULL << 59));
+		ioaicu_write64(ioaicu, IOAICU_ISR, 0);
+		ioaicu_write64(ioaicu, IOAICU_IMR, -1ULL);
+		ioaicu_write64(ioaicu, IOAICU_ICR, -1ULL);
+	}
+
+	for (ioaicu = 0; ioaicu < nr_ioaicus; ioaicu++) {
+		for (pin = 0; pin < ioaicus[ioaicu].nr_registers; pin++) {
+			gsi_cfg = ioaicu_gsi_routing(ioaicu);
+			init_ioaicu_pin(ioaicu,
+					pin + gsi_cfg->gsi_base, pirq_chip);
+		}
+	}
+}
+
+static void handle_irqs(unsigned long long irqs, int i)
+{
+	unsigned int irq;
+	struct irq_data *irqd;
+	struct cpumask affinity;
+	int cpu = smp_processor_id();
+
+	while (irqs) {
+		irq = __ffs(irqs);
+		irqs &= ~(1ULL<<irq);
+		irq += (i << 6);
+
+		/* handled by local core */
+		if (ls3a_ipi_irq2pos[irq] == (unsigned char)-1) {
+			do_IRQ(LS3A_VECTOR2IOIRQ(irq));
+			continue;
+		}
+
+		irqd = irq_get_irq_data(LS3A_VECTOR2IOIRQ(irq));
+		cpumask_and(&affinity, irqd->common->affinity, cpu_active_mask);
+		if (cpumask_empty(&affinity)) {
+			do_IRQ(LS3A_VECTOR2IOIRQ(irq));
+			continue;
+		}
+
+		cpu_for_irq[irq] = cpumask_next(cpu_for_irq[irq], &affinity);
+		if (cpu_for_irq[irq] >= nr_cpu_ids)
+			cpu_for_irq[irq] = cpumask_first(&affinity);
+
+		if (cpu_for_irq[irq] == cpu) {
+			do_IRQ(LS3A_VECTOR2IOIRQ(irq));
+			continue;
+		}
+
+		/* balanced by other cores */
+		loongson3_send_irq_by_ipi(cpu_for_irq[irq],
+					  (0x1 << (ls3a_ipi_irq2pos[irq])));
+	}
+}
+
+static void ioaicu_line_dispatch(void)
+{
+	unsigned long flags;
+	unsigned int ioaicu;
+	unsigned long long intstatus;
+	unsigned long long intmask;
+
+	for_each_ioaicu(ioaicu) {
+		/* read irq status register */
+		intstatus = ioaicu_read64(ioaicu, IOAICU_ISR);
+
+		spin_lock_irqsave(&pch_irq_lock, flags);
+		intmask = ioaicu_read64(ioaicu, IOAICU_IMR);
+		intmask |= intstatus;
+		ioaicu_write64(ioaicu, IOAICU_IMR, intmask);
+		intmask = ioaicu_read64(ioaicu, IOAICU_IMR);
+		spin_unlock_irqrestore(&pch_irq_lock, flags);
+
+		handle_irqs(intstatus, 0);
+	}
+}
+
+/* ioaicu: input/output advanced interrupt control unit */
+void __init setup_ioaicu(void)
+{
+	register_ioaicu(0, LS7A_PCH_REG_BASE, LS7A_IOAICU_IRQ_BASE);
+
+	line_route_init();
+	setup_ioaicu_irqs(&ioaicu_line_chip);
+	loongson_pch.irq_dispatch = ioaicu_line_dispatch;
+}
diff --git a/arch/mips/loongson64/irq.c b/arch/mips/loongson64/irq.c
index 79ad797..988f21f 100644
--- a/arch/mips/loongson64/irq.c
+++ b/arch/mips/loongson64/irq.c
@@ -1,6 +1,7 @@ 
 // SPDX-License-Identifier: GPL-2.0
 #include <loongson.h>
 #include <irq.h>
+#include <ioaicu.h>
 #include <linux/interrupt.h>
 #include <linux/init.h>
 
@@ -71,6 +72,8 @@  static void ht_irqdispatch(void)
 			continue;
 		}
 
+		ls3a_ipi_pos2irq[ht_irq[i]] = ht_irq[i];
+
 		/* balanced by other cores */
 		loongson3_send_irq_by_ipi(irq_cpu[ht_irq[i]], (0x1 << ht_irq[i]));
 	}
@@ -91,7 +94,7 @@  asmlinkage void plat_irq_dispatch(void)
 		loongson3_ipi_interrupt(NULL);
 #endif
 	if (pending & CAUSEF_IP3)
-		ht_irqdispatch();
+		loongson_pch.irq_dispatch();
 	if (pending & CAUSEF_IP2)
 		do_IRQ(LOONGSON_UART_IRQ);
 	if (pending & UNUSED_IPS) {
@@ -137,11 +140,18 @@  void __init arch_init_irq(void)
 
 	clear_c0_status(ST0_IM | ST0_BEV);
 
-	irq_router_init();
 	mips_cpu_irq_init();
-	init_i8259_irqs();
-	chip = irq_get_chip(I8259A_IRQ_BASE);
-	chip->irq_set_affinity = plat_set_irq_affinity;
+
+	if (strstr(eboard->name, "780E")) {
+		irq_router_init();
+		init_i8259_irqs();
+		loongson_pch.irq_dispatch = ht_irqdispatch;
+		chip = irq_get_chip(I8259A_IRQ_BASE);
+		chip->irq_set_affinity = plat_set_irq_affinity;
+	}
+
+	if (strstr(eboard->name, "7A1000"))
+		setup_ioaicu();
 
 	irq_set_chip_and_handler(LOONGSON_UART_IRQ,
 			&loongson_irq_chip, handle_percpu_irq);
diff --git a/arch/mips/loongson64/smp.c b/arch/mips/loongson64/smp.c
index de8e074..0720df7 100644
--- a/arch/mips/loongson64/smp.c
+++ b/arch/mips/loongson64/smp.c
@@ -20,6 +20,7 @@ 
 #include <loongson.h>
 #include <loongson_regs.h>
 #include <workarounds.h>
+#include <ioaicu.h>
 
 #include "smp.h"
 
@@ -336,10 +337,14 @@  void loongson3_ipi_interrupt(struct pt_regs *regs)
 	}
 
 	if (irqs) {
-		int irq;
+		int irq, irq1;
+
 		while ((irq = ffs(irqs))) {
-			do_IRQ(irq-1);
-			irqs &= ~(1<<(irq-1));
+			irq1 = ls3a_ipi_pos2irq[irq - 1];
+			if (likely(irq1 != (unsigned short)-1)) {
+				do_IRQ(irq1);
+				irqs &= ~(1 << (irq - 1));
+			}
 		}
 	}
 }