2005-04-16 15:20:36 -07:00
/*
* ocp . c
*
* ( c ) Benjamin Herrenschmidt ( benh @ kernel . crashing . org )
* Mipsys - France
*
* Derived from work ( c ) Armin Kuster akuster @ pacbell . net
*
* Additional support and port to 2.6 LDM / sysfs by
* Matt Porter < mporter @ kernel . crashing . org >
* Copyright 2004 MontaVista Software , Inc .
*
* 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 .
*
* OCP ( On Chip Peripheral ) is a software emulated " bus " with a
* pseudo discovery method for dumb peripherals . Usually these type
* of peripherals are found on embedded SoC ( System On a Chip )
* processors or highly integrated system controllers that have
* a host bridge and many peripherals . Common examples where
* this is already used include the PPC4xx , PPC85xx , MPC52xx ,
* and MV64xxx parts .
*
* This subsystem creates a standard OCP bus type within the
* device model . The devices on the OCP bus are seeded by an
* an initial OCP device array created by the arch - specific
* Device entries can be added / removed / modified through OCP
* helper functions to accomodate system and board - specific
* parameters commonly found in embedded systems . OCP also
* provides a standard method for devices to describe extended
* attributes about themselves to the system . A standard access
* method allows OCP drivers to obtain the information , both
* SoC - specific and system / board - specific , needed for operation .
*/
# include <linux/module.h>
# include <linux/config.h>
# include <linux/list.h>
# include <linux/miscdevice.h>
# include <linux/slab.h>
# include <linux/types.h>
# include <linux/init.h>
# include <linux/pm.h>
# include <linux/bootmem.h>
# include <linux/device.h>
# include <asm/io.h>
# include <asm/ocp.h>
# include <asm/errno.h>
# include <asm/rwsem.h>
# include <asm/semaphore.h>
//#define DBG(x) printk x
# define DBG(x)
extern int mem_init_done ;
extern struct ocp_def core_ocp [ ] ; /* Static list of devices, provided by
CPU core */
LIST_HEAD ( ocp_devices ) ; /* List of all OCP devices */
DECLARE_RWSEM ( ocp_devices_sem ) ; /* Global semaphores for those lists */
static int ocp_inited ;
/* Sysfs support */
# define OCP_DEF_ATTR(field, format_string) \
static ssize_t \
2005-05-17 06:40:51 -04:00
show_ # # field ( struct device * dev , struct device_attribute * attr , char * buf ) \
2005-04-16 15:20:36 -07:00
{ \
struct ocp_device * odev = to_ocp_dev ( dev ) ; \
\
return sprintf ( buf , format_string , odev - > def - > field ) ; \
} \
static DEVICE_ATTR ( field , S_IRUGO , show_ # # field , NULL ) ;
OCP_DEF_ATTR ( vendor , " 0x%04x \n " ) ;
OCP_DEF_ATTR ( function , " 0x%04x \n " ) ;
OCP_DEF_ATTR ( index , " 0x%04x \n " ) ;
# ifdef CONFIG_PTE_64BIT
OCP_DEF_ATTR ( paddr , " 0x%016Lx \n " ) ;
# else
OCP_DEF_ATTR ( paddr , " 0x%08lx \n " ) ;
# endif
OCP_DEF_ATTR ( irq , " %d \n " ) ;
OCP_DEF_ATTR ( pm , " %lu \n " ) ;
void ocp_create_sysfs_dev_files ( struct ocp_device * odev )
{
struct device * dev = & odev - > dev ;
/* Current OCP device def attributes */
device_create_file ( dev , & dev_attr_vendor ) ;
device_create_file ( dev , & dev_attr_function ) ;
device_create_file ( dev , & dev_attr_index ) ;
device_create_file ( dev , & dev_attr_paddr ) ;
device_create_file ( dev , & dev_attr_irq ) ;
device_create_file ( dev , & dev_attr_pm ) ;
/* Current OCP device additions attributes */
if ( odev - > def - > additions & & odev - > def - > show )
odev - > def - > show ( dev ) ;
}
/**
* ocp_device_match - Match one driver to one device
* @ drv : driver to match
* @ dev : device to match
*
* This function returns 0 if the driver and device don ' t match
*/
static int
ocp_device_match ( struct device * dev , struct device_driver * drv )
{
struct ocp_device * ocp_dev = to_ocp_dev ( dev ) ;
struct ocp_driver * ocp_drv = to_ocp_drv ( drv ) ;
const struct ocp_device_id * ids = ocp_drv - > id_table ;
if ( ! ids )
return 0 ;
while ( ids - > vendor | | ids - > function ) {
if ( ( ids - > vendor = = OCP_ANY_ID
| | ids - > vendor = = ocp_dev - > def - > vendor )
& & ( ids - > function = = OCP_ANY_ID
| | ids - > function = = ocp_dev - > def - > function ) )
return 1 ;
ids + + ;
}
return 0 ;
}
static int
ocp_device_probe ( struct device * dev )
{
int error = 0 ;
struct ocp_driver * drv ;
struct ocp_device * ocp_dev ;
drv = to_ocp_drv ( dev - > driver ) ;
ocp_dev = to_ocp_dev ( dev ) ;
if ( drv - > probe ) {
error = drv - > probe ( ocp_dev ) ;
if ( error > = 0 ) {
ocp_dev - > driver = drv ;
error = 0 ;
}
}
return error ;
}
static int
ocp_device_remove ( struct device * dev )
{
struct ocp_device * ocp_dev = to_ocp_dev ( dev ) ;
if ( ocp_dev - > driver ) {
if ( ocp_dev - > driver - > remove )
ocp_dev - > driver - > remove ( ocp_dev ) ;
ocp_dev - > driver = NULL ;
}
return 0 ;
}
static int
2005-09-06 15:16:13 -07:00
ocp_device_suspend ( struct device * dev , pm_message_t state )
2005-04-16 15:20:36 -07:00
{
struct ocp_device * ocp_dev = to_ocp_dev ( dev ) ;
struct ocp_driver * ocp_drv = to_ocp_drv ( dev - > driver ) ;
if ( dev - > driver & & ocp_drv - > suspend )
return ocp_drv - > suspend ( ocp_dev , state ) ;
return 0 ;
}
static int
ocp_device_resume ( struct device * dev )
{
struct ocp_device * ocp_dev = to_ocp_dev ( dev ) ;
struct ocp_driver * ocp_drv = to_ocp_drv ( dev - > driver ) ;
if ( dev - > driver & & ocp_drv - > resume )
return ocp_drv - > resume ( ocp_dev ) ;
return 0 ;
}
struct bus_type ocp_bus_type = {
. name = " ocp " ,
. match = ocp_device_match ,
2006-01-18 18:40:16 -05:00
. probe = ocp_device_probe ,
. remove = ocp_device_remove ,
2005-04-16 15:20:36 -07:00
. suspend = ocp_device_suspend ,
. resume = ocp_device_resume ,
} ;
/**
* ocp_register_driver - Register an OCP driver
* @ drv : pointer to statically defined ocp_driver structure
*
* The driver ' s probe ( ) callback is called either recursively
* by this function or upon later call of ocp_driver_init
*
* NOTE : Detection of devices is a 2 pass step on this implementation ,
* hotswap isn ' t supported . First , all OCP devices are put in the device
* list , _then_ all drivers are probed on each match .
*/
int
ocp_register_driver ( struct ocp_driver * drv )
{
/* initialize common driver fields */
drv - > driver . name = drv - > name ;
drv - > driver . bus = & ocp_bus_type ;
/* register with core */
return driver_register ( & drv - > driver ) ;
}
/**
* ocp_unregister_driver - Unregister an OCP driver
* @ drv : pointer to statically defined ocp_driver structure
*
* The driver ' s remove ( ) callback is called recursively
* by this function for any device already registered
*/
void
ocp_unregister_driver ( struct ocp_driver * drv )
{
DBG ( ( " ocp: ocp_unregister_driver(%s)... \n " , drv - > name ) ) ;
driver_unregister ( & drv - > driver ) ;
DBG ( ( " ocp: ocp_unregister_driver(%s)... done. \n " , drv - > name ) ) ;
}
/* Core of ocp_find_device(). Caller must hold ocp_devices_sem */
static struct ocp_device *
__ocp_find_device ( unsigned int vendor , unsigned int function , int index )
{
struct list_head * entry ;
struct ocp_device * dev , * found = NULL ;
DBG ( ( " ocp: __ocp_find_device(vendor: %x, function: %x, index: %d)... \n " , vendor , function , index ) ) ;
list_for_each ( entry , & ocp_devices ) {
dev = list_entry ( entry , struct ocp_device , link ) ;
if ( vendor ! = OCP_ANY_ID & & vendor ! = dev - > def - > vendor )
continue ;
if ( function ! = OCP_ANY_ID & & function ! = dev - > def - > function )
continue ;
if ( index ! = OCP_ANY_INDEX & & index ! = dev - > def - > index )
continue ;
found = dev ;
break ;
}
DBG ( ( " ocp: __ocp_find_device(vendor: %x, function: %x, index: %d)... done \n " , vendor , function , index ) ) ;
return found ;
}
/**
* ocp_find_device - Find a device by function & index
* @ vendor : vendor ID of the device ( or OCP_ANY_ID )
* @ function : function code of the device ( or OCP_ANY_ID )
* @ idx : index of the device ( or OCP_ANY_INDEX )
*
* This function allows a lookup of a given function by it ' s
* index , it ' s typically used to find the MAL or ZMII associated
* with an EMAC or similar horrors .
* You can pass vendor , though you usually want OCP_ANY_ID there . . .
*/
struct ocp_device *
ocp_find_device ( unsigned int vendor , unsigned int function , int index )
{
struct ocp_device * dev ;
down_read ( & ocp_devices_sem ) ;
dev = __ocp_find_device ( vendor , function , index ) ;
up_read ( & ocp_devices_sem ) ;
return dev ;
}
/**
* ocp_get_one_device - Find a def by function & index
* @ vendor : vendor ID of the device ( or OCP_ANY_ID )
* @ function : function code of the device ( or OCP_ANY_ID )
* @ idx : index of the device ( or OCP_ANY_INDEX )
*
* This function allows a lookup of a given ocp_def by it ' s
* vendor , function , and index . The main purpose for is to
* allow modification of the def before binding to the driver
*/
struct ocp_def *
ocp_get_one_device ( unsigned int vendor , unsigned int function , int index )
{
struct ocp_device * dev ;
struct ocp_def * found = NULL ;
DBG ( ( " ocp: ocp_get_one_device(vendor: %x, function: %x, index: %d)... \n " ,
vendor , function , index ) ) ;
dev = ocp_find_device ( vendor , function , index ) ;
if ( dev )
found = dev - > def ;
DBG ( ( " ocp: ocp_get_one_device(vendor: %x, function: %x, index: %d)... done. \n " ,
vendor , function , index ) ) ;
return found ;
}
/**
* ocp_add_one_device - Add a device
* @ def : static device definition structure
*
* This function adds a device definition to the
* device list . It may only be called before
* ocp_driver_init ( ) and will return an error
* otherwise .
*/
int
ocp_add_one_device ( struct ocp_def * def )
{
struct ocp_device * dev ;
DBG ( ( " ocp: ocp_add_one_device()... \n " ) ) ;
/* Can't be called after ocp driver init */
if ( ocp_inited )
return 1 ;
if ( mem_init_done )
dev = kmalloc ( sizeof ( * dev ) , GFP_KERNEL ) ;
else
dev = alloc_bootmem ( sizeof ( * dev ) ) ;
if ( dev = = NULL )
return 1 ;
memset ( dev , 0 , sizeof ( * dev ) ) ;
dev - > def = def ;
dev - > current_state = 4 ;
sprintf ( dev - > name , " OCP device %04x:%04x:%04x " ,
dev - > def - > vendor , dev - > def - > function , dev - > def - > index ) ;
down_write ( & ocp_devices_sem ) ;
list_add_tail ( & dev - > link , & ocp_devices ) ;
up_write ( & ocp_devices_sem ) ;
DBG ( ( " ocp: ocp_add_one_device()...done \n " ) ) ;
return 0 ;
}
/**
* ocp_remove_one_device - Remove a device by function & index
* @ vendor : vendor ID of the device ( or OCP_ANY_ID )
* @ function : function code of the device ( or OCP_ANY_ID )
* @ idx : index of the device ( or OCP_ANY_INDEX )
*
* This function allows removal of a given function by its
* index . It may only be called before ocp_driver_init ( )
* and will return an error otherwise .
*/
int
ocp_remove_one_device ( unsigned int vendor , unsigned int function , int index )
{
struct ocp_device * dev ;
DBG ( ( " ocp: ocp_remove_one_device(vendor: %x, function: %x, index: %d)... \n " , vendor , function , index ) ) ;
/* Can't be called after ocp driver init */
if ( ocp_inited )
return 1 ;
down_write ( & ocp_devices_sem ) ;
dev = __ocp_find_device ( vendor , function , index ) ;
list_del ( ( struct list_head * ) dev ) ;
up_write ( & ocp_devices_sem ) ;
DBG ( ( " ocp: ocp_remove_one_device(vendor: %x, function: %x, index: %d)... done. \n " , vendor , function , index ) ) ;
return 0 ;
}
/**
* ocp_for_each_device - Iterate over OCP devices
* @ callback : routine to execute for each ocp device .
* @ arg : user data to be passed to callback routine .
*
* This routine holds the ocp_device semaphore , so the
* callback routine cannot modify the ocp_device list .
*/
void
ocp_for_each_device ( void ( * callback ) ( struct ocp_device * , void * arg ) , void * arg )
{
struct list_head * entry ;
if ( callback ) {
down_read ( & ocp_devices_sem ) ;
list_for_each ( entry , & ocp_devices )
callback ( list_entry ( entry , struct ocp_device , link ) ,
arg ) ;
up_read ( & ocp_devices_sem ) ;
}
}
/**
* ocp_early_init - Init OCP device management
*
* This function builds the list of devices before setup_arch .
* This allows platform code to modify the device lists before
* they are bound to drivers ( changes to paddr , removing devices
* etc )
*/
int __init
ocp_early_init ( void )
{
struct ocp_def * def ;
DBG ( ( " ocp: ocp_early_init()... \n " ) ) ;
/* Fill the devices list */
for ( def = core_ocp ; def - > vendor ! = OCP_VENDOR_INVALID ; def + + )
ocp_add_one_device ( def ) ;
DBG ( ( " ocp: ocp_early_init()... done. \n " ) ) ;
return 0 ;
}
/**
* ocp_driver_init - Init OCP device management
*
* This function is meant to be called via OCP bus registration .
*/
static int __init
ocp_driver_init ( void )
{
int ret = 0 , index = 0 ;
struct device * ocp_bus ;
struct list_head * entry ;
struct ocp_device * dev ;
if ( ocp_inited )
return ret ;
ocp_inited = 1 ;
DBG ( ( " ocp: ocp_driver_init()... \n " ) ) ;
/* Allocate/register primary OCP bus */
2006-03-06 21:13:32 +01:00
ocp_bus = kzalloc ( sizeof ( struct device ) , GFP_KERNEL ) ;
2005-04-16 15:20:36 -07:00
if ( ocp_bus = = NULL )
return 1 ;
strcpy ( ocp_bus - > bus_id , " ocp " ) ;
bus_register ( & ocp_bus_type ) ;
device_register ( ocp_bus ) ;
/* Put each OCP device into global device list */
list_for_each ( entry , & ocp_devices ) {
dev = list_entry ( entry , struct ocp_device , link ) ;
sprintf ( dev - > dev . bus_id , " %2.2x " , index ) ;
dev - > dev . parent = ocp_bus ;
dev - > dev . bus = & ocp_bus_type ;
device_register ( & dev - > dev ) ;
ocp_create_sysfs_dev_files ( dev ) ;
index + + ;
}
DBG ( ( " ocp: ocp_driver_init()... done. \n " ) ) ;
return 0 ;
}
postcore_initcall ( ocp_driver_init ) ;
EXPORT_SYMBOL ( ocp_bus_type ) ;
EXPORT_SYMBOL ( ocp_find_device ) ;
EXPORT_SYMBOL ( ocp_register_driver ) ;
EXPORT_SYMBOL ( ocp_unregister_driver ) ;