2009-09-03 20:14:05 +03:00
/*
* omap_device implementation
*
* Copyright ( C ) 2009 Nokia Corporation
* Paul Walmsley
*
* Developed in collaboration with ( alphabetical order ) : Benoit
* Cousson , Kevin Hilman , Tony Lindgren , Rajendra Nayak , Vikram
* Pandita , Sakari Poussa , Anand Sawant , Santosh Shilimkar , Richard
* Woodruff
*
* 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 code provides a consistent interface for OMAP device drivers
* to control power management and interconnect properties of their
* devices .
*
* In the medium - to long - term , this code should either be
* a ) implemented via arch - specific pointers in platform_data
* or
* b ) implemented as a proper omap_bus / omap_device in Linux , no more
* platform_data func pointers
*
*
* Guidelines for usage by driver authors :
*
* 1. These functions are intended to be used by device drivers via
* function pointers in struct platform_data . As an example ,
* omap_device_enable ( ) should be passed to the driver as
*
* struct foo_driver_platform_data {
* . . .
* int ( * device_enable ) ( struct platform_device * pdev ) ;
* . . .
* }
*
* Note that the generic " device_enable " name is used , rather than
* " omap_device_enable " . This is so other architectures can pass in their
* own enable / disable functions here .
*
* This should be populated during device setup :
*
* . . .
* pdata - > device_enable = omap_device_enable ;
* . . .
*
* 2. Drivers should first check to ensure the function pointer is not null
* before calling it , as in :
*
* if ( pdata - > device_enable )
* pdata - > device_enable ( pdev ) ;
*
* This allows other architectures that don ' t use similar device_enable ( ) /
* device_shutdown ( ) functions to execute normally .
*
* . . .
*
* Suggested usage by device drivers :
*
* During device initialization :
* device_enable ( )
*
* During device idle :
* ( save remaining device context if necessary )
* device_idle ( ) ;
*
* During device resume :
* device_enable ( ) ;
* ( restore context if necessary )
*
* During device shutdown :
* device_shutdown ( )
* ( device must be reinitialized at this point to use it again )
*
*/
# undef DEBUG
# include <linux/kernel.h>
# include <linux/platform_device.h>
# include <linux/err.h>
# include <linux/io.h>
2009-10-20 09:40:47 -07:00
# include <plat/omap_device.h>
# include <plat/omap_hwmod.h>
2009-09-03 20:14:05 +03:00
/* These parameters are passed to _omap_device_{de,}activate() */
# define USE_WAKEUP_LAT 0
# define IGNORE_WAKEUP_LAT 1
/* Private functions */
/**
* _omap_device_activate - increase device readiness
* @ od : struct omap_device *
* @ ignore_lat : increase to latency target ( 0 ) or full readiness ( 1 ) ?
*
* Increase readiness of omap_device @ od ( thus decreasing device
* wakeup latency , but consuming more power ) . If @ ignore_lat is
* IGNORE_WAKEUP_LAT , make the omap_device fully active . Otherwise ,
* if @ ignore_lat is USE_WAKEUP_LAT , and the device ' s maximum wakeup
* latency is greater than the requested maximum wakeup latency , step
* backwards in the omap_device_pm_latency table to ensure the
* device ' s maximum wakeup latency is less than or equal to the
* requested maximum wakeup latency . Returns 0.
*/
static int _omap_device_activate ( struct omap_device * od , u8 ignore_lat )
{
2009-10-19 15:25:24 -07:00
struct timespec a , b , c ;
2009-09-03 20:14:05 +03:00
pr_debug ( " omap_device: %s: activating \n " , od - > pdev . name ) ;
while ( od - > pm_lat_level > 0 ) {
struct omap_device_pm_latency * odpl ;
2009-10-19 15:25:24 -07:00
unsigned long long act_lat = 0 ;
2009-09-03 20:14:05 +03:00
od - > pm_lat_level - - ;
odpl = od - > pm_lats + od - > pm_lat_level ;
if ( ! ignore_lat & &
( od - > dev_wakeup_lat < = od - > _dev_wakeup_lat_limit ) )
break ;
2009-12-08 16:34:23 -07:00
read_persistent_clock ( & a ) ;
2009-09-03 20:14:05 +03:00
/* XXX check return code */
odpl - > activate_func ( od ) ;
2009-12-08 16:34:23 -07:00
read_persistent_clock ( & b ) ;
2009-09-03 20:14:05 +03:00
2009-10-19 15:25:24 -07:00
c = timespec_sub ( b , a ) ;
2009-12-08 16:34:26 -07:00
act_lat = timespec_to_ns ( & c ) ;
2009-09-03 20:14:05 +03:00
pr_debug ( " omap_device: %s: pm_lat %d: activate: elapsed time "
2009-12-08 16:34:26 -07:00
" %llu nsec \n " , od - > pdev . name , od - > pm_lat_level ,
2009-10-19 15:25:24 -07:00
act_lat ) ;
2009-09-03 20:14:05 +03:00
WARN ( act_lat > odpl - > activate_lat , " omap_device: %s.%d: "
2009-10-19 15:25:24 -07:00
" activate step %d took longer than expected (%llu > %d) \n " ,
2009-09-03 20:14:05 +03:00
od - > pdev . name , od - > pdev . id , od - > pm_lat_level ,
act_lat , odpl - > activate_lat ) ;
od - > dev_wakeup_lat - = odpl - > activate_lat ;
}
return 0 ;
}
/**
* _omap_device_deactivate - decrease device readiness
* @ od : struct omap_device *
* @ ignore_lat : decrease to latency target ( 0 ) or full inactivity ( 1 ) ?
*
* Decrease readiness of omap_device @ od ( thus increasing device
* wakeup latency , but conserving power ) . If @ ignore_lat is
* IGNORE_WAKEUP_LAT , make the omap_device fully inactive . Otherwise ,
* if @ ignore_lat is USE_WAKEUP_LAT , and the device ' s maximum wakeup
* latency is less than the requested maximum wakeup latency , step
* forwards in the omap_device_pm_latency table to ensure the device ' s
* maximum wakeup latency is less than or equal to the requested
* maximum wakeup latency . Returns 0.
*/
static int _omap_device_deactivate ( struct omap_device * od , u8 ignore_lat )
{
2009-10-19 15:25:24 -07:00
struct timespec a , b , c ;
2009-09-03 20:14:05 +03:00
pr_debug ( " omap_device: %s: deactivating \n " , od - > pdev . name ) ;
while ( od - > pm_lat_level < od - > pm_lats_cnt ) {
struct omap_device_pm_latency * odpl ;
2009-10-19 15:25:24 -07:00
unsigned long long deact_lat = 0 ;
2009-09-03 20:14:05 +03:00
odpl = od - > pm_lats + od - > pm_lat_level ;
if ( ! ignore_lat & &
( ( od - > dev_wakeup_lat + odpl - > activate_lat ) >
od - > _dev_wakeup_lat_limit ) )
break ;
2009-12-08 16:34:23 -07:00
read_persistent_clock ( & a ) ;
2009-09-03 20:14:05 +03:00
/* XXX check return code */
odpl - > deactivate_func ( od ) ;
2009-12-08 16:34:23 -07:00
read_persistent_clock ( & b ) ;
2009-09-03 20:14:05 +03:00
2009-10-19 15:25:24 -07:00
c = timespec_sub ( b , a ) ;
2009-12-08 16:34:26 -07:00
deact_lat = timespec_to_ns ( & c ) ;
2009-09-03 20:14:05 +03:00
pr_debug ( " omap_device: %s: pm_lat %d: deactivate: elapsed time "
2009-12-08 16:34:26 -07:00
" %llu nsec \n " , od - > pdev . name , od - > pm_lat_level ,
2009-09-03 20:14:05 +03:00
deact_lat ) ;
WARN ( deact_lat > odpl - > deactivate_lat , " omap_device: %s.%d: "
2009-10-19 15:25:24 -07:00
" deactivate step %d took longer than expected "
" (%llu > %d) \n " , od - > pdev . name , od - > pdev . id ,
od - > pm_lat_level , deact_lat , odpl - > deactivate_lat ) ;
2009-09-03 20:14:05 +03:00
od - > dev_wakeup_lat + = odpl - > activate_lat ;
od - > pm_lat_level + + ;
}
return 0 ;
}
static inline struct omap_device * _find_by_pdev ( struct platform_device * pdev )
{
return container_of ( pdev , struct omap_device , pdev ) ;
}
/* Public functions for use by core code */
/**
* omap_device_count_resources - count number of struct resource entries needed
* @ od : struct omap_device *
*
* Count the number of struct resource entries needed for this
* omap_device @ od . Used by omap_device_build_ss ( ) to determine how
* much memory to allocate before calling
* omap_device_fill_resources ( ) . Returns the count .
*/
int omap_device_count_resources ( struct omap_device * od )
{
struct omap_hwmod * oh ;
int c = 0 ;
int i ;
for ( i = 0 , oh = * od - > hwmods ; i < od - > hwmods_cnt ; i + + , oh + + )
c + = omap_hwmod_count_resources ( oh ) ;
pr_debug ( " omap_device: %s: counted %d total resources across %d "
" hwmods \n " , od - > pdev . name , c , od - > hwmods_cnt ) ;
return c ;
}
/**
* omap_device_fill_resources - fill in array of struct resource
* @ od : struct omap_device *
* @ res : pointer to an array of struct resource to be filled in
*
* Populate one or more empty struct resource pointed to by @ res with
* the resource data for this omap_device @ od . Used by
* omap_device_build_ss ( ) after calling omap_device_count_resources ( ) .
* Ideally this function would not be needed at all . If omap_device
* replaces platform_device , then we can specify our own
* get_resource ( ) / get_irq ( ) / etc functions that use the underlying
* omap_hwmod information . Or if platform_device is extended to use
* subarchitecture - specific function pointers , the various
* platform_device functions can simply call omap_device internal
* functions to get device resources . Hacking around the existing
* platform_device code wastes memory . Returns 0.
*/
int omap_device_fill_resources ( struct omap_device * od , struct resource * res )
{
struct omap_hwmod * oh ;
int c = 0 ;
int i , r ;
for ( i = 0 , oh = * od - > hwmods ; i < od - > hwmods_cnt ; i + + , oh + + ) {
r = omap_hwmod_fill_resources ( oh , res ) ;
res + = r ;
c + = r ;
}
return 0 ;
}
/**
* omap_device_build - build and register an omap_device with one omap_hwmod
* @ pdev_name : name of the platform_device driver to use
* @ pdev_id : this platform_device ' s connection ID
* @ oh : ptr to the single omap_hwmod that backs this omap_device
* @ pdata : platform_data ptr to associate with the platform_device
* @ pdata_len : amount of memory pointed to by @ pdata
* @ pm_lats : pointer to a omap_device_pm_latency array for this device
* @ pm_lats_cnt : ARRAY_SIZE ( ) of @ pm_lats
*
* Convenience function for building and registering a single
* omap_device record , which in turn builds and registers a
* platform_device record . See omap_device_build_ss ( ) for more
* information . Returns ERR_PTR ( - EINVAL ) if @ oh is NULL ; otherwise ,
* passes along the return value of omap_device_build_ss ( ) .
*/
struct omap_device * omap_device_build ( const char * pdev_name , int pdev_id ,
struct omap_hwmod * oh , void * pdata ,
int pdata_len ,
struct omap_device_pm_latency * pm_lats ,
int pm_lats_cnt )
{
struct omap_hwmod * ohs [ ] = { oh } ;
if ( ! oh )
return ERR_PTR ( - EINVAL ) ;
return omap_device_build_ss ( pdev_name , pdev_id , ohs , 1 , pdata ,
pdata_len , pm_lats , pm_lats_cnt ) ;
}
/**
* omap_device_build_ss - build and register an omap_device with multiple hwmods
* @ pdev_name : name of the platform_device driver to use
* @ pdev_id : this platform_device ' s connection ID
* @ oh : ptr to the single omap_hwmod that backs this omap_device
* @ pdata : platform_data ptr to associate with the platform_device
* @ pdata_len : amount of memory pointed to by @ pdata
* @ pm_lats : pointer to a omap_device_pm_latency array for this device
* @ pm_lats_cnt : ARRAY_SIZE ( ) of @ pm_lats
*
* Convenience function for building and registering an omap_device
* subsystem record . Subsystem records consist of multiple
* omap_hwmods . This function in turn builds and registers a
* platform_device record . Returns an ERR_PTR ( ) on error , or passes
* along the return value of omap_device_register ( ) .
*/
struct omap_device * omap_device_build_ss ( const char * pdev_name , int pdev_id ,
struct omap_hwmod * * ohs , int oh_cnt ,
void * pdata , int pdata_len ,
struct omap_device_pm_latency * pm_lats ,
int pm_lats_cnt )
{
int ret = - ENOMEM ;
struct omap_device * od ;
char * pdev_name2 ;
struct resource * res = NULL ;
int res_count ;
struct omap_hwmod * * hwmods ;
if ( ! ohs | | oh_cnt = = 0 | | ! pdev_name )
return ERR_PTR ( - EINVAL ) ;
if ( ! pdata & & pdata_len > 0 )
return ERR_PTR ( - EINVAL ) ;
pr_debug ( " omap_device: %s: building with %d hwmods \n " , pdev_name ,
oh_cnt ) ;
od = kzalloc ( sizeof ( struct omap_device ) , GFP_KERNEL ) ;
if ( ! od )
return ERR_PTR ( - ENOMEM ) ;
od - > hwmods_cnt = oh_cnt ;
hwmods = kzalloc ( sizeof ( struct omap_hwmod * ) * oh_cnt ,
GFP_KERNEL ) ;
if ( ! hwmods )
goto odbs_exit1 ;
memcpy ( hwmods , ohs , sizeof ( struct omap_hwmod * ) * oh_cnt ) ;
od - > hwmods = hwmods ;
pdev_name2 = kzalloc ( strlen ( pdev_name ) + 1 , GFP_KERNEL ) ;
if ( ! pdev_name2 )
goto odbs_exit2 ;
strcpy ( pdev_name2 , pdev_name ) ;
od - > pdev . name = pdev_name2 ;
od - > pdev . id = pdev_id ;
res_count = omap_device_count_resources ( od ) ;
if ( res_count > 0 ) {
res = kzalloc ( sizeof ( struct resource ) * res_count , GFP_KERNEL ) ;
if ( ! res )
goto odbs_exit3 ;
}
omap_device_fill_resources ( od , res ) ;
od - > pdev . num_resources = res_count ;
od - > pdev . resource = res ;
platform_device_add_data ( & od - > pdev , pdata , pdata_len ) ;
od - > pm_lats = pm_lats ;
od - > pm_lats_cnt = pm_lats_cnt ;
ret = omap_device_register ( od ) ;
if ( ret )
goto odbs_exit4 ;
return od ;
odbs_exit4 :
kfree ( res ) ;
odbs_exit3 :
kfree ( pdev_name2 ) ;
odbs_exit2 :
kfree ( hwmods ) ;
odbs_exit1 :
kfree ( od ) ;
pr_err ( " omap_device: %s: build failed (%d) \n " , pdev_name , ret ) ;
return ERR_PTR ( ret ) ;
}
/**
* omap_device_register - register an omap_device with one omap_hwmod
* @ od : struct omap_device * to register
*
* Register the omap_device structure . This currently just calls
* platform_device_register ( ) on the underlying platform_device .
* Returns the return value of platform_device_register ( ) .
*/
int omap_device_register ( struct omap_device * od )
{
pr_debug ( " omap_device: %s: registering \n " , od - > pdev . name ) ;
return platform_device_register ( & od - > pdev ) ;
}
/* Public functions for use by device drivers through struct platform_data */
/**
* omap_device_enable - fully activate an omap_device
* @ od : struct omap_device * to activate
*
* Do whatever is necessary for the hwmods underlying omap_device @ od
* to be accessible and ready to operate . This generally involves
* enabling clocks , setting SYSCONFIG registers ; and in the future may
* involve remuxing pins . Device drivers should call this function
* ( through platform_data function pointers ) where they would normally
* enable clocks , etc . Returns - EINVAL if called when the omap_device
* is already enabled , or passes along the return value of
* _omap_device_activate ( ) .
*/
int omap_device_enable ( struct platform_device * pdev )
{
int ret ;
struct omap_device * od ;
od = _find_by_pdev ( pdev ) ;
if ( od - > _state = = OMAP_DEVICE_STATE_ENABLED ) {
WARN ( 1 , " omap_device: %s.%d: omap_device_enable() called from "
" invalid state \n " , od - > pdev . name , od - > pdev . id ) ;
return - EINVAL ;
}
/* Enable everything if we're enabling this device from scratch */
if ( od - > _state = = OMAP_DEVICE_STATE_UNKNOWN )
od - > pm_lat_level = od - > pm_lats_cnt ;
ret = _omap_device_activate ( od , IGNORE_WAKEUP_LAT ) ;
od - > dev_wakeup_lat = 0 ;
2009-12-08 16:34:22 -07:00
od - > _dev_wakeup_lat_limit = UINT_MAX ;
2009-09-03 20:14:05 +03:00
od - > _state = OMAP_DEVICE_STATE_ENABLED ;
return ret ;
}
/**
* omap_device_idle - idle an omap_device
* @ od : struct omap_device * to idle
*
* Idle omap_device @ od by calling as many . deactivate_func ( ) entries
* in the omap_device ' s pm_lats table as is possible without exceeding
* the device ' s maximum wakeup latency limit , pm_lat_limit . Device
* drivers should call this function ( through platform_data function
* pointers ) where they would normally disable clocks after operations
* complete , etc . . Returns - EINVAL if the omap_device is not
* currently enabled , or passes along the return value of
* _omap_device_deactivate ( ) .
*/
int omap_device_idle ( struct platform_device * pdev )
{
int ret ;
struct omap_device * od ;
od = _find_by_pdev ( pdev ) ;
if ( od - > _state ! = OMAP_DEVICE_STATE_ENABLED ) {
WARN ( 1 , " omap_device: %s.%d: omap_device_idle() called from "
" invalid state \n " , od - > pdev . name , od - > pdev . id ) ;
return - EINVAL ;
}
ret = _omap_device_deactivate ( od , USE_WAKEUP_LAT ) ;
od - > _state = OMAP_DEVICE_STATE_IDLE ;
return ret ;
}
/**
* omap_device_shutdown - shut down an omap_device
* @ od : struct omap_device * to shut down
*
* Shut down omap_device @ od by calling all . deactivate_func ( ) entries
* in the omap_device ' s pm_lats table and then shutting down all of
* the underlying omap_hwmods . Used when a device is being " removed "
* or a device driver is being unloaded . Returns - EINVAL if the
* omap_device is not currently enabled or idle , or passes along the
* return value of _omap_device_deactivate ( ) .
*/
int omap_device_shutdown ( struct platform_device * pdev )
{
int ret , i ;
struct omap_device * od ;
struct omap_hwmod * oh ;
od = _find_by_pdev ( pdev ) ;
if ( od - > _state ! = OMAP_DEVICE_STATE_ENABLED & &
od - > _state ! = OMAP_DEVICE_STATE_IDLE ) {
WARN ( 1 , " omap_device: %s.%d: omap_device_shutdown() called "
" from invalid state \n " , od - > pdev . name , od - > pdev . id ) ;
return - EINVAL ;
}
ret = _omap_device_deactivate ( od , IGNORE_WAKEUP_LAT ) ;
for ( i = 0 , oh = * od - > hwmods ; i < od - > hwmods_cnt ; i + + , oh + + )
omap_hwmod_shutdown ( oh ) ;
od - > _state = OMAP_DEVICE_STATE_SHUTDOWN ;
return ret ;
}
/**
* omap_device_align_pm_lat - activate / deactivate device to match wakeup lat lim
* @ od : struct omap_device *
*
* When a device ' s maximum wakeup latency limit changes , call some of
* the . activate_func or . deactivate_func function pointers in the
* omap_device ' s pm_lats array to ensure that the device ' s maximum
* wakeup latency is less than or equal to the new latency limit .
* Intended to be called by OMAP PM code whenever a device ' s maximum
* wakeup latency limit changes ( e . g . , via
* omap_pm_set_dev_wakeup_lat ( ) ) . Returns 0 if nothing needs to be
* done ( e . g . , if the omap_device is not currently idle , or if the
* wakeup latency is already current with the new limit ) or passes
* along the return value of _omap_device_deactivate ( ) or
* _omap_device_activate ( ) .
*/
int omap_device_align_pm_lat ( struct platform_device * pdev ,
u32 new_wakeup_lat_limit )
{
int ret = - EINVAL ;
struct omap_device * od ;
od = _find_by_pdev ( pdev ) ;
if ( new_wakeup_lat_limit = = od - > dev_wakeup_lat )
return 0 ;
od - > _dev_wakeup_lat_limit = new_wakeup_lat_limit ;
if ( od - > _state ! = OMAP_DEVICE_STATE_IDLE )
return 0 ;
else if ( new_wakeup_lat_limit > od - > dev_wakeup_lat )
ret = _omap_device_deactivate ( od , USE_WAKEUP_LAT ) ;
else if ( new_wakeup_lat_limit < od - > dev_wakeup_lat )
ret = _omap_device_activate ( od , USE_WAKEUP_LAT ) ;
return ret ;
}
/**
* omap_device_get_pwrdm - return the powerdomain * associated with @ od
* @ od : struct omap_device *
*
* Return the powerdomain associated with the first underlying
* omap_hwmod for this omap_device . Intended for use by core OMAP PM
* code . Returns NULL on error or a struct powerdomain * upon
* success .
*/
struct powerdomain * omap_device_get_pwrdm ( struct omap_device * od )
{
/*
* XXX Assumes that all omap_hwmod powerdomains are identical .
* This may not necessarily be true . There should be a sanity
* check in here to WARN ( ) if any difference appears .
*/
if ( ! od - > hwmods_cnt )
return NULL ;
return omap_hwmod_get_pwrdm ( od - > hwmods [ 0 ] ) ;
}
/*
* Public functions intended for use in omap_device_pm_latency
* . activate_func and . deactivate_func function pointers
*/
/**
* omap_device_enable_hwmods - call omap_hwmod_enable ( ) on all hwmods
* @ od : struct omap_device * od
*
* Enable all underlying hwmods . Returns 0.
*/
int omap_device_enable_hwmods ( struct omap_device * od )
{
struct omap_hwmod * oh ;
int i ;
for ( i = 0 , oh = * od - > hwmods ; i < od - > hwmods_cnt ; i + + , oh + + )
omap_hwmod_enable ( oh ) ;
/* XXX pass along return value here? */
return 0 ;
}
/**
* omap_device_idle_hwmods - call omap_hwmod_idle ( ) on all hwmods
* @ od : struct omap_device * od
*
* Idle all underlying hwmods . Returns 0.
*/
int omap_device_idle_hwmods ( struct omap_device * od )
{
struct omap_hwmod * oh ;
int i ;
for ( i = 0 , oh = * od - > hwmods ; i < od - > hwmods_cnt ; i + + , oh + + )
omap_hwmod_idle ( oh ) ;
/* XXX pass along return value here? */
return 0 ;
}
/**
* omap_device_disable_clocks - disable all main and interface clocks
* @ od : struct omap_device * od
*
* Disable the main functional clock and interface clock for all of the
* omap_hwmods associated with the omap_device . Returns 0.
*/
int omap_device_disable_clocks ( struct omap_device * od )
{
struct omap_hwmod * oh ;
int i ;
for ( i = 0 , oh = * od - > hwmods ; i < od - > hwmods_cnt ; i + + , oh + + )
omap_hwmod_disable_clocks ( oh ) ;
/* XXX pass along return value here? */
return 0 ;
}
/**
* omap_device_enable_clocks - enable all main and interface clocks
* @ od : struct omap_device * od
*
* Enable the main functional clock and interface clock for all of the
* omap_hwmods associated with the omap_device . Returns 0.
*/
int omap_device_enable_clocks ( struct omap_device * od )
{
struct omap_hwmod * oh ;
int i ;
for ( i = 0 , oh = * od - > hwmods ; i < od - > hwmods_cnt ; i + + , oh + + )
omap_hwmod_enable_clocks ( oh ) ;
/* XXX pass along return value here? */
return 0 ;
}