cregit-Linux how code gets into the kernel

Release 4.7 drivers/cpufreq/powernow-k7.c

Directory: drivers/cpufreq
/*
 *  AMD K7 Powernow driver.
 *  (C) 2003 Dave Jones on behalf of SuSE Labs.
 *
 *  Licensed under the terms of the GNU GPL License version 2.
 *  Based upon datasheets & sample CPUs kindly provided by AMD.
 *
 * Errata 5:
 *  CPU may fail to execute a FID/VID change in presence of interrupt.
 *  - We cli/sti on stepping A0 CPUs around the FID/VID transition.
 * Errata 15:
 *  CPU with half frequency multipliers may hang upon wakeup from disconnect.
 *  - We disable half multipliers if ACPI is used on A0 stepping CPUs.
 */


#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/dmi.h>
#include <linux/timex.h>
#include <linux/io.h>

#include <asm/timer.h>		/* Needed for recalibrate_cpu_khz() */
#include <asm/msr.h>
#include <asm/cpu_device_id.h>

#ifdef CONFIG_X86_POWERNOW_K7_ACPI
#include <linux/acpi.h>
#include <acpi/processor.h>
#endif

#include "powernow-k7.h"


struct psb_s {
	
u8 signature[10];
	
u8 tableversion;
	
u8 flags;
	
u16 settlingtime;
	
u8 reserved1;
	
u8 numpst;
};


struct pst_s {
	
u32 cpuid;
	
u8 fsbspeed;
	
u8 maxfid;
	
u8 startvid;
	
u8 numpstates;
};

#ifdef CONFIG_X86_POWERNOW_K7_ACPI

union powernow_acpi_control_t {
	struct {
		
unsigned long fid:5,
			
vid:5,
			
sgtc:20,
			
res1:2;
	} 
bits;
	
unsigned long val;
};
#endif

/* divide by 1000 to get VCore voltage in V. */

static const int mobile_vid_table[32] = {
    2000, 1950, 1900, 1850, 1800, 1750, 1700, 1650,
    1600, 1550, 1500, 1450, 1400, 1350, 1300, 0,
    1275, 1250, 1225, 1200, 1175, 1150, 1125, 1100,
    1075, 1050, 1025, 1000, 975, 950, 925, 0,
};

/* divide by 10 to get FID. */

static const int fid_codes[32] = {
    110, 115, 120, 125, 50, 55, 60, 65,
    70, 75, 80, 85, 90, 95, 100, 105,
    30, 190, 40, 200, 130, 135, 140, 210,
    150, 225, 160, 165, 170, 180, -1, -1,
};

/* This parameter is used in order to force ACPI instead of legacy method for
 * configuration purpose.
 */


static int acpi_force;


static struct cpufreq_frequency_table *powernow_table;


static unsigned int can_scale_bus;

static unsigned int can_scale_vid;

static unsigned int minimum_speed = -1;

static unsigned int maximum_speed;

static unsigned int number_scales;

static unsigned int fsb;

static unsigned int latency;

static char have_a0;


static int check_fsb(unsigned int fsbspeed) { int delta; unsigned int f = fsb / 1000; delta = (fsbspeed > f) ? fsbspeed - f : f - fsbspeed; return delta < 5; }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones42100.00%1100.00%
Total42100.00%1100.00%

