linux/drivers/cpufreq/gx-suspmod.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *      Cyrix MediaGX and NatSemi Geode Suspend Modulation
   4 *      (C) 2002 Zwane Mwaikambo <zwane@commfireservices.com>
   5 *      (C) 2002 Hiroshi Miura   <miura@da-cha.org>
   6 *      All Rights Reserved
   7 *
   8 *      The author(s) of this software shall not be held liable for damages
   9 *      of any nature resulting due to the use of this software. This
  10 *      software is provided AS-IS with no warranties.
  11 *
  12 * Theoretical note:
  13 *
  14 *      (see Geode(tm) CS5530 manual (rev.4.1) page.56)
  15 *
  16 *      CPU frequency control on NatSemi Geode GX1/GXLV processor and CS55x0
  17 *      are based on Suspend Modulation.
  18 *
  19 *      Suspend Modulation works by asserting and de-asserting the SUSP# pin
  20 *      to CPU(GX1/GXLV) for configurable durations. When asserting SUSP#
  21 *      the CPU enters an idle state. GX1 stops its core clock when SUSP# is
  22 *      asserted then power consumption is reduced.
  23 *
  24 *      Suspend Modulation's OFF/ON duration are configurable
  25 *      with 'Suspend Modulation OFF Count Register'
  26 *      and 'Suspend Modulation ON Count Register'.
  27 *      These registers are 8bit counters that represent the number of
  28 *      32us intervals which the SUSP# pin is asserted(ON)/de-asserted(OFF)
  29 *      to the processor.
  30 *
  31 *      These counters define a ratio which is the effective frequency
  32 *      of operation of the system.
  33 *
  34 *                             OFF Count
  35 *      F_eff = Fgx * ----------------------
  36 *                      OFF Count + ON Count
  37 *
  38 *      0 <= On Count, Off Count <= 255
  39 *
  40 *      From these limits, we can get register values
  41 *
  42 *      off_duration + on_duration <= MAX_DURATION
  43 *      on_duration = off_duration * (stock_freq - freq) / freq
  44 *
  45 *      off_duration  =  (freq * DURATION) / stock_freq
  46 *      on_duration = DURATION - off_duration
  47 *
  48 *---------------------------------------------------------------------------
  49 *
  50 * ChangeLog:
  51 *      Dec. 12, 2003   Hiroshi Miura <miura@da-cha.org>
  52 *              - fix on/off register mistake
  53 *              - fix cpu_khz calc when it stops cpu modulation.
  54 *
  55 *      Dec. 11, 2002   Hiroshi Miura <miura@da-cha.org>
  56 *              - rewrite for Cyrix MediaGX Cx5510/5520 and
  57 *                NatSemi Geode Cs5530(A).
  58 *
  59 *      Jul. ??, 2002  Zwane Mwaikambo <zwane@commfireservices.com>
  60 *              - cs5530_mod patch for 2.4.19-rc1.
  61 *
  62 *---------------------------------------------------------------------------
  63 *
  64 * Todo
  65 *      Test on machines with 5510, 5530, 5530A
  66 */
  67
  68/************************************************************************
  69 *                      Suspend Modulation - Definitions                *
  70 ************************************************************************/
  71
  72#include <linux/kernel.h>
  73#include <linux/module.h>
  74#include <linux/init.h>
  75#include <linux/smp.h>
  76#include <linux/cpufreq.h>
  77#include <linux/pci.h>
  78#include <linux/errno.h>
  79#include <linux/slab.h>
  80
  81#include <asm/cpu_device_id.h>
  82#include <asm/processor-cyrix.h>
  83
  84/* PCI config registers, all at F0 */
  85#define PCI_PMER1       0x80    /* power management enable register 1 */
  86#define PCI_PMER2       0x81    /* power management enable register 2 */
  87#define PCI_PMER3       0x82    /* power management enable register 3 */
  88#define PCI_IRQTC       0x8c    /* irq speedup timer counter register:typical 2 to 4ms */
  89#define PCI_VIDTC       0x8d    /* video speedup timer counter register: typical 50 to 100ms */
  90#define PCI_MODOFF      0x94    /* suspend modulation OFF counter register, 1 = 32us */
  91#define PCI_MODON       0x95    /* suspend modulation ON counter register */
  92#define PCI_SUSCFG      0x96    /* suspend configuration register */
  93
  94/* PMER1 bits */
  95#define GPM             (1<<0)  /* global power management */
  96#define GIT             (1<<1)  /* globally enable PM device idle timers */
  97#define GTR             (1<<2)  /* globally enable IO traps */
  98#define IRQ_SPDUP       (1<<3)  /* disable clock throttle during interrupt handling */
  99#define VID_SPDUP       (1<<4)  /* disable clock throttle during vga video handling */
 100
 101/* SUSCFG bits */
 102#define SUSMOD          (1<<0)  /* enable/disable suspend modulation */
 103/* the below is supported only with cs5530 (after rev.1.2)/cs5530A */
 104#define SMISPDUP        (1<<1)  /* select how SMI re-enable suspend modulation: */
 105                                /* IRQTC timer or read SMI speedup disable reg.(F1BAR[08-09h]) */
 106#define SUSCFG          (1<<2)  /* enable powering down a GXLV processor. "Special 3Volt Suspend" mode */
 107/* the below is supported only with cs5530A */
 108#define PWRSVE_ISA      (1<<3)  /* stop ISA clock  */
 109#define PWRSVE          (1<<4)  /* active idle */
 110
 111struct gxfreq_params {
 112        u8 on_duration;
 113        u8 off_duration;
 114        u8 pci_suscfg;
 115        u8 pci_pmer1;
 116        u8 pci_pmer2;
 117        struct pci_dev *cs55x0;
 118};
 119
 120static struct gxfreq_params *gx_params;
 121static int stock_freq;
 122
 123/* PCI bus clock - defaults to 30.000 if cpu_khz is not available */
 124static int pci_busclk;
 125module_param(pci_busclk, int, 0444);
 126
 127/* maximum duration for which the cpu may be suspended
 128 * (32us * MAX_DURATION). If no parameter is given, this defaults
 129 * to 255.
 130 * Note that this leads to a maximum of 8 ms(!) where the CPU clock
 131 * is suspended -- processing power is just 0.39% of what it used to be,
 132 * though. 781.25 kHz(!) for a 200 MHz processor -- wow. */
 133static int max_duration = 255;
 134module_param(max_duration, int, 0444);
 135
 136/* For the default policy, we want at least some processing power
 137 * - let's say 5%. (min = maxfreq / POLICY_MIN_DIV)
 138 */
 139#define POLICY_MIN_DIV 20
 140
 141
 142/**
 143 * we can detect a core multiplier from dir0_lsb
 144 * from GX1 datasheet p.56,
 145 *      MULT[3:0]:
 146 *      0000 = SYSCLK multiplied by 4 (test only)
 147 *      0001 = SYSCLK multiplied by 10
 148 *      0010 = SYSCLK multiplied by 4
 149 *      0011 = SYSCLK multiplied by 6
 150 *      0100 = SYSCLK multiplied by 9
 151 *      0101 = SYSCLK multiplied by 5
 152 *      0110 = SYSCLK multiplied by 7
 153 *      0111 = SYSCLK multiplied by 8
 154 *              of 33.3MHz
 155 **/
 156static int gx_freq_mult[16] = {
 157                4, 10, 4, 6, 9, 5, 7, 8,
 158                0, 0, 0, 0, 0, 0, 0, 0
 159};
 160
 161
 162/****************************************************************
 163 *      Low Level chipset interface                             *
 164 ****************************************************************/
 165static struct pci_device_id gx_chipset_tbl[] __initdata = {
 166        { PCI_VDEVICE(CYRIX, PCI_DEVICE_ID_CYRIX_5530_LEGACY), },
 167        { PCI_VDEVICE(CYRIX, PCI_DEVICE_ID_CYRIX_5520), },
 168        { PCI_VDEVICE(CYRIX, PCI_DEVICE_ID_CYRIX_5510), },
 169        { 0, },
 170};
 171MODULE_DEVICE_TABLE(pci, gx_chipset_tbl);
 172
 173static void gx_write_byte(int reg, int value)
 174{
 175        pci_write_config_byte(gx_params->cs55x0, reg, value);
 176}
 177
 178/**
 179 * gx_detect_chipset:
 180 *
 181 **/
 182static struct pci_dev * __init gx_detect_chipset(void)
 183{
 184        struct pci_dev *gx_pci = NULL;
 185
 186        /* detect which companion chip is used */
 187        for_each_pci_dev(gx_pci) {
 188                if ((pci_match_id(gx_chipset_tbl, gx_pci)) != NULL)
 189                        return gx_pci;
 190        }
 191
 192        pr_debug("error: no supported chipset found!\n");
 193        return NULL;
 194}
 195
 196/**
 197 * gx_get_cpuspeed:
 198 *
 199 * Finds out at which efficient frequency the Cyrix MediaGX/NatSemi
 200 * Geode CPU runs.
 201 */
 202static unsigned int gx_get_cpuspeed(unsigned int cpu)
 203{
 204        if ((gx_params->pci_suscfg & SUSMOD) == 0)
 205                return stock_freq;
 206
 207        return (stock_freq * gx_params->off_duration)
 208                / (gx_params->on_duration + gx_params->off_duration);
 209}
 210
 211/**
 212 *      gx_validate_speed:
 213 *      determine current cpu speed
 214 *
 215 **/
 216
 217static unsigned int gx_validate_speed(unsigned int khz, u8 *on_duration,
 218                u8 *off_duration)
 219{
 220        unsigned int i;
 221        u8 tmp_on, tmp_off;
 222        int old_tmp_freq = stock_freq;
 223        int tmp_freq;
 224
 225        *off_duration = 1;
 226        *on_duration = 0;
 227
 228        for (i = max_duration; i > 0; i--) {
 229                tmp_off = ((khz * i) / stock_freq) & 0xff;
 230                tmp_on = i - tmp_off;
 231                tmp_freq = (stock_freq * tmp_off) / i;
 232                /* if this relation is closer to khz, use this. If it's equal,
 233                 * prefer it, too - lower latency */
 234                if (abs(tmp_freq - khz) <= abs(old_tmp_freq - khz)) {
 235                        *on_duration = tmp_on;
 236                        *off_duration = tmp_off;
 237                        old_tmp_freq = tmp_freq;
 238                }
 239        }
 240
 241        return old_tmp_freq;
 242}
 243
 244
 245/**
 246 * gx_set_cpuspeed:
 247 * set cpu speed in khz.
 248 **/
 249
 250static void gx_set_cpuspeed(struct cpufreq_policy *policy, unsigned int khz)
 251{
 252        u8 suscfg, pmer1;
 253        unsigned int new_khz;
 254        unsigned long flags;
 255        struct cpufreq_freqs freqs;
 256
 257        freqs.old = gx_get_cpuspeed(0);
 258
 259        new_khz = gx_validate_speed(khz, &gx_params->on_duration,
 260                        &gx_params->off_duration);
 261
 262        freqs.new = new_khz;
 263
 264        cpufreq_freq_transition_begin(policy, &freqs);
 265        local_irq_save(flags);
 266
 267        if (new_khz != stock_freq) {
 268                /* if new khz == 100% of CPU speed, it is special case */
 269                switch (gx_params->cs55x0->device) {
 270                case PCI_DEVICE_ID_CYRIX_5530_LEGACY:
 271                        pmer1 = gx_params->pci_pmer1 | IRQ_SPDUP | VID_SPDUP;
 272                        /* FIXME: need to test other values -- Zwane,Miura */
 273                        /* typical 2 to 4ms */
 274                        gx_write_byte(PCI_IRQTC, 4);
 275                        /* typical 50 to 100ms */
 276                        gx_write_byte(PCI_VIDTC, 100);
 277                        gx_write_byte(PCI_PMER1, pmer1);
 278
 279                        if (gx_params->cs55x0->revision < 0x10) {
 280                                /* CS5530(rev 1.2, 1.3) */
 281                                suscfg = gx_params->pci_suscfg|SUSMOD;
 282                        } else {
 283                                /* CS5530A,B.. */
 284                                suscfg = gx_params->pci_suscfg|SUSMOD|PWRSVE;
 285                        }
 286                        break;
 287                case PCI_DEVICE_ID_CYRIX_5520:
 288                case PCI_DEVICE_ID_CYRIX_5510:
 289                        suscfg = gx_params->pci_suscfg | SUSMOD;
 290                        break;
 291                default:
 292                        local_irq_restore(flags);
 293                        pr_debug("fatal: try to set unknown chipset.\n");
 294                        return;
 295                }
 296        } else {
 297                suscfg = gx_params->pci_suscfg & ~(SUSMOD);
 298                gx_params->off_duration = 0;
 299                gx_params->on_duration = 0;
 300                pr_debug("suspend modulation disabled: cpu runs 100%% speed.\n");
 301        }
 302
 303        gx_write_byte(PCI_MODOFF, gx_params->off_duration);
 304        gx_write_byte(PCI_MODON, gx_params->on_duration);
 305
 306        gx_write_byte(PCI_SUSCFG, suscfg);
 307        pci_read_config_byte(gx_params->cs55x0, PCI_SUSCFG, &suscfg);
 308
 309        local_irq_restore(flags);
 310
 311        gx_params->pci_suscfg = suscfg;
 312
 313        cpufreq_freq_transition_end(policy, &freqs, 0);
 314
 315        pr_debug("suspend modulation w/ duration of ON:%d us, OFF:%d us\n",
 316                gx_params->on_duration * 32, gx_params->off_duration * 32);
 317        pr_debug("suspend modulation w/ clock speed: %d kHz.\n", freqs.new);
 318}
 319
 320/****************************************************************
 321 *             High level functions                             *
 322 ****************************************************************/
 323
 324/*
 325 *      cpufreq_gx_verify: test if frequency range is valid
 326 *
 327 *      This function checks if a given frequency range in kHz is valid
 328 *      for the hardware supported by the driver.
 329 */
 330
 331static int cpufreq_gx_verify(struct cpufreq_policy_data *policy)
 332{
 333        unsigned int tmp_freq = 0;
 334        u8 tmp1, tmp2;
 335
 336        if (!stock_freq || !policy)
 337                return -EINVAL;
 338
 339        policy->cpu = 0;
 340        cpufreq_verify_within_limits(policy, (stock_freq / max_duration),
 341                        stock_freq);
 342
 343        /* it needs to be assured that at least one supported frequency is
 344         * within policy->min and policy->max. If it is not, policy->max
 345         * needs to be increased until one frequency is supported.
 346         * policy->min may not be decreased, though. This way we guarantee a
 347         * specific processing capacity.
 348         */
 349        tmp_freq = gx_validate_speed(policy->min, &tmp1, &tmp2);
 350        if (tmp_freq < policy->min)
 351                tmp_freq += stock_freq / max_duration;
 352        policy->min = tmp_freq;
 353        if (policy->min > policy->max)
 354                policy->max = tmp_freq;
 355        tmp_freq = gx_validate_speed(policy->max, &tmp1, &tmp2);
 356        if (tmp_freq > policy->max)
 357                tmp_freq -= stock_freq / max_duration;
 358        policy->max = tmp_freq;
 359        if (policy->max < policy->min)
 360                policy->max = policy->min;
 361        cpufreq_verify_within_limits(policy, (stock_freq / max_duration),
 362                        stock_freq);
 363
 364        return 0;
 365}
 366
 367/*
 368 *      cpufreq_gx_target:
 369 *
 370 */
 371static int cpufreq_gx_target(struct cpufreq_policy *policy,
 372                             unsigned int target_freq,
 373                             unsigned int relation)
 374{
 375        u8 tmp1, tmp2;
 376        unsigned int tmp_freq;
 377
 378        if (!stock_freq || !policy)
 379                return -EINVAL;
 380
 381        policy->cpu = 0;
 382
 383        tmp_freq = gx_validate_speed(target_freq, &tmp1, &tmp2);
 384        while (tmp_freq < policy->min) {
 385                tmp_freq += stock_freq / max_duration;
 386                tmp_freq = gx_validate_speed(tmp_freq, &tmp1, &tmp2);
 387        }
 388        while (tmp_freq > policy->max) {
 389                tmp_freq -= stock_freq / max_duration;
 390                tmp_freq = gx_validate_speed(tmp_freq, &tmp1, &tmp2);
 391        }
 392
 393        gx_set_cpuspeed(policy, tmp_freq);
 394
 395        return 0;
 396}
 397
 398static int cpufreq_gx_cpu_init(struct cpufreq_policy *policy)
 399{
 400        unsigned int maxfreq;
 401
 402        if (!policy || policy->cpu != 0)
 403                return -ENODEV;
 404
 405        /* determine maximum frequency */
 406        if (pci_busclk)
 407                maxfreq = pci_busclk * gx_freq_mult[getCx86(CX86_DIR1) & 0x0f];
 408        else if (cpu_khz)
 409                maxfreq = cpu_khz;
 410        else
 411                maxfreq = 30000 * gx_freq_mult[getCx86(CX86_DIR1) & 0x0f];
 412
 413        stock_freq = maxfreq;
 414
 415        pr_debug("cpu max frequency is %d.\n", maxfreq);
 416
 417        /* setup basic struct for cpufreq API */
 418        policy->cpu = 0;
 419
 420        if (max_duration < POLICY_MIN_DIV)
 421                policy->min = maxfreq / max_duration;
 422        else
 423                policy->min = maxfreq / POLICY_MIN_DIV;
 424        policy->max = maxfreq;
 425        policy->cpuinfo.min_freq = maxfreq / max_duration;
 426        policy->cpuinfo.max_freq = maxfreq;
 427
 428        return 0;
 429}
 430
 431/*
 432 * cpufreq_gx_init:
 433 *   MediaGX/Geode GX initialize cpufreq driver
 434 */
 435static struct cpufreq_driver gx_suspmod_driver = {
 436        .flags          = CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING,
 437        .get            = gx_get_cpuspeed,
 438        .verify         = cpufreq_gx_verify,
 439        .target         = cpufreq_gx_target,
 440        .init           = cpufreq_gx_cpu_init,
 441        .name           = "gx-suspmod",
 442};
 443
 444static int __init cpufreq_gx_init(void)
 445{
 446        int ret;
 447        struct gxfreq_params *params;
 448        struct pci_dev *gx_pci;
 449
 450        /* Test if we have the right hardware */
 451        gx_pci = gx_detect_chipset();
 452        if (gx_pci == NULL)
 453                return -ENODEV;
 454
 455        /* check whether module parameters are sane */
 456        if (max_duration > 0xff)
 457                max_duration = 0xff;
 458
 459        pr_debug("geode suspend modulation available.\n");
 460
 461        params = kzalloc(sizeof(*params), GFP_KERNEL);
 462        if (params == NULL)
 463                return -ENOMEM;
 464
 465        params->cs55x0 = gx_pci;
 466        gx_params = params;
 467
 468        /* keep cs55x0 configurations */
 469        pci_read_config_byte(params->cs55x0, PCI_SUSCFG, &(params->pci_suscfg));
 470        pci_read_config_byte(params->cs55x0, PCI_PMER1, &(params->pci_pmer1));
 471        pci_read_config_byte(params->cs55x0, PCI_PMER2, &(params->pci_pmer2));
 472        pci_read_config_byte(params->cs55x0, PCI_MODON, &(params->on_duration));
 473        pci_read_config_byte(params->cs55x0, PCI_MODOFF,
 474                        &(params->off_duration));
 475
 476        ret = cpufreq_register_driver(&gx_suspmod_driver);
 477        if (ret) {
 478                kfree(params);
 479                return ret;                   /* register error! */
 480        }
 481
 482        return 0;
 483}
 484
 485static void __exit cpufreq_gx_exit(void)
 486{
 487        cpufreq_unregister_driver(&gx_suspmod_driver);
 488        pci_dev_put(gx_params->cs55x0);
 489        kfree(gx_params);
 490}
 491
 492MODULE_AUTHOR("Hiroshi Miura <miura@da-cha.org>");
 493MODULE_DESCRIPTION("Cpufreq driver for Cyrix MediaGX and NatSemi Geode");
 494MODULE_LICENSE("GPL");
 495
 496module_init(cpufreq_gx_init);
 497module_exit(cpufreq_gx_exit);
 498
 499