blob: e7bfd2d1581ee364ae5f2e521d0d7b3f187711f3 [file] [log] [blame]
/*
* Copyright (c) 2024-2025 Renesas Electronics Corporation
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT renesas_rz_gtm_counter
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/counter.h>
#include <r_gtm.h>
#define RZ_GTM_TOP_VALUE UINT32_MAX
#if defined(CONFIG_CPU_CORTEX_M)
#define counter_rz_gtm_clear_pending(irq) NVIC_ClearPendingIRQ(irq)
#define counter_rz_gtm_set_pending(irq) NVIC_SetPendingIRQ(irq)
#define counter_rz_gtm_is_pending(irq) NVIC_GetPendingIRQ(irq)
#elif defined(CONFIG_CPU_CORTEX_A)
#define counter_rz_gtm_clear_pending(irq) R_BSP_GICD_ClearSpiPending(irq)
#define counter_rz_gtm_set_pending(irq) R_BSP_GICD_SetSpiPending(irq)
#define counter_rz_gtm_is_pending(irq) R_BSP_GICD_GetSpiPending(irq)
#endif
struct counter_rz_gtm_config {
struct counter_config_info config_info;
const timer_api_t *fsp_api;
};
struct counter_rz_gtm_data {
timer_cfg_t *fsp_cfg;
gtm_instance_ctrl_t *fsp_ctrl;
/* Top callback function */
counter_top_callback_t top_cb;
/* Alarm callback function */
counter_alarm_callback_t alarm_cb;
void *user_data;
uint32_t clk_freq;
struct k_spinlock lock;
uint32_t guard_period;
uint32_t top_val;
bool is_started;
bool is_periodic;
};
static int counter_rz_gtm_get_value(const struct device *dev, uint32_t *ticks)
{
const struct counter_rz_gtm_config *cfg = dev->config;
struct counter_rz_gtm_data *data = dev->data;
timer_status_t timer_status;
fsp_err_t err;
err = cfg->fsp_api->statusGet(data->fsp_ctrl, &timer_status);
if (err != FSP_SUCCESS) {
return -EIO;
}
*ticks = (uint32_t)timer_status.counter;
return 0;
}
static void counter_rz_gtm_irq_handler(timer_callback_args_t *p_args)
{
const struct device *dev = p_args->p_context;
struct counter_rz_gtm_data *data = dev->data;
counter_alarm_callback_t alarm_callback = data->alarm_cb;
if (alarm_callback) {
uint32_t now;
if (counter_rz_gtm_get_value(dev, &now) != 0) {
return;
}
data->alarm_cb = NULL;
alarm_callback(dev, 0, now, data->user_data);
} else if (data->top_cb) {
data->top_cb(dev, data->user_data);
}
}
static int counter_rz_gtm_init(const struct device *dev)
{
struct counter_rz_gtm_data *data = dev->data;
const struct counter_rz_gtm_config *cfg = dev->config;
int err;
data->top_val = data->fsp_cfg->period_counts;
err = cfg->fsp_api->open(data->fsp_ctrl, data->fsp_cfg);
if (err != FSP_SUCCESS) {
return -EIO;
}
data->is_periodic = false;
return err;
}
static int counter_rz_gtm_switch_timer_mode(const struct device *dev)
{
const struct counter_rz_gtm_config *cfg = dev->config;
struct counter_rz_gtm_data *data = dev->data;
gtm_extended_cfg_t *fsp_cfg_extend = (gtm_extended_cfg_t *)data->fsp_cfg->p_extend;
int err;
if (data->is_periodic) {
fsp_cfg_extend->gtm_mode = GTM_TIMER_MODE_INTERVAL;
} else {
fsp_cfg_extend->gtm_mode = GTM_TIMER_MODE_FREERUN;
}
err = cfg->fsp_api->close(data->fsp_ctrl);
if (err != FSP_SUCCESS) {
return -EIO;
}
err = cfg->fsp_api->open(data->fsp_ctrl, data->fsp_cfg);
if (err != FSP_SUCCESS) {
return -EIO;
}
err = cfg->fsp_api->start(data->fsp_ctrl);
if (err != FSP_SUCCESS) {
return -EIO;
}
return err;
}
static int counter_rz_gtm_start(const struct device *dev)
{
struct counter_rz_gtm_data *data = dev->data;
k_spinlock_key_t key;
int err;
key = k_spin_lock(&data->lock);
if (data->is_started) {
k_spin_unlock(&data->lock, key);
return -EALREADY;
}
if (data->is_periodic) {
data->fsp_cfg->period_counts = data->top_val;
}
err = counter_rz_gtm_switch_timer_mode(dev);
if (err != FSP_SUCCESS) {
k_spin_unlock(&data->lock, key);
return -EIO;
}
counter_rz_gtm_clear_pending(data->fsp_cfg->cycle_end_irq);
data->is_started = true;
if (data->top_cb) {
irq_enable(data->fsp_cfg->cycle_end_irq);
}
k_spin_unlock(&data->lock, key);
return err;
}
static int counter_rz_gtm_stop(const struct device *dev)
{
const struct counter_rz_gtm_config *cfg = dev->config;
struct counter_rz_gtm_data *data = dev->data;
k_spinlock_key_t key;
int err;
key = k_spin_lock(&data->lock);
if (!data->is_started) {
k_spin_unlock(&data->lock, key);
return 0;
}
/* Stop timer */
err = cfg->fsp_api->stop(data->fsp_ctrl);
if (err != FSP_SUCCESS) {
k_spin_unlock(&data->lock, key);
return -EIO;
}
/* Disable irq */
irq_disable(data->fsp_cfg->cycle_end_irq);
counter_rz_gtm_clear_pending(data->fsp_cfg->cycle_end_irq);
data->top_cb = NULL;
data->alarm_cb = NULL;
data->user_data = NULL;
data->is_started = false;
k_spin_unlock(&data->lock, key);
return err;
}
static int counter_rz_gtm_set_alarm(const struct device *dev, uint8_t chan,
const struct counter_alarm_cfg *alarm_cfg)
{
const struct counter_rz_gtm_config *cfg = dev->config;
struct counter_rz_gtm_data *data = dev->data;
bool absolute;
uint32_t val;
k_spinlock_key_t key;
bool irq_on_late;
uint32_t max_rel_val;
uint32_t now, diff;
uint32_t read_again;
int err;
if (chan != 0) {
return -EINVAL;
}
if (!alarm_cfg) {
return -EINVAL;
}
absolute = alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE;
val = alarm_cfg->ticks;
/* Alarm callback is mandatory */
if (!alarm_cfg->callback) {
return -EINVAL;
}
key = k_spin_lock(&data->lock);
if (!data->is_started) {
k_spin_unlock(&data->lock, key);
return -EINVAL;
}
/* Alarm_cb need equal NULL before */
if (data->alarm_cb) {
k_spin_unlock(&data->lock, key);
return -EBUSY;
}
/* Timer is currently in interval mode */
if (data->is_periodic) {
/* Return error because val exceeded the limit set alarm */
if (val > data->fsp_cfg->period_counts) {
k_spin_unlock(&data->lock, key);
return -EINVAL;
}
/* Restore free running mode */
irq_disable(data->fsp_cfg->cycle_end_irq);
data->top_cb = NULL;
data->top_val = RZ_GTM_TOP_VALUE;
data->is_periodic = false;
data->fsp_cfg->period_counts = data->top_val;
err = counter_rz_gtm_switch_timer_mode(dev);
if (err != FSP_SUCCESS) {
k_spin_unlock(&data->lock, key);
return -EIO;
}
}
err = counter_rz_gtm_get_value(dev, &now);
if (err != 0) {
k_spin_unlock(&data->lock, key);
return -EIO;
}
data->alarm_cb = alarm_cfg->callback;
data->user_data = alarm_cfg->user_data;
if (absolute) {
max_rel_val = RZ_GTM_TOP_VALUE - data->guard_period;
irq_on_late = alarm_cfg->flags & COUNTER_ALARM_CFG_EXPIRE_WHEN_LATE;
} else {
/* If relative value is smaller than half of the counter range
* it is assumed that there is a risk of setting value too late
* and late detection algorithm must be applied. When late
* setting is detected, interrupt shall be triggered for
* immediate expiration of the timer. Detection is performed
* by limiting relative distance between CC and counter.
*
* Note that half of counter range is an arbitrary value.
*/
irq_on_late = val < (RZ_GTM_TOP_VALUE / 2U);
/* Limit max to detect short relative being set too late. */
max_rel_val = irq_on_late ? RZ_GTM_TOP_VALUE / 2U : RZ_GTM_TOP_VALUE;
val = (now + val) & RZ_GTM_TOP_VALUE;
}
/* Set new period */
data->fsp_cfg->period_counts = val;
err = cfg->fsp_api->periodSet(data->fsp_ctrl, data->fsp_cfg->period_counts);
if (err != FSP_SUCCESS) {
k_spin_unlock(&data->lock, key);
return -EIO;
}
err = counter_rz_gtm_get_value(dev, &read_again);
if (err != 0) {
k_spin_unlock(&data->lock, key);
return -EIO;
}
if (val >= read_again) {
diff = (val - read_again);
} else {
diff = val + (RZ_GTM_TOP_VALUE - read_again);
}
if (diff > max_rel_val) {
if (absolute) {
err = -ETIME;
}
/* Interrupt is triggered always for relative alarm and
* for absolute depending on the flag.
*/
if (irq_on_late) {
irq_enable(data->fsp_cfg->cycle_end_irq);
counter_rz_gtm_set_pending(data->fsp_cfg->cycle_end_irq);
} else {
data->alarm_cb = NULL;
}
} else {
if (diff == 0) {
/* RELOAD value could be set just in time for interrupt
* trigger or too late. In any case time is interrupt
* should be triggered. No need to enable interrupt
* on TIMER just make sure interrupt is pending.
*/
irq_enable(data->fsp_cfg->cycle_end_irq);
counter_rz_gtm_set_pending(data->fsp_cfg->cycle_end_irq);
} else {
counter_rz_gtm_clear_pending(data->fsp_cfg->cycle_end_irq);
irq_enable(data->fsp_cfg->cycle_end_irq);
}
}
k_spin_unlock(&data->lock, key);
return err;
}
static int counter_rz_gtm_cancel_alarm(const struct device *dev, uint8_t chan)
{
struct counter_rz_gtm_data *data = dev->data;
k_spinlock_key_t key;
ARG_UNUSED(chan);
key = k_spin_lock(&data->lock);
if (!data->is_started) {
k_spin_unlock(&data->lock, key);
return -EINVAL;
}
if (!data->alarm_cb) {
k_spin_unlock(&data->lock, key);
return 0;
}
irq_disable(data->fsp_cfg->cycle_end_irq);
counter_rz_gtm_clear_pending(data->fsp_cfg->cycle_end_irq);
data->alarm_cb = NULL;
data->user_data = NULL;
k_spin_unlock(&data->lock, key);
return 0;
}
static int counter_rz_gtm_set_top_value(const struct device *dev,
const struct counter_top_cfg *top_cfg)
{
const struct counter_rz_gtm_config *cfg = dev->config;
struct counter_rz_gtm_data *data = dev->data;
k_spinlock_key_t key;
uint32_t cur_tick;
bool reset;
int err = 0;
if (!top_cfg) {
return -EINVAL;
}
/* -EBUSY if any alarm is active */
if (data->alarm_cb) {
return -EBUSY;
}
key = k_spin_lock(&data->lock);
if (!data->is_periodic && top_cfg->ticks == RZ_GTM_TOP_VALUE) {
goto exit_unlock;
}
if (top_cfg->ticks == RZ_GTM_TOP_VALUE) {
/* Restore free running mode */
irq_disable(data->fsp_cfg->cycle_end_irq);
counter_rz_gtm_clear_pending(data->fsp_cfg->cycle_end_irq);
data->top_cb = NULL;
data->user_data = NULL;
data->top_val = RZ_GTM_TOP_VALUE;
data->is_periodic = false;
if (data->is_started) {
err = counter_rz_gtm_switch_timer_mode(dev);
if (err != FSP_SUCCESS) {
k_spin_unlock(&data->lock, key);
return -EIO;
}
counter_rz_gtm_clear_pending(data->fsp_cfg->cycle_end_irq);
}
goto exit_unlock;
}
data->top_cb = top_cfg->callback;
data->user_data = top_cfg->user_data;
data->top_val = top_cfg->ticks;
if (!data->is_started) {
data->is_periodic = true;
goto exit_unlock;
}
if (!data->is_periodic) {
/* Switch to interval mode first time, restart timer */
err = cfg->fsp_api->stop(data->fsp_ctrl);
if (err != FSP_SUCCESS) {
k_spin_unlock(&data->lock, key);
return -EIO;
}
irq_disable(data->fsp_cfg->cycle_end_irq);
data->is_periodic = true;
data->fsp_cfg->period_counts = data->top_val;
err = counter_rz_gtm_switch_timer_mode(dev);
if (err != FSP_SUCCESS) {
k_spin_unlock(&data->lock, key);
return -EIO;
}
if (data->top_cb) {
counter_rz_gtm_clear_pending(data->fsp_cfg->cycle_end_irq);
irq_enable(data->fsp_cfg->cycle_end_irq);
}
goto exit_unlock;
}
if (!data->top_cb) {
/* New top cfg is without callback - stop IRQs */
irq_disable(data->fsp_cfg->cycle_end_irq);
counter_rz_gtm_clear_pending(data->fsp_cfg->cycle_end_irq);
}
/* Timer already in interval mode - only change top value */
data->fsp_cfg->period_counts = data->top_val;
err = cfg->fsp_api->periodSet(data->fsp_ctrl, data->fsp_cfg->period_counts);
if (err != FSP_SUCCESS) {
k_spin_unlock(&data->lock, key);
return -EIO;
}
/* Check if counter reset is required */
reset = false;
if (top_cfg->flags & COUNTER_TOP_CFG_DONT_RESET) {
/* Don't reset counter */
err = counter_rz_gtm_get_value(dev, &cur_tick);
if (err != 0) {
k_spin_unlock(&data->lock, key);
return -EIO;
}
if (cur_tick >= data->top_val) {
err = -ETIME;
if (top_cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE) {
/* Reset counter if current is late */
reset = true;
}
}
} else {
reset = true;
}
if (reset) {
err = cfg->fsp_api->reset(data->fsp_ctrl);
if (err != FSP_SUCCESS) {
k_spin_unlock(&data->lock, key);
return -EIO;
}
}
exit_unlock:
k_spin_unlock(&data->lock, key);
return err;
}
static uint32_t counter_rz_gtm_get_pending_int(const struct device *dev)
{
struct counter_rz_gtm_data *data = dev->data;
return counter_rz_gtm_is_pending(data->fsp_cfg->cycle_end_irq);
}
static uint32_t counter_rz_gtm_get_top_value(const struct device *dev)
{
struct counter_rz_gtm_data *data = dev->data;
const struct counter_rz_gtm_config *cfg = dev->config;
uint32_t top_val = RZ_GTM_TOP_VALUE;
if (data->is_periodic) {
timer_info_t info;
int err;
err = cfg->fsp_api->infoGet(data->fsp_ctrl, &info);
if (err != FSP_SUCCESS) {
return 0;
}
top_val = info.period_counts;
}
return top_val;
}
static uint32_t counter_rz_gtm_get_guard_period(const struct device *dev, uint32_t flags)
{
struct counter_rz_gtm_data *data = dev->data;
ARG_UNUSED(flags);
return data->guard_period;
}
static int counter_rz_gtm_set_guard_period(const struct device *dev, uint32_t guard, uint32_t flags)
{
struct counter_rz_gtm_data *data = dev->data;
ARG_UNUSED(flags);
if (counter_rz_gtm_get_top_value(dev) < guard) {
return -EINVAL;
}
data->guard_period = guard;
return 0;
}
static uint32_t counter_rz_gtm_get_freq(const struct device *dev)
{
struct counter_rz_gtm_data *data = dev->data;
const struct counter_rz_gtm_config *cfg = dev->config;
timer_info_t info;
int err;
err = cfg->fsp_api->infoGet(data->fsp_ctrl, &info);
if (err != FSP_SUCCESS) {
return -EIO;
}
return info.clock_frequency;
}
static DEVICE_API(counter, counter_rz_gtm_driver_api) = {
.start = counter_rz_gtm_start,
.stop = counter_rz_gtm_stop,
.get_value = counter_rz_gtm_get_value,
.set_alarm = counter_rz_gtm_set_alarm,
.cancel_alarm = counter_rz_gtm_cancel_alarm,
.set_top_value = counter_rz_gtm_set_top_value,
.get_pending_int = counter_rz_gtm_get_pending_int,
.get_top_value = counter_rz_gtm_get_top_value,
.get_guard_period = counter_rz_gtm_get_guard_period,
.set_guard_period = counter_rz_gtm_set_guard_period,
.get_freq = counter_rz_gtm_get_freq,
};
void gtm_int_isr(IRQn_Type const irq);
void counter_rz_gtm_ovf_isr(const struct device *dev)
{
struct counter_rz_gtm_data *data = dev->data;
gtm_int_isr(data->fsp_cfg->cycle_end_irq);
}
#define RZ_GTM(idx) DT_INST_PARENT(idx)
#ifdef CONFIG_CPU_CORTEX_M
#define RZ_GTM_GET_IRQ_FLAGS(idx, irq_name) 0
#else /* Cortex-A/R */
#define RZ_GTM_GET_IRQ_FLAGS(idx, irq_name) DT_IRQ_BY_NAME(RZ_GTM(idx), irq_name, flags)
#endif
#define COUNTER_RZ_GTM_INIT(inst) \
static gtm_instance_ctrl_t g_timer##inst##_ctrl; \
static gtm_extended_cfg_t g_timer##inst##_extend = { \
.generate_interrupt_when_starts = GTM_GIWS_TYPE_DISABLED, \
.gtm_mode = GTM_TIMER_MODE_FREERUN, \
}; \
static timer_cfg_t g_timer##inst##_cfg = { \
.mode = TIMER_MODE_PERIODIC, \
.period_counts = (uint32_t)RZ_GTM_TOP_VALUE, \
.channel = DT_PROP(RZ_GTM(inst), channel), \
.p_callback = counter_rz_gtm_irq_handler, \
.p_context = DEVICE_DT_GET(DT_DRV_INST(inst)), \
.p_extend = &g_timer##inst##_extend, \
.cycle_end_ipl = DT_IRQ_BY_NAME(RZ_GTM(inst), overflow, priority), \
.cycle_end_irq = DT_IRQ_BY_NAME(RZ_GTM(inst), overflow, irq), \
}; \
static const struct counter_rz_gtm_config counter_rz_gtm_config_##inst = { \
.config_info = \
{ \
.max_top_value = RZ_GTM_TOP_VALUE, \
.flags = COUNTER_CONFIG_INFO_COUNT_UP, \
.channels = 1, \
}, \
.fsp_api = &g_timer_on_gtm, \
}; \
static struct counter_rz_gtm_data counter_rz_gtm_data_##inst = { \
.fsp_cfg = &g_timer##inst##_cfg, .fsp_ctrl = &g_timer##inst##_ctrl}; \
static int counter_rz_gtm_init_##inst(const struct device *dev) \
{ \
IRQ_CONNECT(DT_IRQ_BY_NAME(RZ_GTM(inst), overflow, irq), \
DT_IRQ_BY_NAME(RZ_GTM(inst), overflow, priority), \
counter_rz_gtm_ovf_isr, DEVICE_DT_INST_GET(inst), \
RZ_GTM_GET_IRQ_FLAGS(inst, overflow)); \
return counter_rz_gtm_init(dev); \
} \
DEVICE_DT_INST_DEFINE(inst, counter_rz_gtm_init_##inst, NULL, &counter_rz_gtm_data_##inst, \
&counter_rz_gtm_config_##inst, PRE_KERNEL_1, \
CONFIG_COUNTER_INIT_PRIORITY, &counter_rz_gtm_driver_api);
DT_INST_FOREACH_STATUS_OKAY(COUNTER_RZ_GTM_INIT)