kernel-fxtec-pro1x/drivers/clocksource/acpi_pm.c
john stultz 6bb74df481 [PATCH] clocksource init adjustments (fix bug #7426)
This patch resolves the issue found here:
http://bugme.osdl.org/show_bug.cgi?id=7426

The basic summary is:
Currently we register most of i386/x86_64 clocksources at module_init
time. Then we enable clocksource selection at late_initcall time. This
causes some problems for drivers that use gettimeofday for init
calibration routines (specifically the es1968 driver in this case),
where durring module_init, the only clocksource available is the low-res
jiffies clocksource. This may cause slight calibration errors, due to
the small sampling time used.

It should be noted that drivers that require fine grained time may not
function on architectures that do not have better then jiffies
resolution timekeeping (there are a few). However, this does not
discount the reasonable need for such fine-grained timekeeping at init
time.

Thus the solution here is to register clocksources earlier (ideally when
the hardware is being initialized), and then we enable clocksource
selection at fs_initcall (before device_initcall).

This patch should probably get some testing time in -mm, since
clocksource selection is one of the most important issues for correct
timekeeping, and I've only been able to test this on a few of my own
boxes.

Signed-off-by: John Stultz <johnstul@us.ibm.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: "David S. Miller" <davem@davemloft.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-03-05 07:57:53 -08:00

220 lines
5.6 KiB
C

/*
* linux/drivers/clocksource/acpi_pm.c
*
* This file contains the ACPI PM based clocksource.
*
* This code was largely moved from the i386 timer_pm.c file
* which was (C) Dominik Brodowski <linux@brodo.de> 2003
* and contained the following comments:
*
* 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/acpi_pmtmr.h>
#include <linux/clocksource.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <asm/io.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 __read_mostly;
static inline u32 read_pmtmr(void)
{
/* mask the output to 24 bits */
return inl(pmtmr_ioport) & ACPI_PM_MASK;
}
u32 acpi_pm_read_verified(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 clock
* source is not latched, you must read it multiple
* times to ensure a safe value is read:
*/
do {
v1 = read_pmtmr();
v2 = read_pmtmr();
v3 = read_pmtmr();
} while (unlikely((v1 > v2 && v1 < v3) || (v2 > v3 && v2 < v1)
|| (v3 > v1 && v3 < v2)));
return v2;
}
static cycle_t acpi_pm_read_slow(void)
{
return (cycle_t)acpi_pm_read_verified();
}
static cycle_t acpi_pm_read(void)
{
return (cycle_t)read_pmtmr();
}
static struct clocksource clocksource_acpi_pm = {
.name = "acpi_pm",
.rating = 200,
.read = acpi_pm_read,
.mask = (cycle_t)ACPI_PM_MASK,
.mult = 0, /*to be caluclated*/
.shift = 22,
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
#ifdef CONFIG_PCI
static int __devinitdata acpi_pm_good;
static int __init acpi_pm_good_setup(char *__str)
{
acpi_pm_good = 1;
return 1;
}
__setup("acpi_pm_good", acpi_pm_good_setup);
static inline void acpi_pm_need_workaround(void)
{
clocksource_acpi_pm.read = acpi_pm_read_slow;
clocksource_acpi_pm.rating = 110;
}
/*
* PIIX4 Errata:
*
* The power management timer may return improper results when read.
* Although the timer value settles properly after incrementing,
* while incrementing there is a 3 ns window every 69.8 ns where the
* timer value is indeterminate (a 4.2% chance that the data will be
* incorrect when read). As a result, the ACPI free running count up
* timer specification is violated due to erroneous reads.
*/
static void __devinit acpi_pm_check_blacklist(struct pci_dev *dev)
{
u8 rev;
if (acpi_pm_good)
return;
pci_read_config_byte(dev, PCI_REVISION_ID, &rev);
/* the bug has been fixed in PIIX4M */
if (rev < 3) {
printk(KERN_WARNING "* Found PM-Timer Bug on the chipset."
" Due to workarounds for a bug,\n"
"* this clock source is slow. Consider trying"
" other clock sources\n");
acpi_pm_need_workaround();
}
}
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3,
acpi_pm_check_blacklist);
static void __devinit acpi_pm_check_graylist(struct pci_dev *dev)
{
if (acpi_pm_good)
return;
printk(KERN_WARNING "* The chipset may have PM-Timer Bug. Due to"
" workarounds for a bug,\n"
"* this clock source is slow. If you are sure your timer"
" does not have\n"
"* this bug, please use \"acpi_pm_good\" to disable the"
" workaround\n");
acpi_pm_need_workaround();
}
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0,
acpi_pm_check_graylist);
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_LE,
acpi_pm_check_graylist);
#endif
#ifndef CONFIG_X86_64
#include "mach_timer.h"
#define PMTMR_EXPECTED_RATE \
((CALIBRATE_LATCH * (PMTMR_TICKS_PER_SEC >> 10)) / (CLOCK_TICK_RATE>>10))
/*
* Some boards have the PMTMR running way too fast. We check
* the PMTMR rate against PIT channel 2 to catch these cases.
*/
static int verify_pmtmr_rate(void)
{
u32 value1, value2;
unsigned long count, delta;
mach_prepare_counter();
value1 = read_pmtmr();
mach_countup(&count);
value2 = read_pmtmr();
delta = (value2 - value1) & ACPI_PM_MASK;
/* Check that the PMTMR delta is within 5% of what we expect */
if (delta < (PMTMR_EXPECTED_RATE * 19) / 20 ||
delta > (PMTMR_EXPECTED_RATE * 21) / 20) {
printk(KERN_INFO "PM-Timer running at invalid rate: %lu%% "
"of normal - aborting.\n",
100UL * delta / PMTMR_EXPECTED_RATE);
return -1;
}
return 0;
}
#else
#define verify_pmtmr_rate() (0)
#endif
static int __init init_acpi_pm_clocksource(void)
{
u32 value1, value2;
unsigned int i;
if (!pmtmr_ioport)
return -ENODEV;
clocksource_acpi_pm.mult = clocksource_hz2mult(PMTMR_TICKS_PER_SEC,
clocksource_acpi_pm.shift);
/* "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:
if (verify_pmtmr_rate() != 0)
return -ENODEV;
return clocksource_register(&clocksource_acpi_pm);
}
/* We use fs_initcall because we want the PCI fixups to have run
* but we still need to load before device_initcall
*/
fs_initcall(init_acpi_pm_clocksource);