cpufreq-nforce2.c 9.34 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
/*
2
 * (C) 2004-2006  Sebastian Witt <se.witt@gmx.net>
Linus Torvalds's avatar
Linus Torvalds committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 *
 *  Licensed under the terms of the GNU GPL License version 2.
 *  Based upon reverse engineered information
 *
 *  BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous*
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/pci.h>
#include <linux/delay.h>

#define NFORCE2_XTAL 25
#define NFORCE2_BOOTFSB 0x48
#define NFORCE2_PLLENABLE 0xa8
#define NFORCE2_PLLREG 0xa4
#define NFORCE2_PLLADR 0xa0
#define NFORCE2_PLL(mul, div) (0x100000 | (mul << 8) | div)

#define NFORCE2_MIN_FSB 50
#define NFORCE2_SAFE_DISTANCE 50

/* Delay in ms between FSB changes */
29
/* #define NFORCE2_DELAY 10 */
Linus Torvalds's avatar
Linus Torvalds committed
30

31 32
/*
 * nforce2_chipset:
Linus Torvalds's avatar
Linus Torvalds committed
33 34
 * FSB is changed using the chipset
 */
35
static struct pci_dev *nforce2_dev;
Linus Torvalds's avatar
Linus Torvalds committed
36 37 38 39

/* fid:
 * multiplier * 10
 */
40
static int fid;
Linus Torvalds's avatar
Linus Torvalds committed
41 42

/* min_fsb, max_fsb:
43
 * minimum and maximum FSB (= FSB at boot time)
Linus Torvalds's avatar
Linus Torvalds committed
44
 */
45 46
static int min_fsb;
static int max_fsb;
Linus Torvalds's avatar
Linus Torvalds committed
47 48 49 50 51 52 53 54 55 56

MODULE_AUTHOR("Sebastian Witt <se.witt@gmx.net>");
MODULE_DESCRIPTION("nForce2 FSB changing cpufreq driver");
MODULE_LICENSE("GPL");

module_param(fid, int, 0444);
module_param(min_fsb, int, 0444);

MODULE_PARM_DESC(fid, "CPU multiplier to use (11.5 = 115)");
MODULE_PARM_DESC(min_fsb,
57
		"Minimum FSB to use, if not defined: current FSB - 50");
Linus Torvalds's avatar
Linus Torvalds committed
58

59 60
#define dprintk(msg...) cpufreq_debug_printk(CPUFREQ_DEBUG_DRIVER, \
		"cpufreq-nforce2", msg)
Linus Torvalds's avatar
Linus Torvalds committed
61

62
/**
Linus Torvalds's avatar
Linus Torvalds committed
63 64
 * nforce2_calc_fsb - calculate FSB
 * @pll: PLL value
65
 *
Linus Torvalds's avatar
Linus Torvalds committed
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
 *   Calculates FSB from PLL value
 */
static int nforce2_calc_fsb(int pll)
{
	unsigned char mul, div;

	mul = (pll >> 8) & 0xff;
	div = pll & 0xff;

	if (div > 0)
		return NFORCE2_XTAL * mul / div;

	return 0;
}

81
/**
Linus Torvalds's avatar
Linus Torvalds committed
82 83
 * nforce2_calc_pll - calculate PLL value
 * @fsb: FSB
84
 *
Linus Torvalds's avatar
Linus Torvalds committed
85 86 87 88 89 90 91 92 93 94
 *   Calculate PLL value for given FSB
 */
static int nforce2_calc_pll(unsigned int fsb)
{
	unsigned char xmul, xdiv;
	unsigned char mul = 0, div = 0;
	int tried = 0;

	/* Try to calculate multiplier and divider up to 4 times */
	while (((mul == 0) || (div == 0)) && (tried <= 3)) {
95
		for (xdiv = 2; xdiv <= 0x80; xdiv++)
Linus Torvalds's avatar
Linus Torvalds committed
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
			for (xmul = 1; xmul <= 0xfe; xmul++)
				if (nforce2_calc_fsb(NFORCE2_PLL(xmul, xdiv)) ==
				    fsb + tried) {
					mul = xmul;
					div = xdiv;
				}
		tried++;
	}

	if ((mul == 0) || (div == 0))
		return -1;

	return NFORCE2_PLL(mul, div);
}

