diff options
author | Linus Walleij <linus.walleij@linaro.org> | 2012-04-28 17:33:47 +0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2012-05-04 19:53:37 +0400 |
commit | 3108e6ab21a9b9dbd88f0b2ff99f73e95b8b1580 (patch) | |
tree | 9e87e1e4196849090103317d109dc95ba9d159f8 /arch/arm/plat-versatile | |
parent | 69964ea4c7b68c9399f7977aa5b9aa6539a6a98a (diff) | |
download | linux-3108e6ab21a9b9dbd88f0b2ff99f73e95b8b1580.tar.xz |
ARM: 7389/2: plat-versatile: modernize FPGA IRQ controller
This does two things to the FPGA IRQ controller in the versatile
family:
- Convert to MULTI_IRQ_HANDLER so we can drop the entry macro
from the Integrator. The C IRQ handler was inspired from
arch/arm/common/vic.c, recent bug discovered in this handler was
accounted for.
- Convert to using IRQ domains so we can get rid of the NO_IRQ
mess and proceed with device tree and such stuff.
As part of the exercise, bump all the low IRQ numbers on the
Integrator PIC to start from 1 rather than 0, since IRQ 0 is
now NO_IRQ. The Linux IRQ numbers are thus entirely decoupled
from the hardware IRQ numbers in this controller.
I was unable to split this patch. The main reason is the half-done
conversion to device tree in Versatile.
Tested on Integrator/AP and Integrator/CP.
Cc: Grant Likely <grant.likely@secretlab.ca>
Acked-by: Rob Herring <rob.herring@calxeda.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch/arm/plat-versatile')
-rw-r--r-- | arch/arm/plat-versatile/Kconfig | 6 | ||||
-rw-r--r-- | arch/arm/plat-versatile/fpga-irq.c | 116 | ||||
-rw-r--r-- | arch/arm/plat-versatile/include/plat/fpga-irq.h | 11 |
3 files changed, 112 insertions, 21 deletions
diff --git a/arch/arm/plat-versatile/Kconfig b/arch/arm/plat-versatile/Kconfig index 043f7b02a9e7..81ee7cc34457 100644 --- a/arch/arm/plat-versatile/Kconfig +++ b/arch/arm/plat-versatile/Kconfig @@ -5,6 +5,12 @@ config PLAT_VERSATILE_CLCD config PLAT_VERSATILE_FPGA_IRQ bool + select IRQ_DOMAIN + +config PLAT_VERSATILE_FPGA_IRQ_NR + int + default 4 + depends on PLAT_VERSATILE_FPGA_IRQ config PLAT_VERSATILE_LEDS def_bool y if LEDS_CLASS diff --git a/arch/arm/plat-versatile/fpga-irq.c b/arch/arm/plat-versatile/fpga-irq.c index f0cc8e19b094..6e70d03824a1 100644 --- a/arch/arm/plat-versatile/fpga-irq.c +++ b/arch/arm/plat-versatile/fpga-irq.c @@ -3,7 +3,10 @@ */ #include <linux/irq.h> #include <linux/io.h> +#include <linux/irqdomain.h> +#include <linux/module.h> +#include <asm/exception.h> #include <asm/mach/irq.h> #include <plat/fpga-irq.h> @@ -12,10 +15,32 @@ #define IRQ_ENABLE_SET 0x08 #define IRQ_ENABLE_CLEAR 0x0c +/** + * struct fpga_irq_data - irq data container for the FPGA IRQ controller + * @base: memory offset in virtual memory + * @irq_start: first IRQ number handled by this instance + * @chip: chip container for this instance + * @domain: IRQ domain for this instance + * @valid: mask for valid IRQs on this controller + * @used_irqs: number of active IRQs on this controller + */ +struct fpga_irq_data { + void __iomem *base; + unsigned int irq_start; + struct irq_chip chip; + u32 valid; + struct irq_domain *domain; + u8 used_irqs; +}; + +/* we cannot allocate memory when the controllers are initially registered */ +static struct fpga_irq_data fpga_irq_devices[CONFIG_PLAT_VERSATILE_FPGA_IRQ_NR]; +static int fpga_irq_id; + static void fpga_irq_mask(struct irq_data *d) { struct fpga_irq_data *f = irq_data_get_irq_chip_data(d); - u32 mask = 1 << (d->irq - f->irq_start); + u32 mask = 1 << d->hwirq; writel(mask, f->base + IRQ_ENABLE_CLEAR); } @@ -23,7 +48,7 @@ static void fpga_irq_mask(struct irq_data *d) static void fpga_irq_unmask(struct irq_data *d) { struct fpga_irq_data *f = irq_data_get_irq_chip_data(d); - u32 mask = 1 << (d->irq - f->irq_start); + u32 mask = 1 << d->hwirq; writel(mask, f->base + IRQ_ENABLE_SET); } @@ -41,32 +66,93 @@ static void fpga_irq_handle(unsigned int irq, struct irq_desc *desc) do { irq = ffs(status) - 1; status &= ~(1 << irq); - - generic_handle_irq(irq + f->irq_start); + generic_handle_irq(irq_find_mapping(f->domain, irq)); } while (status); } -void __init fpga_irq_init(int parent_irq, u32 valid, struct fpga_irq_data *f) +/* + * Handle each interrupt in a single FPGA IRQ controller. Returns non-zero + * if we've handled at least one interrupt. This does a single read of the + * status register and handles all interrupts in order from LSB first. + */ +static int handle_one_fpga(struct fpga_irq_data *f, struct pt_regs *regs) +{ + int handled = 0; + int irq; + u32 status; + + while ((status = readl(f->base + IRQ_STATUS))) { + irq = ffs(status) - 1; + handle_IRQ(irq_find_mapping(f->domain, irq), regs); + handled = 1; + } + + return handled; +} + +/* + * Keep iterating over all registered FPGA IRQ controllers until there are + * no pending interrupts. + */ +asmlinkage void __exception_irq_entry fpga_handle_irq(struct pt_regs *regs) { - unsigned int i; + int i, handled; + do { + for (i = 0, handled = 0; i < fpga_irq_id; ++i) + handled |= handle_one_fpga(&fpga_irq_devices[i], regs); + } while (handled); +} + +static int fpga_irqdomain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct fpga_irq_data *f = d->host_data; + + /* Skip invalid IRQs, only register handlers for the real ones */ + if (!(f->valid & (1 << hwirq))) + return -ENOTSUPP; + irq_set_chip_data(irq, f); + irq_set_chip_and_handler(irq, &f->chip, + handle_level_irq); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + f->used_irqs++; + return 0; +} + +static struct irq_domain_ops fpga_irqdomain_ops = { + .map = fpga_irqdomain_map, + .xlate = irq_domain_xlate_onetwocell, +}; + +void __init fpga_irq_init(void __iomem *base, const char *name, int irq_start, + int parent_irq, u32 valid, struct device_node *node) +{ + struct fpga_irq_data *f; + + if (fpga_irq_id >= ARRAY_SIZE(fpga_irq_devices)) { + printk(KERN_ERR "%s: too few FPGA IRQ controllers, increase CONFIG_PLAT_VERSATILE_FPGA_IRQ_NR\n", __func__); + return; + } + + f = &fpga_irq_devices[fpga_irq_id]; + f->base = base; + f->irq_start = irq_start; + f->chip.name = name; f->chip.irq_ack = fpga_irq_mask; f->chip.irq_mask = fpga_irq_mask; f->chip.irq_unmask = fpga_irq_unmask; + f->valid = valid; if (parent_irq != -1) { irq_set_handler_data(parent_irq, f); irq_set_chained_handler(parent_irq, fpga_irq_handle); } - for (i = 0; i < 32; i++) { - if (valid & (1 << i)) { - unsigned int irq = f->irq_start + i; + f->domain = irq_domain_add_legacy(node, fls(valid), f->irq_start, 0, + &fpga_irqdomain_ops, f); + pr_info("FPGA IRQ chip %d \"%s\" @ %p, %u irqs\n", + fpga_irq_id, name, base, f->used_irqs); - irq_set_chip_data(irq, f); - irq_set_chip_and_handler(irq, &f->chip, - handle_level_irq); - set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); - } - } + fpga_irq_id++; } diff --git a/arch/arm/plat-versatile/include/plat/fpga-irq.h b/arch/arm/plat-versatile/include/plat/fpga-irq.h index 627fafd1e595..91bcfb67551d 100644 --- a/arch/arm/plat-versatile/include/plat/fpga-irq.h +++ b/arch/arm/plat-versatile/include/plat/fpga-irq.h @@ -1,12 +1,11 @@ #ifndef PLAT_FPGA_IRQ_H #define PLAT_FPGA_IRQ_H -struct fpga_irq_data { - void __iomem *base; - unsigned int irq_start; - struct irq_chip chip; -}; +struct device_node; +struct pt_regs; -void fpga_irq_init(int, u32, struct fpga_irq_data *); +void fpga_handle_irq(struct pt_regs *regs); +void fpga_irq_init(void __iomem *, const char *, int, int, u32, + struct device_node *node); #endif |