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