linux/drivers/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/cpu_device_id.h>
  20#include <asm/msr.h>
  21#include <asm/tsc.h>
  22
  23#if defined CONFIG_ACPI_PROCESSOR || defined CONFIG_ACPI_PROCESSOR_MODULE
  24#include <linux/acpi.h>
  25#include <acpi/processor.h>
  26#endif
  27
  28#define EPS_BRAND_C7M   0
  29#define EPS_BRAND_C7    1
  30#define EPS_BRAND_EDEN  2
  31#define EPS_BRAND_C3    3
  32#define EPS_BRAND_C7D   4
  33
  34struct eps_cpu_data {
  35        u32 fsb;
  36#if defined CONFIG_ACPI_PROCESSOR || defined CONFIG_ACPI_PROCESSOR_MODULE
  37        u32 bios_limit;
  38#endif
  39        struct cpufreq_frequency_table freq_table[];
  40};
  41
  42static struct eps_cpu_data *eps_cpu[NR_CPUS];
  43
  44/* Module parameters */
  45static int freq_failsafe_off;
  46static int voltage_failsafe_off;
  47static int set_max_voltage;
  48
  49#if defined CONFIG_ACPI_PROCESSOR || defined CONFIG_ACPI_PROCESSOR_MODULE
  50static int ignore_acpi_limit;
  51
  52static struct acpi_processor_performance *eps_acpi_cpu_perf;
  53
  54/* Minimum necessary to get acpi_processor_get_bios_limit() working */
  55static int eps_acpi_init(void)
  56{
  57        eps_acpi_cpu_perf = kzalloc(sizeof(struct acpi_processor_performance),
  58                                      GFP_KERNEL);
  59        if (!eps_acpi_cpu_perf)
  60                return -ENOMEM;
  61
  62        if (!zalloc_cpumask_var(&eps_acpi_cpu_perf->shared_cpu_map,
  63                                                                GFP_KERNEL)) {
  64                kfree(eps_acpi_cpu_perf);
  65                eps_acpi_cpu_perf = NULL;
  66                return -ENOMEM;
  67        }
  68
  69        if (acpi_processor_register_performance(eps_acpi_cpu_perf, 0)) {
  70                free_cpumask_var(eps_acpi_cpu_perf->shared_cpu_map);
  71                kfree(eps_acpi_cpu_perf);
  72                eps_acpi_cpu_perf = NULL;
  73                return -EIO;
  74        }
  75        return 0;
  76}
  77
  78static int eps_acpi_exit(struct cpufreq_policy *policy)
  79{
  80        if (eps_acpi_cpu_perf) {
  81                acpi_processor_unregister_performance(eps_acpi_cpu_perf, 0);
  82                free_cpumask_var(eps_acpi_cpu_perf->shared_cpu_map);
  83                kfree(eps_acpi_cpu_perf);
  84                eps_acpi_cpu_perf = NULL;
  85        }
  86        return 0;
  87}
  88#endif
  89
  90static unsigned int eps_get(unsigned int cpu)
  91{
  92        struct eps_cpu_data *centaur;
  93        u32 lo, hi;
  94
  95        if (cpu)
  96                return 0;
  97        centaur = eps_cpu[cpu];
  98        if (centaur == NULL)
  99                return 0;
 100
 101        /* Return current frequency */
 102        rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
 103        return centaur->fsb * ((lo >> 8) & 0xff);
 104}
 105
 106static int eps_set_state(struct eps_cpu_data *centaur,
 107                         struct cpufreq_policy *policy,
 108                         u32 dest_state)
 109{
 110        struct cpufreq_freqs freqs;
 111        u32 lo, hi;
 112        int err = 0;
 113        int i;
 114
 115        freqs.old = eps_get(policy->cpu);
 116        freqs.new = centaur->fsb * ((dest_state >> 8) & 0xff);
 117        cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
 118
 119        /* Wait while CPU is busy */
 120        rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
 121        i = 0;
 122        while (lo & ((1 << 16) | (1 << 17))) {
 123                udelay(16);
 124                rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
 125                i++;
 126                if (unlikely(i > 64)) {
 127                        err = -ENODEV;
 128                        goto postchange;
 129                }
 130        }
 131        /* Set new multiplier and voltage */
 132        wrmsr(MSR_IA32_PERF_CTL, dest_state & 0xffff, 0);
 133        /* Wait until transition end */
 134        i = 0;
 135        do {
 136                udelay(16);
 137                rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
 138                i++;
 139                if (unlikely(i > 64)) {
 140                        err = -ENODEV;
 141                        goto postchange;
 142                }
 143        } while (lo & ((1 << 16) | (1 << 17)));
 144
 145        /* Return current frequency */
 146postchange:
 147        rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
 148        freqs.new = centaur->fsb * ((lo >> 8) & 0xff);
 149
 150#ifdef DEBUG
 151        {
 152        u8 current_multiplier, current_voltage;
 153
 154        /* Print voltage and multiplier */
 155        rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
 156        current_voltage = lo & 0xff;
 157        printk(KERN_INFO "eps: Current voltage = %dmV\n",
 158                current_voltage * 16 + 700);
 159        current_multiplier = (lo >> 8) & 0xff;
 160        printk(KERN_INFO "eps: Current multiplier = %d\n",
 161                current_multiplier);
 162        }
 163#endif
 164        cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
 165        return err;
 166}
 167
 168static int eps_target(struct cpufreq_policy *policy,
 169                               unsigned int target_freq,
 170                               unsigned int relation)
 171{
 172        struct eps_cpu_data *centaur;
 173        unsigned int newstate = 0;
 174        unsigned int cpu = policy->cpu;
 175        unsigned int dest_state;
 176        int ret;
 177
 178        if (unlikely(eps_cpu[cpu] == NULL))
 179                return -ENODEV;
 180        centaur = eps_cpu[cpu];
 181
 182        if (unlikely(cpufreq_frequency_table_target(policy,
 183                        &eps_cpu[cpu]->freq_table[0],
 184                        target_freq,
 185                        relation,
 186                        &newstate))) {
 187                return -EINVAL;
 188        }
 189
 190        /* Make frequency transition */
 191        dest_state = centaur->freq_table[newstate].index & 0xffff;
 192        ret = eps_set_state(centaur, policy, dest_state);
 193        if (ret)
 194                printk(KERN_ERR "eps: Timeout!\n");
 195        return ret;
 196}
 197
 198static int eps_verify(struct cpufreq_policy *policy)
 199{
 200        return cpufreq_frequency_table_verify(policy,
 201                        &eps_cpu[policy->cpu]->freq_table[0]);
 202}
 203
 204static int eps_cpu_init(struct cpufreq_policy *policy)
 205{
 206        unsigned int i;
 207        u32 lo, hi;
 208        u64 val;
 209        u8 current_multiplier, current_voltage;
 210        u8 max_multiplier, max_voltage;
 211        u8 min_multiplier, min_voltage;
 212        u8 brand = 0;
 213        u32 fsb;
 214        struct eps_cpu_data *centaur;
 215        struct cpuinfo_x86 *c = &cpu_data(0);
 216        struct cpufreq_frequency_table *f_table;
 217        int k, step, voltage;
 218        int ret;
 219        int states;
 220#if defined CONFIG_ACPI_PROCESSOR || defined CONFIG_ACPI_PROCESSOR_MODULE
 221        unsigned int limit;
 222#endif
 223
 224        if (policy->cpu != 0)
 225                return -ENODEV;
 226
 227        /* Check brand */
 228        printk(KERN_INFO "eps: Detected VIA ");
 229
 230        switch (c->x86_model) {
 231        case 10:
 232                rdmsr(0x1153, lo, hi);
 233                brand = (((lo >> 2) ^ lo) >> 18) & 3;
 234                printk(KERN_CONT "Model A ");
 235                break;
 236        case 13:
 237                rdmsr(0x1154, lo, hi);
 238                brand = (((lo >> 4) ^ (lo >> 2))) & 0x000000ff;
 239                printk(KERN_CONT "Model D ");
 240                break;
 241        }
 242
 243        switch (brand) {
 244        case EPS_BRAND_C7M:
 245                printk(KERN_CONT "C7-M\n");
 246                break;
 247        case EPS_BRAND_C7:
 248                printk(KERN_CONT "C7\n");
 249                break;
 250        case EPS_BRAND_EDEN:
 251                printk(KERN_CONT "Eden\n");
 252                break;
 253        case EPS_BRAND_C7D:
 254                printk(KERN_CONT "C7-D\n");
 255                break;
 256        case EPS_BRAND_C3:
 257                printk(KERN_CONT "C3\n");
 258                return -ENODEV;
 259                break;
 260        }
 261        /* Enable Enhanced PowerSaver */
 262        rdmsrl(MSR_IA32_MISC_ENABLE, val);
 263        if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
 264                val |= MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP;
 265                wrmsrl(MSR_IA32_MISC_ENABLE, val);
 266                /* Can be locked at 0 */
 267                rdmsrl(MSR_IA32_MISC_ENABLE, val);
 268                if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
 269                        printk(KERN_INFO "eps: Can't enable Enhanced PowerSaver\n");
 270                        return -ENODEV;
 271                }
 272        }
 273
 274        /* Print voltage and multiplier */
 275        rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
 276        current_voltage = lo & 0xff;
 277        printk(KERN_INFO "eps: Current voltage = %dmV\n",
 278                        current_voltage * 16 + 700);
 279        current_multiplier = (lo >> 8) & 0xff;
 280        printk(KERN_INFO "eps: Current multiplier = %d\n", current_multiplier);
 281
 282        /* Print limits */
 283        max_voltage = hi & 0xff;
 284        printk(KERN_INFO "eps: Highest voltage = %dmV\n",
 285                        max_voltage * 16 + 700);
 286        max_multiplier = (hi >> 8) & 0xff;
 287        printk(KERN_INFO "eps: Highest multiplier = %d\n", max_multiplier);
 288        min_voltage = (hi >> 16) & 0xff;
 289        printk(KERN_INFO "eps: Lowest voltage = %dmV\n",
 290                        min_voltage * 16 + 700);
 291        min_multiplier = (hi >> 24) & 0xff;
 292        printk(KERN_INFO "eps: Lowest multiplier = %d\n", min_multiplier);
 293
 294        /* Sanity checks */
 295        if (current_multiplier == 0 || max_multiplier == 0
 296            || min_multiplier == 0)
 297                return -EINVAL;
 298        if (current_multiplier > max_multiplier
 299            || max_multiplier <= min_multiplier)
 300                return -EINVAL;
 301        if (current_voltage > 0x1f || max_voltage > 0x1f)
 302                return -EINVAL;
 303        if (max_voltage < min_voltage
 304            || current_voltage < min_voltage
 305            || current_voltage > max_voltage)
 306                return -EINVAL;
 307
 308        /* Check for systems using underclocked CPU */
 309        if (!freq_failsafe_off && max_multiplier != current_multiplier) {
 310                printk(KERN_INFO "eps: Your processor is running at different "
 311                        "frequency then its maximum. Aborting.\n");
 312                printk(KERN_INFO "eps: You can use freq_failsafe_off option "
 313                        "to disable this check.\n");
 314                return -EINVAL;
 315        }
 316        if (!voltage_failsafe_off && max_voltage != current_voltage) {
 317                printk(KERN_INFO "eps: Your processor is running at different "
 318                        "voltage then its maximum. Aborting.\n");
 319                printk(KERN_INFO "eps: You can use voltage_failsafe_off "
 320                        "option to disable this check.\n");
 321                return -EINVAL;
 322        }
 323
 324        /* Calc FSB speed */
 325        fsb = cpu_khz / current_multiplier;
 326
 327#if defined CONFIG_ACPI_PROCESSOR || defined CONFIG_ACPI_PROCESSOR_MODULE
 328        /* Check for ACPI processor speed limit */
 329        if (!ignore_acpi_limit && !eps_acpi_init()) {
 330                if (!acpi_processor_get_bios_limit(policy->cpu, &limit)) {
 331                        printk(KERN_INFO "eps: ACPI limit %u.%uGHz\n",
 332                                limit/1000000,
 333                                (limit%1000000)/10000);
 334                        eps_acpi_exit(policy);
 335                        /* Check if max_multiplier is in BIOS limits */
 336                        if (limit && max_multiplier * fsb > limit) {
 337                                printk(KERN_INFO "eps: Aborting.\n");
 338                                return -EINVAL;
 339                        }
 340                }
 341        }
 342#endif
 343
 344        /* Allow user to set lower maximum voltage then that reported
 345         * by processor */
 346        if (brand == EPS_BRAND_C7M && set_max_voltage) {
 347                u32 v;
 348
 349                /* Change mV to something hardware can use */
 350                v = (set_max_voltage - 700) / 16;
 351                /* Check if voltage is within limits */
 352                if (v >= min_voltage && v <= max_voltage) {
 353                        printk(KERN_INFO "eps: Setting %dmV as maximum.\n",
 354                                v * 16 + 700);
 355                        max_voltage = v;
 356                }
 357        }
 358
 359        /* Calc number of p-states supported */
 360        if (brand == EPS_BRAND_C7M)
 361                states = max_multiplier - min_multiplier + 1;
 362        else
 363                states = 2;
 364
 365        /* Allocate private data and frequency table for current cpu */
 366        centaur = kzalloc(sizeof(struct eps_cpu_data)
 367                    + (states + 1) * sizeof(struct cpufreq_frequency_table),
 368                    GFP_KERNEL);
 369        if (!centaur)
 370                return -ENOMEM;
 371        eps_cpu[0] = centaur;
 372
 373        /* Copy basic values */
 374        centaur->fsb = fsb;
 375#if defined CONFIG_ACPI_PROCESSOR || defined CONFIG_ACPI_PROCESSOR_MODULE
 376        centaur->bios_limit = limit;
 377#endif
 378
 379        /* Fill frequency and MSR value table */
 380        f_table = &centaur->freq_table[0];
 381        if (brand != EPS_BRAND_C7M) {
 382                f_table[0].frequency = fsb * min_multiplier;
 383                f_table[0].index = (min_multiplier << 8) | min_voltage;
 384                f_table[1].frequency = fsb * max_multiplier;
 385                f_table[1].index = (max_multiplier << 8) | max_voltage;
 386                f_table[2].frequency = CPUFREQ_TABLE_END;
 387        } else {
 388                k = 0;
 389                step = ((max_voltage - min_voltage) * 256)
 390                        / (max_multiplier - min_multiplier);
 391                for (i = min_multiplier; i <= max_multiplier; i++) {
 392                        voltage = (k * step) / 256 + min_voltage;
 393                        f_table[k].frequency = fsb * i;
 394                        f_table[k].index = (i << 8) | voltage;
 395                        k++;
 396                }
 397                f_table[k].frequency = CPUFREQ_TABLE_END;
 398        }
 399
 400        policy->cpuinfo.transition_latency = 140000; /* 844mV -> 700mV in ns */
 401        policy->cur = fsb * current_multiplier;
 402
 403        ret = cpufreq_frequency_table_cpuinfo(policy, &centaur->freq_table[0]);
 404        if (ret) {
 405                kfree(centaur);
 406                return ret;
 407        }
 408
 409        cpufreq_frequency_table_get_attr(&centaur->freq_table[0], policy->cpu);
 410        return 0;
 411}
 412
 413static int eps_cpu_exit(struct cpufreq_policy *policy)
 414{
 415        unsigned int cpu = policy->cpu;
 416
 417        /* Bye */
 418        cpufreq_frequency_table_put_attr(policy->cpu);
 419        kfree(eps_cpu[cpu]);
 420        eps_cpu[cpu] = NULL;
 421        return 0;
 422}
 423
 424static struct freq_attr *eps_attr[] = {
 425        &cpufreq_freq_attr_scaling_available_freqs,
 426        NULL,
 427};
 428
 429static struct cpufreq_driver eps_driver = {
 430        .verify         = eps_verify,
 431        .target         = eps_target,
 432        .init           = eps_cpu_init,
 433        .exit           = eps_cpu_exit,
 434        .get            = eps_get,
 435        .name           = "e_powersaver",
 436        .owner          = THIS_MODULE,
 437        .attr           = eps_attr,
 438};
 439
 440
 441/* This driver will work only on Centaur C7 processors with
 442 * Enhanced SpeedStep/PowerSaver registers */
 443static const struct x86_cpu_id eps_cpu_id[] = {
 444        { X86_VENDOR_CENTAUR, 6, X86_MODEL_ANY, X86_FEATURE_EST },
 445        {}
 446};
 447MODULE_DEVICE_TABLE(x86cpu, eps_cpu_id);
 448
 449static int __init eps_init(void)
 450{
 451        if (!x86_match_cpu(eps_cpu_id) || boot_cpu_data.x86_model < 10)
 452                return -ENODEV;
 453        if (cpufreq_register_driver(&eps_driver))
 454                return -EINVAL;
 455        return 0;
 456}
 457
 458static void __exit eps_exit(void)
 459{
 460        cpufreq_unregister_driver(&eps_driver);
 461}
 462
 463/* Allow user to overclock his machine or to change frequency to higher after
 464 * unloading module */
 465module_param(freq_failsafe_off, int, 0644);
 466MODULE_PARM_DESC(freq_failsafe_off, "Disable current vs max frequency check");
 467module_param(voltage_failsafe_off, int, 0644);
 468MODULE_PARM_DESC(voltage_failsafe_off, "Disable current vs max voltage check");
 469#if defined CONFIG_ACPI_PROCESSOR || defined CONFIG_ACPI_PROCESSOR_MODULE
 470module_param(ignore_acpi_limit, int, 0644);
 471MODULE_PARM_DESC(ignore_acpi_limit, "Don't check ACPI's processor speed limit");
 472#endif
 473module_param(set_max_voltage, int, 0644);
 474MODULE_PARM_DESC(set_max_voltage, "Set maximum CPU voltage (mV) C7-M only");
 475
 476MODULE_AUTHOR("Rafal Bilski <rafalbilski@interia.pl>");
 477MODULE_DESCRIPTION("Enhanced PowerSaver driver for VIA C7 CPU's.");
 478MODULE_LICENSE("GPL");
 479
 480module_init(eps_init);
 481module_exit(eps_exit);
 482
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.