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>
2012-05-14 07:49:31 +04:00
# include <asm/oplib.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 */
2011-06-01 15:04:20 +04:00
for ( sbino = 0 ; bus_mask ; sbino + + , bus_mask > > = 1 ) {
2011-04-18 15:25:44 +04:00
unsigned int idx , mask ;
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 */
slot = ( 1 < < sbil ) ;
2011-06-01 15:04:20 +04:00
for ( idx = 0 ; mask ! = 0 ; idx + + , slot < < = 1 ) {
2011-04-18 15:25:44 +04:00
unsigned int pil ;
struct irq_bucket * p ;
if ( ! ( mask & slot ) )
continue ;
mask & = ~ slot ;
2011-06-01 15:04:20 +04:00
pil = sun4d_encode_irq ( sbino , sbusl , idx ) ;
2011-04-18 15:25:44 +04:00
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-06-01 15:04:20 +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
2011-06-01 15:04:20 +04:00
cc_set_imsk ( cc_get_imsk ( ) & ~ ( 1 < < real_irq ) ) ;
2011-04-18 15:25:44 +04:00
# 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
/* 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 )
{
2012-04-04 23:49:26 +04:00
unsigned int value = limit ? timer_value ( limit ) : 0 ;
bw_set_prof_limit ( cpu , value ) ;
2005-04-17 02:20:36 +04:00
}
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-06-01 14:43:50 +04:00
unsigned int _sun4d_build_device_irq ( unsigned int real_irq ,
unsigned int pil ,
unsigned int board )
{
struct sun4d_handler_data * handler_data ;
unsigned int irq ;
irq = irq_alloc ( real_irq , pil ) ;
if ( irq = = 0 ) {
prom_printf ( " IRQ: allocate for %d %d %d failed \n " ,
real_irq , pil , board ) ;
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 ) ;
err_out :
return irq ;
}
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 ;
2011-06-01 15:11:41 +04:00
struct device_node * board_parent , * bus = dp - > parent ;
char * bus_connection ;
2011-02-26 10:01:19 +03:00
const struct linux_prom_registers * regs ;
2011-04-18 15:25:44 +04:00
unsigned int pil ;
unsigned int irq ;
2011-02-26 10:01:19 +03:00
int board , slot ;
int sbusl ;
2011-06-01 14:43:50 +04:00
irq = real_irq ;
2011-06-01 15:11:41 +04:00
while ( bus ) {
if ( ! strcmp ( bus - > name , " sbi " ) ) {
bus_connection = " io-unit " ;
2011-02-26 10:01:19 +03:00
break ;
2011-06-01 15:11:41 +04:00
}
if ( ! strcmp ( bus - > name , " bootbus " ) ) {
bus_connection = " cpu-unit " ;
break ;
}
2011-02-26 10:01:19 +03:00
2011-06-01 15:11:41 +04:00
bus = bus - > parent ;
2011-02-26 10:01:19 +03:00
}
2011-06-01 15:11:41 +04:00
if ( ! bus )
2011-02-26 10:01:19 +03:00
goto err_out ;
regs = of_get_property ( dp , " reg " , NULL ) ;
if ( ! regs )
goto err_out ;
slot = regs - > which_io ;
/*
2011-06-01 15:11:41 +04:00
* If Bus nodes parent is not io - unit / cpu - unit or the io - unit / cpu - unit
* lacks a " board# " property , something is very wrong .
2011-02-26 10:01:19 +03:00
*/
2011-06-01 15:11:41 +04:00
if ( ! bus - > parent | | strcmp ( bus - > parent - > name , bus_connection ) ) {
printk ( KERN_ERR " %s: Error, parent is not %s. \n " ,
bus - > full_name , bus_connection ) ;
2011-02-26 10:01:19 +03:00
goto err_out ;
}
2011-06-01 15:11:41 +04:00
board_parent = bus - > parent ;
board = of_getintprop_default ( board_parent , " board# " , - 1 ) ;
2011-02-26 10:01:19 +03:00
if ( board = = - 1 ) {
2011-06-01 15:11:41 +04:00
printk ( KERN_ERR " %s: Error, lacks board# property. \n " ,
board_parent - > full_name ) ;
2011-02-26 10:01:19 +03:00
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 ;
2011-06-01 14:43:50 +04:00
irq = _sun4d_build_device_irq ( real_irq , pil , board ) ;
2011-02-26 10:01:19 +03:00
err_out :
2011-06-01 14:43:50 +04:00
return irq ;
2011-02-26 10:01:19 +03:00
}
2011-06-01 14:43:50 +04:00
unsigned int sun4d_build_timer_irq ( unsigned int board , unsigned int real_irq )
{
return _sun4d_build_device_irq ( real_irq , real_irq , board ) ;
}
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 ] ;
2012-05-14 07:49:31 +04:00
local_ops - > cache_all ( ) ;
2008-09-14 09:04:55 +04:00
local_irq_restore ( flags ) ;
2005-04-17 02:20:36 +04:00
# endif
2008-09-14 09:04:55 +04:00
}
2012-04-04 23:49:26 +04:00
static void __init sun4d_init_timers ( void )
2008-09-14 09:04:55 +04:00
{
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 ;
2011-06-01 14:43:50 +04:00
int board ;
2008-09-14 09:04:55 +04:00
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 ) ;
if ( ! reg ) {
prom_printf ( " sun4d_init_timers: No reg property \n " ) ;
prom_halt ( ) ;
}
2011-06-01 14:43:50 +04:00
board = of_getintprop_default ( dp , " board# " , - 1 ) ;
if ( board = = - 1 ) {
prom_printf ( " sun4d_init_timers: No board# property on cpu-unit \n " ) ;
prom_halt ( ) ;
}
of_node_put ( dp ) ;
2008-09-14 09:04:55 +04:00
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 ( ) ;
}
2012-04-04 23:49:26 +04:00
# ifdef CONFIG_SMP
sparc_config . cs_period = SBUS_CLOCK_RATE * 2 ; /* 2 seconds */
# else
sparc_config . cs_period = SBUS_CLOCK_RATE / HZ ; /* 1/HZ sec */
sparc_config . features | = FEAT_L10_CLOCKEVENT ;
# endif
sparc_config . features | = FEAT_L10_CLOCKSOURCE ;
sbus_writel ( timer_value ( sparc_config . cs_period ) ,
& sun4d_timers - > l10_timer_limit ) ;
2005-04-17 02:20:36 +04:00
master_l10_counter = & sun4d_timers - > l10_cur_count ;
2011-06-01 14:43:50 +04:00
irq = sun4d_build_timer_irq ( board , SUN4D_TIMER_IRQ ) ;
2012-04-04 23:49:26 +04:00
err = request_irq ( irq , timer_interrupt , 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 ( ) ;
2012-04-04 15:21:13 +04:00
sparc_config . init_timers = sun4d_init_timers ;
sparc_config . build_device_irq = sun4d_build_device_irq ;
2012-04-04 23:49:26 +04:00
sparc_config . clock_rate = SBUS_CLOCK_RATE ;
2012-05-14 19:30:35 +04:00
sparc_config . clear_clock_irq = sun4d_clear_clock_irq ;
sparc_config . load_profile_irq = sun4d_load_profile_irq ;
2011-02-26 10:00:19 +03:00
2005-04-17 02:20:36 +04:00
/* Cannot enable interrupts until OBP ticker is disabled. */
}