From mboxrd@z Thu Jan 1 00:00:00 1970 From: =?ISO-8859-2?Q?Rafa=B3_Bilski?= Subject: [PATCH] Enhanced Powersaver driver Date: Thu, 11 Jan 2007 18:58:35 +0100 Message-ID: <45A67ACB.6040605@interia.pl> Mime-Version: 1.0 Content-Transfer-Encoding: quoted-printable Return-path: List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: cpufreq-bounces@lists.linux.org.uk Errors-To: cpufreq-bounces+glkc-cpufreq=m.gmane.org+glkc-cpufreq=m.gmane.org@lists.linux.org.uk Content-Type: text/plain; charset="iso-8859-1"; format="flowed" To: Dave Jones Cc: cpufreq@lists.linux.org.uk This is driver for Enhanced Powersaver which is present in VIA C7=20 processors. Beta tested by Jorgen (jorgen (at) greven dot dk).=20 Based on documentation provided by Dave Jones (Thanks!) and=20 C7 Eden datasheet available from www.via.com.tw. Looks like all=20 these C7 Eden CPU's don't have P-states in BIOS. I know that=20 2 p-states is low, but Jorgen finds it usefull anyway because=20 board is passive cooled. Brand detection is necessary because: "The VIA Eden can only change from the highest supported=20 performance state to the lowest supported performance state:=20 intermediate performance states are not guaranteed to work and=20 are not officially supported." There is no such statement in C7-M documentation, so I'm=20 allowing any multiplier between min and max for C7 and C7-M. Jorgen's dmesg: > eps: Detected VIA Eden > eps: Current voltage =3D 844mV > eps: Current multiplier =3D 10 > eps: Highest voltage =3D 844mV > eps: Highest multiplier =3D 10 > eps: Lowest voltage =3D 844mV > eps: Lowest multiplier =3D 4 Signed-off-by: Rafa=B3 Bilski --- diff --git a/arch/i386/kernel/cpu/cpufreq/Kconfig b/arch/i386/kernel/cpu/cp= ufreq/Kconfig --- a/arch/i386/kernel/cpu/cpufreq/Kconfig +++ b/arch/i386/kernel/cpu/cpufreq/Kconfig @@ -213,6 +213,12 @@ config X86_LONGHAUL =20 If in doubt, say N. =20 +config X86_E_POWERSAVER + tristate "VIA C7 Enhanced PowerSaver" + select CPU_FREQ_TABLE + help + If in doubt, say N. + comment "shared options" =20 config X86_ACPI_CPUFREQ_PROC_INTF diff --git a/arch/i386/kernel/cpu/cpufreq/Makefile b/arch/i386/kernel/cpu/c= pufreq/Makefile index 2e894f1..2ccde36 100644 --- a/arch/i386/kernel/cpu/cpufreq/Makefile +++ b/arch/i386/kernel/cpu/cpufreq/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_X86_POWERNOW_K6) +=3D powernow-k6.o obj-$(CONFIG_X86_POWERNOW_K7) +=3D powernow-k7.o obj-$(CONFIG_X86_POWERNOW_K8) +=3D powernow-k8.o obj-$(CONFIG_X86_LONGHAUL) +=3D longhaul.o +obj-$(CONFIG_X86_E_POWERSAVER) +=3D enhanced_powersaver.o obj-$(CONFIG_ELAN_CPUFREQ) +=3D elanfreq.o obj-$(CONFIG_SC520_CPUFREQ) +=3D sc520_freq.o obj-$(CONFIG_X86_LONGRUN) +=3D longrun.o =20 diff --git a/arch/i386/kernel/cpu/cpufreq/enhanced_powersaver.c b/arch/i386= /kernel/cpu/cpufreq/enhanced_powersaver.c new file mode 100644 index 0000000..4b6dc33 --- /dev/null +++ b/arch/i386/kernel/cpu/cpufreq/enhanced_powersaver.c @@ -0,0 +1,383 @@ +/* + * Licensed under the terms of the GNU GPL License version 2. + * + * BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous* + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define EPS_BRAND_C7M 0 +#define EPS_BRAND_C7 1 +#define EPS_BRAND_EDEN 2 +#define EPS_BRAND_C3 3 + +struct eps_cpu_data { + u32 fsb; + struct cpufreq_frequency_table freq_table[]; +}; + +static struct eps_cpu_data *eps_cpu[NR_CPUS]; + +/** + */ +static inline u32 calc_speed(u32 fsb, u8 mult) +{ + u32 khz; + + khz =3D fsb * mult; + return khz; +} + +static unsigned int eps_get(unsigned int cpu) +{ + struct eps_cpu_data *centaur; + u32 lo, hi; + u8 current_multiplier; + unsigned int kHz; + + if (cpu) + return 0; + centaur =3D eps_cpu[cpu]; + if (centaur =3D=3D NULL) + return 0; + /* Return current frequency */ + rdmsr(MSR_IA32_PERF_STATUS, lo, hi); + current_multiplier =3D (lo >> 8) & 0xff; + kHz =3D calc_speed(centaur->fsb, current_multiplier); + return kHz; +} + +/** + */ +static int eps_set_state(struct eps_cpu_data *centaur, + unsigned int cpu, + u32 dest_state) +{ + struct cpufreq_freqs freqs; + u32 lo, hi; + int i; + + freqs.old =3D eps_get(cpu); + freqs.new =3D centaur->fsb * ((dest_state >> 8) & 0xff); + freqs.cpu =3D cpu; + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + + /* Wait until transition end */ + rdmsr(MSR_IA32_PERF_STATUS, lo, hi); + i =3D 0; + while (lo & ((1 << 16) | (1 << 17))) { + udelay(64); + rdmsr(MSR_IA32_PERF_STATUS, lo, hi); + i++; + if (unlikely(i > 16)) goto timeout; + } + /* Set new multiplier and voltage */ + wrmsr(MSR_IA32_PERF_CTL, dest_state & 0xffff, 0); + /* And processor will do the rest */ + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + return 0; + +timeout: + printk(KERN_ERR "eps: Timeout! Aborting.\n"); + return -ENODEV; +} + +/** + */ +static int eps_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + struct eps_cpu_data *centaur; + unsigned int newstate =3D 0; + unsigned int cpu =3D policy->cpu; + unsigned int dest_state; + int ret; + + if (unlikely(eps_cpu[cpu] =3D=3D NULL)) + return -ENODEV; + centaur =3D 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 =3D centaur->freq_table[newstate].index & 0xffff; + ret =3D eps_set_state(centaur, cpu, dest_state); + 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; + u32 fsb; + struct eps_cpu_data *centaur; + struct cpufreq_frequency_table *f_table; + int k, step, voltage; + int ret; + int states; + + if (policy->cpu !=3D 0) + return -ENODEV; + + /* Check brand */ + printk("eps: Detected VIA "); + rdmsr(0x1153, lo, hi); + brand =3D (((lo >> 2) ^ lo) >> 18) & 3; + switch(brand) { + case EPS_BRAND_C7M: + printk("C7-M\n"); + break; + case EPS_BRAND_C7: + printk("C7\n"); + break; + case EPS_BRAND_EDEN: + printk("Eden\n"); + break; + case EPS_BRAND_C3: + printk("C3\n"); + return -ENODEV; + break; + } + /* Enable Enhanced PowerSaver */ + rdmsrl(MSR_IA32_MISC_ENABLE, val); + if (!(val & 1 << 16)) { + val |=3D 1 << 16; + wrmsrl(MSR_IA32_MISC_ENABLE, val); + /* Can be locked at 0 */ + rdmsrl(MSR_IA32_MISC_ENABLE, val); + if (!(val & 1 << 16)) { + printk("eps: Can't enable Enhanced PowerSaver\n"); + return -ENODEV; + } + } + + /* Print voltage and multiplier */ + rdmsr(MSR_IA32_PERF_STATUS, lo, hi); + current_voltage =3D lo & 0xff; + printk("eps: Current voltage =3D %dmV\n", current_voltage * 16 + 700); + current_multiplier =3D (lo >> 8) & 0xff; + printk("eps: Current multiplier =3D %d\n", current_multiplier); + + /* Print limits */ + max_voltage =3D hi & 0xff; + printk("eps: Highest voltage =3D %dmV\n", max_voltage * 16 + 700); + max_multiplier =3D (hi >> 8) & 0xff; + printk("eps: Highest multiplier =3D %d\n", max_multiplier); + min_voltage =3D (hi >> 16) & 0xff; + printk("eps: Lowest voltage =3D %dmV\n", min_voltage * 16 + 700); + min_multiplier =3D (hi >> 24) & 0xff; + printk("eps: Lowest multiplier =3D %d\n", min_multiplier); + + /* Sanity checks */ + if (current_multiplier =3D=3D 0 || max_multiplier =3D=3D 0=20 + || min_multiplier =3D=3D 0) { + return -EINVAL; + } + if (current_multiplier > max_multiplier + || max_multiplier <=3D min_multiplier) { + return -EINVAL; + } + if (current_voltage > 0x1c || max_voltage > 0x1c) { + return -EINVAL; + } + if (max_voltage < min_voltage) { + return -EINVAL; + } + + /* Calc FSB speed */ + fsb =3D cpu_khz / current_multiplier; + /* Calc number of p-states supported */ + if (brand =3D=3D EPS_BRAND_EDEN) { + states =3D 2; + } else { + states =3D max_multiplier - min_multiplier + 1; + } + + /* Allocate private data and frequency table for current cpu */ + centaur =3D kzalloc(sizeof(struct eps_cpu_data)=20 + + (states + 1) * sizeof(struct cpufreq_frequency_table), + GFP_KERNEL); + if (!centaur) + return -ENOMEM; + eps_cpu[0] =3D centaur; + + /* Copy basic values */ + centaur->fsb =3D fsb; + + /* Fill frequency and MSR value table */ + f_table =3D ¢aur->freq_table[0]; + if (brand =3D=3D EPS_BRAND_EDEN) { + f_table[0].frequency =3D calc_speed(fsb, min_multiplier); + f_table[0].index =3D (min_multiplier << 8) | min_voltage; + f_table[1].frequency =3D calc_speed(fsb, max_multiplier); + f_table[1].index =3D (max_multiplier << 8) | max_voltage; + f_table[2].frequency =3D CPUFREQ_TABLE_END; + } else { + k =3D 0; + step =3D ((max_voltage - min_voltage) * 256) / (max_multiplier - min_mul= tiplier); + for (i =3D min_multiplier; i <=3D max_multiplier; i++) { + voltage =3D (k * step) / 256 + min_voltage; + f_table[k].frequency =3D calc_speed(fsb, i); + f_table[k].index =3D (i << 8) | voltage; + k++; + } + f_table[k].frequency =3D CPUFREQ_TABLE_END; + } + + policy->governor =3D CPUFREQ_DEFAULT_GOVERNOR; + policy->cpuinfo.transition_latency =3D 140000; /* 844mV -> 700mV in ns */ + policy->cur =3D calc_speed(fsb, current_multiplier); + + ret =3D 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 =3D policy->cpu; + struct eps_cpu_data *centaur; + u32 lo, hi; + + if (eps_cpu[cpu] =3D=3D NULL) + return -ENODEV; + centaur =3D 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] =3D NULL; + return 0; +} + +/** + */ + +static ssize_t eps_show_temp(struct cpufreq_policy *policy, char *buf) +{ + unsigned int cpu =3D policy->cpu; + u32 i; + ssize_t count; + u32 temp; + + if (eps_cpu[cpu] =3D=3D NULL) + return -ENODEV; + + /* Check Centaur Extended CPUID */ + i =3D cpuid_eax(0xC0000000); + if (i > 0xC0000001) { + i =3D cpuid_eax(0xC0000002); + temp =3D i >> 8; + } else { + temp =3D 0; + } + + count =3D sprintf(&buf[0], "%d\n", temp); + return count; +} + +static struct freq_attr eps_current_temp =3D { + .attr =3D { .name =3D "cpuinfo_cur_temp", + .mode =3D 0444, + .owner =3D THIS_MODULE + }, + .show =3D eps_show_temp, +}; + +static struct freq_attr* eps_attr[] =3D { + &cpufreq_freq_attr_scaling_available_freqs, + &eps_current_temp, + NULL, +}; + +static struct cpufreq_driver eps_driver =3D { + .verify =3D eps_verify, + .target =3D eps_target, + .init =3D eps_cpu_init, + .exit =3D eps_cpu_exit, + .get =3D eps_get, + .name =3D "e_powersaver", + .owner =3D THIS_MODULE, + .attr =3D eps_attr, +}; + + +/** + */ +static int __init eps_init(void) +{ + struct cpuinfo_x86 *c =3D cpu_data; + + if (c->x86_vendor !=3D X86_VENDOR_CENTAUR + || c->x86 !=3D 6 + || c->x86_model !=3D 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); +} + +MODULE_AUTHOR ("Rafa=B3 Bilski"); +MODULE_DESCRIPTION ("Enhanced PowerSaver driver for VIA C7 CPU's."); +MODULE_LICENSE ("GPL"); + +module_init(eps_init); +module_exit(eps_exit); ---------------------------------------------------------------------- Nieograniczona pojemnosc skrzynki >>> http://link.interia.pl/f19e6