111
/**
Linus Torvalds's avatar
Linus Torvalds committed
112 113
 * nforce2_write_pll - write PLL value to chipset
 * @pll: PLL value
114
 *
Linus Torvalds's avatar
Linus Torvalds committed
115 116 117 118 119 120 121
 *   Writes new FSB PLL value to chipset
 */
static void nforce2_write_pll(int pll)
{
	int temp;

	/* Set the pll addr. to 0x00 */
122
	pci_write_config_dword(nforce2_dev, NFORCE2_PLLADR, 0);
Linus Torvalds's avatar
Linus Torvalds committed
123 124

	/* Now write the value in all 64 registers */
125
	for (temp = 0; temp <= 0x3f; temp++)
126
		pci_write_config_dword(nforce2_dev, NFORCE2_PLLREG, pll);
Linus Torvalds's avatar
Linus Torvalds committed
127 128 129 130

	return;
}

131
/**
Linus Torvalds's avatar
Linus Torvalds committed
132 133 134 135 136 137 138 139 140 141 142
 * nforce2_fsb_read - Read FSB
 *
 *   Read FSB from chipset
 *   If bootfsb != 0, return FSB at boot-time
 */
static unsigned int nforce2_fsb_read(int bootfsb)
{
	struct pci_dev *nforce2_sub5;
	u32 fsb, temp = 0;

	/* Get chipset boot FSB from subdevice 5 (FSB at boot-time) */
143 144
	nforce2_sub5 = pci_get_subsys(PCI_VENDOR_ID_NVIDIA, 0x01EF,
				PCI_ANY_ID, PCI_ANY_ID, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
145 146 147 148 149
	if (!nforce2_sub5)
		return 0;

	pci_read_config_dword(nforce2_sub5, NFORCE2_BOOTFSB, &fsb);
	fsb /= 1000000;
150

Linus Torvalds's avatar
Linus Torvalds committed
151
	/* Check if PLL register is already set */
152
	pci_read_config_byte(nforce2_dev, NFORCE2_PLLENABLE, (u8 *)&temp);
153

154
	if (bootfsb || !temp)
Linus Torvalds's avatar
Linus Torvalds committed
155
		return fsb;
156

Linus Torvalds's avatar
Linus Torvalds committed
157
	/* Use PLL register FSB value */
158
	pci_read_config_dword(nforce2_dev, NFORCE2_PLLREG, &temp);
Linus Torvalds's avatar
Linus Torvalds committed
159 160 161 162 163
	fsb = nforce2_calc_fsb(temp);

	return fsb;
}

164
/**
Linus Torvalds's avatar
Linus Torvalds committed
165 166
 * nforce2_set_fsb - set new FSB
 * @fsb: New FSB
167
 *
Linus Torvalds's avatar
Linus Torvalds committed
168 169 170 171
 *   Sets new FSB
 */
static int nforce2_set_fsb(unsigned int fsb)
{
172
	u32 temp = 0;
Linus Torvalds's avatar
Linus Torvalds committed
173 174
	unsigned int tfsb;
	int diff;
175
	int pll = 0;
Linus Torvalds's avatar
Linus Torvalds committed
176 177 178 179 180

	if ((fsb > max_fsb) || (fsb < NFORCE2_MIN_FSB)) {
		printk(KERN_ERR "cpufreq: FSB %d is out of range!\n", fsb);
		return -EINVAL;
	}
181

Linus Torvalds's avatar
Linus Torvalds committed
182 183 184 185 186 187 188
	tfsb = nforce2_fsb_read(0);
	if (!tfsb) {
		printk(KERN_ERR "cpufreq: Error while reading the FSB\n");
		return -EINVAL;
	}

	/* First write? Then set actual value */
189
	pci_read_config_byte(nforce2_dev, NFORCE2_PLLENABLE, (u8 *)&temp);
Linus Torvalds's avatar
Linus Torvalds committed
190 191 192 193 194 195 196 197 198 199 200
	if (!temp) {
		pll = nforce2_calc_pll(tfsb);

		if (pll < 0)
			return -EINVAL;

		nforce2_write_pll(pll);
	}

	/* Enable write access */
	temp = 0x01;
201
	pci_write_config_byte(nforce2_dev, NFORCE2_PLLENABLE, (u8)temp);
Linus Torvalds's avatar
Linus Torvalds committed
202 203 204 205 206 207 208 209 210 211 212 213 214

	diff = tfsb - fsb;

	if (!diff)
		return 0;

	while ((tfsb != fsb) && (tfsb <= max_fsb) && (tfsb >= min_fsb)) {
		if (diff < 0)
			tfsb++;
		else
			tfsb--;

		/* Calculate the PLL reg. value */
215 216
		pll = nforce2_calc_pll(tfsb);
		if (pll == -1)
Linus Torvalds's avatar
Linus Torvalds committed
217
			return -EINVAL;
218

Linus Torvalds's avatar
Linus Torvalds committed
219 220 221 222 223 224 225
		nforce2_write_pll(pll);
#ifdef NFORCE2_DELAY
		mdelay(NFORCE2_DELAY);
#endif
	}

	temp = 0x40;
226
	pci_write_config_byte(nforce2_dev, NFORCE2_PLLADR, (u8)temp);
Linus Torvalds's avatar
Linus Torvalds committed
227 228 229 230 231 232 233

	return 0;
}

/**
 * nforce2_get - get the CPU frequency
 * @cpu: CPU number
234
 *
Linus Torvalds's avatar
Linus Torvalds committed
235 236 237 238 239 240 241 242 243 244 245 246 247
 * Returns the CPU frequency
 */
static unsigned int nforce2_get(unsigned int cpu)
{
	if (cpu)
		return 0;
	return nforce2_fsb_read(0) * fid * 100;
}

/**
 * nforce2_target - set a new CPUFreq policy
 * @policy: new policy
 * @target_freq: the target frequency
248 249
 * @relation: how that frequency relates to achieved frequency
 *  (CPUFREQ_RELATION_L or CPUFREQ_RELATION_H)
Linus Torvalds's avatar
Linus Torvalds committed
250 251 252 253 254 255
 *
 * Sets a new CPUFreq policy.
 */
static int nforce2_target(struct cpufreq_policy *policy,
			  unsigned int target_freq, unsigned int relation)
{
256
/*        unsigned long         flags; */
Linus Torvalds's avatar
Linus Torvalds committed
257 258 259 260 261 262 263 264 265 266
	struct cpufreq_freqs freqs;
	unsigned int target_fsb;

	if ((target_freq > policy->max) || (target_freq < policy->min))
		return -EINVAL;

	target_fsb = target_freq / (fid * 100);

	freqs.old = nforce2_get(policy->cpu);
	freqs.new = target_fsb * fid * 100;
Simon Arlott's avatar
Simon Arlott committed
267
	freqs.cpu = 0;		/* Only one CPU on nForce2 platforms */
Linus Torvalds's avatar
Linus Torvalds committed
268 269 270 271

	if (freqs.old == freqs.new)
		return 0;

272
	dprintk("Old CPU frequency %d kHz, new %d kHz\n",
Linus Torvalds's avatar
Linus Torvalds committed
273 274 275 276 277
	       freqs.old, freqs.new);

	cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);

	/* Disable IRQs */
278
	/* local_irq_save(flags); */
Linus Torvalds's avatar
Linus Torvalds committed
279 280 281

	if (nforce2_set_fsb(target_fsb) < 0)
		printk(KERN_ERR "cpufreq: Changing FSB to %d failed\n",
282
			target_fsb);
Linus Torvalds's avatar
Linus Torvalds committed
283
	else
284
		dprintk("Changed FSB successfully to %d\n",
285
			target_fsb);
Linus Torvalds's avatar
Linus Torvalds committed
286 287

	/* Enable IRQs */
288
	/* local_irq_restore(flags); */
Linus Torvalds's avatar
Linus Torvalds committed
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308

	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);

	return 0;
}

