diff options
author | Dale Farnsworth <dale@farnsworth.org> | 2007-05-12 10:55:24 +1000 |
---|---|---|
committer | Paul Mackerras <paulus@samba.org> | 2007-05-12 11:32:49 +1000 |
commit | e44b8941908ec9ccf03b52713c9e7d3471bada8c (patch) | |
tree | 750f6cf76959a64592223b32d11883bb04b8aba1 /arch/powerpc/sysdev/mv64x60_pic.c | |
parent | ae4b3fbc7a91ea4e5685edb0310bb185a12e5943 (diff) |
[POWERPC] Add interrupt support for Marvell mv64x60 chips
There are 3 interrupt groups each with its own status/mask registers.
We use a separate struct irq_chip for each interrupt group and handle
interrupts in two stages or levels: level 1 selects the appropriate
struct irq_chip, and level 2 selects individual interrupts within
that irq_chip.
Signed-off-by: Dale Farnsworth <dale@farnsworth.org>
Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'arch/powerpc/sysdev/mv64x60_pic.c')
-rw-r--r-- | arch/powerpc/sysdev/mv64x60_pic.c | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/arch/powerpc/sysdev/mv64x60_pic.c b/arch/powerpc/sysdev/mv64x60_pic.c new file mode 100644 index 00000000000..01d31628777 --- /dev/null +++ b/arch/powerpc/sysdev/mv64x60_pic.c @@ -0,0 +1,305 @@ +/* + * Interrupt handling for Marvell mv64360/mv64460 host bridges (Discovery) + * + * Author: Dale Farnsworth <dale@farnsworth.org> + * + * 2007 (c) MontaVista, Software, Inc. 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/stddef.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/prom.h> +#include <asm/irq.h> + +#include "mv64x60.h" + +/* Interrupt Controller Interface Registers */ +#define MV64X60_IC_MAIN_CAUSE_LO 0x0004 +#define MV64X60_IC_MAIN_CAUSE_HI 0x000c +#define MV64X60_IC_CPU0_INTR_MASK_LO 0x0014 +#define MV64X60_IC_CPU0_INTR_MASK_HI 0x001c +#define MV64X60_IC_CPU0_SELECT_CAUSE 0x0024 + +#define MV64X60_HIGH_GPP_GROUPS 0x0f000000 +#define MV64X60_SELECT_CAUSE_HIGH 0x40000000 + +/* General Purpose Pins Controller Interface Registers */ +#define MV64x60_GPP_INTR_CAUSE 0x0008 +#define MV64x60_GPP_INTR_MASK 0x000c + +#define MV64x60_LEVEL1_LOW 0 +#define MV64x60_LEVEL1_HIGH 1 +#define MV64x60_LEVEL1_GPP 2 + +#define MV64x60_LEVEL1_MASK 0x00000060 +#define MV64x60_LEVEL1_OFFSET 5 + +#define MV64x60_LEVEL2_MASK 0x0000001f + +#define MV64x60_NUM_IRQS 96 + +static DEFINE_SPINLOCK(mv64x60_lock); + +static void __iomem *mv64x60_irq_reg_base; +static void __iomem *mv64x60_gpp_reg_base; + +/* + * Interrupt Controller Handling + * + * The interrupt controller handles three groups of interrupts: + * main low: IRQ0-IRQ31 + * main high: IRQ32-IRQ63 + * gpp: IRQ64-IRQ95 + * + * This code handles interrupts in two levels. Level 1 selects the + * interrupt group, and level 2 selects an IRQ within that group. + * Each group has its own irq_chip structure. + */ + +static u32 mv64x60_cached_low_mask; +static u32 mv64x60_cached_high_mask = MV64X60_HIGH_GPP_GROUPS; +static u32 mv64x60_cached_gpp_mask; + +static struct irq_host *mv64x60_irq_host; + +/* + * mv64x60_chip_low functions + */ + +static void mv64x60_mask_low(unsigned int virq) +{ + int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; + unsigned long flags; + + spin_lock_irqsave(&mv64x60_lock, flags); + mv64x60_cached_low_mask &= ~(1 << level2); + out_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_LO, + mv64x60_cached_low_mask); + spin_unlock_irqrestore(&mv64x60_lock, flags); + (void)in_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_LO); +} + +static void mv64x60_unmask_low(unsigned int virq) +{ + int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; + unsigned long flags; + + spin_lock_irqsave(&mv64x60_lock, flags); + mv64x60_cached_low_mask |= 1 << level2; + out_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_LO, + mv64x60_cached_low_mask); + spin_unlock_irqrestore(&mv64x60_lock, flags); + (void)in_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_LO); +} + +static struct irq_chip mv64x60_chip_low = { + .name = "mv64x60_low", + .mask = mv64x60_mask_low, + .mask_ack = mv64x60_mask_low, + .unmask = mv64x60_unmask_low, +}; + +/* + * mv64x60_chip_high functions + */ + +static void mv64x60_mask_high(unsigned int virq) +{ + int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; + unsigned long flags; + + spin_lock_irqsave(&mv64x60_lock, flags); + mv64x60_cached_high_mask &= ~(1 << level2); + out_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_HI, + mv64x60_cached_high_mask); + spin_unlock_irqrestore(&mv64x60_lock, flags); + (void)in_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_HI); +} + +static void mv64x60_unmask_high(unsigned int virq) +{ + int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; + unsigned long flags; + + spin_lock_irqsave(&mv64x60_lock, flags); + mv64x60_cached_high_mask |= 1 << level2; + out_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_HI, + mv64x60_cached_high_mask); + spin_unlock_irqrestore(&mv64x60_lock, flags); + (void)in_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_HI); +} + +static struct irq_chip mv64x60_chip_high = { + .name = "mv64x60_high", + .mask = mv64x60_mask_high, + .mask_ack = mv64x60_mask_high, + .unmask = mv64x60_unmask_high, +}; + +/* + * mv64x60_chip_gpp functions + */ + +static void mv64x60_mask_gpp(unsigned int virq) +{ + int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; + unsigned long flags; + + spin_lock_irqsave(&mv64x60_lock, flags); + mv64x60_cached_gpp_mask &= ~(1 << level2); + out_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_MASK, + mv64x60_cached_gpp_mask); + spin_unlock_irqrestore(&mv64x60_lock, flags); + (void)in_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_MASK); +} + +static void mv64x60_mask_ack_gpp(unsigned int virq) +{ + int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; + unsigned long flags; + + spin_lock_irqsave(&mv64x60_lock, flags); + mv64x60_cached_gpp_mask &= ~(1 << level2); + out_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_MASK, + mv64x60_cached_gpp_mask); + out_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_CAUSE, + ~(1 << level2)); + spin_unlock_irqrestore(&mv64x60_lock, flags); + (void)in_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_CAUSE); +} + +static void mv64x60_unmask_gpp(unsigned int virq) +{ + int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; + unsigned long flags; + + spin_lock_irqsave(&mv64x60_lock, flags); + mv64x60_cached_gpp_mask |= 1 << level2; + out_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_MASK, + mv64x60_cached_gpp_mask); + spin_unlock_irqrestore(&mv64x60_lock, flags); + (void)in_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_MASK); +} + +static struct irq_chip mv64x60_chip_gpp = { + .name = "mv64x60_gpp", + .mask = mv64x60_mask_gpp, + .mask_ack = mv64x60_mask_ack_gpp, + .unmask = mv64x60_unmask_gpp, +}; + +/* + * mv64x60_host_ops functions + */ + +static int mv64x60_host_match(struct irq_host *h, struct device_node *np) +{ + return mv64x60_irq_host->host_data == np; +} + +static struct irq_chip *mv64x60_chips[] = { + [MV64x60_LEVEL1_LOW] = &mv64x60_chip_low, + [MV64x60_LEVEL1_HIGH] = &mv64x60_chip_high, + [MV64x60_LEVEL1_GPP] = &mv64x60_chip_gpp, +}; + +static int mv64x60_host_map(struct irq_host *h, unsigned int virq, + irq_hw_number_t hwirq) +{ + int level1; + + get_irq_desc(virq)->status |= IRQ_LEVEL; + + level1 = (hwirq & MV64x60_LEVEL1_MASK) >> MV64x60_LEVEL1_OFFSET; + BUG_ON(level1 > MV64x60_LEVEL1_GPP); + set_irq_chip_and_handler(virq, mv64x60_chips[level1], handle_level_irq); + + return 0; +} + +static struct irq_host_ops mv64x60_host_ops = { + .match = mv64x60_host_match, + .map = mv64x60_host_map, +}; + +/* + * Global functions + */ + +void __init mv64x60_init_irq(void) +{ + struct device_node *np; + phys_addr_t paddr; + unsigned int size; + const unsigned int *reg; + unsigned long flags; + + np = of_find_compatible_node(NULL, NULL, "marvell,mv64x60-gpp"); + reg = of_get_property(np, "reg", &size); + paddr = of_translate_address(np, reg); + mv64x60_gpp_reg_base = ioremap(paddr, reg[1]); + of_node_put(np); + + np = of_find_compatible_node(NULL, NULL, "marvell,mv64x60-pic"); + reg = of_get_property(np, "reg", &size); + paddr = of_translate_address(np, reg); + of_node_put(np); + mv64x60_irq_reg_base = ioremap(paddr, reg[1]); + + mv64x60_irq_host = irq_alloc_host(IRQ_HOST_MAP_LINEAR, MV64x60_NUM_IRQS, + &mv64x60_host_ops, MV64x60_NUM_IRQS); + + mv64x60_irq_host->host_data = np; + + spin_lock_irqsave(&mv64x60_lock, flags); + out_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_MASK, + mv64x60_cached_gpp_mask); + out_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_LO, + mv64x60_cached_low_mask); + out_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_HI, + mv64x60_cached_high_mask); + + out_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_CAUSE, 0); + out_le32(mv64x60_irq_reg_base + MV64X60_IC_MAIN_CAUSE_LO, 0); + out_le32(mv64x60_irq_reg_base + MV64X60_IC_MAIN_CAUSE_HI, 0); + spin_unlock_irqrestore(&mv64x60_lock, flags); +} + +unsigned int mv64x60_get_irq(void) +{ + u32 cause; + int level1; + irq_hw_number_t hwirq; + int virq = NO_IRQ; + + cause = in_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_SELECT_CAUSE); + if (cause & MV64X60_SELECT_CAUSE_HIGH) { + cause &= mv64x60_cached_high_mask; + level1 = MV64x60_LEVEL1_HIGH; + if (cause & MV64X60_HIGH_GPP_GROUPS) { + cause = in_le32(mv64x60_gpp_reg_base + + MV64x60_GPP_INTR_CAUSE); + cause &= mv64x60_cached_gpp_mask; + level1 = MV64x60_LEVEL1_GPP; + } + } else { + cause &= mv64x60_cached_low_mask; + level1 = MV64x60_LEVEL1_LOW; + } + if (cause) { + hwirq = (level1 << MV64x60_LEVEL1_OFFSET) | __ilog2(cause); + virq = irq_linear_revmap(mv64x60_irq_host, hwirq); + } + + return virq; +} |