diff options
Diffstat (limited to 'arch/microblaze/kernel/intc.c')
| -rw-r--r-- | arch/microblaze/kernel/intc.c | 241 |
1 files changed, 128 insertions, 113 deletions
diff --git a/arch/microblaze/kernel/intc.c b/arch/microblaze/kernel/intc.c index d61ea33aff7..15c7c12ea0e 100644 --- a/arch/microblaze/kernel/intc.c +++ b/arch/microblaze/kernel/intc.c @@ -1,5 +1,6 @@ /* - * Copyright (C) 2007-2009 Michal Simek <monstr@monstr.eu> + * Copyright (C) 2007-2013 Michal Simek <monstr@monstr.eu> + * Copyright (C) 2012-2013 Xilinx, Inc. * Copyright (C) 2007-2009 PetaLogix * Copyright (C) 2006 Atmark Techno, Inc. * @@ -8,24 +9,15 @@ * for more details. */ -#include <linux/init.h> +#include <linux/irqdomain.h> #include <linux/irq.h> -#include <asm/page.h> +#include <linux/of_address.h> #include <linux/io.h> #include <linux/bug.h> -#include <asm/prom.h> -#include <asm/irq.h> +#include "../../drivers/irqchip/irqchip.h" -#ifdef CONFIG_SELFMOD_INTC -#include <asm/selfmod.h> -#define INTC_BASE BARRIER_BASE_ADDR -#else -static unsigned int intc_baseaddr; -#define INTC_BASE intc_baseaddr -#endif - -unsigned int nr_irq; +static void __iomem *intc_baseaddr; /* No one else should require these constants, so define them locally here. */ #define ISR 0x00 /* Interrupt Status Register */ @@ -40,143 +32,166 @@ unsigned int nr_irq; #define MER_ME (1<<0) #define MER_HIE (1<<1) -static void intc_enable_or_unmask(unsigned int irq) +static unsigned int (*read_fn)(void __iomem *); +static void (*write_fn)(u32, void __iomem *); + +static void intc_write32(u32 val, void __iomem *addr) +{ + iowrite32(val, addr); +} + +static unsigned int intc_read32(void __iomem *addr) +{ + return ioread32(addr); +} + +static void intc_write32_be(u32 val, void __iomem *addr) +{ + iowrite32be(val, addr); +} + +static unsigned int intc_read32_be(void __iomem *addr) { - unsigned long mask = 1 << irq; - pr_debug("enable_or_unmask: %d\n", irq); - out_be32(INTC_BASE + SIE, mask); + return ioread32be(addr); +} + +static void intc_enable_or_unmask(struct irq_data *d) +{ + unsigned long mask = 1 << d->hwirq; + + pr_debug("enable_or_unmask: %ld\n", d->hwirq); /* ack level irqs because they can't be acked during * ack function since the handle_level_irq function * acks the irq before calling the interrupt handler */ - if (irq_desc[irq].status & IRQ_LEVEL) - out_be32(INTC_BASE + IAR, mask); -} + if (irqd_is_level_type(d)) + write_fn(mask, intc_baseaddr + IAR); -static void intc_disable_or_mask(unsigned int irq) -{ - pr_debug("disable: %d\n", irq); - out_be32(INTC_BASE + CIE, 1 << irq); + write_fn(mask, intc_baseaddr + SIE); } -static void intc_ack(unsigned int irq) +static void intc_disable_or_mask(struct irq_data *d) { - pr_debug("ack: %d\n", irq); - out_be32(INTC_BASE + IAR, 1 << irq); + pr_debug("disable: %ld\n", d->hwirq); + write_fn(1 << d->hwirq, intc_baseaddr + CIE); } -static void intc_mask_ack(unsigned int irq) +static void intc_ack(struct irq_data *d) { - unsigned long mask = 1 << irq; - pr_debug("disable_and_ack: %d\n", irq); - out_be32(INTC_BASE + CIE, mask); - out_be32(INTC_BASE + IAR, mask); + pr_debug("ack: %ld\n", d->hwirq); + write_fn(1 << d->hwirq, intc_baseaddr + IAR); } -static void intc_end(unsigned int irq) +static void intc_mask_ack(struct irq_data *d) { - unsigned long mask = 1 << irq; - pr_debug("end: %d\n", irq); - if (!(irq_desc[irq].status & (IRQ_DISABLED | IRQ_INPROGRESS))) { - out_be32(INTC_BASE + SIE, mask); - /* ack level sensitive intr */ - if (irq_desc[irq].status & IRQ_LEVEL) - out_be32(INTC_BASE + IAR, mask); - } + unsigned long mask = 1 << d->hwirq; + + pr_debug("disable_and_ack: %ld\n", d->hwirq); + write_fn(mask, intc_baseaddr + CIE); + write_fn(mask, intc_baseaddr + IAR); } static struct irq_chip intc_dev = { .name = "Xilinx INTC", - .unmask = intc_enable_or_unmask, - .mask = intc_disable_or_mask, - .ack = intc_ack, - .mask_ack = intc_mask_ack, - .end = intc_end, + .irq_unmask = intc_enable_or_unmask, + .irq_mask = intc_disable_or_mask, + .irq_ack = intc_ack, + .irq_mask_ack = intc_mask_ack, }; -unsigned int get_irq(struct pt_regs *regs) +static struct irq_domain *root_domain; + +unsigned int get_irq(void) { - int irq; + unsigned int hwirq, irq = -1; - /* - * NOTE: This function is the one that needs to be improved in - * order to handle multiple interrupt controllers. It currently - * is hardcoded to check for interrupts only on the first INTC. - */ - irq = in_be32(INTC_BASE + IVR); - pr_debug("get_irq: %d\n", irq); + hwirq = read_fn(intc_baseaddr + IVR); + if (hwirq != -1U) + irq = irq_find_mapping(root_domain, hwirq); + + pr_debug("get_irq: hwirq=%d, irq=%d\n", hwirq, irq); return irq; } -void __init init_IRQ(void) +static int xintc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) +{ + u32 intr_mask = (u32)d->host_data; + + if (intr_mask & (1 << hw)) { + irq_set_chip_and_handler_name(irq, &intc_dev, + handle_edge_irq, "edge"); + irq_clear_status_flags(irq, IRQ_LEVEL); + } else { + irq_set_chip_and_handler_name(irq, &intc_dev, + handle_level_irq, "level"); + irq_set_status_flags(irq, IRQ_LEVEL); + } + return 0; +} + +static const struct irq_domain_ops xintc_irq_domain_ops = { + .xlate = irq_domain_xlate_onetwocell, + .map = xintc_map, +}; + +static int __init xilinx_intc_of_init(struct device_node *intc, + struct device_node *parent) { - u32 i, j, intr_type; - struct device_node *intc = NULL; -#ifdef CONFIG_SELFMOD_INTC - unsigned int intc_baseaddr = 0; - static int arr_func[] = { - (int)&get_irq, - (int)&intc_enable_or_unmask, - (int)&intc_disable_or_mask, - (int)&intc_mask_ack, - (int)&intc_ack, - (int)&intc_end, - 0 - }; -#endif - const char * const intc_list[] = { - "xlnx,xps-intc-1.00.a", - NULL - }; - - for (j = 0; intc_list[j] != NULL; j++) { - intc = of_find_compatible_node(NULL, NULL, intc_list[j]); - if (intc) - break; + u32 nr_irq, intr_mask; + int ret; + + intc_baseaddr = of_iomap(intc, 0); + BUG_ON(!intc_baseaddr); + + ret = of_property_read_u32(intc, "xlnx,num-intr-inputs", &nr_irq); + if (ret < 0) { + pr_err("%s: unable to read xlnx,num-intr-inputs\n", __func__); + return -EINVAL; + } + + ret = of_property_read_u32(intc, "xlnx,kind-of-intr", &intr_mask); + if (ret < 0) { + pr_err("%s: unable to read xlnx,kind-of-intr\n", __func__); + return -EINVAL; } - BUG_ON(!intc); - - intc_baseaddr = be32_to_cpup(of_get_property(intc, - "reg", NULL)); - intc_baseaddr = (unsigned long) ioremap(intc_baseaddr, PAGE_SIZE); - nr_irq = be32_to_cpup(of_get_property(intc, - "xlnx,num-intr-inputs", NULL)); - - intr_type = - be32_to_cpup(of_get_property(intc, - "xlnx,kind-of-intr", NULL)); - if (intr_type >= (1 << (nr_irq + 1))) - printk(KERN_INFO " ERROR: Mismatch in kind-of-intr param\n"); - -#ifdef CONFIG_SELFMOD_INTC - selfmod_function((int *) arr_func, intc_baseaddr); -#endif - printk(KERN_INFO "%s #0 at 0x%08x, num_irq=%d, edge=0x%x\n", - intc_list[j], intc_baseaddr, nr_irq, intr_type); + + if (intr_mask > (u32)((1ULL << nr_irq) - 1)) + pr_info(" ERROR: Mismatch in kind-of-intr param\n"); + + pr_info("%s: num_irq=%d, edge=0x%x\n", + intc->full_name, nr_irq, intr_mask); + + write_fn = intc_write32; + read_fn = intc_read32; /* * Disable all external interrupts until they are * explicity requested. */ - out_be32(intc_baseaddr + IER, 0); + write_fn(0, intc_baseaddr + IER); /* Acknowledge any pending interrupts just in case. */ - out_be32(intc_baseaddr + IAR, 0xffffffff); + write_fn(0xffffffff, intc_baseaddr + IAR); /* Turn on the Master Enable. */ - out_be32(intc_baseaddr + MER, MER_HIE | MER_ME); - - for (i = 0; i < nr_irq; ++i) { - if (intr_type & (0x00000001 << i)) { - set_irq_chip_and_handler_name(i, &intc_dev, - handle_edge_irq, intc_dev.name); - irq_desc[i].status &= ~IRQ_LEVEL; - } else { - set_irq_chip_and_handler_name(i, &intc_dev, - handle_level_irq, intc_dev.name); - irq_desc[i].status |= IRQ_LEVEL; - } + write_fn(MER_HIE | MER_ME, intc_baseaddr + MER); + if (!(read_fn(intc_baseaddr + MER) & (MER_HIE | MER_ME))) { + write_fn = intc_write32_be; + read_fn = intc_read32_be; + write_fn(MER_HIE | MER_ME, intc_baseaddr + MER); } + + /* Yeah, okay, casting the intr_mask to a void* is butt-ugly, but I'm + * lazy and Michal can clean it up to something nicer when he tests + * and commits this patch. ~~gcl */ + root_domain = irq_domain_add_linear(intc, nr_irq, &xintc_irq_domain_ops, + (void *)intr_mask); + + irq_set_default_host(root_domain); + + return 0; } + +IRQCHIP_DECLARE(xilinx_intc, "xlnx,xps-intc-1.00.a", xilinx_intc_of_init); |
