2010-08-02 14:18:18 +04:00
/*
* OMAP4 Power Management Routines
*
2010-06-16 20:49:49 +04:00
* Copyright ( C ) 2010 - 2011 Texas Instruments , Inc .
2010-08-02 14:18:18 +04:00
* Rajendra Nayak < rnayak @ ti . com >
2010-06-16 20:49:49 +04:00
* Santosh Shilimkar < santosh . shilimkar @ ti . com >
2010-08-02 14:18:18 +04:00
*
* 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/pm.h>
# include <linux/suspend.h>
# include <linux/module.h>
# include <linux/list.h>
# include <linux/err.h>
# include <linux/slab.h>
2012-03-28 21:30:01 +04:00
# include <asm/system_misc.h>
2010-08-02 14:18:18 +04:00
2011-11-11 01:45:17 +04:00
# include "common.h"
2011-01-05 19:33:17 +03:00
# include "clockdomain.h"
2010-12-22 07:05:16 +03:00
# include "powerdomain.h"
2010-06-16 20:49:49 +04:00
# include "pm.h"
2010-08-02 14:18:18 +04:00
struct power_state {
struct powerdomain * pwrdm ;
u32 next_state ;
# ifdef CONFIG_SUSPEND
u32 saved_state ;
2011-06-06 13:03:29 +04:00
u32 saved_logic_state ;
2010-08-02 14:18:18 +04:00
# endif
struct list_head node ;
} ;
static LIST_HEAD ( pwrst_list ) ;
# ifdef CONFIG_SUSPEND
static int omap4_pm_suspend ( void )
{
2010-06-16 20:49:49 +04:00
struct power_state * pwrst ;
int state , ret = 0 ;
u32 cpu_id = smp_processor_id ( ) ;
/* Save current powerdomain state */
list_for_each_entry ( pwrst , & pwrst_list , node ) {
pwrst - > saved_state = pwrdm_read_next_pwrst ( pwrst - > pwrdm ) ;
2011-06-06 13:03:29 +04:00
pwrst - > saved_logic_state = pwrdm_read_logic_retst ( pwrst - > pwrdm ) ;
2010-06-16 20:49:49 +04:00
}
/* Set targeted power domain states by suspend */
list_for_each_entry ( pwrst , & pwrst_list , node ) {
omap_set_pwrdm_state ( pwrst - > pwrdm , pwrst - > next_state ) ;
2011-06-06 13:03:29 +04:00
pwrdm_set_logic_retst ( pwrst - > pwrdm , PWRDM_POWER_OFF ) ;
2010-06-16 20:49:49 +04:00
}
/*
* For MPUSS to hit power domain retention ( CSWR or OSWR ) ,
* CPU0 and CPU1 power domains need to be in OFF or DORMANT state ,
* since CPU power domain CSWR is not supported by hardware
* Only master CPU follows suspend path . All other CPUs follow
* CPU hotplug path in system wide suspend . On OMAP4 , CPU power
* domain CSWR is not supported by hardware .
* More details can be found in OMAP4430 TRM section 4.3 .4 .2 .
*/
omap4_enter_lowpower ( cpu_id , PWRDM_POWER_OFF ) ;
/* Restore next powerdomain state */
list_for_each_entry ( pwrst , & pwrst_list , node ) {
state = pwrdm_read_prev_pwrst ( pwrst - > pwrdm ) ;
if ( state > pwrst - > next_state ) {
pr_info ( " Powerdomain (%s) didn't enter "
" target state %d \n " ,
pwrst - > pwrdm - > name , pwrst - > next_state ) ;
ret = - 1 ;
}
omap_set_pwrdm_state ( pwrst - > pwrdm , pwrst - > saved_state ) ;
2011-06-06 13:03:29 +04:00
pwrdm_set_logic_retst ( pwrst - > pwrdm , pwrst - > saved_logic_state ) ;
2010-06-16 20:49:49 +04:00
}
if ( ret )
pr_crit ( " Could not enter target state in pm_suspend \n " ) ;
else
pr_info ( " Successfully put all powerdomains to target state \n " ) ;
2010-08-02 14:18:18 +04:00
return 0 ;
}
# endif /* CONFIG_SUSPEND */
static int __init pwrdms_setup ( struct powerdomain * pwrdm , void * unused )
{
struct power_state * pwrst ;
if ( ! pwrdm - > pwrsts )
return 0 ;
2010-06-16 20:49:49 +04:00
/*
* Skip CPU0 and CPU1 power domains . CPU1 is programmed
* through hotplug path and CPU0 explicitly programmed
* further down in the code path
*/
if ( ! strncmp ( pwrdm - > name , " cpu " , 3 ) )
return 0 ;
/*
* FIXME : Remove this check when core retention is supported
* Only MPUSS power domain is added in the list .
*/
if ( strcmp ( pwrdm - > name , " mpu_pwrdm " ) )
return 0 ;
2010-08-02 14:18:18 +04:00
pwrst = kmalloc ( sizeof ( struct power_state ) , GFP_ATOMIC ) ;
if ( ! pwrst )
return - ENOMEM ;
2010-06-16 20:49:49 +04:00
2010-08-02 14:18:18 +04:00
pwrst - > pwrdm = pwrdm ;
2010-06-16 20:49:49 +04:00
pwrst - > next_state = PWRDM_POWER_RET ;
2010-08-02 14:18:18 +04:00
list_add ( & pwrst - > node , & pwrst_list ) ;
2010-06-16 20:49:49 +04:00
return omap_set_pwrdm_state ( pwrst - > pwrdm , pwrst - > next_state ) ;
2010-08-02 14:18:18 +04:00
}
2011-07-18 10:55:10 +04:00
/**
* omap_default_idle - OMAP4 default ilde routine . '
*
* Implements OMAP4 memory , IO ordering requirements which can ' t be addressed
2011-12-19 12:03:58 +04:00
* with default cpu_do_idle ( ) hook . Used by all CPUs with ! CONFIG_CPUIDLE and
2011-07-18 10:55:10 +04:00
* by secondary CPU with CONFIG_CPUIDLE .
*/
static void omap_default_idle ( void )
{
local_fiq_disable ( ) ;
omap_do_wfi ( ) ;
local_fiq_enable ( ) ;
}
2010-08-02 14:18:18 +04:00
/**
* omap4_pm_init - Init routine for OMAP4 PM
*
* Initializes all powerdomain and clockdomain target states
* and all PRCM settings .
*/
2012-04-26 12:06:50 +04:00
int __init omap4_pm_init ( void )
2010-08-02 14:18:18 +04:00
{
int ret ;
2012-03-12 19:04:45 +04:00
struct clockdomain * emif_clkdm , * mpuss_clkdm , * l3_1_clkdm , * l4wkup ;
2011-03-08 15:54:30 +03:00
struct clockdomain * ducati_clkdm , * l3_2_clkdm , * l4_per_clkdm ;
2010-08-02 14:18:18 +04:00
2011-03-11 13:43:09 +03:00
if ( omap_rev ( ) = = OMAP4430_REV_ES1_0 ) {
WARN ( 1 , " Power Management not supported on OMAP4430 ES1.0 \n " ) ;
return - ENODEV ;
}
2010-08-02 14:18:18 +04:00
pr_err ( " Power Management for TI OMAP4. \n " ) ;
ret = pwrdm_for_each ( pwrdms_setup , NULL ) ;
if ( ret ) {
pr_err ( " Failed to setup powerdomains \n " ) ;
goto err2 ;
}
2011-03-08 15:54:30 +03:00
/*
* The dynamic dependency between MPUSS - > MEMIF and
* MPUSS - > L4_PER / L3_ * and DUCATI - > L3_ * doesn ' t work as
* expected . The hardware recommendation is to enable static
* dependencies for these to avoid system lock ups or random crashes .
2012-03-12 19:04:45 +04:00
* The L4 wakeup depedency is added to workaround the OCP sync hardware
* BUG with 32 K synctimer which lead to incorrect timer value read
* from the 32 K counter . The BUG applies for GPTIMER1 and WDT2 which
* are part of L4 wakeup clockdomain .
2011-03-08 15:54:30 +03:00
*/
mpuss_clkdm = clkdm_lookup ( " mpuss_clkdm " ) ;
emif_clkdm = clkdm_lookup ( " l3_emif_clkdm " ) ;
l3_1_clkdm = clkdm_lookup ( " l3_1_clkdm " ) ;
l3_2_clkdm = clkdm_lookup ( " l3_2_clkdm " ) ;
l4_per_clkdm = clkdm_lookup ( " l4_per_clkdm " ) ;
2012-03-12 19:04:45 +04:00
l4wkup = clkdm_lookup ( " l4_wkup_clkdm " ) ;
2011-03-08 15:54:30 +03:00
ducati_clkdm = clkdm_lookup ( " ducati_clkdm " ) ;
2012-03-12 19:04:45 +04:00
if ( ( ! mpuss_clkdm ) | | ( ! emif_clkdm ) | | ( ! l3_1_clkdm ) | | ( ! l4wkup ) | |
2011-03-08 15:54:30 +03:00
( ! l3_2_clkdm ) | | ( ! ducati_clkdm ) | | ( ! l4_per_clkdm ) )
goto err2 ;
ret = clkdm_add_wkdep ( mpuss_clkdm , emif_clkdm ) ;
ret | = clkdm_add_wkdep ( mpuss_clkdm , l3_1_clkdm ) ;
ret | = clkdm_add_wkdep ( mpuss_clkdm , l3_2_clkdm ) ;
ret | = clkdm_add_wkdep ( mpuss_clkdm , l4_per_clkdm ) ;
2012-03-12 19:04:45 +04:00
ret | = clkdm_add_wkdep ( mpuss_clkdm , l4wkup ) ;
2011-03-08 15:54:30 +03:00
ret | = clkdm_add_wkdep ( ducati_clkdm , l3_1_clkdm ) ;
ret | = clkdm_add_wkdep ( ducati_clkdm , l3_2_clkdm ) ;
if ( ret ) {
pr_err ( " Failed to add MPUSS -> L3/EMIF/L4PER, DUCATI -> L3 "
" wakeup dependency \n " ) ;
goto err2 ;
}
2010-06-16 20:49:48 +04:00
ret = omap4_mpuss_init ( ) ;
if ( ret ) {
pr_err ( " Failed to initialise OMAP4 MPUSS \n " ) ;
goto err2 ;
}
2012-02-02 13:38:50 +04:00
( void ) clkdm_for_each ( omap_pm_clkdms_setup , NULL ) ;
2011-01-05 19:33:17 +03:00
2010-08-02 14:18:18 +04:00
# ifdef CONFIG_SUSPEND
2012-02-02 13:30:50 +04:00
omap_pm_suspend = omap4_pm_suspend ;
# endif
2010-08-02 14:18:18 +04:00
2011-12-19 12:03:58 +04:00
/* Overwrite the default cpu_do_idle() */
2012-01-05 01:27:48 +04:00
arm_pm_idle = omap_default_idle ;
2011-07-18 10:55:10 +04:00
2011-08-16 16:01:40 +04:00
omap4_idle_init ( ) ;
2010-08-02 14:18:18 +04:00
err2 :
return ret ;
}