2008-05-20 03:53:02 +04:00
/*
2011-01-29 01:08:21 +03:00
* SS1000 / SC2000 interrupt handling .
2005-04-17 02:20:36 +04:00
*
* Copyright ( C ) 1997 , 1998 Jakub Jelinek ( jj @ sunsite . mff . cuni . cz )
* Heavily based on arch / sparc / kernel / irq . c .
*/
# include <linux/kernel_stat.h>
# include <linux/seq_file.h>
2011-01-29 01:08:21 +03:00
2005-04-17 02:20:36 +04:00
# include <asm/timer.h>
# include <asm/traps.h>
# include <asm/irq.h>
# include <asm/io.h>
# include <asm/sbi.h>
# include <asm/cacheflush.h>
2011-04-21 08:20:23 +04:00
# include <asm/setup.h>
2005-04-17 02:20:36 +04:00
2008-12-08 12:08:24 +03:00
# include "kernel.h"
2007-07-22 06:18:57 +04:00
# include "irq.h"
2011-01-29 01:08:21 +03:00
/* Sun4d interrupts fall roughly into two categories. SBUS and
* cpu local . CPU local interrupts cover the timer interrupts
* and whatnot , and we encode those as normal PILs between
* 0 and 15.
2011-04-18 15:25:44 +04:00
* SBUS interrupts are encodes as a combination of board , level and slot .
2011-01-29 01:08:21 +03:00
*/
2011-04-18 15:25:44 +04:00
struct sun4d_handler_data {
unsigned int cpuid ; /* target cpu */
unsigned int real_irq ; /* interrupt level */
} ;
static unsigned int sun4d_encode_irq ( int board , int lvl , int slot )
{
return ( board + 1 ) < < 5 | ( lvl < < 2 ) | slot ;
}
2008-09-14 09:04:55 +04:00
struct sun4d_timer_regs {
u32 l10_timer_limit ;
u32 l10_cur_countx ;
u32 l10_limit_noclear ;
u32 ctrl ;
u32 l10_cur_count ;
} ;
static struct sun4d_timer_regs __iomem * sun4d_timers ;
2011-04-18 15:25:44 +04:00
# define SUN4D_TIMER_IRQ 10
2011-04-18 15:25:42 +04:00
/* Specify which cpu handle interrupts from which board.
* Index is board - value is cpu .
*/
static unsigned char board_to_cpu [ 32 ] ;
2005-04-17 02:20:36 +04:00
static int pil_to_sbus [ ] = {
2011-01-29 01:08:21 +03:00
0 ,
0 ,
1 ,
2 ,
0 ,
3 ,
0 ,
4 ,
0 ,
5 ,
0 ,
6 ,
0 ,
7 ,
0 ,
0 ,
2005-04-17 02:20:36 +04:00
} ;
2008-09-14 09:05:25 +04:00
/* Exported for sun4d_smp.c */
2005-04-17 02:20:36 +04:00
DEFINE_SPINLOCK ( sun4d_imsk_lock ) ;
2011-04-18 15:25:44 +04:00
/* SBUS interrupts are encoded integers including the board number
* ( plus one ) , the SBUS level , and the SBUS slot number . Sun4D
* IRQ dispatch is done by :
*
* 1 ) Reading the BW local interrupt table in order to get the bus
* interrupt mask .
*
* This table is indexed by SBUS interrupt level which can be
* derived from the PIL we got interrupted on .
*
* 2 ) For each bus showing interrupt pending from # 1 , read the
* SBI interrupt state register . This will indicate which slots
* have interrupts pending for that SBUS interrupt level .
*
* 3 ) Call the genreric IRQ support .
*/
static void sun4d_sbus_handler_irq ( int sbusl )
2005-04-17 02:20:36 +04:00
{
2011-04-18 15:25:44 +04:00
unsigned int bus_mask ;
unsigned int sbino , slot ;
unsigned int sbil ;
bus_mask = bw_get_intr_mask ( sbusl ) & 0x3ffff ;
bw_clear_intr_mask ( sbusl , bus_mask ) ;
sbil = ( sbusl < < 2 ) ;
/* Loop for each pending SBI */
for ( sbino = 0 ; bus_mask ; sbino + + ) {
unsigned int idx , mask ;
bus_mask > > = 1 ;
if ( ! ( bus_mask & 1 ) )
continue ;
/* XXX This seems to ACK the irq twice. acquire_sbi()
* XXX uses swap , therefore this writes 0xf < < sbil ,
* XXX then later release_sbi ( ) will write the individual
* XXX bits which were set again .
*/
mask = acquire_sbi ( SBI2DEVID ( sbino ) , 0xf < < sbil ) ;
mask & = ( 0xf < < sbil ) ;
/* Loop for each pending SBI slot */
idx = 0 ;
slot = ( 1 < < sbil ) ;
while ( mask ! = 0 ) {
unsigned int pil ;
struct irq_bucket * p ;
idx + + ;
slot < < = 1 ;
if ( ! ( mask & slot ) )
continue ;
mask & = ~ slot ;
pil = sun4d_encode_irq ( sbino , sbil , idx ) ;
p = irq_map [ pil ] ;
while ( p ) {
struct irq_bucket * next ;
next = p - > next ;
generic_handle_irq ( p - > irq ) ;
p = next ;
2005-04-17 02:20:36 +04:00
}
2011-04-18 15:25:44 +04:00
release_sbi ( SBI2DEVID ( sbino ) , slot ) ;
2005-04-17 02:20:36 +04:00
}
}
}
2011-01-29 01:08:21 +03:00
void sun4d_handler_irq ( int pil , struct pt_regs * regs )
2005-04-17 02:20:36 +04:00
{
2006-10-08 17:30:44 +04:00
struct pt_regs * old_regs ;
2005-04-17 02:20:36 +04:00
/* SBUS IRQ level (1 - 7) */
2011-01-22 14:32:15 +03:00
int sbusl = pil_to_sbus [ pil ] ;
2011-01-29 01:08:21 +03:00
2005-04-17 02:20:36 +04:00
/* FIXME: Is this necessary?? */
cc_get_ipen ( ) ;
2011-01-29 01:08:21 +03:00
2011-01-22 14:32:15 +03:00
cc_set_iclr ( 1 < < pil ) ;
2011-01-29 01:08:21 +03:00
2011-05-02 04:08:54 +04:00
# ifdef CONFIG_SMP
/*
* Check IPI data structures after IRQ has been cleared . Hard and Soft
* IRQ can happen at the same time , so both cases are always handled .
*/
if ( pil = = SUN4D_IPI_IRQ )
sun4d_ipi_interrupt ( ) ;
# endif
2006-10-08 17:30:44 +04:00
old_regs = set_irq_regs ( regs ) ;
2005-04-17 02:20:36 +04:00
irq_enter ( ) ;
2011-04-18 15:25:44 +04:00
if ( sbusl = = 0 ) {
/* cpu interrupt */
struct irq_bucket * p ;
p = irq_map [ pil ] ;
while ( p ) {
struct irq_bucket * next ;
next = p - > next ;
generic_handle_irq ( p - > irq ) ;
p = next ;
}
2005-04-17 02:20:36 +04:00
} else {
2011-04-18 15:25:44 +04:00
/* SBUS interrupt */
sun4d_sbus_handler_irq ( sbusl ) ;
2005-04-17 02:20:36 +04:00
}
irq_exit ( ) ;
2006-10-08 17:30:44 +04:00
set_irq_regs ( old_regs ) ;
2005-04-17 02:20:36 +04:00
}
2011-04-18 15:25:44 +04:00
static void sun4d_mask_irq ( struct irq_data * data )
2005-04-17 02:20:36 +04:00
{
2011-04-18 15:25:44 +04:00
struct sun4d_handler_data * handler_data = data - > handler_data ;
unsigned int real_irq ;
# ifdef CONFIG_SMP
int cpuid = handler_data - > cpuid ;
2005-04-17 02:20:36 +04:00
unsigned long flags ;
2011-04-18 15:25:44 +04:00
# endif
real_irq = handler_data - > real_irq ;
# ifdef CONFIG_SMP
spin_lock_irqsave ( & sun4d_imsk_lock , flags ) ;
cc_set_imsk_other ( cpuid , cc_get_imsk_other ( cpuid ) | ( 1 < < real_irq ) ) ;
spin_unlock_irqrestore ( & sun4d_imsk_lock , flags ) ;
# else
cc_set_imsk ( cc_get_imsk ( ) | ( 1 < < real_irq ) ) ;
# endif
2005-04-17 02:20:36 +04:00
}
2011-04-18 15:25:44 +04:00
static void sun4d_unmask_irq ( struct irq_data * data )
2005-04-17 02:20:36 +04:00
{
2011-04-18 15:25:44 +04:00
struct sun4d_handler_data * handler_data = data - > handler_data ;
unsigned int real_irq ;
# ifdef CONFIG_SMP
int cpuid = handler_data - > cpuid ;
2005-04-17 02:20:36 +04:00
unsigned long flags ;
2011-04-18 15:25:44 +04:00
# endif
real_irq = handler_data - > real_irq ;
2011-01-29 01:08:21 +03:00
2011-04-18 15:25:44 +04:00
# ifdef CONFIG_SMP
2005-04-17 02:20:36 +04:00
spin_lock_irqsave ( & sun4d_imsk_lock , flags ) ;
2011-04-18 15:25:44 +04:00
cc_set_imsk_other ( cpuid , cc_get_imsk_other ( cpuid ) | ~ ( 1 < < real_irq ) ) ;
2005-04-17 02:20:36 +04:00
spin_unlock_irqrestore ( & sun4d_imsk_lock , flags ) ;
2011-04-18 15:25:44 +04:00
# else
cc_set_imsk ( cc_get_imsk ( ) | ~ ( 1 < < real_irq ) ) ;
# endif
2005-04-17 02:20:36 +04:00
}
2011-04-18 15:25:44 +04:00
static unsigned int sun4d_startup_irq ( struct irq_data * data )
2005-04-17 02:20:36 +04:00
{
2011-04-18 15:25:44 +04:00
irq_link ( data - > irq ) ;
sun4d_unmask_irq ( data ) ;
return 0 ;
}
2008-09-14 09:05:25 +04:00
2011-04-18 15:25:44 +04:00
static void sun4d_shutdown_irq ( struct irq_data * data )
{
sun4d_mask_irq ( data ) ;
irq_unlink ( data - > irq ) ;
2005-04-17 02:20:36 +04:00
}
2011-04-18 15:25:44 +04:00
struct irq_chip sun4d_irq = {
. name = " sun4d " ,
. irq_startup = sun4d_startup_irq ,
. irq_shutdown = sun4d_shutdown_irq ,
. irq_unmask = sun4d_unmask_irq ,
. irq_mask = sun4d_mask_irq ,
} ;
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_SMP
static void sun4d_set_cpu_int ( int cpu , int level )
{
sun4d_send_ipi ( cpu , level ) ;
}
static void sun4d_clear_ipi ( int cpu , int level )
{
}
static void sun4d_set_udt ( int cpu )
{
}
/* Setup IRQ distribution scheme. */
void __init sun4d_distribute_irqs ( void )
{
2008-08-27 13:50:57 +04:00
struct device_node * dp ;
2005-04-17 02:20:36 +04:00
int cpuid = cpu_logical_map ( 1 ) ;
if ( cpuid = = - 1 )
cpuid = cpu_logical_map ( 0 ) ;
2008-08-27 13:50:57 +04:00
for_each_node_by_name ( dp , " sbi " ) {
int devid = of_getintprop_default ( dp , " device-id " , 0 ) ;
int board = of_getintprop_default ( dp , " board# " , 0 ) ;
2011-04-18 15:25:42 +04:00
board_to_cpu [ board ] = cpuid ;
2008-08-27 13:50:57 +04:00
set_sbi_tid ( devid , cpuid < < 3 ) ;
2005-04-17 02:20:36 +04:00
}
2011-01-29 01:08:21 +03:00
printk ( KERN_ERR " All sbus IRQs directed to CPU%d \n " , cpuid ) ;
2005-04-17 02:20:36 +04:00
}
# endif
2011-01-29 01:08:21 +03:00
2005-04-17 02:20:36 +04:00
static void sun4d_clear_clock_irq ( void )
{
2008-09-14 09:04:55 +04:00
sbus_readl ( & sun4d_timers - > l10_timer_limit ) ;
2005-04-17 02:20:36 +04:00
}
static void sun4d_load_profile_irq ( int cpu , unsigned int limit )
{
bw_set_prof_limit ( cpu , limit ) ;
}
2008-09-14 09:04:55 +04:00
static void __init sun4d_load_profile_irqs ( void )
2005-04-17 02:20:36 +04:00
{
2008-09-14 09:04:55 +04:00
int cpu = 0 , mid ;
2005-04-17 02:20:36 +04:00
2008-09-14 09:04:55 +04:00
while ( ! cpu_find_by_instance ( cpu , NULL , & mid ) ) {
sun4d_load_profile_irq ( mid > > 3 , 0 ) ;
cpu + + ;
}
}
2011-02-26 10:01:19 +03:00
unsigned int sun4d_build_device_irq ( struct platform_device * op ,
unsigned int real_irq )
{
struct device_node * dp = op - > dev . of_node ;
struct device_node * io_unit , * sbi = dp - > parent ;
const struct linux_prom_registers * regs ;
2011-04-18 15:25:44 +04:00
struct sun4d_handler_data * handler_data ;
unsigned int pil ;
unsigned int irq ;
2011-02-26 10:01:19 +03:00
int board , slot ;
int sbusl ;
2011-04-18 15:25:44 +04:00
irq = 0 ;
2011-02-26 10:01:19 +03:00
while ( sbi ) {
if ( ! strcmp ( sbi - > name , " sbi " ) )
break ;
sbi = sbi - > parent ;
}
if ( ! sbi )
goto err_out ;
regs = of_get_property ( dp , " reg " , NULL ) ;
if ( ! regs )
goto err_out ;
slot = regs - > which_io ;
/*
* If SBI ' s parent is not io - unit or the io - unit lacks
* a " board# " property , something is very wrong .
*/
if ( ! sbi - > parent | | strcmp ( sbi - > parent - > name , " io-unit " ) ) {
printk ( " %s: Error, parent is not io-unit. \n " , sbi - > full_name ) ;
goto err_out ;
}
io_unit = sbi - > parent ;
board = of_getintprop_default ( io_unit , " board# " , - 1 ) ;
if ( board = = - 1 ) {
printk ( " %s: Error, lacks board# property. \n " , io_unit - > full_name ) ;
goto err_out ;
}
sbusl = pil_to_sbus [ real_irq ] ;
if ( sbusl )
2011-04-18 15:25:44 +04:00
pil = sun4d_encode_irq ( board , sbusl , slot ) ;
else
pil = real_irq ;
irq = irq_alloc ( real_irq , pil ) ;
if ( irq = = 0 )
goto err_out ;
handler_data = irq_get_handler_data ( irq ) ;
if ( unlikely ( handler_data ) )
goto err_out ;
handler_data = kzalloc ( sizeof ( struct sun4d_handler_data ) , GFP_ATOMIC ) ;
if ( unlikely ( ! handler_data ) ) {
prom_printf ( " IRQ: kzalloc(sun4d_handler_data) failed. \n " ) ;
prom_halt ( ) ;
}
handler_data - > cpuid = board_to_cpu [ board ] ;
handler_data - > real_irq = real_irq ;
irq_set_chip_and_handler_name ( irq , & sun4d_irq ,
handle_level_irq , " level " ) ;
irq_set_handler_data ( irq , handler_data ) ;
2011-02-26 10:01:19 +03:00
err_out :
return real_irq ;
}
2008-09-14 09:04:55 +04:00
static void __init sun4d_fixup_trap_table ( void )
{
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_SMP
2008-09-14 09:04:55 +04:00
unsigned long flags ;
struct tt_entry * trap_table = & sparc_ttable [ SP_TRAP_IRQ1 + ( 14 - 1 ) ] ;
/* Adjust so that we jump directly to smp4d_ticker */
lvl14_save [ 2 ] + = smp4d_ticker - real_irq_entry ;
/* For SMP we use the level 14 ticker, however the bootup code
* has copied the firmware ' s level 14 vector into the boot cpu ' s
* trap table , we must fix this now or we get squashed .
*/
local_irq_save ( flags ) ;
patchme_maybe_smp_msg [ 0 ] = 0x01000000 ; /* NOP out the branch */
trap_table - > inst_one = lvl14_save [ 0 ] ;
trap_table - > inst_two = lvl14_save [ 1 ] ;
trap_table - > inst_three = lvl14_save [ 2 ] ;
trap_table - > inst_four = lvl14_save [ 3 ] ;
local_flush_cache_all ( ) ;
local_irq_restore ( flags ) ;
2005-04-17 02:20:36 +04:00
# endif
2008-09-14 09:04:55 +04:00
}
static void __init sun4d_init_timers ( irq_handler_t counter_fn )
{
struct device_node * dp ;
struct resource res ;
2011-04-18 15:25:44 +04:00
unsigned int irq ;
2008-09-14 09:04:55 +04:00
const u32 * reg ;
int err ;
dp = of_find_node_by_name ( NULL , " cpu-unit " ) ;
if ( ! dp ) {
prom_printf ( " sun4d_init_timers: Unable to find cpu-unit \n " ) ;
prom_halt ( ) ;
}
/* Which cpu-unit we use is arbitrary, we can view the bootbus timer
* registers via any cpu ' s mapping . The first ' reg ' property is the
* bootbus .
*/
reg = of_get_property ( dp , " reg " , NULL ) ;
2008-12-04 08:10:57 +03:00
of_node_put ( dp ) ;
2008-09-14 09:04:55 +04:00
if ( ! reg ) {
prom_printf ( " sun4d_init_timers: No reg property \n " ) ;
prom_halt ( ) ;
}
res . start = reg [ 1 ] ;
res . end = reg [ 2 ] - 1 ;
res . flags = reg [ 0 ] & 0xff ;
sun4d_timers = of_ioremap ( & res , BW_TIMER_LIMIT ,
sizeof ( struct sun4d_timer_regs ) , " user timer " ) ;
if ( ! sun4d_timers ) {
prom_printf ( " sun4d_init_timers: Can't map timer regs \n " ) ;
prom_halt ( ) ;
}
sbus_writel ( ( ( ( 1000000 / HZ ) + 1 ) < < 10 ) , & sun4d_timers - > l10_timer_limit ) ;
2005-04-17 02:20:36 +04:00
master_l10_counter = & sun4d_timers - > l10_cur_count ;
2011-04-18 15:25:44 +04:00
irq = sun4d_build_device_irq ( NULL , SUN4D_TIMER_IRQ ) ;
err = request_irq ( irq , counter_fn , IRQF_TIMER , " timer " , NULL ) ;
2008-09-14 09:04:55 +04:00
if ( err ) {
2011-01-29 01:08:21 +03:00
prom_printf ( " sun4d_init_timers: request_irq() failed with %d \n " ,
err ) ;
2005-04-17 02:20:36 +04:00
prom_halt ( ) ;
}
2008-09-14 09:04:55 +04:00
sun4d_load_profile_irqs ( ) ;
sun4d_fixup_trap_table ( ) ;
2005-04-17 02:20:36 +04:00
}
void __init sun4d_init_sbi_irq ( void )
{
2008-08-27 13:50:57 +04:00
struct device_node * dp ;
2011-04-21 08:20:23 +04:00
int target_cpu ;
2008-09-14 09:05:25 +04:00
target_cpu = boot_cpu_id ;
2008-08-27 13:50:57 +04:00
for_each_node_by_name ( dp , " sbi " ) {
int devid = of_getintprop_default ( dp , " device-id " , 0 ) ;
int board = of_getintprop_default ( dp , " board# " , 0 ) ;
unsigned int mask ;
2008-09-14 09:05:25 +04:00
set_sbi_tid ( devid , target_cpu < < 3 ) ;
2011-04-18 15:25:42 +04:00
board_to_cpu [ board ] = target_cpu ;
2008-09-14 09:05:25 +04:00
2005-04-17 02:20:36 +04:00
/* Get rid of pending irqs from PROM */
2008-08-27 13:50:57 +04:00
mask = acquire_sbi ( devid , 0xffffffff ) ;
2005-04-17 02:20:36 +04:00
if ( mask ) {
2011-01-29 01:08:21 +03:00
printk ( KERN_ERR " Clearing pending IRQs %08x on SBI %d \n " ,
mask , board ) ;
2008-08-27 13:50:57 +04:00
release_sbi ( devid , mask ) ;
2005-04-17 02:20:36 +04:00
}
}
}
void __init sun4d_init_IRQ ( void )
{
local_irq_disable ( ) ;
BTFIXUPSET_CALL ( clear_clock_irq , sun4d_clear_clock_irq , BTFIXUPCALL_NORM ) ;
BTFIXUPSET_CALL ( load_profile_irq , sun4d_load_profile_irq , BTFIXUPCALL_NORM ) ;
2011-02-26 10:00:19 +03:00
2011-04-18 15:25:44 +04:00
sparc_irq_config . init_timers = sun4d_init_timers ;
2011-02-26 10:01:19 +03:00
sparc_irq_config . build_device_irq = sun4d_build_device_irq ;
2011-02-26 10:00:19 +03:00
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_SMP
BTFIXUPSET_CALL ( set_cpu_int , sun4d_set_cpu_int , BTFIXUPCALL_NORM ) ;
BTFIXUPSET_CALL ( clear_cpu_int , sun4d_clear_ipi , BTFIXUPCALL_NOP ) ;
BTFIXUPSET_CALL ( set_irq_udt , sun4d_set_udt , BTFIXUPCALL_NOP ) ;
# endif
/* Cannot enable interrupts until OBP ticker is disabled. */
}