mirror of
https://github.com/torvalds/linux.git
synced 2024-11-23 20:51:44 +00:00
39e3eb7265
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk> Signed-off-by: Paul Mackerras <paulus@samba.org>
285 lines
7.6 KiB
C
285 lines
7.6 KiB
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/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>
|
|
#include <asm/machdep.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 IRQ_MASK_UIC3(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 == 4
|
|
#define ACK_UIC0_PARENT
|
|
#define ACK_UIC1_PARENT mtdcr(DCRN_UIC_SR(UIC0), UIC0_UIC1NC);
|
|
#define ACK_UIC2_PARENT mtdcr(DCRN_UIC_SR(UIC0), UIC0_UIC2NC);
|
|
#define ACK_UIC3_PARENT mtdcr(DCRN_UIC_SR(UIC0), UIC0_UIC3NC);
|
|
UIC_HANDLERS(0);
|
|
UIC_HANDLERS(1);
|
|
UIC_HANDLERS(2);
|
|
UIC_HANDLERS(3);
|
|
|
|
static int ppc4xx_pic_get_irq(void)
|
|
{
|
|
u32 uic0 = mfdcr(DCRN_UIC_MSR(UIC0));
|
|
if (uic0 & UIC0_UIC1NC)
|
|
return 64 - ffs(mfdcr(DCRN_UIC_MSR(UIC1)));
|
|
else if (uic0 & UIC0_UIC2NC)
|
|
return 96 - ffs(mfdcr(DCRN_UIC_MSR(UIC2)));
|
|
else if (uic0 & UIC0_UIC3NC)
|
|
return 128 - ffs(mfdcr(DCRN_UIC_MSR(UIC3)));
|
|
else
|
|
return uic0 ? 32 - ffs(uic0) : -1;
|
|
}
|
|
|
|
static void __init ppc4xx_pic_impl_init(void)
|
|
{
|
|
/* Enable cascade interrupts in UIC0 */
|
|
ppc_cached_irq_mask[0] |= UIC0_UIC1NC | UIC0_UIC2NC | UIC0_UIC3NC;
|
|
mtdcr(DCRN_UIC_SR(UIC0), UIC0_UIC1NC | UIC0_UIC2NC | UIC0_UIC3NC);
|
|
mtdcr(DCRN_UIC_ER(UIC0), ppc_cached_irq_mask[0]);
|
|
}
|
|
|
|
#elif 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(void)
|
|
{
|
|
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)
|
|
{
|
|
#if defined(CONFIG_440GX)
|
|
/* Disable 440GP compatibility mode if it was enabled in firmware */
|
|
SDR_WRITE(DCRN_SDR_MFR, SDR_READ(DCRN_SDR_MFR) & ~DCRN_SDR_MFR_PCM);
|
|
#endif
|
|
/* 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(void)
|
|
{
|
|
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(void)
|
|
{
|
|
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 },
|
|
#if NR_UICS > 3
|
|
{ .decl = DECLARE_UIC(3), .base = UIC3 },
|
|
#endif
|
|
#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].chip = &__uic[i >> 5].decl;
|
|
if (is_level_sensitive(i))
|
|
irq_desc[i].status |= IRQ_LEVEL;
|
|
}
|
|
|
|
ppc_md.get_irq = ppc4xx_pic_get_irq;
|
|
}
|