225 lines
6.1 KiB
C
225 lines
6.1 KiB
C
/*
|
|
* Copyright (c) 2023 Renesas Electronics Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/drivers/clock_control/smartbond_clock_control.h>
|
|
#include <zephyr/drivers/timer/system_timer.h>
|
|
#include <zephyr/sys_clock.h>
|
|
#include <zephyr/spinlock.h>
|
|
#include <cmsis_core.h>
|
|
#include <zephyr/irq.h>
|
|
#include <da1469x_pdc.h>
|
|
|
|
#define COUNTER_SPAN BIT(24)
|
|
#define CYC_PER_TICK k_ticks_to_cyc_ceil32(1)
|
|
#define TICK_TO_CYC(tick) k_ticks_to_cyc_ceil32(tick)
|
|
#define CYC_TO_TICK(cyc) k_cyc_to_ticks_ceil32(cyc)
|
|
#define MAX_TICKS (((COUNTER_SPAN / 2) - CYC_PER_TICK) / (CYC_PER_TICK))
|
|
#define SMARTBOND_CLOCK_CONTROLLER DEVICE_DT_GET(DT_NODELABEL(osc))
|
|
/* Margin values are based on DA1469x characterization data */
|
|
#define RC32K_FREQ_POSITIVE_MARGIN_DUE_TO_VOLTAGE (675)
|
|
#define RC32K_FREQ_MARGIN_DUE_TO_TEMPERATURE (450)
|
|
|
|
static uint32_t last_timer_val_reg;
|
|
static uint32_t timer_val_31_24;
|
|
|
|
static uint32_t last_isr_val;
|
|
static uint32_t last_isr_val_rounded;
|
|
static uint32_t announced_ticks;
|
|
|
|
static uint32_t get_rc32k_max_frequency(void)
|
|
{
|
|
/* According to DA1469x datasheet */
|
|
uint32_t r32k_frequency = 37000;
|
|
|
|
clock_control_get_rate(SMARTBOND_CLOCK_CONTROLLER,
|
|
(clock_control_subsys_t)SMARTBOND_CLK_RC32K, &r32k_frequency);
|
|
|
|
r32k_frequency += RC32K_FREQ_POSITIVE_MARGIN_DUE_TO_VOLTAGE +
|
|
RC32K_FREQ_MARGIN_DUE_TO_TEMPERATURE;
|
|
return r32k_frequency;
|
|
}
|
|
|
|
static void set_reload(uint32_t val)
|
|
{
|
|
TIMER2->TIMER2_RELOAD_REG = val & TIMER2_TIMER2_RELOAD_REG_TIM_RELOAD_Msk;
|
|
}
|
|
|
|
static uint32_t timer_val_32(void)
|
|
{
|
|
uint32_t timer_val_reg;
|
|
uint32_t val;
|
|
|
|
timer_val_reg = TIMER2->TIMER2_TIMER_VAL_REG &
|
|
TIMER2_TIMER2_TIMER_VAL_REG_TIM_TIMER_VALUE_Msk;
|
|
if (timer_val_reg < last_timer_val_reg) {
|
|
timer_val_31_24 += COUNTER_SPAN;
|
|
}
|
|
last_timer_val_reg = timer_val_reg;
|
|
|
|
val = timer_val_31_24 + timer_val_reg;
|
|
|
|
return val;
|
|
}
|
|
|
|
static uint32_t timer_val_32_noupdate(void)
|
|
{
|
|
uint32_t timer_val_reg;
|
|
uint32_t val;
|
|
|
|
timer_val_reg = TIMER2->TIMER2_TIMER_VAL_REG &
|
|
TIMER2_TIMER2_TIMER_VAL_REG_TIM_TIMER_VALUE_Msk;
|
|
val = timer_val_31_24 + timer_val_reg;
|
|
if (timer_val_reg < last_timer_val_reg) {
|
|
val += COUNTER_SPAN;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
void sys_clock_set_timeout(int32_t ticks, bool idle)
|
|
{
|
|
uint32_t target_val;
|
|
uint32_t timer_val;
|
|
|
|
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
|
|
return;
|
|
}
|
|
|
|
if (ticks == K_TICKS_FOREVER) {
|
|
/* FIXME we could disable timer here */
|
|
}
|
|
|
|
/*
|
|
* When Watchdog is NOT enabled but power management is, system
|
|
* starts watchdog before PD_SYS is powered off.
|
|
* Watchdog default reload value is 0x1FFF (~82s for RC32K and 172s for RCX).
|
|
* After this time watchdog will reset system if not woken up before.
|
|
* When Watchdog is not configured power management freezes watchdog
|
|
* as soon as system is awaken. Following code makes sure that
|
|
* system never goes to sleep for longer time that watchdog reload value.
|
|
*/
|
|
if (!IS_ENABLED(CONFIG_WDT_SMARTBOND) && IS_ENABLED(CONFIG_PM)) {
|
|
uint32_t watchdog_expire_ticks;
|
|
|
|
if (CRG_TOP->CLK_RCX_REG & CRG_TOP_CLK_RCX_REG_RCX_ENABLE_Msk) {
|
|
/*
|
|
* When LP clock is RCX, the watchdog is clocked by RCX clock
|
|
* divided by 320.
|
|
*/
|
|
watchdog_expire_ticks = SYS_WDOG->WATCHDOG_REG * 320;
|
|
} else {
|
|
/*
|
|
* When LP clock is not RCX, the watchdog is clocked by RC32K
|
|
* divided by 320. In this case watchdog value to LP clock
|
|
* ticks must be calculated according to XTAL32K frequency and
|
|
* RC32K maximum frequency.
|
|
*/
|
|
watchdog_expire_ticks = SYS_WDOG->WATCHDOG_REG *
|
|
CONFIG_SYS_CLOCK_TICKS_PER_SEC / (get_rc32k_max_frequency() / 320);
|
|
}
|
|
if (watchdog_expire_ticks - 2 < ticks) {
|
|
ticks = watchdog_expire_ticks - 2;
|
|
}
|
|
}
|
|
ticks = (ticks == K_TICKS_FOREVER) ? MAX_TICKS : ticks;
|
|
ticks = CLAMP(ticks - 1, 0, (int32_t)MAX_TICKS);
|
|
|
|
timer_val = timer_val_32_noupdate();
|
|
|
|
/* Calculate target timer value and align to full tick */
|
|
target_val = timer_val + TICK_TO_CYC(ticks);
|
|
target_val = ((target_val + CYC_PER_TICK - 1) / CYC_PER_TICK) * CYC_PER_TICK;
|
|
|
|
set_reload(target_val);
|
|
|
|
/*
|
|
* If time was so small that it already fired or should fire
|
|
* just now, mark interrupt as pending to avoid losing timer event.
|
|
* Condition is true when target_val (point in time that should be
|
|
* used for wakeup) is behind timer value or is equal to it.
|
|
* In that case we don't know if reload value was set in time or
|
|
* not but time expired anyway so make sure that interrupt is pending.
|
|
*/
|
|
if ((int32_t)(target_val - timer_val_32_noupdate() - 1) < 0) {
|
|
NVIC_SetPendingIRQ(TIMER2_IRQn);
|
|
}
|
|
}
|
|
|
|
uint32_t sys_clock_elapsed(void)
|
|
{
|
|
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
|
|
return 0;
|
|
}
|
|
|
|
return CYC_TO_TICK(timer_val_32_noupdate() - last_isr_val);
|
|
}
|
|
|
|
uint32_t sys_clock_cycle_get_32(void)
|
|
{
|
|
return timer_val_32_noupdate();
|
|
}
|
|
|
|
void sys_clock_idle_exit(void)
|
|
{
|
|
TIMER2->TIMER2_CTRL_REG |= TIMER2_TIMER2_CTRL_REG_TIM_EN_Msk;
|
|
}
|
|
|
|
void sys_clock_disable(void)
|
|
{
|
|
TIMER2->TIMER2_CTRL_REG &= ~TIMER2_TIMER2_CTRL_REG_TIM_EN_Msk;
|
|
}
|
|
|
|
static void timer2_isr(const void *arg)
|
|
{
|
|
uint32_t val;
|
|
int32_t delta;
|
|
int32_t dticks;
|
|
|
|
ARG_UNUSED(arg);
|
|
|
|
TIMER2->TIMER2_CLEAR_IRQ_REG = 1;
|
|
|
|
val = timer_val_32();
|
|
delta = (int32_t)(val - last_isr_val_rounded);
|
|
last_isr_val = val;
|
|
dticks = CYC_TO_TICK(delta);
|
|
last_isr_val_rounded += TICK_TO_CYC(dticks);
|
|
announced_ticks += dticks;
|
|
sys_clock_announce(dticks);
|
|
}
|
|
|
|
static int sys_clock_driver_init(void)
|
|
{
|
|
#if CONFIG_PM
|
|
uint8_t pdc_idx;
|
|
uint8_t en_xtal;
|
|
|
|
en_xtal = DT_NODE_HAS_STATUS(DT_NODELABEL(xtal32m), okay) ? MCU_PDC_EN_XTAL : 0;
|
|
|
|
/* Enable wakeup by TIMER2 */
|
|
pdc_idx = da1469x_pdc_add(MCU_PDC_TRIGGER_TIMER2, MCU_PDC_MASTER_M33, en_xtal);
|
|
__ASSERT_NO_MSG(pdc_idx >= 0);
|
|
da1469x_pdc_set(pdc_idx);
|
|
da1469x_pdc_ack(pdc_idx);
|
|
#endif
|
|
|
|
TIMER2->TIMER2_CTRL_REG = 0;
|
|
TIMER2->TIMER2_PRESCALER_REG = 0;
|
|
TIMER2->TIMER2_CTRL_REG |= TIMER2_TIMER2_CTRL_REG_TIM_CLK_EN_Msk;
|
|
TIMER2->TIMER2_CTRL_REG |= TIMER2_TIMER2_CTRL_REG_TIM_FREE_RUN_MODE_EN_Msk |
|
|
TIMER2_TIMER2_CTRL_REG_TIM_IRQ_EN_Msk |
|
|
TIMER2_TIMER2_CTRL_REG_TIM_EN_Msk;
|
|
|
|
IRQ_CONNECT(TIMER2_IRQn, _IRQ_PRIO_OFFSET, timer2_isr, 0, 0);
|
|
irq_enable(TIMER2_IRQn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
|