elanfreq.c 5.8 KB
Newer Older
L
Linus Torvalds 已提交
1
/*
2
 *	elanfreq:	cpufreq driver for the AMD ELAN family
L
Linus Torvalds 已提交
3 4 5
 *
 *	(c) Copyright 2002 Robert Schwebel <r.schwebel@pengutronix.de>
 *
6
 *	Parts of this code are (c) Sven Geggus <sven@geggus.net>
L
Linus Torvalds 已提交
7
 *
8
 *      All Rights Reserved.
L
Linus Torvalds 已提交
9 10 11 12
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version
13
 *	2 of the License, or (at your option) any later version.
L
Linus Torvalds 已提交
14 15 16 17 18 19 20 21 22 23 24 25
 *
 *	2002-02-13: - initial revision for 2.4.18-pre9 by Robert Schwebel
 *
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>

#include <linux/delay.h>
#include <linux/cpufreq.h>

26
#include <asm/cpu_device_id.h>
L
Linus Torvalds 已提交
27
#include <asm/msr.h>
28 29
#include <linux/timex.h>
#include <linux/io.h>
L
Linus Torvalds 已提交
30

31
#define REG_CSCIR 0x22		/* Chip Setup and Control Index Register    */
L
Linus Torvalds 已提交
32 33 34 35 36 37 38 39 40 41 42 43
#define REG_CSCDR 0x23		/* Chip Setup and Control Data  Register    */

/* Module parameter */
static int max_freq;

struct s_elan_multiplier {
	int clock;		/* frequency in kHz                         */
	int val40h;		/* PMU Force Mode register                  */
	int val80h;		/* CPU Clock Speed Register                 */
};

/*
44
 * It is important that the frequencies
L
Linus Torvalds 已提交
45 46
 * are listed in ascending order here!
 */
D
Dave Jones 已提交
47
static struct s_elan_multiplier elan_multiplier[] = {
L
Linus Torvalds 已提交
48 49 50 51 52 53 54 55 56 57 58
	{1000,	0x02,	0x18},
	{2000,	0x02,	0x10},
	{4000,	0x02,	0x08},
	{8000,	0x00,	0x00},
	{16000,	0x00,	0x02},
	{33000,	0x00,	0x04},
	{66000,	0x01,	0x04},
	{99000,	0x01,	0x05}
};

static struct cpufreq_frequency_table elanfreq_table[] = {
59 60 61 62 63 64 65 66 67
	{0, 0,	1000},
	{0, 1,	2000},
	{0, 2,	4000},
	{0, 3,	8000},
	{0, 4,	16000},
	{0, 5,	33000},
	{0, 6,	66000},
	{0, 7,	99000},
	{0, 0,	CPUFREQ_TABLE_END},
L
Linus Torvalds 已提交
68 69 70 71 72 73 74
};


/**
 *	elanfreq_get_cpu_frequency: determine current cpu speed
 *
 *	Finds out at which frequency the CPU of the Elan SOC runs
75
 *	at the moment. Frequencies from 1 to 33 MHz are generated
L
Linus Torvalds 已提交
76
 *	the normal way, 66 and 99 MHz are called "Hyperspeed Mode"
77
 *	and have the rest of the chip running with 33 MHz.
L
Linus Torvalds 已提交
78 79 80 81
 */

static unsigned int elanfreq_get_cpu_frequency(unsigned int cpu)
{
82 83
	u8 clockspeed_reg;    /* Clock Speed Register */

L
Linus Torvalds 已提交
84
	local_irq_disable();
85
	outb_p(0x80, REG_CSCIR);
86
	clockspeed_reg = inb_p(REG_CSCDR);
L
Linus Torvalds 已提交
87 88
	local_irq_enable();

89 90
	if ((clockspeed_reg & 0xE0) == 0xE0)
		return 0;
L
Linus Torvalds 已提交
91

92 93 94
	/* Are we in CPU clock multiplied mode (66/99 MHz)? */
	if ((clockspeed_reg & 0xE0) == 0xC0) {
		if ((clockspeed_reg & 0x01) == 0)
L
Linus Torvalds 已提交
95
			return 66000;
96 97 98
		else
			return 99000;
	}
L
Linus Torvalds 已提交
99 100

	/* 33 MHz is not 32 MHz... */
101
	if ((clockspeed_reg & 0xE0) == 0xA0)
L
Linus Torvalds 已提交
102 103
		return 33000;

104
	return (1<<((clockspeed_reg & 0xE0) >> 5)) * 1000;
L
Linus Torvalds 已提交
105 106 107
}


108 109
static int elanfreq_target(struct cpufreq_policy *policy,
			    unsigned int state)
