📄 powernow-k8.c
字号:
/* * (c) 2003, 2004, 2005 Advanced Micro Devices, Inc. * Your use of this code is subject to the terms and conditions of the * GNU general public license version 2. See "COPYING" or * http://www.gnu.org/licenses/gpl.html * * Support : mark.langsdorf@amd.com * * Based on the powernow-k7.c module written by Dave Jones. * (C) 2003 Dave Jones <davej@codemonkey.org.uk> on behalf of SuSE Labs * (C) 2004 Dominik Brodowski <linux@brodo.de> * (C) 2004 Pavel Machek <pavel@suse.cz> * Licensed under the terms of the GNU GPL License version 2. * Based upon datasheets & sample CPUs kindly provided by AMD. * * Valuable input gratefully received from Dave Jones, Pavel Machek, * Dominik Brodowski, and others. * Originally developed by Paul Devriendt. * Processor information obtained from Chapter 9 (Power and Thermal Management) * of the "BIOS and Kernel Developer's Guide for the AMD Athlon 64 and AMD * Opteron Processors" available for download from www.amd.com * * Tables for specific CPUs can be infrerred from * http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/30430.pdf */#include <linux/kernel.h>#include <linux/smp.h>#include <linux/module.h>#include <linux/init.h>#include <linux/cpufreq.h>#include <linux/slab.h>#include <linux/string.h>#include <linux/cpumask.h>#include <linux/sched.h> /* for current / set_cpus_allowed() */#include <asm/msr.h>#include <asm/io.h>#include <asm/delay.h>#ifdef CONFIG_X86_POWERNOW_K8_ACPI#include <linux/acpi.h>#include <acpi/processor.h>#endif#define PFX "powernow-k8: "#define BFX PFX "BIOS error: "#define VERSION "version 1.50.4"#include "powernow-k8.h"/* serialize freq changes */static DECLARE_MUTEX(fidvid_sem);static struct powernow_k8_data *powernow_data[NR_CPUS];#ifndef CONFIG_SMPstatic cpumask_t cpu_core_map[1];#endif/* Return a frequency in MHz, given an input fid */static u32 find_freq_from_fid(u32 fid){ return 800 + (fid * 100);}/* Return a frequency in KHz, given an input fid */static u32 find_khz_freq_from_fid(u32 fid){ return 1000 * find_freq_from_fid(fid);}/* Return a voltage in miliVolts, given an input vid */static u32 find_millivolts_from_vid(struct powernow_k8_data *data, u32 vid){ return 1550-vid*25;}/* Return the vco fid for an input fid * * Each "low" fid has corresponding "high" fid, and you can get to "low" fids * only from corresponding high fids. This returns "high" fid corresponding to * "low" one. */static u32 convert_fid_to_vco_fid(u32 fid){ if (fid < HI_FID_TABLE_BOTTOM) { return 8 + (2 * fid); } else { return fid; }}/* * Return 1 if the pending bit is set. Unless we just instructed the processor * to transition to a new state, seeing this bit set is really bad news. */static int pending_bit_stuck(void){ u32 lo, hi; rdmsr(MSR_FIDVID_STATUS, lo, hi); return lo & MSR_S_LO_CHANGE_PENDING ? 1 : 0;}/* * Update the global current fid / vid values from the status msr. * Returns 1 on error. */static int query_current_values_with_pending_wait(struct powernow_k8_data *data){ u32 lo, hi; u32 i = 0; do { if (i++ > 10000) { dprintk("detected change pending stuck\n"); return 1; } rdmsr(MSR_FIDVID_STATUS, lo, hi); } while (lo & MSR_S_LO_CHANGE_PENDING); data->currvid = hi & MSR_S_HI_CURRENT_VID; data->currfid = lo & MSR_S_LO_CURRENT_FID; return 0;}/* the isochronous relief time */static void count_off_irt(struct powernow_k8_data *data){ udelay((1 << data->irt) * 10); return;}/* the voltage stabalization time */static void count_off_vst(struct powernow_k8_data *data){ udelay(data->vstable * VST_UNITS_20US); return;}/* need to init the control msr to a safe value (for each cpu) */static void fidvid_msr_init(void){ u32 lo, hi; u8 fid, vid; rdmsr(MSR_FIDVID_STATUS, lo, hi); vid = hi & MSR_S_HI_CURRENT_VID; fid = lo & MSR_S_LO_CURRENT_FID; lo = fid | (vid << MSR_C_LO_VID_SHIFT); hi = MSR_C_HI_STP_GNT_BENIGN; dprintk("cpu%d, init lo 0x%x, hi 0x%x\n", smp_processor_id(), lo, hi); wrmsr(MSR_FIDVID_CTL, lo, hi);}/* write the new fid value along with the other control fields to the msr */static int write_new_fid(struct powernow_k8_data *data, u32 fid){ u32 lo; u32 savevid = data->currvid; u32 i = 0; if ((fid & INVALID_FID_MASK) || (data->currvid & INVALID_VID_MASK)) { printk(KERN_ERR PFX "internal error - overflow on fid write\n"); return 1; } lo = fid | (data->currvid << MSR_C_LO_VID_SHIFT) | MSR_C_LO_INIT_FID_VID; dprintk("writing fid 0x%x, lo 0x%x, hi 0x%x\n", fid, lo, data->plllock * PLL_LOCK_CONVERSION); do { wrmsr(MSR_FIDVID_CTL, lo, data->plllock * PLL_LOCK_CONVERSION); if (i++ > 100) { printk(KERN_ERR PFX "internal error - pending bit very stuck - no further pstate changes possible\n"); return 1; } } while (query_current_values_with_pending_wait(data)); count_off_irt(data); if (savevid != data->currvid) { printk(KERN_ERR PFX "vid change on fid trans, old 0x%x, new 0x%x\n", savevid, data->currvid); return 1; } if (fid != data->currfid) { printk(KERN_ERR PFX "fid trans failed, fid 0x%x, curr 0x%x\n", fid, data->currfid); return 1; } return 0;}/* Write a new vid to the hardware */static int write_new_vid(struct powernow_k8_data *data, u32 vid){ u32 lo; u32 savefid = data->currfid; int i = 0; if ((data->currfid & INVALID_FID_MASK) || (vid & INVALID_VID_MASK)) { printk(KERN_ERR PFX "internal error - overflow on vid write\n"); return 1; } lo = data->currfid | (vid << MSR_C_LO_VID_SHIFT) | MSR_C_LO_INIT_FID_VID; dprintk("writing vid 0x%x, lo 0x%x, hi 0x%x\n", vid, lo, STOP_GRANT_5NS); do { wrmsr(MSR_FIDVID_CTL, lo, STOP_GRANT_5NS); if (i++ > 100) { printk(KERN_ERR PFX "internal error - pending bit very stuck - no further pstate changes possible\n"); return 1; } } while (query_current_values_with_pending_wait(data)); if (savefid != data->currfid) { printk(KERN_ERR PFX "fid changed on vid trans, old 0x%x new 0x%x\n", savefid, data->currfid); return 1; } if (vid != data->currvid) { printk(KERN_ERR PFX "vid trans failed, vid 0x%x, curr 0x%x\n", vid, data->currvid); return 1; } return 0;}/* * Reduce the vid by the max of step or reqvid. * Decreasing vid codes represent increasing voltages: * vid of 0 is 1.550V, vid of 0x1e is 0.800V, vid of VID_OFF is off. */static int decrease_vid_code_by_step(struct powernow_k8_data *data, u32 reqvid, u32 step){ if ((data->currvid - reqvid) > step) reqvid = data->currvid - step; if (write_new_vid(data, reqvid)) return 1; count_off_vst(data); return 0;}/* Change the fid and vid, by the 3 phases. */static int transition_fid_vid(struct powernow_k8_data *data, u32 reqfid, u32 reqvid){ if (core_voltage_pre_transition(data, reqvid)) return 1; if (core_frequency_transition(data, reqfid)) return 1; if (core_voltage_post_transition(data, reqvid)) return 1; if (query_current_values_with_pending_wait(data)) return 1; if ((reqfid != data->currfid) || (reqvid != data->currvid)) { printk(KERN_ERR PFX "failed (cpu%d): req 0x%x 0x%x, curr 0x%x 0x%x\n", smp_processor_id(), reqfid, reqvid, data->currfid, data->currvid); return 1; } dprintk("transitioned (cpu%d): new fid 0x%x, vid 0x%x\n", smp_processor_id(), data->currfid, data->currvid); return 0;}/* Phase 1 - core voltage transition ... setup voltage */static int core_voltage_pre_transition(struct powernow_k8_data *data, u32 reqvid){ u32 rvosteps = data->rvo; u32 savefid = data->currfid; u32 maxvid, lo; dprintk("ph1 (cpu%d): start, currfid 0x%x, currvid 0x%x, reqvid 0x%x, rvo 0x%x\n", smp_processor_id(), data->currfid, data->currvid, reqvid, data->rvo); rdmsr(MSR_FIDVID_STATUS, lo, maxvid); maxvid = 0x1f & (maxvid >> 16); dprintk("ph1 maxvid=0x%x\n", maxvid); if (reqvid < maxvid) /* lower numbers are higher voltages */ reqvid = maxvid; while (data->currvid > reqvid) { dprintk("ph1: curr 0x%x, req vid 0x%x\n", data->currvid, reqvid); if (decrease_vid_code_by_step(data, reqvid, data->vidmvs)) return 1; } while ((rvosteps > 0) && ((data->rvo + data->currvid) > reqvid)) { if (data->currvid == maxvid) { rvosteps = 0; } else { dprintk("ph1: changing vid for rvo, req 0x%x\n", data->currvid - 1); if (decrease_vid_code_by_step(data, data->currvid - 1, 1)) return 1; rvosteps--; } } if (query_current_values_with_pending_wait(data)) return 1; if (savefid != data->currfid) { printk(KERN_ERR PFX "ph1 err, currfid changed 0x%x\n", data->currfid); return 1; } dprintk("ph1 complete, currfid 0x%x, currvid 0x%x\n", data->currfid, data->currvid); return 0;}/* Phase 2 - core frequency transition */static int core_frequency_transition(struct powernow_k8_data *data, u32 reqfid){ u32 vcoreqfid, vcocurrfid, vcofiddiff, savevid = data->currvid; if ((reqfid < HI_FID_TABLE_BOTTOM) && (data->currfid < HI_FID_TABLE_BOTTOM)) { printk(KERN_ERR PFX "ph2: illegal lo-lo transition 0x%x 0x%x\n", reqfid, data->currfid); return 1; } if (data->currfid == reqfid) { printk(KERN_ERR PFX "ph2 null fid transition 0x%x\n", data->currfid); return 0; } dprintk("ph2 (cpu%d): starting, currfid 0x%x, currvid 0x%x, reqfid 0x%x\n", smp_processor_id(), data->currfid, data->currvid, reqfid); vcoreqfid = convert_fid_to_vco_fid(reqfid); vcocurrfid = convert_fid_to_vco_fid(data->currfid); vcofiddiff = vcocurrfid > vcoreqfid ? vcocurrfid - vcoreqfid : vcoreqfid - vcocurrfid; while (vcofiddiff > 2) { if (reqfid > data->currfid) { if (data->currfid > LO_FID_TABLE_TOP) { if (write_new_fid(data, data->currfid + 2)) { return 1; } } else { if (write_new_fid (data, 2 + convert_fid_to_vco_fid(data->currfid))) { return 1; } } } else { if (write_new_fid(data, data->currfid - 2)) return 1; } vcocurrfid = convert_fid_to_vco_fid(data->currfid); vcofiddiff = vcocurrfid > vcoreqfid ? vcocurrfid - vcoreqfid : vcoreqfid - vcocurrfid; } if (write_new_fid(data, reqfid)) return 1; if (query_current_values_with_pending_wait(data)) return 1; if (data->currfid != reqfid) { printk(KERN_ERR PFX "ph2: mismatch, failed fid transition, curr 0x%x, req 0x%x\n",
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -