linux/arch/arm/plat-spear/shirq.c
viresh kumar 4c18e77f71 ARM: 6091/1: ST SPEAr: Adding support for shared irq layer
Multiple peripherals in SPEAr share common hardware interrupt lines.
This patch adds support for a shared irq layer, which registers hardware
irqs by itself and exposes virtual irq numbers to peripherals.

Signed-off-by: Viresh Kumar <viresh.kumar@st.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
2010-05-04 16:54:54 +01:00

119 lines
3.2 KiB
C

/*
* arch/arm/plat-spear/shirq.c
*
* SPEAr platform shared irq layer source file
*
* Copyright (C) 2009 ST Microelectronics
* Viresh Kumar<viresh.kumar@st.com>
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*/
#include <linux/err.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/spinlock.h>
#include <plat/shirq.h>
struct spear_shirq *shirq;
static DEFINE_SPINLOCK(lock);
static void shirq_irq_mask(unsigned irq)
{
struct spear_shirq *shirq = get_irq_chip_data(irq);
u32 val, id = irq - shirq->dev_config[0].virq;
unsigned long flags;
if ((shirq->regs.enb_reg == -1) || shirq->dev_config[id].enb_mask == -1)
return;
spin_lock_irqsave(&lock, flags);
val = readl(shirq->regs.base + shirq->regs.enb_reg);
if (shirq->regs.reset_to_enb)
val |= shirq->dev_config[id].enb_mask;
else
val &= ~(shirq->dev_config[id].enb_mask);
writel(val, shirq->regs.base + shirq->regs.enb_reg);
spin_unlock_irqrestore(&lock, flags);
}
static void shirq_irq_unmask(unsigned irq)
{
struct spear_shirq *shirq = get_irq_chip_data(irq);
u32 val, id = irq - shirq->dev_config[0].virq;
unsigned long flags;
if ((shirq->regs.enb_reg == -1) || shirq->dev_config[id].enb_mask == -1)
return;
spin_lock_irqsave(&lock, flags);
val = readl(shirq->regs.base + shirq->regs.enb_reg);
if (shirq->regs.reset_to_enb)
val &= ~(shirq->dev_config[id].enb_mask);
else
val |= shirq->dev_config[id].enb_mask;
writel(val, shirq->regs.base + shirq->regs.enb_reg);
spin_unlock_irqrestore(&lock, flags);
}
static struct irq_chip shirq_chip = {
.name = "spear_shirq",
.ack = shirq_irq_mask,
.mask = shirq_irq_mask,
.unmask = shirq_irq_unmask,
};
static void shirq_handler(unsigned irq, struct irq_desc *desc)
{
u32 i, val, mask;
struct spear_shirq *shirq = get_irq_data(irq);
desc->chip->ack(irq);
while ((val = readl(shirq->regs.base + shirq->regs.status_reg) &
shirq->regs.status_reg_mask)) {
for (i = 0; (i < shirq->dev_count) && val; i++) {
if (!(shirq->dev_config[i].status_mask & val))
continue;
generic_handle_irq(shirq->dev_config[i].virq);
/* clear interrupt */
val &= ~shirq->dev_config[i].status_mask;
if ((shirq->regs.clear_reg == -1) ||
shirq->dev_config[i].clear_mask == -1)
continue;
mask = readl(shirq->regs.base + shirq->regs.clear_reg);
if (shirq->regs.reset_to_clear)
mask &= ~shirq->dev_config[i].clear_mask;
else
mask |= shirq->dev_config[i].clear_mask;
writel(mask, shirq->regs.base + shirq->regs.clear_reg);
}
}
desc->chip->unmask(irq);
}
int spear_shirq_register(struct spear_shirq *shirq)
{
int i;
if (!shirq || !shirq->dev_config || !shirq->regs.base)
return -EFAULT;
if (!shirq->dev_count)
return -EINVAL;
set_irq_chained_handler(shirq->irq, shirq_handler);
for (i = 0; i < shirq->dev_count; i++) {
set_irq_chip(shirq->dev_config[i].virq, &shirq_chip);
set_irq_handler(shirq->dev_config[i].virq, handle_simple_irq);
set_irq_flags(shirq->dev_config[i].virq, IRQF_VALID);
set_irq_chip_data(shirq->dev_config[i].virq, shirq);
}
set_irq_data(shirq->irq, shirq);
return 0;
}