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 clk * armclk ;
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_verify_speed ( struct cpufreq_policy * policy )
{
if ( policy - > cpu ! = 0 )
return - EINVAL ;
return cpufreq_frequency_table_verify ( policy , s3c64xx_freq_table ) ;
}
static unsigned int s3c64xx_cpufreq_get_speed ( unsigned int cpu )
{
if ( cpu ! = 0 )
return 0 ;
return clk_get_rate ( armclk ) / 1000 ;
}
static int s3c64xx_cpufreq_set_target ( struct cpufreq_policy * policy ,
unsigned int target_freq ,
unsigned int relation )
{
int ret ;
unsigned int i ;
struct cpufreq_freqs freqs ;
struct s3c64xx_dvfs * dvfs ;
ret = cpufreq_frequency_table_target ( policy , s3c64xx_freq_table ,
target_freq , relation , & i ) ;
if ( ret ! = 0 )
return ret ;
freqs . cpu = 0 ;
freqs . old = clk_get_rate ( armclk ) / 1000 ;
freqs . new = s3c64xx_freq_table [ i ] . frequency ;
freqs . flags = 0 ;
dvfs = & s3c64xx_dvfs_table [ s3c64xx_freq_table [ i ] . index ] ;
if ( freqs . old = = freqs . new )
return 0 ;
2011-12-05 22:22:01 +04:00
pr_debug ( " Transition %d-%dkHz \n " , freqs . old , freqs . new ) ;
2009-06-15 14:23:20 +04:00
cpufreq_notify_transition ( & freqs , CPUFREQ_PRECHANGE ) ;
# ifdef CONFIG_REGULATOR
if ( vddarm & & freqs . new > freqs . old ) {
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 " ,
2009-06-15 14:23:20 +04:00
freqs . new , ret ) ;
goto err ;
}
}
# endif
ret = clk_set_rate ( armclk , freqs . new * 1000 ) ;
if ( ret < 0 ) {
2011-12-05 22:22:01 +04:00
pr_err ( " Failed to set rate %dkHz: %d \n " ,
2009-06-15 14:23:20 +04:00
freqs . new , ret ) ;
goto err ;
}
2011-06-22 18:08:56 +04:00
cpufreq_notify_transition ( & freqs , CPUFREQ_POSTCHANGE ) ;
2009-06-15 14:23:20 +04:00
# ifdef CONFIG_REGULATOR
if ( vddarm & & freqs . new < freqs . old ) {
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 " ,
2009-06-15 14:23:20 +04:00
freqs . new , ret ) ;
goto err_clk ;
}
}
# endif
2011-12-05 22:22:01 +04:00
pr_debug ( " Set actual frequency %lukHz \n " ,
2009-06-15 14:23:20 +04:00
clk_get_rate ( armclk ) / 1000 ) ;
return 0 ;
err_clk :
if ( clk_set_rate ( armclk , freqs . old * 1000 ) < 0 )
pr_err ( " Failed to restore original clock rate \n " ) ;
err :
cpufreq_notify_transition ( & freqs , CPUFREQ_POSTCHANGE ) ;
return ret ;
}
# 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 ;
dvfs = & s3c64xx_dvfs_table [ freq - > index ] ;
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 ;
}
armclk = clk_get ( NULL , " armclk " ) ;
if ( IS_ERR ( armclk ) ) {
2011-12-05 22:22:01 +04:00
pr_err ( " Unable to obtain ARMCLK: %ld \n " ,
2009-06-15 14:23:20 +04:00
PTR_ERR ( armclk ) ) ;
return PTR_ERR ( armclk ) ;
}
# 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 */
r = clk_round_rate ( armclk , freq - > frequency * 1000 ) ;
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 . */
if ( ! vddarm & & freq - > frequency > s3c64xx_cpufreq_get_speed ( 0 ) )
freq - > frequency = CPUFREQ_ENTRY_INVALID ;
freq + + ;
}
policy - > cur = clk_get_rate ( armclk ) / 1000 ;
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 .
*/
policy - > cpuinfo . transition_latency = ( 500 * 1000 ) + regulator_latency ;
2009-06-15 14:23:20 +04:00
ret = cpufreq_frequency_table_cpuinfo ( policy , s3c64xx_freq_table ) ;
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 ) ;
clk_put ( armclk ) ;
}
return ret ;
}
static struct cpufreq_driver s3c64xx_cpufreq_driver = {
. owner = THIS_MODULE ,
. flags = 0 ,
. verify = s3c64xx_cpufreq_verify_speed ,
. target = s3c64xx_cpufreq_set_target ,
. get = s3c64xx_cpufreq_get_speed ,
. 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 ) ;