diff options
Diffstat (limited to 'arch/powerpc/platforms/85xx/socrates_fpga_pic.c')
-rw-r--r-- | arch/powerpc/platforms/85xx/socrates_fpga_pic.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/85xx/socrates_fpga_pic.c b/arch/powerpc/platforms/85xx/socrates_fpga_pic.c new file mode 100644 index 000000000..199a137c0 --- /dev/null +++ b/arch/powerpc/platforms/85xx/socrates_fpga_pic.c @@ -0,0 +1,309 @@ +// 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"); +} |