Commit 1b168acb authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] HPET 4/6: Core

From: "Pallipadi, Venkatesh" <venkatesh.pallipadi@intel.com>

All the changes required to use HPET in place of PIT as the kernel base-timer
at IRQ 0.
parent 669692e4
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
#include <asm/pgalloc.h> #include <asm/pgalloc.h>
#include <asm/desc.h> #include <asm/desc.h>
#include <asm/arch_hooks.h> #include <asm/arch_hooks.h>
#include <asm/hpet.h>
#include <mach_apic.h> #include <mach_apic.h>
...@@ -779,7 +780,8 @@ static unsigned int __init get_8254_timer_count(void) ...@@ -779,7 +780,8 @@ static unsigned int __init get_8254_timer_count(void)
return count; return count;
} }
void __init wait_8254_wraparound(void) /* next tick in 8254 can be caught by catching timer wraparound */
static void __init wait_8254_wraparound(void)
{ {
unsigned int curr_count, prev_count=~0; unsigned int curr_count, prev_count=~0;
int delta; int delta;
...@@ -800,6 +802,12 @@ void __init wait_8254_wraparound(void) ...@@ -800,6 +802,12 @@ void __init wait_8254_wraparound(void)
} while (delta < 300); } while (delta < 300);
} }
/*
* Default initialization for 8254 timers. If we use other timers like HPET,
* we override this later
*/
void (*wait_timer_tick)(void) = wait_8254_wraparound;
/* /*
* This function sets up the local APIC timer, with a timeout of * This function sets up the local APIC timer, with a timeout of
* 'clocks' APIC bus clock. During calibration we actually call * 'clocks' APIC bus clock. During calibration we actually call
...@@ -841,7 +849,7 @@ static void setup_APIC_timer(unsigned int clocks) ...@@ -841,7 +849,7 @@ static void setup_APIC_timer(unsigned int clocks)
/* /*
* Wait for IRQ0's slice: * Wait for IRQ0's slice:
*/ */
wait_8254_wraparound(); wait_timer_tick();
__setup_APIC_LVTT(clocks); __setup_APIC_LVTT(clocks);
...@@ -884,7 +892,7 @@ int __init calibrate_APIC_clock(void) ...@@ -884,7 +892,7 @@ int __init calibrate_APIC_clock(void)
* (the current tick might have been already half done) * (the current tick might have been already half done)
*/ */
wait_8254_wraparound(); wait_timer_tick();
/* /*
* We wrapped around just now. Let's start: * We wrapped around just now. Let's start:
...@@ -897,7 +905,7 @@ int __init calibrate_APIC_clock(void) ...@@ -897,7 +905,7 @@ int __init calibrate_APIC_clock(void)
* Let's wait LOOPS wraprounds: * Let's wait LOOPS wraprounds:
*/ */
for (i = 0; i < LOOPS; i++) for (i = 0; i < LOOPS; i++)
wait_8254_wraparound(); wait_timer_tick();
tt2 = apic_read(APIC_TMCCT); tt2 = apic_read(APIC_TMCCT);
if (cpu_has_tsc) if (cpu_has_tsc)
......
...@@ -60,6 +60,8 @@ ...@@ -60,6 +60,8 @@
#include <linux/timex.h> #include <linux/timex.h>
#include <linux/config.h> #include <linux/config.h>
#include <asm/hpet.h>
#include <asm/arch_hooks.h> #include <asm/arch_hooks.h>
#include "io_ports.h" #include "io_ports.h"
...@@ -291,8 +293,38 @@ static int time_init_device(void) ...@@ -291,8 +293,38 @@ static int time_init_device(void)
device_initcall(time_init_device); device_initcall(time_init_device);
#ifdef CONFIG_HPET_TIMER
extern void (*late_time_init)(void);
/* Duplicate of time_init() below, with hpet_enable part added */
void __init hpet_time_init(void)
{
xtime.tv_sec = get_cmos_time();
wall_to_monotonic.tv_sec = -xtime.tv_sec;
xtime.tv_nsec = (INITIAL_JIFFIES % HZ) * (NSEC_PER_SEC / HZ);
wall_to_monotonic.tv_nsec = -xtime.tv_nsec;
if (hpet_enable() >= 0) {
printk("Using HPET for base-timer\n");
}
cur_timer = select_timer();
time_init_hook();
}
#endif
void __init time_init(void) void __init time_init(void)
{ {
#ifdef CONFIG_HPET_TIMER
if (is_hpet_capable()) {
/*
* HPET initialization needs to do memory-mapped io. So, let
* us do a late initialization after mem_init().
*/
late_time_init = hpet_time_init;
return;
}
#endif
xtime.tv_sec = get_cmos_time(); xtime.tv_sec = get_cmos_time();
wall_to_monotonic.tv_sec = -xtime.tv_sec; wall_to_monotonic.tv_sec = -xtime.tv_sec;
xtime.tv_nsec = (INITIAL_JIFFIES % HZ) * (NSEC_PER_SEC / HZ); xtime.tv_nsec = (INITIAL_JIFFIES % HZ) * (NSEC_PER_SEC / HZ);
......
/*
* linux/arch/i386/kernel/time_hpet.c
* This code largely copied from arch/x86_64/kernel/time.c
* See that file for credits.
*
* 2003-06-30 Venkatesh Pallipadi - Additional changes for HPET support
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/param.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/smp.h>
#include <asm/timer.h>
#include <asm/fixmap.h>
#include <asm/apic.h>
#include <linux/timex.h>
#include <linux/config.h>
#include <asm/hpet.h>
unsigned long hpet_period; /* fsecs / HPET clock */
unsigned long hpet_tick; /* hpet clks count per tick */
unsigned long hpet_address; /* hpet memory map physical address */
static int use_hpet; /* can be used for runtime check of hpet */
static int boot_hpet_disable; /* boottime override for HPET timer */
static unsigned long hpet_virt_address; /* hpet kernel virtual address */
#define FSEC_TO_USEC (1000000000UL)
int hpet_readl(unsigned long a)
{
return readl(hpet_virt_address + a);
}
void hpet_writel(unsigned long d, unsigned long a)
{
writel(d, hpet_virt_address + a);
}
#ifdef CONFIG_X86_LOCAL_APIC
/*
* HPET counters dont wrap around on every tick. They just change the
* comparator value and continue. Next tick can be caught by checking
* for a change in the comparator value. Used in apic.c.
*/
void __init wait_hpet_tick(void)
{
unsigned int start_cmp_val, end_cmp_val;
start_cmp_val = hpet_readl(HPET_T0_CMP);
do {
end_cmp_val = hpet_readl(HPET_T0_CMP);
} while (start_cmp_val == end_cmp_val);
}
#endif
/*
* Check whether HPET was found by ACPI boot parse. If yes setup HPET
* counter 0 for kernel base timer.
*/
int __init hpet_enable(void)
{
unsigned int cfg, id;
unsigned long tick_fsec_low, tick_fsec_high; /* tick in femto sec */
unsigned long hpet_tick_rem;
if (boot_hpet_disable)
return -1;
if (!hpet_address) {
return -1;
}
hpet_virt_address = (unsigned long) ioremap_nocache(hpet_address,
HPET_MMAP_SIZE);
/*
* Read the period, compute tick and quotient.
*/
id = hpet_readl(HPET_ID);
/*
* We are checking for value '1' or more in number field.
* So, we are OK with HPET_EMULATE_RTC part too, where we need
* to have atleast 2 timers.
*/
if (!(id & HPET_ID_NUMBER) ||
!(id & HPET_ID_LEGSUP))
return -1;
if (((id & HPET_ID_VENDOR) >> HPET_ID_VENDOR_SHIFT) !=
HPET_ID_VENDOR_8086)
return -1;
hpet_period = hpet_readl(HPET_PERIOD);
if ((hpet_period < HPET_MIN_PERIOD) || (hpet_period > HPET_MAX_PERIOD))
return -1;
/*
* 64 bit math
* First changing tick into fsec
* Then 64 bit div to find number of hpet clk per tick
*/
ASM_MUL64_REG(tick_fsec_low, tick_fsec_high,
KERNEL_TICK_USEC, FSEC_TO_USEC);
ASM_DIV64_REG(hpet_tick, hpet_tick_rem,
hpet_period, tick_fsec_low, tick_fsec_high);
if (hpet_tick_rem > (hpet_period >> 1))
hpet_tick++; /* rounding the result */
/*
* Stop the timers and reset the main counter.
*/
cfg = hpet_readl(HPET_CFG);
cfg &= ~HPET_CFG_ENABLE;
hpet_writel(cfg, HPET_CFG);
hpet_writel(0, HPET_COUNTER);
hpet_writel(0, HPET_COUNTER + 4);
/*
* Set up timer 0, as periodic with first interrupt to happen at
* hpet_tick, and period also hpet_tick.
*/
cfg = hpet_readl(HPET_T0_CFG);
cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC |
HPET_TN_SETVAL | HPET_TN_32BIT;
hpet_writel(cfg, HPET_T0_CFG);
hpet_writel(hpet_tick, HPET_T0_CMP);
/*
* Go!
*/
cfg = hpet_readl(HPET_CFG);
cfg |= HPET_CFG_ENABLE | HPET_CFG_LEGACY;
hpet_writel(cfg, HPET_CFG);
use_hpet = 1;
#ifdef CONFIG_X86_LOCAL_APIC
wait_timer_tick = wait_hpet_tick;
#endif
return 0;
}
int is_hpet_enabled(void)
{
return use_hpet;
}
int is_hpet_capable(void)
{
if (!boot_hpet_disable && hpet_address)
return 1;
return 0;
}
static int __init hpet_setup(char* str)
{
if (str) {
if (!strncmp("disable", str, 7))
boot_hpet_disable = 1;
}
return 1;
}
__setup("hpet=", hpet_setup);
...@@ -64,6 +64,8 @@ static inline void ack_APIC_irq(void) ...@@ -64,6 +64,8 @@ static inline void ack_APIC_irq(void)
apic_write_around(APIC_EOI, 0); apic_write_around(APIC_EOI, 0);
} }
extern void (*wait_timer_tick)(void);
extern int get_maxlvt(void); extern int get_maxlvt(void);
extern void clear_local_APIC(void); extern void clear_local_APIC(void);
extern void connect_bsp_APIC (void); extern void connect_bsp_APIC (void);
......
#ifndef _I386_HPET_H
#define _I386_HPET_H
#ifdef CONFIG_HPET_TIMER
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/param.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/time.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/smp.h>
#include <asm/io.h>
#include <asm/smp.h>
#include <asm/irq.h>
#include <asm/msr.h>
#include <asm/delay.h>
#include <asm/mpspec.h>
#include <asm/uaccess.h>
#include <asm/processor.h>
#include <linux/timex.h>
#include <linux/config.h>
#include <asm/fixmap.h>
/*
* Documentation on HPET can be found at:
* http://www.intel.com/ial/home/sp/pcmmspec.htm
* ftp://download.intel.com/ial/home/sp/mmts098.pdf
*/
#define HPET_MMAP_SIZE 1024
#define HPET_ID 0x000
#define HPET_PERIOD 0x004
#define HPET_CFG 0x010
#define HPET_STATUS 0x020
#define HPET_COUNTER 0x0f0
#define HPET_T0_CFG 0x100
#define HPET_T0_CMP 0x108
#define HPET_T0_ROUTE 0x110
#define HPET_T1_CFG 0x120
#define HPET_T1_CMP 0x128
#define HPET_T1_ROUTE 0x130
#define HPET_T2_CFG 0x140
#define HPET_T2_CMP 0x148
#define HPET_T2_ROUTE 0x150
#define HPET_ID_VENDOR 0xffff0000
#define HPET_ID_LEGSUP 0x00008000
#define HPET_ID_NUMBER 0x00001f00
#define HPET_ID_REV 0x000000ff
#define HPET_ID_VENDOR_SHIFT 16
#define HPET_ID_VENDOR_8086 0x8086
#define HPET_CFG_ENABLE 0x001
#define HPET_CFG_LEGACY 0x002
#define HPET_TN_ENABLE 0x004
#define HPET_TN_PERIODIC 0x008
#define HPET_TN_PERIODIC_CAP 0x010
#define HPET_TN_SETVAL 0x040
#define HPET_TN_32BIT 0x100
/* Use our own asm for 64 bit multiply/divide */
#define ASM_MUL64_REG(eax_out,edx_out,reg_in,eax_in) \
__asm__ __volatile__("mull %2" \
:"=a" (eax_out), "=d" (edx_out) \
:"r" (reg_in), "0" (eax_in))
#define ASM_DIV64_REG(eax_out,edx_out,reg_in,eax_in,edx_in) \
__asm__ __volatile__("divl %2" \
:"=a" (eax_out), "=d" (edx_out) \
:"r" (reg_in), "0" (eax_in), "1" (edx_in))
#define KERNEL_TICK_USEC (1000000UL/HZ) /* tick value in microsec */
/* Max HPET Period is 10^8 femto sec as in HPET spec */
#define HPET_MAX_PERIOD (100000000UL)
/*
* Min HPET period is 10^5 femto sec just for safety. If it is less than this,
* then 32 bit HPET counter wrapsaround in less than 0.5 sec.
*/
#define HPET_MIN_PERIOD (100000UL)
extern unsigned long hpet_period; /* fsecs / HPET clock */
extern unsigned long hpet_tick; /* hpet clks count per tick */
extern unsigned long hpet_address; /* hpet memory map physical address */
extern int hpet_rtc_timer_init(void);
extern int hpet_enable(void);
extern int is_hpet_enabled(void);
extern int is_hpet_capable(void);
extern int hpet_readl(unsigned long a);
extern void hpet_writel(unsigned long d, unsigned long a);
#endif /* CONFIG_HPET_TIMER */
#endif /* _I386_HPET_H */
...@@ -24,6 +24,10 @@ outb_p((addr),RTC_PORT(0)); \ ...@@ -24,6 +24,10 @@ outb_p((addr),RTC_PORT(0)); \
outb_p((val),RTC_PORT(1)); \ outb_p((val),RTC_PORT(1)); \
}) })
#ifdef CONFIG_HPET_TIMER
#define RTC_IRQ 0
#else
#define RTC_IRQ 8 #define RTC_IRQ 8
#endif
#endif /* _ASM_MC146818RTC_H */ #endif /* _ASM_MC146818RTC_H */
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