/* * 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 #include #include #include #include #include #include #include #include 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) \ ceiling_fraction(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) /* 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; /* 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) { /* * 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 { if (ticks <= 0) { ticks = 1; } cyc_evt_timeout = MIN(EVT_CYCLES_FROM_TICKS(ticks), 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); /* 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); uint32_t delta_ticks = (uint32_t)((npcx_itim_get_sys_cyc64() - cyc_sys_announced) / SYS_CYCLES_PER_TICK); /* Store announced cycles of system timer */ cyc_sys_announced = npcx_itim_get_sys_cyc64(); 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; k_spin_unlock(&lock, key); /* Return how many ticks elapsed since last sys_clock_announce() call */ return (uint32_t)(delta_cycle / SYS_CYCLES_PER_TICK); } 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(const struct device *dev) { ARG_UNUSED(dev); 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);