driver: timer: Support for RX system timer

This commit add a system timer driver for Renesas RX using the
CMT peripheral. The driver supports both system ticks and
high-resolution cycle counting
- Configures CMT0 as the system tick timer
- Configures CMT1 as a free-running cycle timer for precise
  time tracking
- Handles timer overflows to maintain a continuous cycle count.
- Implements sys_clock_cycle_get_32() and sys_clock_cycle_get_64()
  for  high-resolution timing
- Supports Zephyr tickless kernel mode by tracking elapsed cycles
- Enables interrupt-based tick announcement using CMT0

Signed-off-by: Duy Nguyen <duy.nguyen.xa@renesas.com>
Signed-off-by: Yuichi Nakada <yuichi.nakada.sx@renesas.com>
This commit is contained in:
Duy Nguyen 2025-02-26 11:04:26 +07:00 committed by Benjamin Cabé
commit ad42e4d87d
9 changed files with 332 additions and 7 deletions

View file

@ -32,6 +32,7 @@ zephyr_library_sources_ifdef(CONFIG_NATIVE_SIM_TIMER native_sim_timer.c)
zephyr_library_sources_ifdef(CONFIG_NPCX_ITIM_TIMER npcx_itim_timer.c)
zephyr_library_sources_ifdef(CONFIG_NRF_GRTC_TIMER nrf_grtc_timer.c)
zephyr_library_sources_ifdef(CONFIG_NRF_RTC_TIMER nrf_rtc_timer.c)
zephyr_library_sources_ifdef(CONFIG_RENESAS_RX_TIMER_CMT renesas_rx_cmt.c)
zephyr_library_sources_ifdef(CONFIG_RCAR_CMT_TIMER rcar_cmt_timer.c)
zephyr_library_sources_ifdef(CONFIG_RENESAS_RA_ULPT_TIMER renesas_ra_ulpt_timer.c)
zephyr_library_sources_ifdef(CONFIG_RISCV_MACHINE_TIMER riscv_machine_timer.c)

View file

@ -90,6 +90,7 @@ source "drivers/timer/Kconfig.npcx_itim"
source "drivers/timer/Kconfig.nrf_rtc"
source "drivers/timer/Kconfig.nrf_grtc"
source "drivers/timer/Kconfig.nrf_xrtc"
source "drivers/timer/Kconfig.renesas_rx"
source "drivers/timer/Kconfig.rcar_cmt"
source "drivers/timer/Kconfig.riscv_machine"
source "drivers/timer/Kconfig.rv32m1_lptmr"

View file

@ -0,0 +1,13 @@
# Copyright (c) 2024 Renesas Electronics Corporation
# SPDX-License-Identifier: Apache-2.0
config RENESAS_RX_TIMER_CMT
bool "Renesas RX timer CMT"
default y
depends on DT_HAS_RENESAS_RX_TIMER_CMT_ENABLED
select TIMER_HAS_64BIT_CYCLE_COUNTER
select TICKLESS_CAPABLE
help
This module implements a kernel device driver for the Renesas RX
platform provides the standard "system clock driver" interfaces.
If unchecked, no timer will be used.

View file

