powernow-k6.c 6.6 KB
Newer Older
L
Linus Torvalds 已提交
1 2
/*
 *  This file was based upon code in Powertweak Linux (http://powertweak.sf.net)
3 4
 *  (C) 2000-2003  Dave Jones, Arjan van de Ven, Janne Pänkälä,
 *                 Dominik Brodowski.
L
Linus Torvalds 已提交
5 6 7 8 9 10 11
 *
 *  Licensed under the terms of the GNU GPL License version 2.
 *
 *  BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous*
 */

#include <linux/kernel.h>
12
#include <linux/module.h>
L
Linus Torvalds 已提交
13 14 15
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/ioport.h>
16 17
#include <linux/timex.h>
#include <linux/io.h>
L
Linus Torvalds 已提交
18

19
#include <asm/cpu_device_id.h>
20 21
#include <asm/msr.h>

22 23
#define POWERNOW_IOPORT 0xfff0          /* it doesn't matter where, as long
					   as it is unused */
L
Linus Torvalds 已提交
24

25
#define PFX "powernow-k6: "
L
Linus Torvalds 已提交
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
static unsigned int                     busfreq;   /* FSB, in 10 kHz */
static unsigned int                     max_multiplier;


/* Clock ratio multiplied by 10 - see table 27 in AMD#23446 */
static struct cpufreq_frequency_table clock_ratio[] = {
	{45,  /* 000 -> 4.5x */ 0},
	{50,  /* 001 -> 5.0x */ 0},
	{40,  /* 010 -> 4.0x */ 0},
	{55,  /* 011 -> 5.5x */ 0},
	{20,  /* 100 -> 2.0x */ 0},
	{30,  /* 101 -> 3.0x */ 0},
	{60,  /* 110 -> 6.0x */ 0},
	{35,  /* 111 -> 3.5x */ 0},
	{0, CPUFREQ_TABLE_END}
};


/**
 * powernow_k6_get_cpu_multiplier - returns the current FSB multiplier
 *
 *   Returns the current setting of the frequency multiplier. Core clock
 * speed is frequency of the Front-Side Bus multiplied with this value.
 */
static int powernow_k6_get_cpu_multiplier(void)
{
52 53
	u64 invalue = 0;
	u32 msrval;
54

L
Linus Torvalds 已提交
55 56
	msrval = POWERNOW_IOPORT + 0x1;
	wrmsr(MSR_K6_EPMR, msrval, 0); /* enable the PowerNow port */
57
	invalue = inl(POWERNOW_IOPORT + 0x8);
L
Linus Torvalds 已提交
58 59 60
	msrval = POWERNOW_IOPORT + 0x0;
	wrmsr(MSR_K6_EPMR, msrval, 0); /* disable it again */

61
	return clock_ratio[(invalue >> 5)&7].driver_data;
L
Linus Torvalds 已提交
62 63 64 65 66 67 68 69 70
}


/**
 * powernow_k6_set_state - set the PowerNow! multiplier
 * @best_i: clock_ratio[best_i] is the target multiplier
 *
 *   Tries to change the PowerNow! multiplier
 */
71 72
static void powernow_k6_set_state(struct cpufreq_policy *policy,
		unsigned int best_i)
L
Linus Torvalds 已提交
73
{
74 75 76
	unsigned long outvalue = 0, invalue = 0;
	unsigned long msrval;
	struct cpufreq_freqs freqs;
L
Linus Torvalds 已提交
77

78
	if (clock_ratio[best_i].driver_data > max_multiplier) {
79
		printk(KERN_ERR PFX "invalid target frequency\n");
L
Linus Torvalds 已提交
80 81 82 83
		return;
	}

	freqs.old = busfreq * powernow_k6_get_cpu_multiplier();
84
	freqs.new = busfreq * clock_ratio[best_i].driver_data;
85

86
	cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
L
Linus Torvalds 已提交
87 88 89 90 91 92 93

	/* we now need to transform best_i to the BVC format, see AMD#23446 */

	outvalue = (1<<12) | (1<<10) | (1<<9) | (best_i<<5);

	msrval = POWERNOW_IOPORT + 0x1;
	wrmsr(MSR_K6_EPMR, msrval, 0); /* enable the PowerNow port */
94
	invalue = inl(POWERNOW_IOPORT + 0x8);
L
Linus Torvalds 已提交
95 96
	invalue = invalue & 0xf;
	outvalue = outvalue | invalue;
97
	outl(outvalue , (POWERNOW_IOPORT + 0x8));
L
Linus Torvalds 已提交
98 99 100
	msrval = POWERNOW_IOPORT + 0x0;
	wrmsr(MSR_K6_EPMR, msrval, 0); /* disable it again */

101
	cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
L
Linus Torvalds 已提交
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123

	return;
}


/**
 * powernow_k6_verify - verifies a new CPUfreq policy
 * @policy: new policy
 *
 * Policy must be within lowest and highest possible CPU Frequency,
 * and at least one possible state must be within min and max.
 */
static int powernow_k6_verify(struct cpufreq_policy *policy)
{
	return cpufreq_frequency_table_verify(policy, &clock_ratio[0]);
}


/**
 * powernow_k6_setpolicy - sets a new CPUFreq policy
 * @policy: new policy
 * @target_freq: the target frequency
124 125
 * @relation: how that frequency relates to achieved frequency
 *  (CPUFREQ_RELATION_L or CPUFREQ_RELATION_H)
L
Linus Torvalds 已提交
126 127 128
 *
 * sets a new CPUFreq policy
 */
