2006-09-06 23:42:08 +04:00
/*
2007-02-05 01:36:55 +03:00
* Copyright ( C ) 2006 - 2007 PA Semi , Inc
2006-09-06 23:42:08 +04:00
*
* Authors : Kip Walker , PA Semi
* Olof Johansson , PA Semi
*
* Maintained by : Olof Johansson < olof @ lixom . net >
*
* Based on arch / powerpc / platforms / maple / setup . c
*
* This program is free software ; you can redistribute 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 that 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
*/
# include <linux/errno.h>
# include <linux/kernel.h>
# include <linux/delay.h>
# include <linux/console.h>
2006-11-11 09:25:02 +03:00
# include <linux/pci.h>
2007-11-13 20:13:03 +03:00
# include <linux/of_platform.h>
2006-09-06 23:42:08 +04:00
# include <asm/prom.h>
# include <asm/system.h>
# include <asm/iommu.h>
# include <asm/machdep.h>
# include <asm/mpic.h>
# include <asm/smp.h>
# include <asm/time.h>
2007-12-06 09:24:48 +03:00
# include <asm/mmu.h>
2006-09-06 23:42:08 +04:00
2007-10-17 10:26:20 +04:00
# include <pcmcia/ss.h>
# include <pcmcia/cistpl.h>
# include <pcmcia/ds.h>
2006-09-06 23:42:08 +04:00
# include "pasemi.h"
2007-09-05 06:09:23 +04:00
/* SDC reset register, must be pre-mapped at reset time */
2007-02-05 01:36:52 +03:00
static void __iomem * reset_reg ;
2006-09-06 23:42:08 +04:00
2007-09-05 06:09:23 +04:00
/* Various error status registers, must be pre-mapped at MCE time */
# define MAX_MCE_REGS 32
struct mce_regs {
char * name ;
void __iomem * addr ;
} ;
static struct mce_regs mce_regs [ MAX_MCE_REGS ] ;
static int num_mce_regs ;
2007-12-20 22:11:18 +03:00
static int nmi_virq = NO_IRQ ;
2007-09-05 06:09:23 +04:00
2007-02-05 01:36:52 +03:00
static void pas_restart ( char * cmd )
2006-09-06 23:42:08 +04:00
{
2007-11-07 18:31:06 +03:00
/* Need to put others cpu in hold loop so they're not sleeping */
smp_send_stop ( ) ;
udelay ( 10000 ) ;
2007-02-05 01:36:52 +03:00
printk ( " Restarting... \n " ) ;
while ( 1 )
out_le32 ( reset_reg , 0x6000000 ) ;
2006-09-06 23:42:08 +04:00
}
# ifdef CONFIG_SMP
2009-12-02 21:49:50 +03:00
static arch_spinlock_t timebase_lock ;
2007-08-22 06:26:43 +04:00
static unsigned long timebase ;
2007-02-05 01:36:53 +03:00
static void __devinit pas_give_timebase ( void )
{
2009-06-16 20:42:51 +04:00
unsigned long flags ;
local_irq_save ( flags ) ;
hard_irq_disable ( ) ;
2009-12-02 22:01:25 +03:00
arch_spin_lock ( & timebase_lock ) ;
2007-02-05 01:36:53 +03:00
mtspr ( SPRN_TBCTL , TBCTL_FREEZE ) ;
2007-08-22 06:26:43 +04:00
isync ( ) ;
timebase = get_tb ( ) ;
2009-12-02 22:01:25 +03:00
arch_spin_unlock ( & timebase_lock ) ;
2007-08-22 06:26:43 +04:00
while ( timebase )
barrier ( ) ;
mtspr ( SPRN_TBCTL , TBCTL_RESTART ) ;
2009-06-16 20:42:51 +04:00
local_irq_restore ( flags ) ;
2007-02-05 01:36:53 +03:00
}
static void __devinit pas_take_timebase ( void )
{
2007-08-22 06:26:43 +04:00
while ( ! timebase )
smp_rmb ( ) ;
2009-12-02 22:01:25 +03:00
arch_spin_lock ( & timebase_lock ) ;
2007-08-22 06:26:43 +04:00
set_tb ( timebase > > 32 , timebase & 0xffffffff ) ;
timebase = 0 ;
2009-12-02 22:01:25 +03:00
arch_spin_unlock ( & timebase_lock ) ;
2007-02-05 01:36:53 +03:00
}
2006-09-06 23:42:08 +04:00
struct smp_ops_t pas_smp_ops = {
. probe = smp_mpic_probe ,
. message_pass = smp_mpic_message_pass ,
. kick_cpu = smp_generic_kick_cpu ,
. setup_cpu = smp_mpic_setup_cpu ,
2007-02-05 01:36:53 +03:00
. give_timebase = pas_give_timebase ,
. take_timebase = pas_take_timebase ,
2006-09-06 23:42:08 +04:00
} ;
# endif /* CONFIG_SMP */
void __init pas_setup_arch ( void )
{
# ifdef CONFIG_SMP
/* Setup SMP callback */
smp_ops = & pas_smp_ops ;
# endif
/* Lookup PCI hosts */
pas_pci_init ( ) ;
# ifdef CONFIG_DUMMY_CONSOLE
conswitchp = & dummy_con ;
# endif
2007-02-05 01:36:52 +03:00
/* Remap SDC register for doing reset */
/* XXXOJN This should maybe come out of the device tree */
reset_reg = ioremap ( 0xfc101100 , 4 ) ;
2006-09-06 23:42:08 +04:00
}
2007-09-05 06:09:23 +04:00
static int __init pas_setup_mce_regs ( void )
{
struct pci_dev * dev ;
int reg ;
/* Remap various SoC status registers for use by the MCE handler */
reg = 0 ;
dev = pci_get_device ( PCI_VENDOR_ID_PASEMI , 0xa00a , NULL ) ;
while ( dev & & reg < MAX_MCE_REGS ) {
mce_regs [ reg ] . name = kasprintf ( GFP_KERNEL ,
" mc%d_mcdebug_errsta " , reg ) ;
mce_regs [ reg ] . addr = pasemi_pci_getcfgaddr ( dev , 0x730 ) ;
dev = pci_get_device ( PCI_VENDOR_ID_PASEMI , 0xa00a , dev ) ;
reg + + ;
}
dev = pci_get_device ( PCI_VENDOR_ID_PASEMI , 0xa001 , NULL ) ;
if ( dev & & reg + 4 < MAX_MCE_REGS ) {
mce_regs [ reg ] . name = " iobdbg_IntStatus1 " ;
mce_regs [ reg ] . addr = pasemi_pci_getcfgaddr ( dev , 0x438 ) ;
reg + + ;
mce_regs [ reg ] . name = " iobdbg_IOCTbusIntDbgReg " ;
mce_regs [ reg ] . addr = pasemi_pci_getcfgaddr ( dev , 0x454 ) ;
reg + + ;
mce_regs [ reg ] . name = " iobiom_IntStatus " ;
mce_regs [ reg ] . addr = pasemi_pci_getcfgaddr ( dev , 0xc10 ) ;
reg + + ;
mce_regs [ reg ] . name = " iobiom_IntDbgReg " ;
mce_regs [ reg ] . addr = pasemi_pci_getcfgaddr ( dev , 0xc1c ) ;
reg + + ;
}
dev = pci_get_device ( PCI_VENDOR_ID_PASEMI , 0xa009 , NULL ) ;
if ( dev & & reg + 2 < MAX_MCE_REGS ) {
mce_regs [ reg ] . name = " l2csts_IntStatus " ;
mce_regs [ reg ] . addr = pasemi_pci_getcfgaddr ( dev , 0x200 ) ;
reg + + ;
mce_regs [ reg ] . name = " l2csts_Cnt " ;
mce_regs [ reg ] . addr = pasemi_pci_getcfgaddr ( dev , 0x214 ) ;
reg + + ;
}
num_mce_regs = reg ;
return 0 ;
}
2008-01-02 22:32:28 +03:00
machine_device_initcall ( pasemi , pas_setup_mce_regs ) ;
2007-09-05 06:09:23 +04:00
2006-09-06 23:42:08 +04:00
static __init void pas_init_IRQ ( void )
{
struct device_node * np ;
struct device_node * root , * mpic_node ;
unsigned long openpic_addr ;
const unsigned int * opprop ;
int naddr , opplen ;
2007-12-20 22:11:18 +03:00
int mpic_flags ;
const unsigned int * nmiprop ;
2006-09-06 23:42:08 +04:00
struct mpic * mpic ;
mpic_node = NULL ;
for_each_node_by_type ( np , " interrupt-controller " )
2007-05-03 11:26:52 +04:00
if ( of_device_is_compatible ( np , " open-pic " ) ) {
2006-09-06 23:42:08 +04:00
mpic_node = np ;
break ;
}
if ( ! mpic_node )
for_each_node_by_type ( np , " open-pic " ) {
mpic_node = np ;
break ;
}
if ( ! mpic_node ) {
printk ( KERN_ERR
" Failed to locate the MPIC interrupt controller \n " ) ;
return ;
}
/* Find address list in /platform-open-pic */
root = of_find_node_by_path ( " / " ) ;
2007-04-03 04:56:50 +04:00
naddr = of_n_addr_cells ( root ) ;
2007-04-03 16:26:41 +04:00
opprop = of_get_property ( root , " platform-open-pic " , & opplen ) ;
2006-09-06 23:42:08 +04:00
if ( ! opprop ) {
printk ( KERN_ERR " No platform-open-pic property. \n " ) ;
of_node_put ( root ) ;
return ;
}
openpic_addr = of_read_number ( opprop , naddr ) ;
printk ( KERN_DEBUG " OpenPIC addr: %lx \n " , openpic_addr ) ;
2007-12-28 07:16:29 +03:00
mpic_flags = MPIC_PRIMARY | MPIC_LARGE_VECTORS | MPIC_NO_BIAS ;
2007-12-20 22:11:18 +03:00
nmiprop = of_get_property ( mpic_node , " nmi-source " , NULL ) ;
if ( nmiprop )
mpic_flags | = MPIC_ENABLE_MCK ;
2007-01-29 08:33:18 +03:00
mpic = mpic_alloc ( mpic_node , openpic_addr ,
2007-12-20 22:11:18 +03:00
mpic_flags , 0 , 0 , " PASEMI-OPIC " ) ;
2006-09-06 23:42:08 +04:00
BUG_ON ( ! mpic ) ;
mpic_assign_isu ( mpic , 0 , openpic_addr + 0x10000 ) ;
mpic_init ( mpic ) ;
2007-12-20 22:11:18 +03:00
/* The NMI/MCK source needs to be prio 15 */
if ( nmiprop ) {
nmi_virq = irq_create_mapping ( NULL , * nmiprop ) ;
mpic_irq_set_priority ( nmi_virq , 15 ) ;
set_irq_type ( nmi_virq , IRQ_TYPE_EDGE_RISING ) ;
mpic_unmask_irq ( nmi_virq ) ;
}
2006-09-06 23:42:08 +04:00
of_node_put ( mpic_node ) ;
of_node_put ( root ) ;
}
static void __init pas_progress ( char * s , unsigned short hex )
{
printk ( " [%04x] : %s \n " , hex , s ? s : " " ) ;
}
2007-02-05 01:36:50 +03:00
static int pas_machine_check_handler ( struct pt_regs * regs )
{
int cpu = smp_processor_id ( ) ;
unsigned long srr0 , srr1 , dsisr ;
2007-09-05 06:09:23 +04:00
int dump_slb = 0 ;
int i ;
2007-02-05 01:36:50 +03:00
srr0 = regs - > nip ;
srr1 = regs - > msr ;
2007-12-20 22:11:18 +03:00
2008-01-02 21:35:50 +03:00
if ( nmi_virq ! = NO_IRQ & & mpic_get_mcirq ( ) = = nmi_virq ) {
2007-12-20 22:11:18 +03:00
printk ( KERN_ERR " NMI delivered \n " ) ;
debugger ( regs ) ;
mpic_end_irq ( nmi_virq ) ;
goto out ;
}
2007-02-05 01:36:50 +03:00
dsisr = mfspr ( SPRN_DSISR ) ;
printk ( KERN_ERR " Machine Check on CPU %d \n " , cpu ) ;
2007-09-05 06:09:23 +04:00
printk ( KERN_ERR " SRR0 0x%016lx SRR1 0x%016lx \n " , srr0 , srr1 ) ;
printk ( KERN_ERR " DSISR 0x%016lx DAR 0x%016lx \n " , dsisr , regs - > dar ) ;
printk ( KERN_ERR " BER 0x%016lx MER 0x%016lx \n " , mfspr ( SPRN_PA6T_BER ) ,
mfspr ( SPRN_PA6T_MER ) ) ;
printk ( KERN_ERR " IER 0x%016lx DER 0x%016lx \n " , mfspr ( SPRN_PA6T_IER ) ,
mfspr ( SPRN_PA6T_DER ) ) ;
2007-02-05 01:36:50 +03:00
printk ( KERN_ERR " Cause: \n " ) ;
if ( srr1 & 0x200000 )
printk ( KERN_ERR " Signalled by SDC \n " ) ;
2007-09-05 06:09:23 +04:00
2007-02-05 01:36:50 +03:00
if ( srr1 & 0x100000 ) {
printk ( KERN_ERR " Load/Store detected error: \n " ) ;
if ( dsisr & 0x8000 )
printk ( KERN_ERR " D-cache ECC double-bit error or bus error \n " ) ;
if ( dsisr & 0x4000 )
printk ( KERN_ERR " LSU snoop response error \n " ) ;
2007-09-05 06:09:23 +04:00
if ( dsisr & 0x2000 ) {
2007-02-05 01:36:50 +03:00
printk ( KERN_ERR " MMU SLB multi-hit or invalid B field \n " ) ;
2007-09-05 06:09:23 +04:00
dump_slb = 1 ;
}
2007-02-05 01:36:50 +03:00
if ( dsisr & 0x1000 )
printk ( KERN_ERR " Recoverable Duptags \n " ) ;
if ( dsisr & 0x800 )
printk ( KERN_ERR " Recoverable D-cache parity error count overflow \n " ) ;
if ( dsisr & 0x400 )
printk ( KERN_ERR " TLB parity error count overflow \n " ) ;
}
2007-09-05 06:09:23 +04:00
2007-02-05 01:36:50 +03:00
if ( srr1 & 0x80000 )
printk ( KERN_ERR " Bus Error \n " ) ;
2007-09-05 06:09:23 +04:00
if ( srr1 & 0x40000 ) {
2007-02-05 01:36:50 +03:00
printk ( KERN_ERR " I-side SLB multiple hit \n " ) ;
2007-09-05 06:09:23 +04:00
dump_slb = 1 ;
}
2007-02-05 01:36:50 +03:00
if ( srr1 & 0x20000 )
printk ( KERN_ERR " I-cache parity error hit \n " ) ;
2007-09-05 06:09:23 +04:00
if ( num_mce_regs = = 0 )
printk ( KERN_ERR " No MCE registers mapped yet, can't dump \n " ) ;
else
printk ( KERN_ERR " SoC debug registers: \n " ) ;
for ( i = 0 ; i < num_mce_regs ; i + + )
printk ( KERN_ERR " %s: 0x%08x \n " , mce_regs [ i ] . name ,
in_le32 ( mce_regs [ i ] . addr ) ) ;
if ( dump_slb ) {
unsigned long e , v ;
int i ;
printk ( KERN_ERR " slb contents: \n " ) ;
2007-12-06 09:24:48 +03:00
for ( i = 0 ; i < mmu_slb_size ; i + + ) {
2007-09-05 06:09:23 +04:00
asm volatile ( " slbmfee %0,%1 " : " =r " ( e ) : " r " ( i ) ) ;
asm volatile ( " slbmfev %0,%1 " : " =r " ( v ) : " r " ( i ) ) ;
printk ( KERN_ERR " %02d %016lx %016lx \n " , i , e , v ) ;
}
}
2007-12-20 22:11:18 +03:00
out :
2007-02-05 01:36:50 +03:00
/* SRR1[62] is from MSR[62] if recoverable, so pass that back */
return ! ! ( srr1 & 0x2 ) ;
}
2007-02-05 01:36:55 +03:00
static void __init pas_init_early ( void )
{
iommu_init_early_pasemi ( ) ;
}
2007-10-17 10:26:20 +04:00
# ifdef CONFIG_PCMCIA
static int pcmcia_notify ( struct notifier_block * nb , unsigned long action ,
void * data )
{
struct device * dev = data ;
struct device * parent ;
struct pcmcia_device * pdev = to_pcmcia_dev ( dev ) ;
/* We are only intereted in device addition */
if ( action ! = BUS_NOTIFY_ADD_DEVICE )
return 0 ;
parent = pdev - > socket - > dev . parent ;
/* We know electra_cf devices will always have of_node set, since
* electra_cf is an of_platform driver .
*/
if ( ! parent - > archdata . of_node )
return 0 ;
if ( ! of_device_is_compatible ( parent - > archdata . of_node , " electra-cf " ) )
return 0 ;
/* We use the direct ops for localbus */
dev - > archdata . dma_ops = & dma_direct_ops ;
return 0 ;
}
static struct notifier_block pcmcia_notifier = {
. notifier_call = pcmcia_notify ,
} ;
static inline void pasemi_pcmcia_init ( void )
{
extern struct bus_type pcmcia_bus_type ;
bus_register_notifier ( & pcmcia_bus_type , & pcmcia_notifier ) ;
}
# else
static inline void pasemi_pcmcia_init ( void )
{
}
# endif
2007-04-18 10:39:54 +04:00
static struct of_device_id pasemi_bus_ids [ ] = {
2007-11-05 05:57:45 +03:00
/* Unfortunately needed for legacy firmwares */
2007-10-17 10:26:20 +04:00
{ . type = " localbus " , } ,
2007-04-18 10:39:54 +04:00
{ . type = " sdc " , } ,
2007-11-05 05:57:45 +03:00
/* These are the proper entries, which newer firmware uses */
{ . compatible = " pasemi,localbus " , } ,
{ . compatible = " pasemi,sdc " , } ,
2007-04-18 10:39:54 +04:00
{ } ,
} ;
static int __init pasemi_publish_devices ( void )
{
2007-10-17 10:26:20 +04:00
pasemi_pcmcia_init ( ) ;
2007-05-01 08:43:39 +04:00
/* Publish OF platform devices for SDC and other non-PCI devices */
2007-04-18 10:39:54 +04:00
of_platform_bus_probe ( NULL , pasemi_bus_ids , NULL ) ;
return 0 ;
}
2008-01-02 22:32:28 +03:00
machine_device_initcall ( pasemi , pasemi_publish_devices ) ;
2007-04-18 10:39:54 +04:00
2007-02-05 01:36:50 +03:00
2006-09-06 23:42:08 +04:00
/*
* Called very early , MMU is off , device - tree isn ' t unflattened
*/
static int __init pas_probe ( void )
{
unsigned long root = of_get_flat_dt_root ( ) ;
2007-11-05 05:57:45 +03:00
if ( ! of_flat_dt_is_compatible ( root , " PA6T-1682M " ) & &
! of_flat_dt_is_compatible ( root , " pasemi,pwrficient " ) )
2006-09-06 23:42:08 +04:00
return 0 ;
hpte_init_native ( ) ;
2007-02-05 01:36:55 +03:00
alloc_iobmap_l2 ( ) ;
2006-09-06 23:42:08 +04:00
return 1 ;
}
2007-07-05 18:49:04 +04:00
define_machine ( pasemi ) {
2007-11-05 05:57:45 +03:00
. name = " PA Semi PWRficient " ,
2006-09-06 23:42:08 +04:00
. probe = pas_probe ,
. setup_arch = pas_setup_arch ,
2007-02-05 01:36:55 +03:00
. init_early = pas_init_early ,
2006-09-06 23:42:08 +04:00
. init_IRQ = pas_init_IRQ ,
. get_irq = mpic_get_irq ,
. restart = pas_restart ,
. get_boot_time = pas_get_boot_time ,
. calibrate_decr = generic_calibrate_decr ,
. progress = pas_progress ,
2007-02-05 01:36:50 +03:00
. machine_check_exception = pas_machine_check_handler ,
2006-09-06 23:42:08 +04:00
} ;