/* * Copyright (c) 2017 - 2018, Nordic Semiconductor ASA * Copyright (c) 2022 TOKITA Hiroshi * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT gd_gd32_timer #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(counter_gd32_timer); #define TIMER_INT_CH(ch) (TIMER_INT_CH0 << ch) #define TIMER_FLAG_CH(ch) (TIMER_FLAG_CH0 << ch) #define TIMER_INT_ALL (0xFFu) struct counter_gd32_ch_data { counter_alarm_callback_t callback; void *user_data; }; struct counter_gd32_data { counter_top_callback_t top_cb; void *top_user_data; uint32_t guard_period; atomic_t cc_int_pending; uint32_t freq; struct counter_gd32_ch_data alarm[]; }; struct counter_gd32_config { struct counter_config_info counter_info; uint32_t reg; uint16_t clkid; struct reset_dt_spec reset; uint16_t prescaler; void (*irq_config)(const struct device *dev); void (*set_irq_pending)(void); uint32_t (*get_irq_pending)(void); }; static uint32_t get_autoreload_value(const struct device *dev) { const struct counter_gd32_config *config = dev->config; return TIMER_CAR(config->reg); } static void set_autoreload_value(const struct device *dev, uint32_t value) { const struct counter_gd32_config *config = dev->config; TIMER_CAR(config->reg) = value; } static uint32_t get_counter(const struct device *dev) { const struct counter_gd32_config *config = dev->config; return TIMER_CNT(config->reg); } static void set_counter(const struct device *dev, uint32_t value) { const struct counter_gd32_config *config = dev->config; TIMER_CNT(config->reg) = value; } static void set_software_event_gen(const struct device *dev, uint8_t evt) { const struct counter_gd32_config *config = dev->config; TIMER_SWEVG(config->reg) |= evt; } static void set_prescaler(const struct device *dev, uint16_t prescaler) { const struct counter_gd32_config *config = dev->config; TIMER_PSC(config->reg) = prescaler; } static void set_compare_value(const struct device *dev, uint16_t chan, uint32_t compare_value) { const struct counter_gd32_config *config = dev->config; switch (chan) { case 0: TIMER_CH0CV(config->reg) = compare_value; break; case 1: TIMER_CH1CV(config->reg) = compare_value; break; case 2: TIMER_CH2CV(config->reg) = compare_value; break; case 3: TIMER_CH3CV(config->reg) = compare_value; break; } } static void interrupt_enable(const struct device *dev, uint32_t interrupt) { const struct counter_gd32_config *config = dev->config; TIMER_DMAINTEN(config->reg) |= interrupt; } static void interrupt_disable(const struct device *dev, uint32_t interrupt) { const struct counter_gd32_config *config = dev->config; TIMER_DMAINTEN(config->reg) &= ~interrupt; } static uint32_t interrupt_flag_get(const struct device *dev, uint32_t interrupt) { const struct counter_gd32_config *config = dev->config; return (TIMER_DMAINTEN(config->reg) & TIMER_INTF(config->reg) & interrupt); } static void interrupt_flag_clear(const struct device *dev, uint32_t interrupt) { const struct counter_gd32_config *config = dev->config; TIMER_INTF(config->reg) &= ~interrupt; } static int counter_gd32_timer_start(const struct device *dev) { const struct counter_gd32_config *config = dev->config; TIMER_CTL0(config->reg) |= (uint32_t)TIMER_CTL0_CEN; return 0; } static int counter_gd32_timer_stop(const struct device *dev) { const struct counter_gd32_config *config = dev->config; TIMER_CTL0(config->reg) &= ~(uint32_t)TIMER_CTL0_CEN; return 0; } static int counter_gd32_timer_get_value(const struct device *dev, uint32_t *ticks) { *ticks = get_counter(dev); return 0; } static uint32_t counter_gd32_timer_get_top_value(const struct device *dev) { return get_autoreload_value(dev); } static uint32_t ticks_add(uint32_t val1, uint32_t val2, uint32_t top) { uint32_t to_top; if (likely(IS_BIT_MASK(top))) { return (val1 + val2) & top; } to_top = top - val1; return (val2 <= to_top) ? val1 + val2 : val2 - to_top; } static uint32_t ticks_sub(uint32_t val, uint32_t old, uint32_t top) { if (likely(IS_BIT_MASK(top))) { return (val - old) & top; } /* if top is not 2^n-1 */ return (val >= old) ? (val - old) : val + top + 1 - old; } static void set_cc_int_pending(const struct device *dev, uint8_t chan) { const struct counter_gd32_config *config = dev->config; struct counter_gd32_data *data = dev->data; atomic_or(&data->cc_int_pending, TIMER_INT_CH(chan)); config->set_irq_pending(); } static int set_cc(const struct device *dev, uint8_t chan, uint32_t val, uint32_t flags) { const struct counter_gd32_config *config = dev->config; struct counter_gd32_data *data = dev->data; __ASSERT_NO_MSG(data->guard_period < counter_gd32_timer_get_top_value(dev)); bool absolute = flags & COUNTER_ALARM_CFG_ABSOLUTE; uint32_t top = counter_gd32_timer_get_top_value(dev); uint32_t now, diff, max_rel_val; bool irq_on_late; int err = 0; ARG_UNUSED(config); __ASSERT(!(TIMER_DMAINTEN(config->reg) & TIMER_INT_CH(chan)), "Expected that CC interrupt is disabled."); /* First take care of a risk of an event coming from CC being set to * next tick. Reconfigure CC to future (now tick is the furthest * future). */ now = get_counter(dev); set_compare_value(dev, chan, now); interrupt_flag_clear(dev, TIMER_FLAG_CH(chan)); if (absolute) { max_rel_val = top - data->guard_period; irq_on_late = 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 < (top / 2); /* limit max to detect short relative being set too late. */ max_rel_val = irq_on_late ? top / 2 : top; val = ticks_add(now, val, top); } set_compare_value(dev, chan, val); /* decrement value to detect also case when val == get_counter(dev). * Otherwise, condition would need to include comparing diff against 0. */ diff = ticks_sub(val - 1, get_counter(dev), top); 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) { set_cc_int_pending(dev, chan); } else { data->alarm[chan].callback = NULL; } } else { interrupt_enable(dev, TIMER_INT_CH(chan)); } return err; } static int counter_gd32_timer_set_alarm(const struct device *dev, uint8_t chan, const struct counter_alarm_cfg *alarm_cfg) { struct counter_gd32_data *data = dev->data; struct counter_gd32_ch_data *chdata = &data->alarm[chan]; if (alarm_cfg->ticks > counter_gd32_timer_get_top_value(dev)) { return -EINVAL; } if (chdata->callback) { return -EBUSY; } chdata->callback = alarm_cfg->callback; chdata->user_data = alarm_cfg->user_data; return set_cc(dev, chan, alarm_cfg->ticks, alarm_cfg->flags); } static int counter_gd32_timer_cancel_alarm(const struct device *dev, uint8_t chan) { struct counter_gd32_data *data = dev->data; interrupt_disable(dev, TIMER_INT_CH(chan)); data->alarm[chan].callback = NULL; return 0; } static int counter_gd32_timer_set_top_value(const struct device *dev, const struct counter_top_cfg *cfg) { const struct counter_gd32_config *config = dev->config; struct counter_gd32_data *data = dev->data; int err = 0; for (uint32_t i = 0; i < config->counter_info.channels; i++) { /* Overflow can be changed only when all alarms are * disables. */ if (data->alarm[i].callback) { return -EBUSY; } } interrupt_disable(dev, TIMER_INT_UP); set_autoreload_value(dev, cfg->ticks); interrupt_flag_clear(dev, TIMER_INT_FLAG_UP); data->top_cb = cfg->callback; data->top_user_data = cfg->user_data; if (!(cfg->flags & COUNTER_TOP_CFG_DONT_RESET)) { set_counter(dev, 0); } else if (get_counter(dev) >= cfg->ticks) { err = -ETIME; if (cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE) { set_counter(dev, 0); } } if (cfg->callback) { interrupt_enable(dev, TIMER_INT_UP); } return err; } static uint32_t counter_gd32_timer_get_pending_int(const struct device *dev) { const struct counter_gd32_config *cfg = dev->config; return cfg->get_irq_pending(); } static uint32_t counter_gd32_timer_get_freq(const struct device *dev) { struct counter_gd32_data *data = dev->data; return data->freq; } static uint32_t counter_gd32_timer_get_guard_period(const struct device *dev, uint32_t flags) { struct counter_gd32_data *data = dev->data; return data->guard_period; } static int counter_gd32_timer_set_guard_period(const struct device *dev, uint32_t guard, uint32_t flags) { struct counter_gd32_data *data = dev->data; __ASSERT_NO_MSG(guard < counter_gd32_timer_get_top_value(dev)); data->guard_period = guard; return 0; } static void top_irq_handle(const struct device *dev) { struct counter_gd32_data *data = dev->data; counter_top_callback_t cb = data->top_cb; if (interrupt_flag_get(dev, TIMER_INT_FLAG_UP) != 0) { interrupt_flag_clear(dev, TIMER_INT_FLAG_UP); __ASSERT(cb != NULL, "top event enabled - expecting callback"); cb(dev, data->top_user_data); } } static void alarm_irq_handle(const struct device *dev, uint32_t chan) { struct counter_gd32_data *data = dev->data; struct counter_gd32_ch_data *alarm = &data->alarm[chan]; counter_alarm_callback_t cb; bool hw_irq_pending = !!(interrupt_flag_get(dev, TIMER_FLAG_CH(chan))); bool sw_irq_pending = data->cc_int_pending & TIMER_INT_CH(chan); if (hw_irq_pending || sw_irq_pending) { atomic_and(&data->cc_int_pending, ~TIMER_INT_CH(chan)); interrupt_disable(dev, TIMER_INT_CH(chan)); interrupt_flag_clear(dev, TIMER_FLAG_CH(chan)); cb = alarm->callback; alarm->callback = NULL; if (cb) { cb(dev, chan, get_counter(dev), alarm->user_data); } } } static void irq_handler(const struct device *dev) { const struct counter_gd32_config *cfg = dev->config; top_irq_handle(dev); for (uint32_t i = 0; i < cfg->counter_info.channels; i++) { alarm_irq_handle(dev, i); } } static int counter_gd32_timer_init(const struct device *dev) { const struct counter_gd32_config *cfg = dev->config; struct counter_gd32_data *data = dev->data; uint32_t pclk; clock_control_on(GD32_CLOCK_CONTROLLER, (clock_control_subsys_t)&cfg->clkid); clock_control_get_rate(GD32_CLOCK_CONTROLLER, (clock_control_subsys_t)&cfg->clkid, &pclk); data->freq = pclk / (cfg->prescaler + 1); interrupt_disable(dev, TIMER_INT_ALL); reset_line_toggle_dt(&cfg->reset); cfg->irq_config(dev); set_prescaler(dev, cfg->prescaler); set_autoreload_value(dev, cfg->counter_info.max_top_value); set_software_event_gen(dev, TIMER_SWEVG_UPG); return 0; } static const struct counter_driver_api counter_api = { .start = counter_gd32_timer_start, .stop = counter_gd32_timer_stop, .get_value = counter_gd32_timer_get_value, .set_alarm = counter_gd32_timer_set_alarm, .cancel_alarm = counter_gd32_timer_cancel_alarm, .set_top_value = counter_gd32_timer_set_top_value, .get_pending_int = counter_gd32_timer_get_pending_int, .get_top_value = counter_gd32_timer_get_top_value, .get_guard_period = counter_gd32_timer_get_guard_period, .set_guard_period = counter_gd32_timer_set_guard_period, .get_freq = counter_gd32_timer_get_freq, }; #define TIMER_IRQ_CONFIG(n) \ static void irq_config_##n(const struct device *dev) \ { \ IRQ_CONNECT(DT_INST_IRQ_BY_NAME(n, global, irq), \ DT_INST_IRQ_BY_NAME(n, global, priority), \ irq_handler, DEVICE_DT_INST_GET(n), 0); \ irq_enable(DT_INST_IRQ_BY_NAME(n, global, irq)); \ } \ static void set_irq_pending_##n(void) \ { \ (NVIC_SetPendingIRQ(DT_INST_IRQ_BY_NAME(n, global, irq))); \ } \ static uint32_t get_irq_pending_##n(void) \ { \ return NVIC_GetPendingIRQ( \ DT_INST_IRQ_BY_NAME(n, global, irq)); \ } #define TIMER_IRQ_CONFIG_ADVANCED(n) \ static void irq_config_##n(const struct device *dev) \ { \ IRQ_CONNECT((DT_INST_IRQ_BY_NAME(n, up, irq)), \ (DT_INST_IRQ_BY_NAME(n, up, priority)), \ irq_handler, (DEVICE_DT_INST_GET(n)), 0); \ irq_enable((DT_INST_IRQ_BY_NAME(n, up, irq))); \ IRQ_CONNECT((DT_INST_IRQ_BY_NAME(n, cc, irq)), \ (DT_INST_IRQ_BY_NAME(n, cc, priority)), \ irq_handler, (DEVICE_DT_INST_GET(n)), 0); \ irq_enable((DT_INST_IRQ_BY_NAME(n, cc, irq))); \ } \ static void set_irq_pending_##n(void) \ { \ (NVIC_SetPendingIRQ(DT_INST_IRQ_BY_NAME(n, cc, irq))); \ } \ static uint32_t get_irq_pending_##n(void) \ { \ return NVIC_GetPendingIRQ(DT_INST_IRQ_BY_NAME(n, cc, irq)); \ } #define GD32_TIMER_INIT(n) \ COND_CODE_1(DT_INST_PROP(n, is_advanced), \ (TIMER_IRQ_CONFIG_ADVANCED(n)), (TIMER_IRQ_CONFIG(n))); \ static struct counter_gd32_data_##n { \ struct counter_gd32_data data; \ struct counter_gd32_ch_data alarm[DT_INST_PROP(n, channels)]; \ } timer_data_##n = {0}; \ static const struct counter_gd32_config timer_config_##n = { \ .counter_info = {.max_top_value = COND_CODE_1( \ DT_INST_PROP(n, is_32bit), \ (UINT32_MAX), (UINT16_MAX)), \ .flags = COUNTER_CONFIG_INFO_COUNT_UP, \ .freq = 0, \ .channels = DT_INST_PROP(n, channels)}, \ .reg = DT_INST_REG_ADDR(n), \ .clkid = DT_INST_CLOCKS_CELL(n, id), \ .reset = RESET_DT_SPEC_INST_GET(n), \ .prescaler = DT_INST_PROP(n, prescaler), \ .irq_config = irq_config_##n, \ .set_irq_pending = set_irq_pending_##n, \ .get_irq_pending = get_irq_pending_##n, \ }; \ \ DEVICE_DT_INST_DEFINE(n, counter_gd32_timer_init, NULL, \ &timer_data_##n, &timer_config_##n, \ PRE_KERNEL_1, CONFIG_COUNTER_INIT_PRIORITY, \ &counter_api); DT_INST_FOREACH_STATUS_OKAY(GD32_TIMER_INIT);