2011-03-22 00:30:37 +03:00
/*
2011-11-07 15:36:48 +04:00
* Copyright ( C ) 2011 Freescale Semiconductor , Inc . All Rights Reserved .
*
2011-03-22 00:30:37 +03:00
* The code contained herein is licensed under the GNU General Public
* License . You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations :
*
* http : //www.opensource.org/licenses/gpl-license.html
* http : //www.gnu.org/copyleft/gpl.html
*/
2011-11-07 15:36:48 +04:00
# include <linux/suspend.h>
# include <linux/clk.h>
2011-03-22 00:30:37 +03:00
# include <linux/io.h>
2011-11-07 15:36:48 +04:00
# include <linux/err.h>
2012-05-22 02:50:29 +04:00
# include <linux/export.h>
2015-05-12 16:31:03 +03:00
# include <linux/genalloc.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/of_platform.h>
2011-11-07 15:36:48 +04:00
# include <asm/cacheflush.h>
2015-05-12 16:31:03 +03:00
# include <asm/fncpy.h>
2012-05-22 02:50:26 +04:00
# include <asm/system_misc.h>
2011-11-07 15:36:48 +04:00
# include <asm/tlbflush.h>
2012-09-13 17:01:00 +04:00
# include "common.h"
2012-09-13 17:12:50 +04:00
# include "cpuidle.h"
2012-09-14 10:14:45 +04:00
# include "hardware.h"
2011-11-07 15:36:48 +04:00
2014-05-20 09:41:36 +04:00
# define MXC_CCM_CLPCR 0x54
2014-05-20 06:23:50 +04:00
# define MXC_CCM_CLPCR_LPM_OFFSET 0
# define MXC_CCM_CLPCR_LPM_MASK 0x3
# define MXC_CCM_CLPCR_STBY_COUNT_OFFSET 9
# define MXC_CCM_CLPCR_VSTBY (0x1 << 8)
# define MXC_CCM_CLPCR_SBYOS (0x1 << 6)
2014-05-20 10:55:15 +04:00
# define MXC_CORTEXA8_PLAT_LPC 0xc
2014-05-20 06:23:50 +04:00
# define MXC_CORTEXA8_PLAT_LPC_DSM (1 << 0)
# define MXC_CORTEXA8_PLAT_LPC_DBG_DSM (1 << 1)
2014-05-20 10:55:15 +04:00
# define MXC_SRPG_NEON_SRPGCR 0x280
# define MXC_SRPG_ARM_SRPGCR 0x2a0
# define MXC_SRPG_EMPGC0_SRPGCR 0x2c0
# define MXC_SRPG_EMPGC1_SRPGCR 0x2d0
2014-05-20 06:23:50 +04:00
# define MXC_SRPGCR_PCR 1
2012-05-22 02:50:26 +04:00
/*
* The WAIT_UNCLOCKED_POWER_OFF state only requires < = 500 ns to exit .
* This is also the lowest power state possible without affecting
* non - cpu parts of the system . For these reasons , imx5 should default
* to always using this state for cpu idling . The PM_SUSPEND_STANDBY also
* uses this state and needs to take no action when registers remain confgiured
* for this state .
*/
# define IMX5_DEFAULT_CPU_IDLE_STATE WAIT_UNCLOCKED_POWER_OFF
2011-03-22 00:30:37 +03:00
2015-05-12 16:31:03 +03:00
struct imx5_suspend_io_state {
u32 offset ;
u32 clear ;
u32 set ;
u32 saved_value ;
} ;
2014-05-20 10:55:15 +04:00
struct imx5_pm_data {
2015-04-25 17:38:19 +03:00
phys_addr_t ccm_addr ;
2014-05-20 10:55:15 +04:00
phys_addr_t cortex_addr ;
phys_addr_t gpc_addr ;
2015-05-12 16:31:03 +03:00
phys_addr_t m4if_addr ;
phys_addr_t iomuxc_addr ;
void ( * suspend_asm ) ( void __iomem * ocram_vbase ) ;
const u32 * suspend_asm_sz ;
const struct imx5_suspend_io_state * suspend_io_config ;
int suspend_io_count ;
} ;
static const struct imx5_suspend_io_state imx53_suspend_io_config [ ] = {
# define MX53_DSE_HIGHZ_MASK (0x7 << 19)
{ . offset = 0x584 , . clear = MX53_DSE_HIGHZ_MASK } , /* DQM0 */
{ . offset = 0x594 , . clear = MX53_DSE_HIGHZ_MASK } , /* DQM1 */
{ . offset = 0x560 , . clear = MX53_DSE_HIGHZ_MASK } , /* DQM2 */
{ . offset = 0x554 , . clear = MX53_DSE_HIGHZ_MASK } , /* DQM3 */
{ . offset = 0x574 , . clear = MX53_DSE_HIGHZ_MASK } , /* CAS */
{ . offset = 0x588 , . clear = MX53_DSE_HIGHZ_MASK } , /* RAS */
{ . offset = 0x578 , . clear = MX53_DSE_HIGHZ_MASK } , /* SDCLK_0 */
{ . offset = 0x570 , . clear = MX53_DSE_HIGHZ_MASK } , /* SDCLK_1 */
{ . offset = 0x580 , . clear = MX53_DSE_HIGHZ_MASK } , /* SDODT0 */
{ . offset = 0x564 , . clear = MX53_DSE_HIGHZ_MASK } , /* SDODT1 */
{ . offset = 0x57c , . clear = MX53_DSE_HIGHZ_MASK } , /* SDQS0 */
{ . offset = 0x590 , . clear = MX53_DSE_HIGHZ_MASK } , /* SDQS1 */
{ . offset = 0x568 , . clear = MX53_DSE_HIGHZ_MASK } , /* SDQS2 */
{ . offset = 0x558 , . clear = MX53_DSE_HIGHZ_MASK } , /* SDSQ3 */
{ . offset = 0x6f0 , . clear = MX53_DSE_HIGHZ_MASK } , /* GRP_ADDS */
{ . offset = 0x718 , . clear = MX53_DSE_HIGHZ_MASK } , /* GRP_BODS */
{ . offset = 0x71c , . clear = MX53_DSE_HIGHZ_MASK } , /* GRP_B1DS */
{ . offset = 0x728 , . clear = MX53_DSE_HIGHZ_MASK } , /* GRP_B2DS */
{ . offset = 0x72c , . clear = MX53_DSE_HIGHZ_MASK } , /* GRP_B3DS */
/* Controls the CKE signal which is required to leave self refresh */
{ . offset = 0x720 , . clear = MX53_DSE_HIGHZ_MASK , . set = 1 < < 19 } , /* CTLDS */
2014-05-20 10:55:15 +04:00
} ;
static const struct imx5_pm_data imx51_pm_data __initconst = {
2015-04-25 17:38:19 +03:00
. ccm_addr = 0x73fd4000 ,
2014-05-20 10:55:15 +04:00
. cortex_addr = 0x83fa0000 ,
. gpc_addr = 0x73fd8000 ,
} ;
static const struct imx5_pm_data imx53_pm_data __initconst = {
2015-04-25 17:38:19 +03:00
. ccm_addr = 0x53fd4000 ,
2014-05-20 10:55:15 +04:00
. cortex_addr = 0x63fa0000 ,
. gpc_addr = 0x53fd8000 ,
2015-05-12 16:31:03 +03:00
. m4if_addr = 0x63fd8000 ,
. iomuxc_addr = 0x53fa8000 ,
. suspend_asm = & imx53_suspend ,
. suspend_asm_sz = & imx53_suspend_sz ,
. suspend_io_config = imx53_suspend_io_config ,
. suspend_io_count = ARRAY_SIZE ( imx53_suspend_io_config ) ,
2014-05-20 10:55:15 +04:00
} ;
2015-05-12 16:31:03 +03:00
# define MX5_MAX_SUSPEND_IOSTATE ARRAY_SIZE(imx53_suspend_io_config)
/*
* This structure is for passing necessary data for low level ocram
* suspend code ( arch / arm / mach - imx / suspend - imx53 . S ) , if this struct
* definition is changed , the offset definition in that file
* must be also changed accordingly otherwise , the suspend to ocram
* function will be broken !
*/
struct imx5_cpu_suspend_info {
void __iomem * m4if_base ;
void __iomem * iomuxc_base ;
u32 io_count ;
struct imx5_suspend_io_state io_state [ MX5_MAX_SUSPEND_IOSTATE ] ;
} __aligned ( 8 ) ;
2014-05-20 09:41:36 +04:00
static void __iomem * ccm_base ;
2014-05-20 10:55:15 +04:00
static void __iomem * cortex_base ;
static void __iomem * gpc_base ;
2015-05-12 16:31:03 +03:00
static void __iomem * suspend_ocram_base ;
static void ( * imx5_suspend_in_ocram_fn ) ( void __iomem * ocram_vbase ) ;
2014-05-20 09:41:36 +04:00
2011-11-07 15:36:48 +04:00
/*
* set cpu low power mode before WFI instruction . This function is called
2013-01-22 16:40:55 +04:00
* mx5 because it can be used for mx51 , and mx53 .
2011-11-07 15:36:48 +04:00
*/
2012-05-22 02:50:26 +04:00
static void mx5_cpu_lp_set ( enum mxc_cpu_pwr_mode mode )
2011-03-22 00:30:37 +03:00
{
u32 plat_lpc , arm_srpgcr , ccm_clpcr ;
u32 empgc0 , empgc1 ;
int stop_mode = 0 ;
/* always allow platform to issue a deep sleep mode request */
2016-01-27 19:59:35 +03:00
plat_lpc = imx_readl ( cortex_base + MXC_CORTEXA8_PLAT_LPC ) &
2011-03-22 00:30:37 +03:00
~ ( MXC_CORTEXA8_PLAT_LPC_DSM ) ;
2016-01-27 19:59:35 +03:00
ccm_clpcr = imx_readl ( ccm_base + MXC_CCM_CLPCR ) &
2014-05-20 09:41:36 +04:00
~ ( MXC_CCM_CLPCR_LPM_MASK ) ;
2016-01-27 19:59:35 +03:00
arm_srpgcr = imx_readl ( gpc_base + MXC_SRPG_ARM_SRPGCR ) &
2014-05-20 10:55:15 +04:00
~ ( MXC_SRPGCR_PCR ) ;
2016-01-27 19:59:35 +03:00
empgc0 = imx_readl ( gpc_base + MXC_SRPG_EMPGC0_SRPGCR ) &
2014-05-20 10:55:15 +04:00
~ ( MXC_SRPGCR_PCR ) ;
2016-01-27 19:59:35 +03:00
empgc1 = imx_readl ( gpc_base + MXC_SRPG_EMPGC1_SRPGCR ) &
2014-05-20 10:55:15 +04:00
~ ( MXC_SRPGCR_PCR ) ;
2011-03-22 00:30:37 +03:00
switch ( mode ) {
case WAIT_CLOCKED :
break ;
case WAIT_UNCLOCKED :
ccm_clpcr | = 0x1 < < MXC_CCM_CLPCR_LPM_OFFSET ;
break ;
case WAIT_UNCLOCKED_POWER_OFF :
case STOP_POWER_OFF :
plat_lpc | = MXC_CORTEXA8_PLAT_LPC_DSM
| MXC_CORTEXA8_PLAT_LPC_DBG_DSM ;
if ( mode = = WAIT_UNCLOCKED_POWER_OFF ) {
ccm_clpcr | = 0x1 < < MXC_CCM_CLPCR_LPM_OFFSET ;
ccm_clpcr & = ~ MXC_CCM_CLPCR_VSTBY ;
ccm_clpcr & = ~ MXC_CCM_CLPCR_SBYOS ;
stop_mode = 0 ;
} else {
ccm_clpcr | = 0x2 < < MXC_CCM_CLPCR_LPM_OFFSET ;
ccm_clpcr | = 0x3 < < MXC_CCM_CLPCR_STBY_COUNT_OFFSET ;
ccm_clpcr | = MXC_CCM_CLPCR_VSTBY ;
ccm_clpcr | = MXC_CCM_CLPCR_SBYOS ;
stop_mode = 1 ;
}
arm_srpgcr | = MXC_SRPGCR_PCR ;
break ;
case STOP_POWER_ON :
ccm_clpcr | = 0x2 < < MXC_CCM_CLPCR_LPM_OFFSET ;
break ;
default :
printk ( KERN_WARNING " UNKNOWN cpu power mode: %d \n " , mode ) ;
return ;
}
2016-01-27 19:59:35 +03:00
imx_writel ( plat_lpc , cortex_base + MXC_CORTEXA8_PLAT_LPC ) ;
imx_writel ( ccm_clpcr , ccm_base + MXC_CCM_CLPCR ) ;
imx_writel ( arm_srpgcr , gpc_base + MXC_SRPG_ARM_SRPGCR ) ;
imx_writel ( arm_srpgcr , gpc_base + MXC_SRPG_NEON_SRPGCR ) ;
2011-03-22 00:30:37 +03:00
if ( stop_mode ) {
empgc0 | = MXC_SRPGCR_PCR ;
empgc1 | = MXC_SRPGCR_PCR ;
2016-01-27 19:59:35 +03:00
imx_writel ( empgc0 , gpc_base + MXC_SRPG_EMPGC0_SRPGCR ) ;
imx_writel ( empgc1 , gpc_base + MXC_SRPG_EMPGC1_SRPGCR ) ;
2011-03-22 00:30:37 +03:00
}
}
2011-11-07 15:36:48 +04:00
static int mx5_suspend_enter ( suspend_state_t state )
{
switch ( state ) {
case PM_SUSPEND_MEM :
mx5_cpu_lp_set ( STOP_POWER_OFF ) ;
break ;
case PM_SUSPEND_STANDBY :
2012-05-22 02:50:26 +04:00
/* DEFAULT_IDLE_STATE already configured */
2011-11-07 15:36:48 +04:00
break ;
default :
return - EINVAL ;
}
if ( state = = PM_SUSPEND_MEM ) {
local_flush_tlb_all ( ) ;
flush_cache_all ( ) ;
/*clear the EMPGC0/1 bits */
2016-01-27 19:59:35 +03:00
imx_writel ( 0 , gpc_base + MXC_SRPG_EMPGC0_SRPGCR ) ;
imx_writel ( 0 , gpc_base + MXC_SRPG_EMPGC1_SRPGCR ) ;
2015-05-12 16:31:03 +03:00
if ( imx5_suspend_in_ocram_fn )
imx5_suspend_in_ocram_fn ( suspend_ocram_base ) ;
else
cpu_do_idle ( ) ;
} else {
cpu_do_idle ( ) ;
2011-11-07 15:36:48 +04:00
}
2012-05-22 02:50:26 +04:00
/* return registers to default idle state */
mx5_cpu_lp_set ( IMX5_DEFAULT_CPU_IDLE_STATE ) ;
return 0 ;
2011-11-07 15:36:48 +04:00
}
static int mx5_pm_valid ( suspend_state_t state )
{
return ( state > PM_SUSPEND_ON & & state < = PM_SUSPEND_MAX ) ;
}
static const struct platform_suspend_ops mx5_suspend_ops = {
. valid = mx5_pm_valid ,
. enter = mx5_suspend_enter ,
} ;
2012-05-22 02:50:29 +04:00
static inline int imx5_cpu_do_idle ( void )
2011-11-07 15:36:48 +04:00
{
2012-05-22 02:50:29 +04:00
int ret = tzic_enable_wake ( ) ;
if ( likely ( ! ret ) )
2012-05-22 02:50:26 +04:00
cpu_do_idle ( ) ;
2012-05-22 02:50:29 +04:00
return ret ;
}
static void imx5_pm_idle ( void )
{
imx5_cpu_do_idle ( ) ;
}
2015-05-12 16:31:03 +03:00
static int __init imx_suspend_alloc_ocram (
size_t size ,
void __iomem * * virt_out ,
phys_addr_t * phys_out )
{
struct device_node * node ;
struct platform_device * pdev ;
struct gen_pool * ocram_pool ;
unsigned long ocram_base ;
void __iomem * virt ;
phys_addr_t phys ;
int ret = 0 ;
/* Copied from imx6: TODO factorize */
node = of_find_compatible_node ( NULL , NULL , " mmio-sram " ) ;
if ( ! node ) {
pr_warn ( " %s: failed to find ocram node! \n " , __func__ ) ;
return - ENODEV ;
}
pdev = of_find_device_by_node ( node ) ;
if ( ! pdev ) {
pr_warn ( " %s: failed to find ocram device! \n " , __func__ ) ;
ret = - ENODEV ;
goto put_node ;
}
2015-09-05 01:47:43 +03:00
ocram_pool = gen_pool_get ( & pdev - > dev , NULL ) ;
2015-05-12 16:31:03 +03:00
if ( ! ocram_pool ) {
pr_warn ( " %s: ocram pool unavailable! \n " , __func__ ) ;
ret = - ENODEV ;
goto put_node ;
}
ocram_base = gen_pool_alloc ( ocram_pool , size ) ;
if ( ! ocram_base ) {
pr_warn ( " %s: unable to alloc ocram! \n " , __func__ ) ;
ret = - ENOMEM ;
goto put_node ;
}
phys = gen_pool_virt_to_phys ( ocram_pool , ocram_base ) ;
virt = __arm_ioremap_exec ( phys , size , false ) ;
if ( phys_out )
* phys_out = phys ;
if ( virt_out )
* virt_out = virt ;
put_node :
of_node_put ( node ) ;
return ret ;
}
static int __init imx5_suspend_init ( const struct imx5_pm_data * soc_data )
{
struct imx5_cpu_suspend_info * suspend_info ;
int ret ;
/* Need this to avoid compile error due to const typeof in fncpy.h */
void ( * suspend_asm ) ( void __iomem * ) = soc_data - > suspend_asm ;
if ( ! suspend_asm )
return 0 ;
if ( ! soc_data - > suspend_asm_sz | | ! * soc_data - > suspend_asm_sz )
return - EINVAL ;
ret = imx_suspend_alloc_ocram (
* soc_data - > suspend_asm_sz + sizeof ( * suspend_info ) ,
& suspend_ocram_base , NULL ) ;
if ( ret )
return ret ;
suspend_info = suspend_ocram_base ;
suspend_info - > io_count = soc_data - > suspend_io_count ;
memcpy ( suspend_info - > io_state , soc_data - > suspend_io_config ,
sizeof ( * suspend_info - > io_state ) * soc_data - > suspend_io_count ) ;
suspend_info - > m4if_base = ioremap ( soc_data - > m4if_addr , SZ_16K ) ;
if ( ! suspend_info - > m4if_base ) {
ret = - ENOMEM ;
goto failed_map_m4if ;
}
suspend_info - > iomuxc_base = ioremap ( soc_data - > iomuxc_addr , SZ_16K ) ;
if ( ! suspend_info - > iomuxc_base ) {
ret = - ENOMEM ;
goto failed_map_iomuxc ;
}
imx5_suspend_in_ocram_fn = fncpy (
suspend_ocram_base + sizeof ( * suspend_info ) ,
suspend_asm ,
* soc_data - > suspend_asm_sz ) ;
return 0 ;
failed_map_iomuxc :
iounmap ( suspend_info - > m4if_base ) ;
failed_map_m4if :
return ret ;
}
2014-05-20 10:55:15 +04:00
static int __init imx5_pm_common_init ( const struct imx5_pm_data * data )
2012-05-22 02:50:26 +04:00
{
int ret ;
struct clk * gpc_dvfs_clk = clk_get ( NULL , " gpc_dvfs " ) ;
if ( IS_ERR ( gpc_dvfs_clk ) )
return PTR_ERR ( gpc_dvfs_clk ) ;
2011-11-07 15:36:48 +04:00
2012-05-22 02:50:26 +04:00
ret = clk_prepare_enable ( gpc_dvfs_clk ) ;
if ( ret )
return ret ;
2011-11-07 15:36:48 +04:00
2012-05-22 02:50:26 +04:00
arm_pm_idle = imx5_pm_idle ;
2015-04-25 17:38:19 +03:00
ccm_base = ioremap ( data - > ccm_addr , SZ_16K ) ;
2014-05-20 10:55:15 +04:00
cortex_base = ioremap ( data - > cortex_addr , SZ_16K ) ;
gpc_base = ioremap ( data - > gpc_addr , SZ_16K ) ;
WARN_ON ( ! ccm_base | | ! cortex_base | | ! gpc_base ) ;
2014-05-20 09:41:36 +04:00
2012-05-22 02:50:26 +04:00
/* Set the registers to the default cpu idle state. */
mx5_cpu_lp_set ( IMX5_DEFAULT_CPU_IDLE_STATE ) ;
2011-11-07 15:36:48 +04:00
2014-05-20 10:55:15 +04:00
ret = imx5_cpuidle_init ( ) ;
if ( ret )
pr_warn ( " %s: cpuidle init failed %d \n " , __func__ , ret ) ;
2015-05-12 16:31:03 +03:00
ret = imx5_suspend_init ( data ) ;
if ( ret )
pr_warn ( " %s: No DDR LPM support with suspend %d! \n " ,
__func__ , ret ) ;
2014-05-20 10:55:15 +04:00
suspend_set_ops ( & mx5_suspend_ops ) ;
return 0 ;
}
void __init imx51_pm_init ( void )
{
2015-05-21 15:06:30 +03:00
if ( IS_ENABLED ( CONFIG_SOC_IMX51 ) )
imx5_pm_common_init ( & imx51_pm_data ) ;
2011-11-07 15:36:48 +04:00
}
2012-05-22 02:50:26 +04:00
2014-05-20 10:55:15 +04:00
void __init imx53_pm_init ( void )
2012-05-22 02:50:26 +04:00
{
2015-05-21 15:06:30 +03:00
if ( IS_ENABLED ( CONFIG_SOC_IMX53 ) )
imx5_pm_common_init ( & imx53_pm_data ) ;
2012-05-22 02:50:26 +04:00
}