2007-02-05 21:57:25 +03:00
/*
* Based on documentation provided by Dave Jones . Thanks !
*
* Licensed under the terms of the GNU GPL License version 2.
*
* BIG FAT DISCLAIMER : Work in progress code . Possibly * dangerous *
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/cpufreq.h>
# include <linux/ioport.h>
# include <linux/slab.h>
# include <asm/msr.h>
# include <asm/tsc.h>
# include <asm/timex.h>
# include <asm/io.h>
# include <asm/delay.h>
# define EPS_BRAND_C7M 0
# define EPS_BRAND_C7 1
# define EPS_BRAND_EDEN 2
# define EPS_BRAND_C3 3
2008-01-18 00:25:08 +03:00
# define EPS_BRAND_C7D 4
2007-02-05 21:57:25 +03:00
struct eps_cpu_data {
u32 fsb ;
struct cpufreq_frequency_table freq_table [ ] ;
} ;
static struct eps_cpu_data * eps_cpu [ NR_CPUS ] ;
static unsigned int eps_get ( unsigned int cpu )
{
struct eps_cpu_data * centaur ;
u32 lo , hi ;
if ( cpu )
return 0 ;
centaur = eps_cpu [ cpu ] ;
if ( centaur = = NULL )
return 0 ;
/* Return current frequency */
rdmsr ( MSR_IA32_PERF_STATUS , lo , hi ) ;
return centaur - > fsb * ( ( lo > > 8 ) & 0xff ) ;
}
static int eps_set_state ( struct eps_cpu_data * centaur ,
unsigned int cpu ,
u32 dest_state )
{
struct cpufreq_freqs freqs ;
u32 lo , hi ;
2008-01-18 00:25:08 +03:00
u8 current_multiplier , current_voltage ;
2007-02-05 21:57:25 +03:00
int err = 0 ;
int i ;
freqs . old = eps_get ( cpu ) ;
freqs . new = centaur - > fsb * ( ( dest_state > > 8 ) & 0xff ) ;
freqs . cpu = cpu ;
cpufreq_notify_transition ( & freqs , CPUFREQ_PRECHANGE ) ;
/* Wait while CPU is busy */
rdmsr ( MSR_IA32_PERF_STATUS , lo , hi ) ;
i = 0 ;
while ( lo & ( ( 1 < < 16 ) | ( 1 < < 17 ) ) ) {
udelay ( 16 ) ;
rdmsr ( MSR_IA32_PERF_STATUS , lo , hi ) ;
i + + ;
if ( unlikely ( i > 64 ) ) {
err = - ENODEV ;
goto postchange ;
}
}
/* Set new multiplier and voltage */
wrmsr ( MSR_IA32_PERF_CTL , dest_state & 0xffff , 0 ) ;
/* Wait until transition end */
i = 0 ;
do {
udelay ( 16 ) ;
rdmsr ( MSR_IA32_PERF_STATUS , lo , hi ) ;
i + + ;
if ( unlikely ( i > 64 ) ) {
err = - ENODEV ;
goto postchange ;
}
} while ( lo & ( ( 1 < < 16 ) | ( 1 < < 17 ) ) ) ;
/* Return current frequency */
postchange :
rdmsr ( MSR_IA32_PERF_STATUS , lo , hi ) ;
freqs . new = centaur - > fsb * ( ( lo > > 8 ) & 0xff ) ;
2008-01-18 00:25:08 +03:00
/* Print voltage and multiplier */
rdmsr ( MSR_IA32_PERF_STATUS , lo , hi ) ;
current_voltage = lo & 0xff ;
printk ( KERN_INFO " eps: Current voltage = %dmV \n " ,
current_voltage * 16 + 700 ) ;
current_multiplier = ( lo > > 8 ) & 0xff ;
printk ( KERN_INFO " eps: Current multiplier = %d \n " ,
current_multiplier ) ;
2007-02-05 21:57:25 +03:00
cpufreq_notify_transition ( & freqs , CPUFREQ_POSTCHANGE ) ;
return err ;
}
static int eps_target ( struct cpufreq_policy * policy ,
unsigned int target_freq ,
unsigned int relation )
{
struct eps_cpu_data * centaur ;
unsigned int newstate = 0 ;
unsigned int cpu = policy - > cpu ;
unsigned int dest_state ;
int ret ;
if ( unlikely ( eps_cpu [ cpu ] = = NULL ) )
return - ENODEV ;
centaur = eps_cpu [ cpu ] ;
if ( unlikely ( cpufreq_frequency_table_target ( policy ,
& eps_cpu [ cpu ] - > freq_table [ 0 ] ,
target_freq ,
relation ,
& newstate ) ) ) {
return - EINVAL ;
}
/* Make frequency transition */
dest_state = centaur - > freq_table [ newstate ] . index & 0xffff ;
ret = eps_set_state ( centaur , cpu , dest_state ) ;
if ( ret )
printk ( KERN_ERR " eps: Timeout! \n " ) ;
return ret ;
}
static int eps_verify ( struct cpufreq_policy * policy )
{
return cpufreq_frequency_table_verify ( policy ,
& eps_cpu [ policy - > cpu ] - > freq_table [ 0 ] ) ;
}
static int eps_cpu_init ( struct cpufreq_policy * policy )
{
unsigned int i ;
u32 lo , hi ;
u64 val ;
u8 current_multiplier , current_voltage ;
u8 max_multiplier , max_voltage ;
u8 min_multiplier , min_voltage ;
2008-01-18 00:25:08 +03:00
u8 brand = 0 ;
2007-02-05 21:57:25 +03:00
u32 fsb ;
struct eps_cpu_data * centaur ;
2008-01-18 00:25:08 +03:00
struct cpuinfo_x86 * c = & cpu_data ( 0 ) ;
2007-02-05 21:57:25 +03:00
struct cpufreq_frequency_table * f_table ;
int k , step , voltage ;
int ret ;
int states ;
if ( policy - > cpu ! = 0 )
return - ENODEV ;
/* Check brand */
2008-02-07 06:00:31 +03:00
printk ( KERN_INFO " eps: Detected VIA " ) ;
2008-01-18 00:25:08 +03:00
switch ( c - > x86_model ) {
case 10 :
rdmsr ( 0x1153 , lo , hi ) ;
brand = ( ( ( lo > > 2 ) ^ lo ) > > 18 ) & 3 ;
printk ( KERN_CONT " Model A " ) ;
break ;
case 13 :
rdmsr ( 0x1154 , lo , hi ) ;
brand = ( ( ( lo > > 4 ) ^ ( lo > > 2 ) ) ) & 0x000000ff ;
printk ( KERN_CONT " Model D " ) ;
break ;
}
2007-02-05 21:57:25 +03:00
switch ( brand ) {
case EPS_BRAND_C7M :
2008-02-07 06:00:31 +03:00
printk ( KERN_CONT " C7-M \n " ) ;
2007-02-05 21:57:25 +03:00
break ;
case EPS_BRAND_C7 :
2008-02-07 06:00:31 +03:00
printk ( KERN_CONT " C7 \n " ) ;
2007-02-05 21:57:25 +03:00
break ;
case EPS_BRAND_EDEN :
2008-02-07 06:00:31 +03:00
printk ( KERN_CONT " Eden \n " ) ;
2007-02-05 21:57:25 +03:00
break ;
2008-01-18 00:25:08 +03:00
case EPS_BRAND_C7D :
printk ( KERN_CONT " C7-D \n " ) ;
break ;
2007-02-05 21:57:25 +03:00
case EPS_BRAND_C3 :
2008-02-07 06:00:31 +03:00
printk ( KERN_CONT " C3 \n " ) ;
2007-02-05 21:57:25 +03:00
return - ENODEV ;
break ;
}
/* Enable Enhanced PowerSaver */
rdmsrl ( MSR_IA32_MISC_ENABLE , val ) ;
if ( ! ( val & 1 < < 16 ) ) {
val | = 1 < < 16 ;
wrmsrl ( MSR_IA32_MISC_ENABLE , val ) ;
/* Can be locked at 0 */
rdmsrl ( MSR_IA32_MISC_ENABLE , val ) ;
if ( ! ( val & 1 < < 16 ) ) {
2008-02-07 06:00:31 +03:00
printk ( KERN_INFO " eps: Can't enable Enhanced PowerSaver \n " ) ;
2007-02-05 21:57:25 +03:00
return - ENODEV ;
}
}
/* Print voltage and multiplier */
rdmsr ( MSR_IA32_PERF_STATUS , lo , hi ) ;
current_voltage = lo & 0xff ;
2008-02-07 06:00:31 +03:00
printk ( KERN_INFO " eps: Current voltage = %dmV \n " , current_voltage * 16 + 700 ) ;
2007-02-05 21:57:25 +03:00
current_multiplier = ( lo > > 8 ) & 0xff ;
2008-02-07 06:00:31 +03:00
printk ( KERN_INFO " eps: Current multiplier = %d \n " , current_multiplier ) ;
2007-02-05 21:57:25 +03:00
/* Print limits */
max_voltage = hi & 0xff ;
2008-02-07 06:00:31 +03:00
printk ( KERN_INFO " eps: Highest voltage = %dmV \n " , max_voltage * 16 + 700 ) ;
2007-02-05 21:57:25 +03:00
max_multiplier = ( hi > > 8 ) & 0xff ;
2008-02-07 06:00:31 +03:00
printk ( KERN_INFO " eps: Highest multiplier = %d \n " , max_multiplier ) ;
2007-02-05 21:57:25 +03:00
min_voltage = ( hi > > 16 ) & 0xff ;
2008-02-07 06:00:31 +03:00
printk ( KERN_INFO " eps: Lowest voltage = %dmV \n " , min_voltage * 16 + 700 ) ;
2007-02-05 21:57:25 +03:00
min_multiplier = ( hi > > 24 ) & 0xff ;
2008-02-07 06:00:31 +03:00
printk ( KERN_INFO " eps: Lowest multiplier = %d \n " , min_multiplier ) ;
2007-02-05 21:57:25 +03:00
/* Sanity checks */
if ( current_multiplier = = 0 | | max_multiplier = = 0
| | min_multiplier = = 0 )
return - EINVAL ;
if ( current_multiplier > max_multiplier
| | max_multiplier < = min_multiplier )
return - EINVAL ;
2008-01-18 00:25:08 +03:00
if ( current_voltage > 0x1f | | max_voltage > 0x1f )
2007-02-05 21:57:25 +03:00
return - EINVAL ;
if ( max_voltage < min_voltage )
return - EINVAL ;
/* Calc FSB speed */
fsb = cpu_khz / current_multiplier ;
/* Calc number of p-states supported */
if ( brand = = EPS_BRAND_C7M )
states = max_multiplier - min_multiplier + 1 ;
else
states = 2 ;
/* Allocate private data and frequency table for current cpu */
centaur = kzalloc ( sizeof ( struct eps_cpu_data )
+ ( states + 1 ) * sizeof ( struct cpufreq_frequency_table ) ,
GFP_KERNEL ) ;
if ( ! centaur )
return - ENOMEM ;
eps_cpu [ 0 ] = centaur ;
/* Copy basic values */
centaur - > fsb = fsb ;
/* Fill frequency and MSR value table */
f_table = & centaur - > freq_table [ 0 ] ;
2007-02-13 00:19:12 +03:00
if ( brand ! = EPS_BRAND_C7M ) {
2007-02-05 21:57:25 +03:00
f_table [ 0 ] . frequency = fsb * min_multiplier ;
f_table [ 0 ] . index = ( min_multiplier < < 8 ) | min_voltage ;
f_table [ 1 ] . frequency = fsb * max_multiplier ;
f_table [ 1 ] . index = ( max_multiplier < < 8 ) | max_voltage ;
f_table [ 2 ] . frequency = CPUFREQ_TABLE_END ;
} else {
k = 0 ;
step = ( ( max_voltage - min_voltage ) * 256 )
/ ( max_multiplier - min_multiplier ) ;
for ( i = min_multiplier ; i < = max_multiplier ; i + + ) {
voltage = ( k * step ) / 256 + min_voltage ;
f_table [ k ] . frequency = fsb * i ;
f_table [ k ] . index = ( i < < 8 ) | voltage ;
k + + ;
}
f_table [ k ] . frequency = CPUFREQ_TABLE_END ;
}
policy - > cpuinfo . transition_latency = 140000 ; /* 844mV -> 700mV in ns */
policy - > cur = fsb * current_multiplier ;
ret = cpufreq_frequency_table_cpuinfo ( policy , & centaur - > freq_table [ 0 ] ) ;
if ( ret ) {
kfree ( centaur ) ;
return ret ;
}
cpufreq_frequency_table_get_attr ( & centaur - > freq_table [ 0 ] , policy - > cpu ) ;
return 0 ;
}
static int eps_cpu_exit ( struct cpufreq_policy * policy )
{
unsigned int cpu = policy - > cpu ;
struct eps_cpu_data * centaur ;
u32 lo , hi ;
if ( eps_cpu [ cpu ] = = NULL )
return - ENODEV ;
centaur = eps_cpu [ cpu ] ;
/* Get max frequency */
rdmsr ( MSR_IA32_PERF_STATUS , lo , hi ) ;
/* Set max frequency */
eps_set_state ( centaur , cpu , hi & 0xffff ) ;
/* Bye */
cpufreq_frequency_table_put_attr ( policy - > cpu ) ;
kfree ( eps_cpu [ cpu ] ) ;
eps_cpu [ cpu ] = NULL ;
return 0 ;
}
static struct freq_attr * eps_attr [ ] = {
& cpufreq_freq_attr_scaling_available_freqs ,
NULL ,
} ;
2007-02-27 01:55:48 +03:00
static struct cpufreq_driver eps_driver = {
2007-02-05 21:57:25 +03:00
. verify = eps_verify ,
. target = eps_target ,
. init = eps_cpu_init ,
. exit = eps_cpu_exit ,
. get = eps_get ,
. name = " e_powersaver " ,
. owner = THIS_MODULE ,
. attr = eps_attr ,
} ;
static int __init eps_init ( void )
{
2007-10-19 22:35:04 +04:00
struct cpuinfo_x86 * c = & cpu_data ( 0 ) ;
2007-02-05 21:57:25 +03:00
/* This driver will work only on Centaur C7 processors with
* Enhanced SpeedStep / PowerSaver registers */
if ( c - > x86_vendor ! = X86_VENDOR_CENTAUR
2008-01-18 00:25:08 +03:00
| | c - > x86 ! = 6 | | c - > x86_model < 10 )
2007-02-05 21:57:25 +03:00
return - ENODEV ;
if ( ! cpu_has ( c , X86_FEATURE_EST ) )
return - ENODEV ;
if ( cpufreq_register_driver ( & eps_driver ) )
return - EINVAL ;
return 0 ;
}
static void __exit eps_exit ( void )
{
cpufreq_unregister_driver ( & eps_driver ) ;
}
MODULE_AUTHOR ( " Rafa<EFBFBD> Bilski <rafalbilski@interia.pl> " ) ;
MODULE_DESCRIPTION ( " Enhanced PowerSaver driver for VIA C7 CPU's. " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( eps_init ) ;
module_exit ( eps_exit ) ;