2013-01-22 03:24:34 +04:00
/*
2009-07-31 02:23:33 +04:00
* Copyright 2008 Simtec Electronics
* http : //armlinux.simtec.co.uk/
* Ben Dooks < ben @ simtec . co . uk >
*
* S3C2412 CPU Frequency scalling
*
* 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 .
*/
2016-04-05 23:28:25 +03:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2009-07-31 02:23:33 +04:00
# include <linux/init.h>
# include <linux/module.h>
# include <linux/interrupt.h>
# include <linux/ioport.h>
# include <linux/cpufreq.h>
2011-12-22 04:01:38 +04:00
# include <linux/device.h>
2009-07-31 02:23:33 +04:00
# include <linux/delay.h>
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/io.h>
# include <asm/mach/arch.h>
# include <asm/mach/map.h>
# include <mach/regs-clock.h>
2013-04-04 16:54:15 +04:00
# include <mach/s3c2412.h>
2009-07-31 02:23:33 +04:00
# include <plat/cpu.h>
# include <plat/cpu-freq-core.h>
/* our clock resources. */
static struct clk * xtal ;
static struct clk * fclk ;
static struct clk * hclk ;
static struct clk * armclk ;
/* HDIV: 1, 2, 3, 4, 6, 8 */
static int s3c2412_cpufreq_calcdivs ( struct s3c_cpufreq_config * cfg )
{
unsigned int hdiv , pdiv , armdiv , dvs ;
unsigned long hclk , fclk , armclk , armdiv_clk ;
unsigned long hclk_max ;
fclk = cfg - > freq . fclk ;
armclk = cfg - > freq . armclk ;
hclk_max = cfg - > max . hclk ;
/* We can't run hclk above armclk as at the best we have to
* have armclk and hclk in dvs mode . */
if ( hclk_max > armclk )
hclk_max = armclk ;
s3c_freq_dbg ( " %s: fclk=%lu, armclk=%lu, hclk_max=%lu \n " ,
__func__ , fclk , armclk , hclk_max ) ;
s3c_freq_dbg ( " %s: want f=%lu, arm=%lu, h=%lu, p=%lu \n " ,
__func__ , cfg - > freq . fclk , cfg - > freq . armclk ,
cfg - > freq . hclk , cfg - > freq . pclk ) ;
armdiv = fclk / armclk ;
if ( armdiv < 1 )
armdiv = 1 ;
if ( armdiv > 2 )
armdiv = 2 ;
cfg - > divs . arm_divisor = armdiv ;
armdiv_clk = fclk / armdiv ;
hdiv = armdiv_clk / hclk_max ;
if ( hdiv < 1 )
hdiv = 1 ;
cfg - > freq . hclk = hclk = armdiv_clk / hdiv ;
/* set dvs depending on whether we reached armclk or not. */
cfg - > divs . dvs = dvs = armclk < armdiv_clk ;
/* update the actual armclk we achieved. */
cfg - > freq . armclk = dvs ? hclk : armdiv_clk ;
s3c_freq_dbg ( " %s: armclk %lu, hclk %lu, armdiv %d, hdiv %d, dvs %d \n " ,
__func__ , armclk , hclk , armdiv , hdiv , cfg - > divs . dvs ) ;
if ( hdiv > 4 )
goto invalid ;
pdiv = ( hclk > cfg - > max . pclk ) ? 2 : 1 ;
if ( ( hclk / pdiv ) > cfg - > max . pclk )
pdiv + + ;
cfg - > freq . pclk = hclk / pdiv ;
s3c_freq_dbg ( " %s: pdiv %d \n " , __func__ , pdiv ) ;
if ( pdiv > 2 )
goto invalid ;
pdiv * = hdiv ;
/* store the result, and then return */
cfg - > divs . h_divisor = hdiv * armdiv ;
cfg - > divs . p_divisor = pdiv * armdiv ;
return 0 ;
2013-01-22 03:24:34 +04:00
invalid :
2009-07-31 02:23:33 +04:00
return - EINVAL ;
}
static void s3c2412_cpufreq_setdivs ( struct s3c_cpufreq_config * cfg )
{
unsigned long clkdiv ;
unsigned long olddiv ;
olddiv = clkdiv = __raw_readl ( S3C2410_CLKDIVN ) ;
/* clear off current clock info */
clkdiv & = ~ S3C2412_CLKDIVN_ARMDIVN ;
clkdiv & = ~ S3C2412_CLKDIVN_HDIVN_MASK ;
clkdiv & = ~ S3C2412_CLKDIVN_PDIVN ;
if ( cfg - > divs . arm_divisor = = 2 )
clkdiv | = S3C2412_CLKDIVN_ARMDIVN ;
clkdiv | = ( ( cfg - > divs . h_divisor / cfg - > divs . arm_divisor ) - 1 ) ;
if ( cfg - > divs . p_divisor ! = cfg - > divs . h_divisor )
clkdiv | = S3C2412_CLKDIVN_PDIVN ;
s3c_freq_dbg ( " %s: div %08lx => %08lx \n " , __func__ , olddiv , clkdiv ) ;
__raw_writel ( clkdiv , S3C2410_CLKDIVN ) ;
clk_set_parent ( armclk , cfg - > divs . dvs ? hclk : fclk ) ;
}
static void s3c2412_cpufreq_setrefresh ( struct s3c_cpufreq_config * cfg )
{
struct s3c_cpufreq_board * board = cfg - > board ;
unsigned long refresh ;
s3c_freq_dbg ( " %s: refresh %u ns, hclk %lu \n " , __func__ ,
board - > refresh , cfg - > freq . hclk ) ;
/* Reduce both the refresh time (in ns) and the frequency (in MHz)
* by 10 each to ensure that we do not overflow 32 bit numbers . This
* should work for HCLK up to 133 MHz and refresh period up to 30u sec .
*/
refresh = ( board - > refresh / 10 ) ;
refresh * = ( cfg - > freq . hclk / 100 ) ;
refresh / = ( 1 * 1000 * 1000 ) ; /* 10^6 */
s3c_freq_dbg ( " %s: setting refresh 0x%08lx \n " , __func__ , refresh ) ;
__raw_writel ( refresh , S3C2412_REFRESH ) ;
}
/* set the default cpu frequency information, based on an 200MHz part
* as we have no other way of detecting the speed rating in software .
*/
static struct s3c_cpufreq_info s3c2412_cpufreq_info = {
. max = {
. fclk = 200000000 ,
. hclk = 100000000 ,
. pclk = 50000000 ,
} ,
. latency = 5000000 , /* 5ms */
. locktime_m = 150 ,
. locktime_u = 150 ,
. locktime_bits = 16 ,
. name = " s3c2412 " ,
. set_refresh = s3c2412_cpufreq_setrefresh ,
. set_divs = s3c2412_cpufreq_setdivs ,
. calc_divs = s3c2412_cpufreq_calcdivs ,
2009-07-31 02:23:37 +04:00
. calc_iotiming = s3c2412_iotiming_calc ,
. set_iotiming = s3c2412_iotiming_set ,
. get_iotiming = s3c2412_iotiming_get ,
2009-07-31 02:23:42 +04:00
. debug_io_show = s3c_cpufreq_debugfs_call ( s3c2412_iotiming_debugfs ) ,
2009-07-31 02:23:33 +04:00
} ;
2012-01-27 10:35:25 +04:00
static int s3c2412_cpufreq_add ( struct device * dev ,
struct subsys_interface * sif )
2009-07-31 02:23:33 +04:00
{
unsigned long fclk_rate ;
hclk = clk_get ( NULL , " hclk " ) ;
if ( IS_ERR ( hclk ) ) {
2016-04-05 23:28:25 +03:00
pr_err ( " cannot find hclk clock \n " ) ;
2009-07-31 02:23:33 +04:00
return - ENOENT ;
}
fclk = clk_get ( NULL , " fclk " ) ;
if ( IS_ERR ( fclk ) ) {
2016-04-05 23:28:25 +03:00
pr_err ( " cannot find fclk clock \n " ) ;
2009-07-31 02:23:33 +04:00
goto err_fclk ;
}
fclk_rate = clk_get_rate ( fclk ) ;
if ( fclk_rate > 200000000 ) {
2016-04-05 23:28:25 +03:00
pr_info ( " fclk %ld MHz, assuming 266MHz capable part \n " ,
fclk_rate / 1000000 ) ;
2009-07-31 02:23:33 +04:00
s3c2412_cpufreq_info . max . fclk = 266000000 ;
s3c2412_cpufreq_info . max . hclk = 133000000 ;
s3c2412_cpufreq_info . max . pclk = 66000000 ;
}
armclk = clk_get ( NULL , " armclk " ) ;
if ( IS_ERR ( armclk ) ) {
2016-04-05 23:28:25 +03:00
pr_err ( " cannot find arm clock \n " ) ;
2009-07-31 02:23:33 +04:00
goto err_armclk ;
}
xtal = clk_get ( NULL , " xtal " ) ;
if ( IS_ERR ( xtal ) ) {
2016-04-05 23:28:25 +03:00
pr_err ( " cannot find xtal clock \n " ) ;
2009-07-31 02:23:33 +04:00
goto err_xtal ;
}
return s3c_cpufreq_register ( & s3c2412_cpufreq_info ) ;
err_xtal :
clk_put ( armclk ) ;
err_armclk :
clk_put ( fclk ) ;
err_fclk :
clk_put ( hclk ) ;
return - ENOENT ;
}
2011-12-22 04:01:38 +04:00
static struct subsys_interface s3c2412_cpufreq_interface = {
. name = " s3c2412_cpufreq " ,
. subsys = & s3c2412_subsys ,
. add_dev = s3c2412_cpufreq_add ,
2009-07-31 02:23:33 +04:00
} ;
static int s3c2412_cpufreq_init ( void )
{
2011-12-22 04:01:38 +04:00
return subsys_interface_register ( & s3c2412_cpufreq_interface ) ;
2009-07-31 02:23:33 +04:00
}
arch_initcall ( s3c2412_cpufreq_init ) ;