2011-05-10 17:42:08 +04:00
/*
2009-06-15 14:23:20 +04:00
* Copyright 2009 Wolfson Microelectronics plc
*
* S3C64xx CPUfreq Support
*
* 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 .
*/
2011-12-05 22:22:01 +04:00
# define pr_fmt(fmt) "cpufreq: " fmt
2009-06-15 14:23:20 +04:00
# include <linux/kernel.h>
# include <linux/types.h>
# include <linux/init.h>
# include <linux/cpufreq.h>
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/regulator/consumer.h>
2011-07-29 19:19:26 +04:00
# include <linux/module.h>
2009-06-15 14:23:20 +04:00
static struct regulator * vddarm ;
2009-11-03 17:42:11 +03:00
static unsigned long regulator_latency ;
2009-06-15 14:23:20 +04:00
# ifdef CONFIG_CPU_S3C6410
struct s3c64xx_dvfs {
unsigned int vddarm_min ;
unsigned int vddarm_max ;
} ;
static struct s3c64xx_dvfs s3c64xx_dvfs_table [ ] = {
2009-11-03 17:42:12 +03:00
[ 0 ] = { 1000000 , 1150000 } ,
[ 1 ] = { 1050000 , 1150000 } ,
[ 2 ] = { 1100000 , 1150000 } ,
[ 3 ] = { 1200000 , 1350000 } ,
2011-06-08 17:49:15 +04:00
[ 4 ] = { 1300000 , 1350000 } ,
2009-06-15 14:23:20 +04:00
} ;
static struct cpufreq_frequency_table s3c64xx_freq_table [ ] = {
{ 0 , 66000 } ,
2011-06-29 07:26:49 +04:00
{ 0 , 100000 } ,
2009-06-15 14:23:20 +04:00
{ 0 , 133000 } ,
2011-06-29 07:26:49 +04:00
{ 1 , 200000 } ,
2009-06-15 14:23:20 +04:00
{ 1 , 222000 } ,
{ 1 , 266000 } ,
{ 2 , 333000 } ,
{ 2 , 400000 } ,
2009-11-03 17:42:12 +03:00
{ 2 , 532000 } ,
{ 2 , 533000 } ,
{ 3 , 667000 } ,
2011-06-08 17:49:15 +04:00
{ 4 , 800000 } ,
2009-06-15 14:23:20 +04:00
{ 0 , CPUFREQ_TABLE_END } ,
} ;
# endif
static int s3c64xx_cpufreq_set_target ( struct cpufreq_policy * policy ,
2013-10-25 18:15:48 +04:00
unsigned int index )
2009-06-15 14:23:20 +04:00
{
struct s3c64xx_dvfs * dvfs ;
2013-08-14 18:08:24 +04:00
unsigned int old_freq , new_freq ;
int ret ;
2009-06-15 14:23:20 +04:00
2014-01-09 19:08:43 +04:00
old_freq = clk_get_rate ( policy - > clk ) / 1000 ;
2013-08-14 18:08:24 +04:00
new_freq = s3c64xx_freq_table [ index ] . frequency ;
2013-10-25 18:15:48 +04:00
dvfs = & s3c64xx_dvfs_table [ s3c64xx_freq_table [ index ] . driver_data ] ;
2009-06-15 14:23:20 +04:00
# ifdef CONFIG_REGULATOR
2013-08-14 18:08:24 +04:00
if ( vddarm & & new_freq > old_freq ) {
2009-06-15 14:23:20 +04:00
ret = regulator_set_voltage ( vddarm ,
dvfs - > vddarm_min ,
dvfs - > vddarm_max ) ;
if ( ret ! = 0 ) {
2011-12-05 22:22:01 +04:00
pr_err ( " Failed to set VDDARM for %dkHz: %d \n " ,
2013-08-14 18:08:24 +04:00
new_freq , ret ) ;
return ret ;
2009-06-15 14:23:20 +04:00
}
}
# endif
2014-01-09 19:08:43 +04:00
ret = clk_set_rate ( policy - > clk , new_freq * 1000 ) ;
2009-06-15 14:23:20 +04:00
if ( ret < 0 ) {
2011-12-05 22:22:01 +04:00
pr_err ( " Failed to set rate %dkHz: %d \n " ,
2013-08-14 18:08:24 +04:00
new_freq , ret ) ;
return ret ;
2009-06-15 14:23:20 +04:00
}
# ifdef CONFIG_REGULATOR
2013-08-14 18:08:24 +04:00
if ( vddarm & & new_freq < old_freq ) {
2009-06-15 14:23:20 +04:00
ret = regulator_set_voltage ( vddarm ,
dvfs - > vddarm_min ,
dvfs - > vddarm_max ) ;
if ( ret ! = 0 ) {
2011-12-05 22:22:01 +04:00
pr_err ( " Failed to set VDDARM for %dkHz: %d \n " ,
2013-08-14 18:08:24 +04:00
new_freq , ret ) ;
2014-01-09 19:08:43 +04:00
if ( clk_set_rate ( policy - > clk , old_freq * 1000 ) < 0 )
2013-08-14 18:08:24 +04:00
pr_err ( " Failed to restore original clock rate \n " ) ;
return ret ;
2009-06-15 14:23:20 +04:00
}
}
# endif
2011-12-05 22:22:01 +04:00
pr_debug ( " Set actual frequency %lukHz \n " ,
2014-01-09 19:08:43 +04:00
clk_get_rate ( policy - > clk ) / 1000 ) ;
2009-06-15 14:23:20 +04:00
return 0 ;
}
# ifdef CONFIG_REGULATOR
2009-11-03 17:42:11 +03:00
static void __init s3c64xx_cpufreq_config_regulator ( void )
2009-06-15 14:23:20 +04:00
{
int count , v , i , found ;
struct cpufreq_frequency_table * freq ;
struct s3c64xx_dvfs * dvfs ;
count = regulator_count_voltages ( vddarm ) ;
if ( count < 0 ) {
2011-12-05 22:22:01 +04:00
pr_err ( " Unable to check supported voltages \n " ) ;
2009-06-15 14:23:20 +04:00
}
freq = s3c64xx_freq_table ;
2009-11-03 17:42:11 +03:00
while ( count > 0 & & freq - > frequency ! = CPUFREQ_TABLE_END ) {
2009-06-15 14:23:20 +04:00
if ( freq - > frequency = = CPUFREQ_ENTRY_INVALID )
continue ;
2013-10-14 22:36:47 +04:00
dvfs = & s3c64xx_dvfs_table [ freq - > driver_data ] ;
2009-06-15 14:23:20 +04:00
found = 0 ;
for ( i = 0 ; i < count ; i + + ) {
v = regulator_list_voltage ( vddarm , i ) ;
if ( v > = dvfs - > vddarm_min & & v < = dvfs - > vddarm_max )
found = 1 ;
}
if ( ! found ) {
2011-12-05 22:22:01 +04:00
pr_debug ( " %dkHz unsupported by regulator \n " ,
2009-06-15 14:23:20 +04:00
freq - > frequency ) ;
freq - > frequency = CPUFREQ_ENTRY_INVALID ;
}
freq + + ;
}
2009-11-03 17:42:11 +03:00
/* Guess based on having to do an I2C/SPI write; in future we
* will be able to query the regulator performance here . */
regulator_latency = 1 * 1000 * 1000 ;
2009-06-15 14:23:20 +04:00
}
# endif
2011-03-11 10:10:03 +03:00
static int s3c64xx_cpufreq_driver_init ( struct cpufreq_policy * policy )
2009-06-15 14:23:20 +04:00
{
int ret ;
struct cpufreq_frequency_table * freq ;
if ( policy - > cpu ! = 0 )
return - EINVAL ;
if ( s3c64xx_freq_table = = NULL ) {
2011-12-05 22:22:01 +04:00
pr_err ( " No frequency information for this CPU \n " ) ;
2009-06-15 14:23:20 +04:00
return - ENODEV ;
}
2014-01-09 19:08:43 +04:00
policy - > clk = clk_get ( NULL , " armclk " ) ;
if ( IS_ERR ( policy - > clk ) ) {
2011-12-05 22:22:01 +04:00
pr_err ( " Unable to obtain ARMCLK: %ld \n " ,
2014-01-09 19:08:43 +04:00
PTR_ERR ( policy - > clk ) ) ;
return PTR_ERR ( policy - > clk ) ;
2009-06-15 14:23:20 +04:00
}
# ifdef CONFIG_REGULATOR
vddarm = regulator_get ( NULL , " vddarm " ) ;
if ( IS_ERR ( vddarm ) ) {
ret = PTR_ERR ( vddarm ) ;
2011-12-05 22:22:01 +04:00
pr_err ( " Failed to obtain VDDARM: %d \n " , ret ) ;
pr_err ( " Only frequency scaling available \n " ) ;
2009-06-15 14:23:20 +04:00
vddarm = NULL ;
} else {
2009-11-03 17:42:11 +03:00
s3c64xx_cpufreq_config_regulator ( ) ;
2009-06-15 14:23:20 +04:00
}
# endif
freq = s3c64xx_freq_table ;
while ( freq - > frequency ! = CPUFREQ_TABLE_END ) {
unsigned long r ;
/* Check for frequencies we can generate */
2014-01-09 19:08:43 +04:00
r = clk_round_rate ( policy - > clk , freq - > frequency * 1000 ) ;
2009-06-15 14:23:20 +04:00
r / = 1000 ;
2009-11-03 17:42:07 +03:00
if ( r ! = freq - > frequency ) {
2011-12-05 22:22:01 +04:00
pr_debug ( " %dkHz unsupported by clock \n " ,
2009-11-03 17:42:07 +03:00
freq - > frequency ) ;
2009-06-15 14:23:20 +04:00
freq - > frequency = CPUFREQ_ENTRY_INVALID ;
2009-11-03 17:42:07 +03:00
}
2009-06-15 14:23:20 +04:00
/* If we have no regulator then assume startup
* frequency is the maximum we can support . */
2014-01-09 19:08:43 +04:00
if ( ! vddarm & & freq - > frequency > clk_get_rate ( policy - > clk ) / 1000 )
2009-06-15 14:23:20 +04:00
freq - > frequency = CPUFREQ_ENTRY_INVALID ;
freq + + ;
}
2009-11-03 17:42:11 +03:00
/* Datasheet says PLL stabalisation time (if we were to use
* the PLLs , which we don ' t currently ) is ~ 300u s worst case ,
* but add some fudge .
*/
2013-10-03 18:59:22 +04:00
ret = cpufreq_generic_init ( policy , s3c64xx_freq_table ,
( 500 * 1000 ) + regulator_latency ) ;
2009-06-15 14:23:20 +04:00
if ( ret ! = 0 ) {
2011-12-05 22:22:01 +04:00
pr_err ( " Failed to configure frequency table: %d \n " ,
2009-06-15 14:23:20 +04:00
ret ) ;
regulator_put ( vddarm ) ;
2014-01-09 19:08:43 +04:00
clk_put ( policy - > clk ) ;
2009-06-15 14:23:20 +04:00
}
return ret ;
}
static struct cpufreq_driver s3c64xx_cpufreq_driver = {
2013-12-03 09:50:45 +04:00
. flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK ,
2013-10-03 18:58:21 +04:00
. verify = cpufreq_generic_frequency_table_verify ,
2013-10-25 18:15:48 +04:00
. target_index = s3c64xx_cpufreq_set_target ,
2014-01-09 19:08:43 +04:00
. get = cpufreq_generic_get ,
2009-06-15 14:23:20 +04:00
. init = s3c64xx_cpufreq_driver_init ,
. name = " s3c " ,
} ;
static int __init s3c64xx_cpufreq_init ( void )
{
return cpufreq_register_driver ( & s3c64xx_cpufreq_driver ) ;
}
module_init ( s3c64xx_cpufreq_init ) ;