diff --git a/drivers/timer/hpet.c b/drivers/timer/hpet.c index bcca3f07c70..6cce7c3c85a 100644 --- a/drivers/timer/hpet.c +++ b/drivers/timer/hpet.c @@ -13,32 +13,206 @@ #include +#include + +/** + * @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)); -#define HPET_REG32(off) (*(volatile uint32_t *)(long) \ - (DEVICE_MMIO_TOPLEVEL_GET(hpet_regs) + (off))) +#define HPET_REG_ADDR(off) \ + ((mm_reg_t)(DEVICE_MMIO_TOPLEVEL_GET(hpet_regs) + (off))) -#define CLK_PERIOD_REG HPET_REG32(0x04) /* High dword of caps reg */ -#define GENERAL_CONF_REG HPET_REG32(0x10) -#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) +/* High dword of General Capabilities and ID register */ +#define CLK_PERIOD_REG HPET_REG_ADDR(0x04) -/* GENERAL_CONF_REG bits */ -#define GCONF_ENABLE BIT(0) -#define GCONF_LR BIT(1) /* legacy interrupt routing, disables PIT */ +/* General Configuration register */ +#define GCONF_REG HPET_REG_ADDR(0x10) -/* INTR_STATUS_REG bits */ -#define TIMER0_INT_STS BIT(0) +/* General Interrupt Status register */ +#define INTR_STATUS_REG HPET_REG_ADDR(0x20) -/* TIMERn_CONF_REG bits */ -#define TCONF_INT_LEVEL BIT(1) -#define TCONF_INT_ENABLE BIT(2) -#define TCONF_PERIODIC BIT(3) -#define TCONF_VAL_SET BIT(6) -#define TCONF_MODE32 BIT(8) -#define TCONF_FSB_EN BIT(14) /* FSB interrupt delivery enable */ +/* Main Counter Register */ +#define MAIN_COUNTER_REG HPET_REG_ADDR(0xf0) + +/* Timer 0 Configuration and Capabilities register */ +#define TIMER0_CONF_REG HPET_REG_ADDR(0x100) + +/* 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 /* 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); - uint32_t now = MAIN_COUNTER_REG; + uint32_t now = hpet_counter_get(); #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 * be written. */ - INTR_STATUS_REG = TIMER0_INT_STS; + hpet_int_sts_set(TIMER0_INT_STS); #endif 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) { next += cyc_per_tick; } - TIMER0_COMPARATOR_REG = next; + hpet_timer_comparator_set(next); } k_spin_unlock(&lock, key); @@ -117,28 +291,30 @@ static void hpet_isr(const void *arg) __pinned_func static void set_timer0_irq(unsigned int irq) { + uint32_t val = hpet_timer_conf_get(); + /* 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) /* Level trigger */ - val |= TCONF_INT_LEVEL; + val |= TIMER_CONF_INT_LEVEL; #endif - TIMER0_CONF_REG = val; + hpet_timer_conf_set(val); } __boot_func int sys_clock_driver_init(const struct device *dev) { extern int z_clock_hw_cycles_per_sec; - uint32_t hz; + uint32_t hz, reg; ARG_UNUSED(dev); ARG_UNUSED(hz); 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), DT_INST_IRQ(0, priority), @@ -147,28 +323,33 @@ int sys_clock_driver_init(const struct device *dev) irq_enable(DT_INST_IRQN(0)); #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; cyc_per_tick = hz / CONFIG_SYS_CLOCK_TICKS_PER_SEC; max_ticks = (MAX_TICKS - cyc_per_tick) / cyc_per_tick; #endif + last_count = hpet_counter_get(); + /* Note: we set the legacy routing bit, because otherwise * nothing in Zephyr disables the PIT which then fires * interrupts into the same IRQ. But that means we're then * forced to use IRQ2 contra the way the kconfig IRQ selection * is supposed to work. Should fix this. */ - GENERAL_CONF_REG |= GCONF_LR | GCONF_ENABLE; - TIMER0_CONF_REG &= ~TCONF_PERIODIC; - TIMER0_CONF_REG &= ~TCONF_FSB_EN; - TIMER0_CONF_REG |= TCONF_MODE32; + reg = hpet_gconf_get(); + reg |= GCONF_LR | GCONF_ENABLE; + hpet_gconf_set(reg); - 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; - TIMER0_COMPARATOR_REG = MAIN_COUNTER_REG + cyc_per_tick; + hpet_timer_comparator_set(last_count + cyc_per_tick); return 0; } @@ -188,8 +369,12 @@ void sys_clock_set_timeout(int32_t ticks, bool idle) ARG_UNUSED(idle); #if defined(CONFIG_TICKLESS_KERNEL) + uint32_t reg; + if (ticks == K_TICKS_FOREVER && idle) { - GENERAL_CONF_REG &= ~GCONF_ENABLE; + reg = hpet_gconf_get(); + reg &= ~GCONF_ENABLE; + hpet_gconf_set(reg); return; } @@ -197,7 +382,7 @@ void sys_clock_set_timeout(int32_t ticks, bool idle) ticks = CLAMP(ticks - 1, 0, (int32_t)max_ticks); 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; /* Round up to next tick boundary. */ @@ -215,7 +400,7 @@ void sys_clock_set_timeout(int32_t ticks, bool idle) cyc += cyc_per_tick; } - TIMER0_COMPARATOR_REG = cyc; + hpet_timer_comparator_set(cyc); k_spin_unlock(&lock, key); #endif } @@ -228,7 +413,7 @@ uint32_t sys_clock_elapsed(void) } 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); return ret; @@ -237,11 +422,15 @@ uint32_t sys_clock_elapsed(void) __pinned_func uint32_t sys_clock_cycle_get_32(void) { - return MAIN_COUNTER_REG; + return hpet_counter_get(); } __pinned_func 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); }