ed361bf080
Some systems are using 1,2Ghz@844mV processors running at 600MHz@796mV. Try to detect such systems and don't touch anything on it. If CPU doesn't have P-States in BIOS it should run at maximum frequency. Allow user to bypass checks by means of two new options. Don't set frequency to maximum on module unloading to avoid bada boom. It is also possible that some processors may have incorrect values in min/max registers caused by error in manufacturing process. Probably it would be BIOS job to set them to right frequency and P-States tables would have correct values inside. Two additional sanity checks for voltage. Signed-off-by: Rafał Bilski <rafalbilski@interia.pl> Signed-off-by: Dave Jones <davej@redhat.com>
387 lines
9.6 KiB
C
387 lines
9.6 KiB
C
/*
|
|
* 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 <linux/timex.h>
|
|
#include <linux/io.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include <asm/msr.h>
|
|
#include <asm/tsc.h>
|
|
|
|
#define EPS_BRAND_C7M 0
|
|
#define EPS_BRAND_C7 1
|
|
#define EPS_BRAND_EDEN 2
|
|
#define EPS_BRAND_C3 3
|
|
#define EPS_BRAND_C7D 4
|
|
|
|
struct eps_cpu_data {
|
|
u32 fsb;
|
|
struct cpufreq_frequency_table freq_table[];
|
|
};
|
|
|
|
static struct eps_cpu_data *eps_cpu[NR_CPUS];
|
|
|
|
/* Module parameters */
|
|
static int freq_failsafe_off;
|
|
static int voltage_failsafe_off;
|
|
|
|
|
|
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;
|
|
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);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
u8 current_multiplier, current_voltage;
|
|
|
|
/* 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);
|
|
}
|
|
#endif
|
|
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;
|
|
u8 brand = 0;
|
|
u32 fsb;
|
|
struct eps_cpu_data *centaur;
|
|
struct cpuinfo_x86 *c = &cpu_data(0);
|
|
struct cpufreq_frequency_table *f_table;
|
|
int k, step, voltage;
|
|
int ret;
|
|
int states;
|
|
|
|
if (policy->cpu != 0)
|
|
return -ENODEV;
|
|
|
|
/* Check brand */
|
|
printk(KERN_INFO "eps: Detected VIA ");
|
|
|
|
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;
|
|
}
|
|
|
|
switch (brand) {
|
|
case EPS_BRAND_C7M:
|
|
printk(KERN_CONT "C7-M\n");
|
|
break;
|
|
case EPS_BRAND_C7:
|
|
printk(KERN_CONT "C7\n");
|
|
break;
|
|
case EPS_BRAND_EDEN:
|
|
printk(KERN_CONT "Eden\n");
|
|
break;
|
|
case EPS_BRAND_C7D:
|
|
printk(KERN_CONT "C7-D\n");
|
|
break;
|
|
case EPS_BRAND_C3:
|
|
printk(KERN_CONT "C3\n");
|
|
return -ENODEV;
|
|
break;
|
|
}
|
|
/* Enable Enhanced PowerSaver */
|
|
rdmsrl(MSR_IA32_MISC_ENABLE, val);
|
|
if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
|
|
val |= MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP;
|
|
wrmsrl(MSR_IA32_MISC_ENABLE, val);
|
|
/* Can be locked at 0 */
|
|
rdmsrl(MSR_IA32_MISC_ENABLE, val);
|
|
if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
|
|
printk(KERN_INFO "eps: Can't enable Enhanced PowerSaver\n");
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
|
|
/* Print limits */
|
|
max_voltage = hi & 0xff;
|
|
printk(KERN_INFO "eps: Highest voltage = %dmV\n",
|
|
max_voltage * 16 + 700);
|
|
max_multiplier = (hi >> 8) & 0xff;
|
|
printk(KERN_INFO "eps: Highest multiplier = %d\n", max_multiplier);
|
|
min_voltage = (hi >> 16) & 0xff;
|
|
printk(KERN_INFO "eps: Lowest voltage = %dmV\n",
|
|
min_voltage * 16 + 700);
|
|
min_multiplier = (hi >> 24) & 0xff;
|
|
printk(KERN_INFO "eps: Lowest multiplier = %d\n", min_multiplier);
|
|
|
|
/* 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;
|
|
if (current_voltage > 0x1f || max_voltage > 0x1f)
|
|
return -EINVAL;
|
|
if (max_voltage < min_voltage
|
|
|| current_voltage < min_voltage
|
|
|| current_voltage > max_voltage)
|
|
return -EINVAL;
|
|
|
|
/* Check for systems using underclocked CPU */
|
|
if (!freq_failsafe_off && max_multiplier != current_multiplier) {
|
|
printk(KERN_INFO "eps: Your processor is running at different "
|
|
"frequency then its maximum. Aborting.\n");
|
|
printk(KERN_INFO "eps: You can use freq_failsafe_off option "
|
|
"to disable this check.\n");
|
|
return -EINVAL;
|
|
}
|
|
if (!voltage_failsafe_off && max_voltage != current_voltage) {
|
|
printk(KERN_INFO "eps: Your processor is running at different "
|
|
"voltage then its maximum. Aborting.\n");
|
|
printk(KERN_INFO "eps: You can use voltage_failsafe_off "
|
|
"option to disable this check.\n");
|
|
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 = ¢aur->freq_table[0];
|
|
if (brand != EPS_BRAND_C7M) {
|
|
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, ¢aur->freq_table[0]);
|
|
if (ret) {
|
|
kfree(centaur);
|
|
return ret;
|
|
}
|
|
|
|
cpufreq_frequency_table_get_attr(¢aur->freq_table[0], policy->cpu);
|
|
return 0;
|
|
}
|
|
|
|
static int eps_cpu_exit(struct cpufreq_policy *policy)
|
|
{
|
|
unsigned int cpu = policy->cpu;
|
|
|
|
/* 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,
|
|
};
|
|
|
|
static struct cpufreq_driver eps_driver = {
|
|
.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)
|
|
{
|
|
struct cpuinfo_x86 *c = &cpu_data(0);
|
|
|
|
/* This driver will work only on Centaur C7 processors with
|
|
* Enhanced SpeedStep/PowerSaver registers */
|
|
if (c->x86_vendor != X86_VENDOR_CENTAUR
|
|
|| c->x86 != 6 || c->x86_model < 10)
|
|
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);
|
|
}
|
|
|
|
/* 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");
|
|
|
|
MODULE_AUTHOR("Rafal 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);
|