129
static int powernow_k6_target(struct cpufreq_policy *policy,
L
Linus Torvalds 已提交
130 131 132
			       unsigned int target_freq,
			       unsigned int relation)
{
133
	unsigned int newstate = 0;
L
Linus Torvalds 已提交
134

135 136
	if (cpufreq_frequency_table_target(policy, &clock_ratio[0],
				target_freq, relation, &newstate))
L
Linus Torvalds 已提交
137 138
		return -EINVAL;

139
	powernow_k6_set_state(policy, newstate);
L
Linus Torvalds 已提交
140 141 142 143 144 145 146

	return 0;
}


static int powernow_k6_cpu_init(struct cpufreq_policy *policy)
{
147
	unsigned int i, f;
L
Linus Torvalds 已提交
148 149 150 151 152 153 154 155 156 157
	int result;

	if (policy->cpu != 0)
		return -ENODEV;

	/* get frequencies */
	max_multiplier = powernow_k6_get_cpu_multiplier();
	busfreq = cpu_khz / max_multiplier;

	/* table init */
158
	for (i = 0; (clock_ratio[i].frequency != CPUFREQ_TABLE_END); i++) {
159
		f = clock_ratio[i].driver_data;
160
		if (f > max_multiplier)
L
Linus Torvalds 已提交
161 162
			clock_ratio[i].frequency = CPUFREQ_ENTRY_INVALID;
		else
163
			clock_ratio[i].frequency = busfreq * f;
L
Linus Torvalds 已提交
164 165 166
	}

	/* cpuinfo and default policy values */
167
	policy->cpuinfo.transition_latency = 200000;
L
Linus Torvalds 已提交
168 169 170 171
	policy->cur = busfreq * max_multiplier;

	result = cpufreq_frequency_table_cpuinfo(policy, clock_ratio);
	if (result)
172
		return result;
L
Linus Torvalds 已提交
173 174 175 176 177 178 179 180 181 182

	cpufreq_frequency_table_get_attr(clock_ratio, policy->cpu);

	return 0;
}


static int powernow_k6_cpu_exit(struct cpufreq_policy *policy)
{
	unsigned int i;
183 184
	for (i = 0; i < 8; i++) {
		if (i == max_multiplier)
185
			powernow_k6_set_state(policy, i);
L
Linus Torvalds 已提交
186 187
	}
	cpufreq_frequency_table_put_attr(policy->cpu);
188
	return 0;
L
Linus Torvalds 已提交
189 190 191 192
}

static unsigned int powernow_k6_get(unsigned int cpu)
{
193 194 195
	unsigned int ret;
	ret = (busfreq * powernow_k6_get_cpu_multiplier());
	return ret;
L
Linus Torvalds 已提交
196 197
}

198
static struct freq_attr *powernow_k6_attr[] = {
L
Linus Torvalds 已提交
199 200 201 202
	&cpufreq_freq_attr_scaling_available_freqs,
	NULL,
};

203
static struct cpufreq_driver powernow_k6_driver = {
204 205
	.verify		= powernow_k6_verify,
	.target		= powernow_k6_target,
L
Linus Torvalds 已提交
206 207 208 209 210 211 212
	.init		= powernow_k6_cpu_init,
	.exit		= powernow_k6_cpu_exit,
	.get		= powernow_k6_get,
	.name		= "powernow-k6",
	.attr		= powernow_k6_attr,
};

213 214 215 216 217
static const struct x86_cpu_id powernow_k6_ids[] = {
	{ X86_VENDOR_AMD, 5, 12 },
	{ X86_VENDOR_AMD, 5, 13 },
	{}
};
218
MODULE_DEVICE_TABLE(x86cpu, powernow_k6_ids);
L
Linus Torvalds 已提交
219 220 221 222 223 224 225 226 227

/**
 * powernow_k6_init - initializes the k6 PowerNow! CPUFreq driver
 *
 *   Initializes the K6 PowerNow! support. Returns -ENODEV on unsupported
 * devices, -EINVAL or -ENOMEM on problems during initiatization, and zero
 * on success.
 */
static int __init powernow_k6_init(void)
228
{
229
	if (!x86_match_cpu(powernow_k6_ids))
L
Linus Torvalds 已提交
230 231 232
		return -ENODEV;

	if (!request_region(POWERNOW_IOPORT, 16, "PowerNow!")) {
233
		printk(KERN_INFO PFX "PowerNow IOPORT region already used.\n");
L
Linus Torvalds 已提交
234 235 236 237
		return -EIO;
	}

	if (cpufreq_register_driver(&powernow_k6_driver)) {
238
		release_region(POWERNOW_IOPORT, 16);
L
Linus Torvalds 已提交
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
		return -EINVAL;
	}

	return 0;
}


/**
 * powernow_k6_exit - unregisters AMD K6-2+/3+ PowerNow! support
 *
 *   Unregisters AMD K6-2+ / K6-3+ PowerNow! support.
 */
static void __exit powernow_k6_exit(void)
{
	cpufreq_unregister_driver(&powernow_k6_driver);
254
	release_region(POWERNOW_IOPORT, 16);
L
Linus Torvalds 已提交
255 256 257
}


258 259
MODULE_AUTHOR("Arjan van de Ven, Dave Jones <davej@redhat.com>, "
		"Dominik Brodowski <linux@brodo.de>");
260 261
MODULE_DESCRIPTION("PowerNow! driver for AMD K6-2+ / K6-3+ processors.");
MODULE_LICENSE("GPL");
L
Linus Torvalds 已提交
262 263 264

module_init(powernow_k6_init);
module_exit(powernow_k6_exit);