2019-05-27 09:55:14 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2007-02-05 21:57:25 +03:00
/*
* Based on documentation provided by Dave Jones . Thanks !
*
* BIG FAT DISCLAIMER : Work in progress code . Possibly * dangerous *
*/
2016-04-05 23:28:25 +03:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2007-02-05 21:57:25 +03:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/cpufreq.h>
# include <linux/ioport.h>
# include <linux/slab.h>
2009-01-18 06:54:47 +03:00
# include <linux/timex.h>
# include <linux/io.h>
# include <linux/delay.h>
2007-02-05 21:57:25 +03:00
2012-01-26 03:09:12 +04:00
# include <asm/cpu_device_id.h>
2007-02-05 21:57:25 +03:00
# include <asm/msr.h>
# include <asm/tsc.h>
2016-04-27 03:14:13 +03:00
# if IS_ENABLED(CONFIG_ACPI_PROCESSOR)
2011-07-22 01:11:29 +04:00
# include <linux/acpi.h>
# include <acpi/processor.h>
# endif
2007-02-05 21:57:25 +03:00
# 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 ;
2016-04-27 03:14:13 +03:00
# if IS_ENABLED(CONFIG_ACPI_PROCESSOR)
2011-07-22 01:11:29 +04:00
u32 bios_limit ;
# endif
2007-02-05 21:57:25 +03:00
struct cpufreq_frequency_table freq_table [ ] ;
} ;
static struct eps_cpu_data * eps_cpu [ NR_CPUS ] ;
2011-07-21 00:20:56 +04:00
/* Module parameters */
static int freq_failsafe_off ;
static int voltage_failsafe_off ;
2011-07-24 02:35:28 +04:00
static int set_max_voltage ;
2011-07-21 00:20:56 +04:00
2016-04-27 03:14:13 +03:00
# if IS_ENABLED(CONFIG_ACPI_PROCESSOR)
2011-07-22 01:11:29 +04:00
static int ignore_acpi_limit ;
static struct acpi_processor_performance * eps_acpi_cpu_perf ;
/* Minimum necessary to get acpi_processor_get_bios_limit() working */
static int eps_acpi_init ( void )
{
2013-08-06 21:23:06 +04:00
eps_acpi_cpu_perf = kzalloc ( sizeof ( * eps_acpi_cpu_perf ) ,
2011-07-22 01:11:29 +04:00
GFP_KERNEL ) ;
if ( ! eps_acpi_cpu_perf )
return - ENOMEM ;
if ( ! zalloc_cpumask_var ( & eps_acpi_cpu_perf - > shared_cpu_map ,
GFP_KERNEL ) ) {
kfree ( eps_acpi_cpu_perf ) ;
eps_acpi_cpu_perf = NULL ;
return - ENOMEM ;
}
if ( acpi_processor_register_performance ( eps_acpi_cpu_perf , 0 ) ) {
free_cpumask_var ( eps_acpi_cpu_perf - > shared_cpu_map ) ;
kfree ( eps_acpi_cpu_perf ) ;
eps_acpi_cpu_perf = NULL ;
return - EIO ;
}
return 0 ;
}
static int eps_acpi_exit ( struct cpufreq_policy * policy )
{
if ( eps_acpi_cpu_perf ) {
2015-07-22 23:11:16 +03:00
acpi_processor_unregister_performance ( 0 ) ;
2011-07-22 01:11:29 +04:00
free_cpumask_var ( eps_acpi_cpu_perf - > shared_cpu_map ) ;
kfree ( eps_acpi_cpu_perf ) ;
eps_acpi_cpu_perf = NULL ;
}
return 0 ;
}
# endif
2007-02-05 21:57:25 +03:00
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 ,
2013-03-24 10:26:43 +04:00
struct cpufreq_policy * policy ,
2007-02-05 21:57:25 +03:00
u32 dest_state )
{
u32 lo , hi ;
int i ;
/* 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 ) ) {
2013-08-14 18:08:24 +04:00
return - ENODEV ;
2007-02-05 21:57:25 +03:00
}
}
/* 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 ) ) {
2013-08-14 18:08:24 +04:00
return - ENODEV ;
2007-02-05 21:57:25 +03:00
}
} while ( lo & ( ( 1 < < 16 ) | ( 1 < < 17 ) ) ) ;
2008-02-16 02:11:14 +03:00
# ifdef DEBUG
{
u8 current_multiplier , current_voltage ;
2008-01-18 00:25:08 +03:00
/* Print voltage and multiplier */
rdmsr ( MSR_IA32_PERF_STATUS , lo , hi ) ;
current_voltage = lo & 0xff ;
2016-04-05 23:28:25 +03:00
pr_info ( " Current voltage = %dmV \n " , current_voltage * 16 + 700 ) ;
2008-01-18 00:25:08 +03:00
current_multiplier = ( lo > > 8 ) & 0xff ;
2016-04-05 23:28:25 +03:00
pr_info ( " Current multiplier = %d \n " , current_multiplier ) ;
2008-02-16 02:11:14 +03:00
}
# endif
2013-08-14 18:08:24 +04:00
return 0 ;
2007-02-05 21:57:25 +03:00
}
2013-10-25 18:15:48 +04:00
static int eps_target ( struct cpufreq_policy * policy , unsigned int index )
2007-02-05 21:57:25 +03:00
{
struct eps_cpu_data * centaur ;
unsigned int cpu = policy - > cpu ;
unsigned int dest_state ;
int ret ;
if ( unlikely ( eps_cpu [ cpu ] = = NULL ) )
return - ENODEV ;
centaur = eps_cpu [ cpu ] ;
/* Make frequency transition */
2013-10-25 18:15:48 +04:00
dest_state = centaur - > freq_table [ index ] . driver_data & 0xffff ;
2013-03-24 10:26:43 +04:00
ret = eps_set_state ( centaur , policy , dest_state ) ;
2007-02-05 21:57:25 +03:00
if ( ret )
2016-04-05 23:28:25 +03:00
pr_err ( " Timeout! \n " ) ;
2007-02-05 21:57:25 +03:00
return ret ;
}
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 states ;
2016-04-27 03:14:13 +03:00
# if IS_ENABLED(CONFIG_ACPI_PROCESSOR)
2011-07-22 01:11:29 +04:00
unsigned int limit ;
# endif
2007-02-05 21:57:25 +03:00
if ( policy - > cpu ! = 0 )
return - ENODEV ;
/* Check brand */
2016-04-05 23:28:25 +03:00
pr_info ( " 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 ;
2016-04-05 23:28:24 +03:00
pr_cont ( " Model A " ) ;
2008-01-18 00:25:08 +03:00
break ;
case 13 :
rdmsr ( 0x1154 , lo , hi ) ;
brand = ( ( ( lo > > 4 ) ^ ( lo > > 2 ) ) ) & 0x000000ff ;
2016-04-05 23:28:24 +03:00
pr_cont ( " Model D " ) ;
2008-01-18 00:25:08 +03:00
break ;
}
2009-01-18 06:54:47 +03:00
switch ( brand ) {
2007-02-05 21:57:25 +03:00
case EPS_BRAND_C7M :
2016-04-05 23:28:24 +03:00
pr_cont ( " C7-M \n " ) ;
2007-02-05 21:57:25 +03:00
break ;
case EPS_BRAND_C7 :
2016-04-05 23:28:24 +03:00
pr_cont ( " C7 \n " ) ;
2007-02-05 21:57:25 +03:00
break ;
case EPS_BRAND_EDEN :
2016-04-05 23:28:24 +03:00
pr_cont ( " Eden \n " ) ;
2007-02-05 21:57:25 +03:00
break ;
2008-01-18 00:25:08 +03:00
case EPS_BRAND_C7D :
2016-04-05 23:28:24 +03:00
pr_cont ( " C7-D \n " ) ;
2008-01-18 00:25:08 +03:00
break ;
2007-02-05 21:57:25 +03:00
case EPS_BRAND_C3 :
2016-04-05 23:28:24 +03:00
pr_cont ( " C3 \n " ) ;
2007-02-05 21:57:25 +03:00
return - ENODEV ;
break ;
}
/* Enable Enhanced PowerSaver */
rdmsrl ( MSR_IA32_MISC_ENABLE , val ) ;
2009-02-20 13:56:38 +03:00
if ( ! ( val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP ) ) {
val | = MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP ;
2007-02-05 21:57:25 +03:00
wrmsrl ( MSR_IA32_MISC_ENABLE , val ) ;
/* Can be locked at 0 */
rdmsrl ( MSR_IA32_MISC_ENABLE , val ) ;
2009-02-20 13:56:38 +03:00
if ( ! ( val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP ) ) {
2016-04-05 23:28:25 +03:00
pr_info ( " 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 ;
2016-04-05 23:28:25 +03:00
pr_info ( " Current voltage = %dmV \n " , current_voltage * 16 + 700 ) ;
2007-02-05 21:57:25 +03:00
current_multiplier = ( lo > > 8 ) & 0xff ;
2016-04-05 23:28:25 +03:00
pr_info ( " Current multiplier = %d \n " , current_multiplier ) ;
2007-02-05 21:57:25 +03:00
/* Print limits */
max_voltage = hi & 0xff ;
2016-04-05 23:28:25 +03:00
pr_info ( " Highest voltage = %dmV \n " , max_voltage * 16 + 700 ) ;
2007-02-05 21:57:25 +03:00
max_multiplier = ( hi > > 8 ) & 0xff ;
2016-04-05 23:28:25 +03:00
pr_info ( " Highest multiplier = %d \n " , max_multiplier ) ;
2007-02-05 21:57:25 +03:00
min_voltage = ( hi > > 16 ) & 0xff ;
2016-04-05 23:28:25 +03:00
pr_info ( " Lowest voltage = %dmV \n " , min_voltage * 16 + 700 ) ;
2007-02-05 21:57:25 +03:00
min_multiplier = ( hi > > 24 ) & 0xff ;
2016-04-05 23:28:25 +03:00
pr_info ( " 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 ;
2011-07-21 00:20:56 +04:00
if ( max_voltage < min_voltage
| | current_voltage < min_voltage
| | current_voltage > max_voltage )
2007-02-05 21:57:25 +03:00
return - EINVAL ;
2011-07-21 00:20:56 +04:00
/* Check for systems using underclocked CPU */
if ( ! freq_failsafe_off & & max_multiplier ! = current_multiplier ) {
2016-04-05 23:28:25 +03:00
pr_info ( " Your processor is running at different frequency then its maximum. Aborting. \n " ) ;
pr_info ( " You can use freq_failsafe_off option to disable this check. \n " ) ;
2011-07-21 00:20:56 +04:00
return - EINVAL ;
}
if ( ! voltage_failsafe_off & & max_voltage ! = current_voltage ) {
2016-04-05 23:28:25 +03:00
pr_info ( " Your processor is running at different voltage then its maximum. Aborting. \n " ) ;
pr_info ( " You can use voltage_failsafe_off option to disable this check. \n " ) ;
2011-07-21 00:20:56 +04:00
return - EINVAL ;
}
2007-02-05 21:57:25 +03:00
/* Calc FSB speed */
fsb = cpu_khz / current_multiplier ;
2011-07-22 01:11:29 +04:00
2016-04-27 03:14:13 +03:00
# if IS_ENABLED(CONFIG_ACPI_PROCESSOR)
2011-07-22 01:11:29 +04:00
/* Check for ACPI processor speed limit */
if ( ! ignore_acpi_limit & & ! eps_acpi_init ( ) ) {
if ( ! acpi_processor_get_bios_limit ( policy - > cpu , & limit ) ) {
2016-04-05 23:28:25 +03:00
pr_info ( " ACPI limit %u.%uGHz \n " ,
2011-07-22 01:11:29 +04:00
limit / 1000000 ,
( limit % 1000000 ) / 10000 ) ;
eps_acpi_exit ( policy ) ;
/* Check if max_multiplier is in BIOS limits */
if ( limit & & max_multiplier * fsb > limit ) {
2016-04-05 23:28:25 +03:00
pr_info ( " Aborting \n " ) ;
2011-07-22 01:11:29 +04:00
return - EINVAL ;
}
}
}
# endif
2011-07-24 02:35:28 +04:00
/* Allow user to set lower maximum voltage then that reported
* by processor */
if ( brand = = EPS_BRAND_C7M & & set_max_voltage ) {
u32 v ;
/* Change mV to something hardware can use */
v = ( set_max_voltage - 700 ) / 16 ;
/* Check if voltage is within limits */
if ( v > = min_voltage & & v < = max_voltage ) {
2016-04-05 23:28:25 +03:00
pr_info ( " Setting %dmV as maximum \n " , v * 16 + 700 ) ;
2011-07-24 02:35:28 +04:00
max_voltage = v ;
}
}
2007-02-05 21:57:25 +03:00
/* 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 */
2019-01-07 20:33:43 +03:00
centaur = kzalloc ( struct_size ( centaur , freq_table , states + 1 ) ,
GFP_KERNEL ) ;
2007-02-05 21:57:25 +03:00
if ( ! centaur )
return - ENOMEM ;
eps_cpu [ 0 ] = centaur ;
/* Copy basic values */
centaur - > fsb = fsb ;
2016-04-27 03:14:13 +03:00
# if IS_ENABLED(CONFIG_ACPI_PROCESSOR)
2011-07-22 01:11:29 +04:00
centaur - > bios_limit = limit ;
# endif
2007-02-05 21:57:25 +03:00
/* 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 ;
2013-03-30 14:55:15 +04:00
f_table [ 0 ] . driver_data = ( min_multiplier < < 8 ) | min_voltage ;
2007-02-05 21:57:25 +03:00
f_table [ 1 ] . frequency = fsb * max_multiplier ;
2013-03-30 14:55:15 +04:00
f_table [ 1 ] . driver_data = ( max_multiplier < < 8 ) | max_voltage ;
2007-02-05 21:57:25 +03:00
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 ;
2013-03-30 14:55:15 +04:00
f_table [ k ] . driver_data = ( i < < 8 ) | voltage ;
2007-02-05 21:57:25 +03:00
k + + ;
}
f_table [ k ] . frequency = CPUFREQ_TABLE_END ;
}
policy - > cpuinfo . transition_latency = 140000 ; /* 844mV -> 700mV in ns */
2018-02-26 08:08:51 +03:00
policy - > freq_table = & centaur - > freq_table [ 0 ] ;
2007-02-05 21:57:25 +03:00
return 0 ;
}
static int eps_cpu_exit ( struct cpufreq_policy * policy )
{
unsigned int cpu = policy - > cpu ;
/* Bye */
kfree ( eps_cpu [ cpu ] ) ;
eps_cpu [ cpu ] = NULL ;
return 0 ;
}
2007-02-27 01:55:48 +03:00
static struct cpufreq_driver eps_driver = {
2013-10-03 18:58:04 +04:00
. verify = cpufreq_generic_frequency_table_verify ,
2013-10-25 18:15:48 +04:00
. target_index = eps_target ,
2007-02-05 21:57:25 +03:00
. init = eps_cpu_init ,
. exit = eps_cpu_exit ,
. get = eps_get ,
. name = " e_powersaver " ,
2013-10-03 18:58:04 +04:00
. attr = cpufreq_generic_attr ,
2007-02-05 21:57:25 +03:00
} ;
2012-01-26 03:09:12 +04:00
/* This driver will work only on Centaur C7 processors with
* Enhanced SpeedStep / PowerSaver registers */
static const struct x86_cpu_id eps_cpu_id [ ] = {
{ X86_VENDOR_CENTAUR , 6 , X86_MODEL_ANY , X86_FEATURE_EST } ,
{ }
} ;
MODULE_DEVICE_TABLE ( x86cpu , eps_cpu_id ) ;
2007-02-05 21:57:25 +03:00
static int __init eps_init ( void )
{
2012-01-26 03:09:12 +04:00
if ( ! x86_match_cpu ( eps_cpu_id ) | | boot_cpu_data . x86_model < 10 )
2007-02-05 21:57:25 +03:00
return - ENODEV ;
if ( cpufreq_register_driver ( & eps_driver ) )
return - EINVAL ;
return 0 ;
}
static void __exit eps_exit ( void )
{
cpufreq_unregister_driver ( & eps_driver ) ;
}
2011-07-21 00:20:56 +04:00
/* Allow user to overclock his machine or to change frequency to higher after
* unloading module */
module_param ( freq_failsafe_off , int , 0644 ) ;
MODULE_PARM_DESC ( freq_failsafe_off , " Disable current vs max frequency check " ) ;
module_param ( voltage_failsafe_off , int , 0644 ) ;
MODULE_PARM_DESC ( voltage_failsafe_off , " Disable current vs max voltage check " ) ;
2016-04-27 03:14:13 +03:00
# if IS_ENABLED(CONFIG_ACPI_PROCESSOR)
2011-07-22 01:11:29 +04:00
module_param ( ignore_acpi_limit , int , 0644 ) ;
MODULE_PARM_DESC ( ignore_acpi_limit , " Don't check ACPI's processor speed limit " ) ;
# endif
2011-07-24 02:35:28 +04:00
module_param ( set_max_voltage , int , 0644 ) ;
MODULE_PARM_DESC ( set_max_voltage , " Set maximum CPU voltage (mV) C7-M only " ) ;
2011-07-21 00:20:56 +04:00
2009-01-18 06:54:47 +03:00
MODULE_AUTHOR ( " Rafal Bilski <rafalbilski@interia.pl> " ) ;
2007-02-05 21:57:25 +03:00
MODULE_DESCRIPTION ( " Enhanced PowerSaver driver for VIA C7 CPU's. " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( eps_init ) ;
module_exit ( eps_exit ) ;