| /* |
| * Copyright (c) 2017 - 2018, Nordic Semiconductor ASA |
| * Copyright (c) 2022 TOKITA Hiroshi <tokita.hiroshi@gmail.com> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT gd_gd32_timer |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/clock_control/gd32.h> |
| #include <zephyr/drivers/counter.h> |
| #include <zephyr/drivers/reset.h> |
| #include <zephyr/irq.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/atomic.h> |
| |
| #include <gd32_timer.h> |
| |
| 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); |