/* * Copyright (c) 2024 Renesas Electronics Corporation * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT renesas_rz_gtm_counter #include #include #include #include #define RZ_GTM_TOP_VALUE UINT32_MAX #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) struct counter_rz_gtm_config { struct counter_config_info config_info; const timer_api_t *fsp_api; uint8_t irqn; }; 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 err; } uint32_t value = (uint32_t)timer_status.counter; *ticks = value; 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; k_spinlock_key_t key; key = k_spin_lock(&data->lock); if (alarm_callback) { uint32_t now; counter_rz_gtm_get_value(dev, &now); data->alarm_cb = NULL; alarm_callback(dev, 0, now, data->user_data); } else if (data->top_cb) { data->top_cb(dev, data->user_data); } k_spin_unlock(&data->lock, key); } 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 err; } return err; } static void counter_rz_gtm_start_freerun(const struct device *dev) { /* enable counter in free running mode */ 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; fsp_cfg_extend->gtm_mode = GTM_TIMER_MODE_FREERUN; cfg->fsp_api->close(data->fsp_ctrl); cfg->fsp_api->open(data->fsp_ctrl, data->fsp_cfg); cfg->fsp_api->start(data->fsp_ctrl); } static void counter_rz_gtm_start_interval(const struct device *dev) { /* start timer in interval mode */ 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; fsp_cfg_extend->gtm_mode = GTM_TIMER_MODE_INTERVAL; cfg->fsp_api->close(data->fsp_ctrl); cfg->fsp_api->open(data->fsp_ctrl, data->fsp_cfg); cfg->fsp_api->start(data->fsp_ctrl); } static int counter_rz_gtm_start(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; 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; counter_rz_gtm_start_interval(dev); } else { counter_rz_gtm_start_freerun(dev); } counter_rz_gtm_clear_pending(cfg->irqn); data->is_started = true; if (data->top_cb) { irq_enable(cfg->irqn); } k_spin_unlock(&data->lock, key); return 0; } 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; key = k_spin_lock(&data->lock); if (!data->is_started) { k_spin_unlock(&data->lock, key); return 0; } fsp_err_t err = FSP_SUCCESS; /* Stop timer */ err = cfg->fsp_api->stop(data->fsp_ctrl); /* dis irq */ irq_disable(cfg->irqn); counter_rz_gtm_clear_pending(cfg->irqn); 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 = alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE; uint32_t val = alarm_cfg->ticks; k_spinlock_key_t key; bool irq_on_late; uint32_t max_rel_val; uint32_t now, diff; int err = 0; if (!alarm_cfg) { return -EINVAL; } /* 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(cfg->irqn); data->top_cb = NULL; data->alarm_cb = alarm_cfg->callback; data->user_data = NULL; data->top_val = RZ_GTM_TOP_VALUE; data->is_periodic = false; if (data->is_started) { data->fsp_cfg->period_counts = data->top_val; counter_rz_gtm_start_freerun(dev); } } counter_rz_gtm_get_value(dev, &now); 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 err; } uint32_t read_counter_again = 0; counter_rz_gtm_get_value(dev, &read_counter_again); diff = ((val - 1U) - read_counter_again) & RZ_GTM_TOP_VALUE; 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(cfg->irqn); counter_rz_gtm_set_pending(cfg->irqn); } 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(cfg->irqn); counter_rz_gtm_set_pending(cfg->irqn); } else { counter_rz_gtm_clear_pending(cfg->irqn); irq_enable(cfg->irqn); } } k_spin_unlock(&data->lock, key); return err; } static int counter_rz_gtm_cancel_alarm(const struct device *dev, uint8_t chan) { const struct counter_rz_gtm_config *cfg = dev->config; 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(cfg->irqn); counter_rz_gtm_clear_pending(cfg->irqn); 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; int ret = 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(cfg->irqn); counter_rz_gtm_clear_pending(cfg->irqn); data->top_cb = NULL; data->user_data = NULL; data->top_val = RZ_GTM_TOP_VALUE; data->is_periodic = false; if (data->is_started) { counter_rz_gtm_start_freerun(dev); counter_rz_gtm_clear_pending(cfg->irqn); } 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 */ ret = cfg->fsp_api->stop(data->fsp_ctrl); irq_disable(cfg->irqn); data->is_periodic = true; data->fsp_cfg->period_counts = data->top_val; counter_rz_gtm_start_interval(dev); if (data->top_cb) { counter_rz_gtm_clear_pending(cfg->irqn); irq_enable(cfg->irqn); } goto exit_unlock; } if (!data->top_cb) { /* new top cfg is without callback - stop IRQs */ irq_disable(cfg->irqn); counter_rz_gtm_clear_pending(cfg->irqn); } /* timer already in interval mode - only change top value */ data->fsp_cfg->period_counts = data->top_val; cfg->fsp_api->periodSet(&data->fsp_ctrl, data->fsp_cfg->period_counts); /* check if counter reset is required */ if (top_cfg->flags & COUNTER_TOP_CFG_DONT_RESET) { /* Don't reset counter */ counter_rz_gtm_get_value(dev, &cur_tick); if (cur_tick >= data->top_val) { ret = -ETIME; if (top_cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE) { /* Reset counter if current is late */ cfg->fsp_api->stop(data->fsp_ctrl); cfg->fsp_api->start(data->fsp_ctrl); } } } else { /* reset counter */ cfg->fsp_api->stop(data->fsp_ctrl); cfg->fsp_api->start(data->fsp_ctrl); } exit_unlock: k_spin_unlock(&data->lock, key); return ret; } static uint32_t counter_rz_gtm_get_pending_int(const struct device *dev) { const struct counter_rz_gtm_config *cfg = dev->config; /* There is no register to check TIMER peripheral to check for interrupt * pending, check directly in NVIC. */ return counter_rz_gtm_is_pending(cfg->irqn); } 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; cfg->fsp_api->infoGet(data->fsp_ctrl, &info); 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); __ASSERT_NO_MSG(guard < counter_rz_gtm_get_top_value(dev)); 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; cfg->fsp_api->infoGet(data->fsp_ctrl, &info); 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, }; extern void gtm_int_isr(void); #define GTM(idx) DT_INST_PARENT(idx) #define COUNTER_RZG_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(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(GTM(inst), overflow, priority), \ .cycle_end_irq = DT_IRQ_BY_NAME(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, \ .irqn = DT_IRQ_BY_NAME(GTM(inst), overflow, irq), \ }; \ 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(GTM(inst), overflow, irq), \ DT_IRQ_BY_NAME(GTM(inst), overflow, priority), gtm_int_isr, \ DEVICE_DT_INST_GET(inst), 0); \ 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_RZG_GTM_INIT)