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        }
 227        /* Enable Enhanced PowerSaver */
 228        rdmsrl(MSR_IA32_MISC_ENABLE, val);
 229        if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
 230                val |= MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP;
 231                wrmsrl(MSR_IA32_MISC_ENABLE, val);
 232                /* Can be locked at 0 */
 233                rdmsrl(MSR_IA32_MISC_ENABLE, val);
 234                if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
 235                        pr_info("Can't enable Enhanced PowerSaver\n");
 236                        return -ENODEV;
 237                }
 238        }
 239
 240        /* Print voltage and multiplier */
 241        rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
 242        current_voltage = lo & 0xff;
 243        pr_info("Current voltage = %dmV\n", current_voltage * 16 + 700);
 244        current_multiplier = (lo >> 8) & 0xff;
 245        pr_info("Current multiplier = %d\n", current_multiplier);
 246
 247        /* Print limits */
 248        max_voltage = hi & 0xff;
 249        pr_info("Highest voltage = %dmV\n", max_voltage * 16 + 700);
 250        max_multiplier = (hi >> 8) & 0xff;
 251        pr_info("Highest multiplier = %d\n", max_multiplier);
 252        min_voltage = (hi >> 16) & 0xff;
 253        pr_info("Lowest voltage = %dmV\n", min_voltage * 16 + 700);
 254        min_multiplier = (hi >> 24) & 0xff;
 255        pr_info("Lowest multiplier = %d\n", min_multiplier);
 256
 257        /* Sanity checks */
 258        if (current_multiplier == 0 || max_multiplier == 0
 259            || min_multiplier == 0)
 260                return -EINVAL;
 261        if (current_multiplier > max_multiplier
 262            || max_multiplier <= min_multiplier)
 263                return -EINVAL;
 264        if (current_voltage > 0x1f || max_voltage > 0x1f)
 265                return -EINVAL;
 266        if (max_voltage < min_voltage
 267            || current_voltage < min_voltage
 268            || current_voltage > max_voltage)
 269                return -EINVAL;
 270
 271        /* Check for systems using underclocked CPU */
 272        if (!freq_failsafe_off && max_multiplier != current_multiplier) {
 273                pr_info("Your processor is running at different frequency then its maximum. Aborting.\n");
 274                pr_info("You can use freq_failsafe_off option to disable this check.\n");
 275                return -EINVAL;
 276        }
 277        if (!voltage_failsafe_off && max_voltage != current_voltage) {
 278                pr_info("Your processor is running at different voltage then its maximum. Aborting.\n");
 279                pr_info("You can use voltage_failsafe_off option to disable this check.\n");
 280                return -EINVAL;
 281        }
 282
 283        /* Calc FSB speed */
 284        fsb = cpu_khz / current_multiplier;
 285
 286#if IS_ENABLED(CONFIG_ACPI_PROCESSOR)
 287        /* Check for ACPI processor speed limit */
 288        if (!ignore_acpi_limit && !eps_acpi_init()) {
 289                if (!acpi_processor_get_bios_limit(policy->cpu, &limit)) {
 290                        pr_info("ACPI limit %u.%uGHz\n",
 291                                limit/1000000,
 292                                (limit%1000000)/10000);
 293                        eps_acpi_exit(policy);
 294                        /* Check if max_multiplier is in BIOS limits */
 295                        if (limit && max_multiplier * fsb > limit) {
 296                                pr_info("Aborting\n");
 297                                return -EINVAL;
 298                        }
 299                }
 300        }
 301#endif
 302
 303        /* Allow user to set lower maximum voltage then that reported
 304         * by processor */
 305        if (brand == EPS_BRAND_C7M && set_max_voltage) {
 306                u32 v;
 307
 308                /* Change mV to something hardware can use */
 309                v = (set_max_voltage - 700) / 16;
 310                /* Check if voltage is within limits */
 311                if (v >= min_voltage && v <= max_voltage) {
 312                        pr_info("Setting %dmV as maximum\n", v * 16 + 700);
 313                        max_voltage = v;
 314                }
 315        }
 316
 317        /* Calc number of p-states supported */
 318        if (brand == EPS_BRAND_C7M)
 319                states = max_multiplier - min_multiplier + 1;
 320        else
 321                states = 2;
 322
 323        /* Allocate private data and frequency table for current cpu */
 324        centaur = kzalloc(struct_size(centaur, freq_table, states + 1),
 325                          GFP_KERNEL);
 326        if (!centaur)
 327                return -ENOMEM;
 328        eps_cpu[0] = centaur;
 329
 330        /* Copy basic values */
 331        centaur->fsb = fsb;
 332#if IS_ENABLED(CONFIG_ACPI_PROCESSOR)
 333        centaur->bios_limit = limit;
 334#endif
 335
 336        /* Fill frequency and MSR value table */
 337        f_table = &centaur->freq_table[0];
 338        if (brand != EPS_BRAND_C7M) {
 339                f_table[0].frequency = fsb * min_multiplier;
 340                f_table[0].driver_data = (min_multiplier << 8) | min_voltage;
 341                f_table[1].frequency = fsb * max_multiplier;
 342                f_table[1].driver_data = (max_multiplier << 8) | max_voltage;
 343                f_table[2].frequency = CPUFREQ_TABLE_END;
 344        } else {
 345                k = 0;
 346                step = ((max_voltage - min_voltage) * 256)
 347                        / (max_multiplier - min_multiplier);
 348                for (i = min_multiplier; i <= max_multiplier; i++) {
 349                        voltage = (k * step) / 256 + min_voltage;
 350                        f_table[k].frequency = fsb * i;
 351                        f_table[k].driver_data = (i << 8) | voltage;
 352                        k++;
 353                }
 354                f_table[k].frequency = CPUFREQ_TABLE_END;
 355        }
 356
 357        policy->cpuinfo.transition_latency = 140000; /* 844mV -> 700mV in ns */
 358        policy->freq_table = &centaur->freq_table[0];
 359
 360        return 0;
 361}
 362
 363static int eps_cpu_exit(struct cpufreq_policy *policy)
 364{
 365        unsigned int cpu = policy->cpu;
 366
 367        /* Bye */
 368        kfree(eps_cpu[cpu]);
 369        eps_cpu[cpu] = NULL;
 370        return 0;
 371}
 372
 373static struct cpufreq_driver eps_driver = {
 374        .verify         = cpufreq_generic_frequency_table_verify,
 375        .target_index   = eps_target,
 376        .init           = eps_cpu_init,
 377        .exit           = eps_cpu_exit,
 378        .get            = eps_get,
 379        .name           = "e_powersaver",
 380        .attr           = cpufreq_generic_attr,
 381};
 382
 383
 384/* This driver will work only on Centaur C7 processors with
 385 * Enhanced SpeedStep/PowerSaver registers */
 386static const struct x86_cpu_id eps_cpu_id[] = {
 387        X86_MATCH_VENDOR_FAM_FEATURE(CENTAUR, 6, X86_FEATURE_EST, NULL),
 388        {}
 389};
 390MODULE_DEVICE_TABLE(x86cpu, eps_cpu_id);
 391
 392static int __init eps_init(void)
 393{
 394        if (!x86_match_cpu(eps_cpu_id) || boot_cpu_data.x86_model < 10)
 395                return -ENODEV;
 396        if (cpufreq_register_driver(&eps_driver))
 397                return -EINVAL;
 398        return 0;
 399}
 400
 401static void __exit eps_exit(void)
 402{
 403        cpufreq_unregister_driver(&eps_driver);
 404}
 405
 406/* Allow user to overclock his machine or to change frequency to higher after
 407 * unloading module */
 408module_param(freq_failsafe_off, int, 0644);
 409MODULE_PARM_DESC(freq_failsafe_off, "Disable current vs max frequency check");
 410module_param(voltage_failsafe_off, int, 0644);
 411MODULE_PARM_DESC(voltage_failsafe_off, "Disable current vs max voltage check");
 412#if IS_ENABLED(CONFIG_ACPI_PROCESSOR)
 413module_param(ignore_acpi_limit, int, 0644);
 414MODULE_PARM_DESC(ignore_acpi_limit, "Don't check ACPI's processor speed limit");
 415#endif
 416module_param(set_max_voltage, int, 0644);
 417MODULE_PARM_DESC(set_max_voltage, "Set maximum CPU voltage (mV) C7-M only");
 418
 419MODULE_AUTHOR("Rafal Bilski <rafalbilski@interia.pl>");
 420MODULE_DESCRIPTION("Enhanced PowerSaver driver for VIA C7 CPU's.");
 421MODULE_LICENSE("GPL");
 422
 423module_init(eps_init);
 424module_exit(eps_exit);
 425