Skip to content

Commit

Permalink
[PATCH] Time: i386 Clocksource Drivers
Browse files Browse the repository at this point in the history
Implement the time sources for i386 (acpi_pm, cyclone, hpet, pit, and tsc).
With this patch, the conversion of the i386 arch to the generic timekeeping
code should be complete.

The patch should be fairly straight forward, only adding the new clocksources.

[hirofumi@mail.parknet.co.jp: acpi_pm cleanup]
Signed-off-by: John Stultz <johnstul@us.ibm.com>
Signed-off-by: Adrian Bunk <bunk@stusta.de>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
Signed-off-by: John Stultz <johnstul@us.ibm.com>
Signed-off-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
  • Loading branch information
john stultz authored and Linus Torvalds committed Jun 26, 2006
1 parent 61743fe commit 5d0cf41
Show file tree
Hide file tree
Showing 11 changed files with 594 additions and 6 deletions.
5 changes: 5 additions & 0 deletions Documentation/kernel-parameters.txt
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ running once the system is up.
override platform specific driver.
See also Documentation/acpi-hotkey.txt.

acpi_pm_good [IA-32,X86-64]
Override the pmtimer bug detection: force the kernel
to assume that this machine's pmtimer latches its value
and always returns good values.

enable_timer_pin_1 [i386,x86-64]
Enable PIN 1 of APIC timer
Can be useful to work around chipset bugs
Expand Down
1 change: 1 addition & 0 deletions arch/i386/kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ obj-$(CONFIG_EFI) += efi.o efi_stub.o
obj-$(CONFIG_DOUBLEFAULT) += doublefault.o
obj-$(CONFIG_VM86) += vm86.o
obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
obj-$(CONFIG_HPET_TIMER) += hpet.o

EXTRA_AFLAGS := -traditional

Expand Down
67 changes: 67 additions & 0 deletions arch/i386/kernel/hpet.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#include <linux/clocksource.h>
#include <linux/errno.h>
#include <linux/hpet.h>
#include <linux/init.h>

#include <asm/hpet.h>
#include <asm/io.h>

#define HPET_MASK 0xFFFFFFFF
#define HPET_SHIFT 22

/* FSEC = 10^-15 NSEC = 10^-9 */
#define FSEC_PER_NSEC 1000000

static void *hpet_ptr;

static cycle_t read_hpet(void)
{
return (cycle_t)readl(hpet_ptr);
}

static struct clocksource clocksource_hpet = {
.name = "hpet",
.rating = 250,
.read = read_hpet,
.mask = (cycle_t)HPET_MASK,
.mult = 0, /* set below */
.shift = HPET_SHIFT,
.is_continuous = 1,
};

static int __init init_hpet_clocksource(void)
{
unsigned long hpet_period;
void __iomem* hpet_base;
u64 tmp;

if (!hpet_address)
return -ENODEV;

/* calculate the hpet address: */
hpet_base =
(void __iomem*)ioremap_nocache(hpet_address, HPET_MMAP_SIZE);
hpet_ptr = hpet_base + HPET_COUNTER;

/* calculate the frequency: */
hpet_period = readl(hpet_base + HPET_PERIOD);

/*
* hpet period is in femto seconds per cycle
* so we need to convert this to ns/cyc units
* aproximated by mult/2^shift
*
* fsec/cyc * 1nsec/1000000fsec = nsec/cyc = mult/2^shift
* fsec/cyc * 1ns/1000000fsec * 2^shift = mult
* fsec/cyc * 2^shift * 1nsec/1000000fsec = mult
* (fsec/cyc << shift)/1000000 = mult
* (hpet_period << shift)/FSEC_PER_NSEC = mult
*/
tmp = (u64)hpet_period << HPET_SHIFT;
do_div(tmp, FSEC_PER_NSEC);
clocksource_hpet.mult = (u32)tmp;

return register_clocksource(&clocksource_hpet);
}

