393 lines
12 KiB
C
393 lines
12 KiB
C
/*
|
|
* 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/device.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)
|
|
|
|
/* 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(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);
|