summaryrefslogtreecommitdiff
path: root/arch/arm/mach-ns9xxx/irq.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-ns9xxx/irq.c')
-rw-r--r--arch/arm/mach-ns9xxx/irq.c326
1 files changed, 294 insertions, 32 deletions
diff --git a/arch/arm/mach-ns9xxx/irq.c b/arch/arm/mach-ns9xxx/irq.c
index 22e0eb6e9ec4..e678f53cca61 100644
--- a/arch/arm/mach-ns9xxx/irq.c
+++ b/arch/arm/mach-ns9xxx/irq.c
@@ -1,7 +1,7 @@
/*
* arch/arm/mach-ns9xxx/irq.c
*
- * Copyright (C) 2006,2007 by Digi International Inc.
+ * Copyright (C) 2006-2008 by Digi International Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
@@ -10,30 +10,105 @@
*/
#include <linux/interrupt.h>
#include <linux/kernel_stat.h>
-#include <linux/io.h>
+#include <asm/io.h>
#include <asm/mach/irq.h>
+#include <asm/mach-types.h>
+#include <mach/regs-bbu.h>
#include <mach/regs-sys-common.h>
#include <mach/irqs.h>
#include <mach/board.h>
+#include <mach/processor.h>
-#include "generic.h"
+#include "processor-ns921x.h"
+#include "irq.h"
-/* simple interrupt prio table: prio(x) < prio(y) <=> x < y */
-#define irq2prio(i) (i)
-#define prio2irq(p) (p)
+static unsigned prio2irq(unsigned prio)
+{
+ u32 ic = __raw_readl(SYS_IC(prio / 4));
+ return REGGETIM_IDX(ic, SYS_IC, ISD, __SYS_IC_FIELDNUM(prio));
+}
+
+static unsigned irq2prio_map[32];
+
+static unsigned irq2prio(unsigned irq)
+{
+ unsigned cachedprio = irq2prio_map[irq];
+ int timeout;
+
+ if (likely(irq == prio2irq(cachedprio)))
+ return cachedprio;
+
+ for (timeout = 32; timeout; --timeout) {
+ static unsigned i;
+ unsigned iirq;
+
+ i = (i + 1) % 32;
+
+ iirq = prio2irq(i);
+
+ irq2prio_map[iirq] = i;
+
+ pr_debug("%s: update %u -> %u\n", __func__, iirq, i);
+
+ if (iirq == irq)
+ return i;
+ }
+
+ BUG();
+
+ return 0; /* not reached, clean compiler warning */
+}
+
+#define prio2irq_init(p) (p)
static void ns9xxx_mask_irq(unsigned int irq)
{
- /* XXX: better use cpp symbols */
int prio = irq2prio(irq);
u32 ic = __raw_readl(SYS_IC(prio / 4));
- ic &= ~(1 << (7 + 8 * (3 - (prio & 3))));
+ REGSET_IDX(ic, SYS_IC, IE, __SYS_IC_FIELDNUM(prio), DIS);
__raw_writel(ic, SYS_IC(prio / 4));
}
+int ns9xxx_is_enabled_irq(unsigned int irq)
+{
+ int prio = irq2prio(irq);
+ u32 ic, en_bit_mask;
+
+ en_bit_mask = 0x80 << ((3 - (prio & 3)) * 8);
+ ic = __raw_readl(SYS_IC(prio / 4));
+
+ return ic & en_bit_mask;
+}
+EXPORT_SYMBOL(ns9xxx_is_enabled_irq);
+
+static void ns9xxx_disable_irq(unsigned int irq)
+{
+ struct irq_desc *desc = irq_desc + irq;
+
+ ns9xxx_mask_irq(irq);
+ desc->status &= IRQ_MASKED;
+}
+
static void ns9xxx_ack_irq(unsigned int irq)
{
- __raw_writel(0, SYS_ISRADDR);
+ if (irq >= IRQ_NS9XXX_EXT0) {
+ u32 eixctl;
+
+ BUG_ON((unsigned)(irq - IRQ_NS9XXX_EXT0) > 4);
+
+ eixctl = __raw_readl(SYS_EIxCTRL(irq - IRQ_NS9XXX_EXT0));
+
+ if (REGGET(eixctl, SYS_EIxCTRL, TYPE) ==
+ SYS_EIxCTRL_TYPE_EDGE) {
+ REGSETIM(eixctl, SYS_EIxCTRL, CLEAR, 1);
+ __raw_writel(eixctl,
+ SYS_EIxCTRL(irq - IRQ_NS9XXX_EXT0));
+
+ REGSETIM(eixctl, SYS_EIxCTRL, CLEAR, 0);
+ __raw_writel(eixctl,
+ SYS_EIxCTRL(irq - IRQ_NS9XXX_EXT0));
+ }
+ }
}
static void ns9xxx_maskack_irq(unsigned int irq)
@@ -44,23 +119,175 @@ static void ns9xxx_maskack_irq(unsigned int irq)
static void ns9xxx_unmask_irq(unsigned int irq)
{
- /* XXX: better use cpp symbols */
int prio = irq2prio(irq);
u32 ic = __raw_readl(SYS_IC(prio / 4));
- ic |= 1 << (7 + 8 * (3 - (prio & 3)));
+ REGSET_IDX(ic, SYS_IC, IE, __SYS_IC_FIELDNUM(prio), EN);
__raw_writel(ic, SYS_IC(prio / 4));
}
+static void ns9xxx_eoi_irq(unsigned int irq)
+{
+ __raw_writel(irq2prio(irq), SYS_ISRADDR);
+}
+
+static int ns9xxx_set_type_irq(unsigned int irq, unsigned int flow_type)
+{
+ void __iomem *regeixctrl = SYS_EIxCTRL(irq - IRQ_NS9XXX_EXT0);
+ u32 eixctrl = 0;
+
+ REGSETIM(eixctrl, SYS_EIxCTRL, CLEAR, 1);
+
+ if (irq < IRQ_NS9XXX_EXT0 || irq > IRQ_NS9XXX_EXT3)
+ return -EINVAL;
+
+ switch (flow_type) {
+ case IRQF_TRIGGER_HIGH:
+ REGSET(eixctrl, SYS_EIxCTRL, PLTY, HIGH);
+ REGSET(eixctrl, SYS_EIxCTRL, TYPE, LEVEL);
+ break;
+
+ case IRQF_TRIGGER_LOW:
+ REGSET(eixctrl, SYS_EIxCTRL, PLTY, LOW);
+ REGSET(eixctrl, SYS_EIxCTRL, TYPE, LEVEL);
+ break;
+
+ case IRQF_TRIGGER_RISING:
+ REGSET(eixctrl, SYS_EIxCTRL, PLTY, HIGH);
+ REGSET(eixctrl, SYS_EIxCTRL, TYPE, EDGE);
+ break;
+
+ case IRQF_TRIGGER_FALLING:
+ REGSET(eixctrl, SYS_EIxCTRL, PLTY, LOW);
+ REGSET(eixctrl, SYS_EIxCTRL, TYPE, EDGE);
+ break;
+
+ default:
+ pr_warning("%s: cannot configure for flow type %u\n",
+ __func__, flow_type);
+ return -ENODEV;
+ }
+
+ __raw_writel(eixctrl, regeixctrl);
+ REGSETIM(eixctrl, SYS_EIxCTRL, CLEAR, 0);
+ __raw_writel(eixctrl, regeixctrl);
+
+ return 0;
+}
+
+static int ns9xxx_set_wake_irq(unsigned int irq, unsigned int on)
+{
+#if defined(CONFIG_PROCESSOR_NS921X)
+ if (processor_is_ns921x())
+ return ns921x_set_wake_irq(irq, on);
+#endif
+
+ return -EINVAL;
+}
+
static struct irq_chip ns9xxx_chip = {
+ .name = "ns9xxx",
+ .disable = ns9xxx_disable_irq,
.ack = ns9xxx_ack_irq,
.mask = ns9xxx_mask_irq,
.mask_ack = ns9xxx_maskack_irq,
.unmask = ns9xxx_unmask_irq,
+ .eoi = ns9xxx_eoi_irq,
+ .set_type = ns9xxx_set_type_irq,
+ .set_wake = ns9xxx_set_wake_irq,
+};
+
+#if defined(CONFIG_PROCESSOR_NS9360)
+static void ns9360_mask_bbus_irq(unsigned int irq)
+{
+ u32 ier = __raw_readl(NS9360_BBUS_IEN);
+ ier &= ~(1 << (irq - IRQ_NS9360_BBUS(0)));
+ __raw_writel(ier, NS9360_BBUS_IEN);
+}
+
+static void ns9360_unmask_bbus_irq(unsigned int irq)
+{
+ u32 ier = __raw_readl(NS9360_BBUS_IEN);
+ ier |= 1 << (irq - IRQ_NS9360_BBUS(0));
+ __raw_writel(ier, NS9360_BBUS_IEN);
+}
+
+static void ns9360_demux_bbus_irq(unsigned int irq, struct irq_desc *desc)
+{
+ unsigned bbus_irq_plus1;
+ u32 stat = __raw_readl(NS9360_BBUS_ISTAT);
+
+ while ((bbus_irq_plus1 = fls(stat))) {
+ unsigned bbus_irq = bbus_irq_plus1 - 1;
+ stat &= ~(1 << bbus_irq);
+
+ desc_handle_irq(IRQ_NS9360_BBUS(bbus_irq),
+ irq_desc + IRQ_NS9360_BBUS(bbus_irq));
+ }
+
+ /* unmask parent irq */
+ desc->chip->unmask(irq);
+ desc->chip->eoi(irq);
+}
+
+static struct irq_chip ns9360_bbus_chip = {
+ .name = "ns9xxx_bbus",
+ .ack = ns9360_mask_bbus_irq,
+ .mask = ns9360_mask_bbus_irq,
+ .mask_ack = ns9360_mask_bbus_irq,
+ .unmask = ns9360_unmask_bbus_irq,
+};
+
+static void ns9360_mask_bbus_dma_irq(unsigned int irq)
+{
+ u32 ien = __raw_readl(NS9360_BBUS_DMA_IEN);
+ ien &= ~(1 << (irq - IRQ_NS9360_BBUDMA(0)));
+ __raw_writel(ien, NS9360_BBUS_DMA_IEN);
+}
+
+static void ns9360_unmask_bbus_dma_irq( unsigned int irq )
+{
+ u32 ien = __raw_readl(NS9360_BBUS_DMA_IEN);
+ ien |= 1 << (irq - IRQ_NS9360_BBUDMA(0));
+ __raw_writel(ien, NS9360_BBUS_DMA_IEN);
+}
+
+static void ns9360_demux_bbus_dma_irq(unsigned int irq, struct irq_desc *desc)
+{
+ unsigned dma_irq_plus1;
+ u32 stat = __raw_readl(NS9360_BBUS_DMA_ISTAT);
+
+ /* mask parent irq */
+ desc->chip->mask_ack(irq);
+
+ while ((dma_irq_plus1 = fls(stat))) {
+ unsigned dma_irq = dma_irq_plus1 - 1;
+ stat &= ~(1 << dma_irq);
+
+ desc_handle_irq(IRQ_NS9360_BBUDMA(dma_irq),
+ irq_desc + IRQ_NS9360_BBUDMA(dma_irq));
+ }
+
+ /* unmask parent */
+ desc->chip->unmask(irq);
+}
+
+static struct irq_chip ns9360_bbus_dma_chip = {
+ .name = "ns9xxx_bbus_dma",
+ .ack = ns9360_mask_bbus_dma_irq,
+ .mask = ns9360_mask_bbus_dma_irq,
+ .mask_ack = ns9360_mask_bbus_dma_irq,
+ .unmask = ns9360_unmask_bbus_dma_irq,
};
+#endif /* if defined(CONFIG_PROCESSOR_NS9360) */
+
+extern int noirqdebug;
-#if 0
-#define handle_irq handle_level_irq
-#else
+/* this is similar to handle_fasteoi_irq. The differences are:
+ * - handle_prio_irq calls desc->chip->ack at the beginning;
+ * - handle_prio_irq disables an irq directly after handle_IRQ_event to work
+ * around the bug in the ns9xxx' irq priority encoder;
+ * - currently some debug code;
+ */
static void handle_prio_irq(unsigned int irq, struct irq_desc *desc)
{
unsigned int cpu = smp_processor_id();
@@ -69,24 +296,29 @@ static void handle_prio_irq(unsigned int irq, struct irq_desc *desc)
spin_lock(&desc->lock);
- BUG_ON(desc->status & IRQ_INPROGRESS);
+ desc->chip->ack(irq);
+
+ if (unlikely(desc->status & IRQ_INPROGRESS)) {
+ desc->status |= IRQ_PENDING;
+ goto out_unlock;
+ }
desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
kstat_cpu(cpu).irqs[irq]++;
action = desc->action;
- if (unlikely(!action || (desc->status & IRQ_DISABLED)))
+ if (unlikely(!action || (desc->status & IRQ_DISABLED))) {
+ desc->status |= IRQ_PENDING;
goto out_mask;
+ }
desc->status |= IRQ_INPROGRESS;
+ desc->status &= ~IRQ_PENDING;
spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, action);
-
- /* XXX: There is no direct way to access noirqdebug, so check
- * unconditionally for spurious irqs...
- * Maybe this function should go to kernel/irq/chip.c? */
- note_interrupt(irq, desc, action_ret);
+ if (!noirqdebug)
+ note_interrupt(irq, desc, action_ret);
spin_lock(&desc->lock);
desc->status &= ~IRQ_INPROGRESS;
@@ -95,32 +327,62 @@ static void handle_prio_irq(unsigned int irq, struct irq_desc *desc)
out_mask:
desc->chip->mask(irq);
- /* ack unconditionally to unmask lower prio irqs */
- desc->chip->ack(irq);
+ desc->chip->eoi(irq);
+out_unlock:
spin_unlock(&desc->lock);
}
-#define handle_irq handle_prio_irq
-#endif
void __init ns9xxx_init_irq(void)
{
int i;
/* disable all IRQs */
- for (i = 0; i < 8; ++i)
- __raw_writel(prio2irq(4 * i) << 24 |
- prio2irq(4 * i + 1) << 16 |
- prio2irq(4 * i + 2) << 8 |
- prio2irq(4 * i + 3),
- SYS_IC(i));
+ for (i = 0; i < 8; ++i) {
+ u32 ic = 0;
+
+ REGSETIM_IDX(ic, SYS_IC, ISD, __SYS_IC_FIELDNUM(0),
+ prio2irq_init(4 * i));
+ REGSETIM_IDX(ic, SYS_IC, ISD, __SYS_IC_FIELDNUM(1),
+ prio2irq_init(4 * i + 1));
+ REGSETIM_IDX(ic, SYS_IC, ISD, __SYS_IC_FIELDNUM(2),
+ prio2irq_init(4 * i + 2));
+ REGSETIM_IDX(ic, SYS_IC, ISD, __SYS_IC_FIELDNUM(3),
+ prio2irq_init(4 * i + 3));
+
+ __raw_writel(ic, SYS_IC(i));
+ }
for (i = 0; i < 32; ++i)
__raw_writel(prio2irq(i), SYS_IVA(i));
for (i = 0; i <= 31; ++i) {
set_irq_chip(i, &ns9xxx_chip);
- set_irq_handler(i, handle_irq);
+ set_irq_handler(i, handle_prio_irq);
set_irq_flags(i, IRQF_VALID);
}
+
+#ifdef CONFIG_PROCESSOR_NS9360
+ if (processor_is_ns9360()) {
+ /* set up the BBUS interrupt handlers */
+ __raw_writel(NS9360_BBUS_IEN_GLBL, NS9360_BBUS_IEN);
+ for (i = IRQ_NS9360_BBUS(0); i <= IRQ_NS9360_BBUS(25); i++) {
+ set_irq_chip(i, &ns9360_bbus_chip);
+ set_irq_handler(i, handle_level_irq);
+ set_irq_flags(i, IRQF_VALID);
+ }
+ set_irq_chained_handler(IRQ_NS9360_BBUSAGG,
+ ns9360_demux_bbus_irq);
+
+ /* set up the BBUS DMA interrupt handlers */
+ __raw_writel(0, NS9360_BBUS_DMA_IEN);
+ for (i = IRQ_NS9360_BBUDMA(0); i <= IRQ_NS9360_BBUDMA(15); i++) {
+ set_irq_chip(i, &ns9360_bbus_dma_chip);
+ set_irq_handler(i, handle_level_irq);
+ set_irq_flags(i, IRQF_VALID);
+ }
+ set_irq_chained_handler(IRQ_NS9360_BBUS_DMA,
+ ns9360_demux_bbus_dma_irq);
+ }
+#endif
}