2006-12-06 19:25:04 +03:00
/*
* cpu . c : clock scaling for the iMX
*
* Copyright ( C ) 2000 2001 , The Delft University of Technology
* Copyright ( c ) 2004 Sascha Hauer < sascha @ saschahauer . de >
* Copyright ( C ) 2006 Inky Lung < ilung @ cwlinux . com >
* Copyright ( C ) 2006 Pavel Pisa , PiKRON < ppisa @ pikron . com >
*
* Based on SA1100 version written by :
* - Johan Pouwelse ( J . A . Pouwelse @ its . tudelft . nl ) : initial version
* - Erik Mouw ( J . A . K . Mouw @ its . tudelft . nl ) :
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*
*/
/*#define DEBUG*/
# include <linux/kernel.h>
# include <linux/types.h>
# include <linux/init.h>
# include <linux/cpufreq.h>
2008-07-05 12:02:46 +04:00
# include <linux/clk.h>
# include <linux/err.h>
2006-12-06 19:25:04 +03:00
# include <asm/system.h>
# include <asm/hardware.h>
# include "generic.h"
# ifndef __val2mfld
# define __val2mfld(mask,val) (((mask)&~((mask)<<1))*(val)&(mask))
# endif
# ifndef __mfld2val
# define __mfld2val(mask,val) (((val)&(mask)) / ((mask)&~((mask)<<1)))
# endif
# define CR_920T_CLOCK_MODE 0xC0000000
# define CR_920T_FASTBUS_MODE 0x00000000
# define CR_920T_ASYNC_MODE 0xC0000000
static u32 mpctl0_at_boot ;
2007-03-08 01:52:40 +03:00
static u32 bclk_div_at_boot ;
2006-12-06 19:25:04 +03:00
2008-07-05 12:02:46 +04:00
static struct clk * system_clk , * mcu_clk ;
2006-12-06 19:25:04 +03:00
static void imx_set_async_mode ( void )
{
adjust_cr ( CR_920T_CLOCK_MODE , CR_920T_ASYNC_MODE ) ;
}
static void imx_set_fastbus_mode ( void )
{
adjust_cr ( CR_920T_CLOCK_MODE , CR_920T_FASTBUS_MODE ) ;
}
static void imx_set_mpctl0 ( u32 mpctl0 )
{
unsigned long flags ;
if ( mpctl0 = = 0 ) {
local_irq_save ( flags ) ;
CSCR & = ~ CSCR_MPEN ;
local_irq_restore ( flags ) ;
return ;
}
local_irq_save ( flags ) ;
MPCTL0 = mpctl0 ;
CSCR | = CSCR_MPEN ;
local_irq_restore ( flags ) ;
}
/**
* imx_compute_mpctl - compute new PLL parameters
* @ new_mpctl : pointer to location assigned by new PLL control register value
* @ cur_mpctl : current PLL control register parameters
2007-03-08 01:56:16 +03:00
* @ f_ref : reference source frequency Hz
2006-12-06 19:25:04 +03:00
* @ freq : required frequency in Hz
* @ relation : is one of % CPUFREQ_RELATION_L ( supremum )
* and % CPUFREQ_RELATION_H ( infimum )
*/
2007-03-08 01:56:16 +03:00
long imx_compute_mpctl ( u32 * new_mpctl , u32 cur_mpctl , u32 f_ref , unsigned long freq , int relation )
2006-12-06 19:25:04 +03:00
{
u32 mfi ;
u32 mfn ;
u32 mfd ;
u32 pd ;
unsigned long long ll ;
long l ;
long quot ;
/* Fdppl=2*Fref*(MFI+MFN/(MFD+1))/(PD+1) */
/* PD=<0,15>, MFD=<1,1023>, MFI=<5,15> MFN=<0,1022> */
if ( cur_mpctl ) {
mfd = ( ( cur_mpctl > > 16 ) & 0x3ff ) + 1 ;
pd = ( ( cur_mpctl > > 26 ) & 0xf ) + 1 ;
} else {
pd = 2 ; mfd = 313 ;
}
/* pd=2; mfd=313; mfi=8; mfn=183; */
/* (MFI+MFN/(MFD)) = Fdppl / (2*Fref) * (PD); */
quot = ( f_ref + ( 1 < < 9 ) ) > > 10 ;
l = ( freq * pd + quot ) / ( 2 * quot ) ;
mfi = l > > 10 ;
mfn = ( ( l & ( ( 1 < < 10 ) - 1 ) ) * mfd + ( 1 < < 9 ) ) > > 10 ;
mfd - = 1 ;
pd - = 1 ;
* new_mpctl = ( ( mfi & 0xf ) < < 10 ) | ( mfn & 0x3ff ) | ( ( mfd & 0x3ff ) < < 16 )
| ( ( pd & 0xf ) < < 26 ) ;
ll = 2 * ( unsigned long long ) f_ref * ( ( mfi < < 16 ) + ( mfn < < 16 ) / ( mfd + 1 ) ) ;
quot = ( pd + 1 ) * ( 1 < < 16 ) ;
ll + = quot / 2 ;
do_div ( ll , quot ) ;
freq = ll ;
pr_debug ( KERN_DEBUG " imx: new PLL parameters pd=%d mfd=%d mfi=%d mfn=%d, freq=%ld \n " ,
pd , mfd , mfi , mfn , freq ) ;
return freq ;
}
static int imx_verify_speed ( struct cpufreq_policy * policy )
{
if ( policy - > cpu ! = 0 )
return - EINVAL ;
cpufreq_verify_within_limits ( policy , policy - > cpuinfo . min_freq , policy - > cpuinfo . max_freq ) ;
return 0 ;
}
static unsigned int imx_get_speed ( unsigned int cpu )
{
unsigned int freq ;
unsigned int cr ;
unsigned int cscr ;
unsigned int bclk_div ;
if ( cpu )
return 0 ;
cscr = CSCR ;
bclk_div = __mfld2val ( CSCR_BCLK_DIV , cscr ) + 1 ;
cr = get_cr ( ) ;
if ( ( cr & CR_920T_CLOCK_MODE ) = = CR_920T_FASTBUS_MODE ) {
2008-07-05 12:02:46 +04:00
freq = clk_get_rate ( system_clk ) ;
2006-12-06 19:25:04 +03:00
freq = ( freq + bclk_div / 2 ) / bclk_div ;
} else {
2008-07-05 12:02:46 +04:00
freq = clk_get_rate ( mcu_clk ) ;
2006-12-06 19:25:04 +03:00
if ( cscr & CSCR_MPU_PRESC )
freq / = 2 ;
}
freq = ( freq + 500 ) / 1000 ;
return freq ;
}
static int imx_set_target ( struct cpufreq_policy * policy ,
unsigned int target_freq ,
unsigned int relation )
{
struct cpufreq_freqs freqs ;
u32 mpctl0 = 0 ;
u32 cscr ;
unsigned long flags ;
long freq ;
long sysclk ;
2007-03-08 01:52:40 +03:00
unsigned int bclk_div = bclk_div_at_boot ;
2006-12-06 19:25:04 +03:00
2007-01-12 11:57:22 +03:00
/*
* Some governors do not respects CPU and policy lower limits
* which leads to bad things ( division by zero etc ) , ensure
* that such things do not happen .
*/
if ( target_freq < policy - > cpuinfo . min_freq )
target_freq = policy - > cpuinfo . min_freq ;
if ( target_freq < policy - > min )
target_freq = policy - > min ;
2006-12-06 19:25:04 +03:00
freq = target_freq * 1000 ;
pr_debug ( KERN_DEBUG " imx: requested frequency %ld Hz, mpctl0 at boot 0x%08x \n " ,
freq , mpctl0_at_boot ) ;
2008-07-05 12:02:46 +04:00
sysclk = clk_get_rate ( system_clk ) ;
2006-12-06 19:25:04 +03:00
2007-03-08 01:52:40 +03:00
if ( freq > sysclk / bclk_div_at_boot + 1000000 ) {
2007-03-08 01:56:16 +03:00
freq = imx_compute_mpctl ( & mpctl0 , mpctl0_at_boot , CLK32 * 512 , freq , relation ) ;
2006-12-06 19:25:04 +03:00
if ( freq < 0 ) {
printk ( KERN_WARNING " imx: target frequency %ld Hz cannot be set \n " , freq ) ;
return - EINVAL ;
}
} else {
if ( freq + 1000 < sysclk ) {
if ( relation = = CPUFREQ_RELATION_L )
bclk_div = ( sysclk - 1000 ) / freq ;
else
bclk_div = ( sysclk + freq + 1000 ) / freq ;
if ( bclk_div > 16 )
bclk_div = 16 ;
2007-03-08 01:52:40 +03:00
if ( bclk_div < bclk_div_at_boot )
bclk_div = bclk_div_at_boot ;
2006-12-06 19:25:04 +03:00
}
freq = ( sysclk + bclk_div / 2 ) / bclk_div ;
}
freqs . old = imx_get_speed ( 0 ) ;
freqs . new = ( freq + 500 ) / 1000 ;
freqs . cpu = 0 ;
freqs . flags = 0 ;
cpufreq_notify_transition ( & freqs , CPUFREQ_PRECHANGE ) ;
local_irq_save ( flags ) ;
imx_set_fastbus_mode ( ) ;
imx_set_mpctl0 ( mpctl0 ) ;
cscr = CSCR ;
cscr & = ~ CSCR_BCLK_DIV ;
cscr | = __val2mfld ( CSCR_BCLK_DIV , bclk_div - 1 ) ;
CSCR = cscr ;
if ( mpctl0 ) {
CSCR | = CSCR_MPLL_RESTART ;
2007-05-11 23:40:30 +04:00
/* Wait until MPLL is stabilized */
2006-12-06 19:25:04 +03:00
while ( CSCR & CSCR_MPLL_RESTART ) ;
imx_set_async_mode ( ) ;
}
local_irq_restore ( flags ) ;
cpufreq_notify_transition ( & freqs , CPUFREQ_POSTCHANGE ) ;
pr_debug ( KERN_INFO " imx: set frequency %ld Hz, running from %s \n " ,
freq , mpctl0 ? " MPLL " : " SPLL " ) ;
return 0 ;
}
static int __init imx_cpufreq_driver_init ( struct cpufreq_policy * policy )
{
printk ( KERN_INFO " i.MX cpu freq change driver v1.0 \n " ) ;
if ( policy - > cpu ! = 0 )
return - EINVAL ;
policy - > cur = policy - > min = policy - > max = imx_get_speed ( 0 ) ;
policy - > cpuinfo . min_freq = 8000 ;
policy - > cpuinfo . max_freq = 200000 ;
2007-01-12 11:57:22 +03:00
/* Manual states, that PLL stabilizes in two CLK32 periods */
policy - > cpuinfo . transition_latency = 4 * 1000000000LL / CLK32 ;
2006-12-06 19:25:04 +03:00
return 0 ;
}
static struct cpufreq_driver imx_driver = {
. flags = CPUFREQ_STICKY ,
. verify = imx_verify_speed ,
. target = imx_set_target ,
. get = imx_get_speed ,
. init = imx_cpufreq_driver_init ,
. name = " imx " ,
} ;
static int __init imx_cpufreq_init ( void )
{
2007-03-08 01:52:40 +03:00
bclk_div_at_boot = __mfld2val ( CSCR_BCLK_DIV , CSCR ) + 1 ;
2006-12-06 19:25:04 +03:00
mpctl0_at_boot = 0 ;
2008-07-05 12:02:46 +04:00
system_clk = clk_get ( NULL , " system_clk " ) ;
if ( IS_ERR ( system_clk ) )
return PTR_ERR ( system_clk ) ;
mcu_clk = clk_get ( NULL , " mcu_clk " ) ;
if ( IS_ERR ( mcu_clk ) ) {
clk_put ( system_clk ) ;
return PTR_ERR ( mcu_clk ) ;
}
2006-12-06 19:25:04 +03:00
if ( ( CSCR & CSCR_MPEN ) & &
( ( get_cr ( ) & CR_920T_CLOCK_MODE ) ! = CR_920T_FASTBUS_MODE ) )
mpctl0_at_boot = MPCTL0 ;
return cpufreq_register_driver ( & imx_driver ) ;
}
arch_initcall ( imx_cpufreq_init ) ;