/* * Copyright (c) 2021, Sateesh Kotapati * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT silabs_gecko_stimer #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(counter_gecko, CONFIG_COUNTER_LOG_LEVEL); #if SL_SLEEPTIMER_PERIPHERAL == SL_SLEEPTIMER_PERIPHERAL_RTCC #define STIMER_IRQ_HANDLER RTCC_IRQHandler #define STIMER_MAX_VALUE _RTCC_CNT_MASK #elif SL_SLEEPTIMER_PERIPHERAL == SL_SLEEPTIMER_PERIPHERAL_SYSRTC #define STIMER_IRQ_HANDLER SYSRTC_APP_IRQHandler #define STIMER_MAX_VALUE _SYSRTC_CNT_MASK #else #error "Unsupported sleep timer peripheral" #endif #define STIMER_ALARM_NUM 2 struct counter_gecko_config { struct counter_config_info info; void (*irq_config)(void); uint32_t prescaler; }; struct counter_gecko_alarm_data { counter_alarm_callback_t callback; uint8_t chan_id; uint32_t ticks; struct device *dev; void *user_data; }; struct counter_gecko_top_data { counter_top_callback_t callback; uint32_t ticks; struct device *dev; void *user_data; }; struct counter_gecko_data { struct counter_gecko_alarm_data alarm[STIMER_ALARM_NUM]; struct counter_gecko_top_data top_data; }; static sl_sleeptimer_timer_handle_t alarm_timer[STIMER_ALARM_NUM]; static sl_sleeptimer_timer_handle_t top_timer; #ifdef CONFIG_SOC_GECKO_HAS_ERRATA_RTCC_E201 #define ERRATA_RTCC_E201_MESSAGE \ "Errata RTCC_E201: In case RTCC prescaler != 1 the module does not " \ "reset the counter value on CCV1 compare." #endif static void alarm_callback(sl_sleeptimer_timer_handle_t *handle, void *data) { struct counter_gecko_alarm_data *alarm_data = (struct counter_gecko_alarm_data *)data; uint32_t count = ((sl_sleeptimer_get_tick_count()) % (((struct counter_gecko_data *const)(alarm_data->dev)->data)->top_data.ticks)); if (alarm_data->callback != NULL) { alarm_data->callback( alarm_data->dev, alarm_data->chan_id, count, ((struct counter_alarm_cfg *)(alarm_data->user_data))->user_data); } } static void top_callback(sl_sleeptimer_timer_handle_t *handle, void *data) { struct counter_gecko_top_data *top_data = (struct counter_gecko_top_data *)data; if (top_data->callback != NULL) { top_data->callback(top_data->dev, ((struct counter_top_cfg *)(top_data->user_data))->user_data); } } static int counter_gecko_get_value(const struct device *dev, uint32_t *ticks) { struct counter_gecko_data *const dev_data = (struct counter_gecko_data *const)(dev)->data; *ticks = ((sl_sleeptimer_get_tick_count()) % (dev_data->top_data.ticks)); return 0; } static int counter_gecko_start(const struct device *dev) { ARG_UNUSED(dev); sl_status_t error_code; bool is_top_timer_running = false; error_code = sl_sleeptimer_is_timer_running(&top_timer, &is_top_timer_running); if ((error_code == SL_STATUS_OK) && (is_top_timer_running == true)) { return 0; } struct counter_gecko_data *const dev_data = (struct counter_gecko_data *const)(dev)->data; error_code = sl_sleeptimer_start_timer(&top_timer, dev_data->top_data.ticks, top_callback, (void *)&dev_data->top_data, 0, 0); return error_code; } static int counter_gecko_stop(const struct device *dev) { ARG_UNUSED(dev); sl_status_t error_code; bool is_top_timer_running = false; error_code = sl_sleeptimer_is_timer_running(&top_timer, &is_top_timer_running); if ((error_code == SL_STATUS_OK) && (is_top_timer_running == true)) { sl_sleeptimer_stop_timer(&top_timer); } return error_code; } static int counter_gecko_set_top_value(const struct device *dev, const struct counter_top_cfg *cfg) { struct counter_gecko_data *const dev_data = (struct counter_gecko_data *const)(dev)->data; sl_status_t error_code; bool is_top_timer_running = false; #ifdef CONFIG_SOC_GECKO_HAS_ERRATA_RTCC_E201 const struct counter_gecko_config *const dev_cfg = (const struct counter_gecko_config *const)(dev)->config; if (dev_cfg->prescaler != 1) { LOG_ERR(ERRATA_RTCC_E201_MESSAGE); return -EINVAL; } #endif error_code = sl_sleeptimer_is_timer_running(&top_timer, &is_top_timer_running); if ((error_code == SL_STATUS_OK) && (is_top_timer_running == true)) { sl_sleeptimer_stop_timer(&top_timer); } dev_data->top_data.callback = cfg->callback; dev_data->top_data.ticks = cfg->ticks; dev_data->top_data.dev = (struct device *)dev; dev_data->top_data.user_data = (struct counter_top_cfg *)cfg; error_code = sl_sleeptimer_start_periodic_timer(&top_timer, cfg->ticks, top_callback, (void *)&dev_data->top_data, 0, cfg->flags); return error_code; } static uint32_t counter_gecko_get_top_value(const struct device *dev) { struct counter_gecko_data *const dev_data = (struct counter_gecko_data *const)(dev)->data; return dev_data->top_data.ticks; } static int counter_gecko_set_alarm(const struct device *dev, uint8_t chan_id, const struct counter_alarm_cfg *alarm_cfg) { bool is_alarm_timer_running = false; struct counter_gecko_data *const dev_data = (struct counter_gecko_data *const)(dev)->data; sl_status_t error_code; uint32_t now_ticks = 0; uint32_t top_val = counter_gecko_get_top_value(dev); if ((top_val != 0) && (alarm_cfg->ticks > top_val)) { return -EINVAL; } if (chan_id >= STIMER_ALARM_NUM) { printk("Alarm timer count exceeded\n"); return -EINVAL; } error_code = sl_sleeptimer_is_timer_running(&alarm_timer[chan_id], &is_alarm_timer_running); if ((error_code == SL_STATUS_OK) && (is_alarm_timer_running == true)) { sl_sleeptimer_stop_timer(&alarm_timer[chan_id]); } if ((alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) != 0) { /* Absolute */ error_code = counter_gecko_get_value(dev, &now_ticks); if (now_ticks < alarm_cfg->ticks) { dev_data->alarm[chan_id].ticks = top_val + (alarm_cfg->ticks - now_ticks); } else { dev_data->alarm[chan_id].ticks = (top_val - (now_ticks - alarm_cfg->ticks)) % top_val; } } else { /* Relative */ dev_data->alarm[chan_id].ticks = alarm_cfg->ticks; } dev_data->alarm[chan_id].callback = alarm_cfg->callback; dev_data->alarm[chan_id].chan_id = chan_id; dev_data->alarm[chan_id].dev = (struct device *)dev; dev_data->alarm[chan_id].user_data = (struct counter_alarm_cfg *)alarm_cfg; error_code = sl_sleeptimer_start_timer(&alarm_timer[chan_id], dev_data->alarm[chan_id].ticks, alarm_callback, (void *)&dev_data->alarm[chan_id], 0, 0); return 0; } static int counter_gecko_cancel_alarm(const struct device *dev, uint8_t chan_id) { struct counter_gecko_data *const dev_data = (struct counter_gecko_data *const)(dev)->data; if (chan_id >= STIMER_ALARM_NUM) { LOG_DBG("Alarm timer count exceeded\n"); return -EINVAL; } sl_sleeptimer_stop_timer(&alarm_timer[chan_id]); dev_data->alarm[chan_id].callback = NULL; dev_data->alarm[chan_id].user_data = NULL; LOG_DBG("cancel alarm: channel %u", chan_id); return 0; } static uint32_t counter_gecko_get_pending_int(const struct device *dev) { ARG_UNUSED(dev); return 0; } static int counter_gecko_init(const struct device *dev) { const struct counter_gecko_config *const dev_cfg = (const struct counter_gecko_config *const)(dev)->config; struct counter_gecko_data *const dev_data = (struct counter_gecko_data *const)(dev)->data; sl_sleeptimer_init(); dev_data->top_data.ticks = STIMER_MAX_VALUE; /* Configure & enable module interrupts */ dev_cfg->irq_config(); LOG_INF("Device %s initialized", (dev)->name); return 0; } static const struct counter_driver_api counter_gecko_driver_api = { .start = counter_gecko_start, .stop = counter_gecko_stop, .get_value = counter_gecko_get_value, .set_alarm = counter_gecko_set_alarm, .cancel_alarm = counter_gecko_cancel_alarm, .set_top_value = counter_gecko_set_top_value, .get_pending_int = counter_gecko_get_pending_int, .get_top_value = counter_gecko_get_top_value, }; BUILD_ASSERT((DT_INST_PROP(0, prescaler) > 0U) && (DT_INST_PROP(0, prescaler) <= 32768U)); static void counter_gecko_0_irq_config(void) { IRQ_DIRECT_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), STIMER_IRQ_HANDLER, 0); irq_enable(DT_INST_IRQN(0)); } static const struct counter_gecko_config counter_gecko_0_config = { .info = { .max_top_value = STIMER_MAX_VALUE, .freq = DT_INST_PROP(0, clock_frequency) / DT_INST_PROP(0, prescaler), .flags = COUNTER_CONFIG_INFO_COUNT_UP, .channels = STIMER_ALARM_NUM, }, .irq_config = counter_gecko_0_irq_config, .prescaler = DT_INST_PROP(0, prescaler), }; static struct counter_gecko_data counter_gecko_0_data; DEVICE_DT_INST_DEFINE(0, counter_gecko_init, NULL, &counter_gecko_0_data, &counter_gecko_0_config, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &counter_gecko_driver_api);