Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  *  Cyrix MediaGX and NatSemi Geode Suspend Modulation
0004  *  (C) 2002 Zwane Mwaikambo <zwane@commfireservices.com>
0005  *  (C) 2002 Hiroshi Miura   <miura@da-cha.org>
0006  *  All Rights Reserved
0007  *
0008  *      The author(s) of this software shall not be held liable for damages
0009  *      of any nature resulting due to the use of this software. This
0010  *      software is provided AS-IS with no warranties.
0011  *
0012  * Theoretical note:
0013  *
0014  *  (see Geode(tm) CS5530 manual (rev.4.1) page.56)
0015  *
0016  *  CPU frequency control on NatSemi Geode GX1/GXLV processor and CS55x0
0017  *  are based on Suspend Modulation.
0018  *
0019  *  Suspend Modulation works by asserting and de-asserting the SUSP# pin
0020  *  to CPU(GX1/GXLV) for configurable durations. When asserting SUSP#
0021  *  the CPU enters an idle state. GX1 stops its core clock when SUSP# is
0022  *  asserted then power consumption is reduced.
0023  *
0024  *  Suspend Modulation's OFF/ON duration are configurable
0025  *  with 'Suspend Modulation OFF Count Register'
0026  *  and 'Suspend Modulation ON Count Register'.
0027  *  These registers are 8bit counters that represent the number of
0028  *  32us intervals which the SUSP# pin is asserted(ON)/de-asserted(OFF)
0029  *  to the processor.
0030  *
0031  *  These counters define a ratio which is the effective frequency
0032  *  of operation of the system.
0033  *
0034  *                 OFF Count
0035  *  F_eff = Fgx * ----------------------
0036  *                  OFF Count + ON Count
0037  *
0038  *  0 <= On Count, Off Count <= 255
0039  *
0040  *  From these limits, we can get register values
0041  *
0042  *  off_duration + on_duration <= MAX_DURATION
0043  *  on_duration = off_duration * (stock_freq - freq) / freq
0044  *
0045  *      off_duration  =  (freq * DURATION) / stock_freq
0046  *      on_duration = DURATION - off_duration
0047  *
0048  *---------------------------------------------------------------------------
0049  *
0050  * ChangeLog:
0051  *  Dec. 12, 2003   Hiroshi Miura <miura@da-cha.org>
0052  *      - fix on/off register mistake
0053  *      - fix cpu_khz calc when it stops cpu modulation.
0054  *
0055  *  Dec. 11, 2002   Hiroshi Miura <miura@da-cha.org>
0056  *      - rewrite for Cyrix MediaGX Cx5510/5520 and
0057  *        NatSemi Geode Cs5530(A).
0058  *
0059  *  Jul. ??, 2002  Zwane Mwaikambo <zwane@commfireservices.com>
0060  *      - cs5530_mod patch for 2.4.19-rc1.
0061  *
0062  *---------------------------------------------------------------------------
0063  *
0064  * Todo
0065  *  Test on machines with 5510, 5530, 5530A
0066  */
0067 
0068 /************************************************************************
0069  *          Suspend Modulation - Definitions        *
0070  ************************************************************************/
0071 
0072 #include <linux/kernel.h>
0073 #include <linux/module.h>
0074 #include <linux/init.h>
0075 #include <linux/smp.h>
0076 #include <linux/cpufreq.h>
0077 #include <linux/pci.h>
0078 #include <linux/errno.h>
0079 #include <linux/slab.h>
0080 
0081 #include <asm/cpu_device_id.h>
0082 #include <asm/processor-cyrix.h>
0083 
0084 /* PCI config registers, all at F0 */
0085 #define PCI_PMER1   0x80    /* power management enable register 1 */
0086 #define PCI_PMER2   0x81    /* power management enable register 2 */
0087 #define PCI_PMER3   0x82    /* power management enable register 3 */
0088 #define PCI_IRQTC   0x8c    /* irq speedup timer counter register:typical 2 to 4ms */
0089 #define PCI_VIDTC   0x8d    /* video speedup timer counter register: typical 50 to 100ms */
0090 #define PCI_MODOFF  0x94    /* suspend modulation OFF counter register, 1 = 32us */
0091 #define PCI_MODON   0x95    /* suspend modulation ON counter register */
0092 #define PCI_SUSCFG  0x96    /* suspend configuration register */
0093 
0094 /* PMER1 bits */
0095 #define GPM     (1<<0)  /* global power management */
0096 #define GIT     (1<<1)  /* globally enable PM device idle timers */
0097 #define GTR     (1<<2)  /* globally enable IO traps */
0098 #define IRQ_SPDUP   (1<<3)  /* disable clock throttle during interrupt handling */
0099 #define VID_SPDUP   (1<<4)  /* disable clock throttle during vga video handling */
0100 
0101 /* SUSCFG bits */
0102 #define SUSMOD      (1<<0)  /* enable/disable suspend modulation */
0103 /* the below is supported only with cs5530 (after rev.1.2)/cs5530A */
0104 #define SMISPDUP    (1<<1)  /* select how SMI re-enable suspend modulation: */
0105                 /* IRQTC timer or read SMI speedup disable reg.(F1BAR[08-09h]) */
0106 #define SUSCFG      (1<<2)  /* enable powering down a GXLV processor. "Special 3Volt Suspend" mode */
0107 /* the below is supported only with cs5530A */
0108 #define PWRSVE_ISA  (1<<3)  /* stop ISA clock  */
0109 #define PWRSVE      (1<<4)  /* active idle */
0110 
0111 struct gxfreq_params {
0112     u8 on_duration;
0113     u8 off_duration;
0114     u8 pci_suscfg;
0115     u8 pci_pmer1;
0116     u8 pci_pmer2;
0117     struct pci_dev *cs55x0;
0118 };
0119 
0120 static struct gxfreq_params *gx_params;
0121 static int stock_freq;
0122 
0123 /* PCI bus clock - defaults to 30.000 if cpu_khz is not available */
0124 static int pci_busclk;
0125 module_param(pci_busclk, int, 0444);
0126 
0127 /* maximum duration for which the cpu may be suspended
0128  * (32us * MAX_DURATION). If no parameter is given, this defaults
0129  * to 255.
0130  * Note that this leads to a maximum of 8 ms(!) where the CPU clock
0131  * is suspended -- processing power is just 0.39% of what it used to be,
0132  * though. 781.25 kHz(!) for a 200 MHz processor -- wow. */
0133 static int max_duration = 255;
0134 module_param(max_duration, int, 0444);
0135 
0136 /* For the default policy, we want at least some processing power
0137  * - let's say 5%. (min = maxfreq / POLICY_MIN_DIV)
0138  */
0139 #define POLICY_MIN_DIV 20
0140 
0141 
0142 /**
0143  * we can detect a core multiplier from dir0_lsb
0144  * from GX1 datasheet p.56,
0145  *  MULT[3:0]:
0146  *  0000 = SYSCLK multiplied by 4 (test only)
0147  *  0001 = SYSCLK multiplied by 10
0148  *  0010 = SYSCLK multiplied by 4
0149  *  0011 = SYSCLK multiplied by 6
0150  *  0100 = SYSCLK multiplied by 9
0151  *  0101 = SYSCLK multiplied by 5
0152  *  0110 = SYSCLK multiplied by 7
0153  *  0111 = SYSCLK multiplied by 8
0154  *              of 33.3MHz
0155  **/
0156 static int gx_freq_mult[16] = {
0157         4, 10, 4, 6, 9, 5, 7, 8,
0158         0, 0, 0, 0, 0, 0, 0, 0
0159 };
0160 
0161 
0162 /****************************************************************
0163  *  Low Level chipset interface             *
0164  ****************************************************************/
0165 static struct pci_device_id gx_chipset_tbl[] __initdata = {
0166     { PCI_VDEVICE(CYRIX, PCI_DEVICE_ID_CYRIX_5530_LEGACY), },
0167     { PCI_VDEVICE(CYRIX, PCI_DEVICE_ID_CYRIX_5520), },
0168     { PCI_VDEVICE(CYRIX, PCI_DEVICE_ID_CYRIX_5510), },
0169     { 0, },
0170 };
0171 MODULE_DEVICE_TABLE(pci, gx_chipset_tbl);
0172 
0173 static void gx_write_byte(int reg, int value)
0174 {
0175     pci_write_config_byte(gx_params->cs55x0, reg, value);
0176 }
0177 
0178 /**
0179  * gx_detect_chipset:
0180  *
0181  **/
0182 static struct pci_dev * __init gx_detect_chipset(void)
0183 {
0184     struct pci_dev *gx_pci = NULL;
0185 
0186     /* detect which companion chip is used */
0187     for_each_pci_dev(gx_pci) {
0188         if ((pci_match_id(gx_chipset_tbl, gx_pci)) != NULL)
0189             return gx_pci;
0190     }
0191 
0192     pr_debug("error: no supported chipset found!\n");
0193     return NULL;
0194 }
0195 
0196 /**
0197  * gx_get_cpuspeed:
0198  *
0199  * Finds out at which efficient frequency the Cyrix MediaGX/NatSemi
0200  * Geode CPU runs.
0201  */
0202 static unsigned int gx_get_cpuspeed(unsigned int cpu)
0203 {
0204     if ((gx_params->pci_suscfg & SUSMOD) == 0)
0205         return stock_freq;
0206 
0207     return (stock_freq * gx_params->off_duration)
0208         / (gx_params->on_duration + gx_params->off_duration);
0209 }
0210 
0211 /**
0212  *      gx_validate_speed:
0213  *      determine current cpu speed
0214  *
0215  **/
0216 
0217 static unsigned int gx_validate_speed(unsigned int khz, u8 *on_duration,
0218         u8 *off_duration)
0219 {
0220     unsigned int i;
0221     u8 tmp_on, tmp_off;
0222     int old_tmp_freq = stock_freq;
0223     int tmp_freq;
0224 
0225     *off_duration = 1;
0226     *on_duration = 0;
0227 
0228     for (i = max_duration; i > 0; i--) {
0229         tmp_off = ((khz * i) / stock_freq) & 0xff;
0230         tmp_on = i - tmp_off;
0231         tmp_freq = (stock_freq * tmp_off) / i;
0232         /* if this relation is closer to khz, use this. If it's equal,
0233          * prefer it, too - lower latency */
0234         if (abs(tmp_freq - khz) <= abs(old_tmp_freq - khz)) {
0235             *on_duration = tmp_on;
0236             *off_duration = tmp_off;
0237             old_tmp_freq = tmp_freq;
0238         }
0239     }
0240 
0241     return old_tmp_freq;
0242 }
0243 
0244 
0245 /**
0246  * gx_set_cpuspeed:
0247  * set cpu speed in khz.
0248  **/
0249 
0250 static void gx_set_cpuspeed(struct cpufreq_policy *policy, unsigned int khz)
0251 {
0252     u8 suscfg, pmer1;
0253     unsigned int new_khz;
0254     unsigned long flags;
0255     struct cpufreq_freqs freqs;
0256 
0257     freqs.old = gx_get_cpuspeed(0);
0258 
0259     new_khz = gx_validate_speed(khz, &gx_params->on_duration,
0260             &gx_params->off_duration);
0261 
0262     freqs.new = new_khz;
0263 
0264     cpufreq_freq_transition_begin(policy, &freqs);
0265     local_irq_save(flags);
0266 
0267     if (new_khz != stock_freq) {
0268         /* if new khz == 100% of CPU speed, it is special case */
0269         switch (gx_params->cs55x0->device) {
0270         case PCI_DEVICE_ID_CYRIX_5530_LEGACY:
0271             pmer1 = gx_params->pci_pmer1 | IRQ_SPDUP | VID_SPDUP;
0272             /* FIXME: need to test other values -- Zwane,Miura */
0273             /* typical 2 to 4ms */
0274             gx_write_byte(PCI_IRQTC, 4);
0275             /* typical 50 to 100ms */
0276             gx_write_byte(PCI_VIDTC, 100);
0277             gx_write_byte(PCI_PMER1, pmer1);
0278 
0279             if (gx_params->cs55x0->revision < 0x10) {
0280                 /* CS5530(rev 1.2, 1.3) */
0281                 suscfg = gx_params->pci_suscfg|SUSMOD;
0282             } else {
0283                 /* CS5530A,B.. */
0284                 suscfg = gx_params->pci_suscfg|SUSMOD|PWRSVE;
0285             }
0286             break;
0287         case PCI_DEVICE_ID_CYRIX_5520:
0288         case PCI_DEVICE_ID_CYRIX_5510:
0289             suscfg = gx_params->pci_suscfg | SUSMOD;
0290             break;
0291         default:
0292             local_irq_restore(flags);
0293             pr_debug("fatal: try to set unknown chipset.\n");
0294             return;
0295         }
0296     } else {
0297         suscfg = gx_params->pci_suscfg & ~(SUSMOD);
0298         gx_params->off_duration = 0;
0299         gx_params->on_duration = 0;
0300         pr_debug("suspend modulation disabled: cpu runs 100%% speed.\n");
0301     }
0302 
0303     gx_write_byte(PCI_MODOFF, gx_params->off_duration);
0304     gx_write_byte(PCI_MODON, gx_params->on_duration);
0305 
0306     gx_write_byte(PCI_SUSCFG, suscfg);
0307     pci_read_config_byte(gx_params->cs55x0, PCI_SUSCFG, &suscfg);
0308 
0309     local_irq_restore(flags);
0310 
0311     gx_params->pci_suscfg = suscfg;
0312 
0313     cpufreq_freq_transition_end(policy, &freqs, 0);
0314 
0315     pr_debug("suspend modulation w/ duration of ON:%d us, OFF:%d us\n",
0316         gx_params->on_duration * 32, gx_params->off_duration * 32);
0317     pr_debug("suspend modulation w/ clock speed: %d kHz.\n", freqs.new);
0318 }
0319 
0320 /****************************************************************
0321  *             High level functions                             *
0322  ****************************************************************/
0323 
0324 /*
0325  *  cpufreq_gx_verify: test if frequency range is valid
0326  *
0327  *  This function checks if a given frequency range in kHz is valid
0328  *      for the hardware supported by the driver.
0329  */
0330 
0331 static int cpufreq_gx_verify(struct cpufreq_policy_data *policy)
0332 {
0333     unsigned int tmp_freq = 0;
0334     u8 tmp1, tmp2;
0335 
0336     if (!stock_freq || !policy)
0337         return -EINVAL;
0338 
0339     policy->cpu = 0;
0340     cpufreq_verify_within_limits(policy, (stock_freq / max_duration),
0341             stock_freq);
0342 
0343     /* it needs to be assured that at least one supported frequency is
0344      * within policy->min and policy->max. If it is not, policy->max
0345      * needs to be increased until one frequency is supported.
0346      * policy->min may not be decreased, though. This way we guarantee a
0347      * specific processing capacity.
0348      */
0349     tmp_freq = gx_validate_speed(policy->min, &tmp1, &tmp2);
0350     if (tmp_freq < policy->min)
0351         tmp_freq += stock_freq / max_duration;
0352     policy->min = tmp_freq;
0353     if (policy->min > policy->max)
0354         policy->max = tmp_freq;
0355     tmp_freq = gx_validate_speed(policy->max, &tmp1, &tmp2);
0356     if (tmp_freq > policy->max)
0357         tmp_freq -= stock_freq / max_duration;
0358     policy->max = tmp_freq;
0359     if (policy->max < policy->min)
0360         policy->max = policy->min;
0361     cpufreq_verify_within_limits(policy, (stock_freq / max_duration),
0362             stock_freq);
0363 
0364     return 0;
0365 }
0366 
0367 /*
0368  *      cpufreq_gx_target:
0369  *
0370  */
0371 static int cpufreq_gx_target(struct cpufreq_policy *policy,
0372                  unsigned int target_freq,
0373                  unsigned int relation)
0374 {
0375     u8 tmp1, tmp2;
0376     unsigned int tmp_freq;
0377 
0378     if (!stock_freq || !policy)
0379         return -EINVAL;
0380 
0381     policy->cpu = 0;
0382 
0383     tmp_freq = gx_validate_speed(target_freq, &tmp1, &tmp2);
0384     while (tmp_freq < policy->min) {
0385         tmp_freq += stock_freq / max_duration;
0386         tmp_freq = gx_validate_speed(tmp_freq, &tmp1, &tmp2);
0387     }
0388     while (tmp_freq > policy->max) {
0389         tmp_freq -= stock_freq / max_duration;
0390         tmp_freq = gx_validate_speed(tmp_freq, &tmp1, &tmp2);
0391     }
0392 
0393     gx_set_cpuspeed(policy, tmp_freq);
0394 
0395     return 0;
0396 }
0397 
0398 static int cpufreq_gx_cpu_init(struct cpufreq_policy *policy)
0399 {
0400     unsigned int maxfreq;
0401 
0402     if (!policy || policy->cpu != 0)
0403         return -ENODEV;
0404 
0405     /* determine maximum frequency */
0406     if (pci_busclk)
0407         maxfreq = pci_busclk * gx_freq_mult[getCx86(CX86_DIR1) & 0x0f];
0408     else if (cpu_khz)
0409         maxfreq = cpu_khz;
0410     else
0411         maxfreq = 30000 * gx_freq_mult[getCx86(CX86_DIR1) & 0x0f];
0412 
0413     stock_freq = maxfreq;
0414 
0415     pr_debug("cpu max frequency is %d.\n", maxfreq);
0416 
0417     /* setup basic struct for cpufreq API */
0418     policy->cpu = 0;
0419 
0420     if (max_duration < POLICY_MIN_DIV)
0421         policy->min = maxfreq / max_duration;
0422     else
0423         policy->min = maxfreq / POLICY_MIN_DIV;
0424     policy->max = maxfreq;
0425     policy->cpuinfo.min_freq = maxfreq / max_duration;
0426     policy->cpuinfo.max_freq = maxfreq;
0427 
0428     return 0;
0429 }
0430 
0431 /*
0432  * cpufreq_gx_init:
0433  *   MediaGX/Geode GX initialize cpufreq driver
0434  */
0435 static struct cpufreq_driver gx_suspmod_driver = {
0436     .flags      = CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING,
0437     .get        = gx_get_cpuspeed,
0438     .verify     = cpufreq_gx_verify,
0439     .target     = cpufreq_gx_target,
0440     .init       = cpufreq_gx_cpu_init,
0441     .name       = "gx-suspmod",
0442 };
0443 
0444 static int __init cpufreq_gx_init(void)
0445 {
0446     int ret;
0447     struct gxfreq_params *params;
0448     struct pci_dev *gx_pci;
0449 
0450     /* Test if we have the right hardware */
0451     gx_pci = gx_detect_chipset();
0452     if (gx_pci == NULL)
0453         return -ENODEV;
0454 
0455     /* check whether module parameters are sane */
0456     if (max_duration > 0xff)
0457         max_duration = 0xff;
0458 
0459     pr_debug("geode suspend modulation available.\n");
0460 
0461     params = kzalloc(sizeof(*params), GFP_KERNEL);
0462     if (params == NULL)
0463         return -ENOMEM;
0464 
0465     params->cs55x0 = gx_pci;
0466     gx_params = params;
0467 
0468     /* keep cs55x0 configurations */
0469     pci_read_config_byte(params->cs55x0, PCI_SUSCFG, &(params->pci_suscfg));
0470     pci_read_config_byte(params->cs55x0, PCI_PMER1, &(params->pci_pmer1));
0471     pci_read_config_byte(params->cs55x0, PCI_PMER2, &(params->pci_pmer2));
0472     pci_read_config_byte(params->cs55x0, PCI_MODON, &(params->on_duration));
0473     pci_read_config_byte(params->cs55x0, PCI_MODOFF,
0474             &(params->off_duration));
0475 
0476     ret = cpufreq_register_driver(&gx_suspmod_driver);
0477     if (ret) {
0478         kfree(params);
0479         return ret;                   /* register error! */
0480     }
0481 
0482     return 0;
0483 }
0484 
0485 static void __exit cpufreq_gx_exit(void)
0486 {
0487     cpufreq_unregister_driver(&gx_suspmod_driver);
0488     pci_dev_put(gx_params->cs55x0);
0489     kfree(gx_params);
0490 }
0491 
0492 MODULE_AUTHOR("Hiroshi Miura <miura@da-cha.org>");
0493 MODULE_DESCRIPTION("Cpufreq driver for Cyrix MediaGX and NatSemi Geode");
0494 MODULE_LICENSE("GPL");
0495 
0496 module_init(cpufreq_gx_init);
0497 module_exit(cpufreq_gx_exit);
0498