linux/drivers/irqchip/irq-wpcm450-aic.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2// Copyright 2021 Jonathan Neusch\xC3\xA4fer
   3
   4#include <linux/irqchip.h>
   5#include <linux/of_address.h>
   6#include <linux/of_irq.h>
   7#include <linux/printk.h>
   8
   9#include <asm/exception.h>
  10
  11#define AIC_SCR(x)      ((x)*4) /* Source control registers */
  12#define AIC_GEN         0x84    /* Interrupt group enable control register */
  13#define AIC_GRSR        0x88    /* Interrupt group raw status register */
  14#define AIC_IRSR        0x100   /* Interrupt raw status register */
  15#define AIC_IASR        0x104   /* Interrupt active status register */
  16#define AIC_ISR         0x108   /* Interrupt status register */
  17#define AIC_IPER        0x10c   /* Interrupt priority encoding register */
  18#define AIC_ISNR        0x110   /* Interrupt source number register */
  19#define AIC_IMR         0x114   /* Interrupt mask register */
  20#define AIC_OISR        0x118   /* Output interrupt status register */
  21#define AIC_MECR        0x120   /* Mask enable command register */
  22#define AIC_MDCR        0x124   /* Mask disable command register */
  23#define AIC_SSCR        0x128   /* Source set command register */
  24#define AIC_SCCR        0x12c   /* Source clear command register */
  25#define AIC_EOSCR       0x130   /* End of service command register */
  26
  27#define AIC_SCR_SRCTYPE_LOW_LEVEL       (0 << 6)
  28#define AIC_SCR_SRCTYPE_HIGH_LEVEL      (1 << 6)
  29#define AIC_SCR_SRCTYPE_NEG_EDGE        (2 << 6)
  30#define AIC_SCR_SRCTYPE_POS_EDGE        (3 << 6)
  31#define AIC_SCR_PRIORITY(x)             (x)
  32#define AIC_SCR_PRIORITY_MASK           0x7
  33
  34#define AIC_NUM_IRQS            32
  35
  36struct wpcm450_aic {
  37        void __iomem *regs;
  38        struct irq_domain *domain;
  39};
  40
  41static struct wpcm450_aic *aic;
  42
  43static void wpcm450_aic_init_hw(void)
  44{
  45        int i;
  46
  47        /* Disable (mask) all interrupts */
  48        writel(0xffffffff, aic->regs + AIC_MDCR);
  49
  50        /*
  51         * Make sure the interrupt controller is ready to serve new interrupts.
  52         * Reading from IPER indicates that the nIRQ signal may be deasserted,
  53         * and writing to EOSCR indicates that interrupt handling has finished.
  54         */
  55        readl(aic->regs + AIC_IPER);
  56        writel(0, aic->regs + AIC_EOSCR);
  57
  58        /* Initialize trigger mode and priority of each interrupt source */
  59        for (i = 0; i < AIC_NUM_IRQS; i++)
  60                writel(AIC_SCR_SRCTYPE_HIGH_LEVEL | AIC_SCR_PRIORITY(7),
  61                       aic->regs + AIC_SCR(i));
  62}
  63
  64static void __exception_irq_entry wpcm450_aic_handle_irq(struct pt_regs *regs)
  65{
  66        int hwirq;
  67
  68        /* Determine the interrupt source */
  69        /* Read IPER to signal that nIRQ can be de-asserted */
  70        hwirq = readl(aic->regs + AIC_IPER) / 4;
  71
  72        handle_domain_irq(aic->domain, hwirq, regs);
  73}
  74
  75static void wpcm450_aic_eoi(struct irq_data *d)
  76{
  77        /* Signal end-of-service */
  78        writel(0, aic->regs + AIC_EOSCR);
  79}
  80
  81static void wpcm450_aic_mask(struct irq_data *d)
  82{
  83        unsigned int mask = BIT(d->hwirq);
  84
  85        /* Disable (mask) the interrupt */
  86        writel(mask, aic->regs + AIC_MDCR);
  87}
  88
  89static void wpcm450_aic_unmask(struct irq_data *d)
  90{
  91        unsigned int mask = BIT(d->hwirq);
  92
  93        /* Enable (unmask) the interrupt */
  94        writel(mask, aic->regs + AIC_MECR);
  95}
  96
  97static int wpcm450_aic_set_type(struct irq_data *d, unsigned int flow_type)
  98{
  99        /*
 100         * The hardware supports high/low level, as well as rising/falling edge
 101         * modes, and the DT binding accommodates for that, but as long as
 102         * other modes than high level mode are not used and can't be tested,
 103         * they are rejected in this driver.
 104         */
 105        if ((flow_type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_HIGH)
 106                return -EINVAL;
 107
 108        return 0;
 109}
 110
 111static struct irq_chip wpcm450_aic_chip = {
 112        .name = "wpcm450-aic",
 113        .irq_eoi = wpcm450_aic_eoi,
 114        .irq_mask = wpcm450_aic_mask,
 115        .irq_unmask = wpcm450_aic_unmask,
 116        .irq_set_type = wpcm450_aic_set_type,
 117};
 118
 119static int wpcm450_aic_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hwirq)
 120{
 121        if (hwirq >= AIC_NUM_IRQS)
 122                return -EPERM;
 123
 124        irq_set_chip_and_handler(irq, &wpcm450_aic_chip, handle_fasteoi_irq);
 125        irq_set_chip_data(irq, aic);
 126        irq_set_probe(irq);
 127
 128        return 0;
 129}
 130
 131static const struct irq_domain_ops wpcm450_aic_ops = {
 132        .map = wpcm450_aic_map,
 133        .xlate = irq_domain_xlate_twocell,
 134};
 135
 136static int __init wpcm450_aic_of_init(struct device_node *node,
 137                                      struct device_node *parent)
 138{
 139        if (parent)
 140                return -EINVAL;
 141
 142        aic = kzalloc(sizeof(*aic), GFP_KERNEL);
 143        if (!aic)
 144                return -ENOMEM;
 145
 146        aic->regs = of_iomap(node, 0);
 147        if (!aic->regs) {
 148                pr_err("Failed to map WPCM450 AIC registers\n");
 149                return -ENOMEM;
 150        }
 151
 152        wpcm450_aic_init_hw();
 153
 154        set_handle_irq(wpcm450_aic_handle_irq);
 155
 156        aic->domain = irq_domain_add_linear(node, AIC_NUM_IRQS, &wpcm450_aic_ops, aic);
 157
 158        return 0;
 159}
 160
 161IRQCHIP_DECLARE(wpcm450_aic, "nuvoton,wpcm450-aic", wpcm450_aic_of_init);
 162