The WPCM450 AIC ("Advanced Interrupt Controller") is the interrupt
controller found in the Nuvoton WPCM450 SoC and other Winbond/Nuvoton
SoCs.

The list of registers if based on the AMI vendor kernel and the
Nuvoton W90N745 datasheet.

Although the hardware supports other interrupt modes, the driver only
supports high-level interrupts at the moment, because other modes could
not be tested so far.

Signed-off-by: Jonathan Neuschäfer <j.neuschae...@gmx.net>
---

v2:
- Rename IRQS macro to AIC_NUM_IRQS
- Fix IRQ range check
- Use linux/printk.h header instead of linux/console.h
- Add AIC_SCR_PRIORITY_MASK constant
- Add missing register descriptions
- Remove superfluous printk about IRQ flow type mismatch
- Use BIT() macro
- Rename _ack function to _eoi for accuracy, and use handle_fasteoi_irq
---
 arch/arm/mach-npcm/Kconfig        |   1 +
 drivers/irqchip/Kconfig           |   6 ++
 drivers/irqchip/Makefile          |   1 +
 drivers/irqchip/irq-wpcm450-aic.c | 161 ++++++++++++++++++++++++++++++
 4 files changed, 169 insertions(+)
 create mode 100644 drivers/irqchip/irq-wpcm450-aic.c

diff --git a/arch/arm/mach-npcm/Kconfig b/arch/arm/mach-npcm/Kconfig
index 658c8efb4ca14..a71cf1d189ae5 100644
--- a/arch/arm/mach-npcm/Kconfig
+++ b/arch/arm/mach-npcm/Kconfig
@@ -10,6 +10,7 @@ config ARCH_WPCM450
        bool "Support for WPCM450 BMC (Hermon)"
        depends on ARCH_MULTI_V5
        select CPU_ARM926T
+       select WPCM450_AIC
        select NPCM7XX_TIMER
        help
          General support for WPCM450 BMC (Hermon).
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index e74fa206240a1..baf4efec31c67 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -586,4 +586,10 @@ config MST_IRQ
        help
          Support MStar Interrupt Controller.

+config WPCM450_AIC
+       bool "Nuvoton WPCM450 Advanced Interrupt Controller"
+       depends on ARCH_WPCM450 || COMPILE_TEST
+       help
+         Support for the interrupt controller in the Nuvoton WPCM450 BMC SoC.
+
 endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index c59b95a0532c9..bef57937e7296 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -113,3 +113,4 @@ obj-$(CONFIG_LOONGSON_PCH_MSI)              += 
irq-loongson-pch-msi.o
 obj-$(CONFIG_MST_IRQ)                  += irq-mst-intc.o
 obj-$(CONFIG_SL28CPLD_INTC)            += irq-sl28cpld.o
 obj-$(CONFIG_MACH_REALTEK_RTL)         += irq-realtek-rtl.o
