diff options
Diffstat (limited to 'arch/arm/plat-versatile/fpga-irq.c')
| -rw-r--r-- | arch/arm/plat-versatile/fpga-irq.c | 116 | 
1 files changed, 101 insertions, 15 deletions
diff --git a/arch/arm/plat-versatile/fpga-irq.c b/arch/arm/plat-versatile/fpga-irq.c index f0cc8e19b09..6e70d03824a 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++;  }  |