2005-04-17 02:20:36 +04:00
/*
* Macintosh interrupts
*
* General design :
* In contrary to the Amiga and Atari platforms , the Mac hardware seems to
* exclusively use the autovector interrupts ( the ' generic level0 - level7 '
* interrupts with exception vectors 0x19 - 0x1f ) . The following interrupt levels
* are used :
* 1 - VIA1
* - slot 0 : one second interrupt ( CA2 )
* - slot 1 : VBlank ( CA1 )
* - slot 2 : ADB data ready ( SR full )
* - slot 3 : ADB data ( CB2 )
* - slot 4 : ADB clock ( CB1 )
* - slot 5 : timer 2
* - slot 6 : timer 1
* - slot 7 : status of IRQ ; signals ' any enabled int . '
*
* 2 - VIA2 or RBV
* - slot 0 : SCSI DRQ ( CA2 )
* - slot 1 : NUBUS IRQ ( CA1 ) need to read port A to find which
* - slot 2 : / EXP IRQ ( only on IIci )
* - slot 3 : SCSI IRQ ( CB2 )
* - slot 4 : ASC IRQ ( CB1 )
* - slot 5 : timer 2 ( not on IIci )
* - slot 6 : timer 1 ( not on IIci )
* - slot 7 : status of IRQ ; signals ' any enabled int . '
*
* 2 - OSS ( IIfx only ? )
* - slot 0 : SCSI interrupt
* - slot 1 : Sound interrupt
*
* Levels 3 - 6 vary by machine type . For VIA or RBV Macintoshes :
*
* 3 - unused ( ? )
*
* 4 - SCC ( slot number determined by reading RR3 on the SSC itself )
* - slot 1 : SCC channel A
* - slot 2 : SCC channel B
*
* 5 - unused ( ? )
* [ serial errors or special conditions seem to raise level 6
* interrupts on some models ( LC4xx ? ) ]
*
* 6 - off switch ( ? )
*
* For OSS Macintoshes ( IIfx only at this point ) :
*
* 3 - Nubus interrupt
* - slot 0 : Slot $ 9
* - slot 1 : Slot $ A
* - slot 2 : Slot $ B
* - slot 3 : Slot $ C
* - slot 4 : Slot $ D
* - slot 5 : Slot $ E
*
* 4 - SCC IOP
* - slot 1 : SCC channel A
* - slot 2 : SCC channel B
*
* 5 - ISM IOP ( ADB ? )
*
* 6 - unused
*
* For PSC Macintoshes ( 660 AV , 840 AV ) :
*
* 3 - PSC level 3
* - slot 0 : MACE
*
* 4 - PSC level 4
* - slot 1 : SCC channel A interrupt
* - slot 2 : SCC channel B interrupt
* - slot 3 : MACE DMA
*
* 5 - PSC level 5
*
* 6 - PSC level 6
*
* Finally we have good ' ole level 7 , the non - maskable interrupt :
*
* 7 - NMI ( programmer ' s switch on the back of some Macs )
* Also RAM parity error on models which support it ( IIc , IIfx ? )
*
* The current interrupt logic looks something like this :
*
* - We install dispatchers for the autovector interrupts ( 1 - 7 ) . These
* dispatchers are responsible for querying the hardware ( the
* VIA / RBV / OSS / PSC chips ) to determine the actual interrupt source . Using
* this information a machspec interrupt number is generated by placing the
* index of the interrupt hardware into the low three bits and the original
* autovector interrupt number in the upper 5 bits . The handlers for the
* resulting machspec interrupt are then called .
*
* - Nubus is a special case because its interrupts are hidden behind two
* layers of hardware . Nubus interrupts come in as index 1 on VIA # 2 ,
* which translates to IRQ number 17. In this spot we install _another_
* dispatcher . This dispatcher finds the interrupting slot number ( 9 - F ) and
* then forms a new machspec interrupt number as above with the slot number
* minus 9 in the low three bits and the pseudo - level 7 in the upper five
* bits . The handlers for this new machspec interrupt number are then
* called . This puts Nubus interrupts into the range 56 - 62.
*
* - The Baboon interrupts ( used on some PowerBooks ) are an even more special
* case . They ' re hidden behind the Nubus slot $ C interrupt thus adding a
* third layer of indirection . Why oh why did the Apple engineers do that ?
*
* - We support " fast " and " slow " handlers , just like the Amiga port . The
* fast handlers are called first and with all interrupts disabled . They
* are expected to execute quickly ( hence the name ) . The slow handlers are
* called last with interrupts enabled and the interrupt level restored .
* They must therefore be reentrant .
*
* TODO :
*
*/
2007-07-20 07:33:28 +04:00
# include <linux/module.h>
2005-04-17 02:20:36 +04:00
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/kernel_stat.h>
# include <linux/interrupt.h> /* for intr_count */
# include <linux/delay.h>
# include <linux/seq_file.h>
# include <asm/system.h>
# include <asm/irq.h>
# include <asm/traps.h>
# include <asm/bootinfo.h>
# include <asm/machw.h>
# include <asm/macintosh.h>
# include <asm/mac_via.h>
# include <asm/mac_psc.h>
# include <asm/hwtest.h>
# include <asm/errno.h>
# include <asm/macints.h>
2006-10-07 17:16:45 +04:00
# include <asm/irq_regs.h>
2005-04-17 02:20:36 +04:00
# define DEBUG_SPURIOUS
# define SHUTUP_SONIC
/* SCC interrupt mask */
static int scc_mask ;
/*
* VIA / RBV hooks
*/
extern void via_init ( void ) ;
extern void via_register_interrupts ( void ) ;
extern void via_irq_enable ( int ) ;
extern void via_irq_disable ( int ) ;
extern void via_irq_clear ( int ) ;
extern int via_irq_pending ( int ) ;
/*
* OSS hooks
*/
extern int oss_present ;
extern void oss_init ( void ) ;
extern void oss_register_interrupts ( void ) ;
extern void oss_irq_enable ( int ) ;
extern void oss_irq_disable ( int ) ;
extern void oss_irq_clear ( int ) ;
extern int oss_irq_pending ( int ) ;
/*
* PSC hooks
*/
extern int psc_present ;
extern void psc_init ( void ) ;
extern void psc_register_interrupts ( void ) ;
extern void psc_irq_enable ( int ) ;
extern void psc_irq_disable ( int ) ;
extern void psc_irq_clear ( int ) ;
extern int psc_irq_pending ( int ) ;
/*
* IOP hooks
*/
extern void iop_register_interrupts ( void ) ;
/*
* Baboon hooks
*/
extern int baboon_present ;
extern void baboon_init ( void ) ;
extern void baboon_register_interrupts ( void ) ;
extern void baboon_irq_enable ( int ) ;
extern void baboon_irq_disable ( int ) ;
extern void baboon_irq_clear ( int ) ;
extern int baboon_irq_pending ( int ) ;
/*
* SCC interrupt routines
*/
2006-06-25 16:47:04 +04:00
static void scc_irq_enable ( unsigned int ) ;
static void scc_irq_disable ( unsigned int ) ;
2005-04-17 02:20:36 +04:00
/*
* console_loglevel determines NMI handler function
*/
2006-10-07 17:16:45 +04:00
irqreturn_t mac_nmi_handler ( int , void * ) ;
irqreturn_t mac_debug_handler ( int , void * ) ;
2005-04-17 02:20:36 +04:00
/* #define DEBUG_MACINTS */
2006-06-25 16:47:04 +04:00
static void mac_enable_irq ( unsigned int irq ) ;
static void mac_disable_irq ( unsigned int irq ) ;
static struct irq_controller mac_irq_controller = {
. name = " mac " ,
2007-05-07 01:50:54 +04:00
. lock = __SPIN_LOCK_UNLOCKED ( mac_irq_controller . lock ) ,
2006-06-25 16:47:04 +04:00
. enable = mac_enable_irq ,
. disable = mac_disable_irq ,
} ;
2007-07-20 07:33:28 +04:00
void __init mac_init_IRQ ( void )
2005-04-17 02:20:36 +04:00
{
# ifdef DEBUG_MACINTS
printk ( " mac_init_IRQ(): Setting things up... \n " ) ;
# endif
scc_mask = 0 ;
2006-06-25 16:47:04 +04:00
m68k_setup_irq_controller ( & mac_irq_controller , IRQ_USER ,
NUM_MAC_SOURCES - IRQ_USER ) ;
2005-04-17 02:20:36 +04:00
/* Make sure the SONIC interrupt is cleared or things get ugly */
# ifdef SHUTUP_SONIC
printk ( " Killing onboard sonic... " ) ;
/* This address should hopefully be mapped already */
if ( hwreg_present ( ( void * ) ( 0x50f0a000 ) ) ) {
* ( long * ) ( 0x50f0a014 ) = 0x7fffL ;
* ( long * ) ( 0x50f0a010 ) = 0L ;
}
printk ( " Done. \n " ) ;
# endif /* SHUTUP_SONIC */
/*
* Now register the handlers for the master IRQ handlers
* at levels 1 - 7. Most of the work is done elsewhere .
*/
2006-06-25 16:47:04 +04:00
if ( oss_present )
2005-04-17 02:20:36 +04:00
oss_register_interrupts ( ) ;
2006-06-25 16:47:04 +04:00
else
2005-04-17 02:20:36 +04:00
via_register_interrupts ( ) ;
2006-06-25 16:47:04 +04:00
if ( psc_present )
psc_register_interrupts ( ) ;
if ( baboon_present )
baboon_register_interrupts ( ) ;
2005-04-17 02:20:36 +04:00
iop_register_interrupts ( ) ;
2006-06-25 16:47:04 +04:00
request_irq ( IRQ_AUTO_7 , mac_nmi_handler , 0 , " NMI " ,
2005-04-17 02:20:36 +04:00
mac_nmi_handler ) ;
# ifdef DEBUG_MACINTS
printk ( " mac_init_IRQ(): Done! \n " ) ;
# endif
}
/*
* mac_enable_irq - enable an interrupt source
* mac_disable_irq - disable an interrupt source
* mac_clear_irq - clears a pending interrupt
* mac_pending_irq - Returns the pending status of an IRQ ( nonzero = pending )
*
* These routines are just dispatchers to the VIA / OSS / PSC routines .
*/
2006-06-25 16:47:04 +04:00
static void mac_enable_irq ( unsigned int irq )
2005-04-17 02:20:36 +04:00
{
2006-06-25 16:47:04 +04:00
int irq_src = IRQ_SRC ( irq ) ;
2005-04-17 02:20:36 +04:00
switch ( irq_src ) {
2006-06-25 16:47:04 +04:00
case 1 :
via_irq_enable ( irq ) ;
break ;
case 2 :
case 7 :
if ( oss_present )
oss_irq_enable ( irq ) ;
else
via_irq_enable ( irq ) ;
break ;
case 3 :
case 4 :
case 5 :
case 6 :
if ( psc_present )
psc_irq_enable ( irq ) ;
else if ( oss_present )
oss_irq_enable ( irq ) ;
else if ( irq_src = = 4 )
scc_irq_enable ( irq ) ;
break ;
case 8 :
if ( baboon_present )
baboon_irq_enable ( irq ) ;
break ;
2005-04-17 02:20:36 +04:00
}
}
2006-06-25 16:47:04 +04:00
static void mac_disable_irq ( unsigned int irq )
2005-04-17 02:20:36 +04:00
{
2006-06-25 16:47:04 +04:00
int irq_src = IRQ_SRC ( irq ) ;
2005-04-17 02:20:36 +04:00
switch ( irq_src ) {
2006-06-25 16:47:04 +04:00
case 1 :
via_irq_disable ( irq ) ;
break ;
case 2 :
case 7 :
if ( oss_present )
oss_irq_disable ( irq ) ;
else
via_irq_disable ( irq ) ;
break ;
case 3 :
case 4 :
case 5 :
case 6 :
if ( psc_present )
psc_irq_disable ( irq ) ;
else if ( oss_present )
oss_irq_disable ( irq ) ;
else if ( irq_src = = 4 )
scc_irq_disable ( irq ) ;
break ;
case 8 :
if ( baboon_present )
baboon_irq_disable ( irq ) ;
break ;
2005-04-17 02:20:36 +04:00
}
}
2006-06-25 16:47:04 +04:00
void mac_clear_irq ( unsigned int irq )
2005-04-17 02:20:36 +04:00
{
switch ( IRQ_SRC ( irq ) ) {
2006-06-25 16:47:04 +04:00
case 1 :
via_irq_clear ( irq ) ;
break ;
case 2 :
case 7 :
if ( oss_present )
oss_irq_clear ( irq ) ;
else
via_irq_clear ( irq ) ;
break ;
case 3 :
case 4 :
case 5 :
case 6 :
if ( psc_present )
psc_irq_clear ( irq ) ;
else if ( oss_present )
oss_irq_clear ( irq ) ;
break ;
case 8 :
if ( baboon_present )
baboon_irq_clear ( irq ) ;
break ;
2005-04-17 02:20:36 +04:00
}
}
2006-06-25 16:47:04 +04:00
int mac_irq_pending ( unsigned int irq )
2005-04-17 02:20:36 +04:00
{
switch ( IRQ_SRC ( irq ) ) {
2006-06-25 16:47:04 +04:00
case 1 :
return via_irq_pending ( irq ) ;
case 2 :
case 7 :
if ( oss_present )
return oss_irq_pending ( irq ) ;
else
return via_irq_pending ( irq ) ;
case 3 :
case 4 :
case 5 :
case 6 :
if ( psc_present )
return psc_irq_pending ( irq ) ;
else if ( oss_present )
return oss_irq_pending ( irq ) ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
}
2007-07-20 07:33:28 +04:00
EXPORT_SYMBOL ( mac_irq_pending ) ;
2005-04-17 02:20:36 +04:00
static int num_debug [ 8 ] ;
2006-10-07 17:16:45 +04:00
irqreturn_t mac_debug_handler ( int irq , void * dev_id )
2005-04-17 02:20:36 +04:00
{
if ( num_debug [ irq ] < 10 ) {
printk ( " DEBUG: Unexpected IRQ %d \n " , irq ) ;
num_debug [ irq ] + + ;
}
return IRQ_HANDLED ;
}
static int in_nmi ;
static volatile int nmi_hold ;
2006-10-07 17:16:45 +04:00
irqreturn_t mac_nmi_handler ( int irq , void * dev_id )
2005-04-17 02:20:36 +04:00
{
int i ;
/*
* generate debug output on NMI switch if ' debug ' kernel option given
* ( only works with Penguin ! )
*/
in_nmi + + ;
for ( i = 0 ; i < 100 ; i + + )
udelay ( 1000 ) ;
if ( in_nmi = = 1 ) {
nmi_hold = 1 ;
printk ( " ... pausing, press NMI to resume ... " ) ;
} else {
printk ( " ok! \n " ) ;
nmi_hold = 0 ;
}
barrier ( ) ;
while ( nmi_hold = = 1 )
udelay ( 1000 ) ;
2006-06-25 16:47:04 +04:00
if ( console_loglevel > = 8 ) {
2005-04-17 02:20:36 +04:00
#if 0
2006-10-07 17:16:45 +04:00
struct pt_regs * fp = get_irq_regs ( ) ;
2005-04-17 02:20:36 +04:00
show_state ( ) ;
printk ( " PC: %08lx \n SR: %04x SP: %p \n " , fp - > pc , fp - > sr , fp ) ;
printk ( " d0: %08lx d1: %08lx d2: %08lx d3: %08lx \n " ,
fp - > d0 , fp - > d1 , fp - > d2 , fp - > d3 ) ;
printk ( " d4: %08lx d5: %08lx a0: %08lx a1: %08lx \n " ,
fp - > d4 , fp - > d5 , fp - > a0 , fp - > a1 ) ;
if ( STACK_MAGIC ! = * ( unsigned long * ) current - > kernel_stack_page )
printk ( " Corrupted stack page \n " ) ;
printk ( " Process %s (pid: %d, stackpage=%08lx) \n " ,
current - > comm , current - > pid , current - > kernel_stack_page ) ;
if ( intr_count = = 1 )
dump_stack ( ( struct frame * ) fp ) ;
# else
/* printk("NMI "); */
# endif
}
in_nmi - - ;
return IRQ_HANDLED ;
}
/*
* Simple routines for masking and unmasking
* SCC interrupts in cases where this can ' t be
* done in hardware ( only the PSC can do that . )
*/
2006-06-25 16:47:04 +04:00
static void scc_irq_enable ( unsigned int irq )
{
int irq_idx = IRQ_IDX ( irq ) ;
2005-04-17 02:20:36 +04:00
scc_mask | = ( 1 < < irq_idx ) ;
}
2006-06-25 16:47:04 +04:00
static void scc_irq_disable ( unsigned int irq )
{
int irq_idx = IRQ_IDX ( irq ) ;
2005-04-17 02:20:36 +04:00
scc_mask & = ~ ( 1 < < irq_idx ) ;
}
/*
* SCC master interrupt handler . We have to do a bit of magic here
* to figure out what channel gave us the interrupt ; putting this
* here is cleaner than hacking it into drivers / char / macserial . c .
*/
2006-10-07 17:16:45 +04:00
void mac_scc_dispatch ( int irq , void * dev_id )
2005-04-17 02:20:36 +04:00
{
volatile unsigned char * scc = ( unsigned char * ) mac_bi_data . sccbase + 2 ;
unsigned char reg ;
unsigned long flags ;
/* Read RR3 from the chip. Always do this on channel A */
/* This must be an atomic operation so disable irqs. */
local_irq_save ( flags ) ;
* scc = 3 ;
reg = * scc ;
local_irq_restore ( flags ) ;
/* Now dispatch. Bits 0-2 are for channel B and */
/* bits 3-5 are for channel A. We can safely */
/* ignore the remaining bits here. */
/* */
/* Note that we're ignoring scc_mask for now. */
/* If we actually mask the ints then we tend to */
/* get hammered by very persistent SCC irqs, */
/* and since they're autovector interrupts they */
/* pretty much kill the system. */
2006-06-25 16:47:04 +04:00
if ( reg & 0x38 )
2006-10-07 17:16:45 +04:00
m68k_handle_int ( IRQ_SCCA ) ;
2006-06-25 16:47:04 +04:00
if ( reg & 0x07 )
2006-10-07 17:16:45 +04:00
m68k_handle_int ( IRQ_SCCB ) ;
2005-04-17 02:20:36 +04:00
}