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        if (err)
 165                freqs.new = freqs.old;
 166
 167        cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
 168        return err;
 169}
 170
 171static int eps_target(struct cpufreq_policy *policy,
 172                               unsigned int target_freq,
 173                               unsigned int relation)
 174{
 175        struct eps_cpu_data *centaur;
 176        unsigned int newstate = 0;
 177        unsigned int cpu = policy->cpu;
 178        unsigned int dest_state;
 179        int ret;
 180
 181        if (unlikely(eps_cpu[cpu] == NULL))
 182                return -ENODEV;
 183        centaur = eps_cpu[cpu];
 184
 185        if (unlikely(cpufreq_frequency_table_target(policy,
 186                        &eps_cpu[cpu]->freq_table[0],
 187                        target_freq,
 188                        relation,
 189                        &newstate))) {
 190                return -EINVAL;
 191        }
 192
 193        /* Make frequency transition */
 194        dest_state = centaur->freq_table[newstate].driver_data & 0xffff;
 195        ret = eps_set_state(centaur, policy, dest_state);
 196        if (ret)
 197                printk(KERN_ERR "eps: Timeout!\n");
 198        return ret;
 199}
 200
 201static int eps_verify(struct cpufreq_policy *policy)
 202{
 203        return cpufreq_frequency_table_verify(policy,
 204                        &eps_cpu[policy->cpu]->freq_table[0]);
 205}
 206
 207static int eps_cpu_init(struct cpufreq_policy *policy)
 208{
 209        unsigned int i;
 210        u32 lo, hi;
 211        u64 val;
 212        u8 current_multiplier, current_voltage;
 213        u8 max_multiplier, max_voltage;
 214        u8 min_multiplier, min_voltage;
 215        u8 brand = 0;
 216        u32 fsb;
 217        struct eps_cpu_data *centaur;
 218        struct cpuinfo_x86 *c = &cpu_data(0);
 219        struct cpufreq_frequency_table *f_table;
 220        int k, step, voltage;
 221        int ret;
 222        int states;
 223#if defined CONFIG_ACPI_PROCESSOR || defined CONFIG_ACPI_PROCESSOR_MODULE
 224        unsigned int limit;
 225#endif
 226
 227        if (policy->cpu != 0)
 228                return -ENODEV;
 229
 230        /* Check brand */
 231        printk(KERN_INFO "eps: Detected VIA ");
 232
 233        switch (c->x86_model) {
 234        case 10:
 235                rdmsr(0x1153, lo, hi);
 236                brand = (((lo >> 2) ^ lo) >> 18) & 3;
 237                printk(KERN_CONT "Model A ");
 238                break;
 239        case 13:
 240                rdmsr(0x1154, lo, hi);
 241                brand = (((lo >> 4) ^ (lo >> 2))) & 0x000000ff;
 242                printk(KERN_CONT "Model D ");
 243                break;
 244        }
 245
 246        switch (brand) {
 247        case EPS_BRAND_C7M:
 248                printk(KERN_CONT "C7-M\n");
 249                break;
 250        case EPS_BRAND_C7:
 251                printk(KERN_CONT "C7\n");
 252                break;
 253        case EPS_BRAND_EDEN:
 254                printk(KERN_CONT "Eden\n");
 255                break;
 256        case EPS_BRAND_C7D:
 257                printk(KERN_CONT "C7-D\n");
 258                break;
 259        case EPS_BRAND_C3:
 260                printk(KERN_CONT "C3\n");
 261                return -ENODEV;
 262                break;
 263        }
 264        /* Enable Enhanced PowerSaver */
 265        rdmsrl(MSR_IA32_MISC_ENABLE, val);
 266        if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
 267                val |= MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP;
 268                wrmsrl(MSR_IA32_MISC_ENABLE, val);
 269                /* Can be locked at 0 */
 270                rdmsrl(MSR_IA32_MISC_ENABLE, val);
 271                if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
 272                        printk(KERN_INFO "eps: Can't enable Enhanced PowerSaver\n");
 273                        return -ENODEV;
 274                }
 275        }
 276
 277        /* Print voltage and multiplier */
 278        rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
 279        current_voltage = lo & 0xff;
 280        printk(KERN_INFO "eps: Current voltage = %dmV\n",
 281                        current_voltage * 16 + 700);
 282        current_multiplier = (lo >> 8) & 0xff;
 283        printk(KERN_INFO "eps: Current multiplier = %d\n", current_multiplier);
 284
 285        /* Print limits */
 286        max_voltage = hi & 0xff;
 287        printk(KERN_INFO "eps: Highest voltage = %dmV\n",
 288                        max_voltage * 16 + 700);
 289        max_multiplier = (hi >> 8) & 0xff;
 290        printk(KERN_INFO "eps: Highest multiplier = %d\n", max_multiplier);
 291        min_voltage = (hi >> 16) & 0xff;
 292        printk(KERN_INFO "eps: Lowest voltage = %dmV\n",
 293                        min_voltage * 16 + 700);
 294        min_multiplier = (hi >> 24) & 0xff;
 295        printk(KERN_INFO "eps: Lowest multiplier = %d\n", min_multiplier);
 296
 297        /* Sanity checks */
 298        if (current_multiplier == 0 || max_multiplier == 0
 299            || min_multiplier == 0)
 300                return -EINVAL;
 301        if (current_multiplier > max_multiplier
 302            || max_multiplier <= min_multiplier)
 303                return -EINVAL;
 304        if (current_voltage > 0x1f || max_voltage > 0x1f)
 305                return -EINVAL;
 306        if (max_voltage < min_voltage
 307            || current_voltage < min_voltage
 308            || current_voltage > max_voltage)
 309                return -EINVAL;
 310
 311        /* Check for systems using underclocked CPU */
 312        if (!freq_failsafe_off && max_multiplier != current_multiplier) {
 313                printk(KERN_INFO "eps: Your processor is running at different "
 314                        "frequency then its maximum. Aborting.\n");
 315                printk(KERN_INFO "eps: You can use freq_failsafe_off option "
 316                        "to disable this check.\n");
 317                return -EINVAL;
 318        }
 319        if (!voltage_failsafe_off && max_voltage != current_voltage) {
 320                printk(KERN_INFO "eps: Your processor is running at different "
 321                        "voltage then its maximum. Aborting.\n");
 322                printk(KERN_INFO "eps: You can use voltage_failsafe_off "
 323                        "option to disable this check.\n");
 324                return -EINVAL;
 325        }
 326
 327        /* Calc FSB speed */
 328        fsb = cpu_khz / current_multiplier;
 329
 330#if defined CONFIG_ACPI_PROCESSOR || defined CONFIG_ACPI_PROCESSOR_MODULE
 331        /* Check for ACPI processor speed limit */
 332        if (!ignore_acpi_limit && !eps_acpi_init()) {
 333                if (!acpi_processor_get_bios_limit(policy->cpu, &limit)) {
 334                        printk(KERN_INFO "eps: ACPI limit %u.%uGHz\n",
 335                                limit/1000000,
 336                                (limit%1000000)/10000);
 337                        eps_acpi_exit(policy);
 338                        /* Check if max_multiplier is in BIOS limits */
 339                        if (limit && max_multiplier * fsb > limit) {
 340                                printk(KERN_INFO "eps: Aborting.\n");
 341                                return -EINVAL;
 342                        }
 343                }
 344        }
 345#endif
 346
 347        /* Allow user to set lower maximum voltage then that reported
 348         * by processor */
 349        if (brand == EPS_BRAND_C7M && set_max_voltage) {
 350                u32 v;
 351
 352                /* Change mV to something hardware can use */
 353                v = (set_max_voltage - 700) / 16;
 354                /* Check if voltage is within limits */
 355                if (v >= min_voltage && v <= max_voltage) {
 356                        printk(KERN_INFO "eps: Setting %dmV as maximum.\n",
 357                                v * 16 + 700);
 358                        max_voltage = v;
 359                }
 360        }
 361
 362        /* Calc number of p-states supported */
 363        if (brand == EPS_BRAND_C7M)
 364                states = max_multiplier - min_multiplier + 1;
 365        else
 366                states = 2;
 367
 368        /* Allocate private data and frequency table for current cpu */
 369        centaur = kzalloc(sizeof(struct eps_cpu_data)
 370                    + (states + 1) * sizeof(struct cpufreq_frequency_table),
 371                    GFP_KERNEL);
 372        if (!centaur)
 373                return -ENOMEM;
 374        eps_cpu[0] = centaur;
 375
 376        /* Copy basic values */
 377        centaur->fsb = fsb;
 378#if defined CONFIG_ACPI_PROCESSOR || defined CONFIG_ACPI_PROCESSOR_MODULE
 379        centaur->bios_limit = limit;
 380#endif
 381
 382        /* Fill frequency and MSR value table */
 383        f_table = &centaur->freq_table[0];
 384        if (brand != EPS_BRAND_C7M) {
 385                f_table[0].frequency = fsb * min_multiplier;
 386                f_table[0].driver_data = (min_multiplier << 8) | min_voltage;
 387                f_table[1].frequency = fsb * max_multiplier;
 388                f_table[1].driver_data = (max_multiplier << 8) | max_voltage;
 389                f_table[2].frequency = CPUFREQ_TABLE_END;
 390        } else {
 391                k = 0;
 392                step = ((max_voltage - min_voltage) * 256)
 393                        / (max_multiplier - min_multiplier);
 394                for (i = min_multiplier; i <= max_multiplier; i++) {
 395                        voltage = (k * step) / 256 + min_voltage;
 396                        f_table[k].frequency = fsb * i;
 397                        f_table[k].driver_data = (i << 8) | voltage;
 398                        k++;
 399                }
 400                f_table[k].frequency = CPUFREQ_TABLE_END;
 401        }
 402
 403        policy->cpuinfo.transition_latency = 140000; /* 844mV -> 700mV in ns */
 404        policy->cur = fsb * current_multiplier;
 405
 406        ret = cpufreq_frequency_table_cpuinfo(policy, &centaur->freq_table[0]);
 407        if (ret) {
 408                kfree(centaur);
 409                return ret;
 410        }
 411
 412        cpufreq_frequency_table_get_attr(&centaur->freq_table[0], policy->cpu);
 413        return 0;
 414}
 415
 416static int eps_cpu_exit(struct cpufreq_policy *policy)
 417{
 418        unsigned int cpu = policy->cpu;
 419
 420        /* Bye */
 421        cpufreq_frequency_table_put_attr(policy->cpu);
 422        kfree(eps_cpu[cpu]);
 423        eps_cpu[cpu] = NULL;
 424        return 0;
 425}
 426
 427static struct freq_attr *eps_attr[] = {
 428        &cpufreq_freq_attr_scaling_available_freqs,
 429        NULL,
 430};
 431
 432static struct cpufreq_driver eps_driver = {
 433        .verify         = eps_verify,
 434        .target         = eps_target,
 435        .init           = eps_cpu_init,
 436        .exit           = eps_cpu_exit,
 437        .get            = eps_get,
 438        .name           = "e_powersaver",
 439        .owner          = THIS_MODULE,
 440        .attr           = eps_attr,
 441};
 442
 443
 444/* This driver will work only on Centaur C7 processors with
 445 * Enhanced SpeedStep/PowerSaver registers */
 446static const struct x86_cpu_id eps_cpu_id[] = {
 447        { X86_VENDOR_CENTAUR, 6, X86_MODEL_ANY, X86_FEATURE_EST },
 448        {}
 449};
 450MODULE_DEVICE_TABLE(x86cpu, eps_cpu_id);
 451
 452static int __init eps_init(void)
 453{
 454        if (!x86_match_cpu(eps_cpu_id) || boot_cpu_data.x86_model < 10)
 455                return -ENODEV;
 456        if (cpufreq_register_driver(&eps_driver))
 457                return -EINVAL;
 458        return 0;
 459}
 460
 461static void __exit eps_exit(void)
 462{
 463        cpufreq_unregister_driver(&eps_driver);
 464}
 465
 466/* Allow user to overclock his machine or to change frequency to higher after
 467 * unloading module */
 468module_param(freq_failsafe_off, int, 0644);
 469MODULE_PARM_DESC(freq_failsafe_off, "Disable current vs max frequency check");
 470module_param(voltage_failsafe_off, int, 0644);
 471MODULE_PARM_DESC(voltage_failsafe_off, "Disable current vs max voltage check");
 472#if defined CONFIG_ACPI_PROCESSOR || defined CONFIG_ACPI_PROCESSOR_MODULE
 473module_param(ignore_acpi_limit, int, 0644);
 474MODULE_PARM_DESC(ignore_acpi_limit, "Don't check ACPI's processor speed limit");
 475#endif
 476module_param(set_max_voltage, int, 0644);
 477MODULE_PARM_DESC(set_max_voltage, "Set maximum CPU voltage (mV) C7-M only");
 478
 479MODULE_AUTHOR("Rafal Bilski <rafalbilski@interia.pl>");
 480MODULE_DESCRIPTION("Enhanced PowerSaver driver for VIA C7 CPU's.");
 481MODULE_LICENSE("GPL");
 482
 483module_init(eps_init);
 484module_exit(eps_exit);
 485
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.