linux/drivers/cpufreq/elanfreq.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 *      elanfreq:       cpufreq driver for the AMD ELAN family
   4 *
   5 *      (c) Copyright 2002 Robert Schwebel <r.schwebel@pengutronix.de>
   6 *
   7 *      Parts of this code are (c) Sven Geggus <sven@geggus.net>
   8 *
   9 *      All Rights Reserved.
  10 *
  11 *      2002-02-13: - initial revision for 2.4.18-pre9 by Robert Schwebel
  12 */
  13
  14#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  15
  16#include <linux/kernel.h>
  17#include <linux/module.h>
  18#include <linux/init.h>
  19
  20#include <linux/delay.h>
  21#include <linux/cpufreq.h>
  22
  23#include <asm/cpu_device_id.h>
  24#include <asm/msr.h>
  25#include <linux/timex.h>
  26#include <linux/io.h>
  27
  28#define REG_CSCIR 0x22          /* Chip Setup and Control Index Register    */
  29#define REG_CSCDR 0x23          /* Chip Setup and Control Data  Register    */
  30
  31/* Module parameter */
  32static int max_freq;
  33
  34struct s_elan_multiplier {
  35        int clock;              /* frequency in kHz                         */
  36        int val40h;             /* PMU Force Mode register                  */
  37        int val80h;             /* CPU Clock Speed Register                 */
  38};
  39
  40/*
  41 * It is important that the frequencies
  42 * are listed in ascending order here!
  43 */
  44static struct s_elan_multiplier elan_multiplier[] = {
  45        {1000,  0x02,   0x18},
  46        {2000,  0x02,   0x10},
  47        {4000,  0x02,   0x08},
  48        {8000,  0x00,   0x00},
  49        {16000, 0x00,   0x02},
  50        {33000, 0x00,   0x04},
  51        {66000, 0x01,   0x04},
  52        {99000, 0x01,   0x05}
  53};
  54
  55static struct cpufreq_frequency_table elanfreq_table[] = {
  56        {0, 0,  1000},
  57        {0, 1,  2000},
  58        {0, 2,  4000},
  59        {0, 3,  8000},
  60        {0, 4,  16000},
  61        {0, 5,  33000},
  62        {0, 6,  66000},
  63        {0, 7,  99000},
  64        {0, 0,  CPUFREQ_TABLE_END},
  65};
  66
  67
  68/**
  69 *      elanfreq_get_cpu_frequency: determine current cpu speed
  70 *
  71 *      Finds out at which frequency the CPU of the Elan SOC runs
  72 *      at the moment. Frequencies from 1 to 33 MHz are generated
  73 *      the normal way, 66 and 99 MHz are called "Hyperspeed Mode"
  74 *      and have the rest of the chip running with 33 MHz.
  75 */
  76
  77static unsigned int elanfreq_get_cpu_frequency(unsigned int cpu)
  78{
  79        u8 clockspeed_reg;    /* Clock Speed Register */
  80
  81        local_irq_disable();
  82        outb_p(0x80, REG_CSCIR);
  83        clockspeed_reg = inb_p(REG_CSCDR);
  84        local_irq_enable();
  85
  86        if ((clockspeed_reg & 0xE0) == 0xE0)
  87                return 0;
  88
  89        /* Are we in CPU clock multiplied mode (66/99 MHz)? */
  90        if ((clockspeed_reg & 0xE0) == 0xC0) {
  91                if ((clockspeed_reg & 0x01) == 0)
  92                        return 66000;
  93                else
  94                        return 99000;
  95        }
  96
  97        /* 33 MHz is not 32 MHz... */
  98        if ((clockspeed_reg & 0xE0) == 0xA0)
  99                return 33000;
 100
 101        return (1<<((clockspeed_reg & 0xE0) >> 5)) * 1000;
 102}
 103
 104
 105static int elanfreq_target(struct cpufreq_policy *policy,
 106                            unsigned int state)
 107{
 108        /*
 109         * Access to the Elan's internal registers is indexed via
 110         * 0x22: Chip Setup & Control Register Index Register (CSCI)
 111         * 0x23: Chip Setup & Control Register Data  Register (CSCD)
 112         *
 113         */
 114
 115        /*
 116         * 0x40 is the Power Management Unit's Force Mode Register.
 117         * Bit 6 enables Hyperspeed Mode (66/100 MHz core frequency)
 118         */
 119
 120        local_irq_disable();
 121        outb_p(0x40, REG_CSCIR);                /* Disable hyperspeed mode */
 122        outb_p(0x00, REG_CSCDR);
 123        local_irq_enable();             /* wait till internal pipelines and */
 124        udelay(1000);                   /* buffers have cleaned up          */
 125
 126        local_irq_disable();
 127
 128        /* now, set the CPU clock speed register (0x80) */
 129        outb_p(0x80, REG_CSCIR);
 130        outb_p(elan_multiplier[state].val80h, REG_CSCDR);
 131
 132        /* now, the hyperspeed bit in PMU Force Mode Register (0x40) */
 133        outb_p(0x40, REG_CSCIR);
 134        outb_p(elan_multiplier[state].val40h, REG_CSCDR);
 135        udelay(10000);
 136        local_irq_enable();
 137
 138        return 0;
 139}
 140/*
 141 *      Module init and exit code
 142 */
 143
 144static int elanfreq_cpu_init(struct cpufreq_policy *policy)
 145{
 146        struct cpuinfo_x86 *c = &cpu_data(0);
 147        struct cpufreq_frequency_table *pos;
 148
 149        /* capability check */
 150        if ((c->x86_vendor != X86_VENDOR_AMD) ||
 151            (c->x86 != 4) || (c->x86_model != 10))
 152                return -ENODEV;
 153
 154        /* max freq */
 155        if (!max_freq)
 156                max_freq = elanfreq_get_cpu_frequency(0);
 157
 158        /* table init */
 159        cpufreq_for_each_entry(pos, elanfreq_table)
 160                if (pos->frequency > max_freq)
 161                        pos->frequency = CPUFREQ_ENTRY_INVALID;
 162
 163        policy->freq_table = elanfreq_table;
 164        return 0;
 165}
 166
 167
 168#ifndef MODULE
 169/**
 170 * elanfreq_setup - elanfreq command line parameter parsing
 171 *
 172 * elanfreq command line parameter.  Use:
 173 *  elanfreq=66000
 174 * to set the maximum CPU frequency to 66 MHz. Note that in
 175 * case you do not give this boot parameter, the maximum
 176 * frequency will fall back to _current_ CPU frequency which
 177 * might be lower. If you build this as a module, use the
 178 * max_freq module parameter instead.
 179 */
 180static int __init elanfreq_setup(char *str)
 181{
 182        max_freq = simple_strtoul(str, &str, 0);
 183        pr_warn("You're using the deprecated elanfreq command line option. Use elanfreq.max_freq instead, please!\n");
 184        return 1;
 185}
 186__setup("elanfreq=", elanfreq_setup);
 187#endif
 188
 189
 190static struct cpufreq_driver elanfreq_driver = {
 191        .get            = elanfreq_get_cpu_frequency,
 192        .flags          = CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING,
 193        .verify         = cpufreq_generic_frequency_table_verify,
 194        .target_index   = elanfreq_target,
 195        .init           = elanfreq_cpu_init,
 196        .name           = "elanfreq",
 197        .attr           = cpufreq_generic_attr,
 198};
 199
 200static const struct x86_cpu_id elan_id[] = {
 201        X86_MATCH_VENDOR_FAM_MODEL(AMD, 4, 10, NULL),
 202        {}
 203};
 204MODULE_DEVICE_TABLE(x86cpu, elan_id);
 205
 206static int __init elanfreq_init(void)
 207{
 208        if (!x86_match_cpu(elan_id))
 209                return -ENODEV;
 210        return cpufreq_register_driver(&elanfreq_driver);
 211}
 212
 213
 214static void __exit elanfreq_exit(void)
 215{
 216        cpufreq_unregister_driver(&elanfreq_driver);
 217}
 218
 219
 220module_param(max_freq, int, 0444);
 221
 222MODULE_LICENSE("GPL");
 223MODULE_AUTHOR("Robert Schwebel <r.schwebel@pengutronix.de>, "
 224                "Sven Geggus <sven@geggus.net>");
 225MODULE_DESCRIPTION("cpufreq driver for AMD's Elan CPUs");
 226
 227module_init(elanfreq_init);
 228module_exit(elanfreq_exit);
 229