linux/arch/ppc/syslib/ppc4xx_pic.c

245 lines
6.4 KiB
C
Raw Normal View History

/*
* 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;
}