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 61 62 63 64 65 66 67 68 69 70
	msrval = POWERNOW_IOPORT + 0x0;
	wrmsr(MSR_K6_EPMR, msrval, 0); /* disable it again */

	return clock_ratio[(invalue >> 5)&7].index;
}


/**
 * 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
static void powernow_k6_set_state(unsigned int best_i)
L
Linus Torvalds 已提交
72
{
73 74 75
	unsigned long outvalue = 0, invalue = 0;
	unsigned long msrval;
	struct cpufreq_freqs freqs;
L
Linus Torvalds 已提交
76 77

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

	freqs.old = busfreq * powernow_k6_get_cpu_multiplier();
	freqs.new = busfreq * clock_ratio[best_i].index;
	freqs.cpu = 0; /* powernow-k6.c is UP only driver */
85

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

	/* 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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
	msrval = POWERNOW_IOPORT + 0x0;
	wrmsr(MSR_K6_EPMR, msrval, 0); /* disable it again */

	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);

	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 139 140 141 142 143 144 145 146
		return -EINVAL;

	powernow_k6_set_state(newstate);

	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 160
		f = clock_ratio[i].index;
		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)
L
Linus Torvalds 已提交
185 186 187
			powernow_k6_set_state(i);
	}
	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 213
	.init		= powernow_k6_cpu_init,
	.exit		= powernow_k6_cpu_exit,
	.get		= powernow_k6_get,
	.name		= "powernow-k6",
	.owner		= THIS_MODULE,
	.attr		= powernow_k6_attr,
};

214 215 216 217 218 219
static const struct x86_cpu_id powernow_k6_ids[] = {
	{ X86_VENDOR_AMD, 5, 12 },
	{ X86_VENDOR_AMD, 5, 13 },
	{}
};

L
Linus Torvalds 已提交
220 221 222 223 224 225 226 227 228

/**
 * 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)
229
{
230
	if (!x86_match_cpu(powernow_k6_ids))
L
Linus Torvalds 已提交
231 232 233
		return -ENODEV;

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

	if (cpufreq_register_driver(&powernow_k6_driver)) {
239
		release_region(POWERNOW_IOPORT, 16);
L
Linus Torvalds 已提交
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
		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);
255
	release_region(POWERNOW_IOPORT, 16);
L
Linus Torvalds 已提交
256 257 258
}


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

module_init(powernow_k6_init);
module_exit(powernow_k6_exit);