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