2010-10-08 14:25:27 +04:00
/*
* PowerPC 4 xx Clock and Power Management
*
* Copyright ( C ) 2010 , Applied Micro Circuits Corporation
* Victor Gallardo ( vgallardo @ apm . com )
*
* Based on arch / powerpc / platforms / 44 x / idle . c :
* Jerone Young < jyoung5 @ us . ibm . com >
* Copyright 2008 IBM Corp .
*
* Based on arch / powerpc / sysdev / fsl_pmc . c :
* Anton Vorontsov < avorontsov @ ru . mvista . com >
* Copyright 2009 MontaVista Software , Inc .
*
* See file CREDITS for list of people who contributed to this
* project .
*
* 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 . , 59 Temple Place , Suite 330 , Boston ,
* MA 02111 - 1307 USA
*/
# include <linux/kernel.h>
# include <linux/of_platform.h>
# include <linux/sysfs.h>
# include <linux/cpu.h>
# include <linux/suspend.h>
# include <asm/dcr.h>
# include <asm/dcr-native.h>
# include <asm/machdep.h>
# define CPM_ER 0
# define CPM_FR 1
# define CPM_SR 2
# define CPM_IDLE_WAIT 0
# define CPM_IDLE_DOZE 1
struct cpm {
dcr_host_t dcr_host ;
unsigned int dcr_offset [ 3 ] ;
unsigned int powersave_off ;
unsigned int unused ;
unsigned int idle_doze ;
unsigned int standby ;
unsigned int suspend ;
} ;
static struct cpm cpm ;
struct cpm_idle_mode {
unsigned int enabled ;
const char * name ;
} ;
static struct cpm_idle_mode idle_mode [ ] = {
[ CPM_IDLE_WAIT ] = { 1 , " wait " } , /* default */
[ CPM_IDLE_DOZE ] = { 0 , " doze " } ,
} ;
static unsigned int cpm_set ( unsigned int cpm_reg , unsigned int mask )
{
unsigned int value ;
/* CPM controller supports 3 different types of sleep interface
* known as class 1 , 2 and 3. For class 1 units , they are
* unconditionally put to sleep when the corresponding CPM bit is
* set . For class 2 and 3 units this is not case ; if they can be
* put to to sleep , they will . Here we do not verify , we just
* set them and expect them to eventually go off when they can .
*/
value = dcr_read ( cpm . dcr_host , cpm . dcr_offset [ cpm_reg ] ) ;
dcr_write ( cpm . dcr_host , cpm . dcr_offset [ cpm_reg ] , value | mask ) ;
/* return old state, to restore later if needed */
return value ;
}
static void cpm_idle_wait ( void )
{
unsigned long msr_save ;
/* save off initial state */
msr_save = mfmsr ( ) ;
/* sync required when CPM0_ER[CPU] is set */
mb ( ) ;
/* set wait state MSR */
mtmsr ( msr_save | MSR_WE | MSR_EE | MSR_CE | MSR_DE ) ;
isync ( ) ;
/* return to initial state */
mtmsr ( msr_save ) ;
isync ( ) ;
}
static void cpm_idle_sleep ( unsigned int mask )
{
unsigned int er_save ;
/* update CPM_ER state */
er_save = cpm_set ( CPM_ER , mask ) ;
/* go to wait state so that CPM0_ER[CPU] can take effect */
cpm_idle_wait ( ) ;
/* restore CPM_ER state */
dcr_write ( cpm . dcr_host , cpm . dcr_offset [ CPM_ER ] , er_save ) ;
}
static void cpm_idle_doze ( void )
{
cpm_idle_sleep ( cpm . idle_doze ) ;
}
static void cpm_idle_config ( int mode )
{
int i ;
if ( idle_mode [ mode ] . enabled )
return ;
for ( i = 0 ; i < ARRAY_SIZE ( idle_mode ) ; i + + )
idle_mode [ i ] . enabled = 0 ;
idle_mode [ mode ] . enabled = 1 ;
}
static ssize_t cpm_idle_show ( struct kobject * kobj ,
struct kobj_attribute * attr , char * buf )
{
char * s = buf ;
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( idle_mode ) ; i + + ) {
if ( idle_mode [ i ] . enabled )
s + = sprintf ( s , " [%s] " , idle_mode [ i ] . name ) ;
else
s + = sprintf ( s , " %s " , idle_mode [ i ] . name ) ;
}
* ( s - 1 ) = ' \n ' ; /* convert the last space to a newline */
return s - buf ;
}
static ssize_t cpm_idle_store ( struct kobject * kobj ,
struct kobj_attribute * attr ,
const char * buf , size_t n )
{
int i ;
char * p ;
int len ;
p = memchr ( buf , ' \n ' , n ) ;
len = p ? p - buf : n ;
for ( i = 0 ; i < ARRAY_SIZE ( idle_mode ) ; i + + ) {
if ( strncmp ( buf , idle_mode [ i ] . name , len ) = = 0 ) {
cpm_idle_config ( i ) ;
return n ;
}
}
return - EINVAL ;
}
static struct kobj_attribute cpm_idle_attr =
__ATTR ( idle , 0644 , cpm_idle_show , cpm_idle_store ) ;
static void cpm_idle_config_sysfs ( void )
{
2011-12-22 02:29:42 +04:00
struct device * dev ;
2010-10-08 14:25:27 +04:00
unsigned long ret ;
2011-12-22 02:29:42 +04:00
dev = get_cpu_device ( 0 ) ;
2010-10-08 14:25:27 +04:00
2011-12-22 02:29:42 +04:00
ret = sysfs_create_file ( & dev - > kobj ,
2010-10-08 14:25:27 +04:00
& cpm_idle_attr . attr ) ;
if ( ret )
printk ( KERN_WARNING
" cpm: failed to create idle sysfs entry \n " ) ;
}
static void cpm_idle ( void )
{
if ( idle_mode [ CPM_IDLE_DOZE ] . enabled )
cpm_idle_doze ( ) ;
else
cpm_idle_wait ( ) ;
}
static int cpm_suspend_valid ( suspend_state_t state )
{
switch ( state ) {
case PM_SUSPEND_STANDBY :
return ! ! cpm . standby ;
case PM_SUSPEND_MEM :
return ! ! cpm . suspend ;
default :
return 0 ;
}
}
static void cpm_suspend_standby ( unsigned int mask )
{
unsigned long tcr_save ;
/* disable decrement interrupt */
tcr_save = mfspr ( SPRN_TCR ) ;
mtspr ( SPRN_TCR , tcr_save & ~ TCR_DIE ) ;
/* go to sleep state */
cpm_idle_sleep ( mask ) ;
/* restore decrement interrupt */
mtspr ( SPRN_TCR , tcr_save ) ;
}
static int cpm_suspend_enter ( suspend_state_t state )
{
switch ( state ) {
case PM_SUSPEND_STANDBY :
cpm_suspend_standby ( cpm . standby ) ;
break ;
case PM_SUSPEND_MEM :
cpm_suspend_standby ( cpm . suspend ) ;
break ;
}
return 0 ;
}
static struct platform_suspend_ops cpm_suspend_ops = {
. valid = cpm_suspend_valid ,
. enter = cpm_suspend_enter ,
} ;
static int cpm_get_uint_property ( struct device_node * np ,
const char * name )
{
int len ;
const unsigned int * prop = of_get_property ( np , name , & len ) ;
if ( prop = = NULL | | len < sizeof ( u32 ) )
return 0 ;
return * prop ;
}
static int __init cpm_init ( void )
{
struct device_node * np ;
int dcr_base , dcr_len ;
int ret = 0 ;
if ( ! cpm . powersave_off ) {
cpm_idle_config ( CPM_IDLE_WAIT ) ;
ppc_md . power_save = & cpm_idle ;
}
np = of_find_compatible_node ( NULL , NULL , " ibm,cpm " ) ;
if ( ! np ) {
ret = - EINVAL ;
goto out ;
}
dcr_base = dcr_resource_start ( np , 0 ) ;
dcr_len = dcr_resource_len ( np , 0 ) ;
if ( dcr_base = = 0 | | dcr_len = = 0 ) {
printk ( KERN_ERR " cpm: could not parse dcr property for %s \n " ,
np - > full_name ) ;
ret = - EINVAL ;
goto out ;
}
cpm . dcr_host = dcr_map ( np , dcr_base , dcr_len ) ;
if ( ! DCR_MAP_OK ( cpm . dcr_host ) ) {
printk ( KERN_ERR " cpm: failed to map dcr property for %s \n " ,
np - > full_name ) ;
ret = - EINVAL ;
goto out ;
}
/* All 4xx SoCs with a CPM controller have one of two
* different order for the CPM registers . Some have the
* CPM registers in the following order ( ER , FR , SR ) . The
* others have them in the following order ( SR , ER , FR ) .
*/
if ( cpm_get_uint_property ( np , " er-offset " ) = = 0 ) {
cpm . dcr_offset [ CPM_ER ] = 0 ;
cpm . dcr_offset [ CPM_FR ] = 1 ;
cpm . dcr_offset [ CPM_SR ] = 2 ;
} else {
cpm . dcr_offset [ CPM_ER ] = 1 ;
cpm . dcr_offset [ CPM_FR ] = 2 ;
cpm . dcr_offset [ CPM_SR ] = 0 ;
}
/* Now let's see what IPs to turn off for the following modes */
cpm . unused = cpm_get_uint_property ( np , " unused-units " ) ;
cpm . idle_doze = cpm_get_uint_property ( np , " idle-doze " ) ;
cpm . standby = cpm_get_uint_property ( np , " standby " ) ;
cpm . suspend = cpm_get_uint_property ( np , " suspend " ) ;
/* If some IPs are unused let's turn them off now */
if ( cpm . unused ) {
cpm_set ( CPM_ER , cpm . unused ) ;
cpm_set ( CPM_FR , cpm . unused ) ;
}
/* Now let's export interfaces */
if ( ! cpm . powersave_off & & cpm . idle_doze )
cpm_idle_config_sysfs ( ) ;
if ( cpm . standby | | cpm . suspend )
suspend_set_ops ( & cpm_suspend_ops ) ;
out :
if ( np )
of_node_put ( np ) ;
return ret ;
}
late_initcall ( cpm_init ) ;
static int __init cpm_powersave_off ( char * arg )
{
cpm . powersave_off = 1 ;
return 0 ;
}
__setup ( " powersave=off " , cpm_powersave_off ) ;