powernow-k6.c 6.5 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

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

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

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

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

169
	return cpufreq_table_validate_and_show(policy, clock_ratio);
L
Linus Torvalds 已提交
170 171 172 173 174 175
}


static int powernow_k6_cpu_exit(struct cpufreq_policy *policy)
{
	unsigned int i;
176 177
	for (i = 0; i < 8; i++) {
		if (i == max_multiplier)
178
			powernow_k6_set_state(policy, i);
L
Linus Torvalds 已提交
179 180
	}
	cpufreq_frequency_table_put_attr(policy->cpu);
181
	return 0;
L
Linus Torvalds 已提交
182 183 184 185
}

static unsigned int powernow_k6_get(unsigned int cpu)
{
186 187 188
	unsigned int ret;
	ret = (busfreq * powernow_k6_get_cpu_multiplier());
	return ret;
L
Linus Torvalds 已提交
189 190
}

191
static struct freq_attr *powernow_k6_attr[] = {
L
Linus Torvalds 已提交
192 193 194 195
	&cpufreq_freq_attr_scaling_available_freqs,
	NULL,
};

196
static struct cpufreq_driver powernow_k6_driver = {
197 198
	.verify		= powernow_k6_verify,
	.target		= powernow_k6_target,
L
Linus Torvalds 已提交
199 200 201 202 203 204 205
	.init		= powernow_k6_cpu_init,
	.exit		= powernow_k6_cpu_exit,
	.get		= powernow_k6_get,
	.name		= "powernow-k6",
	.attr		= powernow_k6_attr,
};

206 207 208 209 210
static const struct x86_cpu_id powernow_k6_ids[] = {
	{ X86_VENDOR_AMD, 5, 12 },
	{ X86_VENDOR_AMD, 5, 13 },
	{}
};
211
MODULE_DEVICE_TABLE(x86cpu, powernow_k6_ids);
L
Linus Torvalds 已提交
212 213 214 215 216 217 218 219 220

/**
 * 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)
221
{
222
	if (!x86_match_cpu(powernow_k6_ids))
L
Linus Torvalds 已提交
223 224 225
		return -ENODEV;

	if (!request_region(POWERNOW_IOPORT, 16, "PowerNow!")) {
226
		printk(KERN_INFO PFX "PowerNow IOPORT region already used.\n");
L
Linus Torvalds 已提交
227 228 229 230
		return -EIO;
	}

	if (cpufreq_register_driver(&powernow_k6_driver)) {
231
		release_region(POWERNOW_IOPORT, 16);
L
Linus Torvalds 已提交
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
		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);
247
	release_region(POWERNOW_IOPORT, 16);
L
Linus Torvalds 已提交
248 249 250
}


251 252
MODULE_AUTHOR("Arjan van de Ven, Dave Jones <davej@redhat.com>, "
		"Dominik Brodowski <linux@brodo.de>");
253 254
MODULE_DESCRIPTION("PowerNow! driver for AMD K6-2+ / K6-3+ processors.");
MODULE_LICENSE("GPL");
L
Linus Torvalds 已提交
255 256 257

module_init(powernow_k6_init);
module_exit(powernow_k6_exit);