Commit ad77865c authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] ACPI PM timer

From: Dominik Brodowski <linux@dominikbrodowski.de>,
      John Stultz <johnstul@us.ibm.com>,
      Dmitry Torokhov

Add the ACPI Powermanagement Timer as x86 kernel timing source.  Unlike the
Time Stamp Counter, it is a reliable timing source which does not get
affected by aggressive powermanagement features like CPU frequency scaling.

Some ideas and some code are based on Arjan van de Ven's implementation for
2.4, and on R.  Byron Moore's drivers/acpi/hardware/hwtimer.c.


We also replace the loop based delay_pmtmr with a TSC based delay_pmtmr,
which resolves a number of issues caused by the loop based delay.  Unsynced
TSCs as well frequency changing TSCs will effect the length of __delay(), but
it seems this method works best.
parent ee6afa31
......@@ -237,7 +237,7 @@ running once the system is up.
Forces specified timesource (if avaliable) to be used
when calculating gettimeofday(). If specicified timesource
is not avalible, it defaults to PIT.
Format: { pit | tsc | cyclone | ... }
Format: { pit | tsc | cyclone | pmtmr }
hpet= [IA-32,HPET] option to disable HPET and use PIT.
Format: disable
......
......@@ -376,6 +376,37 @@ static int __init acpi_parse_hpet(unsigned long phys, unsigned long size)
}
#endif
/* detect the location of the ACPI PM Timer */
#ifdef CONFIG_X86_PM_TIMER
extern u32 pmtmr_ioport;
static int __init acpi_parse_fadt(unsigned long phys, unsigned long size)
{
struct fadt_descriptor_rev2 *fadt =0;
fadt = (struct fadt_descriptor_rev2*) __acpi_map_table(phys,size);
if(!fadt) {
printk(KERN_WARNING PREFIX "Unable to map FADT\n");
return 0;
}
if (fadt->revision >= FADT2_REVISION_ID) {
/* FADT rev. 2 */
if (fadt->xpm_tmr_blk.address_space_id != ACPI_ADR_SPACE_SYSTEM_IO)
return 0;
pmtmr_ioport = fadt->xpm_tmr_blk.address;
} else {
/* FADT rev. 1 */
pmtmr_ioport = fadt->V1_pm_tmr_blk;
}
if (pmtmr_ioport)
printk(KERN_INFO PREFIX "PM-Timer IO Port: %#x\n", pmtmr_ioport);
return 0;
}
#endif
unsigned long __init
acpi_find_rsdp (void)
{
......@@ -448,6 +479,10 @@ acpi_boot_init (void)
return result;
}
#ifdef CONFIG_X86_PM_TIMER
acpi_table_parse(ACPI_FADT, acpi_parse_fadt);
#endif
#ifdef CONFIG_X86_LOCAL_APIC
/*
......
......@@ -6,3 +6,4 @@ obj-y := timer.o timer_none.o timer_tsc.o timer_pit.o common.o
obj-$(CONFIG_X86_CYCLONE_TIMER) += timer_cyclone.o
obj-$(CONFIG_HPET_TIMER) += timer_hpet.o
obj-$(CONFIG_X86_PM_TIMER) += timer_pm.o
......@@ -137,3 +137,23 @@ unsigned long __init calibrate_tsc_hpet(unsigned long *tsc_hpet_quotient_ptr)
}
#endif
/* calculate cpu_khz */
void __init init_cpu_khz(void)
{
if (cpu_has_tsc) {
unsigned long tsc_quotient = calibrate_tsc();
if (tsc_quotient) {
/* report CPU clock rate in Hz.
* The formula is (10^6 * 2^32) / (2^32 * 1 / (clocks/us)) =
* clock/second. Our precision is about 100 ppm.
*/
{ unsigned long eax=0, edx=1000;
__asm__("divl %2"
:"=a" (cpu_khz), "=d" (edx)
:"r" (tsc_quotient),
"0" (eax), "1" (edx));
printk("Detected %lu.%03lu MHz processor.\n", cpu_khz / 1000, cpu_khz % 1000);
}
}
}
}
......@@ -18,6 +18,9 @@ static struct timer_opts* timers[] = {
#endif
#ifdef CONFIG_HPET_TIMER
&timer_hpet,
#endif
#ifdef CONFIG_X86_PM_TIMER
&timer_pmtmr,
#endif
&timer_tsc,
&timer_pit,
......
......@@ -212,26 +212,7 @@ static int __init init_cyclone(char* override)
}
}
/* init cpu_khz.
* XXX - This should really be done elsewhere,
* and in a more generic fashion. -johnstul@us.ibm.com
*/
if (cpu_has_tsc) {
unsigned long tsc_quotient = calibrate_tsc();
if (tsc_quotient) {
/* report CPU clock rate in Hz.
* The formula is (10^6 * 2^32) / (2^32 * 1 / (clocks/us)) =
* clock/second. Our precision is about 100 ppm.
*/
{ unsigned long eax=0, edx=1000;
__asm__("divl %2"
:"=a" (cpu_khz), "=d" (edx)
:"r" (tsc_quotient),
"0" (eax), "1" (edx));
printk("Detected %lu.%03lu MHz processor.\n", cpu_khz / 1000, cpu_khz % 1000);
}
}
}
init_cpu_khz();
/* Everything looks good! */
return 0;
......
/*
* (C) Dominik Brodowski <linux@brodo.de> 2003
*
* Driver to use the Power Management Timer (PMTMR) available in some
* southbridges as primary timing source for the Linux kernel.
*
* Based on parts of linux/drivers/acpi/hardware/hwtimer.c, timer_pit.c,
* timer_hpet.c, and on Arjan van de Ven's implementation for 2.4.
*
* This file is licensed under the GPL v2.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/init.h>
#include <asm/types.h>
#include <asm/timer.h>
#include <asm/smp.h>
#include <asm/io.h>
#include <asm/arch_hooks.h>
/* The I/O port the PMTMR resides at.
* The location is detected during setup_arch(),
* in arch/i386/acpi/boot.c */
u32 pmtmr_ioport = 0;
/* value of the Power timer at last timer interrupt */
static u32 offset_tick;
static u32 offset_delay;
static unsigned long long monotonic_base;
static seqlock_t monotonic_lock = SEQLOCK_UNLOCKED;
#define ACPI_PM_MASK 0xFFFFFF /* limit it to 24 bits */
/*helper function to safely read acpi pm timesource*/
static inline u32 read_pmtmr(void)
{
u32 v1=0,v2=0,v3=0;
/* It has been reported that because of various broken
* chipsets (ICH4, PIIX4 and PIIX4E) where the ACPI PM time
* source is not latched, so you must read it multiple
* times to insure a safe value is read.
*/
do {
v1 = inl(pmtmr_ioport);
v2 = inl(pmtmr_ioport);
v3 = inl(pmtmr_ioport);
} while ((v1 > v2 && v1 < v3) || (v2 > v3 && v2 < v1)
|| (v3 > v1 && v3 < v2));
/* mask the output to 24 bits */
return v2 & ACPI_PM_MASK;
}
static int init_pmtmr(char* override)
{
u32 value1, value2;
unsigned int i;
if (override[0] && strncmp(override,"pmtmr",5))
return -ENODEV;
if (!pmtmr_ioport)
return -ENODEV;
/* we use the TSC for delay_pmtmr, so make sure it exists */
if (!cpu_has_tsc)
return -ENODEV;
/* "verify" this timing source */
value1 = read_pmtmr();
for (i = 0; i < 10000; i++) {
value2 = read_pmtmr();
if (value2 == value1)
continue;
if (value2 > value1)
goto pm_good;
if ((value2 < value1) && ((value2) < 0xFFF))
goto pm_good;
printk(KERN_INFO "PM-Timer had inconsistent results: 0x%#x, 0x%#x - aborting.\n", value1, value2);
return -EINVAL;
}
printk(KERN_INFO "PM-Timer had no reasonable result: 0x%#x - aborting.\n", value1);
return -ENODEV;
pm_good:
init_cpu_khz();
return 0;
}
static inline u32 cyc2us(u32 cycles)
{
/* The Power Management Timer ticks at 3.579545 ticks per microsecond.
* 1 / PM_TIMER_FREQUENCY == 0.27936511 =~ 286/1024 [error: 0.024%]
*
* Even with HZ = 100, delta is at maximum 35796 ticks, so it can
* easily be multiplied with 286 (=0x11E) without having to fear
* u32 overflows.
*/
cycles *= 286;
return (cycles >> 10);
}
/*
* this gets called during each timer interrupt
* - Called while holding the writer xtime_lock
*/
static void mark_offset_pmtmr(void)
{
u32 lost, delta, last_offset;
static int first_run = 1;
last_offset = offset_tick;
write_seqlock(&monotonic_lock);
offset_tick = read_pmtmr();
/* calculate tick interval */
delta = (offset_tick - last_offset) & ACPI_PM_MASK;
/* convert to usecs */
delta = cyc2us(delta);
/* update the monotonic base value */
monotonic_base += delta * NSEC_PER_USEC;
write_sequnlock(&monotonic_lock);
/* convert to ticks */
delta += offset_delay;
lost = delta / (USEC_PER_SEC / HZ);
offset_delay = delta % (USEC_PER_SEC / HZ);
/* compensate for lost ticks */
if (lost >= 2)
jiffies_64 += lost - 1;
/* don't calculate delay for first run,
or if we've got less then a tick */
if (first_run || (lost < 1)) {
first_run = 0;
offset_delay = 0;
}
}
static unsigned long long monotonic_clock_pmtmr(void)
{
u32 last_offset, this_offset;
unsigned long long base, ret;
unsigned seq;
/* atomically read monotonic base & last_offset */
do {
seq = read_seqbegin(&monotonic_lock);
last_offset = offset_tick;
base = monotonic_base;
} while (read_seqretry(&monotonic_lock, seq));
/* Read the pmtmr */
this_offset = read_pmtmr();
/* convert to nanoseconds */
ret = (this_offset - last_offset) & ACPI_PM_MASK;
ret = base + (cyc2us(ret) * NSEC_PER_USEC);
return ret;
}
static void delay_pmtmr(unsigned long loops)
{
unsigned long bclock, now;
rdtscl(bclock);
do
{
rep_nop();
rdtscl(now);
} while ((now-bclock) < loops);
}
/*
* get the offset (in microseconds) from the last call to mark_offset()
* - Called holding a reader xtime_lock
*/
static unsigned long get_offset_pmtmr(void)
{
u32 now, offset, delta = 0;
offset = offset_tick;
now = read_pmtmr();
delta = (now - offset)&ACPI_PM_MASK;
return (unsigned long) offset_delay + cyc2us(delta);
}
/* acpi timer_opts struct */
struct timer_opts timer_pmtmr = {
.name = "pmtmr",
.init = init_pmtmr,
.mark_offset = mark_offset_pmtmr,
.get_offset = get_offset_pmtmr,
.monotonic_clock = monotonic_clock_pmtmr,
.delay = delay_pmtmr,
};
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Dominik Brodowski <linux@brodo.de>");
MODULE_DESCRIPTION("Power Management Timer (PMTMR) as primary timing source for x86");
......@@ -263,5 +263,23 @@ config ACPI_RELAXED_AML
particular, many Toshiba laptops require this for correct operation
of the AC module.
config X86_PM_TIMER
bool "Power Management Timer Support"
depends on X86 && ACPI
depends on ACPI_BOOT && EXPERIMENTAL
default n
help
The Power Management Timer is available on all ACPI-capable,
in most cases even if ACPI is unusable or blacklisted.
This timing source is not affected by powermanagement features
like aggressive processor idling, throttling, frequency and/or
voltage scaling, unlike the commonly used Time Stamp Counter
(TSC) timing source.
So, if you see messages like 'Losing too many ticks!' in the
kernel logs, and/or you are using a this on a notebook which
does not yet have an HPET, you should say "Y" here.
endmenu
......@@ -40,9 +40,13 @@ extern struct timer_opts timer_cyclone;
#endif
extern unsigned long calibrate_tsc(void);
extern void init_cpu_khz(void);
#ifdef CONFIG_HPET_TIMER
extern struct timer_opts timer_hpet;
extern unsigned long calibrate_tsc_hpet(unsigned long *tsc_hpet_quotient_ptr);
#endif
#ifdef CONFIG_X86_PM_TIMER
extern struct timer_opts timer_pmtmr;
#endif
#endif
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