| // SPDX-License-Identifier: GPL-2.0-only | 
| /* | 
|  *  Copyright (C) 2008 Ilya Yanok, Emcraft Systems | 
|  */ | 
|   | 
| #include <linux/irq.h> | 
| #include <linux/of_address.h> | 
| #include <linux/of_irq.h> | 
| #include <linux/of_platform.h> | 
| #include <linux/io.h> | 
|   | 
| /* | 
|  * The FPGA supports 9 interrupt sources, which can be routed to 3 | 
|  * interrupt request lines of the MPIC. The line to be used can be | 
|  * specified through the third cell of FDT property  "interrupts". | 
|  */ | 
|   | 
| #define SOCRATES_FPGA_NUM_IRQS    9 | 
|   | 
| #define FPGA_PIC_IRQCFG        (0x0) | 
| #define FPGA_PIC_IRQMASK(n)    (0x4 + 0x4 * (n)) | 
|   | 
| #define SOCRATES_FPGA_IRQ_MASK    ((1 << SOCRATES_FPGA_NUM_IRQS) - 1) | 
|   | 
| struct socrates_fpga_irq_info { | 
|     unsigned int irq_line; | 
|     int type; | 
| }; | 
|   | 
| /* | 
|  * Interrupt routing and type table | 
|  * | 
|  * IRQ_TYPE_NONE means the interrupt type is configurable, | 
|  * otherwise it's fixed to the specified value. | 
|  */ | 
| static struct socrates_fpga_irq_info fpga_irqs[SOCRATES_FPGA_NUM_IRQS] = { | 
|     [0] = {0, IRQ_TYPE_NONE}, | 
|     [1] = {0, IRQ_TYPE_LEVEL_HIGH}, | 
|     [2] = {0, IRQ_TYPE_LEVEL_LOW}, | 
|     [3] = {0, IRQ_TYPE_NONE}, | 
|     [4] = {0, IRQ_TYPE_NONE}, | 
|     [5] = {0, IRQ_TYPE_NONE}, | 
|     [6] = {0, IRQ_TYPE_NONE}, | 
|     [7] = {0, IRQ_TYPE_NONE}, | 
|     [8] = {0, IRQ_TYPE_LEVEL_HIGH}, | 
| }; | 
|   | 
| static DEFINE_RAW_SPINLOCK(socrates_fpga_pic_lock); | 
|   | 
| static void __iomem *socrates_fpga_pic_iobase; | 
| static struct irq_domain *socrates_fpga_pic_irq_host; | 
| static unsigned int socrates_fpga_irqs[3]; | 
|   | 
| static inline uint32_t socrates_fpga_pic_read(int reg) | 
| { | 
|     return in_be32(socrates_fpga_pic_iobase + reg); | 
| } | 
|   | 
| static inline void socrates_fpga_pic_write(int reg, uint32_t val) | 
| { | 
|     out_be32(socrates_fpga_pic_iobase + reg, val); | 
| } | 
|   | 
| static inline unsigned int socrates_fpga_pic_get_irq(unsigned int irq) | 
| { | 
|     uint32_t cause; | 
|     unsigned long flags; | 
|     int i; | 
|   | 
|     /* Check irq line routed to the MPIC */ | 
|     for (i = 0; i < 3; i++) { | 
|         if (irq == socrates_fpga_irqs[i]) | 
|             break; | 
|     } | 
|     if (i == 3) | 
|         return 0; | 
|   | 
|     raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); | 
|     cause = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(i)); | 
|     raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); | 
|     for (i = SOCRATES_FPGA_NUM_IRQS - 1; i >= 0; i--) { | 
|         if (cause >> (i + 16)) | 
|             break; | 
|     } | 
|     return irq_linear_revmap(socrates_fpga_pic_irq_host, | 
|             (irq_hw_number_t)i); | 
| } | 
|   | 
| static void socrates_fpga_pic_cascade(struct irq_desc *desc) | 
| { | 
|     struct irq_chip *chip = irq_desc_get_chip(desc); | 
|     unsigned int irq = irq_desc_get_irq(desc); | 
|     unsigned int cascade_irq; | 
|   | 
|     /* | 
|      * See if we actually have an interrupt, call generic handling code if | 
|      * we do. | 
|      */ | 
|     cascade_irq = socrates_fpga_pic_get_irq(irq); | 
|   | 
|     if (cascade_irq) | 
|         generic_handle_irq(cascade_irq); | 
|     chip->irq_eoi(&desc->irq_data); | 
| } | 
|   | 
| static void socrates_fpga_pic_ack(struct irq_data *d) | 
| { | 
|     unsigned long flags; | 
|     unsigned int irq_line, hwirq = irqd_to_hwirq(d); | 
|     uint32_t mask; | 
|   | 
|     irq_line = fpga_irqs[hwirq].irq_line; | 
|     raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); | 
|     mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line)) | 
|         & SOCRATES_FPGA_IRQ_MASK; | 
|     mask |= (1 << (hwirq + 16)); | 
|     socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask); | 
|     raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); | 
| } | 
|   | 
| static void socrates_fpga_pic_mask(struct irq_data *d) | 
| { | 
|     unsigned long flags; | 
|     unsigned int hwirq = irqd_to_hwirq(d); | 
|     int irq_line; | 
|     u32 mask; | 
|   | 
|     irq_line = fpga_irqs[hwirq].irq_line; | 
|     raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); | 
|     mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line)) | 
|         & SOCRATES_FPGA_IRQ_MASK; | 
|     mask &= ~(1 << hwirq); | 
|     socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask); | 
|     raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); | 
| } | 
|   | 
| static void socrates_fpga_pic_mask_ack(struct irq_data *d) | 
| { | 
|     unsigned long flags; | 
|     unsigned int hwirq = irqd_to_hwirq(d); | 
|     int irq_line; | 
|     u32 mask; | 
|   | 
|     irq_line = fpga_irqs[hwirq].irq_line; | 
|     raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); | 
|     mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line)) | 
|         & SOCRATES_FPGA_IRQ_MASK; | 
|     mask &= ~(1 << hwirq); | 
|     mask |= (1 << (hwirq + 16)); | 
|     socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask); | 
|     raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); | 
| } | 
|   | 
| static void socrates_fpga_pic_unmask(struct irq_data *d) | 
| { | 
|     unsigned long flags; | 
|     unsigned int hwirq = irqd_to_hwirq(d); | 
|     int irq_line; | 
|     u32 mask; | 
|   | 
|     irq_line = fpga_irqs[hwirq].irq_line; | 
|     raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); | 
|     mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line)) | 
|         & SOCRATES_FPGA_IRQ_MASK; | 
|     mask |= (1 << hwirq); | 
|     socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask); | 
|     raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); | 
| } | 
|   | 
| static void socrates_fpga_pic_eoi(struct irq_data *d) | 
| { | 
|     unsigned long flags; | 
|     unsigned int hwirq = irqd_to_hwirq(d); | 
|     int irq_line; | 
|     u32 mask; | 
|   | 
|     irq_line = fpga_irqs[hwirq].irq_line; | 
|     raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); | 
|     mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line)) | 
|         & SOCRATES_FPGA_IRQ_MASK; | 
|     mask |= (1 << (hwirq + 16)); | 
|     socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask); | 
|     raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); | 
| } | 
|   | 
| static int socrates_fpga_pic_set_type(struct irq_data *d, | 
|         unsigned int flow_type) | 
| { | 
|     unsigned long flags; | 
|     unsigned int hwirq = irqd_to_hwirq(d); | 
|     int polarity; | 
|     u32 mask; | 
|   | 
|     if (fpga_irqs[hwirq].type != IRQ_TYPE_NONE) | 
|         return -EINVAL; | 
|   | 
|     switch (flow_type & IRQ_TYPE_SENSE_MASK) { | 
|     case IRQ_TYPE_LEVEL_HIGH: | 
|         polarity = 1; | 
|         break; | 
|     case IRQ_TYPE_LEVEL_LOW: | 
|         polarity = 0; | 
|         break; | 
|     default: | 
|         return -EINVAL; | 
|     } | 
|     raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); | 
|     mask = socrates_fpga_pic_read(FPGA_PIC_IRQCFG); | 
|     if (polarity) | 
|         mask |= (1 << hwirq); | 
|     else | 
|         mask &= ~(1 << hwirq); | 
|     socrates_fpga_pic_write(FPGA_PIC_IRQCFG, mask); | 
|     raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); | 
|     return 0; | 
| } | 
|   | 
| static struct irq_chip socrates_fpga_pic_chip = { | 
|     .name        = "FPGA-PIC", | 
|     .irq_ack    = socrates_fpga_pic_ack, | 
|     .irq_mask    = socrates_fpga_pic_mask, | 
|     .irq_mask_ack    = socrates_fpga_pic_mask_ack, | 
|     .irq_unmask    = socrates_fpga_pic_unmask, | 
|     .irq_eoi    = socrates_fpga_pic_eoi, | 
|     .irq_set_type    = socrates_fpga_pic_set_type, | 
| }; | 
|   | 
| static int socrates_fpga_pic_host_map(struct irq_domain *h, unsigned int virq, | 
|         irq_hw_number_t hwirq) | 
| { | 
|     /* All interrupts are LEVEL sensitive */ | 
|     irq_set_status_flags(virq, IRQ_LEVEL); | 
|     irq_set_chip_and_handler(virq, &socrates_fpga_pic_chip, | 
|                  handle_fasteoi_irq); | 
|   | 
|     return 0; | 
| } | 
|   | 
| static int socrates_fpga_pic_host_xlate(struct irq_domain *h, | 
|         struct device_node *ct,    const u32 *intspec, unsigned int intsize, | 
|         irq_hw_number_t *out_hwirq, unsigned int *out_flags) | 
| { | 
|     struct socrates_fpga_irq_info *fpga_irq = &fpga_irqs[intspec[0]]; | 
|   | 
|     *out_hwirq = intspec[0]; | 
|     if  (fpga_irq->type == IRQ_TYPE_NONE) { | 
|         /* type is configurable */ | 
|         if (intspec[1] != IRQ_TYPE_LEVEL_LOW && | 
|             intspec[1] != IRQ_TYPE_LEVEL_HIGH) { | 
|             pr_warn("FPGA PIC: invalid irq type, setting default active low\n"); | 
|             *out_flags = IRQ_TYPE_LEVEL_LOW; | 
|         } else { | 
|             *out_flags = intspec[1]; | 
|         } | 
|     } else { | 
|         /* type is fixed */ | 
|         *out_flags = fpga_irq->type; | 
|     } | 
|   | 
|     /* Use specified interrupt routing */ | 
|     if (intspec[2] <= 2) | 
|         fpga_irq->irq_line = intspec[2]; | 
|     else | 
|         pr_warn("FPGA PIC: invalid irq routing\n"); | 
|   | 
|     return 0; | 
| } | 
|   | 
| static const struct irq_domain_ops socrates_fpga_pic_host_ops = { | 
|     .map    = socrates_fpga_pic_host_map, | 
|     .xlate  = socrates_fpga_pic_host_xlate, | 
| }; | 
|   | 
| void socrates_fpga_pic_init(struct device_node *pic) | 
| { | 
|     unsigned long flags; | 
|     int i; | 
|   | 
|     /* Setup an irq_domain structure */ | 
|     socrates_fpga_pic_irq_host = irq_domain_add_linear(pic, | 
|             SOCRATES_FPGA_NUM_IRQS, &socrates_fpga_pic_host_ops, NULL); | 
|     if (socrates_fpga_pic_irq_host == NULL) { | 
|         pr_err("FPGA PIC: Unable to allocate host\n"); | 
|         return; | 
|     } | 
|   | 
|     for (i = 0; i < 3; i++) { | 
|         socrates_fpga_irqs[i] = irq_of_parse_and_map(pic, i); | 
|         if (!socrates_fpga_irqs[i]) { | 
|             pr_warn("FPGA PIC: can't get irq%d\n", i); | 
|             continue; | 
|         } | 
|         irq_set_chained_handler(socrates_fpga_irqs[i], | 
|                     socrates_fpga_pic_cascade); | 
|     } | 
|   | 
|     socrates_fpga_pic_iobase = of_iomap(pic, 0); | 
|   | 
|     raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags); | 
|     socrates_fpga_pic_write(FPGA_PIC_IRQMASK(0), | 
|             SOCRATES_FPGA_IRQ_MASK << 16); | 
|     socrates_fpga_pic_write(FPGA_PIC_IRQMASK(1), | 
|             SOCRATES_FPGA_IRQ_MASK << 16); | 
|     socrates_fpga_pic_write(FPGA_PIC_IRQMASK(2), | 
|             SOCRATES_FPGA_IRQ_MASK << 16); | 
|     raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags); | 
|   | 
|     pr_info("FPGA PIC: Setting up Socrates FPGA PIC\n"); | 
| } |