You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
750 lines
21 KiB
750 lines
21 KiB
/* |
|
* Copyright (c) 2012-2015 Wind River Systems, Inc. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
/** |
|
* @file |
|
* @brief Intel HPET device driver |
|
* |
|
* This module implements a kernel device driver for the Intel High Precision |
|
* Event Timer (HPET) device, and provides the standard "system clock driver" |
|
* interfaces. |
|
* |
|
* The driver utilizes HPET timer0 to provide kernel ticks. |
|
* |
|
* \INTERNAL IMPLEMENTATION DETAILS |
|
* The HPET device driver makes no assumption about the initial state of the |
|
* HPET, and explicitly puts the device into a reset-like state. It also assumes |
|
* that the main up counter never wraps around to 0 during the lifetime of the |
|
* system. |
|
* |
|
* The platform can configure the HPET to use level rather than the default edge |
|
* sensitive interrupts by enabling the following configuration parameters: |
|
* CONFIG_HPET_TIMER_LEVEL_HIGH or CONFIG_HPET_TIMER_LEVEL_LOW |
|
* |
|
* When not configured to support tickless idle timer0 is programmed in periodic |
|
* mode so it automatically generates a single interrupt per kernel tick |
|
* interval. |
|
* |
|
* When configured to support tickless idle timer0 is programmed in one-shot |
|
* mode. When the CPU is not idling the timer interrupt handler sets the timer |
|
* to expire when the next kernel tick is due, waits for this to occur, and then |
|
* repeats this "ad infinitum". When the CPU begins idling the timer driver |
|
* reprograms the expiry time for the timer (thereby overriding the previously |
|
* scheduled timer interrupt) and waits for the timer to expire or for a |
|
* non-timer interrupt to occur. When the CPU ceases idling the driver |
|
* determines how many complete ticks have elapsed, reprograms the timer so that |
|
* it expires on the next tick, and announces the number of elapsed ticks (if |
|
* any) to the kernel. |
|
* |
|
*/ |
|
|
|
#include <kernel.h> |
|
#include <toolchain.h> |
|
#include <linker/sections.h> |
|
#include <sys_clock.h> |
|
#include <drivers/ioapic.h> |
|
#include <drivers/system_timer.h> |
|
#include <kernel_structs.h> |
|
|
|
#include <board.h> |
|
|
|
/* HPET register offsets */ |
|
|
|
#define GENERAL_CAPS_REG 0 /* 64-bit register */ |
|
#define GENERAL_CONFIG_REG 0x10 /* 64-bit register */ |
|
#define GENERAL_INT_STATUS_REG 0x20 /* 64-bit register */ |
|
#define MAIN_COUNTER_VALUE_REG 0xf0 /* 64-bit register */ |
|
|
|
#define TIMER0_CONFIG_CAPS_REG 0x100 /* 64-bit register */ |
|
#define TIMER0_COMPARATOR_REG 0x108 /* 64-bit register */ |
|
#define TIMER0_FSB_INT_ROUTE_REG 0x110 /* 64-bit register */ |
|
|
|
/* read the GENERAL_CAPS_REG to determine # of timers actually implemented */ |
|
|
|
#define TIMER1_CONFIG_CAP_REG 0x120 /* 64-bit register */ |
|
#define TIMER1_COMPARATOR_REG 0x128 /* 64-bit register */ |
|
#define TIMER1_FSB_INT_ROUTE_REG 0x130 /* 64-bit register */ |
|
|
|
#define TIMER2_CONFIG_CAP_REG 0x140 /* 64-bit register */ |
|
#define TIMER2_COMPARATOR_REG 0x148 /* 64-bit register */ |
|
#define TIMER2_FSB_INT_ROUTE_REG 0x150 /* 64-bit register */ |
|
|
|
/* convenience macros for accessing specific HPET registers */ |
|
|
|
#define _HPET_GENERAL_CAPS ((volatile u64_t *) \ |
|
(CONFIG_HPET_TIMER_BASE_ADDRESS + GENERAL_CAPS_REG)) |
|
|
|
/* |
|
* Although the general configuration register is 64-bits, only a 32-bit access |
|
* is performed since the most significant bits contain no useful information. |
|
*/ |
|
|
|
#define _HPET_GENERAL_CONFIG ((volatile u32_t *) \ |
|
(CONFIG_HPET_TIMER_BASE_ADDRESS + GENERAL_CONFIG_REG)) |
|
|
|
/* |
|
* Although the general interrupt status is 64-bits, only a 32-bit access |
|
* is performed since this driver only utilizes timer0 |
|
* (i.e. there is no need to determine the interrupt status of other timers). |
|
*/ |
|
|
|
#define _HPET_GENERAL_INT_STATUS ((volatile u32_t *) \ |
|
(CONFIG_HPET_TIMER_BASE_ADDRESS + GENERAL_INT_STATUS_REG)) |
|
|
|
#define _HPET_MAIN_COUNTER_VALUE ((volatile u64_t *) \ |
|
(CONFIG_HPET_TIMER_BASE_ADDRESS + MAIN_COUNTER_VALUE_REG)) |
|
#define _HPET_MAIN_COUNTER_LSW ((volatile u32_t *) \ |
|
(CONFIG_HPET_TIMER_BASE_ADDRESS + MAIN_COUNTER_VALUE_REG)) |
|
#define _HPET_MAIN_COUNTER_MSW ((volatile u32_t *) \ |
|
(CONFIG_HPET_TIMER_BASE_ADDRESS + MAIN_COUNTER_VALUE_REG + 0x4)) |
|
|
|
#define _HPET_TIMER0_CONFIG_CAPS ((volatile u64_t *) \ |
|
(CONFIG_HPET_TIMER_BASE_ADDRESS + TIMER0_CONFIG_CAPS_REG)) |
|
#define _HPET_TIMER0_COMPARATOR ((volatile u64_t *) \ |
|
(CONFIG_HPET_TIMER_BASE_ADDRESS + TIMER0_COMPARATOR_REG)) |
|
#define _HPET_TIMER0_FSB_INT_ROUTE ((volatile u64_t *) \ |
|
(CONFIG_HPET_TIMER_BASE_ADDRESS + TIMER0_FSB_INT_ROUTE_REG)) |
|
|
|
/* general capabilities register macros */ |
|
|
|
#define HPET_COUNTER_CLK_PERIOD(caps) (caps >> 32) |
|
#define HPET_NUM_TIMERS(caps) (((caps >> 8) & 0x1f) + 1) |
|
#define HPET_IS64BITS(caps) (caps & 0x1000) |
|
|
|
/* general configuration register macros */ |
|
|
|
#define HPET_ENABLE_CNF (1 << 0) |
|
#define HPET_LEGACY_RT_CNF (1 << 1) |
|
|
|
/* timer N configuration and capabilities register macros */ |
|
|
|
#define HPET_Tn_INT_ROUTE_CAP(caps) (caps > 32) |
|
#define HPET_Tn_FSB_INT_DEL_CAP(caps) (caps & (1 << 15)) |
|
#define HPET_Tn_FSB_EN_CNF (1 << 14) |
|
#define HPET_Tn_INT_ROUTE_CNF_MASK (0x1f << 9) |
|
#define HPET_Tn_INT_ROUTE_CNF_SHIFT 9 |
|
#define HPET_Tn_32MODE_CNF (1 << 8) |
|
#define HPET_Tn_VAL_SET_CNF (1 << 6) |
|
#define HPET_Tn_SIZE_CAP(caps) (caps & (1 << 5)) |
|
#define HPET_Tn_PER_INT_CAP(caps) (caps & (1 << 4)) |
|
#define HPET_Tn_TYPE_CNF (1 << 3) |
|
#define HPET_Tn_INT_ENB_CNF (1 << 2) |
|
#define HPET_Tn_INT_TYPE_CNF (1 << 1) |
|
|
|
/* |
|
* HPET comparator delay factor; this is the minimum value by which a new |
|
* timer expiration setting must exceed the current main counter value when |
|
* programming a timer in one-shot mode. Failure to allow for delays incurred |
|
* in programming a timer may result in the HPET not generating an interrupt |
|
* when the desired expiration time is reached. (See HPET documentation for |
|
* a more complete description of this issue.) |
|
* |
|
* The value is expressed in main counter units. For example, if the HPET main |
|
* counter increments at a rate of 19.2 MHz, this delay corresponds to 10 us |
|
* (or about 0.1% of a system clock tick, assuming a tick rate of 100 Hz). |
|
*/ |
|
|
|
#define HPET_COMP_DELAY 192 |
|
|
|
#if defined(CONFIG_HPET_TIMER_FALLING_EDGE) |
|
#define HPET_IOAPIC_FLAGS (IOAPIC_EDGE | IOAPIC_LOW) |
|
#elif defined(CONFIG_HPET_TIMER_RISING_EDGE) |
|
#define HPET_IOAPIC_FLAGS (IOAPIC_EDGE | IOAPIC_HIGH) |
|
#elif defined(CONFIG_HPET_TIMER_LEVEL_HIGH) |
|
#define HPET_IOAPIC_FLAGS (IOAPIC_LEVEL | IOAPIC_HIGH) |
|
#elif defined(CONFIG_HPET_TIMER_LEVEL_LOW) |
|
#define HPET_IOAPIC_FLAGS (IOAPIC_LEVEL | IOAPIC_LOW) |
|
#endif |
|
|
|
|
|
#ifdef CONFIG_INT_LATENCY_BENCHMARK |
|
static u32_t main_count_first_irq_value; |
|
static u32_t main_count_expected_value; |
|
extern u32_t _hw_irq_to_c_handler_latency; |
|
#endif |
|
|
|
#ifdef CONFIG_HPET_TIMER_DEBUG |
|
#include <misc/printk.h> |
|
#define DBG(...) printk(__VA_ARGS__) |
|
#else |
|
#define DBG(...) |
|
#endif |
|
|
|
#ifdef CONFIG_TICKLESS_IDLE |
|
|
|
/* additional globals, locals, and forward declarations */ |
|
|
|
extern s32_t _sys_idle_elapsed_ticks; |
|
|
|
/* main counter units per system tick */ |
|
static u32_t __noinit counter_load_value; |
|
/* counter value for most recent tick */ |
|
static u64_t counter_last_value; |
|
/* # ticks timer is programmed for */ |
|
static s32_t programmed_ticks = 1; |
|
/* is stale interrupt possible? */ |
|
static int stale_irq_check; |
|
|
|
/** |
|
* |
|
* @brief Safely read the main HPET up counter |
|
* |
|
* This routine simulates an atomic read of the 64-bit system clock on CPUs |
|
* that only support 32-bit memory accesses. The most significant word |
|
* of the counter is read twice to ensure it doesn't change while the least |
|
* significant word is being retrieved (as per HPET documentation). |
|
* |
|
* @return current 64-bit counter value |
|
*/ |
|
static u64_t _hpetMainCounterAtomic(void) |
|
{ |
|
u32_t highBits; |
|
u32_t lowBits; |
|
|
|
do { |
|
highBits = *_HPET_MAIN_COUNTER_MSW; |
|
lowBits = *_HPET_MAIN_COUNTER_LSW; |
|
} while (highBits != *_HPET_MAIN_COUNTER_MSW); |
|
|
|
return ((u64_t)highBits << 32) | lowBits; |
|
} |
|
|
|
#endif /* CONFIG_TICKLESS_IDLE */ |
|
|
|
#ifdef CONFIG_TICKLESS_KERNEL |
|
static inline void program_max_cycles(void) |
|
{ |
|
stale_irq_check = 1; |
|
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_VAL_SET_CNF; |
|
counter_last_value = *_HPET_TIMER0_COMPARATOR; |
|
*_HPET_TIMER0_COMPARATOR = counter_last_value - 1; |
|
} |
|
#endif |
|
|
|
/** |
|
* |
|
* @brief System clock tick handler |
|
* |
|
* This routine handles the system clock tick interrupt. A TICK_EVENT event |
|
* is pushed onto the kernel stack. |
|
* |
|
* @return N/A |
|
*/ |
|
void _timer_int_handler(void *unused) |
|
{ |
|
ARG_UNUSED(unused); |
|
|
|
#if defined(CONFIG_HPET_TIMER_LEVEL_LOW) || defined(CONFIG_HPET_TIMER_LEVEL_HIGH) |
|
/* Acknowledge interrupt */ |
|
*_HPET_GENERAL_INT_STATUS = 1; |
|
#endif |
|
|
|
#ifdef CONFIG_INT_LATENCY_BENCHMARK |
|
u32_t delta = *_HPET_MAIN_COUNTER_VALUE - main_count_expected_value; |
|
|
|
if (_hw_irq_to_c_handler_latency > delta) { |
|
/* keep the lowest value observed */ |
|
_hw_irq_to_c_handler_latency = delta; |
|
} |
|
/* compute the next expected main counter value */ |
|
main_count_expected_value += main_count_first_irq_value; |
|
#endif |
|
|
|
|
|
#ifndef CONFIG_TICKLESS_IDLE |
|
|
|
/* |
|
* one more tick has occurred -- don't need to do anything special since |
|
* timer is already configured to interrupt on the following tick |
|
*/ |
|
|
|
_sys_clock_tick_announce(); |
|
|
|
#else |
|
|
|
/* see if interrupt was triggered while timer was being reprogrammed */ |
|
|
|
#if defined(CONFIG_TICKLESS_KERNEL) |
|
/* If timer not programmed or already consumed exit */ |
|
if (!programmed_ticks) { |
|
if (_sys_clock_always_on) { |
|
_sys_clock_tick_count = _get_elapsed_clock_time(); |
|
program_max_cycles(); |
|
} |
|
return; |
|
} |
|
#endif |
|
if (stale_irq_check) { |
|
stale_irq_check = 0; |
|
if (_hpetMainCounterAtomic() < *_HPET_TIMER0_COMPARATOR) { |
|
return; /* ignore "stale" interrupt */ |
|
} |
|
} |
|
|
|
/* configure timer to expire on next tick for tick based kernel */ |
|
|
|
#if defined(CONFIG_TICKLESS_KERNEL) |
|
|
|
_sys_idle_elapsed_ticks = programmed_ticks; |
|
|
|
/* |
|
* Clear programmed ticks before announcing elapsed time so |
|
* that recursive calls to _update_elapsed_time() will not |
|
* announce already consumed elapsed time |
|
*/ |
|
programmed_ticks = 0; |
|
_sys_clock_tick_announce(); |
|
|
|
/* _sys_clock_tick_announce() could cause new programming */ |
|
if (!programmed_ticks && _sys_clock_always_on) { |
|
_sys_clock_tick_count = _get_elapsed_clock_time(); |
|
program_max_cycles(); |
|
} |
|
#else |
|
counter_last_value = *_HPET_TIMER0_COMPARATOR; |
|
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_VAL_SET_CNF; |
|
*_HPET_TIMER0_COMPARATOR = counter_last_value + counter_load_value; |
|
programmed_ticks = 1; |
|
_sys_clock_final_tick_announce(); |
|
#endif |
|
#endif /* !CONFIG_TICKLESS_IDLE */ |
|
|
|
} |
|
|
|
#ifdef CONFIG_TICKLESS_KERNEL |
|
u32_t _get_program_time(void) |
|
{ |
|
return programmed_ticks; |
|
} |
|
|
|
u32_t _get_remaining_program_time(void) |
|
{ |
|
if (programmed_ticks == 0) { |
|
return 0; |
|
} |
|
|
|
return (u32_t) ((s64_t) |
|
(*_HPET_TIMER0_COMPARATOR - |
|
_hpetMainCounterAtomic()) / counter_load_value); |
|
} |
|
|
|
u32_t _get_elapsed_program_time(void) |
|
{ |
|
if (programmed_ticks == 0) { |
|
return 0; |
|
} |
|
|
|
return (u32_t) (programmed_ticks - |
|
((s64_t)(*_HPET_TIMER0_COMPARATOR - |
|
_hpetMainCounterAtomic()) / counter_load_value)); |
|
} |
|
|
|
void _set_time(u32_t time) |
|
{ |
|
/* Assumes cycles in one time unit is greater than HPET_COMP_DELAY */ |
|
|
|
if (!time) { |
|
programmed_ticks = 0; |
|
return; |
|
} |
|
|
|
programmed_ticks = time; |
|
|
|
_sys_clock_tick_count = _get_elapsed_clock_time(); |
|
|
|
stale_irq_check = 1; |
|
|
|
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_VAL_SET_CNF; |
|
counter_last_value = _hpetMainCounterAtomic(); |
|
*_HPET_TIMER0_COMPARATOR = |
|
counter_last_value + time * counter_load_value; |
|
} |
|
|
|
void _enable_sys_clock(void) |
|
{ |
|
if (!programmed_ticks) { |
|
program_max_cycles(); |
|
} |
|
} |
|
|
|
u64_t _get_elapsed_clock_time(void) |
|
{ |
|
u64_t elapsed; |
|
|
|
elapsed = _sys_clock_tick_count; |
|
elapsed += ((s64_t)(_hpetMainCounterAtomic() - |
|
counter_last_value) / counter_load_value); |
|
|
|
return elapsed; |
|
} |
|
#endif |
|
|
|
#ifdef CONFIG_TICKLESS_IDLE |
|
|
|
/* |
|
* Ensure that _timer_idle_enter() is never asked to idle for fewer than 2 |
|
* ticks (since this might require the timer to be reprogrammed for a deadline |
|
* too close to the current time, resulting in a missed interrupt which would |
|
* permanently disable the tick timer) |
|
*/ |
|
|
|
#if (CONFIG_TICKLESS_IDLE_THRESH < 2) |
|
#error Tickless idle threshold is too small (must be at least 2) |
|
#endif |
|
|
|
/** |
|
* |
|
* @brief Place system timer into idle state |
|
* |
|
* Re-program the timer to enter into the idle state for the given number of |
|
* ticks (-1 means infinite number of ticks). |
|
* |
|
* @return N/A |
|
* |
|
* \INTERNAL IMPLEMENTATION DETAILS |
|
* Called while interrupts are locked. |
|
*/ |
|
|
|
void _timer_idle_enter(s32_t ticks /* system ticks */ |
|
) |
|
{ |
|
#ifdef CONFIG_TICKLESS_KERNEL |
|
if (ticks != K_FOREVER) { |
|
/* Need to reprogram only if current program is smaller */ |
|
if (ticks > programmed_ticks) { |
|
_set_time(ticks); |
|
} |
|
} else { |
|
programmed_ticks = 0; |
|
counter_last_value = *_HPET_TIMER0_COMPARATOR; |
|
*_HPET_GENERAL_CONFIG &= ~HPET_ENABLE_CNF; |
|
} |
|
#else |
|
/* |
|
* reprogram timer to expire at the desired time (which is guaranteed |
|
* to be at least one full tick from the current counter value) |
|
*/ |
|
|
|
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_VAL_SET_CNF; |
|
*_HPET_TIMER0_COMPARATOR = |
|
(ticks >= 0) ? counter_last_value + ticks * counter_load_value |
|
: ~(u64_t)0; |
|
programmed_ticks = ticks; |
|
#endif |
|
stale_irq_check = 1; |
|
} |
|
|
|
/** |
|
* |
|
* @brief Take system timer out of idle state |
|
* |
|
* Determine how long timer has been idling and reprogram it to interrupt at the |
|
* next tick. |
|
* |
|
* Note that in this routine, _sys_idle_elapsed_ticks must be zero because the |
|
* ticker has done its work and consumed all the ticks. This has to be true |
|
* otherwise idle mode wouldn't have been entered in the first place. |
|
* |
|
* @return N/A |
|
* |
|
*/ |
|
|
|
void _timer_idle_exit(void) |
|
{ |
|
#ifdef CONFIG_TICKLESS_KERNEL |
|
if (!programmed_ticks && _sys_clock_always_on) { |
|
program_max_cycles(); |
|
} |
|
#else |
|
u64_t currTime = _hpetMainCounterAtomic(); |
|
s32_t elapsedTicks; |
|
u64_t counterNextValue; |
|
|
|
/* see if idling ended because timer expired at the desired tick */ |
|
|
|
if (currTime >= *_HPET_TIMER0_COMPARATOR) { |
|
/* |
|
* update # of ticks since last tick event was announced, |
|
* so that this value is available to ISRs that run before the |
|
* timer interrupt handler runs (which is unlikely, but could |
|
* happen) |
|
*/ |
|
|
|
_sys_idle_elapsed_ticks = programmed_ticks - 1; |
|
|
|
/* |
|
* Announce elapsed ticks to the kernel. Note we are guaranteed |
|
* that the timer ISR will execute first before the tick event |
|
* is serviced. |
|
*/ |
|
_sys_clock_tick_announce(); |
|
|
|
/* timer interrupt handler reprograms the timer for the next |
|
* tick |
|
*/ |
|
|
|
return; |
|
} |
|
|
|
/* |
|
* idling ceased because a non-timer interrupt occurred |
|
* |
|
* compute how much idle time has elapsed and reprogram the timer |
|
* to expire on the next tick; if the next tick will happen so soon |
|
* that HPET might miss the interrupt declare that tick prematurely |
|
* and program the timer for the tick after that |
|
* |
|
* note: a premature tick declaration has no significant impact on |
|
* the kernel, which gets informed of the correct number of elapsed |
|
* ticks when the following tick finally occurs; however, any ISRs that |
|
* access _sys_idle_elapsed_ticks to determine the current time may be |
|
* misled during the (very brief) interval before the tick-in-progress |
|
* finishes and the following tick begins |
|
*/ |
|
|
|
elapsedTicks = |
|
(s32_t)((currTime - counter_last_value) / counter_load_value); |
|
counter_last_value += (u64_t)elapsedTicks * counter_load_value; |
|
|
|
counterNextValue = counter_last_value + counter_load_value; |
|
|
|
if ((counterNextValue - currTime) <= HPET_COMP_DELAY) { |
|
elapsedTicks++; |
|
counterNextValue += counter_load_value; |
|
counter_last_value += counter_load_value; |
|
} |
|
|
|
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_VAL_SET_CNF; |
|
*_HPET_TIMER0_COMPARATOR = counterNextValue; |
|
stale_irq_check = 1; |
|
|
|
/* |
|
* update # of ticks since last tick event was announced, |
|
* so that this value is available to ISRs that run before the timer |
|
* expires and the timer interrupt handler runs |
|
*/ |
|
|
|
_sys_idle_elapsed_ticks = elapsedTicks; |
|
|
|
if (_sys_idle_elapsed_ticks) { |
|
/* Announce elapsed ticks to the kernel */ |
|
_sys_clock_tick_announce(); |
|
} |
|
|
|
/* |
|
* Any elapsed ticks have been accounted for so simply set the |
|
* programmed ticks to 1 since the timer has been programmed to fire on |
|
* the next tick boundary. |
|
*/ |
|
|
|
programmed_ticks = 1; |
|
#endif |
|
} |
|
|
|
#endif /* CONFIG_TICKLESS_IDLE */ |
|
|
|
/** |
|
* |
|
* @brief Initialize and enable the system clock |
|
* |
|
* This routine is used to program the HPET to deliver interrupts at the |
|
* rate specified via the 'sys_clock_us_per_tick' global variable. |
|
* |
|
* @return 0 |
|
*/ |
|
|
|
int _sys_clock_driver_init(struct device *device) |
|
{ |
|
u64_t hpetClockPeriod; |
|
u64_t tickFempto; |
|
#ifndef CONFIG_TICKLESS_IDLE |
|
u32_t counter_load_value; |
|
#endif |
|
|
|
ARG_UNUSED(device); |
|
|
|
/* |
|
* Initial state of HPET is unknown, so put it back in a reset-like |
|
* state (i.e. set main counter to 0 and disable interrupts) |
|
*/ |
|
|
|
*_HPET_GENERAL_CONFIG &= ~HPET_ENABLE_CNF; |
|
*_HPET_MAIN_COUNTER_VALUE = 0; |
|
|
|
/* |
|
* Determine the comparator load value (based on a start count of 0) |
|
* to achieve the configured tick rate. |
|
*/ |
|
|
|
/* |
|
* Convert the 'sys_clock_us_per_tick' value |
|
* from microseconds to femptoseconds |
|
*/ |
|
|
|
tickFempto = (u64_t)sys_clock_us_per_tick * 1000000000; |
|
|
|
/* |
|
* This driver shall read the COUNTER_CLK_PERIOD value from the general |
|
* capabilities register rather than rely on a board.h provide macro |
|
* (or the global variable 'sys_clock_hw_cycles_per_tick') |
|
* to determine the frequency of clock applied to the HPET device. |
|
*/ |
|
|
|
/* read the clock period: units are fempto (10^-15) seconds */ |
|
|
|
hpetClockPeriod = HPET_COUNTER_CLK_PERIOD(*_HPET_GENERAL_CAPS); |
|
|
|
/* |
|
* compute value for the comparator register to achieve |
|
* 'sys_clock_us_per_tick' period |
|
*/ |
|
|
|
counter_load_value = (u32_t)(tickFempto / hpetClockPeriod); |
|
|
|
DBG("\n\nHPET: configuration: 0x%x, clock period: 0x%x (%d pico-s)\n", |
|
(u32_t)(*_HPET_GENERAL_CAPS), |
|
(u32_t)hpetClockPeriod, (u32_t)hpetClockPeriod / 1000); |
|
|
|
DBG("HPET: timer0: available interrupts mask 0x%x\n", |
|
(u32_t)(*_HPET_TIMER0_CONFIG_CAPS >> 32)); |
|
|
|
/* Initialize sys_clock_hw_cycles_per_tick/sec */ |
|
|
|
sys_clock_hw_cycles_per_tick = counter_load_value; |
|
sys_clock_hw_cycles_per_sec = sys_clock_hw_cycles_per_tick * |
|
sys_clock_ticks_per_sec; |
|
|
|
|
|
#ifdef CONFIG_INT_LATENCY_BENCHMARK |
|
main_count_first_irq_value = counter_load_value; |
|
main_count_expected_value = main_count_first_irq_value; |
|
#endif |
|
|
|
#ifdef CONFIG_HPET_TIMER_LEGACY_EMULATION |
|
/* |
|
* Configure HPET replace legacy 8254 timer. |
|
* In this case the timer0 interrupt is routed to IRQ2 |
|
* and legacy timer generates no interrupts |
|
*/ |
|
*_HPET_GENERAL_CONFIG |= HPET_LEGACY_RT_CNF; |
|
#endif /* CONFIG_HPET_TIMER_LEGACY_EMULATION */ |
|
|
|
#ifndef CONFIG_TICKLESS_IDLE |
|
/* |
|
* Set timer0 to periodic mode, ready to expire every tick |
|
* Setting 32-bit mode during the first load of the comparator |
|
* value is required to work around some hardware that otherwise |
|
* does not work properly. |
|
*/ |
|
|
|
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_TYPE_CNF | HPET_Tn_32MODE_CNF; |
|
#else |
|
/* set timer0 to one-shot mode, ready to expire on the first tick */ |
|
|
|
*_HPET_TIMER0_CONFIG_CAPS &= ~HPET_Tn_TYPE_CNF; |
|
#endif /* !CONFIG_TICKLESS_IDLE */ |
|
|
|
/* |
|
* Set the comparator register for timer0. The write to the comparator |
|
* register is allowed due to setting the HPET_Tn_VAL_SET_CNF bit. |
|
*/ |
|
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_VAL_SET_CNF; |
|
*_HPET_TIMER0_COMPARATOR = counter_load_value; |
|
/* |
|
* After the comparator is loaded, 32-bit mode can be safely |
|
* switched off |
|
*/ |
|
*_HPET_TIMER0_CONFIG_CAPS &= ~HPET_Tn_32MODE_CNF; |
|
|
|
/* |
|
* Route interrupts to the I/O APIC. If HPET_Tn_INT_TYPE_CNF is set this |
|
* means edge triggered interrupt mode is utilized; Otherwise level |
|
* sensitive interrupts are used. |
|
*/ |
|
|
|
/* |
|
* HPET timers IRQ field is 5 bits wide, and hence, can support only |
|
* IRQ's up to 31. Some platforms, however, use IRQs greater than 31. In |
|
* this case program leaves the IRQ fields blank. |
|
*/ |
|
|
|
*_HPET_TIMER0_CONFIG_CAPS = |
|
#if CONFIG_HPET_TIMER_IRQ < 32 |
|
(*_HPET_TIMER0_CONFIG_CAPS & ~HPET_Tn_INT_ROUTE_CNF_MASK) | |
|
(CONFIG_HPET_TIMER_IRQ << HPET_Tn_INT_ROUTE_CNF_SHIFT) |
|
#else |
|
(*_HPET_TIMER0_CONFIG_CAPS & ~HPET_Tn_INT_ROUTE_CNF_MASK) |
|
#endif |
|
|
|
#if defined(CONFIG_HPET_TIMER_LEVEL_LOW) || defined(CONFIG_HPET_TIMER_LEVEL_HIGH) |
|
| HPET_Tn_INT_TYPE_CNF; |
|
#else |
|
; |
|
#endif |
|
|
|
/* |
|
* Although the stub has already been "connected", the vector number |
|
* still has to be programmed into the interrupt controller. |
|
*/ |
|
IRQ_CONNECT(CONFIG_HPET_TIMER_IRQ, CONFIG_HPET_TIMER_IRQ_PRIORITY, |
|
_timer_int_handler, 0, HPET_IOAPIC_FLAGS); |
|
|
|
/* enable the IRQ in the interrupt controller */ |
|
|
|
irq_enable(CONFIG_HPET_TIMER_IRQ); |
|
|
|
/* enable the HPET generally, and timer0 specifically */ |
|
|
|
*_HPET_GENERAL_CONFIG |= HPET_ENABLE_CNF; |
|
*_HPET_TIMER0_CONFIG_CAPS |= HPET_Tn_INT_ENB_CNF; |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* |
|
* @brief Read the platform's timer hardware |
|
* |
|
* This routine returns the current time in terms of timer hardware clock |
|
* cycles. |
|
* |
|
* @return up counter of elapsed clock cycles |
|
* |
|
* \INTERNAL WARNING |
|
* If this routine is ever enhanced to return all 64 bits of the counter |
|
* it will need to call _hpetMainCounterAtomic(). |
|
*/ |
|
|
|
u32_t _timer_cycle_get_32(void) |
|
{ |
|
return (u32_t) *_HPET_MAIN_COUNTER_VALUE; |
|
} |
|
|
|
#ifdef CONFIG_SYSTEM_CLOCK_DISABLE |
|
|
|
/** |
|
* |
|
* @brief Stop announcing ticks into the kernel |
|
* |
|
* This routine disables the HPET so that timer interrupts are no |
|
* longer delivered. |
|
* |
|
* @return N/A |
|
*/ |
|
|
|
void sys_clock_disable(void) |
|
{ |
|
/* |
|
* disable the main HPET up counter and all timer interrupts; |
|
* there is no need to lock interrupts before doing this since |
|
* no other code alters the HPET's main configuration register |
|
* once the driver has been initialized |
|
*/ |
|
|
|
*_HPET_GENERAL_CONFIG &= ~HPET_ENABLE_CNF; |
|
} |
|
|
|
#endif /* CONFIG_SYSTEM_CLOCK_DISABLE */
|
|
|