2005-04-17 02:20:36 +04:00
/*
* linux / arch / arm / common / amba . c
*
* Copyright ( C ) 2003 Deep Blue Solutions Ltd , All Rights Reserved .
*
* 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 .
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/device.h>
2005-10-31 02:03:48 +03:00
# include <linux/string.h>
# include <linux/slab.h>
2009-01-08 12:58:51 +03:00
# include <linux/io.h>
2006-01-07 16:52:45 +03:00
# include <linux/amba/bus.h>
2005-04-17 02:20:36 +04:00
2009-01-08 12:58:51 +03:00
# include <asm/irq.h>
2005-04-17 02:20:36 +04:00
# include <asm/sizes.h>
# define to_amba_device(d) container_of(d, struct amba_device, dev)
# define to_amba_driver(d) container_of(d, struct amba_driver, drv)
static struct amba_id *
amba_lookup ( struct amba_id * table , struct amba_device * dev )
{
int ret = 0 ;
while ( table - > mask ) {
ret = ( dev - > periphid & table - > mask ) = = table - > id ;
if ( ret )
break ;
table + + ;
}
return ret ? table : NULL ;
}
static int amba_match ( struct device * dev , struct device_driver * drv )
{
struct amba_device * pcdev = to_amba_device ( dev ) ;
struct amba_driver * pcdrv = to_amba_driver ( drv ) ;
return amba_lookup ( pcdrv - > id_table , pcdev ) ! = NULL ;
}
# ifdef CONFIG_HOTPLUG
2007-08-14 17:15:12 +04:00
static int amba_uevent ( struct device * dev , struct kobj_uevent_env * env )
2005-04-17 02:20:36 +04:00
{
struct amba_device * pcdev = to_amba_device ( dev ) ;
2007-08-14 17:15:12 +04:00
int retval = 0 ;
2005-04-17 02:20:36 +04:00
2007-08-14 17:15:12 +04:00
retval = add_uevent_var ( env , " AMBA_ID=%08x " , pcdev - > periphid ) ;
2007-03-31 09:23:12 +04:00
return retval ;
2005-04-17 02:20:36 +04:00
}
# else
2005-11-26 07:04:26 +03:00
# define amba_uevent NULL
2005-04-17 02:20:36 +04:00
# endif
static int amba_suspend ( struct device * dev , pm_message_t state )
{
struct amba_driver * drv = to_amba_driver ( dev - > driver ) ;
int ret = 0 ;
if ( dev - > driver & & drv - > suspend )
ret = drv - > suspend ( to_amba_device ( dev ) , state ) ;
return ret ;
}
static int amba_resume ( struct device * dev )
{
struct amba_driver * drv = to_amba_driver ( dev - > driver ) ;
int ret = 0 ;
if ( dev - > driver & & drv - > resume )
ret = drv - > resume ( to_amba_device ( dev ) ) ;
return ret ;
}
2006-11-30 17:04:49 +03:00
# define amba_attr_func(name,fmt,arg...) \
static ssize_t name # # _show ( struct device * _dev , \
struct device_attribute * attr , char * buf ) \
{ \
struct amba_device * dev = to_amba_device ( _dev ) ; \
return sprintf ( buf , fmt , arg ) ; \
}
# define amba_attr(name,fmt,arg...) \
amba_attr_func ( name , fmt , arg ) \
static DEVICE_ATTR ( name , S_IRUGO , name # # _show , NULL )
amba_attr_func ( id , " %08x \n " , dev - > periphid ) ;
amba_attr ( irq0 , " %u \n " , dev - > irq [ 0 ] ) ;
amba_attr ( irq1 , " %u \n " , dev - > irq [ 1 ] ) ;
amba_attr_func ( resource , " \t %016llx \t %016llx \t %016lx \n " ,
( unsigned long long ) dev - > res . start , ( unsigned long long ) dev - > res . end ,
dev - > res . flags ) ;
static struct device_attribute amba_dev_attrs [ ] = {
__ATTR_RO ( id ) ,
__ATTR_RO ( resource ) ,
__ATTR_NULL ,
} ;
2005-04-17 02:20:36 +04:00
/*
* Primecells are part of the Advanced Microcontroller Bus Architecture ,
* so we call the bus " amba " .
*/
static struct bus_type amba_bustype = {
. name = " amba " ,
2006-11-30 17:04:49 +03:00
. dev_attrs = amba_dev_attrs ,
2005-04-17 02:20:36 +04:00
. match = amba_match ,
2005-11-26 07:04:26 +03:00
. uevent = amba_uevent ,
2005-04-17 02:20:36 +04:00
. suspend = amba_suspend ,
. resume = amba_resume ,
} ;
static int __init amba_init ( void )
{
return bus_register ( & amba_bustype ) ;
}
postcore_initcall ( amba_init ) ;
2010-07-15 13:47:14 +04:00
static int amba_get_enable_pclk ( struct amba_device * pcdev )
{
struct clk * pclk = clk_get ( & pcdev - > dev , " apb_pclk " ) ;
int ret ;
pcdev - > pclk = pclk ;
if ( IS_ERR ( pclk ) )
return PTR_ERR ( pclk ) ;
ret = clk_enable ( pclk ) ;
if ( ret )
clk_put ( pclk ) ;
return ret ;
}
static void amba_put_disable_pclk ( struct amba_device * pcdev )
{
struct clk * pclk = pcdev - > pclk ;
clk_disable ( pclk ) ;
clk_put ( pclk ) ;
}
2010-11-04 15:06:59 +03:00
static int amba_get_enable_vcore ( struct amba_device * pcdev )
{
struct regulator * vcore = regulator_get ( & pcdev - > dev , " vcore " ) ;
int ret ;
pcdev - > vcore = vcore ;
if ( IS_ERR ( vcore ) ) {
/* It is OK not to supply a vcore regulator */
if ( PTR_ERR ( vcore ) = = - ENODEV )
return 0 ;
return PTR_ERR ( vcore ) ;
}
ret = regulator_enable ( vcore ) ;
if ( ret ) {
regulator_put ( vcore ) ;
pcdev - > vcore = ERR_PTR ( - ENODEV ) ;
}
return ret ;
}
static void amba_put_disable_vcore ( struct amba_device * pcdev )
{
struct regulator * vcore = pcdev - > vcore ;
if ( ! IS_ERR ( vcore ) ) {
regulator_disable ( vcore ) ;
regulator_put ( vcore ) ;
}
}
2005-04-17 02:20:36 +04:00
/*
* These are the device model conversion veneers ; they convert the
* device model structures to our more specific structures .
*/
static int amba_probe ( struct device * dev )
{
struct amba_device * pcdev = to_amba_device ( dev ) ;
struct amba_driver * pcdrv = to_amba_driver ( dev - > driver ) ;
2010-07-15 13:47:14 +04:00
struct amba_id * id = amba_lookup ( pcdrv - > id_table , pcdev ) ;
int ret ;
2005-04-17 02:20:36 +04:00
2010-07-15 13:47:14 +04:00
do {
2010-11-04 15:06:59 +03:00
ret = amba_get_enable_vcore ( pcdev ) ;
if ( ret )
break ;
2010-07-15 13:47:14 +04:00
ret = amba_get_enable_pclk ( pcdev ) ;
if ( ret )
break ;
ret = pcdrv - > probe ( pcdev , id ) ;
if ( ret = = 0 )
break ;
2005-04-17 02:20:36 +04:00
2010-07-15 13:47:14 +04:00
amba_put_disable_pclk ( pcdev ) ;
2010-11-04 15:06:59 +03:00
amba_put_disable_vcore ( pcdev ) ;
2010-07-15 13:47:14 +04:00
} while ( 0 ) ;
return ret ;
2005-04-17 02:20:36 +04:00
}
static int amba_remove ( struct device * dev )
{
2010-07-15 13:47:14 +04:00
struct amba_device * pcdev = to_amba_device ( dev ) ;
2005-04-17 02:20:36 +04:00
struct amba_driver * drv = to_amba_driver ( dev - > driver ) ;
2010-07-15 13:47:14 +04:00
int ret = drv - > remove ( pcdev ) ;
amba_put_disable_pclk ( pcdev ) ;
2010-11-04 15:06:59 +03:00
amba_put_disable_vcore ( pcdev ) ;
2010-07-15 13:47:14 +04:00
return ret ;
2005-04-17 02:20:36 +04:00
}
static void amba_shutdown ( struct device * dev )
{
struct amba_driver * drv = to_amba_driver ( dev - > driver ) ;
drv - > shutdown ( to_amba_device ( dev ) ) ;
}
/**
* amba_driver_register - register an AMBA device driver
* @ drv : amba device driver structure
*
* Register an AMBA device driver with the Linux device model
* core . If devices pre - exist , the drivers probe function will
* be called .
*/
int amba_driver_register ( struct amba_driver * drv )
{
drv - > drv . bus = & amba_bustype ;
# define SETFN(fn) if (drv->fn) drv->drv.fn = amba_##fn
SETFN ( probe ) ;
SETFN ( remove ) ;
SETFN ( shutdown ) ;
return driver_register ( & drv - > drv ) ;
}
/**
* amba_driver_unregister - remove an AMBA device driver
* @ drv : AMBA device driver structure to remove
*
* Unregister an AMBA device driver from the Linux device
* model . The device model will call the drivers remove function
* for each device the device driver is currently handling .
*/
void amba_driver_unregister ( struct amba_driver * drv )
{
driver_unregister ( & drv - > drv ) ;
}
static void amba_device_release ( struct device * dev )
{
struct amba_device * d = to_amba_device ( dev ) ;
if ( d - > res . parent )
release_resource ( & d - > res ) ;
kfree ( d ) ;
}
/**
* amba_device_register - register an AMBA device
* @ dev : AMBA device to register
* @ parent : parent memory resource
*
* Setup the AMBA device , reading the cell ID if present .
* Claim the resource , and register the AMBA device with
* the Linux device manager .
*/
int amba_device_register ( struct amba_device * dev , struct resource * parent )
{
2009-07-29 02:34:59 +04:00
u32 size ;
2005-04-17 02:20:36 +04:00
void __iomem * tmp ;
int i , ret ;
2009-07-06 01:39:08 +04:00
device_initialize ( & dev - > dev ) ;
/*
* Copy from device_add
*/
if ( dev - > dev . init_name ) {
dev_set_name ( & dev - > dev , " %s " , dev - > dev . init_name ) ;
dev - > dev . init_name = NULL ;
}
2005-04-17 02:20:36 +04:00
dev - > dev . release = amba_device_release ;
dev - > dev . bus = & amba_bustype ;
dev - > dev . dma_mask = & dev - > dma_mask ;
2009-03-25 02:38:22 +03:00
dev - > res . name = dev_name ( & dev - > dev ) ;
2005-04-17 02:20:36 +04:00
if ( ! dev - > dev . coherent_dma_mask & & dev - > dma_mask )
dev_warn ( & dev - > dev , " coherent dma mask is unset \n " ) ;
ret = request_resource ( parent , & dev - > res ) ;
2006-11-30 17:04:49 +03:00
if ( ret )
goto err_out ;
2009-07-29 02:34:59 +04:00
/*
* Dynamically calculate the size of the resource
* and use this for iomap
*/
size = resource_size ( & dev - > res ) ;
tmp = ioremap ( dev - > res . start , size ) ;
2006-11-30 17:04:49 +03:00
if ( ! tmp ) {
ret = - ENOMEM ;
goto err_release ;
2005-04-17 02:20:36 +04:00
}
2006-11-30 17:04:49 +03:00
2010-07-15 13:47:14 +04:00
ret = amba_get_enable_pclk ( dev ) ;
if ( ret = = 0 ) {
u32 pid , cid ;
2006-11-30 17:04:49 +03:00
2010-07-15 13:47:14 +04:00
/*
* Read pid and cid based on size of resource
* they are located at end of region
*/
for ( pid = 0 , i = 0 ; i < 4 ; i + + )
pid | = ( readl ( tmp + size - 0x20 + 4 * i ) & 255 ) < <
( i * 8 ) ;
for ( cid = 0 , i = 0 ; i < 4 ; i + + )
cid | = ( readl ( tmp + size - 0x10 + 4 * i ) & 255 ) < <
( i * 8 ) ;
2006-11-30 17:04:49 +03:00
2010-07-15 13:47:14 +04:00
amba_put_disable_pclk ( dev ) ;
2006-11-30 17:04:49 +03:00
2010-09-08 01:43:19 +04:00
if ( cid = = AMBA_CID )
2010-07-15 13:47:14 +04:00
dev - > periphid = pid ;
if ( ! dev - > periphid )
ret = - ENODEV ;
2006-11-30 17:04:49 +03:00
}
2010-07-15 13:47:14 +04:00
iounmap ( tmp ) ;
if ( ret )
goto err_release ;
2009-07-06 01:39:08 +04:00
ret = device_add ( & dev - > dev ) ;
2006-11-30 17:04:49 +03:00
if ( ret )
goto err_release ;
if ( dev - > irq [ 0 ] ! = NO_IRQ )
ret = device_create_file ( & dev - > dev , & dev_attr_irq0 ) ;
if ( ret = = 0 & & dev - > irq [ 1 ] ! = NO_IRQ )
ret = device_create_file ( & dev - > dev , & dev_attr_irq1 ) ;
if ( ret = = 0 )
return ret ;
device_unregister ( & dev - > dev ) ;
err_release :
release_resource ( & dev - > res ) ;
err_out :
2005-04-17 02:20:36 +04:00
return ret ;
}
/**
* amba_device_unregister - unregister an AMBA device
* @ dev : AMBA device to remove
*
* Remove the specified AMBA device from the Linux device
* manager . All files associated with this object will be
* destroyed , and device drivers notified that the device has
* been removed . The AMBA device ' s resources including
* the amba_device structure will be freed once all
* references to it have been dropped .
*/
void amba_device_unregister ( struct amba_device * dev )
{
device_unregister ( & dev - > dev ) ;
}
struct find_data {
struct amba_device * dev ;
struct device * parent ;
const char * busid ;
unsigned int id ;
unsigned int mask ;
} ;
static int amba_find_match ( struct device * dev , void * data )
{
struct find_data * d = data ;
struct amba_device * pcdev = to_amba_device ( dev ) ;
int r ;
r = ( pcdev - > periphid & d - > mask ) = = d - > id ;
if ( d - > parent )
r & = d - > parent = = dev - > parent ;
if ( d - > busid )
2009-03-25 02:38:22 +03:00
r & = strcmp ( dev_name ( dev ) , d - > busid ) = = 0 ;
2005-04-17 02:20:36 +04:00
if ( r ) {
get_device ( dev ) ;
d - > dev = pcdev ;
}
return r ;
}
/**
* amba_find_device - locate an AMBA device given a bus id
* @ busid : bus id for device ( or NULL )
* @ parent : parent device ( or NULL )
* @ id : peripheral ID ( or 0 )
* @ mask : peripheral ID mask ( or 0 )
*
* Return the AMBA device corresponding to the supplied parameters .
* If no device matches , returns NULL .
*
* NOTE : When a valid device is found , its refcount is
* incremented , and must be decremented before the returned
* reference .
*/
struct amba_device *
amba_find_device ( const char * busid , struct device * parent , unsigned int id ,
unsigned int mask )
{
struct find_data data ;
data . dev = NULL ;
data . parent = parent ;
data . busid = busid ;
data . id = id ;
data . mask = mask ;
bus_for_each_dev ( & amba_bustype , NULL , & data , amba_find_match ) ;
return data . dev ;
}
/**
* amba_request_regions - request all mem regions associated with device
* @ dev : amba_device structure for device
* @ name : name , or NULL to use driver name
*/
int amba_request_regions ( struct amba_device * dev , const char * name )
{
int ret = 0 ;
2009-07-29 02:34:59 +04:00
u32 size ;
2005-04-17 02:20:36 +04:00
if ( ! name )
name = dev - > dev . driver - > name ;
2009-07-29 02:34:59 +04:00
size = resource_size ( & dev - > res ) ;
if ( ! request_mem_region ( dev - > res . start , size , name ) )
2005-04-17 02:20:36 +04:00
ret = - EBUSY ;
return ret ;
}
/**
* amba_release_regions - release mem regions assoicated with device
* @ dev : amba_device structure for device
*
* Release regions claimed by a successful call to amba_request_regions .
*/
void amba_release_regions ( struct amba_device * dev )
{
2009-07-29 02:34:59 +04:00
u32 size ;
size = resource_size ( & dev - > res ) ;
release_mem_region ( dev - > res . start , size ) ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL ( amba_driver_register ) ;
EXPORT_SYMBOL ( amba_driver_unregister ) ;
EXPORT_SYMBOL ( amba_device_register ) ;
EXPORT_SYMBOL ( amba_device_unregister ) ;
EXPORT_SYMBOL ( amba_find_device ) ;
EXPORT_SYMBOL ( amba_request_regions ) ;
EXPORT_SYMBOL ( amba_release_regions ) ;