All of lore.kernel.org
 help / color / mirror / Atom feed
From: Rickard Holmberg <rickard@holmberg.info>
To: cpufreq@lists.linux.org.uk
Subject: [PATCH] Adjusting CPU core voltages via sysfs on Centrino
Date: Mon, 08 Aug 2005 17:30:45 +0200	[thread overview]
Message-ID: <42F77AA5.4030403@holmberg.info> (raw)

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);
 }

             reply	other threads:[~2005-08-08 15:30 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2005-08-08 15:30 Rickard Holmberg [this message]
2005-08-16  8:56 ` [PATCH] Adjusting CPU core voltages via sysfs on Centrino Dominik Brodowski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=42F77AA5.4030403@holmberg.info \
    --to=rickard@holmberg.info \
    --cc=cpufreq@lists.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.