2005-04-17 02:20:36 +04:00
/*
* Pentium 4 / Xeon CPU on demand clock modulation / speed scaling
* ( C ) 2002 - 2003 Dominik Brodowski < linux @ brodo . de >
* ( C ) 2002 Zwane Mwaikambo < zwane @ commfireservices . com >
* ( C ) 2002 Arjan van de Ven < arjanv @ redhat . com >
* ( C ) 2002 Tora T . Engstad
* All Rights Reserved
*
* 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 .
*
* The author ( s ) of this software shall not be held liable for damages
* of any nature resulting due to the use of this software . This
* software is provided AS - IS with no warranties .
2006-02-28 08:43:23 +03:00
*
2005-04-17 02:20:36 +04:00
* Date Errata Description
* 20020525 N44 , O17 12.5 % or 25 % DC causes lockup
*
*/
# include <linux/kernel.h>
2006-02-28 08:43:23 +03:00
# include <linux/module.h>
2005-04-17 02:20:36 +04:00
# include <linux/init.h>
# include <linux/smp.h>
# include <linux/cpufreq.h>
# include <linux/slab.h>
# include <linux/cpumask.h>
2009-01-18 07:55:22 +03:00
# include <linux/timex.h>
2005-04-17 02:20:36 +04:00
2006-02-28 08:43:23 +03:00
# include <asm/processor.h>
2005-04-17 02:20:36 +04:00
# include <asm/msr.h>
# include "speedstep-lib.h"
# define PFX "p4-clockmod: "
2009-01-18 07:55:22 +03:00
# define dprintk(msg...) cpufreq_debug_printk(CPUFREQ_DEBUG_DRIVER, \
" p4-clockmod " , msg )
2005-04-17 02:20:36 +04:00
/*
* Duty Cycle ( 3 bits ) , note DC_DISABLE is not specified in
* intel docs i just use it to mean disable
*/
enum {
DC_RESV , DC_DFLT , DC_25PT , DC_38PT , DC_50PT ,
DC_64PT , DC_75PT , DC_88PT , DC_DISABLE
} ;
# define DC_ENTRIES 8
static int has_N44_O17_errata [ NR_CPUS ] ;
static unsigned int stock_freq ;
static struct cpufreq_driver p4clockmod_driver ;
static unsigned int cpufreq_p4_get ( unsigned int cpu ) ;
static int cpufreq_p4_setdc ( unsigned int cpu , unsigned int newstate )
{
u32 l , h ;
2009-01-18 07:55:22 +03:00
if ( ! cpu_online ( cpu ) | |
( newstate > DC_DISABLE ) | | ( newstate = = DC_RESV ) )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2007-03-19 19:17:00 +03:00
rdmsr_on_cpu ( cpu , MSR_IA32_THERM_STATUS , & l , & h ) ;
2005-04-17 02:20:36 +04:00
if ( l & 0x01 )
dprintk ( " CPU#%d currently thermal throttled \n " , cpu ) ;
2009-01-18 07:55:22 +03:00
if ( has_N44_O17_errata [ cpu ] & &
( newstate = = DC_25PT | | newstate = = DC_DFLT ) )
2005-04-17 02:20:36 +04:00
newstate = DC_38PT ;
2007-03-19 19:17:00 +03:00
rdmsr_on_cpu ( cpu , MSR_IA32_THERM_CONTROL , & l , & h ) ;
2005-04-17 02:20:36 +04:00
if ( newstate = = DC_DISABLE ) {
dprintk ( " CPU#%d disabling modulation \n " , cpu ) ;
2007-03-19 19:17:00 +03:00
wrmsr_on_cpu ( cpu , MSR_IA32_THERM_CONTROL , l & ~ ( 1 < < 4 ) , h ) ;
2005-04-17 02:20:36 +04:00
} else {
dprintk ( " CPU#%d setting duty cycle to %d%% \n " ,
cpu , ( ( 125 * newstate ) / 10 ) ) ;
2006-02-28 08:43:23 +03:00
/* bits 63 - 5 : reserved
2005-04-17 02:20:36 +04:00
* bit 4 : enable / disable
* bits 3 - 1 : duty cycle
* bit 0 : reserved
*/
l = ( l & ~ 14 ) ;
l = l | ( 1 < < 4 ) | ( ( newstate & 0x7 ) < < 1 ) ;
2007-03-19 19:17:00 +03:00
wrmsr_on_cpu ( cpu , MSR_IA32_THERM_CONTROL , l , h ) ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
}
static struct cpufreq_frequency_table p4clockmod_table [ ] = {
{ DC_RESV , CPUFREQ_ENTRY_INVALID } ,
{ DC_DFLT , 0 } ,
{ DC_25PT , 0 } ,
{ DC_38PT , 0 } ,
{ DC_50PT , 0 } ,
{ DC_64PT , 0 } ,
{ DC_75PT , 0 } ,
{ DC_88PT , 0 } ,
{ DC_DISABLE , 0 } ,
{ DC_RESV , CPUFREQ_TABLE_END } ,
} ;
static int cpufreq_p4_target ( struct cpufreq_policy * policy ,
unsigned int target_freq ,
unsigned int relation )
{
unsigned int newstate = DC_RESV ;
struct cpufreq_freqs freqs ;
int i ;
2009-01-18 07:55:22 +03:00
if ( cpufreq_frequency_table_target ( policy , & p4clockmod_table [ 0 ] ,
target_freq , relation , & newstate ) )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
freqs . old = cpufreq_p4_get ( policy - > cpu ) ;
freqs . new = stock_freq * p4clockmod_table [ newstate ] . index / 8 ;
if ( freqs . new = = freqs . old )
return 0 ;
/* notifiers */
2009-01-04 16:18:06 +03:00
for_each_cpu ( i , policy - > cpus ) {
2005-04-17 02:20:36 +04:00
freqs . cpu = i ;
cpufreq_notify_transition ( & freqs , CPUFREQ_PRECHANGE ) ;
}
2009-01-18 07:55:22 +03:00
/* run on each logical CPU,
* see section 13.15 .3 of IA32 Intel Architecture Software
2006-02-28 08:43:23 +03:00
* Developer ' s Manual , Volume 3
2005-04-17 02:20:36 +04:00
*/
2009-01-04 16:18:06 +03:00
for_each_cpu ( i , policy - > cpus )
2005-04-17 02:20:36 +04:00
cpufreq_p4_setdc ( i , p4clockmod_table [ newstate ] . index ) ;
/* notifiers */
2009-01-04 16:18:06 +03:00
for_each_cpu ( i , policy - > cpus ) {
2005-04-17 02:20:36 +04:00
freqs . cpu = i ;
cpufreq_notify_transition ( & freqs , CPUFREQ_POSTCHANGE ) ;
}
return 0 ;
}
static int cpufreq_p4_verify ( struct cpufreq_policy * policy )
{
return cpufreq_frequency_table_verify ( policy , & p4clockmod_table [ 0 ] ) ;
}
static unsigned int cpufreq_p4_get_frequency ( struct cpuinfo_x86 * c )
{
2006-10-31 20:44:08 +03:00
if ( c - > x86 = = 0x06 ) {
if ( cpu_has ( c , X86_FEATURE_EST ) )
2009-01-18 07:55:22 +03:00
printk ( KERN_WARNING PFX " Warning: EST-capable CPU "
" detected. The acpi-cpufreq module offers "
" voltage scaling in addition of frequency "
" scaling. You should use that instead of "
" p4-clockmod, if possible. \n " ) ;
2006-10-31 20:44:08 +03:00
switch ( c - > x86_model ) {
case 0x0E : /* Core */
case 0x0F : /* Core Duo */
2008-11-15 22:02:46 +03:00
case 0x16 : /* Celeron Core */
2006-10-31 20:44:08 +03:00
p4clockmod_driver . flags | = CPUFREQ_CONST_LOOPS ;
2009-01-18 07:55:22 +03:00
return speedstep_get_frequency ( SPEEDSTEP_CPU_PCORE ) ;
2006-10-31 20:44:08 +03:00
case 0x0D : /* Pentium M (Dothan) */
p4clockmod_driver . flags | = CPUFREQ_CONST_LOOPS ;
/* fall through */
case 0x09 : /* Pentium M (Banias) */
2009-01-18 07:55:22 +03:00
return speedstep_get_frequency ( SPEEDSTEP_CPU_PM ) ;
2006-10-31 20:44:08 +03:00
}
2005-04-17 02:20:36 +04:00
}
if ( c - > x86 ! = 0xF ) {
2008-10-18 00:52:04 +04:00
if ( ! cpu_has ( c , X86_FEATURE_EST ) )
2009-01-18 07:55:22 +03:00
printk ( KERN_WARNING PFX " Unknown CPU. "
" Please send an e-mail to "
" <cpufreq@vger.kernel.org> \n " ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
/* on P-4s, the TSC runs with constant frequency independent whether
* throttling is active or not . */
p4clockmod_driver . flags | = CPUFREQ_CONST_LOOPS ;
2009-01-18 07:55:22 +03:00
if ( speedstep_detect_processor ( ) = = SPEEDSTEP_CPU_P4M ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_WARNING PFX " Warning: Pentium 4-M detected. "
" The speedstep-ich or acpi cpufreq modules offer "
" voltage scaling in addition of frequency scaling. "
" You should use either one instead of p4-clockmod, "
" if possible. \n " ) ;
2009-01-18 07:55:22 +03:00
return speedstep_get_frequency ( SPEEDSTEP_CPU_P4M ) ;
2005-04-17 02:20:36 +04:00
}
2009-01-18 07:55:22 +03:00
return speedstep_get_frequency ( SPEEDSTEP_CPU_P4D ) ;
2005-04-17 02:20:36 +04:00
}
2006-02-28 08:43:23 +03:00
2005-04-17 02:20:36 +04:00
static int cpufreq_p4_cpu_init ( struct cpufreq_policy * policy )
{
2007-10-19 22:35:04 +04:00
struct cpuinfo_x86 * c = & cpu_data ( policy - > cpu ) ;
2005-04-17 02:20:36 +04:00
int cpuid = 0 ;
unsigned int i ;
# ifdef CONFIG_SMP
2009-01-04 16:18:06 +03:00
cpumask_copy ( policy - > cpus , & per_cpu ( cpu_sibling_map , policy - > cpu ) ) ;
2005-04-17 02:20:36 +04:00
# endif
/* Errata workaround */
cpuid = ( c - > x86 < < 8 ) | ( c - > x86_model < < 4 ) | c - > x86_mask ;
switch ( cpuid ) {
case 0x0f07 :
case 0x0f0a :
case 0x0f11 :
case 0x0f12 :
has_N44_O17_errata [ policy - > cpu ] = 1 ;
dprintk ( " has errata -- disabling low frequencies \n " ) ;
}
2006-02-28 08:43:23 +03:00
2005-04-17 02:20:36 +04:00
/* get max frequency */
stock_freq = cpufreq_p4_get_frequency ( c ) ;
if ( ! stock_freq )
return - EINVAL ;
/* table init */
2009-01-18 07:55:22 +03:00
for ( i = 1 ; ( p4clockmod_table [ i ] . frequency ! = CPUFREQ_TABLE_END ) ; i + + ) {
if ( ( i < 2 ) & & ( has_N44_O17_errata [ policy - > cpu ] ) )
2005-04-17 02:20:36 +04:00
p4clockmod_table [ i ] . frequency = CPUFREQ_ENTRY_INVALID ;
else
p4clockmod_table [ i ] . frequency = ( stock_freq * i ) / 8 ;
}
cpufreq_frequency_table_get_attr ( p4clockmod_table , policy - > cpu ) ;
2006-02-28 08:43:23 +03:00
2005-04-17 02:20:36 +04:00
/* cpuinfo and default policy values */
policy - > cpuinfo . transition_latency = 1000000 ; /* assumed */
policy - > cur = stock_freq ;
return cpufreq_frequency_table_cpuinfo ( policy , & p4clockmod_table [ 0 ] ) ;
}
static int cpufreq_p4_cpu_exit ( struct cpufreq_policy * policy )
{
2006-02-28 08:43:23 +03:00
cpufreq_frequency_table_put_attr ( policy - > cpu ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static unsigned int cpufreq_p4_get ( unsigned int cpu )
{
u32 l , h ;
2007-03-19 19:17:00 +03:00
rdmsr_on_cpu ( cpu , MSR_IA32_THERM_CONTROL , & l , & h ) ;
2005-04-17 02:20:36 +04:00
if ( l & 0x10 ) {
l = l > > 1 ;
l & = 0x7 ;
} else
l = DC_DISABLE ;
if ( l ! = DC_DISABLE )
2009-01-18 07:55:22 +03:00
return stock_freq * l / 8 ;
2005-04-17 02:20:36 +04:00
return stock_freq ;
}
2009-01-18 07:55:22 +03:00
static struct freq_attr * p4clockmod_attr [ ] = {
2005-04-17 02:20:36 +04:00
& cpufreq_freq_attr_scaling_available_freqs ,
NULL ,
} ;
static struct cpufreq_driver p4clockmod_driver = {
2006-02-28 08:43:23 +03:00
. verify = cpufreq_p4_verify ,
2005-04-17 02:20:36 +04:00
. target = cpufreq_p4_target ,
. init = cpufreq_p4_cpu_init ,
. exit = cpufreq_p4_cpu_exit ,
. get = cpufreq_p4_get ,
. name = " p4-clockmod " ,
. owner = THIS_MODULE ,
. attr = p4clockmod_attr ,
2008-11-25 21:29:47 +03:00
. hide_interface = 1 ,
2005-04-17 02:20:36 +04:00
} ;
static int __init cpufreq_p4_init ( void )
2006-02-28 08:43:23 +03:00
{
2007-10-19 22:35:04 +04:00
struct cpuinfo_x86 * c = & cpu_data ( 0 ) ;
2005-04-17 02:20:36 +04:00
int ret ;
/*
2006-02-28 08:43:23 +03:00
* THERM_CONTROL is architectural for IA32 now , so
2005-04-17 02:20:36 +04:00
* we can rely on the capability checks
*/
if ( c - > x86_vendor ! = X86_VENDOR_INTEL )
return - ENODEV ;
2008-02-26 10:52:16 +03:00
if ( ! test_cpu_cap ( c , X86_FEATURE_ACPI ) | |
! test_cpu_cap ( c , X86_FEATURE_ACC ) )
2005-04-17 02:20:36 +04:00
return - ENODEV ;
ret = cpufreq_register_driver ( & p4clockmod_driver ) ;
if ( ! ret )
2009-01-18 07:55:22 +03:00
printk ( KERN_INFO PFX " P4/Xeon(TM) CPU On-Demand Clock "
" Modulation available \n " ) ;
2005-04-17 02:20:36 +04:00
2009-01-18 07:55:22 +03:00
return ret ;
2005-04-17 02:20:36 +04:00
}
static void __exit cpufreq_p4_exit ( void )
{
cpufreq_unregister_driver ( & p4clockmod_driver ) ;
}
2009-01-18 07:55:22 +03:00
MODULE_AUTHOR ( " Zwane Mwaikambo <zwane@commfireservices.com> " ) ;
MODULE_DESCRIPTION ( " cpufreq driver for Pentium(TM) 4/Xeon(TM) " ) ;
MODULE_LICENSE ( " GPL " ) ;
2005-04-17 02:20:36 +04:00
late_initcall ( cpufreq_p4_init ) ;
module_exit ( cpufreq_p4_exit ) ;