162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Cyrix MediaGX and NatSemi Geode Suspend Modulation 462306a36Sopenharmony_ci * (C) 2002 Zwane Mwaikambo <zwane@commfireservices.com> 562306a36Sopenharmony_ci * (C) 2002 Hiroshi Miura <miura@da-cha.org> 662306a36Sopenharmony_ci * All Rights Reserved 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * The author(s) of this software shall not be held liable for damages 962306a36Sopenharmony_ci * of any nature resulting due to the use of this software. This 1062306a36Sopenharmony_ci * software is provided AS-IS with no warranties. 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * Theoretical note: 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * (see Geode(tm) CS5530 manual (rev.4.1) page.56) 1562306a36Sopenharmony_ci * 1662306a36Sopenharmony_ci * CPU frequency control on NatSemi Geode GX1/GXLV processor and CS55x0 1762306a36Sopenharmony_ci * are based on Suspend Modulation. 1862306a36Sopenharmony_ci * 1962306a36Sopenharmony_ci * Suspend Modulation works by asserting and de-asserting the SUSP# pin 2062306a36Sopenharmony_ci * to CPU(GX1/GXLV) for configurable durations. When asserting SUSP# 2162306a36Sopenharmony_ci * the CPU enters an idle state. GX1 stops its core clock when SUSP# is 2262306a36Sopenharmony_ci * asserted then power consumption is reduced. 2362306a36Sopenharmony_ci * 2462306a36Sopenharmony_ci * Suspend Modulation's OFF/ON duration are configurable 2562306a36Sopenharmony_ci * with 'Suspend Modulation OFF Count Register' 2662306a36Sopenharmony_ci * and 'Suspend Modulation ON Count Register'. 2762306a36Sopenharmony_ci * These registers are 8bit counters that represent the number of 2862306a36Sopenharmony_ci * 32us intervals which the SUSP# pin is asserted(ON)/de-asserted(OFF) 2962306a36Sopenharmony_ci * to the processor. 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci * These counters define a ratio which is the effective frequency 3262306a36Sopenharmony_ci * of operation of the system. 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * OFF Count 3562306a36Sopenharmony_ci * F_eff = Fgx * ---------------------- 3662306a36Sopenharmony_ci * OFF Count + ON Count 3762306a36Sopenharmony_ci * 3862306a36Sopenharmony_ci * 0 <= On Count, Off Count <= 255 3962306a36Sopenharmony_ci * 4062306a36Sopenharmony_ci * From these limits, we can get register values 4162306a36Sopenharmony_ci * 4262306a36Sopenharmony_ci * off_duration + on_duration <= MAX_DURATION 4362306a36Sopenharmony_ci * on_duration = off_duration * (stock_freq - freq) / freq 4462306a36Sopenharmony_ci * 4562306a36Sopenharmony_ci * off_duration = (freq * DURATION) / stock_freq 4662306a36Sopenharmony_ci * on_duration = DURATION - off_duration 4762306a36Sopenharmony_ci * 4862306a36Sopenharmony_ci *--------------------------------------------------------------------------- 4962306a36Sopenharmony_ci * 5062306a36Sopenharmony_ci * ChangeLog: 5162306a36Sopenharmony_ci * Dec. 12, 2003 Hiroshi Miura <miura@da-cha.org> 5262306a36Sopenharmony_ci * - fix on/off register mistake 5362306a36Sopenharmony_ci * - fix cpu_khz calc when it stops cpu modulation. 5462306a36Sopenharmony_ci * 5562306a36Sopenharmony_ci * Dec. 11, 2002 Hiroshi Miura <miura@da-cha.org> 5662306a36Sopenharmony_ci * - rewrite for Cyrix MediaGX Cx5510/5520 and 5762306a36Sopenharmony_ci * NatSemi Geode Cs5530(A). 5862306a36Sopenharmony_ci * 5962306a36Sopenharmony_ci * Jul. ??, 2002 Zwane Mwaikambo <zwane@commfireservices.com> 6062306a36Sopenharmony_ci * - cs5530_mod patch for 2.4.19-rc1. 6162306a36Sopenharmony_ci * 6262306a36Sopenharmony_ci *--------------------------------------------------------------------------- 6362306a36Sopenharmony_ci * 6462306a36Sopenharmony_ci * Todo 6562306a36Sopenharmony_ci * Test on machines with 5510, 5530, 5530A 6662306a36Sopenharmony_ci */ 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci/************************************************************************ 6962306a36Sopenharmony_ci * Suspend Modulation - Definitions * 7062306a36Sopenharmony_ci ************************************************************************/ 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci#include <linux/kernel.h> 7362306a36Sopenharmony_ci#include <linux/module.h> 7462306a36Sopenharmony_ci#include <linux/init.h> 7562306a36Sopenharmony_ci#include <linux/smp.h> 7662306a36Sopenharmony_ci#include <linux/cpufreq.h> 7762306a36Sopenharmony_ci#include <linux/pci.h> 7862306a36Sopenharmony_ci#include <linux/errno.h> 7962306a36Sopenharmony_ci#include <linux/slab.h> 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci#include <asm/cpu_device_id.h> 8262306a36Sopenharmony_ci#include <asm/processor-cyrix.h> 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci/* PCI config registers, all at F0 */ 8562306a36Sopenharmony_ci#define PCI_PMER1 0x80 /* power management enable register 1 */ 8662306a36Sopenharmony_ci#define PCI_PMER2 0x81 /* power management enable register 2 */ 8762306a36Sopenharmony_ci#define PCI_PMER3 0x82 /* power management enable register 3 */ 8862306a36Sopenharmony_ci#define PCI_IRQTC 0x8c /* irq speedup timer counter register:typical 2 to 4ms */ 8962306a36Sopenharmony_ci#define PCI_VIDTC 0x8d /* video speedup timer counter register: typical 50 to 100ms */ 9062306a36Sopenharmony_ci#define PCI_MODOFF 0x94 /* suspend modulation OFF counter register, 1 = 32us */ 9162306a36Sopenharmony_ci#define PCI_MODON 0x95 /* suspend modulation ON counter register */ 9262306a36Sopenharmony_ci#define PCI_SUSCFG 0x96 /* suspend configuration register */ 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci/* PMER1 bits */ 9562306a36Sopenharmony_ci#define GPM (1<<0) /* global power management */ 9662306a36Sopenharmony_ci#define GIT (1<<1) /* globally enable PM device idle timers */ 9762306a36Sopenharmony_ci#define GTR (1<<2) /* globally enable IO traps */ 9862306a36Sopenharmony_ci#define IRQ_SPDUP (1<<3) /* disable clock throttle during interrupt handling */ 9962306a36Sopenharmony_ci#define VID_SPDUP (1<<4) /* disable clock throttle during vga video handling */ 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci/* SUSCFG bits */ 10262306a36Sopenharmony_ci#define SUSMOD (1<<0) /* enable/disable suspend modulation */ 10362306a36Sopenharmony_ci/* the below is supported only with cs5530 (after rev.1.2)/cs5530A */ 10462306a36Sopenharmony_ci#define SMISPDUP (1<<1) /* select how SMI re-enable suspend modulation: */ 10562306a36Sopenharmony_ci /* IRQTC timer or read SMI speedup disable reg.(F1BAR[08-09h]) */ 10662306a36Sopenharmony_ci#define SUSCFG (1<<2) /* enable powering down a GXLV processor. "Special 3Volt Suspend" mode */ 10762306a36Sopenharmony_ci/* the below is supported only with cs5530A */ 10862306a36Sopenharmony_ci#define PWRSVE_ISA (1<<3) /* stop ISA clock */ 10962306a36Sopenharmony_ci#define PWRSVE (1<<4) /* active idle */ 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistruct gxfreq_params { 11262306a36Sopenharmony_ci u8 on_duration; 11362306a36Sopenharmony_ci u8 off_duration; 11462306a36Sopenharmony_ci u8 pci_suscfg; 11562306a36Sopenharmony_ci u8 pci_pmer1; 11662306a36Sopenharmony_ci u8 pci_pmer2; 11762306a36Sopenharmony_ci struct pci_dev *cs55x0; 11862306a36Sopenharmony_ci}; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic struct gxfreq_params *gx_params; 12162306a36Sopenharmony_cistatic int stock_freq; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci/* PCI bus clock - defaults to 30.000 if cpu_khz is not available */ 12462306a36Sopenharmony_cistatic int pci_busclk; 12562306a36Sopenharmony_cimodule_param(pci_busclk, int, 0444); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci/* maximum duration for which the cpu may be suspended 12862306a36Sopenharmony_ci * (32us * MAX_DURATION). If no parameter is given, this defaults 12962306a36Sopenharmony_ci * to 255. 13062306a36Sopenharmony_ci * Note that this leads to a maximum of 8 ms(!) where the CPU clock 13162306a36Sopenharmony_ci * is suspended -- processing power is just 0.39% of what it used to be, 13262306a36Sopenharmony_ci * though. 781.25 kHz(!) for a 200 MHz processor -- wow. */ 13362306a36Sopenharmony_cistatic int max_duration = 255; 13462306a36Sopenharmony_cimodule_param(max_duration, int, 0444); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci/* For the default policy, we want at least some processing power 13762306a36Sopenharmony_ci * - let's say 5%. (min = maxfreq / POLICY_MIN_DIV) 13862306a36Sopenharmony_ci */ 13962306a36Sopenharmony_ci#define POLICY_MIN_DIV 20 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci/** 14362306a36Sopenharmony_ci * we can detect a core multiplier from dir0_lsb 14462306a36Sopenharmony_ci * from GX1 datasheet p.56, 14562306a36Sopenharmony_ci * MULT[3:0]: 14662306a36Sopenharmony_ci * 0000 = SYSCLK multiplied by 4 (test only) 14762306a36Sopenharmony_ci * 0001 = SYSCLK multiplied by 10 14862306a36Sopenharmony_ci * 0010 = SYSCLK multiplied by 4 14962306a36Sopenharmony_ci * 0011 = SYSCLK multiplied by 6 15062306a36Sopenharmony_ci * 0100 = SYSCLK multiplied by 9 15162306a36Sopenharmony_ci * 0101 = SYSCLK multiplied by 5 15262306a36Sopenharmony_ci * 0110 = SYSCLK multiplied by 7 15362306a36Sopenharmony_ci * 0111 = SYSCLK multiplied by 8 15462306a36Sopenharmony_ci * of 33.3MHz 15562306a36Sopenharmony_ci **/ 15662306a36Sopenharmony_cistatic int gx_freq_mult[16] = { 15762306a36Sopenharmony_ci 4, 10, 4, 6, 9, 5, 7, 8, 15862306a36Sopenharmony_ci 0, 0, 0, 0, 0, 0, 0, 0 15962306a36Sopenharmony_ci}; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci/**************************************************************** 16362306a36Sopenharmony_ci * Low Level chipset interface * 16462306a36Sopenharmony_ci ****************************************************************/ 16562306a36Sopenharmony_cistatic struct pci_device_id gx_chipset_tbl[] __initdata = { 16662306a36Sopenharmony_ci { PCI_VDEVICE(CYRIX, PCI_DEVICE_ID_CYRIX_5530_LEGACY), }, 16762306a36Sopenharmony_ci { PCI_VDEVICE(CYRIX, PCI_DEVICE_ID_CYRIX_5520), }, 16862306a36Sopenharmony_ci { PCI_VDEVICE(CYRIX, PCI_DEVICE_ID_CYRIX_5510), }, 16962306a36Sopenharmony_ci { 0, }, 17062306a36Sopenharmony_ci}; 17162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, gx_chipset_tbl); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic void gx_write_byte(int reg, int value) 17462306a36Sopenharmony_ci{ 17562306a36Sopenharmony_ci pci_write_config_byte(gx_params->cs55x0, reg, value); 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci/** 17962306a36Sopenharmony_ci * gx_detect_chipset: 18062306a36Sopenharmony_ci * 18162306a36Sopenharmony_ci **/ 18262306a36Sopenharmony_cistatic struct pci_dev * __init gx_detect_chipset(void) 18362306a36Sopenharmony_ci{ 18462306a36Sopenharmony_ci struct pci_dev *gx_pci = NULL; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci /* detect which companion chip is used */ 18762306a36Sopenharmony_ci for_each_pci_dev(gx_pci) { 18862306a36Sopenharmony_ci if ((pci_match_id(gx_chipset_tbl, gx_pci)) != NULL) 18962306a36Sopenharmony_ci return gx_pci; 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci pr_debug("error: no supported chipset found!\n"); 19362306a36Sopenharmony_ci return NULL; 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci/** 19762306a36Sopenharmony_ci * gx_get_cpuspeed: 19862306a36Sopenharmony_ci * 19962306a36Sopenharmony_ci * Finds out at which efficient frequency the Cyrix MediaGX/NatSemi 20062306a36Sopenharmony_ci * Geode CPU runs. 20162306a36Sopenharmony_ci */ 20262306a36Sopenharmony_cistatic unsigned int gx_get_cpuspeed(unsigned int cpu) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci if ((gx_params->pci_suscfg & SUSMOD) == 0) 20562306a36Sopenharmony_ci return stock_freq; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci return (stock_freq * gx_params->off_duration) 20862306a36Sopenharmony_ci / (gx_params->on_duration + gx_params->off_duration); 20962306a36Sopenharmony_ci} 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci/** 21262306a36Sopenharmony_ci * gx_validate_speed: 21362306a36Sopenharmony_ci * determine current cpu speed 21462306a36Sopenharmony_ci * 21562306a36Sopenharmony_ci **/ 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cistatic unsigned int gx_validate_speed(unsigned int khz, u8 *on_duration, 21862306a36Sopenharmony_ci u8 *off_duration) 21962306a36Sopenharmony_ci{ 22062306a36Sopenharmony_ci unsigned int i; 22162306a36Sopenharmony_ci u8 tmp_on, tmp_off; 22262306a36Sopenharmony_ci int old_tmp_freq = stock_freq; 22362306a36Sopenharmony_ci int tmp_freq; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci *off_duration = 1; 22662306a36Sopenharmony_ci *on_duration = 0; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci for (i = max_duration; i > 0; i--) { 22962306a36Sopenharmony_ci tmp_off = ((khz * i) / stock_freq) & 0xff; 23062306a36Sopenharmony_ci tmp_on = i - tmp_off; 23162306a36Sopenharmony_ci tmp_freq = (stock_freq * tmp_off) / i; 23262306a36Sopenharmony_ci /* if this relation is closer to khz, use this. If it's equal, 23362306a36Sopenharmony_ci * prefer it, too - lower latency */ 23462306a36Sopenharmony_ci if (abs(tmp_freq - khz) <= abs(old_tmp_freq - khz)) { 23562306a36Sopenharmony_ci *on_duration = tmp_on; 23662306a36Sopenharmony_ci *off_duration = tmp_off; 23762306a36Sopenharmony_ci old_tmp_freq = tmp_freq; 23862306a36Sopenharmony_ci } 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci return old_tmp_freq; 24262306a36Sopenharmony_ci} 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci/** 24662306a36Sopenharmony_ci * gx_set_cpuspeed: 24762306a36Sopenharmony_ci * set cpu speed in khz. 24862306a36Sopenharmony_ci **/ 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic void gx_set_cpuspeed(struct cpufreq_policy *policy, unsigned int khz) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci u8 suscfg, pmer1; 25362306a36Sopenharmony_ci unsigned int new_khz; 25462306a36Sopenharmony_ci unsigned long flags; 25562306a36Sopenharmony_ci struct cpufreq_freqs freqs; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci freqs.old = gx_get_cpuspeed(0); 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci new_khz = gx_validate_speed(khz, &gx_params->on_duration, 26062306a36Sopenharmony_ci &gx_params->off_duration); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci freqs.new = new_khz; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci cpufreq_freq_transition_begin(policy, &freqs); 26562306a36Sopenharmony_ci local_irq_save(flags); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci if (new_khz != stock_freq) { 26862306a36Sopenharmony_ci /* if new khz == 100% of CPU speed, it is special case */ 26962306a36Sopenharmony_ci switch (gx_params->cs55x0->device) { 27062306a36Sopenharmony_ci case PCI_DEVICE_ID_CYRIX_5530_LEGACY: 27162306a36Sopenharmony_ci pmer1 = gx_params->pci_pmer1 | IRQ_SPDUP | VID_SPDUP; 27262306a36Sopenharmony_ci /* FIXME: need to test other values -- Zwane,Miura */ 27362306a36Sopenharmony_ci /* typical 2 to 4ms */ 27462306a36Sopenharmony_ci gx_write_byte(PCI_IRQTC, 4); 27562306a36Sopenharmony_ci /* typical 50 to 100ms */ 27662306a36Sopenharmony_ci gx_write_byte(PCI_VIDTC, 100); 27762306a36Sopenharmony_ci gx_write_byte(PCI_PMER1, pmer1); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci if (gx_params->cs55x0->revision < 0x10) { 28062306a36Sopenharmony_ci /* CS5530(rev 1.2, 1.3) */ 28162306a36Sopenharmony_ci suscfg = gx_params->pci_suscfg|SUSMOD; 28262306a36Sopenharmony_ci } else { 28362306a36Sopenharmony_ci /* CS5530A,B.. */ 28462306a36Sopenharmony_ci suscfg = gx_params->pci_suscfg|SUSMOD|PWRSVE; 28562306a36Sopenharmony_ci } 28662306a36Sopenharmony_ci break; 28762306a36Sopenharmony_ci case PCI_DEVICE_ID_CYRIX_5520: 28862306a36Sopenharmony_ci case PCI_DEVICE_ID_CYRIX_5510: 28962306a36Sopenharmony_ci suscfg = gx_params->pci_suscfg | SUSMOD; 29062306a36Sopenharmony_ci break; 29162306a36Sopenharmony_ci default: 29262306a36Sopenharmony_ci local_irq_restore(flags); 29362306a36Sopenharmony_ci pr_debug("fatal: try to set unknown chipset.\n"); 29462306a36Sopenharmony_ci return; 29562306a36Sopenharmony_ci } 29662306a36Sopenharmony_ci } else { 29762306a36Sopenharmony_ci suscfg = gx_params->pci_suscfg & ~(SUSMOD); 29862306a36Sopenharmony_ci gx_params->off_duration = 0; 29962306a36Sopenharmony_ci gx_params->on_duration = 0; 30062306a36Sopenharmony_ci pr_debug("suspend modulation disabled: cpu runs 100%% speed.\n"); 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci gx_write_byte(PCI_MODOFF, gx_params->off_duration); 30462306a36Sopenharmony_ci gx_write_byte(PCI_MODON, gx_params->on_duration); 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci gx_write_byte(PCI_SUSCFG, suscfg); 30762306a36Sopenharmony_ci pci_read_config_byte(gx_params->cs55x0, PCI_SUSCFG, &suscfg); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci local_irq_restore(flags); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci gx_params->pci_suscfg = suscfg; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci cpufreq_freq_transition_end(policy, &freqs, 0); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci pr_debug("suspend modulation w/ duration of ON:%d us, OFF:%d us\n", 31662306a36Sopenharmony_ci gx_params->on_duration * 32, gx_params->off_duration * 32); 31762306a36Sopenharmony_ci pr_debug("suspend modulation w/ clock speed: %d kHz.\n", freqs.new); 31862306a36Sopenharmony_ci} 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci/**************************************************************** 32162306a36Sopenharmony_ci * High level functions * 32262306a36Sopenharmony_ci ****************************************************************/ 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci/* 32562306a36Sopenharmony_ci * cpufreq_gx_verify: test if frequency range is valid 32662306a36Sopenharmony_ci * 32762306a36Sopenharmony_ci * This function checks if a given frequency range in kHz is valid 32862306a36Sopenharmony_ci * for the hardware supported by the driver. 32962306a36Sopenharmony_ci */ 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_cistatic int cpufreq_gx_verify(struct cpufreq_policy_data *policy) 33262306a36Sopenharmony_ci{ 33362306a36Sopenharmony_ci unsigned int tmp_freq = 0; 33462306a36Sopenharmony_ci u8 tmp1, tmp2; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci if (!stock_freq || !policy) 33762306a36Sopenharmony_ci return -EINVAL; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci policy->cpu = 0; 34062306a36Sopenharmony_ci cpufreq_verify_within_limits(policy, (stock_freq / max_duration), 34162306a36Sopenharmony_ci stock_freq); 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci /* it needs to be assured that at least one supported frequency is 34462306a36Sopenharmony_ci * within policy->min and policy->max. If it is not, policy->max 34562306a36Sopenharmony_ci * needs to be increased until one frequency is supported. 34662306a36Sopenharmony_ci * policy->min may not be decreased, though. This way we guarantee a 34762306a36Sopenharmony_ci * specific processing capacity. 34862306a36Sopenharmony_ci */ 34962306a36Sopenharmony_ci tmp_freq = gx_validate_speed(policy->min, &tmp1, &tmp2); 35062306a36Sopenharmony_ci if (tmp_freq < policy->min) 35162306a36Sopenharmony_ci tmp_freq += stock_freq / max_duration; 35262306a36Sopenharmony_ci policy->min = tmp_freq; 35362306a36Sopenharmony_ci if (policy->min > policy->max) 35462306a36Sopenharmony_ci policy->max = tmp_freq; 35562306a36Sopenharmony_ci tmp_freq = gx_validate_speed(policy->max, &tmp1, &tmp2); 35662306a36Sopenharmony_ci if (tmp_freq > policy->max) 35762306a36Sopenharmony_ci tmp_freq -= stock_freq / max_duration; 35862306a36Sopenharmony_ci policy->max = tmp_freq; 35962306a36Sopenharmony_ci if (policy->max < policy->min) 36062306a36Sopenharmony_ci policy->max = policy->min; 36162306a36Sopenharmony_ci cpufreq_verify_within_limits(policy, (stock_freq / max_duration), 36262306a36Sopenharmony_ci stock_freq); 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci return 0; 36562306a36Sopenharmony_ci} 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci/* 36862306a36Sopenharmony_ci * cpufreq_gx_target: 36962306a36Sopenharmony_ci * 37062306a36Sopenharmony_ci */ 37162306a36Sopenharmony_cistatic int cpufreq_gx_target(struct cpufreq_policy *policy, 37262306a36Sopenharmony_ci unsigned int target_freq, 37362306a36Sopenharmony_ci unsigned int relation) 37462306a36Sopenharmony_ci{ 37562306a36Sopenharmony_ci u8 tmp1, tmp2; 37662306a36Sopenharmony_ci unsigned int tmp_freq; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci if (!stock_freq || !policy) 37962306a36Sopenharmony_ci return -EINVAL; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci policy->cpu = 0; 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci tmp_freq = gx_validate_speed(target_freq, &tmp1, &tmp2); 38462306a36Sopenharmony_ci while (tmp_freq < policy->min) { 38562306a36Sopenharmony_ci tmp_freq += stock_freq / max_duration; 38662306a36Sopenharmony_ci tmp_freq = gx_validate_speed(tmp_freq, &tmp1, &tmp2); 38762306a36Sopenharmony_ci } 38862306a36Sopenharmony_ci while (tmp_freq > policy->max) { 38962306a36Sopenharmony_ci tmp_freq -= stock_freq / max_duration; 39062306a36Sopenharmony_ci tmp_freq = gx_validate_speed(tmp_freq, &tmp1, &tmp2); 39162306a36Sopenharmony_ci } 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci gx_set_cpuspeed(policy, tmp_freq); 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci return 0; 39662306a36Sopenharmony_ci} 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_cistatic int cpufreq_gx_cpu_init(struct cpufreq_policy *policy) 39962306a36Sopenharmony_ci{ 40062306a36Sopenharmony_ci unsigned int maxfreq; 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci if (!policy || policy->cpu != 0) 40362306a36Sopenharmony_ci return -ENODEV; 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci /* determine maximum frequency */ 40662306a36Sopenharmony_ci if (pci_busclk) 40762306a36Sopenharmony_ci maxfreq = pci_busclk * gx_freq_mult[getCx86(CX86_DIR1) & 0x0f]; 40862306a36Sopenharmony_ci else if (cpu_khz) 40962306a36Sopenharmony_ci maxfreq = cpu_khz; 41062306a36Sopenharmony_ci else 41162306a36Sopenharmony_ci maxfreq = 30000 * gx_freq_mult[getCx86(CX86_DIR1) & 0x0f]; 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci stock_freq = maxfreq; 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci pr_debug("cpu max frequency is %d.\n", maxfreq); 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci /* setup basic struct for cpufreq API */ 41862306a36Sopenharmony_ci policy->cpu = 0; 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci if (max_duration < POLICY_MIN_DIV) 42162306a36Sopenharmony_ci policy->min = maxfreq / max_duration; 42262306a36Sopenharmony_ci else 42362306a36Sopenharmony_ci policy->min = maxfreq / POLICY_MIN_DIV; 42462306a36Sopenharmony_ci policy->max = maxfreq; 42562306a36Sopenharmony_ci policy->cpuinfo.min_freq = maxfreq / max_duration; 42662306a36Sopenharmony_ci policy->cpuinfo.max_freq = maxfreq; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci return 0; 42962306a36Sopenharmony_ci} 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci/* 43262306a36Sopenharmony_ci * cpufreq_gx_init: 43362306a36Sopenharmony_ci * MediaGX/Geode GX initialize cpufreq driver 43462306a36Sopenharmony_ci */ 43562306a36Sopenharmony_cistatic struct cpufreq_driver gx_suspmod_driver = { 43662306a36Sopenharmony_ci .flags = CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING, 43762306a36Sopenharmony_ci .get = gx_get_cpuspeed, 43862306a36Sopenharmony_ci .verify = cpufreq_gx_verify, 43962306a36Sopenharmony_ci .target = cpufreq_gx_target, 44062306a36Sopenharmony_ci .init = cpufreq_gx_cpu_init, 44162306a36Sopenharmony_ci .name = "gx-suspmod", 44262306a36Sopenharmony_ci}; 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_cistatic int __init cpufreq_gx_init(void) 44562306a36Sopenharmony_ci{ 44662306a36Sopenharmony_ci int ret; 44762306a36Sopenharmony_ci struct gxfreq_params *params; 44862306a36Sopenharmony_ci struct pci_dev *gx_pci; 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci /* Test if we have the right hardware */ 45162306a36Sopenharmony_ci gx_pci = gx_detect_chipset(); 45262306a36Sopenharmony_ci if (gx_pci == NULL) 45362306a36Sopenharmony_ci return -ENODEV; 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci /* check whether module parameters are sane */ 45662306a36Sopenharmony_ci if (max_duration > 0xff) 45762306a36Sopenharmony_ci max_duration = 0xff; 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci pr_debug("geode suspend modulation available.\n"); 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci params = kzalloc(sizeof(*params), GFP_KERNEL); 46262306a36Sopenharmony_ci if (params == NULL) 46362306a36Sopenharmony_ci return -ENOMEM; 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci params->cs55x0 = gx_pci; 46662306a36Sopenharmony_ci gx_params = params; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci /* keep cs55x0 configurations */ 46962306a36Sopenharmony_ci pci_read_config_byte(params->cs55x0, PCI_SUSCFG, &(params->pci_suscfg)); 47062306a36Sopenharmony_ci pci_read_config_byte(params->cs55x0, PCI_PMER1, &(params->pci_pmer1)); 47162306a36Sopenharmony_ci pci_read_config_byte(params->cs55x0, PCI_PMER2, &(params->pci_pmer2)); 47262306a36Sopenharmony_ci pci_read_config_byte(params->cs55x0, PCI_MODON, &(params->on_duration)); 47362306a36Sopenharmony_ci pci_read_config_byte(params->cs55x0, PCI_MODOFF, 47462306a36Sopenharmony_ci &(params->off_duration)); 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci ret = cpufreq_register_driver(&gx_suspmod_driver); 47762306a36Sopenharmony_ci if (ret) { 47862306a36Sopenharmony_ci kfree(params); 47962306a36Sopenharmony_ci return ret; /* register error! */ 48062306a36Sopenharmony_ci } 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci return 0; 48362306a36Sopenharmony_ci} 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_cistatic void __exit cpufreq_gx_exit(void) 48662306a36Sopenharmony_ci{ 48762306a36Sopenharmony_ci cpufreq_unregister_driver(&gx_suspmod_driver); 48862306a36Sopenharmony_ci pci_dev_put(gx_params->cs55x0); 48962306a36Sopenharmony_ci kfree(gx_params); 49062306a36Sopenharmony_ci} 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ciMODULE_AUTHOR("Hiroshi Miura <miura@da-cha.org>"); 49362306a36Sopenharmony_ciMODULE_DESCRIPTION("Cpufreq driver for Cyrix MediaGX and NatSemi Geode"); 49462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_cimodule_init(cpufreq_gx_init); 49762306a36Sopenharmony_cimodule_exit(cpufreq_gx_exit); 49862306a36Sopenharmony_ci 499