timer: hpet: convert register access to functions
This converts register access from macro to functions. This allows SoCs to override these functions if needed. Signed-off-by: Daniel Leung <daniel.leung@intel.com>
This commit is contained in:
parent
b9cc043434
commit
f7d82da541
1 changed files with 231 additions and 42 deletions
|
@ -13,32 +13,206 @@
|
||||||
|
|
||||||
#include <dt-bindings/interrupt-controller/intel-ioapic.h>
|
#include <dt-bindings/interrupt-controller/intel-ioapic.h>
|
||||||
|
|
||||||
|
#include <soc.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* @brief HPET (High Precision Event Timers) driver
|
||||||
|
*
|
||||||
|
* HPET hardware contains a number of timers which can be used by
|
||||||
|
* the operating system, where the number of timers is implementation
|
||||||
|
* specific. The timers are implemented as a single up-counter with
|
||||||
|
* a set of comparators where the counter increases monotonically.
|
||||||
|
* Each timer has a match register and a comparator, and can generate
|
||||||
|
* an interrupt when the value in the match register equals the value of
|
||||||
|
* the free running counter. Some of these timers can be enabled to
|
||||||
|
* generate periodic interrupt.
|
||||||
|
*
|
||||||
|
* The HPET registers are usually mapped to memory space on x86
|
||||||
|
* hardware. If this is not the case, custom register access functions
|
||||||
|
* can be used by defining macro HPET_USE_CUSTOM_REG_ACCESS_FUNCS in
|
||||||
|
* soc.h, and implementing necessary initialization and access
|
||||||
|
* functions as described below.
|
||||||
|
*
|
||||||
|
* HPET_COUNTER_CLK_PERIOD can be overridden in soc.h if
|
||||||
|
* COUNTER_CLK_PERIOD is not in femtoseconds (1e-15 sec).
|
||||||
|
*
|
||||||
|
* HPET_CMP_MIN_DELAY can be overridden in soc.h to better match
|
||||||
|
* the frequency of the timers. Default is 1000 where the value
|
||||||
|
* written to the comparator must be 1000 larger than the current
|
||||||
|
* main counter value.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* General Configuration register */
|
||||||
|
#define GCONF_ENABLE BIT(0)
|
||||||
|
#define GCONF_LR BIT(1) /* legacy interrupt routing, */
|
||||||
|
/* disables PIT */
|
||||||
|
|
||||||
|
/* General Interrupt Status register */
|
||||||
|
#define TIMER0_INT_STS BIT(0)
|
||||||
|
|
||||||
|
/* Timer Configuration and Capabilities register */
|
||||||
|
#define TIMER_CONF_INT_LEVEL BIT(1)
|
||||||
|
#define TIMER_CONF_INT_ENABLE BIT(2)
|
||||||
|
#define TIMER_CONF_PERIODIC BIT(3)
|
||||||
|
#define TIMER_CONF_VAL_SET BIT(6)
|
||||||
|
#define TIMER_CONF_MODE32 BIT(8)
|
||||||
|
#define TIMER_CONF_FSB_EN BIT(14) /* FSB interrupt delivery */
|
||||||
|
/* enable */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following MMIO initialization and register access functions
|
||||||
|
* should work on generic x86 hardware. If the targeted SoC requires
|
||||||
|
* special handling of HPET registers, these functions will need to be
|
||||||
|
* implemented in the SoC layer by first defining the macro
|
||||||
|
* HPET_USE_CUSTOM_REG_ACCESS_FUNCS in soc.h to signal such intent.
|
||||||
|
*
|
||||||
|
* This is a list of functions which must be implemented in the SoC
|
||||||
|
* layer:
|
||||||
|
* void hpet_mmio_init(void)
|
||||||
|
* uint32_t hpet_counter_get(void)
|
||||||
|
* uint32_t hpet_counter_clk_period_get(void)
|
||||||
|
* uint32_t hpet_gconf_get(void)
|
||||||
|
* void hpet_gconf_set(uint32_t val)
|
||||||
|
* void hpet_int_sts_set(uint32_t val)
|
||||||
|
* uint32_t hpet_timer_conf_get(void)
|
||||||
|
* void hpet_timer_conf_set(uint32_t val)
|
||||||
|
* void hpet_timer_comparator_set(uint32_t val)
|
||||||
|
*/
|
||||||
|
#ifndef HPET_USE_CUSTOM_REG_ACCESS_FUNCS
|
||||||
DEVICE_MMIO_TOPLEVEL_STATIC(hpet_regs, DT_DRV_INST(0));
|
DEVICE_MMIO_TOPLEVEL_STATIC(hpet_regs, DT_DRV_INST(0));
|
||||||
|
|
||||||
#define HPET_REG32(off) (*(volatile uint32_t *)(long) \
|
#define HPET_REG_ADDR(off) \
|
||||||
(DEVICE_MMIO_TOPLEVEL_GET(hpet_regs) + (off)))
|
((mm_reg_t)(DEVICE_MMIO_TOPLEVEL_GET(hpet_regs) + (off)))
|
||||||
|
|
||||||
#define CLK_PERIOD_REG HPET_REG32(0x04) /* High dword of caps reg */
|
/* High dword of General Capabilities and ID register */
|
||||||
#define GENERAL_CONF_REG HPET_REG32(0x10)
|
#define CLK_PERIOD_REG HPET_REG_ADDR(0x04)
|
||||||
#define INTR_STATUS_REG HPET_REG32(0x20)
|
|
||||||
#define MAIN_COUNTER_REG HPET_REG32(0xf0)
|
|
||||||
#define TIMER0_CONF_REG HPET_REG32(0x100)
|
|
||||||
#define TIMER0_COMPARATOR_REG HPET_REG32(0x108)
|
|
||||||
|
|
||||||
/* GENERAL_CONF_REG bits */
|
/* General Configuration register */
|
||||||
#define GCONF_ENABLE BIT(0)
|
#define GCONF_REG HPET_REG_ADDR(0x10)
|
||||||
#define GCONF_LR BIT(1) /* legacy interrupt routing, disables PIT */
|
|
||||||
|
|
||||||
/* INTR_STATUS_REG bits */
|
/* General Interrupt Status register */
|
||||||
#define TIMER0_INT_STS BIT(0)
|
#define INTR_STATUS_REG HPET_REG_ADDR(0x20)
|
||||||
|
|
||||||
/* TIMERn_CONF_REG bits */
|
/* Main Counter Register */
|
||||||
#define TCONF_INT_LEVEL BIT(1)
|
#define MAIN_COUNTER_REG HPET_REG_ADDR(0xf0)
|
||||||
#define TCONF_INT_ENABLE BIT(2)
|
|
||||||
#define TCONF_PERIODIC BIT(3)
|
/* Timer 0 Configuration and Capabilities register */
|
||||||
#define TCONF_VAL_SET BIT(6)
|
#define TIMER0_CONF_REG HPET_REG_ADDR(0x100)
|
||||||
#define TCONF_MODE32 BIT(8)
|
|
||||||
#define TCONF_FSB_EN BIT(14) /* FSB interrupt delivery enable */
|
/* Timer 0 Comparator Register */
|
||||||
|
#define TIMER0_COMPARATOR_REG HPET_REG_ADDR(0x108)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Setup memory mappings needed to access HPET registers.
|
||||||
|
*
|
||||||
|
* This is called in sys_clock_driver_init() to setup any memory
|
||||||
|
* mappings needed to access HPET registers.
|
||||||
|
*/
|
||||||
|
static inline void hpet_mmio_init(void)
|
||||||
|
{
|
||||||
|
DEVICE_MMIO_TOPLEVEL_MAP(hpet_regs, K_MEM_CACHE_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the value of the main counter.
|
||||||
|
*
|
||||||
|
* @return Value of Main Counter
|
||||||
|
*/
|
||||||
|
static inline uint32_t hpet_counter_get(void)
|
||||||
|
{
|
||||||
|
return sys_read32(MAIN_COUNTER_REG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get COUNTER_CLK_PERIOD
|
||||||
|
*
|
||||||
|
* Read and return the COUNTER_CLK_PERIOD, which is the high
|
||||||
|
* 32-bit of the General Capabilities and ID Register. This can
|
||||||
|
* be used to calculate the frequency of the main counter.
|
||||||
|
*
|
||||||
|
* Usually the period is in femtoseconds. If this is not
|
||||||
|
* the case, define HPET_COUNTER_CLK_PERIOD in soc.h so
|
||||||
|
* it can be used to calculate frequency.
|
||||||
|
*
|
||||||
|
* @return COUNTER_CLK_PERIOD
|
||||||
|
*/
|
||||||
|
static inline uint32_t hpet_counter_clk_period_get(void)
|
||||||
|
{
|
||||||
|
return sys_read32(CLK_PERIOD_REG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the value of the General Configuration Register
|
||||||
|
*
|
||||||
|
* @return Value of the General Configuration Register
|
||||||
|
*/
|
||||||
|
static inline uint32_t hpet_gconf_get(void)
|
||||||
|
{
|
||||||
|
return sys_read32(GCONF_REG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write to General Configuration Register
|
||||||
|
*
|
||||||
|
* @param val Value to be written to the register
|
||||||
|
*/
|
||||||
|
static inline void hpet_gconf_set(uint32_t val)
|
||||||
|
{
|
||||||
|
sys_write32(val, GCONF_REG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write to General Interrupt Status Register
|
||||||
|
*
|
||||||
|
* This is used to acknowledge and clear interrupt bits.
|
||||||
|
*
|
||||||
|
* @param val Value to be written to the register
|
||||||
|
*/
|
||||||
|
static inline void hpet_int_sts_set(uint32_t val)
|
||||||
|
{
|
||||||
|
sys_write32(val, INTR_STATUS_REG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the value of the Timer Configuration Register
|
||||||
|
*
|
||||||
|
* This reads and returns the value of the Timer Configuration
|
||||||
|
* Register of Timer #0.
|
||||||
|
*
|
||||||
|
* @return Value of the Timer Configuration Register
|
||||||
|
*/
|
||||||
|
static inline uint32_t hpet_timer_conf_get(void)
|
||||||
|
{
|
||||||
|
return sys_read32(TIMER0_CONF_REG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write to the Timer Configuration Register
|
||||||
|
*
|
||||||
|
* This writes the specified value to the Timer Configuration
|
||||||
|
* Register of Timer #0.
|
||||||
|
*
|
||||||
|
* @param val Value to be written to the register
|
||||||
|
*/
|
||||||
|
static inline void hpet_timer_conf_set(uint32_t val)
|
||||||
|
{
|
||||||
|
sys_write32(val, TIMER0_CONF_REG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write to the Timer Comparator Value Register
|
||||||
|
*
|
||||||
|
* This writes the specified value to the Timer Comparator
|
||||||
|
* Value Register of Timer #0.
|
||||||
|
*
|
||||||
|
* @param val Value to be written to the register
|
||||||
|
*/
|
||||||
|
static inline void hpet_timer_comparator_set(uint32_t val)
|
||||||
|
{
|
||||||
|
sys_write32(val, TIMER0_COMPARATOR_REG);
|
||||||
|
}
|
||||||
|
#endif /* HPET_USE_CUSTOM_REG_ACCESS_FUNCS */
|
||||||
|
|
||||||
#ifndef HPET_COUNTER_CLK_PERIOD
|
#ifndef HPET_COUNTER_CLK_PERIOD
|
||||||
/* COUNTER_CLK_PERIOD (CLK_PERIOD_REG) is in femtoseconds (1e-15 sec) */
|
/* COUNTER_CLK_PERIOD (CLK_PERIOD_REG) is in femtoseconds (1e-15 sec) */
|
||||||
|
@ -73,7 +247,7 @@ static void hpet_isr(const void *arg)
|
||||||
|
|
||||||
k_spinlock_key_t key = k_spin_lock(&lock);
|
k_spinlock_key_t key = k_spin_lock(&lock);
|
||||||
|
|
||||||
uint32_t now = MAIN_COUNTER_REG;
|
uint32_t now = hpet_counter_get();
|
||||||
|
|
||||||
#if ((DT_INST_IRQ(0, sense) & IRQ_TYPE_LEVEL) == IRQ_TYPE_LEVEL)
|
#if ((DT_INST_IRQ(0, sense) & IRQ_TYPE_LEVEL) == IRQ_TYPE_LEVEL)
|
||||||
/*
|
/*
|
||||||
|
@ -81,7 +255,7 @@ static void hpet_isr(const void *arg)
|
||||||
* When edge trigger is selected, spec says only 0 can
|
* When edge trigger is selected, spec says only 0 can
|
||||||
* be written.
|
* be written.
|
||||||
*/
|
*/
|
||||||
INTR_STATUS_REG = TIMER0_INT_STS;
|
hpet_int_sts_set(TIMER0_INT_STS);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_SMP) &&
|
if (IS_ENABLED(CONFIG_SMP) &&
|
||||||
|
@ -107,7 +281,7 @@ static void hpet_isr(const void *arg)
|
||||||
if ((int32_t)(next - now) < HPET_CMP_MIN_DELAY) {
|
if ((int32_t)(next - now) < HPET_CMP_MIN_DELAY) {
|
||||||
next += cyc_per_tick;
|
next += cyc_per_tick;
|
||||||
}
|
}
|
||||||
TIMER0_COMPARATOR_REG = next;
|
hpet_timer_comparator_set(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
k_spin_unlock(&lock, key);
|
k_spin_unlock(&lock, key);
|
||||||
|
@ -117,28 +291,30 @@ static void hpet_isr(const void *arg)
|
||||||
__pinned_func
|
__pinned_func
|
||||||
static void set_timer0_irq(unsigned int irq)
|
static void set_timer0_irq(unsigned int irq)
|
||||||
{
|
{
|
||||||
|
uint32_t val = hpet_timer_conf_get();
|
||||||
|
|
||||||
/* 5-bit IRQ field starting at bit 9 */
|
/* 5-bit IRQ field starting at bit 9 */
|
||||||
uint32_t val = (TIMER0_CONF_REG & ~(0x1f << 9)) | ((irq & 0x1f) << 9);
|
val = (val & ~(0x1f << 9)) | ((irq & 0x1f) << 9);
|
||||||
|
|
||||||
#if ((DT_INST_IRQ(0, sense) & IRQ_TYPE_LEVEL) == IRQ_TYPE_LEVEL)
|
#if ((DT_INST_IRQ(0, sense) & IRQ_TYPE_LEVEL) == IRQ_TYPE_LEVEL)
|
||||||
/* Level trigger */
|
/* Level trigger */
|
||||||
val |= TCONF_INT_LEVEL;
|
val |= TIMER_CONF_INT_LEVEL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TIMER0_CONF_REG = val;
|
hpet_timer_conf_set(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
__boot_func
|
__boot_func
|
||||||
int sys_clock_driver_init(const struct device *dev)
|
int sys_clock_driver_init(const struct device *dev)
|
||||||
{
|
{
|
||||||
extern int z_clock_hw_cycles_per_sec;
|
extern int z_clock_hw_cycles_per_sec;
|
||||||
uint32_t hz;
|
uint32_t hz, reg;
|
||||||
|
|
||||||
ARG_UNUSED(dev);
|
ARG_UNUSED(dev);
|
||||||
ARG_UNUSED(hz);
|
ARG_UNUSED(hz);
|
||||||
ARG_UNUSED(z_clock_hw_cycles_per_sec);
|
ARG_UNUSED(z_clock_hw_cycles_per_sec);
|
||||||
|
|
||||||
DEVICE_MMIO_TOPLEVEL_MAP(hpet_regs, K_MEM_CACHE_NONE);
|
hpet_mmio_init();
|
||||||
|
|
||||||
IRQ_CONNECT(DT_INST_IRQN(0),
|
IRQ_CONNECT(DT_INST_IRQN(0),
|
||||||
DT_INST_IRQ(0, priority),
|
DT_INST_IRQ(0, priority),
|
||||||
|
@ -147,28 +323,33 @@ int sys_clock_driver_init(const struct device *dev)
|
||||||
irq_enable(DT_INST_IRQN(0));
|
irq_enable(DT_INST_IRQN(0));
|
||||||
|
|
||||||
#ifdef CONFIG_TIMER_READS_ITS_FREQUENCY_AT_RUNTIME
|
#ifdef CONFIG_TIMER_READS_ITS_FREQUENCY_AT_RUNTIME
|
||||||
hz = (uint32_t)(HPET_COUNTER_CLK_PERIOD / CLK_PERIOD_REG);
|
hz = (uint32_t)(HPET_COUNTER_CLK_PERIOD / hpet_counter_clk_period_get());
|
||||||
z_clock_hw_cycles_per_sec = hz;
|
z_clock_hw_cycles_per_sec = hz;
|
||||||
cyc_per_tick = hz / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
|
cyc_per_tick = hz / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
|
||||||
|
|
||||||
max_ticks = (MAX_TICKS - cyc_per_tick) / cyc_per_tick;
|
max_ticks = (MAX_TICKS - cyc_per_tick) / cyc_per_tick;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
last_count = hpet_counter_get();
|
||||||
|
|
||||||
/* Note: we set the legacy routing bit, because otherwise
|
/* Note: we set the legacy routing bit, because otherwise
|
||||||
* nothing in Zephyr disables the PIT which then fires
|
* nothing in Zephyr disables the PIT which then fires
|
||||||
* interrupts into the same IRQ. But that means we're then
|
* interrupts into the same IRQ. But that means we're then
|
||||||
* forced to use IRQ2 contra the way the kconfig IRQ selection
|
* forced to use IRQ2 contra the way the kconfig IRQ selection
|
||||||
* is supposed to work. Should fix this.
|
* is supposed to work. Should fix this.
|
||||||
*/
|
*/
|
||||||
GENERAL_CONF_REG |= GCONF_LR | GCONF_ENABLE;
|
reg = hpet_gconf_get();
|
||||||
TIMER0_CONF_REG &= ~TCONF_PERIODIC;
|
reg |= GCONF_LR | GCONF_ENABLE;
|
||||||
TIMER0_CONF_REG &= ~TCONF_FSB_EN;
|
hpet_gconf_set(reg);
|
||||||
TIMER0_CONF_REG |= TCONF_MODE32;
|
|
||||||
|
|
||||||
last_count = MAIN_COUNTER_REG;
|
reg = hpet_timer_conf_get();
|
||||||
|
reg &= ~TIMER_CONF_PERIODIC;
|
||||||
|
reg &= ~TIMER_CONF_FSB_EN;
|
||||||
|
reg |= TIMER_CONF_MODE32;
|
||||||
|
reg |= TIMER_CONF_INT_ENABLE;
|
||||||
|
hpet_timer_conf_set(reg);
|
||||||
|
|
||||||
TIMER0_CONF_REG |= TCONF_INT_ENABLE;
|
hpet_timer_comparator_set(last_count + cyc_per_tick);
|
||||||
TIMER0_COMPARATOR_REG = MAIN_COUNTER_REG + cyc_per_tick;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -188,8 +369,12 @@ void sys_clock_set_timeout(int32_t ticks, bool idle)
|
||||||
ARG_UNUSED(idle);
|
ARG_UNUSED(idle);
|
||||||
|
|
||||||
#if defined(CONFIG_TICKLESS_KERNEL)
|
#if defined(CONFIG_TICKLESS_KERNEL)
|
||||||
|
uint32_t reg;
|
||||||
|
|
||||||
if (ticks == K_TICKS_FOREVER && idle) {
|
if (ticks == K_TICKS_FOREVER && idle) {
|
||||||
GENERAL_CONF_REG &= ~GCONF_ENABLE;
|
reg = hpet_gconf_get();
|
||||||
|
reg &= ~GCONF_ENABLE;
|
||||||
|
hpet_gconf_set(reg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +382,7 @@ void sys_clock_set_timeout(int32_t ticks, bool idle)
|
||||||
ticks = CLAMP(ticks - 1, 0, (int32_t)max_ticks);
|
ticks = CLAMP(ticks - 1, 0, (int32_t)max_ticks);
|
||||||
|
|
||||||
k_spinlock_key_t key = k_spin_lock(&lock);
|
k_spinlock_key_t key = k_spin_lock(&lock);
|
||||||
uint32_t now = MAIN_COUNTER_REG, cyc, adj;
|
uint32_t now = hpet_counter_get(), cyc, adj;
|
||||||
uint32_t max_cyc = max_ticks * cyc_per_tick;
|
uint32_t max_cyc = max_ticks * cyc_per_tick;
|
||||||
|
|
||||||
/* Round up to next tick boundary. */
|
/* Round up to next tick boundary. */
|
||||||
|
@ -215,7 +400,7 @@ void sys_clock_set_timeout(int32_t ticks, bool idle)
|
||||||
cyc += cyc_per_tick;
|
cyc += cyc_per_tick;
|
||||||
}
|
}
|
||||||
|
|
||||||
TIMER0_COMPARATOR_REG = cyc;
|
hpet_timer_comparator_set(cyc);
|
||||||
k_spin_unlock(&lock, key);
|
k_spin_unlock(&lock, key);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -228,7 +413,7 @@ uint32_t sys_clock_elapsed(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
k_spinlock_key_t key = k_spin_lock(&lock);
|
k_spinlock_key_t key = k_spin_lock(&lock);
|
||||||
uint32_t ret = (MAIN_COUNTER_REG - last_count) / cyc_per_tick;
|
uint32_t ret = (hpet_counter_get() - last_count) / cyc_per_tick;
|
||||||
|
|
||||||
k_spin_unlock(&lock, key);
|
k_spin_unlock(&lock, key);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -237,11 +422,15 @@ uint32_t sys_clock_elapsed(void)
|
||||||
__pinned_func
|
__pinned_func
|
||||||
uint32_t sys_clock_cycle_get_32(void)
|
uint32_t sys_clock_cycle_get_32(void)
|
||||||
{
|
{
|
||||||
return MAIN_COUNTER_REG;
|
return hpet_counter_get();
|
||||||
}
|
}
|
||||||
|
|
||||||
__pinned_func
|
__pinned_func
|
||||||
void sys_clock_idle_exit(void)
|
void sys_clock_idle_exit(void)
|
||||||
{
|
{
|
||||||
GENERAL_CONF_REG |= GCONF_ENABLE;
|
uint32_t reg;
|
||||||
|
|
||||||
|
reg = hpet_gconf_get();
|
||||||
|
reg |= GCONF_ENABLE;
|
||||||
|
hpet_gconf_set(reg);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue