summaryrefslogtreecommitdiffstats
path: root/arch/ppc/syslib/ppc4xx_pic.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/ppc/syslib/ppc4xx_pic.c')
-rw-r--r--arch/ppc/syslib/ppc4xx_pic.c244
1 files changed, 244 insertions, 0 deletions
diff --git a/arch/ppc/syslib/ppc4xx_pic.c b/arch/ppc/syslib/ppc4xx_pic.c
new file mode 100644
index 000000000000..08f06dd17e7b
--- /dev/null
+++ b/arch/ppc/syslib/ppc4xx_pic.c
@@ -0,0 +1,244 @@
+/*
+ * arch/ppc/syslib/ppc4xx_pic.c
+ *
+ * Interrupt controller driver for PowerPC 4xx-based processors.
+ *
+ * Eugene Surovegin <eugene.surovegin@zultys.com> or <ebs@ebshome.net>
+ * Copyright (c) 2004, 2005 Zultys Technologies
+ *
+ * Based on original code by
+ * Copyright (c) 1999 Grant Erickson <grant@lcse.umn.edu>
+ * Armin Custer <akuster@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+*/
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/signal.h>
+#include <linux/stddef.h>
+
+#include <asm/processor.h>
+#include <asm/system.h>
+#include <asm/irq.h>
+#include <asm/ppc4xx_pic.h>
+
+/* See comment in include/arch-ppc/ppc4xx_pic.h
+ * for more info about these two variables
+ */
+extern struct ppc4xx_uic_settings ppc4xx_core_uic_cfg[NR_UICS]
+ __attribute__ ((weak));
+extern unsigned char ppc4xx_uic_ext_irq_cfg[] __attribute__ ((weak));
+
+#define IRQ_MASK_UIC0(irq) (1 << (31 - (irq)))
+#define IRQ_MASK_UICx(irq) (1 << (31 - ((irq) & 0x1f)))
+#define IRQ_MASK_UIC1(irq) IRQ_MASK_UICx(irq)
+#define IRQ_MASK_UIC2(irq) IRQ_MASK_UICx(irq)
+
+#define UIC_HANDLERS(n) \
+static void ppc4xx_uic##n##_enable(unsigned int irq) \
+{ \
+ ppc_cached_irq_mask[n] |= IRQ_MASK_UIC##n(irq); \
+ mtdcr(DCRN_UIC_ER(UIC##n), ppc_cached_irq_mask[n]); \
+} \
+ \
+static void ppc4xx_uic##n##_disable(unsigned int irq) \
+{ \
+ ppc_cached_irq_mask[n] &= ~IRQ_MASK_UIC##n(irq); \
+ mtdcr(DCRN_UIC_ER(UIC##n), ppc_cached_irq_mask[n]); \
+ ACK_UIC##n##_PARENT \
+} \
+ \
+static void ppc4xx_uic##n##_ack(unsigned int irq) \
+{ \
+ u32 mask = IRQ_MASK_UIC##n(irq); \
+ ppc_cached_irq_mask[n] &= ~mask; \
+ mtdcr(DCRN_UIC_ER(UIC##n), ppc_cached_irq_mask[n]); \
+ mtdcr(DCRN_UIC_SR(UIC##n), mask); \
+ ACK_UIC##n##_PARENT \
+} \
+ \
+static void ppc4xx_uic##n##_end(unsigned int irq) \
+{ \
+ unsigned int status = irq_desc[irq].status; \
+ u32 mask = IRQ_MASK_UIC##n(irq); \
+ if (status & IRQ_LEVEL) { \
+ mtdcr(DCRN_UIC_SR(UIC##n), mask); \
+ ACK_UIC##n##_PARENT \
+ } \
+ if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) { \
+ ppc_cached_irq_mask[n] |= mask; \
+ mtdcr(DCRN_UIC_ER(UIC##n), ppc_cached_irq_mask[n]); \
+ } \
+}
+
+#define DECLARE_UIC(n) \
+{ \
+ .typename = "UIC"#n, \
+ .enable = ppc4xx_uic##n##_enable, \
+ .disable = ppc4xx_uic##n##_disable, \
+ .ack = ppc4xx_uic##n##_ack, \
+ .end = ppc4xx_uic##n##_end, \
+} \
+
+#if NR_UICS == 3
+#define ACK_UIC0_PARENT mtdcr(DCRN_UIC_SR(UICB), UICB_UIC0NC);
+#define ACK_UIC1_PARENT mtdcr(DCRN_UIC_SR(UICB), UICB_UIC1NC);
+#define ACK_UIC2_PARENT mtdcr(DCRN_UIC_SR(UICB), UICB_UIC2NC);
+UIC_HANDLERS(0);
+UIC_HANDLERS(1);
+UIC_HANDLERS(2);
+
+static int ppc4xx_pic_get_irq(struct pt_regs *regs)
+{
+ u32 uicb = mfdcr(DCRN_UIC_MSR(UICB));
+ if (uicb & UICB_UIC0NC)
+ return 32 - ffs(mfdcr(DCRN_UIC_MSR(UIC0)));
+ else if (uicb & UICB_UIC1NC)
+ return 64 - ffs(mfdcr(DCRN_UIC_MSR(UIC1)));
+ else if (uicb & UICB_UIC2NC)
+ return 96 - ffs(mfdcr(DCRN_UIC_MSR(UIC2)));
+ else
+ return -1;
+}
+
+static void __init ppc4xx_pic_impl_init(void)
+{
+ /* Configure Base UIC */
+ mtdcr(DCRN_UIC_CR(UICB), 0);
+ mtdcr(DCRN_UIC_TR(UICB), 0);
+ mtdcr(DCRN_UIC_PR(UICB), 0xffffffff);
+ mtdcr(DCRN_UIC_SR(UICB), 0xffffffff);
+ mtdcr(DCRN_UIC_ER(UICB), UICB_UIC0NC | UICB_UIC1NC | UICB_UIC2NC);
+}
+
+#elif NR_UICS == 2
+#define ACK_UIC0_PARENT
+#define ACK_UIC1_PARENT mtdcr(DCRN_UIC_SR(UIC0), UIC0_UIC1NC);
+UIC_HANDLERS(0);
+UIC_HANDLERS(1);
+
+static int ppc4xx_pic_get_irq(struct pt_regs *regs)
+{
+ u32 uic0 = mfdcr(DCRN_UIC_MSR(UIC0));
+ if (uic0 & UIC0_UIC1NC)
+ return 64 - ffs(mfdcr(DCRN_UIC_MSR(UIC1)));
+ else
+ return uic0 ? 32 - ffs(uic0) : -1;
+}
+
+static void __init ppc4xx_pic_impl_init(void)
+{
+ /* Enable cascade interrupt in UIC0 */
+ ppc_cached_irq_mask[0] |= UIC0_UIC1NC;
+ mtdcr(DCRN_UIC_SR(UIC0), UIC0_UIC1NC);
+ mtdcr(DCRN_UIC_ER(UIC0), ppc_cached_irq_mask[0]);
+}
+
+#elif NR_UICS == 1
+#define ACK_UIC0_PARENT
+UIC_HANDLERS(0);
+
+static int ppc4xx_pic_get_irq(struct pt_regs *regs)
+{
+ u32 uic0 = mfdcr(DCRN_UIC_MSR(UIC0));
+ return uic0 ? 32 - ffs(uic0) : -1;
+}
+
+static inline void ppc4xx_pic_impl_init(void)
+{
+}
+#endif
+
+static struct ppc4xx_uic_impl {
+ struct hw_interrupt_type decl;
+ int base; /* Base DCR number */
+} __uic[] = {
+ { .decl = DECLARE_UIC(0), .base = UIC0 },
+#if NR_UICS > 1
+ { .decl = DECLARE_UIC(1), .base = UIC1 },
+#if NR_UICS > 2
+ { .decl = DECLARE_UIC(2), .base = UIC2 },
+#endif
+#endif
+};
+
+static inline int is_level_sensitive(int irq)
+{
+ u32 tr = mfdcr(DCRN_UIC_TR(__uic[irq >> 5].base));
+ return (tr & IRQ_MASK_UICx(irq)) == 0;
+}
+
+void __init ppc4xx_pic_init(void)
+{
+ int i;
+ unsigned char *eirqs = ppc4xx_uic_ext_irq_cfg;
+
+ for (i = 0; i < NR_UICS; ++i) {
+ int base = __uic[i].base;
+
+ /* Disable everything by default */
+ ppc_cached_irq_mask[i] = 0;
+ mtdcr(DCRN_UIC_ER(base), 0);
+
+ /* We don't use critical interrupts */
+ mtdcr(DCRN_UIC_CR(base), 0);
+
+ /* Configure polarity and triggering */
+ if (ppc4xx_core_uic_cfg) {
+ struct ppc4xx_uic_settings *p = ppc4xx_core_uic_cfg + i;
+ u32 mask = p->ext_irq_mask;
+ u32 pr = mfdcr(DCRN_UIC_PR(base)) & mask;
+ u32 tr = mfdcr(DCRN_UIC_TR(base)) & mask;
+
+ /* "Fixed" interrupts (on-chip devices) */
+ pr |= p->polarity & ~mask;
+ tr |= p->triggering & ~mask;
+
+ /* Merge external IRQs settings if board port
+ * provided them
+ */
+ if (eirqs && mask) {
+ pr &= ~mask;
+ tr &= ~mask;
+ while (mask) {
+ /* Extract current external IRQ mask */
+ u32 eirq_mask = 1 << __ilog2(mask);
+
+ if (!(*eirqs & IRQ_SENSE_LEVEL))
+ tr |= eirq_mask;
+
+ if (*eirqs & IRQ_POLARITY_POSITIVE)
+ pr |= eirq_mask;
+
+ mask &= ~eirq_mask;
+ ++eirqs;
+ }
+ }
+ mtdcr(DCRN_UIC_PR(base), pr);
+ mtdcr(DCRN_UIC_TR(base), tr);
+ }
+
+ /* ACK any pending interrupts to prevent false
+ * triggering after first enable
+ */
+ mtdcr(DCRN_UIC_SR(base), 0xffffffff);
+ }
+
+ /* Perform optional implementation specific setup
+ * (e.g. enable cascade interrupts for multi-UIC configurations)
+ */
+ ppc4xx_pic_impl_init();
+
+ /* Attach low-level handlers */
+ for (i = 0; i < (NR_UICS << 5); ++i) {
+ irq_desc[i].handler = &__uic[i >> 5].decl;
+ if (is_level_sensitive(i))
+ irq_desc[i].status |= IRQ_LEVEL;
+ }
+
+ ppc_md.get_irq = ppc4xx_pic_get_irq;
+}