2019-05-27 14:55:01 +08:00
// SPDX-License-Identifier: GPL-2.0-or-later
2005-04-17 06:20:36 +08: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
*
* 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 13:43:23 +08:00
*
2005-04-17 06:20:36 +08:00
* Date Errata Description
* 20020525 N44 , O17 12.5 % or 25 % DC causes lockup
*/
2016-04-06 04:28:25 +08:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2005-04-17 06:20:36 +08:00
# include <linux/kernel.h>
2006-02-28 13:43:23 +08:00
# include <linux/module.h>
2005-04-17 06:20:36 +08:00
# include <linux/init.h>
# include <linux/smp.h>
# include <linux/cpufreq.h>
# include <linux/cpumask.h>
2009-01-18 12:55:22 +08:00
# include <linux/timex.h>
2005-04-17 06:20:36 +08:00
2006-02-28 13:43:23 +08:00
# include <asm/processor.h>
2005-04-17 06:20:36 +08:00
# include <asm/msr.h>
2009-02-21 09:52:17 +08:00
# include <asm/timer.h>
2012-01-26 07:09:12 +08:00
# include <asm/cpu_device_id.h>
2005-04-17 06:20:36 +08:00
# include "speedstep-lib.h"
/*
* 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 ;
2013-04-01 20:57:46 +08:00
if ( ( newstate > DC_DISABLE ) | | ( newstate = = DC_RESV ) )
2005-04-17 06:20:36 +08:00
return - EINVAL ;
2007-03-20 00:17:00 +08:00
rdmsr_on_cpu ( cpu , MSR_IA32_THERM_STATUS , & l , & h ) ;
2005-04-17 06:20:36 +08:00
if ( l & 0x01 )
2011-03-27 21:04:46 +08:00
pr_debug ( " CPU#%d currently thermal throttled \n " , cpu ) ;
2005-04-17 06:20:36 +08:00
2009-01-18 12:55:22 +08:00
if ( has_N44_O17_errata [ cpu ] & &
( newstate = = DC_25PT | | newstate = = DC_DFLT ) )
2005-04-17 06:20:36 +08:00
newstate = DC_38PT ;
2007-03-20 00:17:00 +08:00
rdmsr_on_cpu ( cpu , MSR_IA32_THERM_CONTROL , & l , & h ) ;
2005-04-17 06:20:36 +08:00
if ( newstate = = DC_DISABLE ) {
2011-03-27 21:04:46 +08:00
pr_debug ( " CPU#%d disabling modulation \n " , cpu ) ;
2007-03-20 00:17:00 +08:00
wrmsr_on_cpu ( cpu , MSR_IA32_THERM_CONTROL , l & ~ ( 1 < < 4 ) , h ) ;
2005-04-17 06:20:36 +08:00
} else {
2011-03-27 21:04:46 +08:00
pr_debug ( " CPU#%d setting duty cycle to %d%% \n " ,
2005-04-17 06:20:36 +08:00
cpu , ( ( 125 * newstate ) / 10 ) ) ;
2006-02-28 13:43:23 +08:00
/* bits 63 - 5 : reserved
2005-04-17 06:20:36 +08: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-20 00:17:00 +08:00
wrmsr_on_cpu ( cpu , MSR_IA32_THERM_CONTROL , l , h ) ;
2005-04-17 06:20:36 +08:00
}
return 0 ;
}
static struct cpufreq_frequency_table p4clockmod_table [ ] = {
2014-03-28 21:41:47 +08:00
{ 0 , DC_RESV , CPUFREQ_ENTRY_INVALID } ,
{ 0 , DC_DFLT , 0 } ,
{ 0 , DC_25PT , 0 } ,
{ 0 , DC_38PT , 0 } ,
{ 0 , DC_50PT , 0 } ,
{ 0 , DC_64PT , 0 } ,
{ 0 , DC_75PT , 0 } ,
{ 0 , DC_88PT , 0 } ,
{ 0 , DC_DISABLE , 0 } ,
{ 0 , DC_RESV , CPUFREQ_TABLE_END } ,
2005-04-17 06:20:36 +08:00
} ;
2013-10-25 22:15:48 +08:00
static int cpufreq_p4_target ( struct cpufreq_policy * policy , unsigned int index )
2005-04-17 06:20:36 +08:00
{
int i ;
2009-01-18 12:55:22 +08:00
/* run on each logical CPU,
* see section 13.15 .3 of IA32 Intel Architecture Software
2006-02-28 13:43:23 +08:00
* Developer ' s Manual , Volume 3
2005-04-17 06:20:36 +08:00
*/
2009-01-04 21:18:06 +08:00
for_each_cpu ( i , policy - > cpus )
2013-10-25 22:15:48 +08:00
cpufreq_p4_setdc ( i , p4clockmod_table [ index ] . driver_data ) ;
2005-04-17 06:20:36 +08:00
return 0 ;
}
static unsigned int cpufreq_p4_get_frequency ( struct cpuinfo_x86 * c )
{
2006-11-01 01:44:08 +08:00
if ( c - > x86 = = 0x06 ) {
if ( cpu_has ( c , X86_FEATURE_EST ) )
2016-04-06 04:28:25 +08:00
pr_warn_once ( " Warning: EST-capable CPU detected. The acpi-cpufreq module offers voltage scaling in addition to frequency scaling. You should use that instead of p4-clockmod, if possible. \n " ) ;
2006-11-01 01:44:08 +08:00
switch ( c - > x86_model ) {
case 0x0E : /* Core */
case 0x0F : /* Core Duo */
2008-11-16 03:02:46 +08:00
case 0x16 : /* Celeron Core */
2009-03-07 04:24:57 +08:00
case 0x1C : /* Atom */
2006-11-01 01:44:08 +08:00
p4clockmod_driver . flags | = CPUFREQ_CONST_LOOPS ;
2009-01-18 12:55:22 +08:00
return speedstep_get_frequency ( SPEEDSTEP_CPU_PCORE ) ;
2006-11-01 01:44:08 +08:00
case 0x0D : /* Pentium M (Dothan) */
p4clockmod_driver . flags | = CPUFREQ_CONST_LOOPS ;
/* fall through */
case 0x09 : /* Pentium M (Banias) */
2009-01-18 12:55:22 +08:00
return speedstep_get_frequency ( SPEEDSTEP_CPU_PM ) ;
2006-11-01 01:44:08 +08:00
}
2005-04-17 06:20:36 +08:00
}
2010-08-04 01:47:30 +08:00
if ( c - > x86 ! = 0xF )
2005-04-17 06:20:36 +08: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 12:55:22 +08:00
if ( speedstep_detect_processor ( ) = = SPEEDSTEP_CPU_P4M ) {
2016-04-06 04:28:25 +08:00
pr_warn ( " 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 12:55:22 +08:00
return speedstep_get_frequency ( SPEEDSTEP_CPU_P4M ) ;
2005-04-17 06:20:36 +08:00
}
2009-01-18 12:55:22 +08:00
return speedstep_get_frequency ( SPEEDSTEP_CPU_P4D ) ;
2005-04-17 06:20:36 +08:00
}
2006-02-28 13:43:23 +08:00
2005-04-17 06:20:36 +08:00
static int cpufreq_p4_cpu_init ( struct cpufreq_policy * policy )
{
2007-10-20 02:35:04 +08:00
struct cpuinfo_x86 * c = & cpu_data ( policy - > cpu ) ;
2005-04-17 06:20:36 +08:00
int cpuid = 0 ;
unsigned int i ;
# ifdef CONFIG_SMP
2015-05-26 21:11:32 +08:00
cpumask_copy ( policy - > cpus , topology_sibling_cpumask ( policy - > cpu ) ) ;
2005-04-17 06:20:36 +08:00
# endif
/* Errata workaround */
2018-01-01 09:52:10 +08:00
cpuid = ( c - > x86 < < 8 ) | ( c - > x86_model < < 4 ) | c - > x86_stepping ;
2005-04-17 06:20:36 +08:00
switch ( cpuid ) {
case 0x0f07 :
case 0x0f0a :
case 0x0f11 :
case 0x0f12 :
has_N44_O17_errata [ policy - > cpu ] = 1 ;
2011-03-27 21:04:46 +08:00
pr_debug ( " has errata -- disabling low frequencies \n " ) ;
2005-04-17 06:20:36 +08:00
}
2006-02-28 13:43:23 +08:00
2009-02-21 09:52:17 +08:00
if ( speedstep_detect_processor ( ) = = SPEEDSTEP_CPU_P4D & &
c - > x86_model < 2 ) {
/* switch to maximum frequency and measure result */
cpufreq_p4_setdc ( policy - > cpu , DC_DISABLE ) ;
recalibrate_cpu_khz ( ) ;
}
2005-04-17 06:20:36 +08:00
/* get max frequency */
stock_freq = cpufreq_p4_get_frequency ( c ) ;
if ( ! stock_freq )
return - EINVAL ;
/* table init */
2009-01-18 12:55:22 +08:00
for ( i = 1 ; ( p4clockmod_table [ i ] . frequency ! = CPUFREQ_TABLE_END ) ; i + + ) {
if ( ( i < 2 ) & & ( has_N44_O17_errata [ policy - > cpu ] ) )
2005-04-17 06:20:36 +08:00
p4clockmod_table [ i ] . frequency = CPUFREQ_ENTRY_INVALID ;
else
p4clockmod_table [ i ] . frequency = ( stock_freq * i ) / 8 ;
}
2006-02-28 13:43:23 +08:00
2005-04-17 06:20:36 +08:00
/* cpuinfo and default policy values */
2009-03-05 13:16:26 +08:00
/* the transition latency is set to be 1 higher than the maximum
* transition latency of the ondemand governor */
policy - > cpuinfo . transition_latency = 10000001 ;
2018-02-26 13:08:56 +08:00
policy - > freq_table = & p4clockmod_table [ 0 ] ;
2005-04-17 06:20:36 +08:00
2018-02-26 13:08:56 +08:00
return 0 ;
2005-04-17 06:20:36 +08:00
}
static unsigned int cpufreq_p4_get ( unsigned int cpu )
{
u32 l , h ;
2007-03-20 00:17:00 +08:00
rdmsr_on_cpu ( cpu , MSR_IA32_THERM_CONTROL , & l , & h ) ;
2005-04-17 06:20:36 +08:00
if ( l & 0x10 ) {
l = l > > 1 ;
l & = 0x7 ;
} else
l = DC_DISABLE ;
if ( l ! = DC_DISABLE )
2009-01-18 12:55:22 +08:00
return stock_freq * l / 8 ;
2005-04-17 06:20:36 +08:00
return stock_freq ;
}
static struct cpufreq_driver p4clockmod_driver = {
2013-10-03 22:58:14 +08:00
. verify = cpufreq_generic_frequency_table_verify ,
2013-10-25 22:15:48 +08:00
. target_index = cpufreq_p4_target ,
2005-04-17 06:20:36 +08:00
. init = cpufreq_p4_cpu_init ,
. get = cpufreq_p4_get ,
. name = " p4-clockmod " ,
2013-10-03 22:58:14 +08:00
. attr = cpufreq_generic_attr ,
2005-04-17 06:20:36 +08:00
} ;
2012-01-26 07:09:12 +08:00
static const struct x86_cpu_id cpufreq_p4_id [ ] = {
{ X86_VENDOR_INTEL , X86_FAMILY_ANY , X86_MODEL_ANY , X86_FEATURE_ACC } ,
{ }
} ;
/*
* Intentionally no MODULE_DEVICE_TABLE here : this driver should not
* be auto loaded . Please don ' t add one .
*/
2005-04-17 06:20:36 +08:00
static int __init cpufreq_p4_init ( void )
2006-02-28 13:43:23 +08:00
{
2005-04-17 06:20:36 +08:00
int ret ;
/*
2006-02-28 13:43:23 +08:00
* THERM_CONTROL is architectural for IA32 now , so
2005-04-17 06:20:36 +08:00
* we can rely on the capability checks
*/
2012-01-26 07:09:12 +08:00
if ( ! x86_match_cpu ( cpufreq_p4_id ) | | ! boot_cpu_has ( X86_FEATURE_ACPI ) )
2005-04-17 06:20:36 +08:00
return - ENODEV ;
ret = cpufreq_register_driver ( & p4clockmod_driver ) ;
if ( ! ret )
2016-04-06 04:28:25 +08:00
pr_info ( " P4/Xeon(TM) CPU On-Demand Clock Modulation available \n " ) ;
2005-04-17 06:20:36 +08:00
2009-01-18 12:55:22 +08:00
return ret ;
2005-04-17 06:20:36 +08:00
}
static void __exit cpufreq_p4_exit ( void )
{
cpufreq_unregister_driver ( & p4clockmod_driver ) ;
}
2009-01-18 12:55:22 +08:00
MODULE_AUTHOR ( " Zwane Mwaikambo <zwane@commfireservices.com> " ) ;
MODULE_DESCRIPTION ( " cpufreq driver for Pentium(TM) 4/Xeon(TM) " ) ;
MODULE_LICENSE ( " GPL " ) ;
2005-04-17 06:20:36 +08:00
late_initcall ( cpufreq_p4_init ) ;
module_exit ( cpufreq_p4_exit ) ;