+obj-$(CONFIG_WPCM450_AIC)              += irq-wpcm450-aic.o
diff --git a/drivers/irqchip/irq-wpcm450-aic.c 
b/drivers/irqchip/irq-wpcm450-aic.c
new file mode 100644
index 0000000000000..f3ac392d5bc87
--- /dev/null
+++ b/drivers/irqchip/irq-wpcm450-aic.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright 2021 Jonathan Neuschäfer
+
+#include <linux/irqchip.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/printk.h>
+
+#include <asm/exception.h>
+
+#define AIC_SCR(x)     ((x)*4) /* Source control registers */
+#define AIC_GEN                0x84    /* Interrupt group enable control 
register */
+#define AIC_GRSR       0x88    /* Interrupt group raw status register */
+#define AIC_IRSR       0x100   /* Interrupt raw status register */
+#define AIC_IASR       0x104   /* Interrupt active status register */
+#define AIC_ISR                0x108   /* Interrupt status register */
+#define AIC_IPER       0x10c   /* Interrupt priority encoding register */
+#define AIC_ISNR       0x110   /* Interrupt source number register */
+#define AIC_IMR                0x114   /* Interrupt mask register */
+#define AIC_OISR       0x118   /* Output interrupt status register */
+#define AIC_MECR       0x120   /* Mask enable command register */
+#define AIC_MDCR       0x124   /* Mask disable command register */
+#define AIC_SSCR       0x128   /* Source set command register */
+#define AIC_SCCR       0x12c   /* Source clear command register */
+#define AIC_EOSCR      0x130   /* End of service command register */
+
+#define AIC_SCR_SRCTYPE_LOW_LEVEL      (0 << 6)
+#define AIC_SCR_SRCTYPE_HIGH_LEVEL     (1 << 6)
+#define AIC_SCR_SRCTYPE_NEG_EDGE       (2 << 6)
+#define AIC_SCR_SRCTYPE_POS_EDGE       (3 << 6)
+#define AIC_SCR_PRIORITY(x)            (x)
+#define AIC_SCR_PRIORITY_MASK          0x7
+
+#define AIC_NUM_IRQS           32
+
+struct wpcm450_aic {
+       void __iomem *regs;
+       struct irq_domain *domain;
+};
+
+static struct wpcm450_aic *aic;
+
+static void wpcm450_aic_init_hw(void)
+{
+       int i;
+
+       /* Disable (mask) all interrupts */
+       writel(0xffffffff, aic->regs + AIC_MDCR);
+
+       /*
+        * Make sure the interrupt controller is ready to serve new interrupts.
+        * Reading from IPER indicates that the nIRQ signal may be deasserted,
+        * and writing to EOSCR indicates that interrupt handling has finished.
+        */
+       readl(aic->regs + AIC_IPER);
+       writel(0, aic->regs + AIC_EOSCR);
+
+       /* Initialize trigger mode and priority of each interrupt source */
+       for (i = 0; i < AIC_NUM_IRQS; i++)
+               writel(AIC_SCR_SRCTYPE_HIGH_LEVEL | AIC_SCR_PRIORITY(7),
+                      aic->regs + AIC_SCR(i));
+}
+
+static void __exception_irq_entry wpcm450_aic_handle_irq(struct pt_regs *regs)
+{
+       int hwirq;
+
+       /* Determine the interrupt source */
+       /* Read IPER to signal that nIRQ can be de-asserted */
+       hwirq = readl(aic->regs + AIC_IPER) / 4;
+
+       handle_domain_irq(aic->domain, hwirq, regs);
+}
+
+static void wpcm450_aic_eoi(struct irq_data *d)
+{
+       /* Signal end-of-service */
+       writel(0, aic->regs + AIC_EOSCR);
+}
+
+static void wpcm450_aic_mask(struct irq_data *d)
+{
+       unsigned int mask = BIT(d->hwirq);
+
+       /* Disable (mask) the interrupt */
+       writel(mask, aic->regs + AIC_MDCR);
+}
+
+static void wpcm450_aic_unmask(struct irq_data *d)
+{
+       unsigned int mask = BIT(d->hwirq);
+
+       /* Enable (unmask) the interrupt */
+       writel(mask, aic->regs + AIC_MECR);
+}
+
+static int wpcm450_aic_set_type(struct irq_data *d, unsigned int flow_type)
+{
+       /*
+        * The hardware supports high/low level, as well as rising/falling edge
+        * modes, and the DT binding accommodates for that, but as long as
+        * other modes than high level mode are not used and can't be tested,
+        * they are rejected in this driver.
+        */
+       if ((flow_type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_HIGH)
+               return -EINVAL;
+
+       return 0;
+}
+
+static struct irq_chip wpcm450_aic_chip = {
+       .name = "wpcm450-aic",
+       .irq_eoi = wpcm450_aic_eoi,
+       .irq_mask = wpcm450_aic_mask,
+       .irq_unmask = wpcm450_aic_unmask,
+       .irq_set_type = wpcm450_aic_set_type,
+};
+
+static int wpcm450_aic_map(struct irq_domain *d, unsigned int irq, 
irq_hw_number_t hwirq)
+{
+       if (hwirq >= AIC_NUM_IRQS)
+               return -EPERM;
+
+       irq_set_chip_and_handler(irq, &wpcm450_aic_chip, handle_fasteoi_irq);
+       irq_set_chip_data(irq, aic);
+       irq_set_probe(irq);
+
+       return 0;
+}
+
+static const struct irq_domain_ops wpcm450_aic_ops = {
+       .map = wpcm450_aic_map,
+       .xlate = irq_domain_xlate_twocell,
+};
+
+static int __init wpcm450_aic_of_init(struct device_node *node,
+                                     struct device_node *parent)
+{
+       if (parent)
+               return -EINVAL;
+
+       aic = kzalloc(sizeof(*aic), GFP_KERNEL);
+       if (!aic)
+               return -ENOMEM;
+
+       aic->regs = of_iomap(node, 0);
+       if (!aic->regs) {
+               pr_err("Failed to map WPCM450 AIC registers\n");
+               return -ENOMEM;
+       }
+
+       wpcm450_aic_init_hw();
+
+       set_handle_irq(wpcm450_aic_handle_irq);
+
+       aic->domain = irq_domain_add_linear(node, AIC_NUM_IRQS, 
&wpcm450_aic_ops, aic);
+
+       return 0;
+}
+
+IRQCHIP_DECLARE(wpcm450_aic, "nuvoton,wpcm450-aic", wpcm450_aic_of_init);
--
2.30.2

Reply via email to