@ -0,0 +1,227 @@
/*
* Copyright (c) 2024 Renesas Electronics Corporation
* SPDX-License-Identifier: Apache-2.0
*/
#include "zephyr/sys_clock.h"
#include <zephyr/arch/cpu.h>
#include <zephyr/init.h>
#include <soc.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/irq.h>
#include <zephyr/spinlock.h>
#include <zephyr/drivers/clock_control/renesas_rx_cgc.h>
#define DT_DRV_COMPAT renesas_rx_timer_cmt
#define CMT_NODE DT_NODELABEL(cmt)
#define CMT0_NODE DT_NODELABEL(cmt0)
#define CMT1_NODE DT_NODELABEL(cmt1)
#define ICU_NODE DT_NODELABEL(icu)
#define ICU_IR_ADDR DT_REG_ADDR_BY_NAME(ICU_NODE, IR)
#define ICU_IER_ADDR DT_REG_ADDR_BY_NAME(ICU_NODE, IER)
#define ICU_IPR_ADDR DT_REG_ADDR_BY_NAME(ICU_NODE, IPR)
#define ICU_FIR_ADDR DT_REG_ADDR_BY_NAME(ICU_NODE, FIR)
#define ICU_IR ((volatile uint8_t *)ICU_IR_ADDR)
#define CMT0_IRQ_NUM DT_IRQ_BY_NAME(CMT0_NODE, cmi, irq)
#define CMT1_IRQ_NUM DT_IRQ_BY_NAME(CMT1_NODE, cmi, irq)
#define COUNTER_MAX 0x0000ffff
#define CYCLES_PER_SEC (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC)
#define TICKS_PER_SEC (CONFIG_SYS_CLOCK_TICKS_PER_SEC)
#define CYCLES_PER_TICK (CYCLES_PER_SEC / TICKS_PER_SEC)
#define CYCLES_CYCLE_TIMER (COUNTER_MAX + 1)
#define MSTPCR_ADDR ((volatile uint32_t *)0x00080010) /* MSTPCR register address */
#define MSTP_CMT0_BIT (1 << 15) /* Bit 15 controls MSTPA15 (CMT0) */
static const struct clock_control_rx_subsys_cfg cmt_clk_cfg = {
.mstp = DT_CLOCKS_CELL_BY_IDX(DT_INST_PARENT(0), 0, mstp),
.stop_bit = DT_CLOCKS_CELL_BY_IDX(DT_INST_PARENT(0), 0, stop_bit),
};
struct timer_rx_cfg {
volatile uint16_t *cmstr;
volatile uint16_t *cmcr;
volatile uint16_t *cmcnt;
volatile uint16_t *cmcor;
};
static const struct timer_rx_cfg tick_timer_cfg = {
.cmstr = (uint16_t *)DT_REG_ADDR_BY_NAME(CMT_NODE, CMSTR0),
.cmcr = (uint16_t *)DT_REG_ADDR_BY_NAME(CMT0_NODE, CMCR),
.cmcnt = (uint16_t *)DT_REG_ADDR_BY_NAME(CMT0_NODE, CMCNT),
.cmcor = (uint16_t *)DT_REG_ADDR_BY_NAME(CMT0_NODE, CMCOR)};
static const struct timer_rx_cfg cycle_timer_cfg = {
.cmstr = (uint16_t *)DT_REG_ADDR_BY_NAME(CMT_NODE, CMSTR0),
.cmcr = (uint16_t *)DT_REG_ADDR_BY_NAME(CMT1_NODE, CMCR),
.cmcnt = (uint16_t *)DT_REG_ADDR_BY_NAME(CMT1_NODE, CMCNT),
.cmcor = (uint16_t *)DT_REG_ADDR_BY_NAME(CMT1_NODE, CMCOR)};
#ifdef CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER
typedef uint64_t cycle_t;
#define CYCLE_COUNT_MAX (0xffffffffffffffff)
#else
typedef uint32_t cycle_t;
#define CYCLE_COUNT_MAX (0xffffffff)
#endif
static cycle_t cycle_count;
static uint16_t clock_cycles_per_tick;
static volatile cycle_t announced_cycle_count;
static struct k_spinlock lock;
#if defined(CONFIG_TEST)
const int32_t z_sys_timer_irq_for_test = CMT0_IRQ_NUM;
#endif
static cycle_t cmt1_elapsed(void)
{
uint32_t val1 = (uint32_t)(*cycle_timer_cfg.cmcnt);
uint8_t cmt_ir = ICU_IR[29];
uint32_t val2 = (uint32_t)(*cycle_timer_cfg.cmcnt);
if ((1 == cmt_ir) || (val1 > val2)) {
cycle_count += CYCLES_CYCLE_TIMER;
ICU_IR[29] = 0;
}
return (val2 + cycle_count);
}
uint32_t sys_clock_cycle_get_32(void)
{
k_spinlock_key_t key = k_spin_lock(&lock);
uint32_t ret = (uint32_t)cmt1_elapsed();
k_spin_unlock(&lock, key);
return ret;
}
#ifdef CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER
uint64_t sys_clock_cycle_get_64(void)
{
k_spinlock_key_t key = k_spin_lock(&lock);
uint64_t ret = (uint64_t)cmt1_elapsed();
k_spin_unlock(&lock, key);
return ret;
}
#endif /* CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER */
uint32_t sys_clock_elapsed(void)
{
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
/* Always return 0 for tickful operation */
return 0;
}
k_spinlock_key_t key = k_spin_lock(&lock);
uint32_t ret;
cycle_t current_cycle_count;
current_cycle_count = cmt1_elapsed();
if (current_cycle_count < announced_cycle_count) {
/* cycle_count overflowed */
ret = (uint32_t)((current_cycle_count +
(CYCLE_COUNT_MAX - announced_cycle_count + 1)) /
CYCLES_PER_TICK);
} else {
ret = (uint32_t)((current_cycle_count - announced_cycle_count) / CYCLES_PER_TICK);
}
k_spin_unlock(&lock, key);
return ret;
}
static void cmt0_isr(void)
{
k_ticks_t dticks;
cycle_t current_cycle_count;
k_spinlock_key_t key = k_spin_lock(&lock);
current_cycle_count = cmt1_elapsed();
if (current_cycle_count < announced_cycle_count) {
/* cycle_count overflowed */
dticks = (k_ticks_t)((current_cycle_count +
(CYCLE_COUNT_MAX - announced_cycle_count + 1)) /
CYCLES_PER_TICK);
} else {
dticks = (k_ticks_t)((current_cycle_count - announced_cycle_count) /
CYCLES_PER_TICK);
}
announced_cycle_count = (current_cycle_count / CYCLES_PER_TICK) * CYCLES_PER_TICK;
k_spin_unlock(&lock, key);
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
sys_clock_announce(1);
} else {
sys_clock_announce(dticks);
}
}
static int sys_clock_driver_init(void)
{
const struct device *clk = DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_INST_PARENT(0)));
int ret;
if (!device_is_ready(clk)) {
return -ENODEV;
}
ret = clock_control_on(clk, (clock_control_subsys_t)&cmt_clk_cfg);
if (ret < 0) {
return ret;
}
*tick_timer_cfg.cmcr = 0x00C0; /* enable CMT0 interrupt */
*cycle_timer_cfg.cmcr = 0x00C0; /* enable CMT1 interrupt */
clock_cycles_per_tick = (uint16_t)(CYCLES_PER_TICK);
*tick_timer_cfg.cmcor = clock_cycles_per_tick - 1;
*cycle_timer_cfg.cmcor = (uint16_t)COUNTER_MAX;
IRQ_CONNECT(CMT0_IRQ_NUM, 0x01, cmt0_isr, NULL, 0);
irq_enable(CMT0_IRQ_NUM);
*tick_timer_cfg.cmstr = 0x0003; /* start cmt0,1 */
return 0;
}
void sys_clock_set_timeout(int32_t ticks, bool idle)
{
if (IS_ENABLED(CONFIG_TICKLESS_KERNEL) && idle && ticks == K_TICKS_FOREVER) {
*tick_timer_cfg.cmstr = 0x0000; /* stop cmt0,1 */
return;
}
#if defined(CONFIG_TICKLESS_KERNEL)
/*
* By default, it is implemented as a no operation.
* Implement the processing as needed.
*/
#endif /* CONFIG_TICKLESS_KERNEL */
}
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);

View file

@ -0,0 +1,23 @@
# Copyright (c) 2021 KT-Elektronik, Klaucke und Partner GmbH
# Copyright (c) 2024 Renesas Electronics Corporation
# SPDX-License-Identifier: Apache-2.0
description: Renesas RX timer node
compatible: "renesas,rx-timer-cmt-start-control"
include: base.yaml
properties:
reg:
required: true
clocks:
required: true
clock-frequency:
type: int
required: true
description: |
Clock frequency information for Timer operation
This value need to be set at PCLKB/8

View file

@ -0,0 +1,13 @@
# Copyright (c) 2021 KT-Elektronik, Klaucke und Partner GmbH
# Copyright (c) 2024 Renesas Electronics Corporation
# SPDX-License-Identifier: Apache-2.0
description: Renesas RX timer node
compatible: "renesas,rx-timer-cmt"
include: base.yaml
properties:
reg:
required: true

View file

@ -71,13 +71,38 @@
};
};
cmt0: timer@88004 {
compatible = "renesas,rx-timer-cmt";
reg = <0x00088004 0x02>,
<0x00088000 0x02>,
<0x00088002 0x02>,
<0x00088006 0x02>;
status = "disabled";
cmt: timer@88000 {
compatible = "renesas,rx-timer-cmt-start-control";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x00088000 0x02>;
clocks = <&pclkb MSTPA 15>;
reg-names = "CMSTR0";
status = "okay";
cmt0: timer@88002 {
compatible = "renesas,rx-timer-cmt";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x00088002 0x02>,
<0x00088004 0x02>,
<0x00088006 0x02>;
reg-names = "CMCR", "CMCNT", "CMCOR";
interrupts = <28 1>;
interrupt-names = "cmi";
status = "okay";
};
cmt1: timer@88008 {
compatible = "renesas,rx-timer-cmt";
reg = <0x00088008 0x02>,
<0x0008800A 0x02>,
<0x0008800C 0x02>;
reg-names = "CMCR", "CMCNT", "CMCOR";
interrupts = <29 1>;
interrupt-names = "cmi";
status = "disabled";
};
};
ofsm: ofsm@ffffff80 {

View file

@ -3,6 +3,17 @@
if SOC_SERIES_RX130
DT_CMT_PATH := $(dt_nodelabel_path,cmt)
config SYS_CLOCK_HW_CYCLES_PER_SEC
default $(dt_node_int_prop_int,$(DT_CMT_PATH),clock-frequency)
# SYS_CLOCK_TICKS_PER_SEC is set to 100 if PCLKB is 48MHz or less.
# (PCLKB = SYS_CLOCK_HW_CYCLES_PER_SEC * 8)
config SYS_CLOCK_TICKS_PER_SEC
default 100 if SYS_CLOCK_HW_CYCLES_PER_SEC <= 6000000
default 1000
config INITIALIZATION_STACK_SIZE
default 512

View file

@ -3,6 +3,17 @@
if SOC_SERIES_RX62N
DT_CMT_PATH := $(dt_nodelabel_path,cmt)
config SYS_CLOCK_HW_CYCLES_PER_SEC
default $(dt_node_int_prop_int,$(DT_CMT_PATH),clock-frequency)
# SYS_CLOCK_TICKS_PER_SEC is set to 100 if PCLKB is 48MHz or less.
# (PCLKB = SYS_CLOCK_HW_CYCLES_PER_SEC * 8)
config SYS_CLOCK_TICKS_PER_SEC
default 100 if SYS_CLOCK_HW_CYCLES_PER_SEC <= 6000000
default 10000
config INITIALIZATION_STACK_SIZE
default 512