2005-04-17 02:20:36 +04:00
/*
* Carsten Langgaard , carstenl @ mips . com
* Copyright ( C ) 2000 , 2001 , 2004 MIPS Technologies , Inc .
* Copyright ( C ) 2001 Ralf Baechle
*
* This program is free software ; you can distribute it and / or modify it
* under the terms of the GNU General Public License ( Version 2 ) as
* published by the Free Software Foundation .
*
* This program is distributed in the hope it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License
* for more details .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 59 Temple Place - Suite 330 , Boston MA 02111 - 1307 , USA .
*
* Routines for generic manipulation of the interrupts found on the MIPS
* Malta board .
* The interrupt controller is located in the South Bridge a PIIX4 device
* with two internal 82 C95 interrupt controllers .
*/
# include <linux/init.h>
# include <linux/irq.h>
# include <linux/sched.h>
# include <linux/slab.h>
# include <linux/interrupt.h>
# include <linux/kernel_stat.h>
2007-02-05 05:42:11 +03:00
# include <linux/kernel.h>
2005-04-17 02:20:36 +04:00
# include <linux/random.h>
# include <asm/i8259.h>
2005-07-14 19:57:16 +04:00
# include <asm/irq_cpu.h>
2005-04-17 02:20:36 +04:00
# include <asm/io.h>
2006-10-15 12:17:43 +04:00
# include <asm/irq_regs.h>
2005-04-17 02:20:36 +04:00
# include <asm/mips-boards/malta.h>
# include <asm/mips-boards/maltaint.h>
# include <asm/mips-boards/piix4.h>
# include <asm/gt64120.h>
# include <asm/mips-boards/generic.h>
# include <asm/mips-boards/msc01_pci.h>
2005-07-14 19:57:16 +04:00
# include <asm/msc01_ic.h>
2005-04-17 02:20:36 +04:00
static DEFINE_SPINLOCK ( mips_irq_lock ) ;
static inline int mips_pcibios_iack ( void )
{
int irq ;
u32 dummy ;
/*
* Determine highest priority pending interrupt by performing
* a PCI Interrupt Acknowledge cycle .
*/
2007-04-27 18:58:41 +04:00
switch ( mips_revision_sconid ) {
case MIPS_REVISION_SCON_SOCIT :
case MIPS_REVISION_SCON_ROCIT :
case MIPS_REVISION_SCON_SOCITSC :
case MIPS_REVISION_SCON_SOCITSCP :
2005-04-17 02:20:36 +04:00
MSC_READ ( MSC01_PCI_IACK , irq ) ;
irq & = 0xff ;
break ;
2007-04-27 18:58:41 +04:00
case MIPS_REVISION_SCON_GT64120 :
2005-04-17 02:20:36 +04:00
irq = GT_READ ( GT_PCI0_IACK_OFS ) ;
irq & = 0xff ;
break ;
2007-04-27 18:58:41 +04:00
case MIPS_REVISION_SCON_BONITO :
2005-04-17 02:20:36 +04:00
/* The following will generate a PCI IACK cycle on the
* Bonito controller . It ' s a little bit kludgy , but it
* was the easiest way to implement it in hardware at
* the given time .
*/
BONITO_PCIMAP_CFG = 0x20000 ;
/* Flush Bonito register block */
dummy = BONITO_PCIMAP_CFG ;
iob ( ) ; /* sync */
2007-04-26 18:46:24 +04:00
irq = readl ( ( u32 * ) _pcictrl_bonito_pcicfg ) ;
2005-04-17 02:20:36 +04:00
iob ( ) ; /* sync */
irq & = 0xff ;
BONITO_PCIMAP_CFG = 0 ;
break ;
default :
2007-04-27 18:58:41 +04:00
printk ( " Unknown system controller. \n " ) ;
2005-04-17 02:20:36 +04:00
return - 1 ;
}
return irq ;
}
2005-07-14 19:57:16 +04:00
static inline int get_int ( void )
2005-04-17 02:20:36 +04:00
{
unsigned long flags ;
2005-07-14 19:57:16 +04:00
int irq ;
2005-04-17 02:20:36 +04:00
spin_lock_irqsave ( & mips_irq_lock , flags ) ;
2005-07-14 19:57:16 +04:00
irq = mips_pcibios_iack ( ) ;
2005-04-17 02:20:36 +04:00
/*
2005-08-16 19:44:06 +04:00
* The only way we can decide if an interrupt is spurious
* is by checking the 8259 registers . This needs a spinlock
* on an SMP system , so leave it up to the generic code . . .
2005-04-17 02:20:36 +04:00
*/
spin_unlock_irqrestore ( & mips_irq_lock , flags ) ;
2005-07-14 19:57:16 +04:00
return irq ;
2005-04-17 02:20:36 +04:00
}
2006-10-07 22:44:33 +04:00
static void malta_hw0_irqdispatch ( void )
2005-04-17 02:20:36 +04:00
{
int irq ;
2005-07-14 19:57:16 +04:00
irq = get_int ( ) ;
2006-04-05 12:45:45 +04:00
if ( irq < 0 ) {
2005-07-14 19:57:16 +04:00
return ; /* interrupt has already been cleared */
2006-04-05 12:45:45 +04:00
}
2005-04-17 02:20:36 +04:00
2006-10-07 22:44:33 +04:00
do_IRQ ( MALTA_INT_BASE + irq ) ;
2005-04-17 02:20:36 +04:00
}
2006-10-07 22:44:33 +04:00
static void corehi_irqdispatch ( void )
2005-04-17 02:20:36 +04:00
{
2006-10-07 22:44:33 +04:00
unsigned int intedge , intsteer , pcicmd , pcibadaddr ;
unsigned int pcimstat , intisr , inten , intpol ;
2005-07-14 19:57:16 +04:00
unsigned int intrcause , datalo , datahi ;
2006-10-15 12:17:43 +04:00
struct pt_regs * regs = get_irq_regs ( ) ;
2005-04-17 02:20:36 +04:00
printk ( " CoreHI interrupt, shouldn't happen, so we die here!!! \n " ) ;
2006-10-07 22:44:33 +04:00
printk ( " epc : %08lx \n Status: %08lx \n "
" Cause : %08lx \n badVaddr : %08lx \n " ,
regs - > cp0_epc , regs - > cp0_status ,
regs - > cp0_cause , regs - > cp0_badvaddr ) ;
2005-07-14 19:57:16 +04:00
/* Read all the registers and then print them as there is a
problem with interspersed printk ' s upsetting the Bonito controller .
Do it for the others too .
*/
2007-04-27 18:58:41 +04:00
switch ( mips_revision_sconid ) {
case MIPS_REVISION_SCON_SOCIT :
case MIPS_REVISION_SCON_ROCIT :
case MIPS_REVISION_SCON_SOCITSC :
case MIPS_REVISION_SCON_SOCITSCP :
2006-10-07 22:44:33 +04:00
ll_msc_irq ( ) ;
2005-04-17 02:20:36 +04:00
break ;
2007-04-27 18:58:41 +04:00
case MIPS_REVISION_SCON_GT64120 :
2005-07-14 19:57:16 +04:00
intrcause = GT_READ ( GT_INTRCAUSE_OFS ) ;
datalo = GT_READ ( GT_CPUERR_ADDRLO_OFS ) ;
2005-04-17 02:20:36 +04:00
datahi = GT_READ ( GT_CPUERR_ADDRHI_OFS ) ;
2005-07-14 19:57:16 +04:00
printk ( " GT_INTRCAUSE = %08x \n " , intrcause ) ;
printk ( " GT_CPUERR_ADDR = %02x%08x \n " , datahi , datalo ) ;
2005-04-17 02:20:36 +04:00
break ;
2007-04-27 18:58:41 +04:00
case MIPS_REVISION_SCON_BONITO :
2005-07-14 19:57:16 +04:00
pcibadaddr = BONITO_PCIBADADDR ;
pcimstat = BONITO_PCIMSTAT ;
intisr = BONITO_INTISR ;
inten = BONITO_INTEN ;
intpol = BONITO_INTPOL ;
intedge = BONITO_INTEDGE ;
intsteer = BONITO_INTSTEER ;
pcicmd = BONITO_PCICMD ;
printk ( " BONITO_INTISR = %08x \n " , intisr ) ;
printk ( " BONITO_INTEN = %08x \n " , inten ) ;
printk ( " BONITO_INTPOL = %08x \n " , intpol ) ;
printk ( " BONITO_INTEDGE = %08x \n " , intedge ) ;
printk ( " BONITO_INTSTEER = %08x \n " , intsteer ) ;
printk ( " BONITO_PCICMD = %08x \n " , pcicmd ) ;
printk ( " BONITO_PCIBADADDR = %08x \n " , pcibadaddr ) ;
printk ( " BONITO_PCIMSTAT = %08x \n " , pcimstat ) ;
2005-04-17 02:20:36 +04:00
break ;
}
/* We die here*/
die ( " CoreHi interrupt " , regs ) ;
}
2006-04-03 20:56:36 +04:00
static inline int clz ( unsigned long x )
{
2007-10-12 02:46:15 +04:00
__asm__ (
2006-04-03 20:56:36 +04:00
" .set push \n "
" .set mips32 \n "
" clz %0, %1 \n "
" .set pop \n "
: " =r " ( x )
: " r " ( x ) ) ;
return x ;
}
/*
* Version of ffs that only looks at bits 12. .15 .
*/
static inline unsigned int irq_ffs ( unsigned int pending )
{
# if defined(CONFIG_CPU_MIPS32) || defined(CONFIG_CPU_MIPS64)
return - clz ( pending ) + 31 - CAUSEB_IP ;
# else
unsigned int a0 = 7 ;
unsigned int t0 ;
2006-06-05 14:54:41 +04:00
t0 = pending & 0xf000 ;
2006-04-03 20:56:36 +04:00
t0 = t0 < 1 ;
t0 = t0 < < 2 ;
a0 = a0 - t0 ;
2006-06-05 14:54:41 +04:00
pending = pending < < t0 ;
2006-04-03 20:56:36 +04:00
2006-06-05 14:54:41 +04:00
t0 = pending & 0xc000 ;
2006-04-03 20:56:36 +04:00
t0 = t0 < 1 ;
t0 = t0 < < 1 ;
a0 = a0 - t0 ;
2006-06-05 14:54:41 +04:00
pending = pending < < t0 ;
2006-04-03 20:56:36 +04:00
2006-06-05 14:54:41 +04:00
t0 = pending & 0x8000 ;
2006-04-03 20:56:36 +04:00
t0 = t0 < 1 ;
//t0 = t0 << 2;
a0 = a0 - t0 ;
2006-06-05 14:54:41 +04:00
//pending = pending << t0;
2006-04-03 20:56:36 +04:00
return a0 ;
# endif
}
/*
* IRQs on the Malta board look basically ( barring software IRQs which we
* don ' t use at all and all external interrupt sources are combined together
* on hardware interrupt 0 ( MIPS IRQ 2 ) ) like :
*
* MIPS IRQ Source
* - - - - - - - - - - - - - -
* 0 Software ( ignored )
* 1 Software ( ignored )
* 2 Combined hardware interrupt ( hw0 )
* 3 Hardware ( ignored )
* 4 Hardware ( ignored )
* 5 Hardware ( ignored )
* 6 Hardware ( ignored )
* 7 R4k timer ( what we use )
*
* We handle the IRQ according to _our_ priority which is :
*
* Highest - - - - R4k Timer
* Lowest - - - - Combined hardware interrupt
*
* then we just return , if multiple IRQs are pending then we will just take
* another exception , big deal .
*/
2006-10-07 22:44:33 +04:00
asmlinkage void plat_irq_dispatch ( void )
2006-04-03 20:56:36 +04:00
{
unsigned int pending = read_c0_cause ( ) & read_c0_status ( ) & ST0_IM ;
int irq ;
irq = irq_ffs ( pending ) ;
if ( irq = = MIPSCPU_INT_I8259A )
2006-10-07 22:44:33 +04:00
malta_hw0_irqdispatch ( ) ;
2007-09-13 20:36:22 +04:00
else if ( irq > = 0 )
2007-06-21 01:27:10 +04:00
do_IRQ ( MIPS_CPU_IRQ_BASE + irq ) ;
2006-04-03 20:56:36 +04:00
else
2006-10-07 22:44:33 +04:00
spurious_interrupt ( ) ;
2006-04-03 20:56:36 +04:00
}
2005-07-14 19:57:16 +04:00
static struct irqaction i8259irq = {
. handler = no_action ,
. name = " XT-PIC cascade "
} ;
static struct irqaction corehi_irqaction = {
. handler = no_action ,
. name = " CoreHi "
} ;
msc_irqmap_t __initdata msc_irqmap [ ] = {
{ MSC01C_INT_TMR , MSC01_IRQ_EDGE , 0 } ,
{ MSC01C_INT_PCI , MSC01_IRQ_LEVEL , 0 } ,
} ;
2007-02-05 05:42:11 +03:00
int __initdata msc_nr_irqs = ARRAY_SIZE ( msc_irqmap ) ;
2005-07-14 19:57:16 +04:00
msc_irqmap_t __initdata msc_eicirqmap [ ] = {
{ MSC01E_INT_SW0 , MSC01_IRQ_LEVEL , 0 } ,
{ MSC01E_INT_SW1 , MSC01_IRQ_LEVEL , 0 } ,
{ MSC01E_INT_I8259A , MSC01_IRQ_LEVEL , 0 } ,
{ MSC01E_INT_SMI , MSC01_IRQ_LEVEL , 0 } ,
{ MSC01E_INT_COREHI , MSC01_IRQ_LEVEL , 0 } ,
{ MSC01E_INT_CORELO , MSC01_IRQ_LEVEL , 0 } ,
{ MSC01E_INT_TMR , MSC01_IRQ_EDGE , 0 } ,
{ MSC01E_INT_PCI , MSC01_IRQ_LEVEL , 0 } ,
{ MSC01E_INT_PERFCTR , MSC01_IRQ_LEVEL , 0 } ,
{ MSC01E_INT_CPUCTR , MSC01_IRQ_LEVEL , 0 }
} ;
2007-02-05 05:42:11 +03:00
int __initdata msc_nr_eicirqs = ARRAY_SIZE ( msc_eicirqmap ) ;
2005-07-14 19:57:16 +04:00
2005-04-17 02:20:36 +04:00
void __init arch_init_irq ( void )
{
init_i8259_irqs ( ) ;
2005-07-14 19:57:16 +04:00
if ( ! cpu_has_veic )
2007-01-07 20:14:29 +03:00
mips_cpu_irq_init ( ) ;
2005-07-14 19:57:16 +04:00
2007-05-08 17:05:39 +04:00
switch ( mips_revision_sconid ) {
case MIPS_REVISION_SCON_SOCIT :
case MIPS_REVISION_SCON_ROCIT :
if ( cpu_has_veic )
2007-10-12 02:46:15 +04:00
init_msc_irqs ( MIPS_MSC01_IC_REG_BASE , MSC01E_INT_BASE , msc_eicirqmap , msc_nr_eicirqs ) ;
2007-05-08 17:05:39 +04:00
else
2007-10-12 02:46:15 +04:00
init_msc_irqs ( MIPS_MSC01_IC_REG_BASE , MSC01C_INT_BASE , msc_irqmap , msc_nr_irqs ) ;
2007-05-08 17:05:39 +04:00
break ;
case MIPS_REVISION_SCON_SOCITSC :
case MIPS_REVISION_SCON_SOCITSCP :
2005-07-14 19:57:16 +04:00
if ( cpu_has_veic )
2007-10-12 02:46:15 +04:00
init_msc_irqs ( MIPS_SOCITSC_IC_REG_BASE , MSC01E_INT_BASE , msc_eicirqmap , msc_nr_eicirqs ) ;
2005-07-14 19:57:16 +04:00
else
2007-10-12 02:46:15 +04:00
init_msc_irqs ( MIPS_SOCITSC_IC_REG_BASE , MSC01C_INT_BASE , msc_irqmap , msc_nr_irqs ) ;
2005-07-14 19:57:16 +04:00
}
if ( cpu_has_veic ) {
2007-10-12 02:46:15 +04:00
set_vi_handler ( MSC01E_INT_I8259A , malta_hw0_irqdispatch ) ;
set_vi_handler ( MSC01E_INT_COREHI , corehi_irqdispatch ) ;
setup_irq ( MSC01E_INT_BASE + MSC01E_INT_I8259A , & i8259irq ) ;
setup_irq ( MSC01E_INT_BASE + MSC01E_INT_COREHI , & corehi_irqaction ) ;
2005-07-14 19:57:16 +04:00
}
else if ( cpu_has_vint ) {
2007-10-12 02:46:15 +04:00
set_vi_handler ( MIPSCPU_INT_I8259A , malta_hw0_irqdispatch ) ;
set_vi_handler ( MIPSCPU_INT_COREHI , corehi_irqdispatch ) ;
2006-04-05 12:45:45 +04:00
# ifdef CONFIG_MIPS_MT_SMTC
2007-10-12 02:46:15 +04:00
setup_irq_smtc ( MIPS_CPU_IRQ_BASE + MIPSCPU_INT_I8259A , & i8259irq ,
2006-04-05 12:45:45 +04:00
( 0x100 < < MIPSCPU_INT_I8259A ) ) ;
2007-10-12 02:46:15 +04:00
setup_irq_smtc ( MIPS_CPU_IRQ_BASE + MIPSCPU_INT_COREHI ,
2006-04-05 12:45:45 +04:00
& corehi_irqaction , ( 0x100 < < MIPSCPU_INT_COREHI ) ) ;
2007-07-27 21:45:25 +04:00
/*
* Temporary hack to ensure that the subsidiary device
* interrupts coing in via the i8259A , but associated
* with low IRQ numbers , will restore the Status . IM
* value associated with the i8259A .
*/
{
int i ;
for ( i = 0 ; i < 16 ; i + + )
irq_hwmask [ i ] = ( 0x100 < < MIPSCPU_INT_I8259A ) ;
}
2006-04-05 12:45:45 +04:00
# else /* Not SMTC */
2007-10-12 02:46:15 +04:00
setup_irq ( MIPS_CPU_IRQ_BASE + MIPSCPU_INT_I8259A , & i8259irq ) ;
setup_irq ( MIPS_CPU_IRQ_BASE + MIPSCPU_INT_COREHI , & corehi_irqaction ) ;
2006-04-05 12:45:45 +04:00
# endif /* CONFIG_MIPS_MT_SMTC */
2005-07-14 19:57:16 +04:00
}
else {
2007-10-12 02:46:15 +04:00
setup_irq ( MIPS_CPU_IRQ_BASE + MIPSCPU_INT_I8259A , & i8259irq ) ;
setup_irq ( MIPS_CPU_IRQ_BASE + MIPSCPU_INT_COREHI , & corehi_irqaction ) ;
2005-07-14 19:57:16 +04:00
}
2005-04-17 02:20:36 +04:00
}