All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] Adjusting CPU core voltages via sysfs on Centrino
@ 2005-08-08 15:30 Rickard Holmberg
  2005-08-16  8:56 ` Dominik Brodowski
  0 siblings, 1 reply; 2+ messages in thread
From: Rickard Holmberg @ 2005-08-08 15:30 UTC (permalink / raw)
  To: cpufreq

Hi everyone.

This patch makes speedstep-centrino add a read/writable
/sys/devices/system/cpu/cpu0/cpufreq/scaling_voltages file,
allowing hand-tuning the voltages table on the Centrino. It
only permits lowering voltages relative to what was originally
determined from ACPI or tables.

I don't know if this is a desirable thing to have in a standard
kernel. Only tested on a Dothan with voltage tables from ACPI.
Comments are very welcome. The patch might be considered bloated
and/or spaghetti-code. It is Kconfigurable with
CONFIG_X86_SPEEDSTEP_CENTRINO_ADJUST_VOLTAGES.
Perhaps instability should be mentioned in the Kconfig help as a
possible side-effect of lowering the voltage too much.

	/ Rickard Holmberg

Signed-off-by: Rickard Holmberg <rickard@holmberg.info>

diff --git a/arch/i386/kernel/cpu/cpufreq/Kconfig b/arch/i386/kernel/cpu/cpufreq/Kconfig
--- a/arch/i386/kernel/cpu/cpufreq/Kconfig
+++ b/arch/i386/kernel/cpu/cpufreq/Kconfig
@@ -131,6 +131,18 @@ config X86_SPEEDSTEP_CENTRINO_ACPI

          If in doubt, say Y.

+config X86_SPEEDSTEP_CENTRINO_ADJUST_VOLTAGES
+       bool "Allow adjusting Pentium M core voltages"
+       depends on X86_SPEEDSTEP_CENTRINO
+       default n
+       help
+         Allow hand-tuning the Pentium M CPU core voltage tables
+         through the sysfs file system. It is primarily useful to lower
+         the CPU core temperature and reduce power consumption in
+         battery powered environments.
+
+         If in doubt, say N.
+
 config X86_SPEEDSTEP_CENTRINO_TABLE
        bool "Built-in tables for Banias CPUs"
        depends on X86_SPEEDSTEP_CENTRINO
diff --git a/arch/i386/kernel/cpu/cpufreq/speedstep-centrino.c b/arch/i386/kernel/cpu/cpufreq/speedstep-centrino.c
--- a/arch/i386/kernel/cpu/cpufreq/speedstep-centrino.c
+++ b/arch/i386/kernel/cpu/cpufreq/speedstep-centrino.c
@@ -24,6 +24,7 @@
 #include <linux/config.h>
 #include <linux/delay.h>
 #include <linux/compiler.h>
+#include <linux/ctype.h>

 #ifdef CONFIG_X86_SPEEDSTEP_CENTRINO_ACPI
 #include <linux/acpi.h>
@@ -86,13 +87,16 @@ static struct cpufreq_driver centrino_dr

 #ifdef CONFIG_X86_SPEEDSTEP_CENTRINO_TABLE

+#define VOLTAGE_TO_VID(mv) (((mv) - 700) / 16)
+#define VID_TO_VOLTAGE(vid) ((vid) * 16 + 700)
+
 /* Computes the correct form for IA32_PERF_CTL MSR for a particular
    frequency/voltage operating point; frequency in MHz, volts in mV.
    This is stored as "index" in the structure. */
 #define OP(mhz, mv)                                                    \
        {                                                               \
                .frequency = (mhz) * 1000,                              \
-               .index = (((mhz)/100) << 8) | ((mv - 700) / 16)         \
+               .index = (((mhz)/100) << 8) | VOLTAGE_TO_VID(mv)        \
        }

 /*
@@ -666,7 +670,163 @@ migrate_end:
        return (retval);
 }

+/* sysfs things for altering voltages */
+#ifdef CONFIG_X86_SPEEDSTEP_CENTRINO_ADJUST_VOLTAGES
+static struct cpufreq_frequency_table **original_table = NULL;
+
+static ssize_t show_scaling_voltages(struct cpufreq_policy *policy, char *buf)
+{
+       unsigned int cpu;
+       int i, j;
+
+       if (!policy)
+           return -ENODEV;
+       cpu = policy->cpu;
+       if (!centrino_model[cpu] || !centrino_model[cpu]->op_points)
+           return -ENODEV;
+
+       j=snprintf(buf, PAGE_SIZE, "# frequency voltage\n");
+
+       for (i=0; centrino_model[cpu]->op_points[i].frequency
+                    != CPUFREQ_TABLE_END; i++) {
+               /* Print lines of frequency and voltage */
+               j += snprintf(&buf[j], PAGE_SIZE-j, "%u %u\n",
+                             centrino_model[cpu]->op_points[i].frequency,
+                             VID_TO_VOLTAGE(centrino_model[cpu]->op_points[i].index & 0xff));
+       }
+       buf[PAGE_SIZE-1] = 0;
+       return j;
+}
+
+static ssize_t store_scaling_voltages(struct cpufreq_policy *policy,
+               const char *buf, size_t count)
+{
+       unsigned int cpu;
+       unsigned int frequency, voltage;
+       int ret, i, j;
+       int msr, vid;
+       unsigned int cur_freq;
+
+       if (!policy)
+           return -ENODEV;
+       cpu = policy->cpu;
+       if (!centrino_model[cpu] || !centrino_model[cpu]->op_points)
+           return -ENODEV;
+
+       /* Remember the original voltages, and don't let the user
+        * raise the voltages above those. As stated in
+        * centrino_init(), be paranoid about releasing people's
+        * valuable magic smoke. */
+
+       if (!original_table) {
+               original_table =
+                       kmalloc(sizeof(struct cpufreq_frequency_table *)*NR_CPUS,
+                               GFP_KERNEL);
+
+               if (!original_table)
+                       return -ENOMEM;
+
+               for (i=0; i < NR_CPUS; i++)
+                       original_table[i] = NULL;
+       }
+
+       if (!original_table[cpu]) {
+               /* Count number of frequencies and allocate memory for a copy */
+               for (i=0; centrino_model[cpu]->op_points[i].frequency
+                            != CPUFREQ_TABLE_END; i++);
+               original_table[cpu] = (struct cpufreq_frequency_table*)
+                       kmalloc(sizeof(struct cpufreq_frequency_table)*(i+1),
+                               GFP_KERNEL);
+
+               if (!original_table[cpu])
+                       return -ENOMEM;
+
+               /* Make copy of frequency/voltage pairs */
+               for (i=0; centrino_model[cpu]->op_points[i].frequency
+                            != CPUFREQ_TABLE_END; i++) {
+                       original_table[cpu][i].frequency =
+                               centrino_model[cpu]->op_points[i].frequency;
+                       original_table[cpu][i].index =
+                               centrino_model[cpu]->op_points[i].index;
+               }
+               original_table[cpu][i].frequency = CPUFREQ_TABLE_END;
+       }
+
+       /* Don't use sscanf, since we want to return the actual number
+        * of characters read. I don't feel comfortable using
+        * simple_stroul either, since I'm not sure that the buf
+        * really is zero-terminated. */
+
+       /* Eat non-digits */
+       for (ret = 0; ret < count && !isdigit(buf[ret]); ret++);
+       if (ret == count)
+           return ret;
+       /* Read frequency */
+       for (frequency = 0; ret < count && isdigit(buf[ret]); ret++)
+               frequency = 10*frequency + buf[ret] - '0';
+       /* Eat non-digits */
+       for (; ret < count && !isdigit(buf[ret]); ret++);
+       /* Read new voltage */
+       for (voltage = 0; ret < count && isdigit(buf[ret]); ret++)
+               voltage = 10*voltage + buf[ret] - '0';
+
+       if (frequency == 0 || voltage < 700)
+               return -EINVAL;
+
+       /* Check so that the voltage is not higher than the original one */
+       for (j=0; original_table[cpu][j].frequency != CPUFREQ_TABLE_END; j++)
+               if (frequency == original_table[cpu][j].frequency)
+                       break;
+       if (original_table[cpu][j].frequency == CPUFREQ_TABLE_END)
+               return -EINVAL;
+       if (voltage > VID_TO_VOLTAGE(original_table[cpu][j].index & 0xff))
+               return -EINVAL;
+
+       for (i=0; centrino_model[cpu]->op_points[i].frequency
+                    != CPUFREQ_TABLE_END; i++) {
+               if (frequency == centrino_model[cpu]->op_points[i].frequency)
+                       break;
+       }
+
+       if (centrino_model[cpu]->op_points[i].frequency == CPUFREQ_TABLE_END)
+               return -EINVAL;
+
+       msr = centrino_model[cpu]->op_points[i].index;
+       vid = VOLTAGE_TO_VID(voltage);
+       msr = (msr & ~0xff) | (vid & 0xff);
+
+       /* Double check so that the voltage id is not higher than the original one */
+       if (vid > (original_table[cpu][j].index & 0xff))
+               return -EINVAL;
+
+       dprintk(KERN_INFO PFX "altering centrino cpufreq table, to make %u kHz have "
+               "voltage %u mV (VID=%u)\n", frequency, voltage, vid);
+
+       centrino_model[cpu]->op_points[i].index = msr;
+
+
+       /* If the updated voltage applies to the frequency currently
+        * used, tell the CPU about the new voltage */
+       cur_freq = cpufreq_get(policy->cpu);
+       if (cur_freq == frequency)
+           centrino_target(policy, cur_freq, CPUFREQ_RELATION_L);
+
+       return ret;
+}
+
+#define define_one_rw(_name) \
+static struct freq_attr _name = \
+__ATTR(_name, 0644, show_##_name, store_##_name)
+
+define_one_rw(scaling_voltages);
+#endif /* CONFIG_X86_SPEEDSTEP_CENTRINO_ADJUST_VOLTAGES */
+
+/* end of sysfs voltage things */
+
 static struct freq_attr* centrino_attr[] = {
+#ifdef CONFIG_X86_SPEEDSTEP_CENTRINO_ADJUST_VOLTAGES
+       &scaling_voltages,
+#endif
        &cpufreq_freq_attr_scaling_available_freqs,
        NULL,
 };
@@ -710,6 +870,17 @@ static int __init centrino_init(void)

 static void __exit centrino_exit(void)
 {
+#ifdef CONFIG_X86_SPEEDSTEP_CENTRINO_ADJUST_VOLTAGES
+       if (original_table) {
+               int i;
+               for (i=0; i < NR_CPUS; i++) {
+                       if (original_table[i])
+                               kfree(original_table[i]);
+               }
+               kfree(original_table);
+       }
+#endif
+
        cpufreq_unregister_driver(&centrino_driver);
 }

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2005-08-16  8:56 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-08-08 15:30 [PATCH] Adjusting CPU core voltages via sysfs on Centrino Rickard Holmberg
2005-08-16  8:56 ` Dominik Brodowski

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.