lkml.org 
[lkml]   [2021]   [Mar]   [20]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 08/14] irqchip: Add driver for WPCM450 interrupt controller
    Date
    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.neuschaefer@gmx.net>
    ---
    arch/arm/mach-npcm/Kconfig | 1 +
    drivers/irqchip/Kconfig | 6 ++
    drivers/irqchip/Makefile | 1 +
    drivers/irqchip/irq-wpcm450-aic.c | 162 ++++++++++++++++++++++++++++++
    4 files changed, 170 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..0d6dd8b1fc824
    --- /dev/null
    +++ b/drivers/irqchip/irq-wpcm450-aic.c
    @@ -0,0 +1,162 @@
    +// SPDX-License-Identifier: GPL-2.0-only
    +// Copyright 2021 Jonathan Neuschäfer
    +
    +#include <linux/console.h>
    +#include <linux/irqchip.h>
    +#include <linux/of_address.h>
    +#include <linux/of_irq.h>
    +
    +#include <asm/exception.h>
    +
    +#define AIC_SCR(x) ((x)*4) /* Source control registers */
    +#define AIC_GEN 0x84
    +#define AIC_GRSR 0x88
    +#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 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 < 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_ack(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 = 1U << d->hwirq;
    +
    + /* Disable (mask) the interrupt */
    + writel(mask, aic->regs + AIC_MDCR);
    +}
    +
    +static void wpcm450_aic_unmask(struct irq_data *d)
    +{
    + unsigned int mask = 1U << 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) {
    + pr_err("IRQ mode %#x is not supported\n", flow_type);
    + return -EINVAL;
    + }
    +
    + return 0;
    +}
    +
    +static struct irq_chip wpcm450_aic_chip = {
    + .name = "wpcm450-aic",
    + .irq_ack = wpcm450_aic_ack,
    + .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 > IRQS)
    + return -EPERM;
    +
    + irq_set_chip_and_handler(irq, &wpcm450_aic_chip, handle_level_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, IRQS, &wpcm450_aic_ops, aic);
    +
    + return 0;
    +}
    +
    +IRQCHIP_DECLARE(wpcm450_aic, "nuvoton,wpcm450-aic", wpcm450_aic_of_init);
    --
    2.30.2
    \
     
     \ /
      Last update: 2021-03-20 19:23    [W:2.346 / U:0.008 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site