module_init(init_hpet_clocksource);
53 changes: 53 additions & 0 deletions arch/i386/kernel/i8253.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* i8253.c 8253/PIT functions
*
*/
#include <linux/clocksource.h>
#include <linux/spinlock.h>
#include <linux/jiffies.h>
#include <linux/sysdev.h>
Expand Down Expand Up @@ -30,3 +31,55 @@ void setup_pit_timer(void)
outb(LATCH >> 8 , PIT_CH0); /* MSB */
spin_unlock_irqrestore(&i8253_lock, flags);
}

/*
* Since the PIT overflows every tick, its not very useful
* to just read by itself. So use jiffies to emulate a free
* running counter:
*/
static cycle_t pit_read(void)
{
unsigned long flags;
int count;
u64 jifs;

spin_lock_irqsave(&i8253_lock, flags);
outb_p(0x00, PIT_MODE); /* latch the count ASAP */
count = inb_p(PIT_CH0); /* read the latched count */
count |= inb_p(PIT_CH0) << 8;

/* VIA686a test code... reset the latch if count > max + 1 */
if (count > LATCH) {
outb_p(0x34, PIT_MODE);
outb_p(LATCH & 0xff, PIT_CH0);
outb(LATCH >> 8, PIT_CH0);
count = LATCH - 1;
}
spin_unlock_irqrestore(&i8253_lock, flags);

jifs = jiffies_64;

jifs -= INITIAL_JIFFIES;
count = (LATCH-1) - count;

return (cycle_t)(jifs * LATCH) + count;
}

static struct clocksource clocksource_pit = {
.name = "pit",
.rating = 110,
.read = pit_read,
.mask = (cycle_t)-1,
.mult = 0,
.shift = 20,
};

static int __init init_pit_clocksource(void)
{
if (num_possible_cpus() > 4) /* PIT does not scale! */
return 0;

clocksource_pit.mult = clocksource_hz2mult(CLOCK_TICK_RATE, 20);
return register_clocksource(&clocksource_pit);
}
module_init(init_pit_clocksource);
3 changes: 0 additions & 3 deletions arch/i386/kernel/time.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,6 @@ extern unsigned long wall_jiffies;
DEFINE_SPINLOCK(rtc_lock);
EXPORT_SYMBOL(rtc_lock);

/* XXX - necessary to keep things compiling. to be removed later */
u32 pmtmr_ioport;

/*
* This is a special lock that is owned by the CPU and holds the index
* register we are working with. It is required for NMI access to the
Expand Down
161 changes: 161 additions & 0 deletions arch/i386/kernel/tsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
* See comments there for proper credits.
*/

#include <linux/clocksource.h>
#include <linux/workqueue.h>
#include <linux/cpufreq.h>
#include <linux/jiffies.h>
#include <linux/init.h>
#include <linux/dmi.h>

#include <asm/delay.h>
#include <asm/tsc.h>
#include <asm/delay.h>
#include <asm/io.h>
Expand Down Expand Up @@ -315,3 +318,161 @@ static int __init cpufreq_tsc(void)
core_initcall(cpufreq_tsc);

#endif

/* clock source code */

static unsigned long current_tsc_khz = 0;
static int tsc_update_callback(void);

static cycle_t read_tsc(void)
{
cycle_t ret;

rdtscll(ret);

return ret;
}

static struct clocksource clocksource_tsc = {
.name = "tsc",
.rating = 300,
.read = read_tsc,
.mask = (cycle_t)-1,
.mult = 0, /* to be set */
.shift = 22,
.update_callback = tsc_update_callback,
.is_continuous = 1,
};

static int tsc_update_callback(void)
{
int change = 0;

/* check to see if we should switch to the safe clocksource: */
if (clocksource_tsc.rating != 50 && check_tsc_unstable()) {
clocksource_tsc.rating = 50;
reselect_clocksource();
change = 1;
}

/* only update if tsc_khz has changed: */
if (current_tsc_khz != tsc_khz) {
current_tsc_khz = tsc_khz;
clocksource_tsc.mult = clocksource_khz2mult(current_tsc_khz,
clocksource_tsc.shift);
change = 1;
}

return change;
}

