2005-04-17 02:20:36 +04:00
/*
* arch / sh / kernel / cpufreq . c
*
* cpufreq driver for the SuperH processors .
*
* Copyright ( C ) 2002 , 2003 , 2004 , 2005 Paul Mundt
* Copyright ( C ) 2002 M . R . Brown
*
* 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 .
*/
# include <linux/types.h>
# include <linux/cpufreq.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/cpumask.h>
# include <linux/smp.h>
2005-10-31 02:03:48 +03:00
# include <linux/sched.h> /* set_cpus_allowed() */
2005-04-17 02:20:36 +04:00
# include <asm/processor.h>
# include <asm/watchdog.h>
# include <asm/freq.h>
# include <asm/io.h>
/*
* For SuperH , each policy change requires that we change the IFC , BFC , and
* PFC at the same time . Here we define sane values that won ' t trash the
* system .
*
* Note the max set is computed at runtime , we use the divisors that we booted
* with to setup our maximum operating frequencies .
*/
struct clock_set {
unsigned int ifc ;
unsigned int bfc ;
unsigned int pfc ;
} clock_sets [ ] = {
# if defined(CONFIG_CPU_SH3) || defined(CONFIG_CPU_SH2)
{ 0 , 0 , 0 } , /* not implemented yet */
# elif defined(CONFIG_CPU_SH4)
{ 4 , 8 , 8 } , /* min - IFC: 1/4, BFC: 1/8, PFC: 1/8 */
{ 1 , 2 , 2 } , /* max - IFC: 1, BFC: 1/2, PFC: 1/2 */
# endif
} ;
# define MIN_CLOCK_SET 0
# define MAX_CLOCK_SET (ARRAY_SIZE(clock_sets) - 1)
/*
* For the time being , we only support two frequencies , which in turn are
* aimed at the POWERSAVE and PERFORMANCE policies , which in turn are derived
* directly from the respective min / max clock sets . Technically we could
* support a wider range of frequencies , but these vary far too much for each
* CPU subtype ( and we ' d have to construct a frequency table for each subtype ) .
*
* Maybe something to implement in the future . .
*/
# define SH_FREQ_MAX 0
# define SH_FREQ_MIN 1
static struct cpufreq_frequency_table sh_freqs [ ] = {
{ SH_FREQ_MAX , 0 } ,
{ SH_FREQ_MIN , 0 } ,
{ 0 , CPUFREQ_TABLE_END } ,
} ;
static void sh_cpufreq_update_clocks ( unsigned int set )
{
current_cpu_data . cpu_clock = current_cpu_data . master_clock / clock_sets [ set ] . ifc ;
current_cpu_data . bus_clock = current_cpu_data . master_clock / clock_sets [ set ] . bfc ;
current_cpu_data . module_clock = current_cpu_data . master_clock / clock_sets [ set ] . pfc ;
current_cpu_data . loops_per_jiffy = loops_per_jiffy ;
}
/* XXX: This needs to be split out per CPU and CPU subtype. */
/*
* Here we notify other drivers of the proposed change and the final change .
*/
static int sh_cpufreq_setstate ( unsigned int cpu , unsigned int set )
{
unsigned short frqcr = ctrl_inw ( FRQCR ) ;
cpumask_t cpus_allowed ;
struct cpufreq_freqs freqs ;
if ( ! cpu_online ( cpu ) )
return - ENODEV ;
cpus_allowed = current - > cpus_allowed ;
set_cpus_allowed ( current , cpumask_of_cpu ( cpu ) ) ;
BUG_ON ( smp_processor_id ( ) ! = cpu ) ;
freqs . cpu = cpu ;
freqs . old = current_cpu_data . cpu_clock / 1000 ;
freqs . new = ( current_cpu_data . master_clock / clock_sets [ set ] . ifc ) / 1000 ;
cpufreq_notify_transition ( & freqs , CPUFREQ_PRECHANGE ) ;
# if defined(CONFIG_CPU_SH3)
frqcr | = ( newstate & 0x4000 ) < < 14 ;
frqcr | = ( newstate & 0x000c ) < < 2 ;
# elif defined(CONFIG_CPU_SH4)
/*
* FRQCR . PLL2EN is 1 , we need to allow the PLL to stabilize by
* initializing the WDT .
*/
if ( frqcr & ( 1 < < 9 ) ) {
__u8 csr ;
/*
* Set the overflow period to the highest available ,
* in this case a 1 / 4096 division ratio yields a 5.25 ms
* overflow period . See asm - sh / watchdog . h for more
* information and a range of other divisors .
*/
csr = sh_wdt_read_csr ( ) ;
csr | = WTCSR_CKS_4096 ;
sh_wdt_write_csr ( csr ) ;
sh_wdt_write_cnt ( 0 ) ;
}
frqcr & = 0x0e00 ; /* Clear ifc, bfc, pfc */
frqcr | = get_ifc_value ( clock_sets [ set ] . ifc ) < < 6 ;
frqcr | = get_bfc_value ( clock_sets [ set ] . bfc ) < < 3 ;
frqcr | = get_pfc_value ( clock_sets [ set ] . pfc ) ;
# endif
ctrl_outw ( frqcr , FRQCR ) ;
sh_cpufreq_update_clocks ( set ) ;
set_cpus_allowed ( current , cpus_allowed ) ;
cpufreq_notify_transition ( & freqs , CPUFREQ_POSTCHANGE ) ;
return 0 ;
}
static int sh_cpufreq_cpu_init ( struct cpufreq_policy * policy )
{
unsigned int min_freq , max_freq ;
unsigned int ifc , bfc , pfc ;
if ( ! cpu_online ( policy - > cpu ) )
return - ENODEV ;
/* Update our maximum clock set */
get_current_frequency_divisors ( & ifc , & bfc , & pfc ) ;
clock_sets [ MAX_CLOCK_SET ] . ifc = ifc ;
clock_sets [ MAX_CLOCK_SET ] . bfc = bfc ;
clock_sets [ MAX_CLOCK_SET ] . pfc = pfc ;
/* Convert from Hz to kHz */
max_freq = current_cpu_data . cpu_clock / 1000 ;
min_freq = ( current_cpu_data . master_clock / clock_sets [ MIN_CLOCK_SET ] . ifc ) / 1000 ;
sh_freqs [ SH_FREQ_MAX ] . frequency = max_freq ;
sh_freqs [ SH_FREQ_MIN ] . frequency = min_freq ;
/* cpuinfo and default policy values */
policy - > governor = CPUFREQ_DEFAULT_GOVERNOR ;
policy - > cpuinfo . transition_latency = CPUFREQ_ETERNAL ;
policy - > cur = max_freq ;
return cpufreq_frequency_table_cpuinfo ( policy , & sh_freqs [ 0 ] ) ;
}
static int sh_cpufreq_verify ( struct cpufreq_policy * policy )
{
return cpufreq_frequency_table_verify ( policy , & sh_freqs [ 0 ] ) ;
}
static int sh_cpufreq_target ( struct cpufreq_policy * policy ,
unsigned int target_freq ,
unsigned int relation )
{
unsigned int set , idx = 0 ;
if ( cpufreq_frequency_table_target ( policy , & sh_freqs [ 0 ] , target_freq , relation , & idx ) )
return - EINVAL ;
set = ( idx = = SH_FREQ_MIN ) ? MIN_CLOCK_SET : MAX_CLOCK_SET ;
sh_cpufreq_setstate ( policy - > cpu , set ) ;
return 0 ;
}
static struct cpufreq_driver sh_cpufreq_driver = {
. owner = THIS_MODULE ,
. name = " SH cpufreq " ,
. init = sh_cpufreq_cpu_init ,
. verify = sh_cpufreq_verify ,
. target = sh_cpufreq_target ,
} ;
static int __init sh_cpufreq_init ( void )
{
if ( ! current_cpu_data . cpu_clock )
return - EINVAL ;
if ( cpufreq_register_driver ( & sh_cpufreq_driver ) )
return - EINVAL ;
return 0 ;
}
static void __exit sh_cpufreq_exit ( void )
{
cpufreq_unregister_driver ( & sh_cpufreq_driver ) ;
}
module_init ( sh_cpufreq_init ) ;
module_exit ( sh_cpufreq_exit ) ;
MODULE_AUTHOR ( " Paul Mundt <lethal@linux-sh.org> " ) ;
MODULE_DESCRIPTION ( " cpufreq driver for SuperH " ) ;
MODULE_LICENSE ( " GPL " ) ;