2007-01-12 04:03:28 +03:00
/*
* Support for SCC external PCI
*
* ( C ) Copyright 2004 - 2007 TOSHIBA CORPORATION
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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 . ,
* 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA .
*/
# undef DEBUG
# include <linux/kernel.h>
# include <linux/threads.h>
# include <linux/pci.h>
# include <linux/init.h>
# include <linux/pci_regs.h>
# include <linux/bootmem.h>
# include <asm/io.h>
# include <asm/irq.h>
# include <asm/prom.h>
# include <asm/pci-bridge.h>
# include <asm/ppc-pci.h>
2008-04-24 13:26:28 +04:00
# include "celleb_scc.h"
# include "celleb_pci.h"
2007-01-12 04:03:28 +03:00
# define MAX_PCI_DEVICES 32
# define MAX_PCI_FUNCTIONS 8
# define iob() __asm__ __volatile__("eieio; sync":::"memory")
2007-10-02 12:26:53 +04:00
static inline PCI_IO_ADDR celleb_epci_get_epci_base (
2007-03-02 10:59:25 +03:00
struct pci_controller * hose )
{
/*
* Note :
* Celleb epci uses cfg_addr as a base address for
* epci control registers .
*/
return hose - > cfg_addr ;
}
2007-10-02 12:26:53 +04:00
static inline PCI_IO_ADDR celleb_epci_get_epci_cfg (
2007-03-02 10:59:25 +03:00
struct pci_controller * hose )
{
/*
* Note :
* Celleb epci uses cfg_data as a base address for
* configuration area for epci devices .
*/
return hose - > cfg_data ;
}
2007-01-12 04:03:28 +03:00
static inline void clear_and_disable_master_abort_interrupt (
struct pci_controller * hose )
{
2007-10-02 12:26:53 +04:00
PCI_IO_ADDR epci_base ;
PCI_IO_ADDR reg ;
2007-03-02 10:59:25 +03:00
epci_base = celleb_epci_get_epci_base ( hose ) ;
reg = epci_base + PCI_COMMAND ;
out_be32 ( reg , in_be32 ( reg ) | ( PCI_STATUS_REC_MASTER_ABORT < < 16 ) ) ;
2007-01-12 04:03:28 +03:00
}
static int celleb_epci_check_abort ( struct pci_controller * hose ,
2007-10-02 12:26:53 +04:00
PCI_IO_ADDR addr )
2007-01-12 04:03:28 +03:00
{
2007-10-02 12:26:53 +04:00
PCI_IO_ADDR reg ;
PCI_IO_ADDR epci_base ;
2007-01-12 04:03:28 +03:00
u32 val ;
iob ( ) ;
2007-03-02 10:59:25 +03:00
epci_base = celleb_epci_get_epci_base ( hose ) ;
2007-01-12 04:03:28 +03:00
reg = epci_base + PCI_COMMAND ;
val = in_be32 ( reg ) ;
if ( val & ( PCI_STATUS_REC_MASTER_ABORT < < 16 ) ) {
out_be32 ( reg ,
( val & 0xffff ) | ( PCI_STATUS_REC_MASTER_ABORT < < 16 ) ) ;
/* clear PCI Controller error, FRE, PMFE */
reg = epci_base + SCC_EPCI_STATUS ;
out_be32 ( reg , SCC_EPCI_INT_PAI ) ;
reg = epci_base + SCC_EPCI_VCSR ;
val = in_be32 ( reg ) & 0xffff ;
val | = SCC_EPCI_VCSR_FRE ;
out_be32 ( reg , val ) ;
reg = epci_base + SCC_EPCI_VISTAT ;
out_be32 ( reg , SCC_EPCI_VISTAT_PMFE ) ;
return PCIBIOS_DEVICE_NOT_FOUND ;
}
return PCIBIOS_SUCCESSFUL ;
}
2008-04-24 13:26:28 +04:00
static PCI_IO_ADDR celleb_epci_make_config_addr ( struct pci_bus * bus ,
struct pci_controller * hose , unsigned int devfn , int where )
2007-01-12 04:03:28 +03:00
{
2007-10-02 12:26:53 +04:00
PCI_IO_ADDR addr ;
2007-01-12 04:03:28 +03:00
2007-05-09 11:36:40 +04:00
if ( bus ! = hose - > bus )
2007-03-02 10:59:25 +03:00
addr = celleb_epci_get_epci_cfg ( hose ) +
2007-01-12 04:03:28 +03:00
( ( ( bus - > number & 0xff ) < < 16 )
2008-03-14 15:19:34 +03:00
| ( ( devfn & 0xff ) < < 8 )
| ( where & 0xff )
| 0x01000000 ) ;
2007-01-12 04:03:28 +03:00
else
2007-03-02 10:59:25 +03:00
addr = celleb_epci_get_epci_cfg ( hose ) +
2007-01-12 04:03:28 +03:00
( ( ( devfn & 0xff ) < < 8 ) | ( where & 0xff ) ) ;
2007-02-09 19:39:50 +03:00
pr_debug ( " EPCI: config_addr = 0x%p \n " , addr ) ;
2007-01-12 04:03:28 +03:00
return addr ;
}
static int celleb_epci_read_config ( struct pci_bus * bus ,
2008-03-14 15:19:34 +03:00
unsigned int devfn , int where , int size , u32 * val )
2007-01-12 04:03:28 +03:00
{
2007-10-02 12:26:53 +04:00
PCI_IO_ADDR epci_base ;
PCI_IO_ADDR addr ;
2009-04-30 07:10:15 +04:00
struct pci_controller * hose = pci_bus_to_host ( bus ) ;
2007-01-12 04:03:28 +03:00
/* allignment check */
BUG_ON ( where % size ) ;
2007-03-02 10:59:25 +03:00
if ( ! celleb_epci_get_epci_cfg ( hose ) )
2007-01-12 04:03:28 +03:00
return PCIBIOS_DEVICE_NOT_FOUND ;
if ( bus - > number = = hose - > first_busno & & devfn = = 0 ) {
/* EPCI controller self */
2007-03-02 10:59:25 +03:00
epci_base = celleb_epci_get_epci_base ( hose ) ;
addr = epci_base + where ;
2007-01-12 04:03:28 +03:00
switch ( size ) {
case 1 :
2007-02-09 19:39:50 +03:00
* val = in_8 ( addr ) ;
2007-01-12 04:03:28 +03:00
break ;
case 2 :
2007-02-09 19:39:50 +03:00
* val = in_be16 ( addr ) ;
2007-01-12 04:03:28 +03:00
break ;
case 4 :
2007-02-09 19:39:50 +03:00
* val = in_be32 ( addr ) ;
2007-01-12 04:03:28 +03:00
break ;
default :
return PCIBIOS_DEVICE_NOT_FOUND ;
}
} else {
clear_and_disable_master_abort_interrupt ( hose ) ;
2007-05-09 11:36:40 +04:00
addr = celleb_epci_make_config_addr ( bus , hose , devfn , where ) ;
2007-01-12 04:03:28 +03:00
switch ( size ) {
case 1 :
2007-02-09 19:39:50 +03:00
* val = in_8 ( addr ) ;
2007-01-12 04:03:28 +03:00
break ;
case 2 :
2007-02-09 19:39:50 +03:00
* val = in_le16 ( addr ) ;
2007-01-12 04:03:28 +03:00
break ;
case 4 :
2007-02-09 19:39:50 +03:00
* val = in_le32 ( addr ) ;
2007-01-12 04:03:28 +03:00
break ;
default :
return PCIBIOS_DEVICE_NOT_FOUND ;
}
}
pr_debug ( " EPCI: "
2007-03-02 10:59:25 +03:00
" addr=0x%p, devfn=0x%x, where=0x%x, size=0x%x, val=0x%x \n " ,
2007-01-12 04:03:28 +03:00
addr , devfn , where , size , * val ) ;
2007-02-09 19:39:50 +03:00
return celleb_epci_check_abort ( hose , NULL ) ;
2007-01-12 04:03:28 +03:00
}
static int celleb_epci_write_config ( struct pci_bus * bus ,
unsigned int devfn , int where , int size , u32 val )
{
2007-10-02 12:26:53 +04:00
PCI_IO_ADDR epci_base ;
PCI_IO_ADDR addr ;
2009-04-30 07:10:15 +04:00
struct pci_controller * hose = pci_bus_to_host ( bus ) ;
2007-01-12 04:03:28 +03:00
/* allignment check */
BUG_ON ( where % size ) ;
2007-03-02 10:59:25 +03:00
if ( ! celleb_epci_get_epci_cfg ( hose ) )
2007-01-12 04:03:28 +03:00
return PCIBIOS_DEVICE_NOT_FOUND ;
if ( bus - > number = = hose - > first_busno & & devfn = = 0 ) {
/* EPCI controller self */
2007-03-02 10:59:25 +03:00
epci_base = celleb_epci_get_epci_base ( hose ) ;
addr = epci_base + where ;
2007-01-12 04:03:28 +03:00
switch ( size ) {
case 1 :
2007-02-09 19:39:50 +03:00
out_8 ( addr , val ) ;
2007-01-12 04:03:28 +03:00
break ;
case 2 :
2007-02-09 19:39:50 +03:00
out_be16 ( addr , val ) ;
2007-01-12 04:03:28 +03:00
break ;
case 4 :
2007-02-09 19:39:50 +03:00
out_be32 ( addr , val ) ;
2007-01-12 04:03:28 +03:00
break ;
default :
return PCIBIOS_DEVICE_NOT_FOUND ;
}
} else {
clear_and_disable_master_abort_interrupt ( hose ) ;
2007-05-09 11:36:40 +04:00
addr = celleb_epci_make_config_addr ( bus , hose , devfn , where ) ;
2007-01-12 04:03:28 +03:00
switch ( size ) {
case 1 :
2007-02-09 19:39:50 +03:00
out_8 ( addr , val ) ;
2007-01-12 04:03:28 +03:00
break ;
case 2 :
2007-02-09 19:39:50 +03:00
out_le16 ( addr , val ) ;
2007-01-12 04:03:28 +03:00
break ;
case 4 :
2007-02-09 19:39:50 +03:00
out_le32 ( addr , val ) ;
2007-01-12 04:03:28 +03:00
break ;
default :
return PCIBIOS_DEVICE_NOT_FOUND ;
}
}
return celleb_epci_check_abort ( hose , addr ) ;
}
struct pci_ops celleb_epci_ops = {
2007-08-09 23:18:38 +04:00
. read = celleb_epci_read_config ,
. write = celleb_epci_write_config ,
2007-01-12 04:03:28 +03:00
} ;
/* to be moved in FW */
2007-07-26 14:02:27 +04:00
static int __init celleb_epci_init ( struct pci_controller * hose )
2007-01-12 04:03:28 +03:00
{
u32 val ;
2007-10-02 12:26:53 +04:00
PCI_IO_ADDR reg ;
PCI_IO_ADDR epci_base ;
2007-01-12 04:03:28 +03:00
int hwres = 0 ;
2007-03-02 10:59:25 +03:00
epci_base = celleb_epci_get_epci_base ( hose ) ;
2007-01-12 04:03:28 +03:00
/* PCI core reset(Internal bus and PCI clock) */
reg = epci_base + SCC_EPCI_CKCTRL ;
val = in_be32 ( reg ) ;
if ( val = = 0x00030101 )
hwres = 1 ;
else {
val & = ~ ( SCC_EPCI_CKCTRL_CRST0 | SCC_EPCI_CKCTRL_CRST1 ) ;
out_be32 ( reg , val ) ;
/* set PCI core clock */
val = in_be32 ( reg ) ;
val | = ( SCC_EPCI_CKCTRL_OCLKEN | SCC_EPCI_CKCTRL_LCLKEN ) ;
out_be32 ( reg , val ) ;
/* release PCI core reset (internal bus) */
val = in_be32 ( reg ) ;
val | = SCC_EPCI_CKCTRL_CRST0 ;
out_be32 ( reg , val ) ;
/* set PCI clock select */
reg = epci_base + SCC_EPCI_CLKRST ;
val = in_be32 ( reg ) ;
val & = ~ SCC_EPCI_CLKRST_CKS_MASK ;
val | = SCC_EPCI_CLKRST_CKS_2 ;
out_be32 ( reg , val ) ;
/* set arbiter */
reg = epci_base + SCC_EPCI_ABTSET ;
out_be32 ( reg , 0x0f1f001f ) ; /* temporary value */
/* buffer on */
reg = epci_base + SCC_EPCI_CLKRST ;
val = in_be32 ( reg ) ;
val | = SCC_EPCI_CLKRST_BC ;
out_be32 ( reg , val ) ;
/* PCI clock enable */
val = in_be32 ( reg ) ;
val | = SCC_EPCI_CLKRST_PCKEN ;
out_be32 ( reg , val ) ;
/* release PCI core reset (all) */
reg = epci_base + SCC_EPCI_CKCTRL ;
val = in_be32 ( reg ) ;
val | = ( SCC_EPCI_CKCTRL_CRST0 | SCC_EPCI_CKCTRL_CRST1 ) ;
out_be32 ( reg , val ) ;
/* set base translation registers. (already set by Beat) */
/* set base address masks. (already set by Beat) */
}
/* release interrupt masks and clear all interrupts */
reg = epci_base + SCC_EPCI_INTSET ;
out_be32 ( reg , 0x013f011f ) ; /* all interrupts enable */
reg = epci_base + SCC_EPCI_VIENAB ;
val = SCC_EPCI_VIENAB_PMPEE | SCC_EPCI_VIENAB_PMFEE ;
out_be32 ( reg , val ) ;
reg = epci_base + SCC_EPCI_STATUS ;
out_be32 ( reg , 0xffffffff ) ;
reg = epci_base + SCC_EPCI_VISTAT ;
out_be32 ( reg , 0xffffffff ) ;
/* disable PCI->IB address translation */
reg = epci_base + SCC_EPCI_VCSR ;
val = in_be32 ( reg ) ;
val & = ~ ( SCC_EPCI_VCSR_DR | SCC_EPCI_VCSR_AT ) ;
out_be32 ( reg , val ) ;
/* set base addresses. (no need to set?) */
/* memory space, bus master enable */
reg = epci_base + PCI_COMMAND ;
val = PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER ;
out_be32 ( reg , val ) ;
/* endian mode setup */
reg = epci_base + SCC_EPCI_ECMODE ;
val = 0x00550155 ;
out_be32 ( reg , val ) ;
/* set control option */
reg = epci_base + SCC_EPCI_CNTOPT ;
val = in_be32 ( reg ) ;
val | = SCC_EPCI_CNTOPT_O2PMB ;
out_be32 ( reg , val ) ;
/* XXX: temporay: set registers for address conversion setup */
reg = epci_base + SCC_EPCI_CNF10_REG ;
out_be32 ( reg , 0x80000008 ) ;
reg = epci_base + SCC_EPCI_CNF14_REG ;
out_be32 ( reg , 0x40000008 ) ;
reg = epci_base + SCC_EPCI_BAM0 ;
out_be32 ( reg , 0x80000000 ) ;
reg = epci_base + SCC_EPCI_BAM1 ;
out_be32 ( reg , 0xe0000000 ) ;
reg = epci_base + SCC_EPCI_PVBAT ;
out_be32 ( reg , 0x80000000 ) ;
if ( ! hwres ) {
/* release external PCI reset */
reg = epci_base + SCC_EPCI_CLKRST ;
val = in_be32 ( reg ) ;
val | = SCC_EPCI_CLKRST_PCIRST ;
out_be32 ( reg , val ) ;
}
return 0 ;
}
2008-04-24 13:24:13 +04:00
static int __init celleb_setup_epci ( struct device_node * node ,
struct pci_controller * hose )
2007-01-12 04:03:28 +03:00
{
struct resource r ;
pr_debug ( " PCI: celleb_setup_epci() \n " ) ;
2007-03-02 10:59:25 +03:00
/*
* Note :
* Celleb epci uses cfg_addr and cfg_data member of
* pci_controller structure in irregular way .
*
* cfg_addr is used to map for control registers of
* celleb epci .
*
* cfg_data is used for configuration area of devices
* on Celleb epci buses .
*/
2007-01-12 04:03:28 +03:00
if ( of_address_to_resource ( node , 0 , & r ) )
goto error ;
2011-06-09 20:13:32 +04:00
hose - > cfg_addr = ioremap ( r . start , resource_size ( & r ) ) ;
2007-01-12 04:03:28 +03:00
if ( ! hose - > cfg_addr )
goto error ;
2009-01-06 17:26:03 +03:00
pr_debug ( " EPCI: cfg_addr map 0x%016llx->0x%016lx + 0x%016llx \n " ,
2011-06-09 20:13:32 +04:00
r . start , ( unsigned long ) hose - > cfg_addr , resource_size ( & r ) ) ;
2007-01-12 04:03:28 +03:00
if ( of_address_to_resource ( node , 2 , & r ) )
goto error ;
2011-06-09 20:13:32 +04:00
hose - > cfg_data = ioremap ( r . start , resource_size ( & r ) ) ;
2007-01-12 04:03:28 +03:00
if ( ! hose - > cfg_data )
goto error ;
2009-01-06 17:26:03 +03:00
pr_debug ( " EPCI: cfg_data map 0x%016llx->0x%016lx + 0x%016llx \n " ,
2011-06-09 20:13:32 +04:00
r . start , ( unsigned long ) hose - > cfg_data , resource_size ( & r ) ) ;
2007-01-12 04:03:28 +03:00
2007-10-02 12:26:53 +04:00
hose - > ops = & celleb_epci_ops ;
2007-01-12 04:03:28 +03:00
celleb_epci_init ( hose ) ;
return 0 ;
error :
2007-10-02 12:26:53 +04:00
if ( hose - > cfg_addr )
iounmap ( hose - > cfg_addr ) ;
if ( hose - > cfg_data )
iounmap ( hose - > cfg_data ) ;
2007-01-12 04:03:28 +03:00
return 1 ;
}
2008-04-24 13:24:13 +04:00
struct celleb_phb_spec celleb_epci_spec __initdata = {
. setup = celleb_setup_epci ,
. ops = & spiderpci_ops ,
. iowa_init = & spiderpci_iowa_init ,
. iowa_data = ( void * ) 0 ,
} ;