#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
+#include <linux/kernel_stat.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <asm/coldfire.h>
* via a set of 4 "Interrupt Controller Registers" (ICR). There is a
* loose mapping of vector number to register and internal bits, but
* a table is the easiest and quickest way to map them.
+ *
+ * Note that the external interrupts are edge triggered (unlike the
+ * internal interrupt sources which are level triggered). Which means
+ * they also need acknowledgeing via acknowledge bits.
*/
struct irqmap {
unsigned char icr;
/*MCF_IRQ_SWTO*/ { .icr = MCFSIM_ICR4, .index = 16, .ack = 0, },
};
+/*
+ * The act of masking the interrupt also has a side effect of 'ack'ing
+ * an interrupt on this irq (for the external irqs). So this mask function
+ * is also an ack_mask function.
+ */
static void intc_irq_mask(unsigned int irq)
{
if ((irq >= MCFINT_VECBASE) && (irq <= MCFINT_VECMAX)) {
irq -= MCFINT_VECBASE;
if (intc_irqmap[irq].ack) {
u32 v;
- v = 0xd << intc_irqmap[irq].index;
+ v = readl(MCF_MBAR + intc_irqmap[irq].icr);
+ v &= (0x7 << intc_irqmap[irq].index);
+ v |= (0x8 << intc_irqmap[irq].index);
writel(v, MCF_MBAR + intc_irqmap[irq].icr);
}
}
static int intc_irq_set_type(unsigned int irq, unsigned int type)
{
- /* We can set the edge type here for external interrupts */
+ if ((irq >= MCFINT_VECBASE) && (irq <= MCFINT_VECMAX)) {
+ irq -= MCFINT_VECBASE;
+ if (intc_irqmap[irq].ack) {
+ u32 v;
+ v = readl(MCF_MBAR + MCFSIM_PITR);
+ if (type == IRQ_TYPE_EDGE_FALLING)
+ v &= ~(0x1 << (32 - irq));
+ else
+ v |= (0x1 << (32 - irq));
+ writel(v, MCF_MBAR + MCFSIM_PITR);
+ }
+ }
return 0;
}
+/*
+ * Simple flow handler to deal with the external edge triggered interrupts.
+ * We need to be careful with the masking/acking due to the side effects
+ * of masking an interrupt.
+ */
+static void intc_external_irq(unsigned int irq, struct irq_desc *desc)
+{
+ kstat_incr_irqs_this_cpu(irq, desc);
+ desc->status |= IRQ_INPROGRESS;
+ desc->chip->ack(irq);
+ handle_IRQ_event(irq, desc->action);
+ desc->status &= ~IRQ_INPROGRESS;
+}
+
static struct irq_chip intc_irq_chip = {
.name = "CF-INTC",
.mask = intc_irq_mask,
.unmask = intc_irq_unmask,
+ .mask_ack = intc_irq_mask,
.ack = intc_irq_ack,
.set_type = intc_irq_set_type,
};
void __init init_IRQ(void)
{
- int irq;
+ int irq, edge;
init_vectors();
for (irq = 0; (irq < NR_IRQS); irq++) {
set_irq_chip(irq, &intc_irq_chip);
- set_irq_type(irq, IRQ_TYPE_LEVEL_HIGH);
- set_irq_handler(irq, handle_level_irq);
+ edge = 0;
+ if ((irq >= MCFINT_VECBASE) && (irq <= MCFINT_VECMAX))
+ edge = intc_irqmap[irq - MCFINT_VECBASE].ack;
+ if (edge) {
+ set_irq_type(irq, IRQ_TYPE_EDGE_RISING);
+ set_irq_handler(irq, intc_external_irq);
+ } else {
+ set_irq_type(irq, IRQ_TYPE_LEVEL_HIGH);
+ set_irq_handler(irq, handle_level_irq);
+ }
}
}