zephyr/drivers/timer/hpet.c

636 lines
19 KiB
C

/*
* Copyright (c) 2012-2015 Wind River Systems, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @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 <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 uint64_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 uint32_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 uint32_t *) \
(CONFIG_HPET_TIMER_BASE_ADDRESS + GENERAL_INT_STATUS_REG))
#define _HPET_MAIN_COUNTER_VALUE ((volatile uint64_t *) \
(CONFIG_HPET_TIMER_BASE_ADDRESS + MAIN_COUNTER_VALUE_REG))
#define _HPET_MAIN_COUNTER_LSW ((volatile uint32_t *) \
(CONFIG_HPET_TIMER_BASE_ADDRESS + MAIN_COUNTER_VALUE_REG))
#define _HPET_MAIN_COUNTER_MSW ((volatile uint32_t *) \
(CONFIG_HPET_TIMER_BASE_ADDRESS + MAIN_COUNTER_VALUE_REG + 0x4))
#define _HPET_TIMER0_CONFIG_CAPS ((volatile uint64_t *) \
(CONFIG_HPET_TIMER_BASE_ADDRESS + TIMER0_CONFIG_CAPS_REG))
#define _HPET_TIMER0_COMPARATOR ((volatile uint64_t *) \
(CONFIG_HPET_TIMER_BASE_ADDRESS + TIMER0_COMPARATOR_REG))
#define _HPET_TIMER0_FSB_INT_ROUTE ((volatile uint64_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 uint32_t main_count_first_irq_value;
static uint32_t main_count_expected_value;
extern uint32_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 int32_t _sys_idle_elapsed_ticks;
/* main counter units per system tick */
static uint32_t __noinit counter_load_value;
/* counter value for most recent tick */
static uint64_t counter_last_value;
/* # ticks timer is programmed for */
static int32_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 uint64_t _hpetMainCounterAtomic(void)
{
uint32_t highBits;
uint32_t lowBits;
do {
highBits = *_HPET_MAIN_COUNTER_MSW;
lowBits = *_HPET_MAIN_COUNTER_LSW;
} while (highBits != *_HPET_MAIN_COUNTER_MSW);
return ((uint64_t)highBits << 32) | lowBits;
}
#endif /* CONFIG_TICKLESS_IDLE */
/**
*
* @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
uint32_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 (stale_irq_check) {
stale_irq_check = 0;
if (_hpetMainCounterAtomic() < *_HPET_TIMER0_COMPARATOR) {
return; /* ignore "stale" interrupt */
}
}
/* configure timer to expire on next tick */
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 /* !CONFIG_TICKLESS_IDLE */
}
#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(int32_t ticks /* system ticks */
)
{
/*
* 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
: ~(uint64_t)0;
stale_irq_check = 1;
programmed_ticks = ticks;
}
/**
*
* @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)
{
uint64_t currTime = _hpetMainCounterAtomic();
int32_t elapsedTicks;
uint64_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 =
(int32_t)((currTime - counter_last_value) / counter_load_value);
counter_last_value += (uint64_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 /* 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)
{
uint64_t hpetClockPeriod;
uint64_t tickFempto;
#ifndef CONFIG_TICKLESS_IDLE
uint32_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 = (uint64_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 = (uint32_t)(tickFempto / hpetClockPeriod);
DBG("\n\nHPET: configuration: 0x%x, clock period: 0x%x (%d pico-s)\n",
(uint32_t)(*_HPET_GENERAL_CAPS),
(uint32_t)hpetClockPeriod, (uint32_t)hpetClockPeriod / 1000);
DBG("HPET: timer0: available interrupts mask 0x%x\n",
(uint32_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().
*/
uint32_t k_cycle_get_32(void)
{
return (uint32_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 */