static int __init dmi_mark_tsc_unstable(struct dmi_system_id *d)
{
printk(KERN_NOTICE "%s detected: marking TSC unstable.\n",
d->ident);
mark_tsc_unstable();
return 0;
}

/* List of systems that have known TSC problems */
static struct dmi_system_id __initdata bad_tsc_dmi_table[] = {
{
.callback = dmi_mark_tsc_unstable,
.ident = "IBM Thinkpad 380XD",
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "IBM"),
DMI_MATCH(DMI_BOARD_NAME, "2635FA0"),
},
},
{}
};

#define TSC_FREQ_CHECK_INTERVAL (10*MSEC_PER_SEC) /* 10sec in MS */
static struct timer_list verify_tsc_freq_timer;

/* XXX - Probably should add locking */
static void verify_tsc_freq(unsigned long unused)
{
static u64 last_tsc;
static unsigned long last_jiffies;

u64 now_tsc, interval_tsc;
unsigned long now_jiffies, interval_jiffies;


if (check_tsc_unstable())
return;

rdtscll(now_tsc);
now_jiffies = jiffies;

if (!last_jiffies) {
goto out;
}

interval_jiffies = now_jiffies - last_jiffies;
interval_tsc = now_tsc - last_tsc;
interval_tsc *= HZ;
do_div(interval_tsc, cpu_khz*1000);

if (interval_tsc < (interval_jiffies * 3 / 4)) {
printk("TSC appears to be running slowly. "
"Marking it as unstable\n");
mark_tsc_unstable();
return;
}

out:
last_tsc = now_tsc;
last_jiffies = now_jiffies;
/* set us up to go off on the next interval: */
mod_timer(&verify_tsc_freq_timer,
jiffies + msecs_to_jiffies(TSC_FREQ_CHECK_INTERVAL));
}

/*
* Make an educated guess if the TSC is trustworthy and synchronized
* over all CPUs.
*/
static __init int unsynchronized_tsc(void)
{
/*
* Intel systems are normally all synchronized.
* Exceptions must mark TSC as unstable:
*/
if (boot_cpu_data.x86_vendor == X86_VENDOR_INTEL)
return 0;

/* assume multi socket systems are not synchronized: */
return num_possible_cpus() > 1;
}

static int __init init_tsc_clocksource(void)
{

if (cpu_has_tsc && tsc_khz && !tsc_disable) {
/* check blacklist */
dmi_check_system(bad_tsc_dmi_table);

if (unsynchronized_tsc()) /* mark unstable if unsynced */
mark_tsc_unstable();
current_tsc_khz = tsc_khz;
clocksource_tsc.mult = clocksource_khz2mult(current_tsc_khz,
clocksource_tsc.shift);
/* lower the rating if we already know its unstable: */
if (check_tsc_unstable())
clocksource_tsc.rating = 50;

init_timer(&verify_tsc_freq_timer);
verify_tsc_freq_timer.function = verify_tsc_freq;
verify_tsc_freq_timer.expires =
jiffies + msecs_to_jiffies(TSC_FREQ_CHECK_INTERVAL);
add_timer(&verify_tsc_freq_timer);

return register_clocksource(&clocksource_tsc);
}

return 0;
}

module_init(init_tsc_clocksource);
1 change: 1 addition & 0 deletions drivers/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,5 @@ obj-$(CONFIG_SGI_SN) += sn/
obj-y += firmware/
obj-$(CONFIG_CRYPTO) += crypto/
obj-$(CONFIG_SUPERH) += sh/
obj-$(CONFIG_GENERIC_TIME) += clocksource/
obj-$(CONFIG_DMA_ENGINE) += dma/
2 changes: 2 additions & 0 deletions drivers/clocksource/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
obj-$(CONFIG_X86_CYCLONE_TIMER) += cyclone.o
obj-$(CONFIG_X86_PM_TIMER) += acpi_pm.o
Loading

0 comments on commit 5d0cf41

Please sign in to comment.