| /* |
| * Copyright (c) 2021 Nuvoton Technology Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nuvoton_npcx_itim_timer |
| |
| /** |
| * @file |
| * @brief Nuvoton NPCX kernel device driver for "system clock driver" interface |
| * |
| * This file contains a kernel device driver implemented by the internal |
| * 64/32-bit timers in Nuvoton NPCX series. Via these two kinds of timers, the |
| * driver provides an standard "system clock driver" interface. |
| * |
| * It includes: |
| * - A system timer based on an ITIM64 (Internal 64-bit timer) instance, clocked |
| * by APB2 which freq is the same as CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC. |
| * - Provide a 64-bit cycles reading and ticks computation based on it. |
| * - Its prescaler is set to 1 and provide the kernel cycles reading without |
| * handling overflow mechanism. |
| * - After ec entered "sleep/deep sleep" power state which is used for better |
| * power consumption, then its clock will stop. |
| * |
| * - A event timer based on an ITIM32 (Internal 32-bit timer) instance, clocked |
| * by LFCLK which frequency is 32KHz and still activated when ec entered |
| * "sleep/deep sleep" power state. |
| * - Provide a system clock timeout notification. In its ISR, the driver informs |
| * the kernel that the specified number of ticks have elapsed. |
| * - Its prescaler is set to 1 and the formula between event timer's cycles and |
| * ticks is 'cycles = (ticks * 32768) / CONFIG_SYS_CLOCK_TICKS_PER_SEC' |
| * - Compensate reading of ITIM64 which clock is gating after ec entered |
| * "sleep/deep sleep" power state if CONFIG_PM is enabled. |
| */ |
| |
| #include <zephyr/init.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/timer/system_timer.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/sys_clock.h> |
| #include <zephyr/spinlock.h> |
| #include <soc.h> |
| |
| #include <zephyr/logging/log.h> |
| #include <zephyr/irq.h> |
| LOG_MODULE_REGISTER(itim, LOG_LEVEL_ERR); |
| |
| #define NPCX_ITIM32_MAX_CNT 0xffffffff |
| #define NPCX_ITIM64_MAX_HALF_CNT 0xffffffff |
| #define EVT_CYCLES_PER_SEC LFCLK /* 32768 Hz */ |
| #define SYS_CYCLES_PER_TICK (sys_clock_hw_cycles_per_sec() \ |
| / CONFIG_SYS_CLOCK_TICKS_PER_SEC) |
| #define SYS_CYCLES_PER_USEC (sys_clock_hw_cycles_per_sec() / 1000000) |
| #define EVT_CYCLES_FROM_TICKS(ticks) \ |
| DIV_ROUND_UP(ticks * EVT_CYCLES_PER_SEC, \ |
| CONFIG_SYS_CLOCK_TICKS_PER_SEC) |
| #define NPCX_ITIM_CLK_SEL_DELAY 92 /* Delay for clock selection (Unit:us) */ |
| /* Timeout for enabling ITIM module: 100us (Unit:cycles) */ |
| #define NPCX_ITIM_EN_TIMEOUT_CYCLES (100 * SYS_CYCLES_PER_USEC) |
| #define SYS_CYC_PER_EVT_CYC (sys_clock_hw_cycles_per_sec() / EVT_CYCLES_PER_SEC) |
| |
| /* Instance of system and event timers */ |
| static struct itim64_reg *const sys_tmr = (struct itim64_reg *) |
| DT_INST_REG_ADDR_BY_NAME(0, sys_itim); |
| static struct itim32_reg *const evt_tmr = (struct itim32_reg *) |
| DT_INST_REG_ADDR_BY_NAME(0, evt_itim); |
| |
| static const struct npcx_clk_cfg itim_clk_cfg[] = NPCX_DT_CLK_CFG_ITEMS_LIST(0); |
| |
| static struct k_spinlock lock; |
| /* Announced cycles in system timer before executing sys_clock_announce() */ |
| static uint64_t cyc_sys_announced; |
| static uint64_t last_ticks; |
| static uint32_t last_elapsed; |
| |
| /* Current target cycles of time-out signal in event timer */ |
| static uint32_t cyc_evt_timeout; |
| /* Total cycles of system timer stopped in "sleep/deep sleep" mode */ |
| __unused static uint64_t cyc_sys_compensated; |
| /* Current cycles in event timer when ec entered "sleep/deep sleep" mode */ |
| __unused static uint32_t cyc_evt_enter_deep_idle; |
| |
| /* ITIM local inline functions */ |
| static inline uint64_t npcx_itim_get_sys_cyc64(void) |
| { |
| uint32_t cnt64h, cnt64h_check, cnt64l; |
| |
| /* Read 64-bit counter value from two 32-bit registers */ |
| do { |
| cnt64h_check = sys_tmr->ITCNT64H; |
| cnt64l = sys_tmr->ITCNT64L; |
| cnt64h = sys_tmr->ITCNT64H; |
| } while (cnt64h != cnt64h_check); |
| |
| cnt64h = NPCX_ITIM64_MAX_HALF_CNT - cnt64h; |
| cnt64l = NPCX_ITIM64_MAX_HALF_CNT - cnt64l + 1; |
| |
| /* Return current value of 64-bit counter value of system timer */ |
| if (IS_ENABLED(CONFIG_PM)) { |
| return ((((uint64_t)cnt64h) << 32) | cnt64l) + |
| cyc_sys_compensated; |
| } else { |
| return (((uint64_t)cnt64h) << 32) | cnt64l; |
| } |
| } |
| |
| static inline int npcx_itim_evt_enable(void) |
| { |
| uint64_t cyc_start; |
| |
| /* Enable event timer and wait for it to take effect */ |
| evt_tmr->ITCTS32 |= BIT(NPCX_ITCTSXX_ITEN); |
| |
| /* |
| * Usually, it need one clock (30.5 us) to take effect since |
| * asynchronization between core and itim32's source clock (LFCLK). |
| */ |
| cyc_start = npcx_itim_get_sys_cyc64(); |
| while (!IS_BIT_SET(evt_tmr->ITCTS32, NPCX_ITCTSXX_ITEN)) { |
| if (npcx_itim_get_sys_cyc64() - cyc_start > |
| NPCX_ITIM_EN_TIMEOUT_CYCLES) { |
| /* ITEN bit is still unset? */ |
| if (!IS_BIT_SET(evt_tmr->ITCTS32, NPCX_ITCTSXX_ITEN)) { |
| LOG_ERR("Timeout: enabling EVT timer!"); |
| return -ETIMEDOUT; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static inline void npcx_itim_evt_disable(void) |
| { |
| /* Disable event timer and no need to wait for it to take effect */ |
| evt_tmr->ITCTS32 &= ~BIT(NPCX_ITCTSXX_ITEN); |
| } |
| |
| /* ITIM local functions */ |
| static int npcx_itim_start_evt_tmr_by_tick(int32_t ticks) |
| { |
| k_spinlock_key_t key = k_spin_lock(&lock); |
| |
| /* |
| * Get desired cycles of event timer from the requested ticks which |
| * round up to next tick boundary. |
| */ |
| |
| if (ticks == K_TICKS_FOREVER) { |
| cyc_evt_timeout = NPCX_ITIM32_MAX_CNT; |
| } else { |
| uint64_t next_cycs; |
| uint64_t curr = npcx_itim_get_sys_cyc64(); |
| uint32_t dcycles; |
| |
| if (ticks <= 0) { |
| ticks = 1; |
| } |
| |
| next_cycs = (last_ticks + last_elapsed + ticks) * SYS_CYCLES_PER_TICK; |
| if (unlikely(next_cycs <= curr)) { |
| cyc_evt_timeout = 1; |
| } else { |
| dcycles = next_cycs - curr; |
| cyc_evt_timeout = |
| CLAMP((dcycles / SYS_CYC_PER_EVT_CYC), 1, NPCX_ITIM32_MAX_CNT); |
| } |
| |
| } |
| LOG_DBG("ticks %x, cyc_evt_timeout %x", ticks, cyc_evt_timeout); |
| |
| /* Disable event timer if needed before configuring counter */ |
| if (IS_BIT_SET(evt_tmr->ITCTS32, NPCX_ITCTSXX_ITEN)) { |
| npcx_itim_evt_disable(); |
| } |
| |
| /* Upload counter of event timer */ |
| evt_tmr->ITCNT32 = MAX(cyc_evt_timeout - 1, 1); |
| |
| k_spin_unlock(&lock, key); |
| /* Enable event timer and start ticking */ |
| return npcx_itim_evt_enable(); |
| } |
| |
| static void npcx_itim_evt_isr(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| |
| /* Disable ITIM event module first */ |
| npcx_itim_evt_disable(); |
| /* Clear timeout status for event */ |
| evt_tmr->ITCTS32 |= BIT(NPCX_ITCTSXX_TO_STS); |
| |
| if (IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { |
| k_spinlock_key_t key = k_spin_lock(&lock); |
| uint64_t curr = npcx_itim_get_sys_cyc64(); |
| uint32_t delta_ticks = (uint32_t)((curr - cyc_sys_announced) / SYS_CYCLES_PER_TICK); |
| |
| cyc_sys_announced += delta_ticks * SYS_CYCLES_PER_TICK; |
| last_ticks += delta_ticks; |
| last_elapsed = 0; |
| k_spin_unlock(&lock, key); |
| |
| /* Informs kernel that specified number of ticks have elapsed */ |
| sys_clock_announce(delta_ticks); |
| } else { |
| /* Enable event timer for ticking and wait to it take effect */ |
| npcx_itim_evt_enable(); |
| |
| /* Informs kernel that one tick has elapsed */ |
| sys_clock_announce(1); |
| } |
| } |
| |
| #if defined(CONFIG_PM) |
| static inline uint32_t npcx_itim_get_evt_cyc32(void) |
| { |
| uint32_t cnt1, cnt2; |
| |
| cnt1 = evt_tmr->ITCNT32; |
| /* |
| * Wait for two consecutive equal values are read since the source clock |
| * of event timer is 32KHz. |
| */ |
| while ((cnt2 = evt_tmr->ITCNT32) != cnt1) { |
| cnt1 = cnt2; |
| } |
| |
| /* Return current value of 32-bit counter of event timer */ |
| return cnt2; |
| } |
| |
| static uint32_t npcx_itim_evt_elapsed_cyc32(void) |
| { |
| uint32_t cnt1 = npcx_itim_get_evt_cyc32(); |
| uint8_t sys_cts = evt_tmr->ITCTS32; |
| uint16_t cnt2 = npcx_itim_get_evt_cyc32(); |
| |
| /* Event has been triggered but timer ISR doesn't handle it */ |
| if (IS_BIT_SET(sys_cts, NPCX_ITCTSXX_TO_STS) || (cnt2 > cnt1)) { |
| cnt2 = cyc_evt_timeout; |
| } else { |
| cnt2 = cyc_evt_timeout - cnt2; |
| } |
| |
| /* Return elapsed cycles of 32-bit counter of event timer */ |
| return cnt2; |
| } |
| #endif /* CONFIG_PM */ |
| |
| /* System timer api functions */ |
| void sys_clock_set_timeout(int32_t ticks, bool idle) |
| { |
| ARG_UNUSED(idle); |
| |
| if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { |
| /* Only for tickless kernel system */ |
| return; |
| } |
| |
| LOG_DBG("timeout is %d", ticks); |
| /* Start a event timer in ticks */ |
| npcx_itim_start_evt_tmr_by_tick(ticks); |
| } |
| |
| uint32_t sys_clock_elapsed(void) |
| { |
| if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { |
| /* Always return 0 for tickful kernel system */ |
| return 0; |
| } |
| |
| k_spinlock_key_t key = k_spin_lock(&lock); |
| uint64_t delta_cycle = npcx_itim_get_sys_cyc64() - cyc_sys_announced; |
| uint32_t delta_ticks = (uint32_t)delta_cycle / SYS_CYCLES_PER_TICK; |
| |
| last_elapsed = delta_ticks; |
| k_spin_unlock(&lock, key); |
| |
| /* Return how many ticks elapsed since last sys_clock_announce() call */ |
| return delta_ticks; |
| } |
| |
| uint32_t sys_clock_cycle_get_32(void) |
| { |
| k_spinlock_key_t key = k_spin_lock(&lock); |
| uint64_t current = npcx_itim_get_sys_cyc64(); |
| |
| k_spin_unlock(&lock, key); |
| |
| /* Return how many cycles since system kernel timer start counting */ |
| return (uint32_t)(current); |
| } |
| |
| uint64_t sys_clock_cycle_get_64(void) |
| { |
| k_spinlock_key_t key = k_spin_lock(&lock); |
| uint64_t current = npcx_itim_get_sys_cyc64(); |
| |
| k_spin_unlock(&lock, key); |
| |
| /* Return how many cycles since system kernel timer start counting */ |
| return current; |
| } |
| |
| /* Platform specific system timer functions */ |
| #if defined(CONFIG_PM) |
| void npcx_clock_capture_low_freq_timer(void) |
| { |
| cyc_evt_enter_deep_idle = npcx_itim_evt_elapsed_cyc32(); |
| } |
| |
| void npcx_clock_compensate_system_timer(void) |
| { |
| uint32_t cyc_evt_elapsed_in_deep = npcx_itim_evt_elapsed_cyc32() - |
| cyc_evt_enter_deep_idle; |
| |
| cyc_sys_compensated += ((uint64_t)cyc_evt_elapsed_in_deep * |
| sys_clock_hw_cycles_per_sec()) / EVT_CYCLES_PER_SEC; |
| } |
| |
| uint64_t npcx_clock_get_sleep_ticks(void) |
| { |
| return cyc_sys_compensated / SYS_CYCLES_PER_TICK; |
| } |
| #endif /* CONFIG_PM */ |
| |
| static int sys_clock_driver_init(void) |
| { |
| int ret; |
| uint32_t sys_tmr_rate; |
| const struct device *const clk_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE); |
| |
| if (!device_is_ready(clk_dev)) { |
| LOG_ERR("clock control device not ready"); |
| return -ENODEV; |
| } |
| |
| /* Turn on all itim module clocks used for counting */ |
| for (int i = 0; i < ARRAY_SIZE(itim_clk_cfg); i++) { |
| ret = clock_control_on(clk_dev, (clock_control_subsys_t) |
| &itim_clk_cfg[i]); |
| if (ret < 0) { |
| LOG_ERR("Turn on timer %d clock failed.", i); |
| return ret; |
| } |
| } |
| |
| /* |
| * In npcx series, we use ITIM64 as system kernel timer. Its source |
| * clock frequency must equal to CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC. |
| */ |
| ret = clock_control_get_rate(clk_dev, (clock_control_subsys_t) |
| &itim_clk_cfg[1], &sys_tmr_rate); |
| if (ret < 0) { |
| LOG_ERR("Get ITIM64 clock rate failed %d", ret); |
| return ret; |
| } |
| |
| if (sys_tmr_rate != CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC) { |
| LOG_ERR("CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC doesn't match " |
| "ITIM64 clock frequency %d", sys_tmr_rate); |
| return -EINVAL; |
| } |
| |
| /* |
| * Step 1. Use a ITIM64 timer as system kernel timer for counting. |
| * Configure 64-bit timer counter and its prescaler to 1 first. |
| */ |
| sys_tmr->ITPRE64 = 0; |
| sys_tmr->ITCNT64L = NPCX_ITIM64_MAX_HALF_CNT; |
| sys_tmr->ITCNT64H = NPCX_ITIM64_MAX_HALF_CNT; |
| /* |
| * Select APB2 clock which freq is CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC, |
| * and clear timeout status bit before enabling the whole module. |
| */ |
| sys_tmr->ITCTS64 = BIT(NPCX_ITCTSXX_TO_STS); |
| /* Enable 64-bit timer and start ticking */ |
| sys_tmr->ITCTS64 |= BIT(NPCX_ITCTSXX_ITEN); |
| |
| /* |
| * Step 2. Use a ITIM32 timer for event handling (ex. timeout event). |
| * Configure 32-bit timer's prescaler to 1 first. |
| */ |
| evt_tmr->ITPRE32 = 0; |
| /* |
| * Select low frequency clock source (The freq is 32kHz), enable its |
| * interrupt/wake-up sources, and clear timeout status bit before |
| * enabling it. |
| */ |
| evt_tmr->ITCTS32 = BIT(NPCX_ITCTSXX_CKSEL) | BIT(NPCX_ITCTSXX_TO_WUE) |
| | BIT(NPCX_ITCTSXX_TO_IE) | BIT(NPCX_ITCTSXX_TO_STS); |
| |
| /* A delay for ITIM source clock selection */ |
| k_busy_wait(NPCX_ITIM_CLK_SEL_DELAY); |
| |
| /* Configure event timer's ISR */ |
| IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), |
| npcx_itim_evt_isr, NULL, 0); |
| /* Enable event timer interrupt */ |
| irq_enable(DT_INST_IRQN(0)); |
| |
| if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { |
| /* Start a event timer in one tick */ |
| ret = npcx_itim_start_evt_tmr_by_tick(1); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, |
| CONFIG_SYSTEM_CLOCK_INIT_PRIORITY); |