Commit 0ed1fce0 authored by Dominik Brodowski's avatar Dominik Brodowski Committed by Jeff Garzik

[PATCH] (3/5) CPUfreq i386 drivers

CPUFreq i386 drivers for 2.5.39:
arch/i386/config.in				Necessary config options
arch/i386/kernel/cpu/Makefile			allow for compilation of the CPUFreq subdirectory
arch/i386/kernel/cpu/cpufreq/Makefile		Makefile for CPUFreq drivers
arch/i386/kernel/cpu/cpufreq/elanfreq.c		CPUFreq driver for AMD Elan processors
arch/i386/kernel/cpu/cpufreq/longhaul.c		CPUFreq driver for VIA Longhaul processors
arch/i386/kernel/cpu/cpufreq/longrun.c		CPUFreq driver for Transmeta Crusoe processors
arch/i386/kernel/cpu/cpufreq/p4-clockmod.c	CPUFreq driver for Pentium 4 Xeon processors (using clock modulation)
arch/i386/kernel/cpu/cpufreq/powernow-k6.c	CPUFreq driver for mobile AMD K6-2+ and mobile AMD K6-3+ processors
arch/i386/kernel/cpu/cpufreq/speedstep.c	CPUFreq drivers for ICH2-M and ICH3-M chipsets and Intel Pentium 3-M and 4-M processors.
parent 6ea7844f
......@@ -190,6 +190,18 @@ bool 'Machine Check Exception' CONFIG_X86_MCE
dep_bool 'Check for non-fatal errors on Athlon/Duron' CONFIG_X86_MCE_NONFATAL $CONFIG_X86_MCE
dep_bool 'check for P4 thermal throttling interrupt.' CONFIG_X86_MCE_P4THERMAL $CONFIG_X86_MCE $CONFIG_X86_UP_APIC
bool 'CPU Frequency scaling' CONFIG_CPU_FREQ
if [ "$CONFIG_CPU_FREQ" = "y" ]; then
define_bool CONFIG_CPU_FREQ_26_API y
tristate ' AMD Mobile K6-2/K6-3 PowerNow!' CONFIG_X86_POWERNOW_K6
if [ "$CONFIG_MELAN" = "y" ]; then
tristate ' AMD Elan' CONFIG_ELAN_CPUFREQ
fi
tristate ' VIA Cyrix III Longhaul' CONFIG_X86_LONGHAUL
tristate ' Intel Speedstep' CONFIG_X86_SPEEDSTEP
tristate ' Intel Pentium 4 clock modulation' CONFIG_X86_P4_CLOCKMOD
tristate ' Transmeta LongRun' CONFIG_X86_LONGRUN
fi
tristate 'Toshiba Laptop support' CONFIG_TOSHIBA
tristate 'Dell laptop support' CONFIG_I8K
......
......@@ -14,5 +14,6 @@ obj-y += nexgen.o
obj-y += umc.o
obj-$(CONFIG_MTRR) += mtrr/
obj-$(CONFIG_CPU_FREQ) += cpufreq/
include $(TOPDIR)/Rules.make
obj-$(CONFIG_X86_POWERNOW_K6) += powernow-k6.o
obj-$(CONFIG_X86_LONGHAUL) += longhaul.o
obj-$(CONFIG_X86_SPEEDSTEP) += speedstep.o
obj-$(CONFIG_X86_P4_CLOCKMOD) += p4-clockmod.o
obj-$(CONFIG_ELAN_CPUFREQ) += elanfreq.o
obj-$(CONFIG_X86_LONGRUN) += longrun.o
include $(TOPDIR)/Rules.make
/*
* elanfreq: cpufreq driver for the AMD ELAN family
*
* (c) Copyright 2002 Robert Schwebel <r.schwebel@pengutronix.de>
*
* Parts of this code are (c) Sven Geggus <sven@geggus.net>
*
* All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* 2002-02-13: - initial revision for 2.4.18-pre9 by Robert Schwebel
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/cpufreq.h>
#include <asm/msr.h>
#include <asm/timex.h>
#include <asm/io.h>
#define REG_CSCIR 0x22 /* Chip Setup and Control Index Register */
#define REG_CSCDR 0x23 /* Chip Setup and Control Data Register */
#define SAFE_FREQ 33000 /* every Elan CPU can run at 33 MHz */
static struct cpufreq_driver *elanfreq_driver;
/* Module parameter */
static int max_freq;
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Robert Schwebel <r.schwebel@pengutronix.de>, Sven Geggus <sven@geggus.net>");
MODULE_DESCRIPTION("cpufreq driver for AMD's Elan CPUs");
struct s_elan_multiplier {
int clock; /* frequency in kHz */
int val40h; /* PMU Force Mode register */
int val80h; /* CPU Clock Speed Register */
};
/*
* It is important that the frequencies
* are listed in ascending order here!
*/
struct s_elan_multiplier elan_multiplier[] = {
{1000, 0x02, 0x18},
{2000, 0x02, 0x10},
{4000, 0x02, 0x08},
{8000, 0x00, 0x00},
{16000, 0x00, 0x02},
{33000, 0x00, 0x04},
{66000, 0x01, 0x04},
{99000, 0x01, 0x05}
};
/**
* elanfreq_get_cpu_frequency: determine current cpu speed
*
* Finds out at which frequency the CPU of the Elan SOC runs
* at the moment. Frequencies from 1 to 33 MHz are generated
* the normal way, 66 and 99 MHz are called "Hyperspeed Mode"
* and have the rest of the chip running with 33 MHz.
*/
static unsigned int elanfreq_get_cpu_frequency(void)
{
u8 clockspeed_reg; /* Clock Speed Register */
local_irq_disable();
outb_p(0x80,REG_CSCIR);
clockspeed_reg = inb_p(REG_CSCDR);
local_irq_enable();
if ((clockspeed_reg & 0xE0) == 0xE0) { return 0; }
/* Are we in CPU clock multiplied mode (66/99 MHz)? */
if ((clockspeed_reg & 0xE0) == 0xC0) {
if ((clockspeed_reg & 0x01) == 0) {
return 66000;
} else {
return 99000;
}
}
/* 33 MHz is not 32 MHz... */
if ((clockspeed_reg & 0xE0)==0xA0)
return 33000;
return ((1<<((clockspeed_reg & 0xE0) >> 5)) * 1000);
}
/**
* elanfreq_set_cpu_frequency: Change the CPU core frequency
* @cpu: cpu number
* @freq: frequency in kHz
*
* This function takes a frequency value and changes the CPU frequency
* according to this. Note that the frequency has to be checked by
* elanfreq_validatespeed() for correctness!
*
* There is no return value.
*/
static void elanfreq_set_cpu_state (unsigned int state) {
struct cpufreq_freqs freqs;
if (!elanfreq_driver) {
printk(KERN_ERR "cpufreq: initialization problem or invalid target frequency\n");
return;
}
freqs.old = elanfreq_get_cpu_frequency();
freqs.new = elan_multiplier[state].clock;
freqs.cpu = CPUFREQ_ALL_CPUS; /* elanfreq.c is UP only driver */
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
printk(KERN_INFO "elanfreq: attempting to set frequency to %i kHz\n",elan_multiplier[state].clock);
/*
* Access to the Elan's internal registers is indexed via
* 0x22: Chip Setup & Control Register Index Register (CSCI)
* 0x23: Chip Setup & Control Register Data Register (CSCD)
*
*/
/*
* 0x40 is the Power Management Unit's Force Mode Register.
* Bit 6 enables Hyperspeed Mode (66/100 MHz core frequency)
*/
local_irq_disable();
outb_p(0x40,REG_CSCIR); /* Disable hyperspeed mode */
outb_p(0x00,REG_CSCDR);
local_irq_enable(); /* wait till internal pipelines and */
udelay(1000); /* buffers have cleaned up */
local_irq_disable();
/* now, set the CPU clock speed register (0x80) */
outb_p(0x80,REG_CSCIR);
outb_p(elan_multiplier[state].val80h,REG_CSCDR);
/* now, the hyperspeed bit in PMU Force Mode Register (0x40) */
outb_p(0x40,REG_CSCIR);
outb_p(elan_multiplier[state].val40h,REG_CSCDR);
udelay(10000);
local_irq_enable();
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
};
/**
* elanfreq_validatespeed: test if frequency range is valid
*
* This function checks if a given frequency range in kHz is valid
* for the hardware supported by the driver.
*/
static void elanfreq_verify (struct cpufreq_policy *policy)
{
unsigned int number_states = 0;
unsigned int i;
if (!policy || !max_freq)
return;
policy->cpu = 0;
cpufreq_verify_within_limits(policy, 1000, max_freq);
for (i=(sizeof(elan_multiplier)/sizeof(struct s_elan_multiplier) - 1); i>=0; i--)
if ((elan_multiplier[i].clock >= policy->min) &&
(elan_multiplier[i].clock <= policy->max))
number_states++;
if (number_states)
return;
for (i=(sizeof(elan_multiplier)/sizeof(struct s_elan_multiplier) - 1); i>=0; i--)
if (elan_multiplier[i].clock < policy->max)
break;
policy->max = elan_multiplier[i+1].clock;
return;
}
static void elanfreq_setpolicy (struct cpufreq_policy *policy)
{
unsigned int number_states = 0;
unsigned int i, j=4;
if (!elanfreq_driver)
return;
for (i=(sizeof(elan_multiplier)/sizeof(struct s_elan_multiplier) - 1); i>=0; i--)
if ((elan_multiplier[i].clock >= policy->min) &&
(elan_multiplier[i].clock <= policy->max))
{
number_states++;
j = i;
}
if (number_states == 1) {
elanfreq_set_cpu_state(j);
return;
}
switch (policy->policy) {
case CPUFREQ_POLICY_POWERSAVE:
for (i=(sizeof(elan_multiplier)/sizeof(struct s_elan_multiplier) - 1); i>=0; i--)
if ((elan_multiplier[i].clock >= policy->min) &&
(elan_multiplier[i].clock <= policy->max))
j = i;
break;
case CPUFREQ_POLICY_PERFORMANCE:
for (i=0; i<(sizeof(elan_multiplier)/sizeof(struct s_elan_multiplier) - 1); i++)
if ((elan_multiplier[i].clock >= policy->min) &&
(elan_multiplier[i].clock <= policy->max))
j = i;
break;
default:
return;
}
if (elan_multiplier[j].clock > max_freq)
BUG();
elanfreq_set_cpu_state(j);
return;
}
/*
* Module init and exit code
*/
#ifndef MODULE
/**
* elanfreq_setup - elanfreq command line parameter parsing
*
* elanfreq command line parameter. Use:
* elanfreq=66000
* to set the maximum CPU frequency to 66 MHz. Note that in
* case you do not give this boot parameter, the maximum
* frequency will fall back to _current_ CPU frequency which
* might be lower. If you build this as a module, use the
* max_freq module parameter instead.
*/
static int __init elanfreq_setup(char *str)
{
max_freq = simple_strtoul(str, &str, 0);
return 1;
}
__setup("elanfreq=", elanfreq_setup);
#endif
static int __init elanfreq_init(void)
{
struct cpuinfo_x86 *c = cpu_data;
struct cpufreq_driver *driver;
int ret;
/* Test if we have the right hardware */
if ((c->x86_vendor != X86_VENDOR_AMD) ||
(c->x86 != 4) || (c->x86_model!=10))
{
printk(KERN_INFO "elanfreq: error: no Elan processor found!\n");
return -ENODEV;
}
driver = kmalloc(sizeof(struct cpufreq_driver) +
NR_CPUS * sizeof(struct cpufreq_policy), GFP_KERNEL);
if (!driver)
return -ENOMEM;
driver->policy = (struct cpufreq_policy *) (driver + sizeof(struct cpufreq_driver));
if (!max_freq)
max_freq = elanfreq_get_cpu_frequency();
#ifdef CONFIG_CPU_FREQ_24_API
driver->cpu_min_freq = 1000;
driver->cpu_cur_freq[0] = elanfreq_get_cpu_frequency();
#endif
driver->verify = &elanfreq_verify;
driver->setpolicy = &elanfreq_setpolicy;
driver->policy[0].cpu = 0;
driver->policy[0].min = 1000;
driver->policy[0].max = max_freq;
driver->policy[0].policy = CPUFREQ_POLICY_PERFORMANCE;
driver->policy[0].max_cpu_freq = max_freq;
ret = cpufreq_register(driver);
if (ret) {
kfree(driver);
return ret;
}
elanfreq_driver = driver;
return 0;
}
static void __exit elanfreq_exit(void)
{
if (elanfreq_driver) {
cpufreq_unregister();
kfree(elanfreq_driver);
}
}
module_init(elanfreq_init);
module_exit(elanfreq_exit);
MODULE_PARM (max_freq, "i");
This diff is collapsed.
/*
* $Id: longrun.c,v 1.10 2002/09/22 09:01:41 db Exp $
*
* (C) 2002 Dominik Brodowski <linux@brodo.de>
*
* Licensed under the terms of the GNU GPL License version 2.
*
* BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/cpufreq.h>
#include <asm/msr.h>
#include <asm/processor.h>
#include <asm/timex.h>
static struct cpufreq_driver *longrun_driver;
/**
* longrun_{low,high}_freq is needed for the conversion of cpufreq kHz
* values into per cent values. In TMTA microcode, the following is valid:
* performance_pctg = (current_freq - low_freq)/(high_freq - low_freq)
*/
static unsigned int longrun_low_freq, longrun_high_freq;
/**
* longrun_get_policy - get the current LongRun policy
* @policy: struct cpufreq_policy where current policy is written into
*
* Reads the current LongRun policy by access to MSR_TMTA_LONGRUN_FLAGS
* and MSR_TMTA_LONGRUN_CTRL
*/
static void longrun_get_policy(struct cpufreq_policy *policy)
{
u32 msr_lo, msr_hi;
if (!longrun_driver)
return;
rdmsr(MSR_TMTA_LONGRUN_FLAGS, msr_lo, msr_hi);
if (msr_lo & 0x01)
policy->policy = CPUFREQ_POLICY_PERFORMANCE;
else
policy->policy = CPUFREQ_POLICY_POWERSAVE;
rdmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi);
msr_lo &= 0x0000007F;
msr_hi &= 0x0000007F;
policy->min = longrun_low_freq + msr_lo *
((longrun_high_freq - longrun_low_freq) / 100);
policy->min = longrun_low_freq + msr_hi *
((longrun_high_freq - longrun_low_freq) / 100);
policy->cpu = 0;
}
/**
* longrun_set_policy - sets a new CPUFreq policy
* @policy - new policy
*
* Sets a new CPUFreq policy on LongRun-capable processors. This function
* has to be called with cpufreq_driver locked.
*/
static void longrun_set_policy(struct cpufreq_policy *policy)
{
u32 msr_lo, msr_hi;
u32 pctg_lo, pctg_hi;
if (!longrun_driver || !policy)
return;
pctg_lo = (policy->min - longrun_low_freq) /
((longrun_high_freq - longrun_low_freq) / 100);
pctg_hi = (policy->max - longrun_low_freq) /
((longrun_high_freq - longrun_low_freq) / 100);
if (pctg_hi > 100)
pctg_hi = 100;
if (pctg_lo > pctg_hi)
pctg_lo = pctg_hi;
/* performance or economy mode */
rdmsr(MSR_TMTA_LONGRUN_FLAGS, msr_lo, msr_hi);
msr_lo &= 0xFFFFFFFE;
switch (policy->policy) {
case CPUFREQ_POLICY_PERFORMANCE:
msr_lo |= 0x00000001;
break;
case CPUFREQ_POLICY_POWERSAVE:
break;
}
wrmsr(MSR_TMTA_LONGRUN_FLAGS, msr_lo, msr_hi);
/* lower and upper boundary */
rdmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi);
msr_lo &= 0xFFFFFF80;
msr_hi &= 0xFFFFFF80;
msr_lo |= pctg_lo;
msr_hi |= pctg_hi;
wrmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi);
return;
}
/**
* longrun_verify_poliy - verifies a new CPUFreq policy
*
* Validates a new CPUFreq policy. This function has to be called with
* cpufreq_driver locked.
*/
static void longrun_verify_policy(struct cpufreq_policy *policy)
{
if (!policy || !longrun_driver)
return;
policy->cpu = 0;
cpufreq_verify_within_limits(policy, 0,
longrun_driver->policy[0].max_cpu_freq);
return;
}
/**
* longrun_determine_freqs - determines the lowest and highest possible core frequency
*
* Determines the lowest and highest possible core frequencies on this CPU.
* This is neccessary to calculate the performance percentage according to
* TMTA rules:
* performance_pctg = (target_freq - low_freq)/(high_freq - low_freq)
*/
static unsigned int __init longrun_determine_freqs(unsigned int *low_freq,
unsigned int *high_freq)
{
u32 msr_lo, msr_hi;
u32 save_lo, save_hi;
u32 eax, ebx, ecx, edx;
struct cpuinfo_x86 *c = cpu_data;
if (!low_freq || !high_freq)
return -EINVAL;
if (cpu_has(c, X86_FEATURE_LRTI)) {
/* if the LongRun Table Interface is present, the
* detection is a bit easier:
* For minimum frequency, read out the maximum
* level (msr_hi), write that into "currently
* selected level", and read out the frequency.
* For maximum frequency, read out level zero.
*/
/* minimum */
rdmsr(MSR_TMTA_LRTI_READOUT, msr_lo, msr_hi);
wrmsr(MSR_TMTA_LRTI_READOUT, msr_hi, msr_hi);
rdmsr(MSR_TMTA_LRTI_VOLT_MHZ, msr_lo, msr_hi);
*low_freq = msr_lo * 1000; /* to kHz */
/* maximum */
wrmsr(MSR_TMTA_LRTI_READOUT, 0, msr_hi);
rdmsr(MSR_TMTA_LRTI_VOLT_MHZ, msr_lo, msr_hi);
*high_freq = msr_lo * 1000; /* to kHz */
if (*low_freq > *high_freq)
*low_freq = *high_freq;
return 0;
}
/* set the upper border to the value determined during TSC init */
*high_freq = (cpu_khz / 1000);
*high_freq = *high_freq * 1000;
/* get current borders */
rdmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi);
save_lo = msr_lo & 0x0000007F;
save_hi = msr_hi & 0x0000007F;
/* if current perf_pctg is larger than 90%, we need to decrease the
* upper limit to make the calculation more accurate.
*/
cpuid(0x80860007, &eax, &ebx, &ecx, &edx);
if (ecx > 90) {
/* set to 0 to 80 perf_pctg */
msr_lo &= 0xFFFFFF80;
msr_hi &= 0xFFFFFF80;
msr_lo |= 0;
msr_hi |= 80;
wrmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi);
/* read out current core MHz and current perf_pctg */
cpuid(0x80860007, &eax, &ebx, &ecx, &edx);
/* restore values */
wrmsr(MSR_TMTA_LONGRUN_CTRL, save_lo, save_hi);
}
/* performance_pctg = (current_freq - low_freq)/(high_freq - low_freq)
* eqals
* low_freq * ( 1 - perf_pctg) = (cur_freq - high_freq * perf_pctg)
*
* high_freq * perf_pctg is stored tempoarily into "ebx".
*/
ebx = (((cpu_khz / 1000) * ecx) / 100); /* to MHz */
if ((ecx > 95) || (ecx == 0) || (eax < ebx))
return -EIO;
edx = (eax - ebx) / (100 - ecx);
*low_freq = edx * 1000; /* back to kHz */
if (*low_freq > *high_freq)
*low_freq = *high_freq;
return 0;
}
/**
* longrun_init - initializes the Transmeta Crusoe LongRun CPUFreq driver
*
* Initializes the LongRun support.
*/
static int __init longrun_init(void)
{
int result;
struct cpufreq_driver *driver;
struct cpuinfo_x86 *c = cpu_data;
if (c->x86_vendor != X86_VENDOR_TRANSMETA ||
!cpu_has(c, X86_FEATURE_LONGRUN))
return 0;
/* initialization of main "cpufreq" code*/
driver = kmalloc(sizeof(struct cpufreq_driver) +
NR_CPUS * sizeof(struct cpufreq_policy), GFP_KERNEL);
if (!driver)
return -ENOMEM;
driver->policy = (struct cpufreq_policy *) (driver + sizeof(struct cpufreq_driver));
if (longrun_determine_freqs(&longrun_low_freq, &longrun_high_freq)) {
kfree(driver);
return -EIO;
}
driver->policy[0].max_cpu_freq = longrun_high_freq;
longrun_get_policy(&driver->policy[0]);
driver->verify = &longrun_verify_policy;
driver->setpolicy = &longrun_set_policy;
result = cpufreq_register(driver);
if (result) {
kfree(driver);
return result;
}
longrun_driver = driver;
return 0;
}
/**
* longrun_exit - unregisters LongRun support
*/
static void __exit longrun_exit(void)
{
if (longrun_driver) {
cpufreq_unregister();
kfree(longrun_driver);
}
}
MODULE_AUTHOR ("Dominik Brodowski <linux@brodo.de>");
MODULE_DESCRIPTION ("LongRun driver for Transmeta Crusoe processors.");
MODULE_LICENSE ("GPL");
module_init(longrun_init);
module_exit(longrun_exit);
/*
* Pentium 4/Xeon CPU on demand clock modulation/speed scaling
* (C) 2002 Zwane Mwaikambo <zwane@commfireservices.com>
* (C) 2002 Arjan van de Ven <arjanv@redhat.com>
* (C) 2002 Tora T. Engstad
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* The author(s) of this software shall not be held liable for damages
* of any nature resulting due to the use of this software. This
* software is provided AS-IS with no warranties.
*
* Date Errata Description
* 20020525 N44, O17 12.5% or 25% DC causes lockup
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/smp.h>
#include <linux/cpufreq.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <asm/processor.h>
#include <asm/msr.h>
#include <asm/timex.h>
#define PFX "cpufreq: "
/*
* Duty Cycle (3bits), note DC_DISABLE is not specified in
* intel docs i just use it to mean disable
*/
enum {
DC_RESV, DC_DFLT, DC_25PT, DC_38PT, DC_50PT,
DC_64PT, DC_75PT, DC_88PT, DC_DISABLE
};
#define DC_ENTRIES 8
static int has_N44_O17_errata;
static int stock_freq;
MODULE_PARM(stock_freq, "i");
static struct cpufreq_driver *cpufreq_p4_driver;
static unsigned int cpufreq_p4_old_state = 0;
static int cpufreq_p4_setdc(unsigned int cpu, unsigned int newstate)
{
u32 l, h;
unsigned long cpus_allowed;
struct cpufreq_freqs freqs;
if (!cpu_online(cpu) || (newstate > DC_DISABLE) ||
(newstate == DC_RESV))
return -EINVAL;
cpu = cpu >> 1; /* physical CPU #nr */
/* notifiers */
freqs.old = stock_freq * cpufreq_p4_old_state / 8;
freqs.new = stock_freq * newstate / 8;
freqs.cpu = 2*cpu;
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
freqs.cpu++;
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
/* switch to physical CPU where state is to be changed*/
cpus_allowed = current->cpus_allowed;
set_cpus_allowed(current, 3 << (2 * cpu));
BUG_ON(cpu != (smp_processor_id() >> 1));
rdmsr(MSR_IA32_THERM_STATUS, l, h);
if (l & 0x01)
printk(KERN_DEBUG PFX "CPU#%d currently thermal throttled\n", cpu);
if (has_N44_O17_errata && (newstate == DC_25PT || newstate == DC_DFLT))
newstate = DC_38PT;
rdmsr(MSR_IA32_THERM_CONTROL, l, h);
if (newstate == DC_DISABLE) {
printk(KERN_INFO PFX "CPU#%d,%d disabling modulation\n", cpu, (cpu + 1));
wrmsr(MSR_IA32_THERM_CONTROL, l & ~(1<<4), h);
} else {
printk(KERN_INFO PFX "CPU#%d,%d setting duty cycle to %d%%\n", cpu, (cpu + 1), ((125 * newstate) / 10));
/* bits 63 - 5 : reserved
* bit 4 : enable/disable
* bits 3-1 : duty cycle
* bit 0 : reserved
*/
l = (l & ~14);
l = l | (1<<4) | ((newstate & 0x7)<<1);
wrmsr(MSR_IA32_THERM_CONTROL, l, h);
}
set_cpus_allowed(current, cpus_allowed);
/* notifiers */
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
freqs.cpu--;
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
cpufreq_p4_old_state = newstate;
return 0;
}
static void cpufreq_p4_setpolicy(struct cpufreq_policy *policy)
{
unsigned int i;
unsigned int newstate = 0;
unsigned int number_states = 0;
if (!cpufreq_p4_driver || !stock_freq || !policy)
return;
if (policy->policy == CPUFREQ_POLICY_POWERSAVE)
{
for (i=8; i>0; i++)
if ((policy->min <= ((stock_freq / 8) * i)) &&
(policy->max >= ((stock_freq / 8) * i)))
{
newstate = i;
number_states++;
}
} else {
for (i=0; i<=8; i--)
if ((policy->min <= ((stock_freq / 8) * i)) &&
(policy->max >= ((stock_freq / 8) * i)))
{
newstate = i;
number_states++;
}
}
/* if (number_states == 1) */
{
if (policy->cpu == CPUFREQ_ALL_CPUS) {
for (i=0; i<(NR_CPUS/2); i++)
if (cpu_online(2*i))
cpufreq_p4_setdc((2*i), newstate);
} else {
cpufreq_p4_setdc(policy->cpu, newstate);
}
}
/* else {
if (policy->policy == CPUFREQ_POLICY_POWERSAVE) {
min_state = newstate;
max_state = newstate + (number_states - 1);
} else {
max_state = newstate;
min_state = newstate - (number_states - 1);
}
} */
}
static void cpufreq_p4_verify(struct cpufreq_policy *policy)
{
unsigned int number_states = 0;
unsigned int i;
if (!cpufreq_p4_driver || !stock_freq || !policy)
return;
if (!cpu_online(policy->cpu))
policy->cpu = CPUFREQ_ALL_CPUS;
cpufreq_verify_within_limits(policy, (stock_freq / 8), stock_freq);
/* is there at least one state within limit? */
for (i=1; i<=8; i++)
if ((policy->min <= ((stock_freq / 8) * i)) &&
(policy->max >= ((stock_freq / 8) * i)))
number_states++;
if (number_states)
return;
policy->max = (stock_freq / 8) * (((unsigned int) ((policy->max * 8) / stock_freq)) + 1);
return;
}
int __init cpufreq_p4_init(void)
{
struct cpuinfo_x86 *c = cpu_data;
int cpuid;
int ret;
struct cpufreq_driver *driver;
unsigned int i;
/*
* THERM_CONTROL is architectural for IA32 now, so
* we can rely on the capability checks
*/
if (c->x86_vendor != X86_VENDOR_INTEL)
return -ENODEV;
if (!test_bit(X86_FEATURE_ACPI, c->x86_capability) ||
!test_bit(X86_FEATURE_ACC, c->x86_capability))
return -ENODEV;
/* Errata workarounds */
cpuid = (c->x86 << 8) | (c->x86_model << 4) | c->x86_mask;
switch (cpuid) {
case 0x0f07:
case 0x0f0a:
case 0x0f11:
case 0x0f12:
has_N44_O17_errata = 1;
default:
break;
}
printk(KERN_INFO PFX "P4/Xeon(TM) CPU On-Demand Clock Modulation available\n");
driver = kmalloc(sizeof(struct cpufreq_driver) +
NR_CPUS * sizeof(struct cpufreq_freqs), GFP_KERNEL);
if (!driver)
return -ENOMEM;
driver->policy = (struct cpufreq_policy *) (driver + sizeof(struct cpufreq_driver));
if (!stock_freq)
stock_freq = cpu_khz;
#ifdef CONFIG_CPU_FREQ_24_API
driver->cpu_min_freq = stock_freq / 8;
for (i=0;i<NR_CPUS;i++)
driver->cpu_cur_freq[i] = stock_freq;
#endif
cpufreq_p4_old_state = DC_DISABLE;
driver->verify = &cpufreq_p4_verify;
driver->setpolicy = &cpufreq_p4_setpolicy;
for (i=0;i<NR_CPUS;i++) {
if (has_N44_O17_errata)
driver->policy[i].min = (stock_freq * 3) / 8;
else
driver->policy[i].min = stock_freq / 8;
driver->policy[i].max = stock_freq;
driver->policy[i].policy = CPUFREQ_POLICY_PERFORMANCE;
driver->policy[i].max_cpu_freq = stock_freq;
driver->policy[i].cpu = i;
}
ret = cpufreq_register(driver);
if (ret) {
kfree(driver);
return ret;
}
cpufreq_p4_driver = driver;
return 0;
}
void __exit cpufreq_p4_exit(void)
{
u32 l, h;
if (cpufreq_p4_driver) {
cpufreq_unregister();
/* return back to a non modulated state */
rdmsr(MSR_IA32_THERM_CONTROL, l, h);
wrmsr(MSR_IA32_THERM_CONTROL, l & ~(1<<4), h);
kfree(cpufreq_p4_driver);
}
}
MODULE_AUTHOR ("Zwane Mwaikambo <zwane@commfireservices.com>");
MODULE_DESCRIPTION ("cpufreq driver for Pentium(TM) 4/Xeon(TM)");
MODULE_LICENSE ("GPL");
module_init(cpufreq_p4_init);
module_exit(cpufreq_p4_exit);
/*
* $Id: powernow-k6.c,v 1.31 2002/09/21 09:05:29 db Exp $
* This file was part of Powertweak Linux (http://powertweak.sf.net)
* and is shared with the Linux Kernel module.
*
* (C) 2000-2002 Dave Jones, Arjan van de Ven, Janne Pnkl, Dominik Brodowski.
*
* Licensed under the terms of the GNU GPL License version 2.
*
* BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <asm/msr.h>
#include <asm/timex.h>
#include <asm/io.h>
#define POWERNOW_IOPORT 0xfff0 /* it doesn't matter where, as long
as it is unused */
static struct cpufreq_driver *powernow_driver;
static unsigned int busfreq; /* FSB, in 10 kHz */
static unsigned int max_multiplier;
/* Clock ratio multiplied by 10 - see table 27 in AMD#23446 */
static int clock_ratio[8] = {
45, /* 000 -> 4.5x */
50, /* 001 -> 5.0x */
40, /* 010 -> 4.0x */
55, /* 011 -> 5.5x */
20, /* 100 -> 2.0x */
30, /* 101 -> 3.0x */
60, /* 110 -> 6.0x */
35 /* 111 -> 3.5x */
};
/**
* powernow_k6_get_cpu_multiplier - returns the current FSB multiplier
*
* Returns the current setting of the frequency multiplier. Core clock
* speed is frequency of the Front-Side Bus multiplied with this value.
*/
static int powernow_k6_get_cpu_multiplier(void)
{
u64 invalue = 0;
u32 msrval;
msrval = POWERNOW_IOPORT + 0x1;
wrmsr(MSR_K6_EPMR, msrval, 0); /* enable the PowerNow port */
invalue=inl(POWERNOW_IOPORT + 0x8);
msrval = POWERNOW_IOPORT + 0x0;
wrmsr(MSR_K6_EPMR, msrval, 0); /* disable it again */
return clock_ratio[(invalue >> 5)&7];
}
/**
* powernow_k6_set_state - set the PowerNow! multiplier
* @best_i: clock_ratio[best_i] is the target multiplier
*
* Tries to change the PowerNow! multiplier
*/
static void powernow_k6_set_state (unsigned int best_i)
{
unsigned long outvalue=0, invalue=0;
unsigned long msrval;
struct cpufreq_freqs freqs;
if (!powernow_driver) {
printk(KERN_ERR "cpufreq: initialization problem or invalid target frequency\n");
return;
}
freqs.old = busfreq * powernow_k6_get_cpu_multiplier();
freqs.new = busfreq * clock_ratio[best_i];
freqs.cpu = CPUFREQ_ALL_CPUS; /* powernow-k6.c is UP only driver */
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
/* we now need to transform best_i to the BVC format, see AMD#23446 */
outvalue = (1<<12) | (1<<10) | (1<<9) | (best_i<<5);
msrval = POWERNOW_IOPORT + 0x1;
wrmsr(MSR_K6_EPMR, msrval, 0); /* enable the PowerNow port */
invalue=inl(POWERNOW_IOPORT + 0x8);
invalue = invalue & 0xf;
outvalue = outvalue | invalue;
outl(outvalue ,(POWERNOW_IOPORT + 0x8));
msrval = POWERNOW_IOPORT + 0x0;
wrmsr(MSR_K6_EPMR, msrval, 0); /* disable it again */
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
return;
}
/**
* powernow_k6_verify - verifies a new CPUfreq policy
* @policy: new policy
*
* Policy must be within lowest and highest possible CPU Frequency,
* and at least one possible state must be within min and max.
*/
static void powernow_k6_verify(struct cpufreq_policy *policy)
{
unsigned int number_states = 0;
unsigned int i, j;
if (!policy || !busfreq)
return;
policy->cpu = 0;
cpufreq_verify_within_limits(policy, (20 * busfreq),
(max_multiplier * busfreq));
for (i=0; i<8; i++)
if ((policy->min <= (busfreq * clock_ratio[i])) &&
(policy->max >= (busfreq * clock_ratio[i])))
number_states++;
if (number_states)
return;
/* no state is available within range -- find next larger state */
j = 6;
for (i=0; i<8; i++)
if (((clock_ratio[i] * busfreq) >= policy->min) &&
(clock_ratio[i] < clock_ratio[j]))
j = i;
policy->max = clock_ratio[j] * busfreq;
return;
}
/**
* powernow_k6_setpolicy - sets a new CPUFreq policy
* @policy - new policy
*
* sets a new CPUFreq policy
*/
static void powernow_k6_setpolicy (struct cpufreq_policy *policy)
{
unsigned int number_states = 0;
unsigned int i, j=4;
if (!powernow_driver)
return;
for (i=0; i<8; i++)
if ((policy->min <= (busfreq * clock_ratio[i])) &&
(policy->max >= (busfreq * clock_ratio[i])))
{
number_states++;
j = i;
}
if (number_states == 1) {
/* if only one state is within the limit borders, it
is easily detected and set */
powernow_k6_set_state(j);
return;
}
/* more than one state within limit */
switch (policy->policy) {
case CPUFREQ_POLICY_POWERSAVE:
j = 6;
for (i=0; i<8; i++)
if ((policy->min <= (busfreq * clock_ratio[i])) &&
(policy->max >= (busfreq * clock_ratio[i])) &&
(clock_ratio[i] < clock_ratio[j]))
j = i;
break;
case CPUFREQ_POLICY_PERFORMANCE:
j = 4;
for (i=0; i<8; i++)
if ((policy->min <= (busfreq * clock_ratio[i])) &&
(policy->max >= (busfreq * clock_ratio[i])) &&
(clock_ratio[i] > clock_ratio[j]))
j = i;
break;
default:
return;
}
if (clock_ratio[i] > max_multiplier)
BUG();
powernow_k6_set_state(j);
return;
}
/**
* powernow_k6_init - initializes the k6 PowerNow! CPUFreq driver
*
* Initializes the K6 PowerNow! support. Returns -ENODEV on unsupported
* devices, -EINVAL or -ENOMEM on problems during initiatization, and zero
* on success.
*/
static int __init powernow_k6_init(void)
{
struct cpuinfo_x86 *c = cpu_data;
struct cpufreq_driver *driver;
unsigned int result;
if ((c->x86_vendor != X86_VENDOR_AMD) || (c->x86 != 5) ||
((c->x86_model != 12) && (c->x86_model != 13)))
return -ENODEV;
max_multiplier = powernow_k6_get_cpu_multiplier();
busfreq = cpu_khz / max_multiplier;
if (!request_region(POWERNOW_IOPORT, 16, "PowerNow!")) {
printk("cpufreq: PowerNow IOPORT region already used.\n");
return -EIO;
}
/* initialization of main "cpufreq" code*/
driver = kmalloc(sizeof(struct cpufreq_driver) +
NR_CPUS * sizeof(struct cpufreq_freqs), GFP_KERNEL);
if (!driver) {
release_region (POWERNOW_IOPORT, 16);
return -ENOMEM;
}
driver->policy = (struct cpufreq_policy *) (driver + sizeof(struct cpufreq_driver));
#ifdef CONFIG_CPU_FREQ_24_API
driver->cpu_min_freq = busfreq * 20;
driver->cpu_cur_freq[0] = busfreq * max_multiplier;
#endif
driver->verify = &powernow_k6_verify;
driver->setpolicy = &powernow_k6_setpolicy;
driver->policy[0].cpu = 0;
driver->policy[0].min = busfreq * 20;
driver->policy[0].max = busfreq * max_multiplier;
driver->policy[0].policy = CPUFREQ_POLICY_PERFORMANCE;
driver->policy[0].max_cpu_freq = busfreq * max_multiplier;
result = cpufreq_register(driver);
if (result) {
release_region (POWERNOW_IOPORT, 16);
kfree(driver);
return result;
}
powernow_driver = driver;
return 0;
}
/**
* powernow_k6_exit - unregisters AMD K6-2+/3+ PowerNow! support
*
* Unregisters AMD K6-2+ / K6-3+ PowerNow! support.
*/
static void __exit powernow_k6_exit(void)
{
unsigned int i;
if (powernow_driver) {
for (i=0;i<8;i++)
if (clock_ratio[i] == max_multiplier)
powernow_k6_set_state(i);
cpufreq_unregister();
kfree(powernow_driver);
}
}
MODULE_AUTHOR ("Arjan van de Ven <arjanv@redhat.com>, Dave Jones <davej@suse.de>, Dominik Brodowski <linux@brodo.de>");
MODULE_DESCRIPTION ("PowerNow! driver for AMD K6-2+ / K6-3+ processors.");
MODULE_LICENSE ("GPL");
module_init(powernow_k6_init);
module_exit(powernow_k6_exit);
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment