2012-01-27 10:25:00 +04:00
/*
* Exynos Generic power domain support .
*
* Copyright ( c ) 2012 Samsung Electronics Co . , Ltd .
* http : //www.samsung.com
*
* Implementation of Exynos specific power domain control which is used in
* conjunction with runtime - pm . Support for both device - tree and non - device - tree
* based power domain support is included .
*
* 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/io.h>
# include <linux/err.h>
# include <linux/slab.h>
# include <linux/pm_domain.h>
2014-07-11 03:02:15 +04:00
# include <linux/clk.h>
2012-01-27 10:25:00 +04:00
# include <linux/delay.h>
# include <linux/of_address.h>
2012-11-21 19:21:17 +04:00
# include <linux/of_platform.h>
# include <linux/sched.h>
2012-01-27 10:25:00 +04:00
2014-07-08 02:54:19 +04:00
# define INT_LOCAL_PWR_EN 0x7
2014-07-11 03:02:15 +04:00
# define MAX_CLK_PER_DOMAIN 4
2012-01-27 10:25:00 +04:00
/*
* Exynos specific wrapper around the generic power domain
*/
struct exynos_pm_domain {
void __iomem * base ;
char const * name ;
bool is_off ;
struct generic_pm_domain pd ;
2014-07-11 03:02:15 +04:00
struct clk * oscclk ;
struct clk * clk [ MAX_CLK_PER_DOMAIN ] ;
struct clk * pclk [ MAX_CLK_PER_DOMAIN ] ;
2015-03-17 20:12:23 +03:00
struct clk * asb_clk [ MAX_CLK_PER_DOMAIN ] ;
2012-01-27 10:25:00 +04:00
} ;
static int exynos_pd_power ( struct generic_pm_domain * domain , bool power_on )
{
struct exynos_pm_domain * pd ;
void __iomem * base ;
u32 timeout , pwr ;
char * op ;
2015-03-17 20:12:23 +03:00
int i ;
2012-01-27 10:25:00 +04:00
pd = container_of ( domain , struct exynos_pm_domain , pd ) ;
base = pd - > base ;
2015-03-17 20:12:23 +03:00
for ( i = 0 ; i < MAX_CLK_PER_DOMAIN ; i + + ) {
if ( IS_ERR ( pd - > asb_clk [ i ] ) )
break ;
clk_prepare_enable ( pd - > asb_clk [ i ] ) ;
}
2014-07-11 03:02:15 +04:00
/* Set oscclk before powering off a domain*/
if ( ! power_on ) {
for ( i = 0 ; i < MAX_CLK_PER_DOMAIN ; i + + ) {
if ( IS_ERR ( pd - > clk [ i ] ) )
break ;
2015-04-03 12:25:54 +03:00
pd - > pclk [ i ] = clk_get_parent ( pd - > clk [ i ] ) ;
2014-07-11 03:02:15 +04:00
if ( clk_set_parent ( pd - > clk [ i ] , pd - > oscclk ) )
pr_err ( " %s: error setting oscclk as parent to clock %d \n " ,
pd - > name , i ) ;
}
}
2014-07-08 02:54:19 +04:00
pwr = power_on ? INT_LOCAL_PWR_EN : 0 ;
2012-01-27 10:25:00 +04:00
__raw_writel ( pwr , base ) ;
/* Wait max 1ms */
timeout = 10 ;
2014-07-08 02:54:19 +04:00
while ( ( __raw_readl ( base + 0x4 ) & INT_LOCAL_PWR_EN ) ! = pwr ) {
2012-01-27 10:25:00 +04:00
if ( ! timeout ) {
op = ( power_on ) ? " enable " : " disable " ;
pr_err ( " Power domain %s %s failed \n " , domain - > name , op ) ;
return - ETIMEDOUT ;
}
timeout - - ;
cpu_relax ( ) ;
usleep_range ( 80 , 100 ) ;
}
2014-07-11 03:02:15 +04:00
/* Restore clocks after powering on a domain*/
if ( power_on ) {
for ( i = 0 ; i < MAX_CLK_PER_DOMAIN ; i + + ) {
if ( IS_ERR ( pd - > clk [ i ] ) )
break ;
2015-04-03 12:25:54 +03:00
if ( IS_ERR ( pd - > clk [ i ] ) )
continue ; /* Skip on first power up */
2014-07-11 03:02:15 +04:00
if ( clk_set_parent ( pd - > clk [ i ] , pd - > pclk [ i ] ) )
pr_err ( " %s: error setting parent to clock%d \n " ,
pd - > name , i ) ;
}
}
2015-03-17 20:12:23 +03:00
for ( i = 0 ; i < MAX_CLK_PER_DOMAIN ; i + + ) {
if ( IS_ERR ( pd - > asb_clk [ i ] ) )
break ;
clk_disable_unprepare ( pd - > asb_clk [ i ] ) ;
}
2012-01-27 10:25:00 +04:00
return 0 ;
}
static int exynos_pd_power_on ( struct generic_pm_domain * domain )
{
return exynos_pd_power ( domain , true ) ;
}
static int exynos_pd_power_off ( struct generic_pm_domain * domain )
{
return exynos_pd_power ( domain , false ) ;
}
2013-06-15 04:13:25 +04:00
static __init int exynos4_pm_init_power_domain ( void )
2012-01-27 10:25:00 +04:00
{
struct device_node * np ;
for_each_compatible_node ( np , NULL , " samsung,exynos4210-pd " ) {
struct exynos_pm_domain * pd ;
2014-07-11 03:02:15 +04:00
int on , i ;
2012-11-21 19:21:17 +04:00
2012-01-27 10:25:00 +04:00
pd = kzalloc ( sizeof ( * pd ) , GFP_KERNEL ) ;
if ( ! pd ) {
pr_err ( " %s: failed to allocate memory for domain \n " ,
__func__ ) ;
2015-03-27 15:21:28 +03:00
of_node_put ( np ) ;
2012-01-27 10:25:00 +04:00
return - ENOMEM ;
}
2015-06-04 02:09:27 +03:00
pd - > pd . name = kstrdup_const ( strrchr ( np - > full_name , ' / ' ) + 1 ,
GFP_KERNEL ) ;
2015-03-27 15:12:00 +03:00
if ( ! pd - > pd . name ) {
kfree ( pd ) ;
of_node_put ( np ) ;
return - ENOMEM ;
}
2012-11-21 19:21:12 +04:00
pd - > name = pd - > pd . name ;
2012-01-27 10:25:00 +04:00
pd - > base = of_iomap ( np , 0 ) ;
2015-03-27 15:10:06 +03:00
if ( ! pd - > base ) {
2015-06-04 02:09:27 +03:00
pr_warn ( " %s: failed to map memory \n " , __func__ ) ;
2015-07-31 03:09:49 +03:00
kfree_const ( pd - > pd . name ) ;
2015-03-27 15:10:06 +03:00
kfree ( pd ) ;
continue ;
}
2012-01-27 10:25:00 +04:00
pd - > pd . power_off = exynos_pd_power_off ;
pd - > pd . power_on = exynos_pd_power_on ;
2012-11-21 19:21:08 +04:00
2015-03-17 20:12:23 +03:00
for ( i = 0 ; i < MAX_CLK_PER_DOMAIN ; i + + ) {
char clk_name [ 8 ] ;
snprintf ( clk_name , sizeof ( clk_name ) , " asb%d " , i ) ;
2015-06-04 02:09:27 +03:00
pd - > asb_clk [ i ] = of_clk_get_by_name ( np , clk_name ) ;
2015-03-17 20:12:23 +03:00
if ( IS_ERR ( pd - > asb_clk [ i ] ) )
break ;
}
2015-06-04 02:09:27 +03:00
pd - > oscclk = of_clk_get_by_name ( np , " oscclk " ) ;
2014-07-11 03:02:15 +04:00
if ( IS_ERR ( pd - > oscclk ) )
goto no_clk ;
for ( i = 0 ; i < MAX_CLK_PER_DOMAIN ; i + + ) {
char clk_name [ 8 ] ;
snprintf ( clk_name , sizeof ( clk_name ) , " clk%d " , i ) ;
2015-06-04 02:09:27 +03:00
pd - > clk [ i ] = of_clk_get_by_name ( np , clk_name ) ;
2014-07-11 03:02:15 +04:00
if ( IS_ERR ( pd - > clk [ i ] ) )
break ;
2015-04-03 12:25:54 +03:00
/*
* Skip setting parent on first power up .
* The parent at this time may not be useful at all .
*/
pd - > pclk [ i ] = ERR_PTR ( - EINVAL ) ;
2014-07-11 03:02:15 +04:00
}
if ( IS_ERR ( pd - > clk [ 0 ] ) )
clk_put ( pd - > oscclk ) ;
no_clk :
2014-07-08 02:54:19 +04:00
on = __raw_readl ( pd - > base + 0x4 ) & INT_LOCAL_PWR_EN ;
2012-11-21 19:21:08 +04:00
pm_genpd_init ( & pd - > pd , NULL , ! on ) ;
2014-09-19 22:27:43 +04:00
of_genpd_add_provider_simple ( np , & pd - > pd ) ;
2012-01-27 10:25:00 +04:00
}
2012-11-21 19:21:17 +04:00
2015-02-04 17:44:15 +03:00
/* Assign the child power domains to their parents */
for_each_compatible_node ( np , NULL , " samsung,exynos4210-pd " ) {
struct generic_pm_domain * child_domain , * parent_domain ;
struct of_phandle_args args ;
args . np = np ;
args . args_count = 0 ;
child_domain = of_genpd_get_from_provider ( & args ) ;
2015-05-13 11:45:52 +03:00
if ( IS_ERR ( child_domain ) )
2015-10-12 22:32:49 +03:00
continue ;
2015-02-04 17:44:15 +03:00
if ( of_parse_phandle_with_args ( np , " power-domains " ,
" #power-domain-cells " , 0 , & args ) ! = 0 )
2015-10-12 22:32:49 +03:00
continue ;
2015-02-04 17:44:15 +03:00
parent_domain = of_genpd_get_from_provider ( & args ) ;
2015-05-13 11:45:52 +03:00
if ( IS_ERR ( parent_domain ) )
2015-10-12 22:32:49 +03:00
continue ;
2015-02-04 17:44:15 +03:00
if ( pm_genpd_add_subdomain ( parent_domain , child_domain ) )
pr_warn ( " %s failed to add subdomain: %s \n " ,
parent_domain - > name , child_domain - > name ) ;
else
pr_info ( " %s has as child subdomain: %s. \n " ,
parent_domain - > name , child_domain - > name ) ;
}
2012-01-27 10:25:00 +04:00
return 0 ;
}
2015-06-04 02:09:27 +03:00
core_initcall ( exynos4_pm_init_power_domain ) ;