/**
 * nforce2_verify - verifies a new CPUFreq policy
 * @policy: new policy
 */
static int nforce2_verify(struct cpufreq_policy *policy)
{
	unsigned int fsb_pol_max;

	fsb_pol_max = policy->max / (fid * 100);

	if (policy->min < (fsb_pol_max * fid * 100))
		policy->max = (fsb_pol_max + 1) * fid * 100;

	cpufreq_verify_within_limits(policy,
309 310
				     policy->cpuinfo.min_freq,
				     policy->cpuinfo.max_freq);
Linus Torvalds's avatar
Linus Torvalds committed
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
	return 0;
}

static int nforce2_cpu_init(struct cpufreq_policy *policy)
{
	unsigned int fsb;
	unsigned int rfid;

	/* capability check */
	if (policy->cpu != 0)
		return -ENODEV;

	/* Get current FSB */
	fsb = nforce2_fsb_read(0);

	if (!fsb)
		return -EIO;

	/* FIX: Get FID from CPU */
	if (!fid) {
		if (!cpu_khz) {
			printk(KERN_WARNING
333 334
			       "cpufreq: cpu_khz not set, "
			       "can't calculate multiplier!\n");
Linus Torvalds's avatar
Linus Torvalds committed
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
			return -ENODEV;
		}

		fid = cpu_khz / (fsb * 100);
		rfid = fid % 5;

		if (rfid) {
			if (rfid > 2)
				fid += 5 - rfid;
			else
				fid -= rfid;
		}
	}

	printk(KERN_INFO "cpufreq: FSB currently at %i MHz, FID %d.%d\n", fsb,
	       fid / 10, fid % 10);
351

Linus Torvalds's avatar
Linus Torvalds committed
352 353
	/* Set maximum FSB to FSB at boot time */
	max_fsb = nforce2_fsb_read(1);
354

355
	if (!max_fsb)
Linus Torvalds's avatar
Linus Torvalds committed
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
		return -EIO;

	if (!min_fsb)
		min_fsb = max_fsb - NFORCE2_SAFE_DISTANCE;

	if (min_fsb < NFORCE2_MIN_FSB)
		min_fsb = NFORCE2_MIN_FSB;

	/* cpuinfo and default policy values */
	policy->cpuinfo.min_freq = min_fsb * fid * 100;
	policy->cpuinfo.max_freq = max_fsb * fid * 100;
	policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
	policy->cur = nforce2_get(policy->cpu);
	policy->min = policy->cpuinfo.min_freq;
	policy->max = policy->cpuinfo.max_freq;

	return 0;
}

