|  | /* | 
|  | * Copyright (c) 2018-2021 Intel Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT intel_hpet | 
|  | #include <zephyr/init.h> | 
|  | #include <zephyr/drivers/timer/system_timer.h> | 
|  | #include <zephyr/sys_clock.h> | 
|  | #include <zephyr/spinlock.h> | 
|  | #include <zephyr/irq.h> | 
|  | #include <zephyr/linker/sections.h> | 
|  |  | 
|  | #include <zephyr/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). | 
|  | */ | 
|  |  | 
|  | /* 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                   */ | 
|  |  | 
|  | DEVICE_MMIO_TOPLEVEL_STATIC(hpet_regs, DT_DRV_INST(0)); | 
|  |  | 
|  | #define HPET_REG_ADDR(off)			\ | 
|  | ((mm_reg_t)(DEVICE_MMIO_TOPLEVEL_GET(hpet_regs) + (off))) | 
|  |  | 
|  | /* High dword of General Capabilities and ID register */ | 
|  | #define CLK_PERIOD_REG			HPET_REG_ADDR(0x04) | 
|  |  | 
|  | /* General Configuration register */ | 
|  | #define GCONF_REG			HPET_REG_ADDR(0x10) | 
|  |  | 
|  | /* General Interrupt Status register */ | 
|  | #define INTR_STATUS_REG			HPET_REG_ADDR(0x20) | 
|  |  | 
|  | /* Main Counter Register */ | 
|  | #define MAIN_COUNTER_LOW_REG		HPET_REG_ADDR(0xf0) | 
|  | #define MAIN_COUNTER_HIGH_REG		HPET_REG_ADDR(0xf4) | 
|  |  | 
|  | /* Timer 0 Configuration and Capabilities register */ | 
|  | #define TIMER0_CONF_REG			HPET_REG_ADDR(0x100) | 
|  |  | 
|  | /* Timer 0 Comparator Register */ | 
|  | #define TIMER0_COMPARATOR_LOW_REG	HPET_REG_ADDR(0x108) | 
|  | #define TIMER0_COMPARATOR_HIGH_REG	HPET_REG_ADDR(0x10c) | 
|  |  | 
|  | #if defined(CONFIG_TEST) | 
|  | const int32_t z_sys_timer_irq_for_test = DT_IRQN(DT_INST(0, intel_hpet)); | 
|  | #endif | 
|  |  | 
|  | /** | 
|  | * @brief Return the value of the main counter. | 
|  | * | 
|  | * @return Value of Main Counter | 
|  | */ | 
|  | static inline uint64_t hpet_counter_get(void) | 
|  | { | 
|  | #ifdef CONFIG_64BIT | 
|  | uint64_t val = sys_read64(MAIN_COUNTER_LOW_REG); | 
|  |  | 
|  | return val; | 
|  | #else | 
|  | uint32_t high; | 
|  | uint32_t low; | 
|  |  | 
|  | do { | 
|  | high = sys_read32(MAIN_COUNTER_HIGH_REG); | 
|  | low = sys_read32(MAIN_COUNTER_LOW_REG); | 
|  | } while (high != sys_read32(MAIN_COUNTER_HIGH_REG)); | 
|  |  | 
|  | return ((uint64_t)high << 32) | low; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @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 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); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * The following 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_timer_comparator_set(uint32_t val) | 
|  | */ | 
|  | #ifndef HPET_USE_CUSTOM_REG_ACCESS_FUNCS | 
|  |  | 
|  | /** | 
|  | * @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(uint64_t val) | 
|  | { | 
|  | #if CONFIG_X86_64 | 
|  | sys_write64(val, TIMER0_COMPARATOR_LOW_REG); | 
|  | #else | 
|  | sys_write32((uint32_t)val, TIMER0_COMPARATOR_LOW_REG); | 
|  | sys_write32((uint32_t)(val >> 32), TIMER0_COMPARATOR_HIGH_REG); | 
|  | #endif | 
|  | } | 
|  | #endif /* HPET_USE_CUSTOM_REG_ACCESS_FUNCS */ | 
|  |  | 
|  | #ifndef HPET_COUNTER_CLK_PERIOD | 
|  | /* COUNTER_CLK_PERIOD (CLK_PERIOD_REG) is in femtoseconds (1e-15 sec) */ | 
|  | #define HPET_COUNTER_CLK_PERIOD		(1000000000000000ULL) | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | * HPET_INT_LEVEL_TRIGGER is used to set HPET interrupt as level trigger | 
|  | * for ARM CPU with NVIC like EHL PSE, whose DTS interrupt setting | 
|  | * has no "sense" cell. | 
|  | */ | 
|  | #if (DT_INST_IRQ_HAS_CELL(0, sense)) | 
|  | #ifdef HPET_INT_LEVEL_TRIGGER | 
|  | __WARN("HPET_INT_LEVEL_TRIGGER has no effect, DTS setting is used instead") | 
|  | #undef HPET_INT_LEVEL_TRIGGER | 
|  | #endif | 
|  | #if ((DT_INST_IRQ(0, sense) & IRQ_TYPE_LEVEL) == IRQ_TYPE_LEVEL) | 
|  | #define HPET_INT_LEVEL_TRIGGER | 
|  | #endif | 
|  | #endif /* (DT_INST_IRQ_HAS_CELL(0, sense)) */ | 
|  |  | 
|  | static __pinned_bss struct k_spinlock lock; | 
|  | static __pinned_bss uint64_t last_count; | 
|  | static __pinned_bss uint64_t last_tick; | 
|  | static __pinned_bss uint32_t last_elapsed; | 
|  |  | 
|  | #ifdef CONFIG_TIMER_READS_ITS_FREQUENCY_AT_RUNTIME | 
|  | static __pinned_bss unsigned int cyc_per_tick; | 
|  | #else | 
|  | #define cyc_per_tick			\ | 
|  | (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / CONFIG_SYS_CLOCK_TICKS_PER_SEC) | 
|  | #endif /* CONFIG_TIMER_READS_ITS_FREQUENCY_AT_RUNTIME */ | 
|  |  | 
|  | #define HPET_MAX_TICKS ((int32_t)0x7fffffff) | 
|  |  | 
|  | #ifdef HPET_INT_LEVEL_TRIGGER | 
|  | /** | 
|  | * @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); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* ensure the comparator is always set ahead of the current counter value */ | 
|  | static inline void hpet_timer_comparator_set_safe(uint64_t next) | 
|  | { | 
|  | hpet_timer_comparator_set(next); | 
|  |  | 
|  | uint64_t now = hpet_counter_get(); | 
|  |  | 
|  | if (unlikely((int64_t)(next - now) <= 0)) { | 
|  | uint32_t bump = 1; | 
|  |  | 
|  | do { | 
|  | next = now + bump; | 
|  | bump *= 2; | 
|  | hpet_timer_comparator_set(next); | 
|  | now = hpet_counter_get(); | 
|  | } while ((int64_t)(next - now) <= 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | __isr | 
|  | static void hpet_isr(const void *arg) | 
|  | { | 
|  | ARG_UNUSED(arg); | 
|  |  | 
|  | k_spinlock_key_t key = k_spin_lock(&lock); | 
|  |  | 
|  | uint64_t now = hpet_counter_get(); | 
|  |  | 
|  | #ifdef HPET_INT_LEVEL_TRIGGER | 
|  | /* | 
|  | * Clear interrupt only if level trigger is selected. | 
|  | * When edge trigger is selected, spec says only 0 can | 
|  | * be written. | 
|  | */ | 
|  | hpet_int_sts_set(TIMER0_INT_STS); | 
|  | #endif | 
|  |  | 
|  | if (IS_ENABLED(CONFIG_SMP) && | 
|  | IS_ENABLED(CONFIG_QEMU_TARGET)) { | 
|  | /* Qemu in SMP mode has observed the clock going | 
|  | * "backwards" relative to interrupts already received | 
|  | * on the other CPU, despite the HPET being | 
|  | * theoretically a global device. | 
|  | */ | 
|  | int64_t diff = (int64_t)(now - last_count); | 
|  |  | 
|  | if (last_count && diff < 0) { | 
|  | now = last_count; | 
|  | } | 
|  | } | 
|  | uint32_t dticks = (uint32_t)((now - last_count) / cyc_per_tick); | 
|  |  | 
|  | last_count += (uint64_t)dticks * cyc_per_tick; | 
|  | last_tick += dticks; | 
|  | last_elapsed = 0; | 
|  |  | 
|  | if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { | 
|  | uint64_t next = last_count + cyc_per_tick; | 
|  |  | 
|  | hpet_timer_comparator_set_safe(next); | 
|  | } | 
|  |  | 
|  | k_spin_unlock(&lock, key); | 
|  | sys_clock_announce(dticks); | 
|  | } | 
|  |  | 
|  | __pinned_func | 
|  | static void config_timer0(unsigned int irq) | 
|  | { | 
|  | uint32_t val = hpet_timer_conf_get(); | 
|  |  | 
|  | /* 5-bit IRQ field starting at bit 9 */ | 
|  | val = (val & ~(0x1f << 9)) | ((irq & 0x1f) << 9); | 
|  |  | 
|  | #ifdef HPET_INT_LEVEL_TRIGGER | 
|  | /* Set level trigger if selected */ | 
|  | val |= TIMER_CONF_INT_LEVEL; | 
|  | #endif | 
|  |  | 
|  | val &=  ~((uint32_t)(TIMER_CONF_MODE32 | TIMER_CONF_PERIODIC | | 
|  | TIMER_CONF_FSB_EN)); | 
|  | val |= TIMER_CONF_INT_ENABLE; | 
|  |  | 
|  | hpet_timer_conf_set(val); | 
|  | } | 
|  |  | 
|  | __boot_func | 
|  | void smp_timer_init(void) | 
|  | { | 
|  | /* Noop, the HPET is a single system-wide device and it's | 
|  | * configured to deliver interrupts to every CPU, so there's | 
|  | * nothing to do at initialization on auxiliary CPUs. | 
|  | */ | 
|  | } | 
|  |  | 
|  | __pinned_func | 
|  | 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) { | 
|  | reg = hpet_gconf_get(); | 
|  | reg &= ~GCONF_ENABLE; | 
|  | hpet_gconf_set(reg); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ticks = ticks == K_TICKS_FOREVER ? HPET_MAX_TICKS : ticks; | 
|  | ticks = CLAMP(ticks, 0, HPET_MAX_TICKS/2); | 
|  |  | 
|  | k_spinlock_key_t key = k_spin_lock(&lock); | 
|  | uint64_t cyc = (last_tick + last_elapsed + ticks) * cyc_per_tick; | 
|  |  | 
|  | hpet_timer_comparator_set_safe(cyc); | 
|  | k_spin_unlock(&lock, key); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | __pinned_func | 
|  | uint32_t sys_clock_elapsed(void) | 
|  | { | 
|  | if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | k_spinlock_key_t key = k_spin_lock(&lock); | 
|  | uint64_t now = hpet_counter_get(); | 
|  | uint32_t ret = (uint32_t)((now - last_count) / cyc_per_tick); | 
|  |  | 
|  | last_elapsed = ret; | 
|  | k_spin_unlock(&lock, key); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | __pinned_func | 
|  | uint32_t sys_clock_cycle_get_32(void) | 
|  | { | 
|  | return (uint32_t)hpet_counter_get(); | 
|  | } | 
|  |  | 
|  | __pinned_func | 
|  | uint64_t sys_clock_cycle_get_64(void) | 
|  | { | 
|  | return hpet_counter_get(); | 
|  | } | 
|  |  | 
|  | __pinned_func | 
|  | void sys_clock_idle_exit(void) | 
|  | { | 
|  | uint32_t reg; | 
|  |  | 
|  | reg = hpet_gconf_get(); | 
|  | reg |= GCONF_ENABLE; | 
|  | hpet_gconf_set(reg); | 
|  | } | 
|  |  | 
|  | __boot_func | 
|  | static int sys_clock_driver_init(void) | 
|  | { | 
|  | extern int z_clock_hw_cycles_per_sec; | 
|  | uint32_t hz, reg; | 
|  |  | 
|  | ARG_UNUSED(hz); | 
|  | ARG_UNUSED(z_clock_hw_cycles_per_sec); | 
|  |  | 
|  | DEVICE_MMIO_TOPLEVEL_MAP(hpet_regs, K_MEM_CACHE_NONE); | 
|  |  | 
|  | #if DT_INST_IRQ_HAS_CELL(0, sense) | 
|  | IRQ_CONNECT(DT_INST_IRQN(0), | 
|  | DT_INST_IRQ(0, priority), | 
|  | hpet_isr, 0, DT_INST_IRQ(0, sense)); | 
|  | #else | 
|  | IRQ_CONNECT(DT_INST_IRQN(0), | 
|  | DT_INST_IRQ(0, priority), | 
|  | hpet_isr, 0, 0); | 
|  | #endif | 
|  | config_timer0(DT_INST_IRQN(0)); | 
|  | irq_enable(DT_INST_IRQN(0)); | 
|  |  | 
|  | #ifdef CONFIG_TIMER_READS_ITS_FREQUENCY_AT_RUNTIME | 
|  | 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; | 
|  | #endif | 
|  |  | 
|  | reg = hpet_gconf_get(); | 
|  | reg |= GCONF_ENABLE; | 
|  |  | 
|  | #if (DT_INST_PROP(0, no_legacy_irq) == 0) | 
|  | /* 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. | 
|  | */ | 
|  | reg |= GCONF_LR; | 
|  | #endif | 
|  |  | 
|  | hpet_gconf_set(reg); | 
|  |  | 
|  | last_tick = hpet_counter_get() / cyc_per_tick; | 
|  | last_count = last_tick * cyc_per_tick; | 
|  | hpet_timer_comparator_set_safe(last_count + cyc_per_tick); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, | 
|  | CONFIG_SYSTEM_CLOCK_INIT_PRIORITY); |