2012-01-07 15:18:35 +04:00
/*
* Copyright ( c ) 2010 - 2011 Samsung Electronics Co . , Ltd .
* http : //www.samsung.com
*
* EXYNOS - CPU frequency scaling support for EXYNOS series
*
* 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/kernel.h>
# include <linux/err.h>
# include <linux/clk.h>
# include <linux/io.h>
# include <linux/slab.h>
# include <linux/regulator/consumer.h>
# include <linux/cpufreq.h>
# include <linux/suspend.h>
2013-11-28 16:42:42 +04:00
# include <linux/platform_device.h>
2012-01-07 15:18:35 +04:00
2012-01-07 15:18:39 +04:00
# include <plat/cpu.h>
2012-01-07 15:18:35 +04:00
2012-12-29 04:29:10 +04:00
# include "exynos-cpufreq.h"
2012-01-07 15:18:35 +04:00
static struct exynos_dvfs_info * exynos_info ;
static struct regulator * arm_regulator ;
static unsigned int locking_frequency ;
static bool frequency_locked ;
static DEFINE_MUTEX ( cpufreq_lock ) ;
2012-12-24 03:57:48 +04:00
static int exynos_cpufreq_get_index ( unsigned int freq )
{
struct cpufreq_frequency_table * freq_table = exynos_info - > freq_table ;
int index ;
for ( index = 0 ;
freq_table [ index ] . frequency ! = CPUFREQ_TABLE_END ; index + + )
if ( freq_table [ index ] . frequency = = freq )
break ;
if ( freq_table [ index ] . frequency = = CPUFREQ_TABLE_END )
return - EINVAL ;
return index ;
}
static int exynos_cpufreq_scale ( unsigned int target_freq )
2012-01-07 15:18:35 +04:00
{
struct cpufreq_frequency_table * freq_table = exynos_info - > freq_table ;
unsigned int * volt_table = exynos_info - > volt_table ;
2012-12-24 03:57:48 +04:00
struct cpufreq_policy * policy = cpufreq_cpu_get ( 0 ) ;
unsigned int arm_volt , safe_arm_volt = 0 ;
2012-01-07 15:18:35 +04:00
unsigned int mpll_freq_khz = exynos_info - > mpll_freq_khz ;
2013-08-14 18:08:24 +04:00
unsigned int old_freq ;
2013-01-25 22:18:09 +04:00
int index , old_index ;
2012-12-24 03:57:48 +04:00
int ret = 0 ;
2012-01-07 15:18:35 +04:00
2013-08-14 18:08:24 +04:00
old_freq = policy - > cur ;
2012-01-07 15:18:35 +04:00
2012-07-20 06:54:02 +04:00
/*
* The policy max have been changed so that we cannot get proper
* old_index with cpufreq_frequency_table_target ( ) . Thus , ignore
2013-09-26 18:50:21 +04:00
* policy and get the index from the raw frequency table .
2012-07-20 06:54:02 +04:00
*/
2013-08-14 18:08:24 +04:00
old_index = exynos_cpufreq_get_index ( old_freq ) ;
2012-12-24 03:57:48 +04:00
if ( old_index < 0 ) {
ret = old_index ;
2012-01-07 15:18:35 +04:00
goto out ;
}
2012-12-24 03:57:48 +04:00
index = exynos_cpufreq_get_index ( target_freq ) ;
if ( index < 0 ) {
ret = index ;
2012-01-07 15:18:35 +04:00
goto out ;
}
/*
* ARM clock source will be changed APLL to MPLL temporary
* To support this level , need to control regulator for
* required voltage level
*/
if ( exynos_info - > need_apll_change ! = NULL ) {
if ( exynos_info - > need_apll_change ( old_index , index ) & &
( freq_table [ index ] . frequency < mpll_freq_khz ) & &
( freq_table [ old_index ] . frequency < mpll_freq_khz ) )
safe_arm_volt = volt_table [ exynos_info - > pll_safe_idx ] ;
}
arm_volt = volt_table [ index ] ;
/* When the new frequency is higher than current frequency */
2013-08-14 18:08:24 +04:00
if ( ( target_freq > old_freq ) & & ! safe_arm_volt ) {
2012-01-07 15:18:35 +04:00
/* Firstly, voltage up to increase frequency */
2012-12-24 03:57:48 +04:00
ret = regulator_set_voltage ( arm_regulator , arm_volt , arm_volt ) ;
if ( ret ) {
pr_err ( " %s: failed to set cpu voltage to %d \n " ,
__func__ , arm_volt ) ;
2013-08-14 18:08:24 +04:00
return ret ;
2012-12-24 03:57:48 +04:00
}
2012-01-07 15:18:35 +04:00
}
2012-12-24 03:57:48 +04:00
if ( safe_arm_volt ) {
ret = regulator_set_voltage ( arm_regulator , safe_arm_volt ,
2012-01-07 15:18:35 +04:00
safe_arm_volt ) ;
2012-12-24 03:57:48 +04:00
if ( ret ) {
pr_err ( " %s: failed to set cpu voltage to %d \n " ,
__func__ , safe_arm_volt ) ;
2013-08-14 18:08:24 +04:00
return ret ;
2012-12-24 03:57:48 +04:00
}
}
2012-12-24 03:57:39 +04:00
exynos_info - > set_freq ( old_index , index ) ;
2012-01-07 15:18:35 +04:00
/* When the new frequency is lower than current frequency */
2013-08-14 18:08:24 +04:00
if ( ( target_freq < old_freq ) | |
( ( target_freq > old_freq ) & & safe_arm_volt ) ) {
2012-01-07 15:18:35 +04:00
/* down the voltage after frequency change */
2013-10-09 19:13:37 +04:00
ret = regulator_set_voltage ( arm_regulator , arm_volt ,
2012-01-07 15:18:35 +04:00
arm_volt ) ;
2012-12-24 03:57:48 +04:00
if ( ret ) {
pr_err ( " %s: failed to set cpu voltage to %d \n " ,
__func__ , arm_volt ) ;
goto out ;
}
2012-01-07 15:18:35 +04:00
}
2012-12-24 03:57:48 +04:00
out :
cpufreq_cpu_put ( policy ) ;
return ret ;
}
2013-10-25 18:15:48 +04:00
static int exynos_target ( struct cpufreq_policy * policy , unsigned int index )
2012-12-24 03:57:48 +04:00
{
struct cpufreq_frequency_table * freq_table = exynos_info - > freq_table ;
2013-02-01 05:13:39 +04:00
int ret = 0 ;
2012-12-24 03:57:48 +04:00
mutex_lock ( & cpufreq_lock ) ;
if ( frequency_locked )
goto out ;
2013-10-25 18:15:48 +04:00
ret = exynos_cpufreq_scale ( freq_table [ index ] . frequency ) ;
2012-12-24 03:57:48 +04:00
2012-01-07 15:18:35 +04:00
out :
mutex_unlock ( & cpufreq_lock ) ;
return ret ;
}
# ifdef CONFIG_PM
static int exynos_cpufreq_suspend ( struct cpufreq_policy * policy )
{
return 0 ;
}
static int exynos_cpufreq_resume ( struct cpufreq_policy * policy )
{
return 0 ;
}
# endif
/**
* exynos_cpufreq_pm_notifier - block CPUFREQ ' s activities in suspend - resume
* context
* @ notifier
* @ pm_event
* @ v
*
* While frequency_locked = = true , target ( ) ignores every frequency but
* locking_frequency . The locking_frequency value is the initial frequency ,
* which is set by the bootloader . In order to eliminate possible
* inconsistency in clock values , we save and restore frequencies during
* suspend and resume and block CPUFREQ activities . Note that the standard
* suspend / resume cannot be used as they are too deep ( syscore_ops ) for
* regulator actions .
*/
static int exynos_cpufreq_pm_notifier ( struct notifier_block * notifier ,
unsigned long pm_event , void * v )
{
2012-12-24 03:57:48 +04:00
int ret ;
2012-01-07 15:18:35 +04:00
switch ( pm_event ) {
case PM_SUSPEND_PREPARE :
2012-12-24 03:57:48 +04:00
mutex_lock ( & cpufreq_lock ) ;
2012-01-07 15:18:35 +04:00
frequency_locked = true ;
2012-12-24 03:57:48 +04:00
mutex_unlock ( & cpufreq_lock ) ;
2012-01-07 15:18:35 +04:00
2012-12-24 03:57:48 +04:00
ret = exynos_cpufreq_scale ( locking_frequency ) ;
if ( ret < 0 )
return NOTIFY_BAD ;
2012-01-07 15:18:35 +04:00
break ;
case PM_POST_SUSPEND :
2012-12-24 03:57:48 +04:00
mutex_lock ( & cpufreq_lock ) ;
2012-01-07 15:18:35 +04:00
frequency_locked = false ;
2012-12-24 03:57:48 +04:00
mutex_unlock ( & cpufreq_lock ) ;
2012-01-07 15:18:35 +04:00
break ;
}
return NOTIFY_OK ;
}
static struct notifier_block exynos_cpufreq_nb = {
. notifier_call = exynos_cpufreq_pm_notifier ,
} ;
static int exynos_cpufreq_cpu_init ( struct cpufreq_policy * policy )
{
2014-01-09 19:08:43 +04:00
policy - > clk = exynos_info - > cpu_clk ;
2013-10-03 18:59:13 +04:00
return cpufreq_generic_init ( policy , exynos_info - > freq_table , 100000 ) ;
2012-01-07 15:18:35 +04:00
}
static struct cpufreq_driver exynos_driver = {
2013-12-03 09:50:45 +04:00
. flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK ,
2013-10-03 18:58:06 +04:00
. verify = cpufreq_generic_frequency_table_verify ,
2013-10-25 18:15:48 +04:00
. target_index = exynos_target ,
2014-01-09 19:08:43 +04:00
. get = cpufreq_generic_get ,
2012-01-07 15:18:35 +04:00
. init = exynos_cpufreq_cpu_init ,
2013-10-03 18:58:06 +04:00
. exit = cpufreq_generic_exit ,
2012-01-07 15:18:35 +04:00
. name = " exynos_cpufreq " ,
2013-10-03 18:58:06 +04:00
. attr = cpufreq_generic_attr ,
2012-01-07 15:18:35 +04:00
# ifdef CONFIG_PM
. suspend = exynos_cpufreq_suspend ,
. resume = exynos_cpufreq_resume ,
# endif
} ;
2013-11-28 16:42:42 +04:00
static int exynos_cpufreq_probe ( struct platform_device * pdev )
2012-01-07 15:18:35 +04:00
{
int ret = - EINVAL ;
2013-08-06 21:23:06 +04:00
exynos_info = kzalloc ( sizeof ( * exynos_info ) , GFP_KERNEL ) ;
2012-01-07 15:18:35 +04:00
if ( ! exynos_info )
return - ENOMEM ;
if ( soc_is_exynos4210 ( ) )
ret = exynos4210_cpufreq_init ( exynos_info ) ;
2012-03-10 14:59:22 +04:00
else if ( soc_is_exynos4212 ( ) | | soc_is_exynos4412 ( ) )
ret = exynos4x12_cpufreq_init ( exynos_info ) ;
2012-03-10 15:00:02 +04:00
else if ( soc_is_exynos5250 ( ) )
ret = exynos5250_cpufreq_init ( exynos_info ) ;
2012-01-07 15:18:35 +04:00
else
2013-04-08 12:17:36 +04:00
return 0 ;
2012-01-07 15:18:35 +04:00
if ( ret )
goto err_vdd_arm ;
if ( exynos_info - > set_freq = = NULL ) {
pr_err ( " %s: No set_freq function (ERR) \n " , __func__ ) ;
goto err_vdd_arm ;
}
arm_regulator = regulator_get ( NULL , " vdd_arm " ) ;
if ( IS_ERR ( arm_regulator ) ) {
pr_err ( " %s: failed to get resource vdd_arm \n " , __func__ ) ;
goto err_vdd_arm ;
}
2014-01-09 19:08:43 +04:00
locking_frequency = clk_get_rate ( exynos_info - > cpu_clk ) / 1000 ;
2013-01-18 23:09:01 +04:00
2012-01-07 15:18:35 +04:00
register_pm_notifier ( & exynos_cpufreq_nb ) ;
if ( cpufreq_register_driver ( & exynos_driver ) ) {
pr_err ( " %s: failed to register cpufreq driver \n " , __func__ ) ;
goto err_cpufreq ;
}
return 0 ;
err_cpufreq :
unregister_pm_notifier ( & exynos_cpufreq_nb ) ;
2012-12-24 03:51:40 +04:00
regulator_put ( arm_regulator ) ;
2012-01-07 15:18:35 +04:00
err_vdd_arm :
kfree ( exynos_info ) ;
return - EINVAL ;
}
2013-11-28 16:42:42 +04:00
static struct platform_driver exynos_cpufreq_platdrv = {
. driver = {
. name = " exynos-cpufreq " ,
. owner = THIS_MODULE ,
} ,
. probe = exynos_cpufreq_probe ,
} ;
module_platform_driver ( exynos_cpufreq_platdrv ) ;