// 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/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 __init 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"); }