Path: blob/master/drivers/cpufreq/cpufreq-nforce2.c
15112 views
/*1* (C) 2004-2006 Sebastian Witt <[email protected]>2*3* Licensed under the terms of the GNU GPL License version 2.4* Based upon reverse engineered information5*6* BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous*7*/89#include <linux/kernel.h>10#include <linux/module.h>11#include <linux/moduleparam.h>12#include <linux/init.h>13#include <linux/cpufreq.h>14#include <linux/pci.h>15#include <linux/delay.h>1617#define NFORCE2_XTAL 2518#define NFORCE2_BOOTFSB 0x4819#define NFORCE2_PLLENABLE 0xa820#define NFORCE2_PLLREG 0xa421#define NFORCE2_PLLADR 0xa022#define NFORCE2_PLL(mul, div) (0x100000 | (mul << 8) | div)2324#define NFORCE2_MIN_FSB 5025#define NFORCE2_SAFE_DISTANCE 502627/* Delay in ms between FSB changes */28/* #define NFORCE2_DELAY 10 */2930/*31* nforce2_chipset:32* FSB is changed using the chipset33*/34static struct pci_dev *nforce2_dev;3536/* fid:37* multiplier * 1038*/39static int fid;4041/* min_fsb, max_fsb:42* minimum and maximum FSB (= FSB at boot time)43*/44static int min_fsb;45static int max_fsb;4647MODULE_AUTHOR("Sebastian Witt <[email protected]>");48MODULE_DESCRIPTION("nForce2 FSB changing cpufreq driver");49MODULE_LICENSE("GPL");5051module_param(fid, int, 0444);52module_param(min_fsb, int, 0444);5354MODULE_PARM_DESC(fid, "CPU multiplier to use (11.5 = 115)");55MODULE_PARM_DESC(min_fsb,56"Minimum FSB to use, if not defined: current FSB - 50");5758#define PFX "cpufreq-nforce2: "5960/**61* nforce2_calc_fsb - calculate FSB62* @pll: PLL value63*64* Calculates FSB from PLL value65*/66static int nforce2_calc_fsb(int pll)67{68unsigned char mul, div;6970mul = (pll >> 8) & 0xff;71div = pll & 0xff;7273if (div > 0)74return NFORCE2_XTAL * mul / div;7576return 0;77}7879/**80* nforce2_calc_pll - calculate PLL value81* @fsb: FSB82*83* Calculate PLL value for given FSB84*/85static int nforce2_calc_pll(unsigned int fsb)86{87unsigned char xmul, xdiv;88unsigned char mul = 0, div = 0;89int tried = 0;9091/* Try to calculate multiplier and divider up to 4 times */92while (((mul == 0) || (div == 0)) && (tried <= 3)) {93for (xdiv = 2; xdiv <= 0x80; xdiv++)94for (xmul = 1; xmul <= 0xfe; xmul++)95if (nforce2_calc_fsb(NFORCE2_PLL(xmul, xdiv)) ==96fsb + tried) {97mul = xmul;98div = xdiv;99}100tried++;101}102103if ((mul == 0) || (div == 0))104return -1;105106return NFORCE2_PLL(mul, div);107}108109/**110* nforce2_write_pll - write PLL value to chipset111* @pll: PLL value112*113* Writes new FSB PLL value to chipset114*/115static void nforce2_write_pll(int pll)116{117int temp;118119/* Set the pll addr. to 0x00 */120pci_write_config_dword(nforce2_dev, NFORCE2_PLLADR, 0);121122/* Now write the value in all 64 registers */123for (temp = 0; temp <= 0x3f; temp++)124pci_write_config_dword(nforce2_dev, NFORCE2_PLLREG, pll);125126return;127}128129/**130* nforce2_fsb_read - Read FSB131*132* Read FSB from chipset133* If bootfsb != 0, return FSB at boot-time134*/135static unsigned int nforce2_fsb_read(int bootfsb)136{137struct pci_dev *nforce2_sub5;138u32 fsb, temp = 0;139140/* Get chipset boot FSB from subdevice 5 (FSB at boot-time) */141nforce2_sub5 = pci_get_subsys(PCI_VENDOR_ID_NVIDIA, 0x01EF,142PCI_ANY_ID, PCI_ANY_ID, NULL);143if (!nforce2_sub5)144return 0;145146pci_read_config_dword(nforce2_sub5, NFORCE2_BOOTFSB, &fsb);147fsb /= 1000000;148149/* Check if PLL register is already set */150pci_read_config_byte(nforce2_dev, NFORCE2_PLLENABLE, (u8 *)&temp);151152if (bootfsb || !temp)153return fsb;154155/* Use PLL register FSB value */156pci_read_config_dword(nforce2_dev, NFORCE2_PLLREG, &temp);157fsb = nforce2_calc_fsb(temp);158159return fsb;160}161162/**163* nforce2_set_fsb - set new FSB164* @fsb: New FSB165*166* Sets new FSB167*/168static int nforce2_set_fsb(unsigned int fsb)169{170u32 temp = 0;171unsigned int tfsb;172int diff;173int pll = 0;174175if ((fsb > max_fsb) || (fsb < NFORCE2_MIN_FSB)) {176printk(KERN_ERR PFX "FSB %d is out of range!\n", fsb);177return -EINVAL;178}179180tfsb = nforce2_fsb_read(0);181if (!tfsb) {182printk(KERN_ERR PFX "Error while reading the FSB\n");183return -EINVAL;184}185186/* First write? Then set actual value */187pci_read_config_byte(nforce2_dev, NFORCE2_PLLENABLE, (u8 *)&temp);188if (!temp) {189pll = nforce2_calc_pll(tfsb);190191if (pll < 0)192return -EINVAL;193194nforce2_write_pll(pll);195}196197/* Enable write access */198temp = 0x01;199pci_write_config_byte(nforce2_dev, NFORCE2_PLLENABLE, (u8)temp);200201diff = tfsb - fsb;202203if (!diff)204return 0;205206while ((tfsb != fsb) && (tfsb <= max_fsb) && (tfsb >= min_fsb)) {207if (diff < 0)208tfsb++;209else210tfsb--;211212/* Calculate the PLL reg. value */213pll = nforce2_calc_pll(tfsb);214if (pll == -1)215return -EINVAL;216217nforce2_write_pll(pll);218#ifdef NFORCE2_DELAY219mdelay(NFORCE2_DELAY);220#endif221}222223temp = 0x40;224pci_write_config_byte(nforce2_dev, NFORCE2_PLLADR, (u8)temp);225226return 0;227}228229/**230* nforce2_get - get the CPU frequency231* @cpu: CPU number232*233* Returns the CPU frequency234*/235static unsigned int nforce2_get(unsigned int cpu)236{237if (cpu)238return 0;239return nforce2_fsb_read(0) * fid * 100;240}241242/**243* nforce2_target - set a new CPUFreq policy244* @policy: new policy245* @target_freq: the target frequency246* @relation: how that frequency relates to achieved frequency247* (CPUFREQ_RELATION_L or CPUFREQ_RELATION_H)248*249* Sets a new CPUFreq policy.250*/251static int nforce2_target(struct cpufreq_policy *policy,252unsigned int target_freq, unsigned int relation)253{254/* unsigned long flags; */255struct cpufreq_freqs freqs;256unsigned int target_fsb;257258if ((target_freq > policy->max) || (target_freq < policy->min))259return -EINVAL;260261target_fsb = target_freq / (fid * 100);262263freqs.old = nforce2_get(policy->cpu);264freqs.new = target_fsb * fid * 100;265freqs.cpu = 0; /* Only one CPU on nForce2 platforms */266267if (freqs.old == freqs.new)268return 0;269270pr_debug("Old CPU frequency %d kHz, new %d kHz\n",271freqs.old, freqs.new);272273cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);274275/* Disable IRQs */276/* local_irq_save(flags); */277278if (nforce2_set_fsb(target_fsb) < 0)279printk(KERN_ERR PFX "Changing FSB to %d failed\n",280target_fsb);281else282pr_debug("Changed FSB successfully to %d\n",283target_fsb);284285/* Enable IRQs */286/* local_irq_restore(flags); */287288cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);289290return 0;291}292293/**294* nforce2_verify - verifies a new CPUFreq policy295* @policy: new policy296*/297static int nforce2_verify(struct cpufreq_policy *policy)298{299unsigned int fsb_pol_max;300301fsb_pol_max = policy->max / (fid * 100);302303if (policy->min < (fsb_pol_max * fid * 100))304policy->max = (fsb_pol_max + 1) * fid * 100;305306cpufreq_verify_within_limits(policy,307policy->cpuinfo.min_freq,308policy->cpuinfo.max_freq);309return 0;310}311312static int nforce2_cpu_init(struct cpufreq_policy *policy)313{314unsigned int fsb;315unsigned int rfid;316317/* capability check */318if (policy->cpu != 0)319return -ENODEV;320321/* Get current FSB */322fsb = nforce2_fsb_read(0);323324if (!fsb)325return -EIO;326327/* FIX: Get FID from CPU */328if (!fid) {329if (!cpu_khz) {330printk(KERN_WARNING PFX331"cpu_khz not set, can't calculate multiplier!\n");332return -ENODEV;333}334335fid = cpu_khz / (fsb * 100);336rfid = fid % 5;337338if (rfid) {339if (rfid > 2)340fid += 5 - rfid;341else342fid -= rfid;343}344}345346printk(KERN_INFO PFX "FSB currently at %i MHz, FID %d.%d\n", fsb,347fid / 10, fid % 10);348349/* Set maximum FSB to FSB at boot time */350max_fsb = nforce2_fsb_read(1);351352if (!max_fsb)353return -EIO;354355if (!min_fsb)356min_fsb = max_fsb - NFORCE2_SAFE_DISTANCE;357358if (min_fsb < NFORCE2_MIN_FSB)359min_fsb = NFORCE2_MIN_FSB;360361/* cpuinfo and default policy values */362policy->cpuinfo.min_freq = min_fsb * fid * 100;363policy->cpuinfo.max_freq = max_fsb * fid * 100;364policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;365policy->cur = nforce2_get(policy->cpu);366policy->min = policy->cpuinfo.min_freq;367policy->max = policy->cpuinfo.max_freq;368369return 0;370}371372static int nforce2_cpu_exit(struct cpufreq_policy *policy)373{374return 0;375}376377static struct cpufreq_driver nforce2_driver = {378.name = "nforce2",379.verify = nforce2_verify,380.target = nforce2_target,381.get = nforce2_get,382.init = nforce2_cpu_init,383.exit = nforce2_cpu_exit,384.owner = THIS_MODULE,385};386387/**388* nforce2_detect_chipset - detect the Southbridge which contains FSB PLL logic389*390* Detects nForce2 A2 and C1 stepping391*392*/393static int nforce2_detect_chipset(void)394{395nforce2_dev = pci_get_subsys(PCI_VENDOR_ID_NVIDIA,396PCI_DEVICE_ID_NVIDIA_NFORCE2,397PCI_ANY_ID, PCI_ANY_ID, NULL);398399if (nforce2_dev == NULL)400return -ENODEV;401402printk(KERN_INFO PFX "Detected nForce2 chipset revision %X\n",403nforce2_dev->revision);404printk(KERN_INFO PFX405"FSB changing is maybe unstable and can lead to "406"crashes and data loss.\n");407408return 0;409}410411/**412* nforce2_init - initializes the nForce2 CPUFreq driver413*414* Initializes the nForce2 FSB support. Returns -ENODEV on unsupported415* devices, -EINVAL on problems during initiatization, and zero on416* success.417*/418static int __init nforce2_init(void)419{420/* TODO: do we need to detect the processor? */421422/* detect chipset */423if (nforce2_detect_chipset()) {424printk(KERN_INFO PFX "No nForce2 chipset.\n");425return -ENODEV;426}427428return cpufreq_register_driver(&nforce2_driver);429}430431/**432* nforce2_exit - unregisters cpufreq module433*434* Unregisters nForce2 FSB change support.435*/436static void __exit nforce2_exit(void)437{438cpufreq_unregister_driver(&nforce2_driver);439}440441module_init(nforce2_init);442module_exit(nforce2_exit);443444445446