static const struct x86_cpu_id powernow_k7_cpuids[] = { { X86_VENDOR_AMD, 6, }, {} }; MODULE_DEVICE_TABLE(x86cpu, powernow_k7_cpuids);
static int check_powernow(void) { struct cpuinfo_x86 *c = &cpu_data(0); unsigned int maxei, eax, ebx, ecx, edx; if (!x86_match_cpu(powernow_k7_cpuids)) return 0; /* Get maximum capabilities */ maxei = cpuid_eax(0x80000000); if (maxei < 0x80000007) { /* Any powernow info ? */ #ifdef MODULE pr_info("No powernow capabilities detected\n"); #endif return 0; } if ((c->x86_model == 6) && (c->x86_mask == 0)) { pr_info("K7 660[A0] core detected, enabling errata workarounds\n"); have_a0 = 1; } cpuid(0x80000007, &eax, &ebx, &ecx, &edx); /* Check we can actually do something before we say anything.*/ if (!(edx & (1 << 1 | 1 << 2))) return 0; pr_info("PowerNOW! Technology present. Can scale: "); if (edx & 1 << 1) { pr_cont("frequency"); can_scale_bus = 1; } if ((edx & (1 << 1 | 1 << 2)) == 0x6) pr_cont(" and "); if (edx & 1 << 2) { pr_cont("voltage"); can_scale_vid = 1; } pr_cont("\n"); return 1; }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones19390.61%457.14%
joe perchesjoe perches125.63%114.29%
andi kleenandi kleen41.88%114.29%
mike travismike travis41.88%114.29%
Total213100.00%7100.00%

#ifdef CONFIG_X86_POWERNOW_K7_ACPI
static void invalidate_entry(unsigned int entry) { powernow_table[entry].frequency = CPUFREQ_ENTRY_INVALID; }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones19100.00%1100.00%
Total19100.00%1100.00%

#endif
static int get_ranges(unsigned char *pst) { unsigned int j; unsigned int speed; u8 fid, vid; powernow_table = kzalloc((sizeof(*powernow_table) * (number_scales + 1)), GFP_KERNEL); if (!powernow_table) return -ENOMEM; for (j = 0 ; j < number_scales; j++) { fid = *pst++; powernow_table[j].frequency = (fsb * fid_codes[fid]) / 10; powernow_table[j].driver_data = fid; /* lower 8 bits */ speed = powernow_table[j].frequency; if ((fid_codes[fid] % 10) == 5) { #ifdef CONFIG_X86_POWERNOW_K7_ACPI if (have_a0 == 1) invalidate_entry(j); #endif } if (speed < minimum_speed) minimum_speed = speed; if (speed > maximum_speed) maximum_speed = speed; vid = *pst++; powernow_table[j].driver_data |= (vid << 8); /* upper 8 bits */ pr_debug(" FID: 0x%x (%d.%dx [%dMHz]) " "VID: 0x%x (%d.%03dV)\n", fid, fid_codes[fid] / 10, fid_codes[fid] % 10, speed/1000, vid, mobile_vid_table[vid]/1000, mobile_vid_table[vid]%1000); } powernow_table[number_scales].frequency = CPUFREQ_TABLE_END; powernow_table[number_scales].driver_data = 0; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones24097.56%872.73%
viresh kumarviresh kumar52.03%218.18%
dominik brodowskidominik brodowski10.41%19.09%
Total246100.00%11100.00%


static void change_FID(int fid) { union msr_fidvidctl fidvidctl; rdmsrl(MSR_K7_FID_VID_CTL, fidvidctl.val); if (fidvidctl.bits.FID != fid) { fidvidctl.bits.SGTC = latency; fidvidctl.bits.FID = fid; fidvidctl.bits.VIDC = 0; fidvidctl.bits.FIDC = 1; wrmsrl(MSR_K7_FID_VID_CTL, fidvidctl.val); } }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones75100.00%3100.00%
Total75100.00%3100.00%


static void change_VID(int vid) { union msr_fidvidctl fidvidctl; rdmsrl(MSR_K7_FID_VID_CTL, fidvidctl.val); if (fidvidctl.bits.VID != vid) { fidvidctl.bits.SGTC = latency; fidvidctl.bits.VID = vid; fidvidctl.bits.FIDC = 0; fidvidctl.bits.VIDC = 1; wrmsrl(MSR_K7_FID_VID_CTL, fidvidctl.val); } }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones75100.00%4100.00%
Total75100.00%4100.00%


static int powernow_target(struct cpufreq_policy *policy, unsigned int index) { u8 fid, vid; struct cpufreq_freqs freqs; union msr_fidvidstatus fidvidstatus; int cfid; /* fid are the lower 8 bits of the index we stored into * the cpufreq frequency table in powernow_decode_bios, * vid are the upper 8 bits. */ fid = powernow_table[index].driver_data & 0xFF; vid = (powernow_table[index].driver_data & 0xFF00) >> 8; rdmsrl(MSR_K7_FID_VID_STATUS, fidvidstatus.val); cfid = fidvidstatus.bits.CFID; freqs.old = fsb * fid_codes[cfid] / 10; freqs.new = powernow_table[index].frequency; /* Now do the magic poking into the MSRs. */ if (have_a0 == 1) /* A0 errata 5 */ local_irq_disable(); if (freqs.old > freqs.new) { /* Going down, so change FID first */ change_FID(fid); change_VID(vid); } else { /* Going up, so change VID first */ change_VID(vid); change_FID(fid); } if (have_a0 == 1) local_irq_enable(); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones13987.42%861.54%
viresh kumarviresh kumar127.55%323.08%
brian gerstbrian gerst63.77%17.69%
bruno ducrotbruno ducrot21.26%17.69%
Total159100.00%13100.00%

#ifdef CONFIG_X86_POWERNOW_K7_ACPI static struct acpi_processor_performance *acpi_processor_perf;
static int powernow_acpi_init(void) { int i; int retval = 0; union powernow_acpi_control_t pc; if (acpi_processor_perf != NULL && powernow_table != NULL) { retval = -EINVAL; goto err0; } acpi_processor_perf = kzalloc(sizeof(*acpi_processor_perf), GFP_KERNEL); if (!acpi_processor_perf) { retval = -ENOMEM; goto err0; } if (!zalloc_cpumask_var(&acpi_processor_perf->shared_cpu_map, GFP_KERNEL)) { retval = -ENOMEM; goto err05; } if (acpi_processor_register_performance(acpi_processor_perf, 0)) { retval = -EIO; goto err1; } if (acpi_processor_perf->control_register.space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) { retval = -ENODEV; goto err2; } if (acpi_processor_perf->status_register.space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) { retval = -ENODEV; goto err2; } number_scales = acpi_processor_perf->state_count; if (number_scales < 2) { retval = -ENODEV; goto err2; } powernow_table = kzalloc((sizeof(*powernow_table) * (number_scales + 1)), GFP_KERNEL); if (!powernow_table) { retval = -ENOMEM; goto err2; } pc.val = (unsigned long) acpi_processor_perf->states[0].control; for (i = 0; i < number_scales; i++) { u8 fid, vid; struct acpi_processor_px *state = &acpi_processor_perf->states[i]; unsigned int speed, speed_mhz; pc.val = (unsigned long) state->control; pr_debug("acpi: P%d: %d MHz %d mW %d uS control %08x SGTC %d\n", i, (u32) state->core_frequency, (u32) state->power, (u32) state->transition_latency, (u32) state->control, pc.bits.sgtc); vid = pc.bits.vid; fid = pc.bits.fid; powernow_table[i].frequency = fsb * fid_codes[fid] / 10; powernow_table[i].driver_data = fid; /* lower 8 bits */ powernow_table[i].driver_data |= (vid << 8); /* upper 8 bits */ speed = powernow_table[i].frequency; speed_mhz = speed / 1000; /* processor_perflib will multiply the MHz value by 1000 to * get a KHz value (e.g. 1266000). However, powernow-k7 works * with true KHz values (e.g. 1266768). To ensure that all * powernow frequencies are available, we must ensure that * ACPI doesn't restrict them, so we round up the MHz value * to ensure that perflib's computed KHz value is greater than * or equal to powernow's KHz value. */ if (speed % 1000 > 0) speed_mhz++; if ((fid_codes[fid] % 10) == 5) { if (have_a0 == 1) invalidate_entry(i); } pr_debug(" FID: 0x%x (%d.%dx [%dMHz]) " "VID: 0x%x (%d.%03dV)\n", fid, fid_codes[fid] / 10, fid_codes[fid] % 10, speed_mhz, vid, mobile_vid_table[vid]/1000, mobile_vid_table[vid]%1000); if (state->core_frequency != speed_mhz) { state->core_frequency = speed_mhz; pr_debug(" Corrected ACPI frequency to %d\n", speed_mhz); } if (latency < pc.bits.sgtc) latency = pc.bits.sgtc; if (speed < minimum_speed) minimum_speed = speed; if (speed > maximum_speed) maximum_speed = speed; } powernow_table[i].frequency = CPUFREQ_TABLE_END; powernow_table[i].driver_data = 0; /* notify BIOS that we exist */ acpi_processor_notify_smm(THIS_MODULE); return 0; err2: acpi_processor_unregister_performance(0); err1: free_cpumask_var(acpi_processor_perf->shared_cpu_map); err05: kfree(acpi_processor_perf); err0: pr_warn("ACPI perflib can not be used on this platform\n"); acpi_processor_perf = NULL; return retval; }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones47981.05%1055.56%
daniel drakedaniel drake6110.32%15.56%
rusty russellrusty russell315.25%15.56%
viresh kumarviresh kumar71.18%211.11%
len brownlen brown61.02%15.56%
joe perchesjoe perches30.51%15.56%
dominik brodowskidominik brodowski30.51%15.56%
yinghai luyinghai lu10.17%15.56%
Total591100.00%18100.00%

#else
static int powernow_acpi_init(void) { pr_info("no support for ACPI processor found - please recompile your kernel with ACPI processor\n"); return -EINVAL; }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones1482.35%150.00%
joe perchesjoe perches317.65%150.00%
Total17100.00%2100.00%

#endif
static void print_pst_entry(struct pst_s *pst, unsigned int j) { pr_debug("PST:%d (@%p)\n", j, pst); pr_debug(" cpuid: 0x%x fsb: %d maxFID: 0x%x startvid: 0x%x\n", pst->cpuid, pst->fsbspeed, pst->maxfid, pst->startvid); }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones4395.56%150.00%
dominik brodowskidominik brodowski24.44%150.00%
Total45100.00%2100.00%


static int powernow_decode_bios(int maxfid, int startvid) { struct psb_s *psb; struct pst_s *pst; unsigned int i, j; unsigned char *p; unsigned int etuple; unsigned int ret; etuple = cpuid_eax(0x80000001); for (i = 0xC0000; i < 0xffff0 ; i += 16) { p = phys_to_virt(i); if (memcmp(p, "AMDK7PNOW!", 10) == 0) { pr_debug("Found PSB header at %p\n", p); psb = (struct psb_s *) p; pr_debug("Table version: 0x%x\n", psb->tableversion); if (psb->tableversion != 0x12) { pr_info("Sorry, only v1.2 tables supported right now\n"); return -ENODEV; } pr_debug("Flags: 0x%x\n", psb->flags); if ((psb->flags & 1) == 0) pr_debug("Mobile voltage regulator\n"); else pr_debug("Desktop voltage regulator\n"); latency = psb->settlingtime; if (latency < 100) { pr_info("BIOS set settling time to %d microseconds. Should be at least 100. Correcting.\n", latency); latency = 100; } pr_debug("Settling Time: %d microseconds.\n", psb->settlingtime); pr_debug("Has %d PST tables. (Only dumping ones " "relevant to this CPU).\n", psb->numpst); p += sizeof(*psb); pst = (struct pst_s *) p; for (j = 0; j < psb->numpst; j++) { pst = (struct pst_s *) p; number_scales = pst->numpstates; if ((etuple == pst->cpuid) && check_fsb(pst->fsbspeed) && (maxfid == pst->maxfid) && (startvid == pst->startvid)) { print_pst_entry(pst, j); p = (char *)pst + sizeof(*pst); ret = get_ranges(p); return ret; } else { unsigned int k; p = (char *)pst + sizeof(*pst); for (k = 0; k < number_scales; k++) p += 2; } } pr_info("No PST tables match this cpuid (0x%x)\n", etuple); pr_info("This is indicative of a broken BIOS\n"); return -EINVAL; } p++; } return -ENODEV; }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones35592.93%872.73%
joe perchesjoe perches143.66%19.09%
dominik brodowskidominik brodowski71.83%19.09%
viresh kumarviresh kumar61.57%19.09%
Total382100.00%11100.00%

/* * We use the fact that the bus frequency is somehow * a multiple of 100000/3 khz, then we compute sgtc according * to this multiple. * That way, we match more how AMD thinks all of that work. * We will then get the same kind of behaviour already tested under * the "well-known" other OS. */
static int fixup_sgtc(void) { unsigned int sgtc; unsigned int m; m = fsb / 3333; if ((m % 10) >= 5) m += 5; m /= 10; sgtc = 100 * m * latency; sgtc = sgtc / 3; if (sgtc > 0xfffff) { pr_warn("SGTC too large %d\n", sgtc); sgtc = 0xfffff; } return sgtc; }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones7396.05%150.00%
joe perchesjoe perches33.95%150.00%
Total76100.00%2100.00%


static unsigned int powernow_get(unsigned int cpu) { union msr_fidvidstatus fidvidstatus; unsigned int cfid; if (cpu) return 0; rdmsrl(MSR_K7_FID_VID_STATUS, fidvidstatus.val); cfid = fidvidstatus.bits.CFID; return fsb * fid_codes[cfid] / 10; }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones53100.00%1100.00%
Total53100.00%1100.00%


static int acer_cpufreq_pst(const struct dmi_system_id *d) { pr_warn("%s laptop with broken PST tables in BIOS detected\n", d->ident); pr_warn("You need to downgrade to 3A21 (09/09/2002), or try a newer BIOS than 3A71 (01/20/2003)\n"); pr_warn("cpufreq scaling has been disabled as a result of this\n"); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
andrey paninandrey panin2161.76%133.33%
joe perchesjoe perches1235.29%133.33%
thomas gleixnerthomas gleixner12.94%133.33%
Total34100.00%3100.00%

/* * Some Athlon laptops have really fucked PST tables. * A BIOS update is all that can save them. * Mention this, and disable cpufreq. */ static struct dmi_system_id powernow_dmi_table[] = { { .callback = acer_cpufreq_pst, .ident = "Acer Aspire", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Insyde Software"), DMI_MATCH(DMI_BIOS_VERSION, "3A71"), }, }, { } };
static int powernow_cpu_init(struct cpufreq_policy *policy) { union msr_fidvidstatus fidvidstatus; int result; if (policy->cpu != 0) return -ENODEV; rdmsrl(MSR_K7_FID_VID_STATUS, fidvidstatus.val); recalibrate_cpu_khz(); fsb = (10 * cpu_khz) / fid_codes[fidvidstatus.bits.CFID]; if (!fsb) { pr_warn("can not determine bus frequency\n"); return -EINVAL; } pr_debug("FSB: %3dMHz\n", fsb/1000); if (dmi_check_system(powernow_dmi_table) || acpi_force) { pr_info("PSB/PST known to be broken - trying ACPI instead\n"); result = powernow_acpi_init(); } else { result = powernow_decode_bios(fidvidstatus.bits.MFID, fidvidstatus.bits.SVID); if (result) { pr_info("Trying ACPI perflib\n"); maximum_speed = 0; minimum_speed = -1; latency = 0; result = powernow_acpi_init(); if (result) { pr_info("ACPI and legacy methods failed\n"); } } else { /* SGTC use the bus clock as timer */ latency = fixup_sgtc(); pr_info("SGTC: %d\n", latency); } } if (result) return result; pr_info("Minimum speed %d MHz - Maximum speed %d MHz\n", minimum_speed/1000, maximum_speed/1000); policy->cpuinfo.transition_latency = cpufreq_scale(2000000UL, fsb, latency); return cpufreq_table_validate_and_show(policy, powernow_table); }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones19285.71%1066.67%
joe perchesjoe perches198.48%16.67%
dominik brodowskidominik brodowski83.57%213.33%
andrey paninandrey panin41.79%16.67%
viresh kumarviresh kumar10.45%16.67%
Total224100.00%15100.00%


static int powernow_cpu_exit(struct cpufreq_policy *policy) { #ifdef CONFIG_X86_POWERNOW_K7_ACPI if (acpi_processor_perf) { acpi_processor_unregister_performance(0); free_cpumask_var(acpi_processor_perf->shared_cpu_map); kfree(acpi_processor_perf); } #endif kfree(powernow_table); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones4085.11%266.67%
rusty russellrusty russell714.89%133.33%
Total47100.00%3100.00%

static struct cpufreq_driver powernow_driver = { .verify = cpufreq_generic_frequency_table_verify, .target_index = powernow_target, .get = powernow_get, #ifdef CONFIG_X86_POWERNOW_K7_ACPI .bios_limit = acpi_processor_get_bios_limit, #endif .init = powernow_cpu_init, .exit = powernow_cpu_exit, .name = "powernow-k7", .attr = cpufreq_generic_attr, };
static int __init powernow_init(void) { if (check_powernow() == 0) return -ENODEV; return cpufreq_register_driver(&powernow_driver); }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones27100.00%1100.00%
Total27100.00%1100.00%


static void __exit powernow_exit(void) { cpufreq_unregister_driver(&powernow_driver); }

Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones15100.00%1100.00%
Total15100.00%1100.00%

module_param(acpi_force, int, 0444); MODULE_PARM_DESC(acpi_force, "Force ACPI to be used."); MODULE_AUTHOR("Dave Jones"); MODULE_DESCRIPTION("Powernow driver for AMD K7 processors."); MODULE_LICENSE("GPL"); late_initcall(powernow_init); module_exit(powernow_exit);

Overall Contributors

PersonTokensPropCommitsCommitProp
dave jonesdave jones248786.72%4867.61%
joe perchesjoe perches732.55%22.82%
andrey paninandrey panin702.44%11.41%
daniel drakedaniel drake612.13%11.41%
dominik brodowskidominik brodowski461.60%34.23%
rusty russellrusty russell381.32%11.41%
viresh kumarviresh kumar341.19%68.45%
andi kleenandi kleen280.98%11.41%
thomas renningerthomas renninger100.35%11.41%
len brownlen brown60.21%11.41%
brian gerstbrian gerst60.21%11.41%
mike travismike travis40.14%11.41%
bruno ducrotbruno ducrot20.07%11.41%
ben hutchingsben hutchings10.03%11.41%
yinghai luyinghai lu10.03%11.41%
thomas gleixnerthomas gleixner10.03%11.41%
Total2868100.00%71100.00%
Directory: drivers/cpufreq
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
{% endraw %}