From: Cristiano Prisciandaro <cristiano.p@xxxxxxxxx> The bios of the eeepc 900 exposes an acpi method that allows clocking the cpu to 630/900 MHz. This driver allows controlling the frequency switch through the cpufreq subsystem. Signed-off-by: Cristiano Prisciandaro <cristiano.p@xxxxxxxxx> --- diff -uprN -X linux-2.6.vanilla/Documentation/dontdiff linux-2.6.vanilla/arch/x86/kernel/cpu/cpufreq/eee900freq.c linux-2.6/arch/x86/kernel/cpu/cpufreq/eee900freq.c --- linux-2.6.vanilla/arch/x86/kernel/cpu/cpufreq/eee900freq.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6/arch/x86/kernel/cpu/cpufreq/eee900freq.c 2008-11-23 15:06:56.000000000 +0100 @@ -0,0 +1,232 @@ +/* + * Experimental cpu frequency scaling driver for the eeepc 900 + * + * Copyright (C) 2008 Cristiano Prisciandaro <cristiano.p@xxxxxxxxx> + * + * This driver is based on the (experimental) finding that the + * eeepc bios exposes a method to underclock the bus/cpu. + * + * It seems to work fine with the following BIOS versions: + * 0501, 0601, 0704 and 0802. + * + * Parts of this code are from + * asus_acpi.c Copyright (C) Julien Lerouge, Karol Kozimor + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * BIG FAT DISCLAIMER: experimental code. Possibly *dangerous* + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/cpufreq.h> +#include <linux/cpufreq.h> +#include <acpi/acpi_drivers.h> +#include <acpi/acpi_bus.h> + +#define MNAME "eee900freq:" +#define ASUS_HOTK_PREFIX "\\_SB.ATKD" +#define ASUS_CPUFV_READ_METHOD "CFVG" +#define ASUS_CPUFV_WRITE_METHOD "CFVS" + +static acpi_handle handle; +static unsigned int cpufreq_eee900_get(unsigned int cpu); + +/* available frequencies */ +static struct cpufreq_frequency_table eee900freq_table[] = { + {0, 630000}, + {1, 900000}, + {0, CPUFREQ_TABLE_END} +}; + +struct eee900_acpi_value { + int frequency; + int value; +}; + +static struct eee900_acpi_value eee900_acpi_values_table[] = { + {630000, 1}, + {900000, 0} +}; + +/* read from the acpi handle (from asus_acpi.c) */ +static int read_eee900_acpi_int(acpi_handle handle, const char *method, + int *val) +{ + struct acpi_buffer output; + union acpi_object out_obj; + acpi_status status; + + output.length = sizeof(out_obj); + output.pointer = &out_obj; + status = acpi_evaluate_object(handle, (char *)method, NULL, &output); + *val = out_obj.integer.value; + return status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER; +} + +/* write to the acpi handle (from asus_acpi.c) */ +static int write_eee900_acpi_int(acpi_handle handle, const char *method, + int val, struct acpi_buffer *output) +{ + struct acpi_object_list params; + union acpi_object in_obj; + acpi_status status; + + params.count = 1; + params.pointer = &in_obj; + in_obj.type = ACPI_TYPE_INTEGER; + in_obj.integer.value = val; + status = acpi_evaluate_object(handle, (char *)method, ¶ms, output); + return status == AE_OK; +} + +/* return the current frequency as in acpi */ +static unsigned int cpufreq_eee900_get(unsigned int cpu) +{ + int value; + + if (!read_eee900_acpi_int(handle, ASUS_CPUFV_READ_METHOD, &value)) { + printk(KERN_WARNING MNAME + "unable to read current frequency from " + ASUS_CPUFV_READ_METHOD "\n"); + return -EINVAL; + } + + switch (value) { + case 0x200: + return 900000; + case 0x201: + return 630000; + } + + return 0; +} + +static void cpufreq_eee900_set_freq(unsigned int index) +{ + struct cpufreq_freqs freqs; + + freqs.old = cpufreq_eee900_get(0); + freqs.new = eee900freq_table[index].frequency; + freqs.cpu = 0; + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + + if (!write_eee900_acpi_int(handle, ASUS_CPUFV_WRITE_METHOD, + eee900_acpi_values_table[index].value, NULL)) + printk(KERN_WARNING "unable to set new frequency: val=%x", + eee900_acpi_values_table[index].value); + + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return; +} + +static int cpufreq_eee900_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int newstate = 0; + + if (cpufreq_frequency_table_target + (policy, &eee900freq_table[0], target_freq, relation, &newstate)) + return -EINVAL; + + cpufreq_eee900_set_freq(newstate); + + return 0; +} + +static int cpufreq_eee900_cpu_init(struct cpufreq_policy *policy) +{ + + unsigned int cfreq; + + cfreq = cpufreq_eee900_get(policy->cpu); + + cpufreq_frequency_table_get_attr(eee900freq_table, policy->cpu); + + /* cpuinfo and default policy values */ + policy->cpuinfo.transition_latency = 1000000; /* assumed */ + policy->cur = cfreq; + + return cpufreq_frequency_table_cpuinfo(policy, &eee900freq_table[0]); +} + +static int cpufreq_eee900_cpu_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_put_attr(policy->cpu); + return 0; +} + +static int cpufreq_eee900_verify(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, &eee900freq_table[0]); +} + +static struct freq_attr *eee900freq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver eee900freq_driver = { + .verify = cpufreq_eee900_verify, + .target = cpufreq_eee900_target, + .init = cpufreq_eee900_cpu_init, + .exit = cpufreq_eee900_cpu_exit, + .get = cpufreq_eee900_get, + .name = "eee900freq", + .owner = THIS_MODULE, + .attr = eee900freq_attr, +}; + +static int __init cpufreq_eee900_init(void) +{ + struct cpuinfo_x86 *c = &cpu_data(0); + acpi_status status; + int ret; + int test; + + if (c->x86_vendor != X86_VENDOR_INTEL) + return -ENODEV; + + status = acpi_get_handle(NULL, ASUS_HOTK_PREFIX, &handle); + + if (ACPI_FAILURE(status)) { + printk(KERN_INFO MNAME "unable to get acpi handle.\n"); + handle = NULL; + return -ENODEV; + } + + /* check if the control method is supported */ + if (!read_eee900_acpi_int(handle, ASUS_CPUFV_READ_METHOD, &test)) { + printk(KERN_INFO "Get control method test failed\n"); + return -ENODEV; + } + + ret = cpufreq_register_driver(&eee900freq_driver); + + if (!ret) + printk(KERN_INFO MNAME + "CPU frequency scaling driver for the eeepc 900.\n"); + + return ret; +} + +static void __exit cpufreq_eee900_exit(void) +{ + cpufreq_unregister_driver(&eee900freq_driver); + printk(KERN_INFO MNAME + "CPU frequency scaling driver for the eeepc 900 unregistered.\n"); +} + +module_init(cpufreq_eee900_init); +module_exit(cpufreq_eee900_exit); + +MODULE_AUTHOR("Cristiano Prisciandaro <cristiano.p@xxxxxxxxx>"); +MODULE_DESCRIPTION("Frequency scaling driver for the eeepc 900."); +MODULE_LICENSE("GPL"); diff -uprN -X linux-2.6.vanilla/Documentation/dontdiff linux-2.6.vanilla/arch/x86/kernel/cpu/cpufreq/Kconfig linux-2.6/arch/x86/kernel/cpu/cpufreq/Kconfig --- linux-2.6.vanilla/arch/x86/kernel/cpu/cpufreq/Kconfig 2008-11-21 00:19:22.000000000 +0100 +++ linux-2.6/arch/x86/kernel/cpu/cpufreq/Kconfig 2008-11-23 12:24:47.000000000 +0100 @@ -243,6 +243,15 @@ config X86_E_POWERSAVER If in doubt, say N. +config X86_CPUFREQ_EEEPC900 + tristate "Eeepc 900 ACPI frequency scaling driver" + select CPU_FREQ_TABLE + depends on ACPI && X86_32 && EXPERIMENTAL + help + This adds the CPUFreq driver for the eeepc 900. + + If in doubt, say N. + comment "shared options" config X86_ACPI_CPUFREQ_PROC_INTF diff -uprN -X linux-2.6.vanilla/Documentation/dontdiff linux-2.6.vanilla/arch/x86/kernel/cpu/cpufreq/Makefile linux-2.6/arch/x86/kernel/cpu/cpufreq/Makefile --- linux-2.6.vanilla/arch/x86/kernel/cpu/cpufreq/Makefile 2008-11-21 00:19:22.000000000 +0100 +++ linux-2.6/arch/x86/kernel/cpu/cpufreq/Makefile 2008-11-23 12:24:53.000000000 +0100 @@ -14,3 +14,4 @@ obj-$(CONFIG_X86_ACPI_CPUFREQ) += acpi- obj-$(CONFIG_X86_SPEEDSTEP_CENTRINO) += speedstep-centrino.o obj-$(CONFIG_X86_P4_CLOCKMOD) += p4-clockmod.o obj-$(CONFIG_X86_CPUFREQ_NFORCE2) += cpufreq-nforce2.o +obj-$(CONFIG_X86_CPUFREQ_EEEPC900) += eee900freq.o -- To unsubscribe from this list: send the line "unsubscribe cpufreq" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html