static int nforce2_cpu_exit(struct cpufreq_policy *policy)
{
	return 0;
}

380
static struct cpufreq_driver nforce2_driver = {
Linus Torvalds's avatar
Linus Torvalds committed
381 382 383 384 385 386 387 388 389 390 391 392 393
	.name = "nforce2",
	.verify = nforce2_verify,
	.target = nforce2_target,
	.get = nforce2_get,
	.init = nforce2_cpu_init,
	.exit = nforce2_cpu_exit,
	.owner = THIS_MODULE,
};

/**
 * nforce2_detect_chipset - detect the Southbridge which contains FSB PLL logic
 *
 * Detects nForce2 A2 and C1 stepping
394
 *
Linus Torvalds's avatar
Linus Torvalds committed
395 396 397
 */
static unsigned int nforce2_detect_chipset(void)
{
398
	nforce2_dev = pci_get_subsys(PCI_VENDOR_ID_NVIDIA,
399 400
					PCI_DEVICE_ID_NVIDIA_NFORCE2,
					PCI_ANY_ID, PCI_ANY_ID, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
401

402
	if (nforce2_dev == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
403 404 405
		return -ENODEV;

	printk(KERN_INFO "cpufreq: Detected nForce2 chipset revision %X\n",
406
	       nforce2_dev->revision);
Linus Torvalds's avatar
Linus Torvalds committed
407
	printk(KERN_INFO
408 409
	       "cpufreq: FSB changing is maybe unstable and can lead to "
	       "crashes and data loss.\n");
Linus Torvalds's avatar
Linus Torvalds committed
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446

	return 0;
}

/**
 * nforce2_init - initializes the nForce2 CPUFreq driver
 *
 * Initializes the nForce2 FSB support. Returns -ENODEV on unsupported
 * devices, -EINVAL on problems during initiatization, and zero on
 * success.
 */
static int __init nforce2_init(void)
{
	/* TODO: do we need to detect the processor? */

	/* detect chipset */
	if (nforce2_detect_chipset()) {
		printk(KERN_ERR "cpufreq: No nForce2 chipset.\n");
		return -ENODEV;
	}

	return cpufreq_register_driver(&nforce2_driver);
}

/**
 * nforce2_exit - unregisters cpufreq module
 *
 *   Unregisters nForce2 FSB change support.
 */
static void __exit nforce2_exit(void)
{
	cpufreq_unregister_driver(&nforce2_driver);
}

module_init(nforce2_init);
module_exit(nforce2_exit);