brintos

brintos / linux-shallow public Read only

0
0
Text · 13.9 KiB · 75b3ef7 Raw
499 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 *	Cyrix MediaGX and NatSemi Geode Suspend Modulation4 *	(C) 2002 Zwane Mwaikambo <zwane@commfireservices.com>5 *	(C) 2002 Hiroshi Miura   <miura@da-cha.org>6 *	All Rights Reserved7 *8 *      The author(s) of this software shall not be held liable for damages9 *      of any nature resulting due to the use of this software. This10 *      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 CS55x017 *	are based on Suspend Modulation.18 *19 *	Suspend Modulation works by asserting and de-asserting the SUSP# pin20 *	to CPU(GX1/GXLV) for configurable durations. When asserting SUSP#21 *	the CPU enters an idle state. GX1 stops its core clock when SUSP# is22 *	asserted then power consumption is reduced.23 *24 *	Suspend Modulation's OFF/ON duration are configurable25 *	with 'Suspend Modulation OFF Count Register'26 *	and 'Suspend Modulation ON Count Register'.27 *	These registers are 8bit counters that represent the number of28 *	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 frequency32 *	of operation of the system.33 *34 *			       OFF Count35 *	F_eff = Fgx * ----------------------36 *	                OFF Count + ON Count37 *38 *	0 <= On Count, Off Count <= 25539 *40 *	From these limits, we can get register values41 *42 *	off_duration + on_duration <= MAX_DURATION43 *	on_duration = off_duration * (stock_freq - freq) / freq44 *45 *      off_duration  =  (freq * DURATION) / stock_freq46 *      on_duration = DURATION - off_duration47 *48 *---------------------------------------------------------------------------49 *50 * ChangeLog:51 *	Dec. 12, 2003	Hiroshi Miura <miura@da-cha.org>52 *		- fix on/off register mistake53 *		- 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 and57 *		  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 * Todo65 *	Test on machines with 5510, 5530, 5530A66 */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 suspended128 * (32us * MAX_DURATION). If no parameter is given, this defaults129 * to 255.130 * Note that this leads to a maximum of 8 ms(!) where the CPU clock131 * 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 power137 * - let's say 5%. (min = maxfreq / POLICY_MIN_DIV)138 */139#define POLICY_MIN_DIV 20140 141 142/**143 * we can detect a core multiplier from dir0_lsb144 * from GX1 datasheet p.56,145 *	MULT[3:0]:146 *	0000 = SYSCLK multiplied by 4 (test only)147 *	0001 = SYSCLK multiplied by 10148 *	0010 = SYSCLK multiplied by 4149 *	0011 = SYSCLK multiplied by 6150 *	0100 = SYSCLK multiplied by 9151 *	0101 = SYSCLK multiplied by 5152 *	0110 = SYSCLK multiplied by 7153 *	0111 = SYSCLK multiplied by 8154 *              of 33.3MHz155 **/156static int gx_freq_mult[16] = {157		4, 10, 4, 6, 9, 5, 7, 8,158		0, 0, 0, 0, 0, 0, 0, 0159};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/NatSemi200 * 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 speed214 *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 valid326 *327 *	This function checks if a given frequency range in kHz is valid328 *      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 is344	 * within policy->min and policy->max. If it is not, policy->max345	 * needs to be increased until one frequency is supported.346	 * policy->min may not be decreased, though. This way we guarantee a347	 * 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	else411		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	else423		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 driver434 */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