cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

gx-suspmod.c (14247B)


      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