110 111 112 113 114
{
	/*
	 * Access to the Elan's internal registers is indexed via
	 * 0x22: Chip Setup & Control Register Index Register (CSCI)
	 * 0x23: Chip Setup & Control Register Data  Register (CSCD)
L
Linus Torvalds 已提交
115 116 117
	 *
	 */

118 119
	/*
	 * 0x40 is the Power Management Unit's Force Mode Register.
L
Linus Torvalds 已提交
120 121 122 123
	 * Bit 6 enables Hyperspeed Mode (66/100 MHz core frequency)
	 */

	local_irq_disable();
124 125
	outb_p(0x40, REG_CSCIR);		/* Disable hyperspeed mode */
	outb_p(0x00, REG_CSCDR);
L
Linus Torvalds 已提交
126 127 128 129 130 131
	local_irq_enable();		/* wait till internal pipelines and */
	udelay(1000);			/* buffers have cleaned up          */

	local_irq_disable();

	/* now, set the CPU clock speed register (0x80) */
132 133
	outb_p(0x80, REG_CSCIR);
	outb_p(elan_multiplier[state].val80h, REG_CSCDR);
L
Linus Torvalds 已提交
134 135

	/* now, the hyperspeed bit in PMU Force Mode Register (0x40) */
136 137
	outb_p(0x40, REG_CSCIR);
	outb_p(elan_multiplier[state].val40h, REG_CSCDR);
L
Linus Torvalds 已提交
138 139 140 141 142 143 144 145 146 147 148
	udelay(10000);
	local_irq_enable();

	return 0;
}
/*
 *	Module init and exit code
 */

static int elanfreq_cpu_init(struct cpufreq_policy *policy)
{
149
	struct cpuinfo_x86 *c = &cpu_data(0);
L
Linus Torvalds 已提交
150 151 152 153
	unsigned int i;

	/* capability check */
	if ((c->x86_vendor != X86_VENDOR_AMD) ||
154
	    (c->x86 != 4) || (c->x86_model != 10))
L
Linus Torvalds 已提交
155 156 157 158 159 160 161
		return -ENODEV;

	/* max freq */
	if (!max_freq)
		max_freq = elanfreq_get_cpu_frequency(0);

	/* table init */
162
	for (i = 0; (elanfreq_table[i].frequency != CPUFREQ_TABLE_END); i++) {
L
Linus Torvalds 已提交
163 164 165 166 167 168 169
		if (elanfreq_table[i].frequency > max_freq)
			elanfreq_table[i].frequency = CPUFREQ_ENTRY_INVALID;
	}

	/* cpuinfo and default policy values */
	policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;

170
	return cpufreq_table_validate_and_show(policy, elanfreq_table);
L
Linus Torvalds 已提交
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
}


#ifndef MODULE
/**
 * elanfreq_setup - elanfreq command line parameter parsing
 *
 * elanfreq command line parameter.  Use:
 *  elanfreq=66000
 * to set the maximum CPU frequency to 66 MHz. Note that in
 * case you do not give this boot parameter, the maximum
 * frequency will fall back to _current_ CPU frequency which
 * might be lower. If you build this as a module, use the
 * max_freq module parameter instead.
 */
static int __init elanfreq_setup(char *str)
{
	max_freq = simple_strtoul(str, &str, 0);
	printk(KERN_WARNING "You're using the deprecated elanfreq command line option. Use elanfreq.max_freq instead, please!\n");
	return 1;
}
__setup("elanfreq=", elanfreq_setup);
#endif


196
static struct cpufreq_driver elanfreq_driver = {
197
	.get		= elanfreq_get_cpu_frequency,
198
	.verify		= cpufreq_generic_frequency_table_verify,
199
	.target_index	= elanfreq_target,
L
Linus Torvalds 已提交
200 201
	.init		= elanfreq_cpu_init,
	.name		= "elanfreq",
202
	.attr		= cpufreq_generic_attr,
L
Linus Torvalds 已提交
203 204
};

205 206 207 208 209
static const struct x86_cpu_id elan_id[] = {
	{ X86_VENDOR_AMD, 4, 10, },
	{}
};
MODULE_DEVICE_TABLE(x86cpu, elan_id);
L
Linus Torvalds 已提交
210

211 212
static int __init elanfreq_init(void)
{
213
	if (!x86_match_cpu(elan_id))
214
		return -ENODEV;
L
Linus Torvalds 已提交
215 216 217 218
	return cpufreq_register_driver(&elanfreq_driver);
}


219
static void __exit elanfreq_exit(void)
L
Linus Torvalds 已提交
220 221 222 223 224
{
	cpufreq_unregister_driver(&elanfreq_driver);
}


225
module_param(max_freq, int, 0444);
L
Linus Torvalds 已提交
226 227

MODULE_LICENSE("GPL");
228 229
MODULE_AUTHOR("Robert Schwebel <r.schwebel@pengutronix.de>, "
		"Sven Geggus <sven@geggus.net>");
L
Linus Torvalds 已提交
230 231 232 233
MODULE_DESCRIPTION("cpufreq driver for AMD's Elan CPUs");

module_init(elanfreq_init);
module_exit(elanfreq_exit);