powernow-k8.c
来自「Linux Kernel 2.6.9 for OMAP1710」· C语言 代码 · 共 1,121 行 · 第 1/2 页
C
1,121 行
/* * (c) 2003, 2004 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 : paul.devriendt@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. * 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 */#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 <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.00.09b"#include "powernow-k8.h"/* serialize freq changes */static DECLARE_MUTEX(fidvid_sem);static struct powernow_k8_data *powernow_data[NR_CPUS];/* 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 */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; lo = MSR_S_LO_CHANGE_PENDING; while (lo & MSR_S_LO_CHANGE_PENDING) { if (i++ > 0x1000000) { printk(KERN_ERR PFX "detected change pending stuck\n"); return 1; } rdmsr(MSR_FIDVID_STATUS, lo, hi); } 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(PFX "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; 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(KERN_DEBUG PFX "writing fid 0x%x, lo 0x%x, hi 0x%x\n", fid, lo, data->plllock * PLL_LOCK_CONVERSION); wrmsr(MSR_FIDVID_CTL, lo, data->plllock * PLL_LOCK_CONVERSION); if (query_current_values_with_pending_wait(data)) return 1; 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; 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(KERN_DEBUG PFX "writing vid 0x%x, lo 0x%x, hi 0x%x\n", vid, lo, STOP_GRANT_5NS); wrmsr(MSR_FIDVID_CTL, lo, STOP_GRANT_5NS); if (query_current_values_with_pending_wait(data)) return 1; 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 0x1f 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(KERN_INFO PFX "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; dprintk(KERN_DEBUG PFX "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); while (data->currvid > reqvid) { dprintk(KERN_DEBUG PFX "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) { if (data->currvid == 0) { rvosteps = 0; } else { dprintk(KERN_DEBUG PFX "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(KERN_DEBUG PFX "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; u32 vcocurrfid; u32 vcofiddiff; u32 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(KERN_DEBUG PFX "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", data->currfid, reqfid); return 1; } if (savevid != data->currvid) { printk(KERN_ERR PFX "ph2: vid changed, save 0x%x, curr 0x%x\n", savevid, data->currvid); return 1; } dprintk(KERN_DEBUG PFX "ph2 complete, currfid 0x%x, currvid 0x%x\n", data->currfid, data->currvid); return 0;}/* Phase 3 - core voltage transition flow ... jump to the final vid. */static int core_voltage_post_transition(struct powernow_k8_data *data, u32 reqvid){ u32 savefid = data->currfid; u32 savereqvid = reqvid; dprintk(KERN_DEBUG PFX "ph3 (cpu%d): starting, currfid 0x%x, currvid 0x%x\n", smp_processor_id(), data->currfid, data->currvid); if (reqvid != data->currvid) { if (write_new_vid(data, reqvid)) return 1; if (savefid != data->currfid) { printk(KERN_ERR PFX "ph3: bad fid change, save 0x%x, curr 0x%x\n", savefid, data->currfid); return 1; } if (data->currvid != reqvid) { printk(KERN_ERR PFX "ph3: failed vid transition\n, req 0x%x, curr 0x%x", reqvid, data->currvid); return 1; } } if (query_current_values_with_pending_wait(data)) return 1; if (savereqvid != data->currvid) { dprintk(KERN_ERR PFX "ph3 failed, currvid 0x%x\n", data->currvid); return 1; } if (savefid != data->currfid) { dprintk(KERN_ERR PFX "ph3 failed, currfid changed 0x%x\n", data->currfid); return 1; } dprintk(KERN_DEBUG PFX "ph3 complete, currfid 0x%x, currvid 0x%x\n", data->currfid, data->currvid); return 0;}static int check_supported_cpu(unsigned int cpu){ cpumask_t oldmask = CPU_MASK_ALL; u32 eax, ebx, ecx, edx; unsigned int rc = 0; oldmask = current->cpus_allowed; set_cpus_allowed(current, cpumask_of_cpu(cpu)); schedule(); if (smp_processor_id() != cpu) { printk(KERN_ERR "limiting to cpu %u failed\n", cpu); goto out; } if (current_cpu_data.x86_vendor != X86_VENDOR_AMD) goto out; eax = cpuid_eax(CPUID_PROCESSOR_SIGNATURE); if (((eax & CPUID_USE_XFAM_XMOD) != CPUID_USE_XFAM_XMOD) || ((eax & CPUID_XFAM) != CPUID_XFAM_K8) || ((eax & CPUID_XMOD) > CPUID_XMOD_REV_E)) { printk(KERN_INFO PFX "Processor cpuid %x not supported\n", eax); goto out; } eax = cpuid_eax(CPUID_GET_MAX_CAPABILITIES); if (eax < CPUID_FREQ_VOLT_CAPABILITIES) { printk(KERN_INFO PFX "No frequency change capabilities detected\n"); goto out; } cpuid(CPUID_FREQ_VOLT_CAPABILITIES, &eax, &ebx, &ecx, &edx); if ((edx & P_STATE_TRANSITION_CAPABLE) != P_STATE_TRANSITION_CAPABLE) { printk(KERN_INFO PFX "Power state transitions not supported\n"); goto out; } rc = 1;out: set_cpus_allowed(current, oldmask); schedule(); return rc;}static int check_pst_table(struct powernow_k8_data *data, struct pst_s *pst, u8 maxvid){ unsigned int j; u8 lastfid = 0xff; for (j = 0; j < data->numps; j++) { if (pst[j].vid > LEAST_VID) { printk(KERN_ERR PFX "vid %d invalid : 0x%x\n", j, pst[j].vid); return -EINVAL; } if (pst[j].vid < data->rvo) { /* vid + rvo >= 0 */ printk(KERN_ERR BFX "0 vid exceeded with pstate %d\n", j); return -ENODEV; } if (pst[j].vid < maxvid + data->rvo) { /* vid + rvo >= maxvid */ printk(KERN_ERR BFX "maxvid exceeded with pstate %d\n", j); return -ENODEV; } if ((pst[j].fid > MAX_FID) || (pst[j].fid & 1) || (j && (pst[j].fid < HI_FID_TABLE_BOTTOM))) { /* Only first fid is allowed to be in "low" range */ printk(KERN_ERR PFX "fid %d invalid : 0x%x\n", j, pst[j].fid); return -EINVAL; } if (pst[j].fid < lastfid) lastfid = pst[j].fid; } if (lastfid & 1) { printk(KERN_ERR PFX "lastfid invalid\n"); return -EINVAL; } if (lastfid > LO_FID_TABLE_TOP) printk(KERN_INFO PFX "first fid not from lo freq table\n"); return 0;}static void print_basics(struct powernow_k8_data *data){ int j; for (j = 0; j < data->numps; j++) { if (data->powernow_table[j].frequency != CPUFREQ_ENTRY_INVALID) printk(KERN_INFO PFX " %d : fid 0x%x (%d MHz), vid 0x%x (%d mV)\n", j, data->powernow_table[j].index & 0xff, data->powernow_table[j].frequency/1000, data->powernow_table[j].index >> 8, find_millivolts_from_vid(data, data->powernow_table[j].index >> 8)); } if (data->batps) printk(KERN_INFO PFX "Only %d pstates on battery\n", data->batps);}static int fill_powernow_table(struct powernow_k8_data *data, struct pst_s *pst, u8 maxvid){ struct cpufreq_frequency_table *powernow_table; unsigned int j; if (data->batps) { /* use ACPI support to get full speed on mains power */ printk(KERN_WARNING PFX "Only %d pstates usable (use ACPI driver for full range\n", data->batps); data->numps = data->batps; } for ( j=1; j<data->numps; j++ ) { if (pst[j-1].fid >= pst[j].fid) { printk(KERN_ERR PFX "PST out of sequence\n"); return -EINVAL; } } if (data->numps < 2) { printk(KERN_ERR PFX "no p states to transition\n"); return -ENODEV; } if (check_pst_table(data, pst, maxvid)) return -EINVAL; powernow_table = kmalloc((sizeof(struct cpufreq_frequency_table) * (data->numps + 1)), GFP_KERNEL);
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?