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>
2013-11-28 16:42:42 +04:00
# include <linux/platform_device.h>
2014-05-17 03:19:30 +04:00
# include <linux/of.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 ;
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 ;
2014-04-26 00:15:38 +04:00
struct cpufreq_frequency_table * pos ;
2012-12-24 03:57:48 +04:00
2014-04-26 00:15:38 +04:00
cpufreq_for_each_entry ( pos , freq_table )
if ( pos - > frequency = = freq )
2012-12-24 03:57:48 +04:00
break ;
2014-04-26 00:15:38 +04:00
if ( pos - > frequency = = CPUFREQ_TABLE_END )
2012-12-24 03:57:48 +04:00
return - EINVAL ;
2014-04-26 00:15:38 +04:00
return pos - freq_table ;
2012-12-24 03:57:48 +04:00
}
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 ;
2014-04-18 06:20:33 +04:00
struct device * dev = exynos_info - > dev ;
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 ) {
2014-04-18 06:20:33 +04:00
dev_err ( dev , " failed to set cpu voltage to %d \n " ,
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 ) {
2014-04-18 06:20:33 +04:00
dev_err ( dev , " failed to set cpu voltage to %d \n " ,
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 ) {
2014-04-18 06:20:33 +04:00
dev_err ( dev , " failed to set cpu voltage to %d \n " ,
arm_volt ) ;
2012-12-24 03:57:48 +04:00
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
{
2014-03-04 07:00:28 +04:00
return exynos_cpufreq_scale ( exynos_info - > freq_table [ index ] . frequency ) ;
2012-01-07 15:18:35 +04:00
}
static int exynos_cpufreq_cpu_init ( struct cpufreq_policy * policy )
{
2014-01-09 19:08:43 +04:00
policy - > clk = exynos_info - > cpu_clk ;
2014-03-04 07:00:28 +04:00
policy - > suspend_freq = locking_frequency ;
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 ,
. name = " exynos_cpufreq " ,
2013-10-03 18:58:06 +04:00
. attr = cpufreq_generic_attr ,
2013-12-20 18:24:52 +04:00
# ifdef CONFIG_ARM_EXYNOS_CPU_FREQ_BOOST_SW
. boost_supported = true ,
# endif
2012-01-07 15:18:35 +04:00
# ifdef CONFIG_PM
2014-03-04 07:00:28 +04:00
. suspend = cpufreq_generic_suspend ,
2012-01-07 15:18:35 +04:00
# 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 ;
2014-04-18 06:20:33 +04:00
exynos_info - > dev = & pdev - > dev ;
2014-05-17 03:19:30 +04:00
if ( of_machine_is_compatible ( " samsung,exynos4210 " ) ) {
exynos_info - > type = EXYNOS_SOC_4210 ;
2012-01-07 15:18:35 +04:00
ret = exynos4210_cpufreq_init ( exynos_info ) ;
2014-05-17 03:19:30 +04:00
} else if ( of_machine_is_compatible ( " samsung,exynos4212 " ) ) {
exynos_info - > type = EXYNOS_SOC_4212 ;
2012-03-10 14:59:22 +04:00
ret = exynos4x12_cpufreq_init ( exynos_info ) ;
2014-05-17 03:19:30 +04:00
} else if ( of_machine_is_compatible ( " samsung,exynos4412 " ) ) {
exynos_info - > type = EXYNOS_SOC_4412 ;
ret = exynos4x12_cpufreq_init ( exynos_info ) ;
} else if ( of_machine_is_compatible ( " samsung,exynos5250 " ) ) {
exynos_info - > type = EXYNOS_SOC_5250 ;
2012-03-10 15:00:02 +04:00
ret = exynos5250_cpufreq_init ( exynos_info ) ;
2014-05-17 03:19:30 +04:00
} else {
pr_err ( " %s: Unknown SoC type \n " , __func__ ) ;
return - ENODEV ;
}
2012-01-07 15:18:35 +04:00
if ( ret )
goto err_vdd_arm ;
if ( exynos_info - > set_freq = = NULL ) {
2014-04-18 06:20:33 +04:00
dev_err ( & pdev - > dev , " No set_freq function (ERR) \n " ) ;
2012-01-07 15:18:35 +04:00
goto err_vdd_arm ;
}
arm_regulator = regulator_get ( NULL , " vdd_arm " ) ;
if ( IS_ERR ( arm_regulator ) ) {
2014-04-18 06:20:33 +04:00
dev_err ( & pdev - > dev , " failed to get resource vdd_arm \n " ) ;
2012-01-07 15:18:35 +04:00
goto err_vdd_arm ;
}
2014-03-04 07:00:28 +04:00
/* Done here as we want to capture boot frequency */
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
2014-03-04 07:00:28 +04:00
if ( ! cpufreq_register_driver ( & exynos_driver ) )
return 0 ;
2012-01-07 15:18:35 +04:00
2014-04-18 06:20:33 +04:00
dev_err ( & pdev - > dev , " failed to register cpufreq driver \n " ) ;
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 ) ;