From 7f8033b76c8eea89dec9f658f4a31bcd4fe84bbb Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:05 +0200 Subject: clockevents: Remove unused inline function Signed-off-by: Thomas Gleixner Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- include/linux/clockchips.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/linux/clockchips.h b/include/linux/clockchips.h index def5a659b8a5..f368a6fe8cc9 100644 --- a/include/linux/clockchips.h +++ b/include/linux/clockchips.h @@ -130,7 +130,6 @@ extern void clockevents_notify(unsigned long reason, void *arg); #else -static inline void clockevents_resume_events(void) { } #define clockevents_notify(reason, arg) do { } while (0) #endif -- cgit v1.2.3 From de68d9b173ee657115dd0e584c2365b7954253a5 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:05 +0200 Subject: clockevents: Allow build w/o run-tine usage for migration purposes Migration aid to allow preparatory patches which introduce not yet used parts of clock events code. Signed-off-by: Thomas Gleixner Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- include/linux/clockchips.h | 8 ++++++-- kernel/time/Kconfig | 5 +++++ kernel/time/Makefile | 2 +- kernel/time/clockevents.c | 3 ++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/include/linux/clockchips.h b/include/linux/clockchips.h index f368a6fe8cc9..d2ddea926895 100644 --- a/include/linux/clockchips.h +++ b/include/linux/clockchips.h @@ -8,7 +8,7 @@ #ifndef _LINUX_CLOCKCHIPS_H #define _LINUX_CLOCKCHIPS_H -#ifdef CONFIG_GENERIC_CLOCKEVENTS +#ifdef CONFIG_GENERIC_CLOCKEVENTS_BUILD #include #include @@ -126,9 +126,13 @@ extern int clockevents_register_notifier(struct notifier_block *nb); extern int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, ktime_t now); +#ifdef CONFIG_GENERIC_CLOCKEVENTS extern void clockevents_notify(unsigned long reason, void *arg); - #else +# define clockevents_notify(reason, arg) do { } while (0) +#endif + +#else /* CONFIG_GENERIC_CLOCKEVENTS_BUILD */ #define clockevents_notify(reason, arg) do { } while (0) diff --git a/kernel/time/Kconfig b/kernel/time/Kconfig index f66351126544..8d53106a0a92 100644 --- a/kernel/time/Kconfig +++ b/kernel/time/Kconfig @@ -23,3 +23,8 @@ config HIGH_RES_TIMERS hardware is not capable then this option only increases the size of the kernel image. +config GENERIC_CLOCKEVENTS_BUILD + bool + default y + depends on GENERIC_CLOCKEVENTS || GENERIC_CLOCKEVENTS_MIGR + diff --git a/kernel/time/Makefile b/kernel/time/Makefile index 99b6034fc86b..905b0b50792d 100644 --- a/kernel/time/Makefile +++ b/kernel/time/Makefile @@ -1,6 +1,6 @@ obj-y += timekeeping.o ntp.o clocksource.o jiffies.o timer_list.o -obj-$(CONFIG_GENERIC_CLOCKEVENTS) += clockevents.o +obj-$(CONFIG_GENERIC_CLOCKEVENTS_BUILD) += clockevents.o obj-$(CONFIG_GENERIC_CLOCKEVENTS) += tick-common.o obj-$(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST) += tick-broadcast.o obj-$(CONFIG_TICK_ONESHOT) += tick-oneshot.o diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index 41dd3105ce7f..822beebe664a 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c @@ -194,6 +194,7 @@ void clockevents_exchange_device(struct clock_event_device *old, local_irq_restore(flags); } +#ifdef CONFIG_GENERIC_CLOCKEVENTS /** * clockevents_notify - notification about relevant events */ @@ -222,4 +223,4 @@ void clockevents_notify(unsigned long reason, void *arg) spin_unlock(&clockevents_lock); } EXPORT_SYMBOL_GPL(clockevents_notify); - +#endif -- cgit v1.2.3 From c8a1d398de70a7774359b4720c392891cdd485f9 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: clockevents: fix periodic broadcast for oneshot devices The next_event member of the clock event device is used to keep track of the next periodic event. For one shot only devices it is wrong to clear the variable, as the next event will be based on it. Pointed out by Ralf Baechle Signed-off-by: Thomas Gleixner Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- kernel/time/tick-broadcast.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/kernel/time/tick-broadcast.c b/kernel/time/tick-broadcast.c index 0962e0577660..acf15b49e55b 100644 --- a/kernel/time/tick-broadcast.c +++ b/kernel/time/tick-broadcast.c @@ -176,8 +176,6 @@ static void tick_do_periodic_broadcast(void) */ static void tick_handle_periodic_broadcast(struct clock_event_device *dev) { - dev->next_event.tv64 = KTIME_MAX; - tick_do_periodic_broadcast(); /* -- cgit v1.2.3 From 3c9aea47425885ec8b1f7b0df88c2ebc6f747c9d Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: x86: Fix irq0 / local apic timer accounting The clock events merge introduced a change to the nmi watchdog code to handle the not longer increasing local apic timer count in the broadcast mode. This is fine for UP, but on SMP it pampers over a stuck CPU which is not handling the broadcast interrupt due to the unconditional sum up of local apic timer count and irq0 count. To cover all cases we need to keep track on which CPU irq0 is handled. In theory this is CPU#0 due to the explicit disabling of irq balancing for irq0, but there are systems which ignore this on the hardware level. The per cpu irq0 accounting allows us to remove the irq0 to CPU0 binding as well. Add a per cpu counter for irq0 and evaluate this instead of the global irq0 count in the nmi watchdog code. Signed-off-by: Thomas Gleixner Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86/kernel/nmi_32.c | 3 ++- arch/x86/kernel/time_32.c | 3 +++ include/asm-x86/hardirq_32.h | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/x86/kernel/nmi_32.c b/arch/x86/kernel/nmi_32.c index c7227e2180f8..95d3fc203cf7 100644 --- a/arch/x86/kernel/nmi_32.c +++ b/arch/x86/kernel/nmi_32.c @@ -353,7 +353,8 @@ __kprobes int nmi_watchdog_tick(struct pt_regs * regs, unsigned reason) * Take the local apic timer and PIT/HPET into account. We don't * know which one is active, when we have highres/dyntick on */ - sum = per_cpu(irq_stat, cpu).apic_timer_irqs + kstat_cpu(cpu).irqs[0]; + sum = per_cpu(irq_stat, cpu).apic_timer_irqs + + per_cpu(irq_stat, cpu).irq0_irqs; /* if the none of the timers isn't firing, this cpu isn't doing much */ if (!touched && last_irq_sums[cpu] == sum) { diff --git a/arch/x86/kernel/time_32.c b/arch/x86/kernel/time_32.c index 19a6c678d02e..56dadfc2f41c 100644 --- a/arch/x86/kernel/time_32.c +++ b/arch/x86/kernel/time_32.c @@ -157,6 +157,9 @@ EXPORT_SYMBOL(profile_pc); */ irqreturn_t timer_interrupt(int irq, void *dev_id) { + /* Keep nmi watchdog up to date */ + per_cpu(irq_stat, smp_processor_id()).irq0_irqs++; + #ifdef CONFIG_X86_IO_APIC if (timer_ack) { /* diff --git a/include/asm-x86/hardirq_32.h b/include/asm-x86/hardirq_32.h index 0e358dc405f8..34649585bb59 100644 --- a/include/asm-x86/hardirq_32.h +++ b/include/asm-x86/hardirq_32.h @@ -9,6 +9,7 @@ typedef struct { unsigned long idle_timestamp; unsigned int __nmi_count; /* arch dependent */ unsigned int apic_timer_irqs; /* arch dependent */ + unsigned int irq0_irqs; } ____cacheline_aligned irq_cpustat_t; DECLARE_PER_CPU(irq_cpustat_t, irq_stat); -- cgit v1.2.3 From 5fa3a246ea2e1be2ffaa01343bc183c8c0bfa472 Mon Sep 17 00:00:00 2001 From: Venki Pallipadi Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: x86: block irq balancing for timer Disable irq balancing on IRQ0. Several SIS chipsets lock up when you try to change affinity of IRQ #0. Signed-off-by: Venkatesh Pallipadi Signed-off-by: Andi Kleen Cc: john stultz Signed-off-by: Andrew Morton Signed-off-by: Ingo Molnar Signed-off-by: Thomas Gleixner Signed-off-by: Arjan van de Ven --- arch/x86/kernel/time_64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/time_64.c b/arch/x86/kernel/time_64.c index 6d48a4e826d9..2db4d698a2d3 100644 --- a/arch/x86/kernel/time_64.c +++ b/arch/x86/kernel/time_64.c @@ -360,7 +360,7 @@ void stop_timer_interrupt(void) static struct irqaction irq0 = { .handler = timer_interrupt, - .flags = IRQF_DISABLED | IRQF_IRQPOLL, + .flags = IRQF_DISABLED | IRQF_IRQPOLL | IRQF_NOBALANCING, .mask = CPU_MASK_NONE, .name = "timer" }; -- cgit v1.2.3 From 83d7384f8d4aa216b49cf9cb286ea743054b119f Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: x86: Geode Multi-Function General Purpose Timers support This adds support for Multi-Function General Purpose Timers. It detects the available timers during southbridge init, and provides an API for allocating and setting the timers. They're higher resolution than the standard PIT, so the MFGPTs come in handy for quite a few things. Note that we never clobber the timers that the BIOS might have opted to use; we just check for unused timers. Signed-off-by: Jordan Crouse Signed-off-by: Andres Salomon Cc: Andi Kleen Cc: Alan Cox Signed-off-by: Andrew Morton Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven Signed-off-by: Thomas Gleixner --- Documentation/kernel-parameters.txt | 3 + arch/x86/kernel/Makefile_32 | 2 +- arch/x86/kernel/geode_32.c | 4 + arch/x86/kernel/mfgpt_32.c | 197 ++++++++++++++++++++++++++++++++++++ include/asm-x86/geode.h | 50 +++++++++ 5 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 arch/x86/kernel/mfgpt_32.c diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index a57c1f216b21..2128de6d80ea 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -1160,6 +1160,9 @@ and is between 256 and 4096 characters. It is defined in the file nomce [X86-32] Machine Check Exception + nomfgpt [X86-32] Disable Multi-Function General Purpose + Timer usage (for AMD Geode machines). + noreplace-paravirt [X86-32,PV_OPS] Don't patch paravirt_ops noreplace-smp [X86-32,SMP] Don't replace SMP instructions diff --git a/arch/x86/kernel/Makefile_32 b/arch/x86/kernel/Makefile_32 index c624193740fd..10356443998e 100644 --- a/arch/x86/kernel/Makefile_32 +++ b/arch/x86/kernel/Makefile_32 @@ -39,7 +39,7 @@ obj-$(CONFIG_VM86) += vm86_32.o obj-$(CONFIG_EARLY_PRINTK) += early_printk.o obj-$(CONFIG_HPET_TIMER) += hpet_32.o obj-$(CONFIG_K8_NB) += k8.o -obj-$(CONFIG_MGEODE_LX) += geode_32.o +obj-$(CONFIG_MGEODE_LX) += geode_32.o mfgpt_32.o obj-$(CONFIG_VMI) += vmi_32.o vmiclock_32.o obj-$(CONFIG_PARAVIRT) += paravirt_32.o diff --git a/arch/x86/kernel/geode_32.c b/arch/x86/kernel/geode_32.c index 41e8aec4c61d..f12d8c5d9809 100644 --- a/arch/x86/kernel/geode_32.c +++ b/arch/x86/kernel/geode_32.c @@ -145,10 +145,14 @@ EXPORT_SYMBOL_GPL(geode_gpio_setup_event); static int __init geode_southbridge_init(void) { + int timers; + if (!is_geode()) return -ENODEV; init_lbars(); + timers = geode_mfgpt_detect(); + printk(KERN_INFO "geode: %d MFGPT timers available.\n", timers); return 0; } diff --git a/arch/x86/kernel/mfgpt_32.c b/arch/x86/kernel/mfgpt_32.c new file mode 100644 index 000000000000..3a63a2dd8d27 --- /dev/null +++ b/arch/x86/kernel/mfgpt_32.c @@ -0,0 +1,197 @@ +/* + * Driver/API for AMD Geode Multi-Function General Purpose Timers (MFGPT) + * + * Copyright (C) 2006, Advanced Micro Devices, Inc. + * Copyright (C) 2007, Andres Salomon + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * The MFGPTs are documented in AMD Geode CS5536 Companion Device Data Book. + */ + +/* + * We are using the 32Khz input clock - its the only one that has the + * ranges we find desirable. The following table lists the suitable + * divisors and the associated hz, minimum interval + * and the maximum interval: + * + * Divisor Hz Min Delta (S) Max Delta (S) + * 1 32000 .0005 2.048 + * 2 16000 .001 4.096 + * 4 8000 .002 8.192 + * 8 4000 .004 16.384 + * 16 2000 .008 32.768 + * 32 1000 .016 65.536 + * 64 500 .032 131.072 + * 128 250 .064 262.144 + * 256 125 .128 524.288 + */ + +#include +#include +#include +#include + +#define F_AVAIL 0x01 + +static struct mfgpt_timer_t { + int flags; + struct module *owner; +} mfgpt_timers[MFGPT_MAX_TIMERS]; + +/* Selected from the table above */ + +#define MFGPT_DIVISOR 16 +#define MFGPT_SCALE 4 /* divisor = 2^(scale) */ +#define MFGPT_HZ (32000 / MFGPT_DIVISOR) +#define MFGPT_PERIODIC (MFGPT_HZ / HZ) + +/* Allow for disabling of MFGPTs */ +static int disable; +static int __init mfgpt_disable(char *s) +{ + disable = 1; + return 1; +} +__setup("nomfgpt", mfgpt_disable); + +/* + * Check whether any MFGPTs are available for the kernel to use. In most + * cases, firmware that uses AMD's VSA code will claim all timers during + * bootup; we certainly don't want to take them if they're already in use. + * In other cases (such as with VSAless OpenFirmware), the system firmware + * leaves timers available for us to use. + */ +int __init geode_mfgpt_detect(void) +{ + int count = 0, i; + u16 val; + + if (disable) { + printk(KERN_INFO "geode-mfgpt: Skipping MFGPT setup\n"); + return 0; + } + + for (i = 0; i < MFGPT_MAX_TIMERS; i++) { + val = geode_mfgpt_read(i, MFGPT_REG_SETUP); + if (!(val & MFGPT_SETUP_SETUP)) { + mfgpt_timers[i].flags = F_AVAIL; + count++; + } + } + + return count; +} + +int geode_mfgpt_toggle_event(int timer, int cmp, int event, int enable) +{ + u32 msr, mask, value, dummy; + int shift = (cmp == MFGPT_CMP1) ? 0 : 8; + + if (timer < 0 || timer >= MFGPT_MAX_TIMERS) + return -EIO; + + /* + * The register maps for these are described in sections 6.17.1.x of + * the AMD Geode CS5536 Companion Device Data Book. + */ + switch (event) { + case MFGPT_EVENT_RESET: + /* + * XXX: According to the docs, we cannot reset timers above + * 6; that is, resets for 7 and 8 will be ignored. Is this + * a problem? -dilinger + */ + msr = MFGPT_NR_MSR; + mask = 1 << (timer + 24); + break; + + case MFGPT_EVENT_NMI: + msr = MFGPT_NR_MSR; + mask = 1 << (timer + shift); + break; + + case MFGPT_EVENT_IRQ: + msr = MFGPT_IRQ_MSR; + mask = 1 << (timer + shift); + break; + + default: + return -EIO; + } + + rdmsr(msr, value, dummy); + + if (enable) + value |= mask; + else + value &= ~mask; + + wrmsr(msr, value, dummy); + return 0; +} + +int geode_mfgpt_set_irq(int timer, int cmp, int irq, int enable) +{ + u32 val, dummy; + int offset; + + if (timer < 0 || timer >= MFGPT_MAX_TIMERS) + return -EIO; + + if (geode_mfgpt_toggle_event(timer, cmp, MFGPT_EVENT_IRQ, enable)) + return -EIO; + + rdmsr(MSR_PIC_ZSEL_LOW, val, dummy); + + offset = (timer % 4) * 4; + + val &= ~((0xF << offset) | (0xF << (offset + 16))); + + if (enable) { + val |= (irq & 0x0F) << (offset); + val |= (irq & 0x0F) << (offset + 16); + } + + wrmsr(MSR_PIC_ZSEL_LOW, val, dummy); + return 0; +} + +static int mfgpt_get(int timer, struct module *owner) +{ + mfgpt_timers[timer].flags &= ~F_AVAIL; + mfgpt_timers[timer].owner = owner; + printk(KERN_INFO "geode-mfgpt: Registered timer %d\n", timer); + return timer; +} + +int geode_mfgpt_alloc_timer(int timer, int domain, struct module *owner) +{ + int i; + + if (!geode_get_dev_base(GEODE_DEV_MFGPT)) + return -ENODEV; + if (timer >= MFGPT_MAX_TIMERS) + return -EIO; + + if (timer < 0) { + /* Try to find an available timer */ + for (i = 0; i < MFGPT_MAX_TIMERS; i++) { + if (mfgpt_timers[i].flags & F_AVAIL) + return mfgpt_get(i, owner); + + if (i == 5 && domain == MFGPT_DOMAIN_WORKING) + break; + } + } else { + /* If they requested a specific timer, try to honor that */ + if (mfgpt_timers[timer].flags & F_AVAIL) + return mfgpt_get(timer, owner); + } + + /* No timers available - too bad */ + return -1; +} + diff --git a/include/asm-x86/geode.h b/include/asm-x86/geode.h index 6da4bbbea3dc..d94898831bac 100644 --- a/include/asm-x86/geode.h +++ b/include/asm-x86/geode.h @@ -156,4 +156,54 @@ static inline int is_geode(void) return (is_geode_gx() || is_geode_lx()); } +/* MFGPTs */ + +#define MFGPT_MAX_TIMERS 8 +#define MFGPT_TIMER_ANY -1 + +#define MFGPT_DOMAIN_WORKING 1 +#define MFGPT_DOMAIN_STANDBY 2 +#define MFGPT_DOMAIN_ANY (MFGPT_DOMAIN_WORKING | MFGPT_DOMAIN_STANDBY) + +#define MFGPT_CMP1 0 +#define MFGPT_CMP2 1 + +#define MFGPT_EVENT_IRQ 0 +#define MFGPT_EVENT_NMI 1 +#define MFGPT_EVENT_RESET 3 + +#define MFGPT_REG_CMP1 0 +#define MFGPT_REG_CMP2 2 +#define MFGPT_REG_COUNTER 4 +#define MFGPT_REG_SETUP 6 + +#define MFGPT_SETUP_CNTEN (1 << 15) +#define MFGPT_SETUP_CMP2 (1 << 14) +#define MFGPT_SETUP_CMP1 (1 << 13) +#define MFGPT_SETUP_SETUP (1 << 12) +#define MFGPT_SETUP_STOPEN (1 << 11) +#define MFGPT_SETUP_EXTEN (1 << 10) +#define MFGPT_SETUP_REVEN (1 << 5) +#define MFGPT_SETUP_CLKSEL (1 << 4) + +static inline void geode_mfgpt_write(int timer, u16 reg, u16 value) +{ + u32 base = geode_get_dev_base(GEODE_DEV_MFGPT); + outw(value, base + reg + (timer * 8)); +} + +static inline u16 geode_mfgpt_read(int timer, u16 reg) +{ + u32 base = geode_get_dev_base(GEODE_DEV_MFGPT); + return inw(base + reg + (timer * 8)); +} + +extern int __init geode_mfgpt_detect(void); +extern int geode_mfgpt_toggle_event(int timer, int cmp, int event, int enable); +extern int geode_mfgpt_set_irq(int timer, int cmp, int irq, int enable); +extern int geode_mfgpt_alloc_timer(int timer, int domain, struct module *owner); + +#define geode_mfgpt_setup_irq(t, c, i) geode_mfgpt_set_irq((t), (c), (i), 1) +#define geode_mfgpt_release_irq(t, c, i) geode_mfgpt_set_irq((t), (c), (i), 0) + #endif -- cgit v1.2.3 From 8f36881b3c972ebc019195692849f22488f9c0a3 Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: x86: Geode MFGPT clock event device support Add support for an MFGPT clock event device; this allows us to use MFGPTs as the basis for high-resolution timers. Signed-off-by: Jordan Crouse Signed-off-by: Andres Salomon Cc: Andi Kleen Cc: Alan Cox Cc: john stultz Signed-off-by: Andrew Morton Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven Signed-off-by: Thomas Gleixner --- Documentation/kernel-parameters.txt | 4 + arch/i386/Kconfig | 10 +++ arch/x86/kernel/mfgpt_32.c | 165 ++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 2128de6d80ea..f27cdd7125f2 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -1009,6 +1009,10 @@ and is between 256 and 4096 characters. It is defined in the file meye.*= [HW] Set MotionEye Camera parameters See Documentation/video4linux/meye.txt. + mfgpt_irq= [IA-32] Specify the IRQ to use for the + Multi-Function General Purpose Timers on AMD Geode + platforms. + mga= [HW,DRM] mousedev.tap_time= diff --git a/arch/i386/Kconfig b/arch/i386/Kconfig index 2d85e4b87307..6bbbc2755e44 100644 --- a/arch/i386/Kconfig +++ b/arch/i386/Kconfig @@ -1206,6 +1206,16 @@ config SCx200HR_TIMER processor goes idle (as is done by the scheduler). The other workaround is idle=poll boot option. +config GEODE_MFGPT_TIMER + bool "Geode Multi-Function General Purpose Timer (MFGPT) events" + depends on MGEODE_LX && GENERIC_TIME && GENERIC_CLOCKEVENTS + default y + help + This driver provides a clock event source based on the MFGPT + timer(s) in the CS5535 and CS5536 companion chip for the geode. + MFGPTs have a better resolution and max interval than the + generic PIT, and are suitable for use as high-res timers. + config K8_NB def_bool y depends on AGP_AMD64 diff --git a/arch/x86/kernel/mfgpt_32.c b/arch/x86/kernel/mfgpt_32.c index 3a63a2dd8d27..0ab680f2d9db 100644 --- a/arch/x86/kernel/mfgpt_32.c +++ b/arch/x86/kernel/mfgpt_32.c @@ -48,6 +48,12 @@ static struct mfgpt_timer_t { #define MFGPT_HZ (32000 / MFGPT_DIVISOR) #define MFGPT_PERIODIC (MFGPT_HZ / HZ) +#ifdef CONFIG_GEODE_MFGPT_TIMER +static int __init mfgpt_timer_setup(void); +#else +#define mfgpt_timer_setup() (0) +#endif + /* Allow for disabling of MFGPTs */ static int disable; static int __init mfgpt_disable(char *s) @@ -82,6 +88,9 @@ int __init geode_mfgpt_detect(void) } } + /* set up clock event device, if desired */ + i = mfgpt_timer_setup(); + return count; } @@ -195,3 +204,159 @@ int geode_mfgpt_alloc_timer(int timer, int domain, struct module *owner) return -1; } + +#ifdef CONFIG_GEODE_MFGPT_TIMER + +/* + * The MFPGT timers on the CS5536 provide us with suitable timers to use + * as clock event sources - not as good as a HPET or APIC, but certainly + * better then the PIT. This isn't a general purpose MFGPT driver, but + * a simplified one designed specifically to act as a clock event source. + * For full details about the MFGPT, please consult the CS5536 data sheet. + */ + +#include +#include + +static unsigned int mfgpt_tick_mode = CLOCK_EVT_MODE_SHUTDOWN; +static u16 mfgpt_event_clock; + +static int irq = 7; +static int __init mfgpt_setup(char *str) +{ + get_option(&str, &irq); + return 1; +} +__setup("mfgpt_irq=", mfgpt_setup); + +static inline void mfgpt_disable_timer(u16 clock) +{ + u16 val = geode_mfgpt_read(clock, MFGPT_REG_SETUP); + geode_mfgpt_write(clock, MFGPT_REG_SETUP, val & ~MFGPT_SETUP_CNTEN); +} + +static int mfgpt_next_event(unsigned long, struct clock_event_device *); +static void mfgpt_set_mode(enum clock_event_mode, struct clock_event_device *); + +static struct clock_event_device mfgpt_clockevent = { + .name = "mfgpt-timer", + .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, + .set_mode = mfgpt_set_mode, + .set_next_event = mfgpt_next_event, + .rating = 250, + .cpumask = CPU_MASK_ALL, + .shift = 32 +}; + +static inline void mfgpt_start_timer(u16 clock, u16 delta) +{ + geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_CMP2, (u16) delta); + geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_COUNTER, 0); + + geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP, + MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2); +} + +static void mfgpt_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + mfgpt_disable_timer(mfgpt_event_clock); + + if (mode == CLOCK_EVT_MODE_PERIODIC) + mfgpt_start_timer(mfgpt_event_clock, MFGPT_PERIODIC); + + mfgpt_tick_mode = mode; +} + +static int mfgpt_next_event(unsigned long delta, struct clock_event_device *evt) +{ + mfgpt_start_timer(mfgpt_event_clock, delta); + return 0; +} + +/* Assume (foolishly?), that this interrupt was due to our tick */ + +static irqreturn_t mfgpt_tick(int irq, void *dev_id) +{ + if (mfgpt_tick_mode == CLOCK_EVT_MODE_SHUTDOWN) + return IRQ_HANDLED; + + /* Turn off the clock */ + mfgpt_disable_timer(mfgpt_event_clock); + + /* Clear the counter */ + geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_COUNTER, 0); + + /* Restart the clock in periodic mode */ + + if (mfgpt_tick_mode == CLOCK_EVT_MODE_PERIODIC) { + geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP, + MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2); + } + + mfgpt_clockevent.event_handler(&mfgpt_clockevent); + return IRQ_HANDLED; +} + +static struct irqaction mfgptirq = { + .handler = mfgpt_tick, + .flags = IRQF_DISABLED | IRQF_NOBALANCING, + .mask = CPU_MASK_NONE, + .name = "mfgpt-timer" +}; + +static int __init mfgpt_timer_setup(void) +{ + int timer, ret; + u16 val; + + timer = geode_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_WORKING, + THIS_MODULE); + if (timer < 0) { + printk(KERN_ERR + "mfgpt-timer: Could not allocate a MFPGT timer\n"); + return -ENODEV; + } + + mfgpt_event_clock = timer; + /* Set the clock scale and enable the event mode for CMP2 */ + val = MFGPT_SCALE | (3 << 8); + + geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP, val); + + /* Set up the IRQ on the MFGPT side */ + if (geode_mfgpt_setup_irq(mfgpt_event_clock, MFGPT_CMP2, irq)) { + printk(KERN_ERR "mfgpt-timer: Could not set up IRQ %d\n", irq); + return -EIO; + } + + /* And register it with the kernel */ + ret = setup_irq(irq, &mfgptirq); + + if (ret) { + printk(KERN_ERR + "mfgpt-timer: Unable to set up the interrupt.\n"); + goto err; + } + + /* Set up the clock event */ + mfgpt_clockevent.mult = div_sc(MFGPT_HZ, NSEC_PER_SEC, 32); + mfgpt_clockevent.min_delta_ns = clockevent_delta2ns(0xF, + &mfgpt_clockevent); + mfgpt_clockevent.max_delta_ns = clockevent_delta2ns(0xFFFE, + &mfgpt_clockevent); + + printk(KERN_INFO + "mfgpt-timer: registering the MFGT timer as a clock event.\n"); + clockevents_register_device(&mfgpt_clockevent); + + return 0; + +err: + geode_mfgpt_release_irq(mfgpt_event_clock, MFGPT_CMP2, irq); + printk(KERN_ERR + "mfgpt-timer: Unable to set up the MFGPT clock source\n"); + return -EIO; +} + +#endif -- cgit v1.2.3 From 89b2bbd69b89b4c5efdc112a88d72419bdeb8dfc Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: x86_64: Select clocksource watchdog in Kconfig TSC must be verified by a continous and reliable clocksource to allow high resolution timers and or dynamic ticks. Signed-off-by: Thomas Gleixner Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86_64/Kconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/x86_64/Kconfig b/arch/x86_64/Kconfig index b1b98e614f7c..520f33b83bfc 100644 --- a/arch/x86_64/Kconfig +++ b/arch/x86_64/Kconfig @@ -36,6 +36,10 @@ config GENERIC_CMOS_UPDATE bool default y +config CLOCKSOURCE_WATCHDOG + bool + default y + config ZONE_DMA32 bool default y -- cgit v1.2.3 From d371698efd45c3664fd1726780c360f02e1f9580 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: x86_64: Consolidate tsc calibration Move the TSC calibration code to tsc.c. Reimplement it so the pm timer can be used as a reference as well. Signed-off-by: Thomas Gleixner Signed-off-by: Chris Wright Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86/kernel/hpet_64.c | 49 -------------------------- arch/x86/kernel/time_64.c | 33 ++--------------- arch/x86/kernel/tsc_64.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++ include/asm-x86/tsc.h | 4 +++ 4 files changed, 96 insertions(+), 80 deletions(-) diff --git a/arch/x86/kernel/hpet_64.c b/arch/x86/kernel/hpet_64.c index e2d1b912e154..1ebce2ec7eaf 100644 --- a/arch/x86/kernel/hpet_64.c +++ b/arch/x86/kernel/hpet_64.c @@ -184,55 +184,6 @@ int hpet_reenable(void) return hpet_timer_stop_set_go(hpet_tick); } -/* - * calibrate_tsc() calibrates the processor TSC in a very simple way, comparing - * it to the HPET timer of known frequency. - */ - -#define TICK_COUNT 100000000 -#define SMI_THRESHOLD 50000 -#define MAX_TRIES 5 - -/* - * Some platforms take periodic SMI interrupts with 5ms duration. Make sure none - * occurs between the reads of the hpet & TSC. - */ -static void __init read_hpet_tsc(int *hpet, int *tsc) -{ - int tsc1, tsc2, hpet1, i; - - for (i = 0; i < MAX_TRIES; i++) { - tsc1 = get_cycles_sync(); - hpet1 = hpet_readl(HPET_COUNTER); - tsc2 = get_cycles_sync(); - if ((tsc2 - tsc1) < SMI_THRESHOLD) - break; - } - *hpet = hpet1; - *tsc = tsc2; -} - -unsigned int __init hpet_calibrate_tsc(void) -{ - int tsc_start, hpet_start; - int tsc_now, hpet_now; - unsigned long flags; - - local_irq_save(flags); - - read_hpet_tsc(&hpet_start, &tsc_start); - - do { - local_irq_disable(); - read_hpet_tsc(&hpet_now, &tsc_now); - local_irq_restore(flags); - } while ((tsc_now - tsc_start) < TICK_COUNT && - (hpet_now - hpet_start) < TICK_COUNT); - - return (tsc_now - tsc_start) * 1000000000L - / ((hpet_now - hpet_start) * hpet_period / 1000); -} - #ifdef CONFIG_HPET_EMULATE_RTC /* HPET in LegacyReplacement Mode eats up RTC interrupt line. When, HPET * is enabled, we support RTC interrupt functionality in software. diff --git a/arch/x86/kernel/time_64.c b/arch/x86/kernel/time_64.c index 2db4d698a2d3..e97a3ee13551 100644 --- a/arch/x86/kernel/time_64.c +++ b/arch/x86/kernel/time_64.c @@ -292,35 +292,6 @@ static unsigned int __init tsc_calibrate_cpu_khz(void) return pmc_now * tsc_khz / (tsc_now - tsc_start); } -/* - * pit_calibrate_tsc() uses the speaker output (channel 2) of - * the PIT. This is better than using the timer interrupt output, - * because we can read the value of the speaker with just one inb(), - * where we need three i/o operations for the interrupt channel. - * We count how many ticks the TSC does in 50 ms. - */ - -static unsigned int __init pit_calibrate_tsc(void) -{ - unsigned long start, end; - unsigned long flags; - - spin_lock_irqsave(&i8253_lock, flags); - - outb((inb(0x61) & ~0x02) | 0x01, 0x61); - - outb(0xb0, 0x43); - outb((PIT_TICK_RATE / (1000 / 50)) & 0xff, 0x42); - outb((PIT_TICK_RATE / (1000 / 50)) >> 8, 0x42); - start = get_cycles_sync(); - while ((inb(0x61) & 0x20) == 0); - end = get_cycles_sync(); - - spin_unlock_irqrestore(&i8253_lock, flags); - - return (end - start) / 50; -} - #define PIT_MODE 0x43 #define PIT_CH0 0x40 @@ -376,14 +347,14 @@ void __init time_init(void) if (hpet_use_timer) { /* set tick_nsec to use the proper rate for HPET */ tick_nsec = TICK_NSEC_HPET; - tsc_khz = hpet_calibrate_tsc(); timename = "HPET"; } else { pit_init(); - tsc_khz = pit_calibrate_tsc(); timename = "PIT"; } + tsc_calibrate(); + cpu_khz = tsc_khz; if (cpu_has(&boot_cpu_data, X86_FEATURE_CONSTANT_TSC) && boot_cpu_data.x86_vendor == X86_VENDOR_AMD && diff --git a/arch/x86/kernel/tsc_64.c b/arch/x86/kernel/tsc_64.c index 2a59bde663f2..59baecd135ab 100644 --- a/arch/x86/kernel/tsc_64.c +++ b/arch/x86/kernel/tsc_64.c @@ -6,7 +6,9 @@ #include #include #include +#include +#include #include static int notsc __initdata = 0; @@ -118,6 +120,94 @@ core_initcall(cpufreq_tsc); #endif +#define MAX_RETRIES 5 +#define SMI_TRESHOLD 50000 + +/* + * Read TSC and the reference counters. Take care of SMI disturbance + */ +static unsigned long __init tsc_read_refs(unsigned long *pm, + unsigned long *hpet) +{ + unsigned long t1, t2; + int i; + + for (i = 0; i < MAX_RETRIES; i++) { + t1 = get_cycles_sync(); + if (hpet) + *hpet = hpet_readl(HPET_COUNTER) & 0xFFFFFFFF; + else + *pm = acpi_pm_read_early(); + t2 = get_cycles_sync(); + if ((t2 - t1) < SMI_TRESHOLD) + return t2; + } + return ULONG_MAX; +} + +/** + * tsc_calibrate - calibrate the tsc on boot + */ +void __init tsc_calibrate(void) +{ + unsigned long flags, tsc1, tsc2, tr1, tr2, pm1, pm2, hpet1, hpet2; + int hpet = is_hpet_enabled(); + + local_irq_save(flags); + + tsc1 = tsc_read_refs(&pm1, hpet ? &hpet1 : NULL); + + outb((inb(0x61) & ~0x02) | 0x01, 0x61); + + outb(0xb0, 0x43); + outb((CLOCK_TICK_RATE / (1000 / 50)) & 0xff, 0x42); + outb((CLOCK_TICK_RATE / (1000 / 50)) >> 8, 0x42); + tr1 = get_cycles_sync(); + while ((inb(0x61) & 0x20) == 0); + tr2 = get_cycles_sync(); + + tsc2 = tsc_read_refs(&pm2, hpet ? &hpet2 : NULL); + + local_irq_restore(flags); + + /* + * Preset the result with the raw and inaccurate PIT + * calibration value + */ + tsc_khz = (tr2 - tr1) / 50; + + /* hpet or pmtimer available ? */ + if (!hpet && !pm1 && !pm2) { + printk(KERN_INFO "TSC calibrated against PIT\n"); + return; + } + + /* Check, whether the sampling was disturbed by an SMI */ + if (tsc1 == ULONG_MAX || tsc2 == ULONG_MAX) { + printk(KERN_WARNING "TSC calibration disturbed by SMI, " + "using PIT calibration result\n"); + return; + } + + tsc2 = (tsc2 - tsc1) * 1000000L; + + if (hpet) { + printk(KERN_INFO "TSC calibrated against HPET\n"); + if (hpet2 < hpet1) + hpet2 += 0x100000000; + hpet2 -= hpet1; + tsc1 = (hpet2 * hpet_readl(HPET_PERIOD)) / 1000000; + } else { + printk(KERN_INFO "TSC calibrated against PM_TIMER\n"); + if (pm2 < pm1) + pm2 += ACPI_PM_OVRRUN; + pm2 -= pm1; + tsc1 = (pm2 * 1000000000) / PMTMR_TICKS_PER_SEC; + } + + tsc_khz = tsc2 / tsc1; +} + /* * Make an educated guess if the TSC is trustworthy and synchronized * over all CPUs. diff --git a/include/asm-x86/tsc.h b/include/asm-x86/tsc.h index a4d806610b7f..2002f8d6d20a 100644 --- a/include/asm-x86/tsc.h +++ b/include/asm-x86/tsc.h @@ -72,4 +72,8 @@ int check_tsc_unstable(void); extern void check_tsc_sync_source(int cpu); extern void check_tsc_sync_target(void); +#ifdef CONFIG_X86_64 +extern void tsc_calibrate(void); +#endif + #endif -- cgit v1.2.3 From 06a24dec10bc4014fc0974670627efed68f5da27 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: i386: prepare sharing the hpet code with x86_64 The hpet implementations of i386 and x8664 has been mostly the same before the clock events conversion of i386. The clock events conversion of i386 hpet is already done. So it makes sense to share the code for the x86_64 clock events conversion. Abstract out the mapping functions. Signed-off-by: Thomas Gleixner Signed-off-by: Chris Wright Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86/kernel/hpet_32.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/arch/x86/kernel/hpet_32.c b/arch/x86/kernel/hpet_32.c index 533d4932bc79..748abf0286d2 100644 --- a/arch/x86/kernel/hpet_32.c +++ b/arch/x86/kernel/hpet_32.c @@ -8,10 +8,9 @@ #include #include +#include #include -extern struct clock_event_device *global_clock_event; - #define HPET_MASK CLOCKSOURCE_MASK(32) #define HPET_SHIFT 22 @@ -22,7 +21,7 @@ extern struct clock_event_device *global_clock_event; * HPET address is set in acpi/boot.c, when an ACPI entry exists */ unsigned long hpet_address; -static void __iomem * hpet_virt_address; +static void __iomem *hpet_virt_address; static inline unsigned long hpet_readl(unsigned long a) { @@ -34,6 +33,17 @@ static inline void hpet_writel(unsigned long d, unsigned long a) writel(d, hpet_virt_address + a); } +static inline void hpet_set_mapping(void) +{ + hpet_virt_address = ioremap_nocache(hpet_address, HPET_MMAP_SIZE); +} + +static inline void hpet_clear_mapping(void) +{ + iounmap(hpet_virt_address); + hpet_virt_address = NULL; +} + /* * HPET command line enable / disable */ @@ -83,7 +93,7 @@ static void hpet_reserve_platform_timers(unsigned long id) memset(&hd, 0, sizeof (hd)); hd.hd_phys_address = hpet_address; - hd.hd_address = hpet_virt_address; + hd.hd_address = hpet; hd.hd_nirqs = nrtimers; hd.hd_flags = HPET_DATA_PLATFORM; hpet_reserve_timer(&hd, 0); @@ -238,7 +248,7 @@ int __init hpet_enable(void) if (!is_hpet_capable()) return 0; - hpet_virt_address = ioremap_nocache(hpet_address, HPET_MMAP_SIZE); + hpet_set_mapping(); /* * Read the period and check for a sane value: @@ -334,13 +344,11 @@ int __init hpet_enable(void) return 0; out_nohpet: - iounmap(hpet_virt_address); - hpet_virt_address = NULL; + hpet_clear_mapping(); boot_hpet_disable = 1; return 0; } - #ifdef CONFIG_HPET_EMULATE_RTC /* HPET in LegacyReplacement Mode eats up RTC interrupt line. When, HPET -- cgit v1.2.3 From 28769149c285e0a392d2e601ae0cc71ffc345f7d Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: i386: prepare sharing the hpet code with x86_64 Add the x8664 specific bits (mapping) to share the hpet code later. Move the reserve_platform_timer call to late init. This is necessary for x86_64, as hpet enable() is called before memory is setup. i386 calls it in late_time_init, but it does not hurt to do it later for both. Pull in the x8664 hpet disable command line option as well. Signed-off-by: Thomas Gleixner Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86/kernel/hpet_32.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/arch/x86/kernel/hpet_32.c b/arch/x86/kernel/hpet_32.c index 748abf0286d2..dbe0e1d44113 100644 --- a/arch/x86/kernel/hpet_32.c +++ b/arch/x86/kernel/hpet_32.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -7,6 +8,7 @@ #include #include +#include #include #include #include @@ -23,6 +25,10 @@ unsigned long hpet_address; static void __iomem *hpet_virt_address; +/* Temporary hack. Cleanup after x86_64 clock events conversion */ +#undef hpet_readl +#undef hpet_writel + static inline unsigned long hpet_readl(unsigned long a) { return readl(hpet_virt_address + a); @@ -33,6 +39,24 @@ static inline void hpet_writel(unsigned long d, unsigned long a) writel(d, hpet_virt_address + a); } +#ifdef CONFIG_X86_64 + +#include + +static inline void hpet_set_mapping(void) +{ + set_fixmap_nocache(FIX_HPET_BASE, hpet_address); + __set_fixmap(VSYSCALL_HPET, hpet_address, PAGE_KERNEL_VSYSCALL_NOCACHE); + hpet_virt_address = (void __iomem *)fix_to_virt(FIX_HPET_BASE); +} + +static inline void hpet_clear_mapping(void) +{ + hpet_virt_address = NULL; +} + +#else + static inline void hpet_set_mapping(void) { hpet_virt_address = ioremap_nocache(hpet_address, HPET_MMAP_SIZE); @@ -43,6 +67,7 @@ static inline void hpet_clear_mapping(void) iounmap(hpet_virt_address); hpet_virt_address = NULL; } +#endif /* * HPET command line enable / disable @@ -59,6 +84,13 @@ static int __init hpet_setup(char* str) } __setup("hpet=", hpet_setup); +static int __init disable_hpet(char *str) +{ + boot_hpet_disable = 1; + return 1; +} +__setup("nohpet", disable_hpet); + static inline int is_hpet_capable(void) { return (!boot_hpet_disable && hpet_address); @@ -225,6 +257,13 @@ static cycle_t read_hpet(void) return (cycle_t)hpet_readl(HPET_COUNTER); } +#ifdef CONFIG_X86_64 +static cycle_t __vsyscall_fn vread_hpet(void) +{ + return readl((const void __iomem *)fix_to_virt(VSYSCALL_HPET) + 0xf0); +} +#endif + static struct clocksource clocksource_hpet = { .name = "hpet", .rating = 250, @@ -233,6 +272,9 @@ static struct clocksource clocksource_hpet = { .shift = HPET_SHIFT, .flags = CLOCK_SOURCE_IS_CONTINUOUS, .resume = hpet_start_counter, +#ifdef CONFIG_X86_64 + .vread = vread_hpet, +#endif }; /* @@ -331,7 +373,6 @@ int __init hpet_enable(void) if (id & HPET_ID_LEGSUP) { hpet_enable_int(); - hpet_reserve_platform_timers(id); /* * Start hpet with the boot cpu mask and make it * global after the IO_APIC has been initialized. @@ -349,6 +390,22 @@ out_nohpet: return 0; } +/* + * Needs to be late, as the reserve_timer code calls kalloc ! + * + * Not a problem on i386 as hpet_enable is called from late_time_init, + * but on x86_64 it is necessary ! + */ +static __init int hpet_late_init(void) +{ + if (!is_hpet_capable()) + return -ENODEV; + + hpet_reserve_platform_timers(hpet_readl(HPET_ID)); + return 0; +} +fs_initcall(hpet_late_init); + #ifdef CONFIG_HPET_EMULATE_RTC /* HPET in LegacyReplacement Mode eats up RTC interrupt line. When, HPET -- cgit v1.2.3 From f5e0e93faf8421083853b2d7a217267f49e27cc3 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: i386: prepare sharing the PIT code PIT clock events work already and the PIT handling is the same for i386 and x86_64. x86_64 does not support PIT as a clock source, so disable the PIT clocksource for x86_64. Prepare i8253.h to be shared with x8664 Signed-off-by: Thomas Gleixner Signed-off-by: Chris Wright Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86/kernel/i8253_32.c | 4 +++- include/asm-x86/i8253_32.h | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/arch/x86/kernel/i8253_32.c b/arch/x86/kernel/i8253_32.c index 6d839f2f1b1a..ac15e4cbd9c1 100644 --- a/arch/x86/kernel/i8253_32.c +++ b/arch/x86/kernel/i8253_32.c @@ -13,7 +13,6 @@ #include #include #include -#include DEFINE_SPINLOCK(i8253_lock); EXPORT_SYMBOL(i8253_lock); @@ -120,6 +119,7 @@ void __init setup_pit_timer(void) global_clock_event = &pit_clockevent; } +#ifndef CONFIG_X86_64 /* * Since the PIT overflows every tick, its not very useful * to just read by itself. So use jiffies to emulate a free @@ -204,3 +204,5 @@ static int __init init_pit_clocksource(void) return clocksource_register(&clocksource_pit); } arch_initcall(init_pit_clocksource); + +#endif diff --git a/include/asm-x86/i8253_32.h b/include/asm-x86/i8253_32.h index 7577d058d86e..28cf67da59a3 100644 --- a/include/asm-x86/i8253_32.h +++ b/include/asm-x86/i8253_32.h @@ -1,8 +1,6 @@ #ifndef __ASM_I8253_H__ #define __ASM_I8253_H__ -#include - /* i8253A PIT registers */ #define PIT_MODE 0x43 #define PIT_CH0 0x40 @@ -10,8 +8,12 @@ extern spinlock_t i8253_lock; +#ifdef CONFIG_GENERIC_CLOCKEVENTS + extern struct clock_event_device *global_clock_event; extern void setup_pit_timer(void); +#endif + #endif /* __ASM_I8253_H__ */ -- cgit v1.2.3 From 0190dae54de62fbb9ced75d134015266987eb6b8 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: i386: prepare sharing the PIT code PIT clock events work already and the PIT handling is the same for i386 and x86_64. x86_64 does not support PIT as a clock source, so disable the PIT clocksource for x86_64. Use the i386 i8253.h include file for x86_64 as well to share the exports and the PIT constants. Signed-off-by: Thomas Gleixner Signed-off-by: Chris Wright Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86/kernel/time_64.c | 4 ---- include/asm-x86/i8253.h | 26 ++++++++++++++++++++++---- include/asm-x86/i8253_32.h | 19 ------------------- include/asm-x86/i8253_64.h | 6 ------ 4 files changed, 22 insertions(+), 33 deletions(-) delete mode 100644 include/asm-x86/i8253_32.h delete mode 100644 include/asm-x86/i8253_64.h diff --git a/arch/x86/kernel/time_64.c b/arch/x86/kernel/time_64.c index e97a3ee13551..d899216e01ca 100644 --- a/arch/x86/kernel/time_64.c +++ b/arch/x86/kernel/time_64.c @@ -32,7 +32,6 @@ #include /* for PM timer frequency */ #include #endif -#include #include #include #include @@ -292,9 +291,6 @@ static unsigned int __init tsc_calibrate_cpu_khz(void) return pmc_now * tsc_khz / (tsc_now - tsc_start); } -#define PIT_MODE 0x43 -#define PIT_CH0 0x40 - static void __pit_init(int val, u8 mode) { unsigned long flags; diff --git a/include/asm-x86/i8253.h b/include/asm-x86/i8253.h index b2a4f995a33f..7eff6fd27474 100644 --- a/include/asm-x86/i8253.h +++ b/include/asm-x86/i8253.h @@ -1,5 +1,23 @@ -#ifdef CONFIG_X86_32 -# include "i8253_32.h" -#else -# include "i8253_64.h" +#ifndef __ASM_I8253_H__ +#define __ASM_I8253_H__ + +#ifdef CONFIG_X86_64 +# include #endif + +/* i8253A PIT registers */ +#define PIT_MODE 0x43 +#define PIT_CH0 0x40 +#define PIT_CH2 0x42 + +extern spinlock_t i8253_lock; + +#ifdef CONFIG_GENERIC_CLOCKEVENTS + +extern struct clock_event_device *global_clock_event; + +extern void setup_pit_timer(void); + +#endif + +#endif /* __ASM_I8253_H__ */ diff --git a/include/asm-x86/i8253_32.h b/include/asm-x86/i8253_32.h deleted file mode 100644 index 28cf67da59a3..000000000000 --- a/include/asm-x86/i8253_32.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef __ASM_I8253_H__ -#define __ASM_I8253_H__ - -/* i8253A PIT registers */ -#define PIT_MODE 0x43 -#define PIT_CH0 0x40 -#define PIT_CH2 0x42 - -extern spinlock_t i8253_lock; - -#ifdef CONFIG_GENERIC_CLOCKEVENTS - -extern struct clock_event_device *global_clock_event; - -extern void setup_pit_timer(void); - -#endif - -#endif /* __ASM_I8253_H__ */ diff --git a/include/asm-x86/i8253_64.h b/include/asm-x86/i8253_64.h deleted file mode 100644 index 015d8df07690..000000000000 --- a/include/asm-x86/i8253_64.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef __ASM_I8253_H__ -#define __ASM_I8253_H__ - -extern spinlock_t i8253_lock; - -#endif /* __ASM_I8253_H__ */ -- cgit v1.2.3 From 7ffeeb1e03c4fc1c7e8434c5496018dd035f8924 Mon Sep 17 00:00:00 2001 From: Yinghai Lu Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: x86: remove never used apic_mapped [ tglx: arch/x86 adaptation ] Signed-off-by: Yinghai Lu Signed-off-by: Andi Kleen Cc: Christoph Lameter Cc: Len Brown Signed-off-by: Andrew Morton Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven Signed-off-by: Thomas Gleixner --- arch/x86/kernel/apic_64.c | 5 ++--- include/asm-x86/apic_64.h | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/arch/x86/kernel/apic_64.c b/arch/x86/kernel/apic_64.c index 925758dbca0c..48206bb8de1f 100644 --- a/arch/x86/kernel/apic_64.c +++ b/arch/x86/kernel/apic_64.c @@ -39,7 +39,6 @@ #include #include -int apic_mapped; int apic_verbosity; int apic_runs_main_timer; int apic_calibrate_pmtmr __initdata; @@ -706,8 +705,8 @@ void __init init_apic_mappings(void) apic_phys = mp_lapic_addr; set_fixmap_nocache(FIX_APIC_BASE, apic_phys); - apic_mapped = 1; - apic_printk(APIC_VERBOSE,"mapped APIC to %16lx (%16lx)\n", APIC_BASE, apic_phys); + apic_printk(APIC_VERBOSE, "mapped APIC to %16lx (%16lx)\n", + APIC_BASE, apic_phys); /* Put local APIC into the resource map. */ lapic_resource.start = apic_phys; diff --git a/include/asm-x86/apic_64.h b/include/asm-x86/apic_64.h index 85125ef3c414..81335e95365b 100644 --- a/include/asm-x86/apic_64.h +++ b/include/asm-x86/apic_64.h @@ -19,7 +19,6 @@ extern int apic_verbosity; extern int apic_runs_main_timer; extern int ioapic_force; -extern int apic_mapped; /* * Define the default level of output to be very little -- cgit v1.2.3 From 801740971af7c6256b7ad2472706d43ed008fd3d Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: x86_64: prepare apic code for clock events Change __setup_APIC_LVTT so it takes the arguments which are necessary for the later clock events switch. Signed-off-by: Thomas Gleixner Signed-off-by: Chris Wright Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86/kernel/apic_64.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/arch/x86/kernel/apic_64.c b/arch/x86/kernel/apic_64.c index 48206bb8de1f..ce2f8015d5ce 100644 --- a/arch/x86/kernel/apic_64.c +++ b/arch/x86/kernel/apic_64.c @@ -759,14 +759,14 @@ void __init init_apic_mappings(void) #define APIC_DIVISOR 16 -static void __setup_APIC_LVTT(unsigned int clocks) +static void __setup_APIC_LVTT(unsigned int clocks, int oneshot, int irqen) { unsigned int lvtt_value, tmp_value; - int cpu = smp_processor_id(); - - lvtt_value = APIC_LVT_TIMER_PERIODIC | LOCAL_TIMER_VECTOR; - if (cpu_isset(cpu, timer_interrupt_broadcast_ipi_mask)) + lvtt_value = LOCAL_TIMER_VECTOR; + if (!oneshot) + lvtt_value |= APIC_LVT_TIMER_PERIODIC; + if (!irqen) lvtt_value |= APIC_LVT_MASKED; apic_write(APIC_LVTT, lvtt_value); @@ -779,12 +779,14 @@ static void __setup_APIC_LVTT(unsigned int clocks) & ~(APIC_TDR_DIV_1 | APIC_TDR_DIV_TMBASE)) | APIC_TDR_DIV_16); - apic_write(APIC_TMICT, clocks/APIC_DIVISOR); + if (!oneshot) + apic_write(APIC_TMICT, clocks/APIC_DIVISOR); } static void setup_APIC_timer(unsigned int clocks) { unsigned long flags; + int irqen; local_irq_save(flags); @@ -805,7 +807,10 @@ static void setup_APIC_timer(unsigned int clocks) c2 |= inb_p(0x40) << 8; } while (c2 - c1 < 300); } - __setup_APIC_LVTT(clocks); + + irqen = ! cpu_isset(smp_processor_id(), + timer_interrupt_broadcast_ipi_mask); + __setup_APIC_LVTT(clocks, 0, irqen); /* Turn off PIT interrupt if we use APIC timer as main timer. Only works with the PM timer right now TBD fix it for HPET too. */ @@ -843,8 +848,10 @@ static int __init calibrate_APIC_clock(void) * Put whatever arbitrary (but long enough) timeout * value into the APIC clock, we just want to get the * counter running for calibration. + * + * No interrupt enable ! */ - __setup_APIC_LVTT(4000000000); + __setup_APIC_LVTT(4000000000, 0, 0); apic_start = apic_read(APIC_TMCCT); #ifdef CONFIG_X86_PM_TIMER -- cgit v1.2.3 From 500ff08b76878a45391fffc7a4c972883503050e Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: x86_64: remove pit synchronization The APIC timer setup code synchronizes the local APIC timer to the PIT/HPET. This is pointless as the PIT and the local APIC timer frequency are not correlated and the APIC timer calibration can never be accurate enough to avoid that the local APIC timer and the PIT/HPET drift apart. Simply remove it. Signed-off-by: Thomas Gleixner Signed-off-by: Chris Wright Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86/kernel/apic_64.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/arch/x86/kernel/apic_64.c b/arch/x86/kernel/apic_64.c index ce2f8015d5ce..a73b443a1236 100644 --- a/arch/x86/kernel/apic_64.c +++ b/arch/x86/kernel/apic_64.c @@ -790,24 +790,6 @@ static void setup_APIC_timer(unsigned int clocks) local_irq_save(flags); - /* wait for irq slice */ - if (hpet_address && hpet_use_timer) { - u32 trigger = hpet_readl(HPET_T0_CMP); - while (hpet_readl(HPET_T0_CMP) == trigger) - /* do nothing */ ; - } else { - int c1, c2; - outb_p(0x00, 0x43); - c2 = inb_p(0x40); - c2 |= inb_p(0x40) << 8; - do { - c1 = c2; - outb_p(0x00, 0x43); - c2 = inb_p(0x40); - c2 |= inb_p(0x40) << 8; - } while (c2 - c1 < 300); - } - irqen = ! cpu_isset(smp_processor_id(), timer_interrupt_broadcast_ipi_mask); __setup_APIC_LVTT(clocks, 0, irqen); -- cgit v1.2.3 From d03030e917047cf7f475e641cd2d6e83647392b0 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: x86_64: Move apic calibration code around Let the calibration code fill in calibration_result directly and move the variable on top of the file. Fixup a printk w/o log level while at it. Signed-off-by: Thomas Gleixner Signed-off-by: Chris Wright Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86/kernel/apic_64.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/arch/x86/kernel/apic_64.c b/arch/x86/kernel/apic_64.c index a73b443a1236..b08c08ed36a7 100644 --- a/arch/x86/kernel/apic_64.c +++ b/arch/x86/kernel/apic_64.c @@ -55,6 +55,8 @@ static struct resource lapic_resource = { .flags = IORESOURCE_MEM | IORESOURCE_BUSY, }; +static unsigned int calibration_result; + /* * cpu_mask that denotes the CPUs that needs timer interrupt coming in as * IPIs in place of local APIC timers @@ -821,7 +823,7 @@ static void setup_APIC_timer(unsigned int clocks) #define TICK_COUNT 100000000 -static int __init calibrate_APIC_clock(void) +static void __init calibrate_APIC_clock(void) { unsigned apic, apic_start; unsigned long tsc, tsc_start; @@ -855,17 +857,14 @@ static int __init calibrate_APIC_clock(void) result = (apic_start - apic) * 1000L * tsc_khz / (tsc - tsc_start); } - printk("result %d\n", result); - + printk(KERN_DEBUG "APIC timer calibration result %d\n", result); printk(KERN_INFO "Detected %d.%03d MHz APIC timer.\n", result / 1000 / 1000, result / 1000 % 1000); - return result * APIC_DIVISOR / HZ; + calibration_result = result * APIC_DIVISOR / HZ; } -static unsigned int calibration_result; - void __init setup_boot_APIC_clock (void) { if (disable_apic_timer) { @@ -878,7 +877,7 @@ void __init setup_boot_APIC_clock (void) local_irq_disable(); - calibration_result = calibrate_APIC_clock(); + calibrate_APIC_clock(); /* * Now set up the timer for real. */ @@ -985,8 +984,6 @@ void setup_APIC_extended_lvt(unsigned char lvt_off, unsigned char vector, apic_write(reg, v); } -#undef APIC_DIVISOR - /* * Local timer interrupt handler. It does both profiling and * process statistics/rescheduling. -- cgit v1.2.3 From b58eb00df7f7f80b7f456bf8fb740fddf14408ba Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:06 +0200 Subject: x86_64: Remove APIC_DIVISOR APIC_DIVISOR is rather useless. It makes the calibration result more accurate in the first place, but we discard this later when we write the value to the APIC timer by dividing the calibration value by APIC_DIVISOR. Signed-off-by: Thomas Gleixner Signed-off-by: Chris Wright Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86/kernel/apic_64.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/arch/x86/kernel/apic_64.c b/arch/x86/kernel/apic_64.c index b08c08ed36a7..f746f9dc8ced 100644 --- a/arch/x86/kernel/apic_64.c +++ b/arch/x86/kernel/apic_64.c @@ -759,8 +759,6 @@ void __init init_apic_mappings(void) * P5 APIC double write bug. */ -#define APIC_DIVISOR 16 - static void __setup_APIC_LVTT(unsigned int clocks, int oneshot, int irqen) { unsigned int lvtt_value, tmp_value; @@ -782,7 +780,7 @@ static void __setup_APIC_LVTT(unsigned int clocks, int oneshot, int irqen) | APIC_TDR_DIV_16); if (!oneshot) - apic_write(APIC_TMICT, clocks/APIC_DIVISOR); + apic_write(APIC_TMICT, clocks); } static void setup_APIC_timer(unsigned int clocks) @@ -835,7 +833,7 @@ static void __init calibrate_APIC_clock(void) * * No interrupt enable ! */ - __setup_APIC_LVTT(4000000000, 0, 0); + __setup_APIC_LVTT(250000000, 0, 0); apic_start = apic_read(APIC_TMCCT); #ifdef CONFIG_X86_PM_TIMER @@ -862,7 +860,7 @@ static void __init calibrate_APIC_clock(void) printk(KERN_INFO "Detected %d.%03d MHz APIC timer.\n", result / 1000 / 1000, result / 1000 % 1000); - calibration_result = result * APIC_DIVISOR / HZ; + calibration_result = result / HZ; } void __init setup_boot_APIC_clock (void) -- cgit v1.2.3 From abc63fcd3cc61876b2d6f5b0d08021bd1538385c Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:07 +0200 Subject: x86_64: apic change setup_APIC_timer calling convention setup_APIC_timer takes the file global calibration result as an argument. Remove it. Signed-off-by: Thomas Gleixner Signed-off-by: Chris Wright Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86/kernel/apic_64.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/x86/kernel/apic_64.c b/arch/x86/kernel/apic_64.c index f746f9dc8ced..f74b1c20e5c0 100644 --- a/arch/x86/kernel/apic_64.c +++ b/arch/x86/kernel/apic_64.c @@ -783,7 +783,7 @@ static void __setup_APIC_LVTT(unsigned int clocks, int oneshot, int irqen) apic_write(APIC_TMICT, clocks); } -static void setup_APIC_timer(unsigned int clocks) +static void setup_APIC_timer(void) { unsigned long flags; int irqen; @@ -792,7 +792,7 @@ static void setup_APIC_timer(unsigned int clocks) irqen = ! cpu_isset(smp_processor_id(), timer_interrupt_broadcast_ipi_mask); - __setup_APIC_LVTT(clocks, 0, irqen); + __setup_APIC_LVTT(calibration_result, 0, irqen); /* Turn off PIT interrupt if we use APIC timer as main timer. Only works with the PM timer right now TBD fix it for HPET too. */ @@ -879,7 +879,7 @@ void __init setup_boot_APIC_clock (void) /* * Now set up the timer for real. */ - setup_APIC_timer(calibration_result); + setup_APIC_timer(); local_irq_enable(); } @@ -887,7 +887,7 @@ void __init setup_boot_APIC_clock (void) void __cpuinit setup_secondary_APIC_clock(void) { local_irq_disable(); /* FIXME: Do we need this? --RR */ - setup_APIC_timer(calibration_result); + setup_APIC_timer(); local_irq_enable(); } -- cgit v1.2.3 From c4d58cbd158dc9b30c55c0e3881ae7c6b8843d5a Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:07 +0200 Subject: x86_64: remove nested irq disables setup_APIC_timer disables interrupts anyway. So no need to do the same in setup_boot_APIC_clock and setup_secondary_APIC_clock. Disable interrupts explicit in the calibration code. Signed-off-by: Thomas Gleixner Signed-off-by: Chris Wright Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86/kernel/apic_64.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/x86/kernel/apic_64.c b/arch/x86/kernel/apic_64.c index f74b1c20e5c0..40938ef99dc2 100644 --- a/arch/x86/kernel/apic_64.c +++ b/arch/x86/kernel/apic_64.c @@ -826,6 +826,9 @@ static void __init calibrate_APIC_clock(void) unsigned apic, apic_start; unsigned long tsc, tsc_start; int result; + + local_irq_disable(); + /* * Put whatever arbitrary (but long enough) timeout * value into the APIC clock, we just want to get the @@ -855,6 +858,9 @@ static void __init calibrate_APIC_clock(void) result = (apic_start - apic) * 1000L * tsc_khz / (tsc - tsc_start); } + + local_irq_enable(); + printk(KERN_DEBUG "APIC timer calibration result %d\n", result); printk(KERN_INFO "Detected %d.%03d MHz APIC timer.\n", @@ -873,22 +879,16 @@ void __init setup_boot_APIC_clock (void) printk(KERN_INFO "Using local APIC timer interrupts.\n"); using_apic_timer = 1; - local_irq_disable(); - calibrate_APIC_clock(); /* * Now set up the timer for real. */ setup_APIC_timer(); - - local_irq_enable(); } void __cpuinit setup_secondary_APIC_clock(void) { - local_irq_disable(); /* FIXME: Do we need this? --RR */ setup_APIC_timer(); - local_irq_enable(); } void disable_APIC_timer(void) -- cgit v1.2.3 From 02290683343391a50f45599710295dafa2ddd018 Mon Sep 17 00:00:00 2001 From: Chris Wright Date: Fri, 12 Oct 2007 23:04:07 +0200 Subject: x86_64: prepare idle loop for dynamic ticks Add tick_nohz_{stop,restart}_sched_tick to idle loop in prepartion for turning on dynticks. These are just noops until NO_HZ is enabled. Signed-off-by: Chris Wright Signed-off-by: Ingo Molnar Signed-off-by: Thomas Gleixner Signed-off-by: Arjan van de Ven --- arch/x86/kernel/process_64.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/x86/kernel/process_64.c b/arch/x86/kernel/process_64.c index 98956555450b..6f9dbbe65eef 100644 --- a/arch/x86/kernel/process_64.c +++ b/arch/x86/kernel/process_64.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -208,6 +209,8 @@ void cpu_idle (void) if (__get_cpu_var(cpu_idle_state)) __get_cpu_var(cpu_idle_state) = 0; + tick_nohz_stop_sched_tick(); + rmb(); idle = pm_idle; if (!idle) @@ -228,6 +231,7 @@ void cpu_idle (void) __exit_idle(); } + tick_nohz_restart_sched_tick(); preempt_enable_no_resched(); schedule(); preempt_disable(); -- cgit v1.2.3 From ba7eda4c60e1d070b2f6586d42719ec1d5302d3b Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 12 Oct 2007 23:04:07 +0200 Subject: x86_64: Add (not yet used) clock event functions Signed-off-by: Thomas Gleixner Signed-off-by: Chris Wright Signed-off-by: Ingo Molnar Signed-off-by: Arjan van de Ven --- arch/x86/kernel/apic_64.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++ arch/x86_64/Kconfig | 6 ++++ 2 files changed, 85 insertions(+) diff --git a/arch/x86/kernel/apic_64.c b/arch/x86/kernel/apic_64.c index 40938ef99dc2..2c2807abe1d4 100644 --- a/arch/x86/kernel/apic_64.c +++ b/arch/x86/kernel/apic_64.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -57,6 +58,77 @@ static struct resource lapic_resource = { static unsigned int calibration_result; +static int lapic_next_event(unsigned long delta, + struct clock_event_device *evt); +static void lapic_timer_setup(enum clock_event_mode mode, + struct clock_event_device *evt); + +static void lapic_timer_broadcast(cpumask_t mask); + +static void __setup_APIC_LVTT(unsigned int clocks, int oneshot, int irqen); + +static struct clock_event_device lapic_clockevent = { + .name = "lapic", + .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT + | CLOCK_EVT_FEAT_C3STOP | CLOCK_EVT_FEAT_DUMMY, + .shift = 32, + .set_mode = lapic_timer_setup, + .set_next_event = lapic_next_event, + .broadcast = lapic_timer_broadcast, + .rating = 100, + .irq = -1, +}; +static DEFINE_PER_CPU(struct clock_event_device, lapic_events); + +static int lapic_next_event(unsigned long delta, + struct clock_event_device *evt) +{ + apic_write(APIC_TMICT, delta); + return 0; +} + +static void lapic_timer_setup(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + unsigned long flags; + unsigned int v; + + /* Lapic used as dummy for broadcast ? */ + if (evt->features & CLOCK_EVT_FEAT_DUMMY) + return; + + local_irq_save(flags); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + case CLOCK_EVT_MODE_ONESHOT: + __setup_APIC_LVTT(calibration_result, + mode != CLOCK_EVT_MODE_PERIODIC, 1); + break; + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + v = apic_read(APIC_LVTT); + v |= (APIC_LVT_MASKED | LOCAL_TIMER_VECTOR); + apic_write(APIC_LVTT, v); + break; + case CLOCK_EVT_MODE_RESUME: + /* Nothing to do here */ + break; + } + + local_irq_restore(flags); +} + +/* + * Local APIC timer broadcast function + */ +static void lapic_timer_broadcast(cpumask_t mask) +{ +#ifdef CONFIG_SMP + send_IPI_mask(mask, LOCAL_TIMER_VECTOR); +#endif +} + /* * cpu_mask that denotes the CPUs that needs timer interrupt coming in as * IPIs in place of local APIC timers @@ -866,6 +938,13 @@ static void __init calibrate_APIC_clock(void) printk(KERN_INFO "Detected %d.%03d MHz APIC t