2007-01-12 10:03:28 +09:00
/*
* Support for PCI on Celleb platform .
*
* ( C ) Copyright 2006 - 2007 TOSHIBA CORPORATION
*
* This code is based on arch / powerpc / kernel / rtas_pci . c :
* Copyright ( C ) 2001 Dave Engebretsen , IBM Corporation
* Copyright ( C ) 2003 Anton Blanchard < anton @ au . ibm . com > , IBM
*
* 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/string.h>
# include <linux/init.h>
# include <linux/bootmem.h>
# include <linux/pci_regs.h>
2008-01-09 06:20:40 +11:00
# include <linux/of.h>
2007-10-02 18:26:53 +10:00
# include <linux/of_device.h>
2007-01-12 10:03:28 +09:00
# 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 19:25:16 +10:00
# include "io-workarounds.h"
# include "celleb_pci.h"
2007-01-12 10:03:28 +09:00
# define MAX_PCI_DEVICES 32
# define MAX_PCI_FUNCTIONS 8
# define MAX_PCI_BASE_ADDRS 3 /* use 64 bit address */
/* definition for fake pci configuration area for GbE, .... ,and etc. */
struct celleb_pci_resource {
struct resource r [ MAX_PCI_BASE_ADDRS ] ;
} ;
struct celleb_pci_private {
unsigned char * fake_config [ MAX_PCI_DEVICES ] [ MAX_PCI_FUNCTIONS ] ;
struct celleb_pci_resource * res [ MAX_PCI_DEVICES ] [ MAX_PCI_FUNCTIONS ] ;
} ;
static inline u8 celleb_fake_config_readb ( void * addr )
{
u8 * p = addr ;
return * p ;
}
static inline u16 celleb_fake_config_readw ( void * addr )
{
2007-02-09 16:39:50 +00:00
__le16 * p = addr ;
2007-01-12 10:03:28 +09:00
return le16_to_cpu ( * p ) ;
}
static inline u32 celleb_fake_config_readl ( void * addr )
{
2007-02-09 16:39:50 +00:00
__le32 * p = addr ;
2007-01-12 10:03:28 +09:00
return le32_to_cpu ( * p ) ;
}
static inline void celleb_fake_config_writeb ( u32 val , void * addr )
{
u8 * p = addr ;
* p = val ;
}
static inline void celleb_fake_config_writew ( u32 val , void * addr )
{
2007-02-09 16:39:50 +00:00
__le16 val16 ;
__le16 * p = addr ;
2007-01-12 10:03:28 +09:00
val16 = cpu_to_le16 ( val ) ;
* p = val16 ;
}
static inline void celleb_fake_config_writel ( u32 val , void * addr )
{
2007-02-09 16:39:50 +00:00
__le32 val32 ;
__le32 * p = addr ;
2007-01-12 10:03:28 +09:00
val32 = cpu_to_le32 ( val ) ;
* p = val32 ;
}
static unsigned char * get_fake_config_start ( struct pci_controller * hose ,
int devno , int fn )
{
struct celleb_pci_private * private = hose - > private_data ;
if ( private = = NULL )
return NULL ;
return private - > fake_config [ devno ] [ fn ] ;
}
static struct celleb_pci_resource * get_resource_start (
struct pci_controller * hose ,
int devno , int fn )
{
struct celleb_pci_private * private = hose - > private_data ;
if ( private = = NULL )
return NULL ;
return private - > res [ devno ] [ fn ] ;
}
static void celleb_config_read_fake ( unsigned char * config , int where ,
int size , u32 * val )
{
char * p = config + where ;
switch ( size ) {
case 1 :
* val = celleb_fake_config_readb ( p ) ;
break ;
case 2 :
* val = celleb_fake_config_readw ( p ) ;
break ;
case 4 :
* val = celleb_fake_config_readl ( p ) ;
break ;
}
}
static void celleb_config_write_fake ( unsigned char * config , int where ,
int size , u32 val )
{
char * p = config + where ;
switch ( size ) {
case 1 :
celleb_fake_config_writeb ( val , p ) ;
break ;
case 2 :
celleb_fake_config_writew ( val , p ) ;
break ;
case 4 :
celleb_fake_config_writel ( val , p ) ;
break ;
}
}
static int celleb_fake_pci_read_config ( struct pci_bus * bus ,
unsigned int devfn , int where , int size , u32 * val )
{
char * config ;
struct device_node * node ;
struct pci_controller * hose ;
unsigned int devno = devfn > > 3 ;
unsigned int fn = devfn & 0x7 ;
/* allignment check */
BUG_ON ( where % size ) ;
pr_debug ( " fake read: bus=0x%x, " , bus - > number ) ;
node = ( struct device_node * ) bus - > sysdata ;
hose = pci_find_hose_for_OF_device ( node ) ;
config = get_fake_config_start ( hose , devno , fn ) ;
pr_debug ( " devno=0x%x, where=0x%x, size=0x%x, " , devno , where , size ) ;
if ( ! config ) {
pr_debug ( " failed \n " ) ;
return PCIBIOS_DEVICE_NOT_FOUND ;
}
celleb_config_read_fake ( config , where , size , val ) ;
pr_debug ( " val=0x%x \n " , * val ) ;
return PCIBIOS_SUCCESSFUL ;
}
static int celleb_fake_pci_write_config ( struct pci_bus * bus ,
2008-04-24 19:25:16 +10:00
unsigned int devfn , int where , int size , u32 val )
2007-01-12 10:03:28 +09:00
{
char * config ;
struct device_node * node ;
struct pci_controller * hose ;
struct celleb_pci_resource * res ;
unsigned int devno = devfn > > 3 ;
unsigned int fn = devfn & 0x7 ;
/* allignment check */
BUG_ON ( where % size ) ;
node = ( struct device_node * ) bus - > sysdata ;
hose = pci_find_hose_for_OF_device ( node ) ;
config = get_fake_config_start ( hose , devno , fn ) ;
if ( ! config )
return PCIBIOS_DEVICE_NOT_FOUND ;
if ( val = = ~ 0 ) {
int i = ( where - PCI_BASE_ADDRESS_0 ) > > 3 ;
switch ( where ) {
case PCI_BASE_ADDRESS_0 :
case PCI_BASE_ADDRESS_2 :
if ( size ! = 4 )
return PCIBIOS_DEVICE_NOT_FOUND ;
res = get_resource_start ( hose , devno , fn ) ;
if ( ! res )
return PCIBIOS_DEVICE_NOT_FOUND ;
celleb_config_write_fake ( config , where , size ,
( res - > r [ i ] . end - res - > r [ i ] . start ) ) ;
return PCIBIOS_SUCCESSFUL ;
case PCI_BASE_ADDRESS_1 :
case PCI_BASE_ADDRESS_3 :
case PCI_BASE_ADDRESS_4 :
case PCI_BASE_ADDRESS_5 :
break ;
default :
break ;
}
}
celleb_config_write_fake ( config , where , size , val ) ;
pr_debug ( " fake write: where=%x, size=%d, val=%x \n " ,
where , size , val ) ;
return PCIBIOS_SUCCESSFUL ;
}
static struct pci_ops celleb_fake_pci_ops = {
2007-08-10 05:18:37 +10:00
. read = celleb_fake_pci_read_config ,
. write = celleb_fake_pci_write_config ,
2007-01-12 10:03:28 +09:00
} ;
static inline void celleb_setup_pci_base_addrs ( struct pci_controller * hose ,
unsigned int devno , unsigned int fn ,
unsigned int num_base_addr )
{
u32 val ;
unsigned char * config ;
struct celleb_pci_resource * res ;
config = get_fake_config_start ( hose , devno , fn ) ;
res = get_resource_start ( hose , devno , fn ) ;
if ( ! config | | ! res )
return ;
switch ( num_base_addr ) {
case 3 :
val = ( res - > r [ 2 ] . start & 0xfffffff0 )
| PCI_BASE_ADDRESS_MEM_TYPE_64 ;
celleb_config_write_fake ( config , PCI_BASE_ADDRESS_4 , 4 , val ) ;
val = res - > r [ 2 ] . start > > 32 ;
celleb_config_write_fake ( config , PCI_BASE_ADDRESS_5 , 4 , val ) ;
/* FALLTHROUGH */
case 2 :
val = ( res - > r [ 1 ] . start & 0xfffffff0 )
| PCI_BASE_ADDRESS_MEM_TYPE_64 ;
celleb_config_write_fake ( config , PCI_BASE_ADDRESS_2 , 4 , val ) ;
val = res - > r [ 1 ] . start > > 32 ;
celleb_config_write_fake ( config , PCI_BASE_ADDRESS_3 , 4 , val ) ;
/* FALLTHROUGH */
case 1 :
val = ( res - > r [ 0 ] . start & 0xfffffff0 )
| PCI_BASE_ADDRESS_MEM_TYPE_64 ;
celleb_config_write_fake ( config , PCI_BASE_ADDRESS_0 , 4 , val ) ;
val = res - > r [ 0 ] . start > > 32 ;
celleb_config_write_fake ( config , PCI_BASE_ADDRESS_1 , 4 , val ) ;
break ;
}
val = PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER ;
celleb_config_write_fake ( config , PCI_COMMAND , 2 , val ) ;
}
2007-07-26 19:59:17 +10:00
static int __init celleb_setup_fake_pci_device ( struct device_node * node ,
struct pci_controller * hose )
2007-01-12 10:03:28 +09:00
{
unsigned int rlen ;
int num_base_addr = 0 ;
u32 val ;
const u32 * wi0 , * wi1 , * wi2 , * wi3 , * wi4 ;
unsigned int devno , fn ;
struct celleb_pci_private * private = hose - > private_data ;
unsigned char * * config = NULL ;
struct celleb_pci_resource * * res = NULL ;
const char * name ;
const unsigned long * li ;
int size , result ;
if ( private = = NULL ) {
printk ( KERN_ERR " PCI: "
" memory space for pci controller is not assigned \n " ) ;
goto error ;
}
2007-04-03 22:26:41 +10:00
name = of_get_property ( node , " model " , & rlen ) ;
2007-01-12 10:03:28 +09:00
if ( ! name ) {
printk ( KERN_ERR " PCI: model property not found. \n " ) ;
goto error ;
}
2007-04-03 22:26:41 +10:00
wi4 = of_get_property ( node , " reg " , & rlen ) ;
2007-01-12 10:03:28 +09:00
if ( wi4 = = NULL )
goto error ;
devno = ( ( wi4 [ 0 ] > > 8 ) & 0xff ) > > 3 ;
fn = ( wi4 [ 0 ] > > 8 ) & 0x7 ;
pr_debug ( " PCI: celleb_setup_fake_pci() %s devno=%x fn=%x \n " , name ,
devno , fn ) ;
size = 256 ;
config = & private - > fake_config [ devno ] [ fn ] ;
2007-09-17 14:08:06 +10:00
* config = alloc_maybe_bootmem ( size , GFP_KERNEL ) ;
2007-01-12 10:03:28 +09:00
if ( * config = = NULL ) {
printk ( KERN_ERR " PCI: "
" not enough memory for fake configuration space \n " ) ;
goto error ;
}
pr_debug ( " PCI: fake config area assigned 0x%016lx \n " ,
( unsigned long ) * config ) ;
size = sizeof ( struct celleb_pci_resource ) ;
res = & private - > res [ devno ] [ fn ] ;
2007-09-17 14:08:06 +10:00
* res = alloc_maybe_bootmem ( size , GFP_KERNEL ) ;
2007-01-12 10:03:28 +09:00
if ( * res = = NULL ) {
printk ( KERN_ERR
" PCI: not enough memory for resource data space \n " ) ;
goto error ;
}
pr_debug ( " PCI: res assigned 0x%016lx \n " , ( unsigned long ) * res ) ;
2007-04-03 22:26:41 +10:00
wi0 = of_get_property ( node , " device-id " , NULL ) ;
wi1 = of_get_property ( node , " vendor-id " , NULL ) ;
wi2 = of_get_property ( node , " class-code " , NULL ) ;
wi3 = of_get_property ( node , " revision-id " , NULL ) ;
2007-11-29 18:44:18 +11:00
if ( ! wi0 | | ! wi1 | | ! wi2 | | ! wi3 ) {
printk ( KERN_ERR " PCI: Missing device tree properties. \n " ) ;
goto error ;
}
2007-01-12 10:03:28 +09:00
celleb_config_write_fake ( * config , PCI_DEVICE_ID , 2 , wi0 [ 0 ] & 0xffff ) ;
celleb_config_write_fake ( * config , PCI_VENDOR_ID , 2 , wi1 [ 0 ] & 0xffff ) ;
pr_debug ( " class-code = 0x%08x \n " , wi2 [ 0 ] ) ;
celleb_config_write_fake ( * config , PCI_CLASS_PROG , 1 , wi2 [ 0 ] & 0xff ) ;
celleb_config_write_fake ( * config , PCI_CLASS_DEVICE , 2 ,
( wi2 [ 0 ] > > 8 ) & 0xffff ) ;
celleb_config_write_fake ( * config , PCI_REVISION_ID , 1 , wi3 [ 0 ] ) ;
while ( num_base_addr < MAX_PCI_BASE_ADDRS ) {
result = of_address_to_resource ( node ,
num_base_addr , & ( * res ) - > r [ num_base_addr ] ) ;
if ( result )
break ;
num_base_addr + + ;
}
celleb_setup_pci_base_addrs ( hose , devno , fn , num_base_addr ) ;
2007-04-03 22:26:41 +10:00
li = of_get_property ( node , " interrupts " , & rlen ) ;
2007-11-29 18:44:18 +11:00
if ( ! li ) {
printk ( KERN_ERR " PCI: interrupts not found. \n " ) ;
goto error ;
}
2007-01-12 10:03:28 +09:00
val = li [ 0 ] ;
celleb_config_write_fake ( * config , PCI_INTERRUPT_PIN , 1 , 1 ) ;
celleb_config_write_fake ( * config , PCI_INTERRUPT_LINE , 1 , val ) ;
# ifdef DEBUG
pr_debug ( " PCI: %s irq=%ld \n " , name , li [ 0 ] ) ;
for ( i = 0 ; i < 6 ; i + + ) {
celleb_config_read_fake ( * config ,
PCI_BASE_ADDRESS_0 + 0x4 * i , 4 ,
& val ) ;
pr_debug ( " PCI: %s fn=%d base_address_%d=0x%x \n " ,
name , fn , i , val ) ;
}
# endif
celleb_config_write_fake ( * config , PCI_HEADER_TYPE , 1 ,
PCI_HEADER_TYPE_NORMAL ) ;
return 0 ;
error :
if ( mem_init_done ) {
if ( config & & * config )
kfree ( * config ) ;
if ( res & & * res )
kfree ( * res ) ;
} else {
if ( config & & * config ) {
size = 256 ;
free_bootmem ( ( unsigned long ) ( * config ) , size ) ;
}
if ( res & & * res ) {
size = sizeof ( struct celleb_pci_resource ) ;
free_bootmem ( ( unsigned long ) ( * res ) , size ) ;
}
}
return 1 ;
}
2007-07-26 19:59:17 +10:00
static int __init phb_set_bus_ranges ( struct device_node * dev ,
struct pci_controller * phb )
2007-01-12 10:03:28 +09:00
{
const int * bus_range ;
unsigned int len ;
2007-04-03 22:26:41 +10:00
bus_range = of_get_property ( dev , " bus-range " , & len ) ;
2007-01-12 10:03:28 +09:00
if ( bus_range = = NULL | | len < 2 * sizeof ( int ) )
return 1 ;
phb - > first_busno = bus_range [ 0 ] ;
phb - > last_busno = bus_range [ 1 ] ;
return 0 ;
}
2007-07-26 19:59:17 +10:00
static void __init celleb_alloc_private_mem ( struct pci_controller * hose )
2007-01-12 10:03:28 +09:00
{
2007-09-17 14:08:06 +10:00
hose - > private_data =
alloc_maybe_bootmem ( sizeof ( struct celleb_pci_private ) ,
GFP_KERNEL ) ;
2007-01-12 10:03:28 +09:00
}
2007-10-02 18:26:53 +10:00
static int __init celleb_setup_fake_pci ( struct device_node * dev ,
struct pci_controller * phb )
2007-01-12 10:03:28 +09:00
{
struct device_node * node ;
2007-10-02 18:26:53 +10:00
phb - > ops = & celleb_fake_pci_ops ;
celleb_alloc_private_mem ( phb ) ;
2007-01-12 10:03:28 +09:00
2007-10-02 18:26:53 +10:00
for ( node = of_get_next_child ( dev , NULL ) ;
node ! = NULL ; node = of_get_next_child ( dev , node ) )
celleb_setup_fake_pci_device ( node , phb ) ;
return 0 ;
}
2008-04-24 19:24:13 +10:00
static struct celleb_phb_spec celleb_fake_pci_spec __initdata = {
. setup = celleb_setup_fake_pci ,
} ;
2007-01-12 10:03:28 +09:00
2007-10-02 18:26:53 +10:00
static struct of_device_id celleb_phb_match [ ] __initdata = {
{
. name = " pci-pseudo " ,
2008-04-24 19:24:13 +10:00
. data = & celleb_fake_pci_spec ,
2007-10-02 18:26:53 +10:00
} , {
. name = " epci " ,
2008-04-24 19:24:13 +10:00
. data = & celleb_epci_spec ,
2008-04-24 20:27:39 +10:00
} , {
. name = " pcie " ,
. data = & celleb_pciex_spec ,
2007-10-02 18:26:53 +10:00
} , {
} ,
} ;
2007-01-12 10:03:28 +09:00
2008-04-24 19:24:13 +10:00
static int __init celleb_io_workaround_init ( struct pci_controller * phb ,
struct celleb_phb_spec * phb_spec )
{
if ( phb_spec - > ops ) {
iowa_register_bus ( phb , phb_spec - > ops , phb_spec - > iowa_init ,
phb_spec - > iowa_data ) ;
io_workaround_init ( ) ;
}
return 0 ;
}
2007-10-02 18:26:53 +10:00
int __init celleb_setup_phb ( struct pci_controller * phb )
{
2007-12-10 14:33:21 +11:00
struct device_node * dev = phb - > dn ;
2007-10-02 18:26:53 +10:00
const struct of_device_id * match ;
2008-04-24 19:24:13 +10:00
struct celleb_phb_spec * phb_spec ;
int rc ;
2007-10-02 18:26:53 +10:00
match = of_match_node ( celleb_phb_match , dev ) ;
if ( ! match )
2007-01-12 10:03:28 +09:00
return 1 ;
2007-10-02 18:26:53 +10:00
phb_set_bus_ranges ( dev , phb ) ;
phb - > buid = 1 ;
2008-04-24 19:24:13 +10:00
phb_spec = match - > data ;
rc = ( * phb_spec - > setup ) ( dev , phb ) ;
if ( rc )
return 1 ;
return celleb_io_workaround_init ( phb , phb_spec ) ;
2007-01-12 10:03:28 +09:00
}
int celleb_pci_probe_mode ( struct pci_bus * bus )
{
return PCI_PROBE_DEVTREE ;
}