2012-02-16 14:42:32 +04:00
/*
* S3C2416 / 2450 CPUfreq Support
*
* Copyright 2011 Heiko Stuebner < heiko @ sntech . de >
*
* based on s3c64xx_cpufreq . c
*
* Copyright 2009 Wolfson Microelectronics plc
*
* 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/types.h>
# include <linux/init.h>
# include <linux/cpufreq.h>
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/regulator/consumer.h>
# include <linux/reboot.h>
# include <linux/module.h>
static DEFINE_MUTEX ( cpufreq_lock ) ;
struct s3c2416_data {
struct clk * armdiv ;
struct clk * armclk ;
struct clk * hclk ;
unsigned long regulator_latency ;
# ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
struct regulator * vddarm ;
# endif
struct cpufreq_frequency_table * freq_table ;
bool is_dvs ;
bool disable_dvs ;
} ;
static struct s3c2416_data s3c2416_cpufreq ;
struct s3c2416_dvfs {
unsigned int vddarm_min ;
unsigned int vddarm_max ;
} ;
/* pseudo-frequency for dvs mode */
# define FREQ_DVS 132333
/* frequency to sleep and reboot in
* it ' s essential to leave dvs , as some boards do not reconfigure the
* regulator on reboot
*/
# define FREQ_SLEEP 133333
/* Sources for the ARMCLK */
# define SOURCE_HCLK 0
# define SOURCE_ARMDIV 1
# ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
/* S3C2416 only supports changing the voltage in the dvs-mode.
* Voltages down to 1.0 V seem to work , so we take what the regulator
* can get us .
*/
static struct s3c2416_dvfs s3c2416_dvfs_table [ ] = {
[ SOURCE_HCLK ] = { 950000 , 1250000 } ,
[ SOURCE_ARMDIV ] = { 1250000 , 1350000 } ,
} ;
# endif
static struct cpufreq_frequency_table s3c2416_freq_table [ ] = {
2014-03-28 17:41:47 +04:00
{ 0 , SOURCE_HCLK , FREQ_DVS } ,
{ 0 , SOURCE_ARMDIV , 133333 } ,
{ 0 , SOURCE_ARMDIV , 266666 } ,
{ 0 , SOURCE_ARMDIV , 400000 } ,
{ 0 , 0 , CPUFREQ_TABLE_END } ,
2012-02-16 14:42:32 +04:00
} ;
static struct cpufreq_frequency_table s3c2450_freq_table [ ] = {
2014-03-28 17:41:47 +04:00
{ 0 , SOURCE_HCLK , FREQ_DVS } ,
{ 0 , SOURCE_ARMDIV , 133500 } ,
{ 0 , SOURCE_ARMDIV , 267000 } ,
{ 0 , SOURCE_ARMDIV , 534000 } ,
{ 0 , 0 , CPUFREQ_TABLE_END } ,
2012-02-16 14:42:32 +04:00
} ;
static unsigned int s3c2416_cpufreq_get_speed ( unsigned int cpu )
{
struct s3c2416_data * s3c_freq = & s3c2416_cpufreq ;
if ( cpu ! = 0 )
return 0 ;
/* return our pseudo-frequency when in dvs mode */
if ( s3c_freq - > is_dvs )
return FREQ_DVS ;
return clk_get_rate ( s3c_freq - > armclk ) / 1000 ;
}
static int s3c2416_cpufreq_set_armdiv ( struct s3c2416_data * s3c_freq ,
unsigned int freq )
{
int ret ;
if ( clk_get_rate ( s3c_freq - > armdiv ) / 1000 ! = freq ) {
ret = clk_set_rate ( s3c_freq - > armdiv , freq * 1000 ) ;
if ( ret < 0 ) {
pr_err ( " cpufreq: Failed to set armdiv rate %dkHz: %d \n " ,
freq , ret ) ;
return ret ;
}
}
return 0 ;
}
static int s3c2416_cpufreq_enter_dvs ( struct s3c2416_data * s3c_freq , int idx )
{
# ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
struct s3c2416_dvfs * dvfs ;
# endif
int ret ;
if ( s3c_freq - > is_dvs ) {
pr_debug ( " cpufreq: already in dvs mode, nothing to do \n " ) ;
return 0 ;
}
pr_debug ( " cpufreq: switching armclk to hclk (%lukHz) \n " ,
clk_get_rate ( s3c_freq - > hclk ) / 1000 ) ;
ret = clk_set_parent ( s3c_freq - > armclk , s3c_freq - > hclk ) ;
if ( ret < 0 ) {
pr_err ( " cpufreq: Failed to switch armclk to hclk: %d \n " , ret ) ;
return ret ;
}
# ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
/* changing the core voltage is only allowed when in dvs mode */
if ( s3c_freq - > vddarm ) {
dvfs = & s3c2416_dvfs_table [ idx ] ;
2012-07-18 04:09:27 +04:00
pr_debug ( " cpufreq: setting regulator to %d-%d \n " ,
2012-02-16 14:42:32 +04:00
dvfs - > vddarm_min , dvfs - > vddarm_max ) ;
ret = regulator_set_voltage ( s3c_freq - > vddarm ,
dvfs - > vddarm_min ,
dvfs - > vddarm_max ) ;
/* when lowering the voltage failed, there is nothing to do */
if ( ret ! = 0 )
pr_err ( " cpufreq: Failed to set VDDARM: %d \n " , ret ) ;
}
# endif
s3c_freq - > is_dvs = 1 ;
return 0 ;
}
static int s3c2416_cpufreq_leave_dvs ( struct s3c2416_data * s3c_freq , int idx )
{
# ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
struct s3c2416_dvfs * dvfs ;
# endif
int ret ;
if ( ! s3c_freq - > is_dvs ) {
pr_debug ( " cpufreq: not in dvs mode, so can't leave \n " ) ;
return 0 ;
}
# ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
if ( s3c_freq - > vddarm ) {
dvfs = & s3c2416_dvfs_table [ idx ] ;
2012-07-18 04:09:27 +04:00
pr_debug ( " cpufreq: setting regulator to %d-%d \n " ,
2012-02-16 14:42:32 +04:00
dvfs - > vddarm_min , dvfs - > vddarm_max ) ;
ret = regulator_set_voltage ( s3c_freq - > vddarm ,
dvfs - > vddarm_min ,
dvfs - > vddarm_max ) ;
if ( ret ! = 0 ) {
pr_err ( " cpufreq: Failed to set VDDARM: %d \n " , ret ) ;
return ret ;
}
}
# endif
/* force armdiv to hclk frequency for transition from dvs*/
if ( clk_get_rate ( s3c_freq - > armdiv ) > clk_get_rate ( s3c_freq - > hclk ) ) {
pr_debug ( " cpufreq: force armdiv to hclk frequency (%lukHz) \n " ,
clk_get_rate ( s3c_freq - > hclk ) / 1000 ) ;
ret = s3c2416_cpufreq_set_armdiv ( s3c_freq ,
clk_get_rate ( s3c_freq - > hclk ) / 1000 ) ;
if ( ret < 0 ) {
2013-05-31 20:30:56 +04:00
pr_err ( " cpufreq: Failed to set the armdiv to %lukHz: %d \n " ,
2012-02-16 14:42:32 +04:00
clk_get_rate ( s3c_freq - > hclk ) / 1000 , ret ) ;
return ret ;
}
}
pr_debug ( " cpufreq: switching armclk parent to armdiv (%lukHz) \n " ,
clk_get_rate ( s3c_freq - > armdiv ) / 1000 ) ;
ret = clk_set_parent ( s3c_freq - > armclk , s3c_freq - > armdiv ) ;
if ( ret < 0 ) {
pr_err ( " cpufreq: Failed to switch armclk clock parent to armdiv: %d \n " ,
ret ) ;
return ret ;
}
s3c_freq - > is_dvs = 0 ;
return 0 ;
}
static int s3c2416_cpufreq_set_target ( struct cpufreq_policy * policy ,
2013-10-25 18:15:48 +04:00
unsigned int index )
2012-02-16 14:42:32 +04:00
{
struct s3c2416_data * s3c_freq = & s3c2416_cpufreq ;
2013-08-14 18:08:24 +04:00
unsigned int new_freq ;
2012-02-16 14:42:32 +04:00
int idx , ret , to_dvs = 0 ;
mutex_lock ( & cpufreq_lock ) ;
2013-10-25 18:15:48 +04:00
idx = s3c_freq - > freq_table [ index ] . driver_data ;
2012-02-16 14:42:32 +04:00
if ( idx = = SOURCE_HCLK )
to_dvs = 1 ;
/* switching to dvs when it's not allowed */
if ( to_dvs & & s3c_freq - > disable_dvs ) {
pr_debug ( " cpufreq: entering dvs mode not allowed \n " ) ;
ret = - EINVAL ;
goto out ;
}
/* When leavin dvs mode, always switch the armdiv to the hclk rate
* The S3C2416 has stability issues when switching directly to
* higher frequencies .
*/
2013-08-14 18:08:24 +04:00
new_freq = ( s3c_freq - > is_dvs & & ! to_dvs )
2012-02-16 14:42:32 +04:00
? clk_get_rate ( s3c_freq - > hclk ) / 1000
2013-10-25 18:15:48 +04:00
: s3c_freq - > freq_table [ index ] . frequency ;
2012-02-16 14:42:32 +04:00
if ( to_dvs ) {
pr_debug ( " cpufreq: enter dvs \n " ) ;
ret = s3c2416_cpufreq_enter_dvs ( s3c_freq , idx ) ;
} else if ( s3c_freq - > is_dvs ) {
pr_debug ( " cpufreq: leave dvs \n " ) ;
ret = s3c2416_cpufreq_leave_dvs ( s3c_freq , idx ) ;
} else {
2013-08-14 18:08:24 +04:00
pr_debug ( " cpufreq: change armdiv to %dkHz \n " , new_freq ) ;
ret = s3c2416_cpufreq_set_armdiv ( s3c_freq , new_freq ) ;
2012-02-16 14:42:32 +04:00
}
out :
mutex_unlock ( & cpufreq_lock ) ;
return ret ;
}
# ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
2015-02-18 23:55:03 +03:00
static void s3c2416_cpufreq_cfg_regulator ( struct s3c2416_data * s3c_freq )
2012-02-16 14:42:32 +04:00
{
int count , v , i , found ;
2014-04-26 00:15:38 +04:00
struct cpufreq_frequency_table * pos ;
2012-02-16 14:42:32 +04:00
struct s3c2416_dvfs * dvfs ;
count = regulator_count_voltages ( s3c_freq - > vddarm ) ;
if ( count < 0 ) {
pr_err ( " cpufreq: Unable to check supported voltages \n " ) ;
return ;
}
2014-04-26 00:15:38 +04:00
if ( ! count )
goto out ;
2012-02-16 14:42:32 +04:00
2014-04-26 00:15:38 +04:00
cpufreq_for_each_valid_entry ( pos , s3c_freq - > freq_table ) {
dvfs = & s3c2416_dvfs_table [ pos - > driver_data ] ;
2012-02-16 14:42:32 +04:00
found = 0 ;
/* Check only the min-voltage, more is always ok on S3C2416 */
for ( i = 0 ; i < count ; i + + ) {
v = regulator_list_voltage ( s3c_freq - > vddarm , i ) ;
if ( v > = dvfs - > vddarm_min )
found = 1 ;
}
if ( ! found ) {
pr_debug ( " cpufreq: %dkHz unsupported by regulator \n " ,
2014-04-26 00:15:38 +04:00
pos - > frequency ) ;
pos - > frequency = CPUFREQ_ENTRY_INVALID ;
2012-02-16 14:42:32 +04:00
}
}
2014-04-26 00:15:38 +04:00
out :
2012-02-16 14:42:32 +04:00
/* Guessed */
s3c_freq - > regulator_latency = 1 * 1000 * 1000 ;
}
# endif
static int s3c2416_cpufreq_reboot_notifier_evt ( struct notifier_block * this ,
unsigned long event , void * ptr )
{
struct s3c2416_data * s3c_freq = & s3c2416_cpufreq ;
int ret ;
mutex_lock ( & cpufreq_lock ) ;
/* disable further changes */
s3c_freq - > disable_dvs = 1 ;
mutex_unlock ( & cpufreq_lock ) ;
/* some boards don't reconfigure the regulator on reboot, which
* could lead to undervolting the cpu when the clock is reset .
* Therefore we always leave the DVS mode on reboot .
*/
if ( s3c_freq - > is_dvs ) {
pr_debug ( " cpufreq: leave dvs on reboot \n " ) ;
ret = cpufreq_driver_target ( cpufreq_cpu_get ( 0 ) , FREQ_SLEEP , 0 ) ;
if ( ret < 0 )
return NOTIFY_BAD ;
}
return NOTIFY_DONE ;
}
static struct notifier_block s3c2416_cpufreq_reboot_notifier = {
. notifier_call = s3c2416_cpufreq_reboot_notifier_evt ,
} ;
2015-02-18 23:55:03 +03:00
static int s3c2416_cpufreq_driver_init ( struct cpufreq_policy * policy )
2012-02-16 14:42:32 +04:00
{
struct s3c2416_data * s3c_freq = & s3c2416_cpufreq ;
2014-04-26 00:15:38 +04:00
struct cpufreq_frequency_table * pos ;
2012-02-16 14:42:32 +04:00
struct clk * msysclk ;
unsigned long rate ;
int ret ;
if ( policy - > cpu ! = 0 )
return - EINVAL ;
msysclk = clk_get ( NULL , " msysclk " ) ;
if ( IS_ERR ( msysclk ) ) {
ret = PTR_ERR ( msysclk ) ;
pr_err ( " cpufreq: Unable to obtain msysclk: %d \n " , ret ) ;
return ret ;
}
/*
* S3C2416 and S3C2450 share the same processor - ID and also provide no
* other means to distinguish them other than through the rate of
* msysclk . On S3C2416 msysclk runs at 800 MHz and on S3C2450 at 533 MHz .
*/
rate = clk_get_rate ( msysclk ) ;
if ( rate = = 800 * 1000 * 1000 ) {
pr_info ( " cpufreq: msysclk running at %lukHz, using S3C2416 frequency table \n " ,
rate / 1000 ) ;
s3c_freq - > freq_table = s3c2416_freq_table ;
policy - > cpuinfo . max_freq = 400000 ;
} else if ( rate / 1000 = = 534000 ) {
pr_info ( " cpufreq: msysclk running at %lukHz, using S3C2450 frequency table \n " ,
rate / 1000 ) ;
s3c_freq - > freq_table = s3c2450_freq_table ;
policy - > cpuinfo . max_freq = 534000 ;
}
/* not needed anymore */
clk_put ( msysclk ) ;
if ( s3c_freq - > freq_table = = NULL ) {
pr_err ( " cpufreq: No frequency information for this CPU, msysclk at %lukHz \n " ,
rate / 1000 ) ;
return - ENODEV ;
}
s3c_freq - > is_dvs = 0 ;
s3c_freq - > armdiv = clk_get ( NULL , " armdiv " ) ;
if ( IS_ERR ( s3c_freq - > armdiv ) ) {
ret = PTR_ERR ( s3c_freq - > armdiv ) ;
pr_err ( " cpufreq: Unable to obtain ARMDIV: %d \n " , ret ) ;
return ret ;
}
s3c_freq - > hclk = clk_get ( NULL , " hclk " ) ;
if ( IS_ERR ( s3c_freq - > hclk ) ) {
ret = PTR_ERR ( s3c_freq - > hclk ) ;
pr_err ( " cpufreq: Unable to obtain HCLK: %d \n " , ret ) ;
goto err_hclk ;
}
/* chech hclk rate, we only support the common 133MHz for now
* hclk could also run at 66 MHz , but this not often used
*/
rate = clk_get_rate ( s3c_freq - > hclk ) ;
if ( rate < 133 * 1000 * 1000 ) {
pr_err ( " cpufreq: HCLK not at 133MHz \n " ) ;
ret = - EINVAL ;
goto err_armclk ;
}
s3c_freq - > armclk = clk_get ( NULL , " armclk " ) ;
if ( IS_ERR ( s3c_freq - > armclk ) ) {
ret = PTR_ERR ( s3c_freq - > armclk ) ;
pr_err ( " cpufreq: Unable to obtain ARMCLK: %d \n " , ret ) ;
goto err_armclk ;
}
# ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
s3c_freq - > vddarm = regulator_get ( NULL , " vddarm " ) ;
if ( IS_ERR ( s3c_freq - > vddarm ) ) {
ret = PTR_ERR ( s3c_freq - > vddarm ) ;
pr_err ( " cpufreq: Failed to obtain VDDARM: %d \n " , ret ) ;
goto err_vddarm ;
}
s3c2416_cpufreq_cfg_regulator ( s3c_freq ) ;
# else
s3c_freq - > regulator_latency = 0 ;
# endif
2014-04-26 00:15:38 +04:00
cpufreq_for_each_entry ( pos , s3c_freq - > freq_table ) {
2012-02-16 14:42:32 +04:00
/* special handling for dvs mode */
2014-04-26 00:15:38 +04:00
if ( pos - > driver_data = = 0 ) {
2012-02-16 14:42:32 +04:00
if ( ! s3c_freq - > hclk ) {
pr_debug ( " cpufreq: %dkHz unsupported as it would need unavailable dvs mode \n " ,
2014-04-26 00:15:38 +04:00
pos - > frequency ) ;
pos - > frequency = CPUFREQ_ENTRY_INVALID ;
2012-02-16 14:42:32 +04:00
} else {
continue ;
}
}
/* Check for frequencies we can generate */
rate = clk_round_rate ( s3c_freq - > armdiv ,
2014-04-26 00:15:38 +04:00
pos - > frequency * 1000 ) ;
2012-02-16 14:42:32 +04:00
rate / = 1000 ;
2014-04-26 00:15:38 +04:00
if ( rate ! = pos - > frequency ) {
2012-02-16 14:42:32 +04:00
pr_debug ( " cpufreq: %dkHz unsupported by clock (clk_round_rate return %lu) \n " ,
2014-04-26 00:15:38 +04:00
pos - > frequency , rate ) ;
pos - > frequency = CPUFREQ_ENTRY_INVALID ;
2012-02-16 14:42:32 +04:00
}
}
/* Datasheet says PLL stabalisation time must be at least 300us,
* so but add some fudge . ( reference in LOCKCON0 register description )
*/
2013-10-03 18:59:22 +04:00
ret = cpufreq_generic_init ( policy , s3c_freq - > freq_table ,
( 500 * 1000 ) + s3c_freq - > regulator_latency ) ;
2012-02-16 14:42:32 +04:00
if ( ret )
goto err_freq_table ;
register_reboot_notifier ( & s3c2416_cpufreq_reboot_notifier ) ;
return 0 ;
err_freq_table :
# ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
regulator_put ( s3c_freq - > vddarm ) ;
err_vddarm :
# endif
clk_put ( s3c_freq - > armclk ) ;
err_armclk :
clk_put ( s3c_freq - > hclk ) ;
err_hclk :
clk_put ( s3c_freq - > armdiv ) ;
return ret ;
}
static struct cpufreq_driver s3c2416_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 = s3c2416_cpufreq_set_target ,
2012-02-16 14:42:32 +04:00
. get = s3c2416_cpufreq_get_speed ,
. init = s3c2416_cpufreq_driver_init ,
. name = " s3c2416 " ,
2013-10-03 18:58:21 +04:00
. attr = cpufreq_generic_attr ,
2012-02-16 14:42:32 +04:00
} ;
static int __init s3c2416_cpufreq_init ( void )
{
return cpufreq_register_driver ( & s3c2416_cpufreq_driver ) ;
}
module_init ( s3c2416_cpufreq_init ) ;