linux/arch/x86/kernel/cpu/cpufreq/e_powersaver.c
<<
>>
Prefs
   1/*
   2 *  Based on documentation provided by Dave Jones. Thanks!
   3 *
   4 *  Licensed under the terms of the GNU GPL License version 2.
   5 *
   6 *  BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous*
   7 */
   8
   9#include <linux/kernel.h>
  10#include <linux/module.h>
  11#include <linux/init.h>
  12#include <linux/cpufreq.h>
  13#include <linux/ioport.h>
  14#include <linux/slab.h>
  15#include <linux/timex.h>
  16#include <linux/io.h>
  17#include <linux/delay.h>
  18
  19#include <asm/msr.h>
  20#include <asm/tsc.h>
  21
  22#define EPS_BRAND_C7M   0
  23#define EPS_BRAND_C7    1
  24#define EPS_BRAND_EDEN  2
  25#define EPS_BRAND_C3    3
  26#define EPS_BRAND_C7D   4
  27
  28struct eps_cpu_data {
  29        u32 fsb;
  30        struct cpufreq_frequency_table freq_table[];
  31};
  32
  33static struct eps_cpu_data *eps_cpu[NR_CPUS];
  34
  35
  36static unsigned int eps_get(unsigned int cpu)
  37{
  38        struct eps_cpu_data *centaur;
  39        u32 lo, hi;
  40
  41        if (cpu)
  42                return 0;
  43        centaur = eps_cpu[cpu];
  44        if (centaur == NULL)
  45                return 0;
  46
  47        /* Return current frequency */
  48        rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
  49        return centaur->fsb * ((lo >> 8) & 0xff);
  50}
  51
  52static int eps_set_state(struct eps_cpu_data *centaur,
  53                         unsigned int cpu,
  54                         u32 dest_state)
  55{
  56        struct cpufreq_freqs freqs;
  57        u32 lo, hi;
  58        int err = 0;
  59        int i;
  60
  61        freqs.old = eps_get(cpu);
  62        freqs.new = centaur->fsb * ((dest_state >> 8) & 0xff);
  63        freqs.cpu = cpu;
  64        cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
  65
  66        /* Wait while CPU is busy */
  67        rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
  68        i = 0;
  69        while (lo & ((1 << 16) | (1 << 17))) {
  70                udelay(16);
  71                rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
  72                i++;
  73                if (unlikely(i > 64)) {
  74                        err = -ENODEV;
  75                        goto postchange;
  76                }
  77        }
  78        /* Set new multiplier and voltage */
  79        wrmsr(MSR_IA32_PERF_CTL, dest_state & 0xffff, 0);
  80        /* Wait until transition end */
  81        i = 0;
  82        do {
  83                udelay(16);
  84                rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
  85                i++;
  86                if (unlikely(i > 64)) {
  87                        err = -ENODEV;
  88                        goto postchange;
  89                }
  90        } while (lo & ((1 << 16) | (1 << 17)));
  91
  92        /* Return current frequency */
  93postchange:
  94        rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
  95        freqs.new = centaur->fsb * ((lo >> 8) & 0xff);
  96
  97#ifdef DEBUG
  98        {
  99        u8 current_multiplier, current_voltage;
 100
 101        /* Print voltage and multiplier */
 102        rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
 103        current_voltage = lo & 0xff;
 104        printk(KERN_INFO "eps: Current voltage = %dmV\n",
 105                current_voltage * 16 + 700);
 106        current_multiplier = (lo >> 8) & 0xff;
 107        printk(KERN_INFO "eps: Current multiplier = %d\n",
 108                current_multiplier);
 109        }
 110#endif
 111        cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
 112        return err;
 113}
 114
 115static int eps_target(struct cpufreq_policy *policy,
 116                               unsigned int target_freq,
 117                               unsigned int relation)
 118{
 119        struct eps_cpu_data *centaur;
 120        unsigned int newstate = 0;
 121        unsigned int cpu = policy->cpu;
 122        unsigned int dest_state;
 123        int ret;
 124
 125        if (unlikely(eps_cpu[cpu] == NULL))
 126                return -ENODEV;
 127        centaur = eps_cpu[cpu];
 128
 129        if (unlikely(cpufreq_frequency_table_target(policy,
 130                        &eps_cpu[cpu]->freq_table[0],
 131                        target_freq,
 132                        relation,
 133                        &newstate))) {
 134                return -EINVAL;
 135        }
 136
 137        /* Make frequency transition */
 138        dest_state = centaur->freq_table[newstate].index & 0xffff;
 139        ret = eps_set_state(centaur, cpu, dest_state);
 140        if (ret)
 141                printk(KERN_ERR "eps: Timeout!\n");
 142        return ret;
 143}
 144
 145static int eps_verify(struct cpufreq_policy *policy)
 146{
 147        return cpufreq_frequency_table_verify(policy,
 148                        &eps_cpu[policy->cpu]->freq_table[0]);
 149}
 150
 151static int eps_cpu_init(struct cpufreq_policy *policy)
 152{
 153        unsigned int i;
 154        u32 lo, hi;
 155        u64 val;
 156        u8 current_multiplier, current_voltage;
 157        u8 max_multiplier, max_voltage;
 158        u8 min_multiplier, min_voltage;
 159        u8 brand = 0;
 160        u32 fsb;
 161        struct eps_cpu_data *centaur;
 162        struct cpuinfo_x86 *c = &cpu_data(0);
 163        struct cpufreq_frequency_table *f_table;
 164        int k, step, voltage;
 165        int ret;
 166        int states;
 167
 168        if (policy->cpu != 0)
 169                return -ENODEV;
 170
 171        /* Check brand */
 172        printk(KERN_INFO "eps: Detected VIA ");
 173
 174        switch (c->x86_model) {
 175        case 10:
 176                rdmsr(0x1153, lo, hi);
 177                brand = (((lo >> 2) ^ lo) >> 18) & 3;
 178                printk(KERN_CONT "Model A ");
 179                break;
 180        case 13:
 181                rdmsr(0x1154, lo, hi);
 182                brand = (((lo >> 4) ^ (lo >> 2))) & 0x000000ff;
 183                printk(KERN_CONT "Model D ");
 184                break;
 185        }
 186
 187        switch (brand) {
 188        case EPS_BRAND_C7M:
 189                printk(KERN_CONT "C7-M\n");
 190                break;
 191        case EPS_BRAND_C7:
 192                printk(KERN_CONT "C7\n");
 193                break;
 194        case EPS_BRAND_EDEN:
 195                printk(KERN_CONT "Eden\n");
 196                break;
 197        case EPS_BRAND_C7D:
 198                printk(KERN_CONT "C7-D\n");
 199                break;
 200        case EPS_BRAND_C3:
 201                printk(KERN_CONT "C3\n");
 202                return -ENODEV;
 203                break;
 204        }
 205        /* Enable Enhanced PowerSaver */
 206        rdmsrl(MSR_IA32_MISC_ENABLE, val);
 207        if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
 208                val |= MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP;
 209                wrmsrl(MSR_IA32_MISC_ENABLE, val);
 210                /* Can be locked at 0 */
 211                rdmsrl(MSR_IA32_MISC_ENABLE, val);
 212                if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
 213                        printk(KERN_INFO "eps: Can't enable Enhanced PowerSaver\n");
 214                        return -ENODEV;
 215                }
 216        }
 217
 218        /* Print voltage and multiplier */
 219        rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
 220        current_voltage = lo & 0xff;
 221        printk(KERN_INFO "eps: Current voltage = %dmV\n",
 222                        current_voltage * 16 + 700);
 223        current_multiplier = (lo >> 8) & 0xff;
 224        printk(KERN_INFO "eps: Current multiplier = %d\n", current_multiplier);
 225
 226        /* Print limits */
 227        max_voltage = hi & 0xff;
 228        printk(KERN_INFO "eps: Highest voltage = %dmV\n",
 229                        max_voltage * 16 + 700);
 230        max_multiplier = (hi >> 8) & 0xff;
 231        printk(KERN_INFO "eps: Highest multiplier = %d\n", max_multiplier);
 232        min_voltage = (hi >> 16) & 0xff;
 233        printk(KERN_INFO "eps: Lowest voltage = %dmV\n",
 234                        min_voltage * 16 + 700);
 235        min_multiplier = (hi >> 24) & 0xff;
 236        printk(KERN_INFO "eps: Lowest multiplier = %d\n", min_multiplier);
 237
 238        /* Sanity checks */
 239        if (current_multiplier == 0 || max_multiplier == 0
 240            || min_multiplier == 0)
 241                return -EINVAL;
 242        if (current_multiplier > max_multiplier
 243            || max_multiplier <= min_multiplier)
 244                return -EINVAL;
 245        if (current_voltage > 0x1f || max_voltage > 0x1f)
 246                return -EINVAL;
 247        if (max_voltage < min_voltage)
 248                return -EINVAL;
 249
 250        /* Calc FSB speed */
 251        fsb = cpu_khz / current_multiplier;
 252        /* Calc number of p-states supported */
 253        if (brand == EPS_BRAND_C7M)
 254                states = max_multiplier - min_multiplier + 1;
 255        else
 256                states = 2;
 257
 258        /* Allocate private data and frequency table for current cpu */
 259        centaur = kzalloc(sizeof(struct eps_cpu_data)
 260                    + (states + 1) * sizeof(struct cpufreq_frequency_table),
 261                    GFP_KERNEL);
 262        if (!centaur)
 263                return -ENOMEM;
 264        eps_cpu[0] = centaur;
 265
 266        /* Copy basic values */
 267        centaur->fsb = fsb;
 268
 269        /* Fill frequency and MSR value table */
 270        f_table = &centaur->freq_table[0];
 271        if (brand != EPS_BRAND_C7M) {
 272                f_table[0].frequency = fsb * min_multiplier;
 273                f_table[0].index = (min_multiplier << 8) | min_voltage;
 274                f_table[1].frequency = fsb * max_multiplier;
 275                f_table[1].index = (max_multiplier << 8) | max_voltage;
 276                f_table[2].frequency = CPUFREQ_TABLE_END;
 277        } else {
 278                k = 0;
 279                step = ((max_voltage - min_voltage) * 256)
 280                        / (max_multiplier - min_multiplier);
 281                for (i = min_multiplier; i <= max_multiplier; i++) {
 282                        voltage = (k * step) / 256 + min_voltage;
 283                        f_table[k].frequency = fsb * i;
 284                        f_table[k].index = (i << 8) | voltage;
 285                        k++;
 286                }
 287                f_table[k].frequency = CPUFREQ_TABLE_END;
 288        }
 289
 290        policy->cpuinfo.transition_latency = 140000; /* 844mV -> 700mV in ns */
 291        policy->cur = fsb * current_multiplier;
 292
 293        ret = cpufreq_frequency_table_cpuinfo(policy, &centaur->freq_table[0]);
 294        if (ret) {
 295                kfree(centaur);
 296                return ret;
 297        }
 298
 299        cpufreq_frequency_table_get_attr(&centaur->freq_table[0], policy->cpu);
 300        return 0;
 301}
 302
 303static int eps_cpu_exit(struct cpufreq_policy *policy)
 304{
 305        unsigned int cpu = policy->cpu;
 306        struct eps_cpu_data *centaur;
 307        u32 lo, hi;
 308
 309        if (eps_cpu[cpu] == NULL)
 310                return -ENODEV;
 311        centaur = eps_cpu[cpu];
 312
 313        /* Get max frequency */
 314        rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
 315        /* Set max frequency */
 316        eps_set_state(centaur, cpu, hi & 0xffff);
 317        /* Bye */
 318        cpufreq_frequency_table_put_attr(policy->cpu);
 319        kfree(eps_cpu[cpu]);
 320        eps_cpu[cpu] = NULL;
 321        return 0;
 322}
 323
 324static struct freq_attr *eps_attr[] = {
 325        &cpufreq_freq_attr_scaling_available_freqs,
 326        NULL,
 327};
 328
 329static struct cpufreq_driver eps_driver = {
 330        .verify         = eps_verify,
 331        .target         = eps_target,
 332        .init           = eps_cpu_init,
 333        .exit           = eps_cpu_exit,
 334        .get            = eps_get,
 335        .name           = "e_powersaver",
 336        .owner          = THIS_MODULE,
 337        .attr           = eps_attr,
 338};
 339
 340static int __init eps_init(void)
 341{
 342        struct cpuinfo_x86 *c = &cpu_data(0);
 343
 344        /* This driver will work only on Centaur C7 processors with
 345         * Enhanced SpeedStep/PowerSaver registers */
 346        if (c->x86_vendor != X86_VENDOR_CENTAUR
 347            || c->x86 != 6 || c->x86_model < 10)
 348                return -ENODEV;
 349        if (!cpu_has(c, X86_FEATURE_EST))
 350                return -ENODEV;
 351
 352        if (cpufreq_register_driver(&eps_driver))
 353                return -EINVAL;
 354        return 0;
 355}
 356
 357static void __exit eps_exit(void)
 358{
 359        cpufreq_unregister_driver(&eps_driver);
 360}
 361
 362MODULE_AUTHOR("Rafal Bilski <rafalbilski@interia.pl>");
 363MODULE_DESCRIPTION("Enhanced PowerSaver driver for VIA C7 CPU's.");
 364MODULE_LICENSE("GPL");
 365
 366module_init(eps_init);
 367module_exit(eps_exit);
 368