/* * 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) \ { \ u32 mask = IRQ_MASK_UIC##n(irq); \ if (irq_desc[irq].status & IRQ_LEVEL) \ mtdcr(DCRN_UIC_SR(UIC##n), mask); \ ppc_cached_irq_mask[n] |= mask; \ 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; }