| /* |
| * Copyright (c) 2023 Cypress Semiconductor Corporation (an Infineon company) or |
| * an affiliate of Cypress Semiconductor Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @brief Counter driver for Infineon CAT1 MCU family. |
| */ |
| |
| #define DT_DRV_COMPAT infineon_cat1_counter |
| |
| #include <zephyr/drivers/counter.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <cyhal_timer.h> |
| #include <cyhal_gpio.h> |
| |
| #include <cyhal_tcpwm_common.h> |
| #include <zephyr/dt-bindings/pinctrl/ifx_cat1-pinctrl.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(ifx_cat1_counter, CONFIG_COUNTER_LOG_LEVEL); |
| |
| struct ifx_cat1_counter_config { |
| struct counter_config_info counter_info; |
| TCPWM_CNT_Type *reg_addr; |
| cyhal_gpio_t external_pin; |
| IRQn_Type irqn; |
| uint8_t irq_priority; |
| }; |
| |
| struct ifx_cat1_counter_data { |
| cyhal_timer_t counter_obj; |
| cyhal_timer_cfg_t counter_cfg; |
| struct counter_alarm_cfg alarm_cfg_counter; |
| struct counter_top_cfg top_value_cfg_counter; |
| uint32_t guard_period; |
| cyhal_resource_inst_t hw_resource; |
| cyhal_source_t signal_source; |
| bool alarm_irq_flag; |
| }; |
| |
| static const cy_stc_tcpwm_counter_config_t cyhal_timer_default_config = { |
| .period = 32768, |
| .clockPrescaler = CY_TCPWM_COUNTER_PRESCALER_DIVBY_1, |
| .runMode = CY_TCPWM_COUNTER_CONTINUOUS, |
| .countDirection = CY_TCPWM_COUNTER_COUNT_UP, |
| .compareOrCapture = CY_TCPWM_COUNTER_MODE_CAPTURE, |
| .compare0 = 16384, |
| .compare1 = 16384, |
| .enableCompareSwap = false, |
| .interruptSources = CY_TCPWM_INT_NONE, |
| .captureInputMode = 0x3U, |
| .captureInput = CY_TCPWM_INPUT_0, |
| .reloadInputMode = 0x3U, |
| .reloadInput = CY_TCPWM_INPUT_0, |
| .startInputMode = 0x3U, |
| .startInput = CY_TCPWM_INPUT_0, |
| .stopInputMode = 0x3U, |
| .stopInput = CY_TCPWM_INPUT_0, |
| .countInputMode = 0x3U, |
| .countInput = CY_TCPWM_INPUT_1, |
| }; |
| |
| static int get_hw_block_info(TCPWM_CNT_Type *reg_addr, cyhal_resource_inst_t *hw_resource) |
| { |
| uint32_t i; |
| |
| for (i = 0u; i < _CYHAL_TCPWM_INSTANCES; i++) { |
| uintptr_t base = POINTER_TO_UINT(_CYHAL_TCPWM_DATA[i].base); |
| uintptr_t cnt = POINTER_TO_UINT(_CYHAL_TCPWM_DATA[i].base->CNT); |
| uintptr_t reg_addr_ptr = POINTER_TO_UINT(reg_addr); |
| uintptr_t end_addr = base + sizeof(TCPWM_Type); |
| |
| if ((reg_addr_ptr > base) && (reg_addr_ptr < end_addr)) { |
| |
| hw_resource->type = CYHAL_RSC_TCPWM; |
| hw_resource->block_num = i; |
| hw_resource->channel_num = ((reg_addr_ptr - cnt) / sizeof(TCPWM_CNT_Type)); |
| |
| if (hw_resource->channel_num >= _CYHAL_TCPWM_DATA[i].num_channels) { |
| return -EINVAL; |
| } |
| return 0; |
| } |
| } |
| return -EINVAL; |
| } |
| |
| static void ifx_cat1_counter_event_callback(void *callback_arg, cyhal_timer_event_t event) |
| { |
| const struct device *dev = (const struct device *)callback_arg; |
| struct ifx_cat1_counter_data *const data = dev->data; |
| const struct ifx_cat1_counter_config *const config = dev->config; |
| |
| /* Alarm compare/capture event */ |
| if ((data->alarm_cfg_counter.callback != NULL) && |
| (((CYHAL_TIMER_IRQ_CAPTURE_COMPARE & event) == CYHAL_TIMER_IRQ_CAPTURE_COMPARE) || |
| data->alarm_irq_flag)) { |
| /* Alarm works as one-shot, so disable event */ |
| cyhal_timer_enable_event(&data->counter_obj, CYHAL_TIMER_IRQ_CAPTURE_COMPARE, |
| config->irq_priority, false); |
| |
| /* Call User callback for Alarm */ |
| data->alarm_cfg_counter.callback(dev, 1, cyhal_timer_read(&data->counter_obj), |
| data->alarm_cfg_counter.user_data); |
| data->alarm_irq_flag = false; |
| } |
| |
| /* Top_value terminal count event */ |
| if ((data->top_value_cfg_counter.callback != NULL) && |
| ((CYHAL_TIMER_IRQ_TERMINAL_COUNT & event) == CYHAL_TIMER_IRQ_TERMINAL_COUNT)) { |
| |
| /* Call User callback for top value */ |
| data->top_value_cfg_counter.callback(dev, data->top_value_cfg_counter.user_data); |
| } |
| |
| /* NOTE: cyhal handles cleaning of interrupts */ |
| } |
| |
| static void ifx_cat1_counter_set_int_pending(const struct device *dev) |
| { |
| __ASSERT_NO_MSG(dev != NULL); |
| |
| struct ifx_cat1_counter_data *const data = dev->data; |
| const struct ifx_cat1_counter_config *const config = dev->config; |
| |
| cyhal_timer_enable_event(&data->counter_obj, CYHAL_TIMER_IRQ_CAPTURE_COMPARE, |
| config->irq_priority, true); |
| Cy_TCPWM_SetInterrupt(data->counter_obj.tcpwm.base, |
| _CYHAL_TCPWM_CNT_NUMBER(data->counter_obj.tcpwm.resource), |
| CY_TCPWM_INT_ON_CC0); |
| } |
| |
| static int ifx_cat1_counter_init(const struct device *dev) |
| { |
| __ASSERT_NO_MSG(dev != NULL); |
| |
| cy_rslt_t rslt; |
| struct ifx_cat1_counter_data *data = dev->data; |
| const struct ifx_cat1_counter_config *config = dev->config; |
| |
| /* Dedicate Counter HW resource */ |
| if (get_hw_block_info(config->reg_addr, &data->hw_resource) != 0) { |
| return -EIO; |
| } |
| |
| cyhal_timer_configurator_t timer_configurator = { |
| .resource = &data->hw_resource, |
| .config = &cyhal_timer_default_config, |
| }; |
| |
| /* Initialize timer */ |
| rslt = cyhal_timer_init_cfg(&data->counter_obj, &timer_configurator); |
| if (rslt != CY_RSLT_SUCCESS) { |
| return -EIO; |
| } |
| |
| /* Initialize counter structure */ |
| data->alarm_irq_flag = false; |
| data->counter_cfg.compare_value = 0; |
| data->counter_cfg.period = config->counter_info.max_top_value; |
| data->counter_cfg.direction = CYHAL_TIMER_DIR_UP; |
| data->counter_cfg.is_compare = true; |
| data->counter_cfg.is_continuous = true; |
| data->counter_cfg.value = 0; |
| |
| /* Configure timer */ |
| rslt = cyhal_timer_configure(&data->counter_obj, &data->counter_cfg); |
| if (rslt != CY_RSLT_SUCCESS) { |
| return -EIO; |
| } |
| |
| if (config->external_pin == NC) { |
| /* Configure frequency */ |
| rslt = cyhal_timer_set_frequency(&data->counter_obj, config->counter_info.freq); |
| if (rslt != CY_RSLT_SUCCESS) { |
| return -EIO; |
| } |
| } else { |
| rslt = cyhal_gpio_init(config->external_pin, CYHAL_GPIO_DIR_INPUT, |
| CYHAL_GPIO_DRIVE_NONE, 0); |
| if (rslt != CY_RSLT_SUCCESS) { |
| LOG_ERR("External pin configuration error"); |
| return -EIO; |
| } |
| |
| rslt = cyhal_gpio_enable_output(config->external_pin, CYHAL_SIGNAL_TYPE_EDGE, |
| (cyhal_source_t *)&data->signal_source); |
| if (rslt != CY_RSLT_SUCCESS) { |
| if (rslt != CY_RSLT_SUCCESS) { |
| LOG_ERR("error in the enabling of Counter input pin output"); |
| return -EIO; |
| } |
| } |
| |
| rslt = cyhal_timer_connect_digital(&data->counter_obj, data->signal_source, |
| CYHAL_TIMER_INPUT_COUNT); |
| if (rslt != CY_RSLT_SUCCESS) { |
| LOG_ERR("Error connecting signal source"); |
| return -EIO; |
| } |
| } |
| |
| /* Register timer event callback */ |
| cyhal_timer_register_callback(&data->counter_obj, ifx_cat1_counter_event_callback, |
| (void *)dev); |
| |
| return 0; |
| } |
| |
| static int ifx_cat1_counter_start(const struct device *dev) |
| { |
| __ASSERT_NO_MSG(dev != NULL); |
| |
| struct ifx_cat1_counter_data *const data = dev->data; |
| |
| if (cyhal_timer_start(&data->counter_obj) != CY_RSLT_SUCCESS) { |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| static int ifx_cat1_counter_stop(const struct device *dev) |
| { |
| __ASSERT_NO_MSG(dev != NULL); |
| |
| struct ifx_cat1_counter_data *const data = dev->data; |
| |
| if (cyhal_timer_stop(&data->counter_obj) != CY_RSLT_SUCCESS) { |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| static int ifx_cat1_counter_get_value(const struct device *dev, uint32_t *ticks) |
| { |
| __ASSERT_NO_MSG(dev != NULL); |
| __ASSERT_NO_MSG(ticks != NULL); |
| |
| struct ifx_cat1_counter_data *const data = dev->data; |
| |
| *ticks = cyhal_timer_read(&data->counter_obj); |
| |
| return 0; |
| } |
| |
| static int ifx_cat1_counter_set_top_value(const struct device *dev, |
| const struct counter_top_cfg *cfg) |
| { |
| __ASSERT_NO_MSG(dev != NULL); |
| __ASSERT_NO_MSG(cfg != NULL); |
| |
| cy_rslt_t rslt; |
| struct ifx_cat1_counter_data *const data = dev->data; |
| const struct ifx_cat1_counter_config *const config = dev->config; |
| bool ticks_gt_period; |
| |
| data->top_value_cfg_counter = *cfg; |
| data->counter_cfg.period = cfg->ticks; |
| |
| /* Check new top value limit */ |
| if (cfg->ticks > config->counter_info.max_top_value) { |
| return -ENOTSUP; |
| } |
| |
| ticks_gt_period = cfg->ticks > data->counter_cfg.period; |
| /* Checks if new period value is not less then old period value */ |
| if (!(cfg->flags & COUNTER_TOP_CFG_DONT_RESET)) { |
| data->counter_cfg.value = 0u; |
| } else if (ticks_gt_period && (cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE)) { |
| data->counter_cfg.value = 0u; |
| } else { |
| /* cyhal_timer_configure resets timer counter register to value |
| * defined in config structure 'counter_cfg.value', so update |
| * counter value with current value of counter (read by |
| * cyhal_timer_read function). |
| */ |
| data->counter_cfg.value = cyhal_timer_read(&data->counter_obj); |
| } |
| |
| if ((ticks_gt_period == false) || |
| ((ticks_gt_period == true) && (cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE))) { |
| |
| /* Reconfigure timer */ |
| if (config->external_pin == NC) { |
| rslt = cyhal_timer_configure(&data->counter_obj, &data->counter_cfg); |
| if (rslt != CY_RSLT_SUCCESS) { |
| return -EIO; |
| } |
| } else { |
| TCPWM_CNT_PERIOD(data->counter_obj.tcpwm.base, |
| _CYHAL_TCPWM_CNT_NUMBER( |
| data->counter_obj.tcpwm.resource)) = cfg->ticks; |
| } |
| |
| /* Register an top_value terminal count event callback handler if |
| * callback is not NULL. |
| */ |
| if (cfg->callback != NULL) { |
| cyhal_timer_enable_event(&data->counter_obj, CYHAL_TIMER_IRQ_TERMINAL_COUNT, |
| config->irq_priority, true); |
| } |
| } |
| return 0; |
| } |
| |
| static uint32_t ifx_cat1_counter_get_top_value(const struct device *dev) |
| { |
| __ASSERT_NO_MSG(dev != NULL); |
| |
| struct ifx_cat1_counter_data *const data = dev->data; |
| |
| return data->counter_cfg.period; |
| } |
| |
| static inline bool counter_is_bit_mask(uint32_t val) |
| { |
| /* Return true if value equals 2^n - 1 */ |
| return !(val & (val + 1U)); |
| } |
| |
| static uint32_t ifx_cat1_counter_ticks_add(uint32_t val1, uint32_t val2, uint32_t top) |
| { |
| uint32_t to_top; |
| |
| /* refer to https://tbrindus.ca/how-builtin-expect-works/ for 'likely' usage */ |
| if (likely(counter_is_bit_mask(top))) { |
| return (val1 + val2) & top; |
| } |
| |
| to_top = top - val1; |
| |
| return (val2 <= to_top) ? (val1 + val2) : (val2 - to_top - 1U); |
| } |
| |
| static uint32_t ifx_cat1_counter_ticks_sub(uint32_t val, uint32_t old, uint32_t top) |
| { |
| /* refer to https://tbrindus.ca/how-builtin-expect-works/ for 'likely' usage */ |
| if (likely(counter_is_bit_mask(top))) { |
| return (val - old) & top; |
| } |
| |
| /* if top is not 2^n-1 */ |
| return (val >= old) ? (val - old) : (val + top + 1U - old); |
| } |
| |
| static int ifx_cat1_counter_set_alarm(const struct device *dev, uint8_t chan_id, |
| const struct counter_alarm_cfg *alarm_cfg) |
| { |
| ARG_UNUSED(chan_id); |
| __ASSERT_NO_MSG(dev != NULL); |
| __ASSERT_NO_MSG(alarm_cfg != NULL); |
| |
| struct ifx_cat1_counter_data *const data = dev->data; |
| const struct ifx_cat1_counter_config *const config = dev->config; |
| |
| uint32_t val = alarm_cfg->ticks; |
| uint32_t top_val = ifx_cat1_counter_get_top_value(dev); |
| uint32_t flags = alarm_cfg->flags; |
| uint32_t max_rel_val; |
| bool absolute = ((flags & COUNTER_ALARM_CFG_ABSOLUTE) == 0) ? false : true; |
| bool irq_on_late; |
| |
| /* Checks if compare value is not less then period value */ |
| if (alarm_cfg->ticks > top_val) { |
| return -EINVAL; |
| } |
| |
| if (absolute) { |
| max_rel_val = top_val - data->guard_period; |
| irq_on_late = ((flags & COUNTER_ALARM_CFG_EXPIRE_WHEN_LATE) == 0) ? false : true; |
| } 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_val / 2U); |
| |
| /* limit max to detect short relative being set too late. */ |
| max_rel_val = irq_on_late ? (top_val / 2U) : top_val; |
| val = ifx_cat1_counter_ticks_add(cyhal_timer_read(&data->counter_obj), val, |
| top_val); |
| } |
| |
| /* Decrement value to detect also case when val == counter_read(dev). Otherwise, |
| * condition would need to include comparing diff against 0. |
| */ |
| uint32_t curr = cyhal_timer_read(&data->counter_obj); |
| uint32_t diff = ifx_cat1_counter_ticks_sub((val - 1), curr, top_val); |
| |
| if ((absolute && (val < curr)) || (diff > max_rel_val)) { |
| |
| /* Interrupt is triggered always for relative alarm and for absolute depending |
| * on the flag. |
| */ |
| if (irq_on_late) { |
| data->alarm_irq_flag = true; |
| ifx_cat1_counter_set_int_pending(dev); |
| } |
| |
| if (absolute) { |
| return -ETIME; |
| } |
| } else { |
| /* Setting new compare value */ |
| cy_rslt_t rslt; |
| |
| data->alarm_cfg_counter = *alarm_cfg; |
| data->counter_cfg.compare_value = val; |
| |
| /* cyhal_timer_configure resets timer counter register to value |
| * defined in config structure 'counter_cfg.value', so update |
| * counter value with current value of counter (read by |
| * cyhal_timer_read function). |
| */ |
| data->counter_cfg.value = cyhal_timer_read(&data->counter_obj); |
| |
| /* Reconfigure timer */ |
| if (config->external_pin == NC) { |
| rslt = cyhal_timer_configure(&data->counter_obj, &data->counter_cfg); |
| if (rslt != CY_RSLT_SUCCESS) { |
| return -EINVAL; |
| } |
| } else { |
| TCPWM_CNT_CC(data->counter_obj.tcpwm.base, |
| _CYHAL_TCPWM_CNT_NUMBER(data->counter_obj.tcpwm.resource)) = |
| data->counter_cfg.compare_value; |
| } |
| |
| cyhal_timer_enable_event(&data->counter_obj, CYHAL_TIMER_IRQ_CAPTURE_COMPARE, |
| config->irq_priority, true); |
| } |
| |
| return 0; |
| } |
| |
| static int ifx_cat1_counter_cancel_alarm(const struct device *dev, uint8_t chan_id) |
| { |
| ARG_UNUSED(chan_id); |
| __ASSERT_NO_MSG(dev != NULL); |
| |
| struct ifx_cat1_counter_data *const data = dev->data; |
| const struct ifx_cat1_counter_config *const config = dev->config; |
| |
| cyhal_timer_enable_event(&data->counter_obj, CYHAL_TIMER_IRQ_CAPTURE_COMPARE, |
| config->irq_priority, false); |
| return 0; |
| } |
| |
| static uint32_t ifx_cat1_counter_get_pending_int(const struct device *dev) |
| { |
| __ASSERT_NO_MSG(dev != NULL); |
| |
| const struct ifx_cat1_counter_config *const config = dev->config; |
| |
| return NVIC_GetPendingIRQ(config->irqn); |
| } |
| |
| static uint32_t ifx_cat1_counter_get_guard_period(const struct device *dev, uint32_t flags) |
| { |
| ARG_UNUSED(flags); |
| __ASSERT_NO_MSG(dev != NULL); |
| |
| struct ifx_cat1_counter_data *const data = dev->data; |
| |
| return data->guard_period; |
| } |
| |
| static int ifx_cat1_counter_set_guard_period(const struct device *dev, uint32_t guard, |
| uint32_t flags) |
| { |
| ARG_UNUSED(flags); |
| __ASSERT_NO_MSG(dev != NULL); |
| __ASSERT_NO_MSG(guard < ifx_cat1_counter_get_top_value(dev)); |
| |
| struct ifx_cat1_counter_data *const data = dev->data; |
| |
| data->guard_period = guard; |
| return 0; |
| } |
| |
| static const struct counter_driver_api counter_api = { |
| .start = ifx_cat1_counter_start, |
| .stop = ifx_cat1_counter_stop, |
| .get_value = ifx_cat1_counter_get_value, |
| .set_alarm = ifx_cat1_counter_set_alarm, |
| .cancel_alarm = ifx_cat1_counter_cancel_alarm, |
| .set_top_value = ifx_cat1_counter_set_top_value, |
| .get_pending_int = ifx_cat1_counter_get_pending_int, |
| .get_top_value = ifx_cat1_counter_get_top_value, |
| .get_guard_period = ifx_cat1_counter_get_guard_period, |
| .set_guard_period = ifx_cat1_counter_set_guard_period, |
| }; |
| |
| #define DT_INST_GET_CYHAL_GPIO_OR(inst, gpios_prop, default) \ |
| COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, gpios_prop), \ |
| (DT_GET_CYHAL_GPIO_FROM_DT_GPIOS(DT_INST(inst, DT_DRV_COMPAT), gpios_prop)), \ |
| (default)) |
| |
| /* Counter driver init macros */ |
| #define INFINEON_CAT1_COUNTER_INIT(n) \ |
| \ |
| static struct ifx_cat1_counter_data ifx_cat1_counter##n##_data; \ |
| \ |
| static const struct ifx_cat1_counter_config ifx_cat1_counter##n##_config = { \ |
| .counter_info = {.max_top_value = (DT_INST_PROP(n, resolution) == 32) \ |
| ? UINT32_MAX \ |
| : UINT16_MAX, \ |
| .freq = DT_INST_PROP_OR(n, clock_frequency, 10000), \ |
| .flags = COUNTER_CONFIG_INFO_COUNT_UP, \ |
| .channels = 1}, \ |
| .reg_addr = (TCPWM_CNT_Type *)DT_INST_REG_ADDR(n), \ |
| .irq_priority = DT_INST_IRQ(n, priority), \ |
| .irqn = DT_INST_IRQN(n), \ |
| .external_pin = \ |
| (cyhal_gpio_t)DT_INST_GET_CYHAL_GPIO_OR(n, external_trigger_gpios, NC)}; \ |
| DEVICE_DT_INST_DEFINE(n, ifx_cat1_counter_init, NULL, &ifx_cat1_counter##n##_data, \ |
| &ifx_cat1_counter##n##_config, PRE_KERNEL_1, \ |
| CONFIG_COUNTER_INIT_PRIORITY, &counter_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(INFINEON_CAT1_COUNTER_INIT); |