From: Dominik Brodowski 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. arch/i386/Kconfig | 18 ++++ arch/i386/kernel/acpi/boot.c | 31 ++++++++ arch/i386/kernel/timers/Makefile | 1 arch/i386/kernel/timers/timer.c | 3 arch/i386/kernel/timers/timer_pm.c | 139 +++++++++++++++++++++++++++++++++++++ include/asm-i386/timer.h | 3 6 files changed, 195 insertions(+) diff -puN arch/i386/Kconfig~acpi-pm-timer arch/i386/Kconfig --- 25/arch/i386/Kconfig~acpi-pm-timer 2003-12-30 16:49:25.000000000 -0800 +++ 25-akpm/arch/i386/Kconfig 2003-12-30 16:49:25.000000000 -0800 @@ -416,6 +416,24 @@ config HPET_TIMER config HPET_EMULATE_RTC def_bool HPET_TIMER && RTC=y +config X86_PM_TIMER + bool "Power Management Timer Support" + depends on (!X86_VISWS && !X86_VISWS) && 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 (see above), you should say "Y" + here. Otherwise, say "N". + config SMP bool "Symmetric multi-processing support" ---help--- diff -puN arch/i386/kernel/acpi/boot.c~acpi-pm-timer arch/i386/kernel/acpi/boot.c --- 25/arch/i386/kernel/acpi/boot.c~acpi-pm-timer 2003-12-30 16:49:25.000000000 -0800 +++ 25-akpm/arch/i386/kernel/acpi/boot.c 2003-12-30 16:49:25.000000000 -0800 @@ -363,6 +363,33 @@ static int __init acpi_parse_hpet(unsign } #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; + + fadt = __va(phys); + + 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) { @@ -566,5 +593,9 @@ acpi_boot_init (void) acpi_table_parse(ACPI_HPET, acpi_parse_hpet); #endif +#ifdef CONFIG_X86_PM_TIMER + acpi_table_parse(ACPI_FADT, acpi_parse_fadt); +#endif + return 0; } diff -puN arch/i386/kernel/timers/Makefile~acpi-pm-timer arch/i386/kernel/timers/Makefile --- 25/arch/i386/kernel/timers/Makefile~acpi-pm-timer 2003-12-30 16:49:25.000000000 -0800 +++ 25-akpm/arch/i386/kernel/timers/Makefile 2003-12-30 16:49:25.000000000 -0800 @@ -6,3 +6,4 @@ obj-y := timer.o timer_none.o timer_tsc. obj-$(CONFIG_X86_CYCLONE_TIMER) += timer_cyclone.o obj-$(CONFIG_HPET_TIMER) += timer_hpet.o +obj-$(CONFIG_X86_PM_TIMER) += timer_pm.o diff -puN arch/i386/kernel/timers/timer.c~acpi-pm-timer arch/i386/kernel/timers/timer.c --- 25/arch/i386/kernel/timers/timer.c~acpi-pm-timer 2003-12-30 16:49:25.000000000 -0800 +++ 25-akpm/arch/i386/kernel/timers/timer.c 2003-12-30 16:49:25.000000000 -0800 @@ -19,6 +19,9 @@ static struct timer_opts* timers[] = { #ifdef CONFIG_HPET_TIMER &timer_hpet, #endif +#ifdef CONFIG_X86_PM_TIMER + &timer_pmtmr, +#endif &timer_tsc, &timer_pit, NULL, diff -puN /dev/null arch/i386/kernel/timers/timer_pm.c --- /dev/null 2002-08-30 16:31:37.000000000 -0700 +++ 25-akpm/arch/i386/kernel/timers/timer_pm.c 2003-12-30 16:49:25.000000000 -0800 @@ -0,0 +1,139 @@ +/* + * (C) Dominik Brodowski 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 +#include +#include +#include +#include +#include +#include +#include +#include + + +/* 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 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; + + /* "verify" this timing source */ + value1 = inl(pmtmr_ioport); + value1 &= 0xFFFFFF; + for (i=0; i < 10000; i++) { + value2 = inl(pmtmr_ioport); + value2 &= 0xFFFFFF; + if (value2 == value1) + continue; + if (value2 > value1) + return 0; + if ((value2 < value1) && ((value2) < 0xFFF)) + return 0; + 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; +} + + +/* + * this gets called during each timer interrupt + */ +static void mark_offset_pmtmr(void) +{ + offset_tick = inl(pmtmr_ioport); + offset_tick &= 0xFFFFFF; /* limit it to 24 bits */ + return; +} + + +static unsigned long long monotonic_clock_pmtmr(void) +{ + return 0; +} + + +/* + * copied from delay_pit + */ +static void delay_pmtmr(unsigned long loops) +{ + int d0; + __asm__ __volatile__( + "\tjmp 1f\n" + ".align 16\n" + "1:\tjmp 2f\n" + ".align 16\n" + "2:\tdecl %0\n\tjns 2b" + :"=&a" (d0) + :"0" (loops)); +} + + +/* + * get the offset (in microseconds) from the last call to mark_offset() + */ +static unsigned long get_offset_pmtmr(void) +{ + u32 now, offset, delta = 0; + + offset = offset_tick; + now = inl(pmtmr_ioport); + now &= 0xFFFFFF; + if (likely(offset < now)) + delta = now - offset; + else if (offset > now) + delta = (0xFFFFFF - offset) + now; + + /* 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. + */ + delta *= 286; + return (unsigned long) (delta >> 10); +} + + +/* acpi timer_opts struct */ +struct timer_opts timer_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 "); +MODULE_DESCRIPTION("Power Management Timer (PMTMR) as primary timing source for x86"); diff -puN include/asm-i386/timer.h~acpi-pm-timer include/asm-i386/timer.h --- 25/include/asm-i386/timer.h~acpi-pm-timer 2003-12-30 16:49:25.000000000 -0800 +++ 25-akpm/include/asm-i386/timer.h 2003-12-30 16:49:25.000000000 -0800 @@ -45,4 +45,7 @@ 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 _