blob: ad824d77237833fe7510dba2011dd188a3ff0141 [file] [log] [blame]
/*
* Copyright (c) 2025 Infineon Technologies AG,
* or an affiliate of Infineon Technologies AG.
*
* SPDX-License-Identifier: Apache-2.0
*/
/* Counter driver for Infineon TCPWM peripheral. */
#define DT_DRV_COMPAT infineon_tcpwm_counter
#include <zephyr/drivers/counter.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/timer/ifx_tcpwm.h>
#include <zephyr/dt-bindings/pinctrl/ifx_cat1-pinctrl.h>
#include <zephyr/drivers/clock_control/clock_control_ifx_cat1.h>
#include <zephyr/irq.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(ifx_tcpwm_counter, CONFIG_COUNTER_LOG_LEVEL);
#include <cy_sysclk.h>
#include <cy_tcpwm_counter.h>
struct ifx_tcpwm_counter_config {
struct counter_config_info counter_info;
TCPWM_GRP_CNT_Type *reg_base;
uint32_t index;
bool resolution_32_bits;
IRQn_Type irq_num;
cy_en_divider_types_t divider_type;
uint32_t divider_sel;
uint32_t divider_val;
void (*irq_enable_func)(const struct device *dev);
};
struct ifx_tcpwm_counter_data {
bool alarm_irq_flag;
cy_israddress callback;
/* Timer/counter comparison value */
uint32_t compare_value;
/* Default value of the timer/counter. */
uint32_t value;
struct counter_alarm_cfg alarm_cfg;
struct counter_top_cfg top_value_cfg_counter;
uint32_t guard_period;
struct ifx_cat1_clock clock;
uint8_t clock_peri_group;
};
static const cy_stc_tcpwm_counter_config_t counter_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_COMPARE,
.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,
};
typedef enum {
/* No interrupt handled */
COUNTER_IRQ_NONE = 0,
/* Interrupt when terminal count is reached */
COUNTER_IRQ_TERMINAL_COUNT = 1 << 0,
/* Interrupt when Compare/Capture value is reached */
COUNTER_IRQ_CAPTURE_COMPARE = 1 << 1,
/* Interrupt on terminal count and Compare/Capture values */
COUNTER_IRQ_ALL = (1 << 2) - 1,
} counter_event_t;
static void counter_enable_event(const struct device *dev, counter_event_t event, bool enable)
{
const struct ifx_tcpwm_counter_config *const config = dev->config;
uint32_t savedIntrStatus = Cy_SysLib_EnterCriticalSection();
uint32_t old_mask = IFX_TCPWM_GetInterruptMask(config->reg_base);
uint32_t new_event;
if (enable) {
/* Clear any newly enabled events so that old IRQs don't trigger ISRs */
IFX_TCPWM_ClearInterrupt(config->reg_base, ~old_mask & event);
}
new_event = enable ? (old_mask | event) : (old_mask & ~event);
IFX_TCPWM_SetInterruptMask(config->reg_base, new_event);
Cy_SysLib_ExitCriticalSection(savedIntrStatus);
}
static void counter_isr_handler(const struct device *dev)
{
struct ifx_tcpwm_counter_data *const data = dev->data;
const struct ifx_tcpwm_counter_config *const config = dev->config;
uint32_t pending_int;
pending_int = IFX_TCPWM_GetInterruptStatusMasked(config->reg_base);
IFX_TCPWM_ClearInterrupt(config->reg_base, pending_int);
NVIC_ClearPendingIRQ(config->irq_num);
/* Alarm compare/capture interrupt */
if ((data->alarm_cfg.callback != NULL) &&
(((COUNTER_IRQ_CAPTURE_COMPARE & pending_int) == COUNTER_IRQ_CAPTURE_COMPARE) ||
data->alarm_irq_flag)) {
/* Alarm works as one-shot, so disable interrupt */
counter_enable_event(dev, COUNTER_IRQ_CAPTURE_COMPARE, false);
/* Call User callback for Alarm */
data->alarm_cfg.callback(dev, 1, IFX_TCPWM_Counter_GetCounter(config->reg_base),
data->alarm_cfg.user_data);
data->alarm_irq_flag = false;
}
/* Top_value terminal count interrupt */
if ((data->top_value_cfg_counter.callback != NULL) &&
((COUNTER_IRQ_TERMINAL_COUNT & pending_int) == COUNTER_IRQ_TERMINAL_COUNT)) {
/* Call User callback for top value */
data->top_value_cfg_counter.callback(dev, data->top_value_cfg_counter.user_data);
}
}
static int ifx_tcpwm_counter_init(const struct device *dev)
{
__ASSERT_NO_MSG(dev != NULL);
cy_rslt_t rslt;
struct ifx_tcpwm_counter_data *const data = dev->data;
const struct ifx_tcpwm_counter_config *config = dev->config;
uint32_t clk_connection;
cy_stc_tcpwm_counter_config_t counter_config = counter_default_config;
/* Calculate clock connection based on TCPWM index */
if (config->resolution_32_bits) {
clk_connection = PCLK_TCPWM0_CLOCK_COUNTER_EN0 + config->index;
} else {
clk_connection = PCLK_TCPWM0_CLOCK_COUNTER_EN256 + config->index;
}
/* Configure PWM clock */
Cy_SysClk_PeriPclkDisableDivider((en_clk_dst_t)clk_connection, config->divider_type,
config->divider_sel);
Cy_SysClk_PeriPclkSetDivider((en_clk_dst_t)clk_connection, config->divider_type,
config->divider_sel, config->divider_val);
Cy_SysClk_PeriPclkEnableDivider((en_clk_dst_t)clk_connection, config->divider_type,
config->divider_sel);
Cy_SysClk_PeriPclkAssignDivider(clk_connection, config->divider_type, config->divider_sel);
/* Initialize counter structure */
data->alarm_irq_flag = false;
data->top_value_cfg_counter.ticks = config->counter_info.max_top_value;
data->compare_value = 0;
data->value = 0;
/* Configure timer */
counter_config.period = data->top_value_cfg_counter.ticks;
counter_config.compare0 = data->compare_value;
/* DeInit will clear the interrupt mask; save it now and restore after we re-nit */
uint32_t old_mask = IFX_TCPWM_GetInterruptMask(config->reg_base);
IFX_TCPWM_Counter_DeInit(config->reg_base, &counter_config);
rslt = (cy_rslt_t)IFX_TCPWM_Counter_Init(config->reg_base, &counter_config);
if (rslt != CY_RSLT_SUCCESS) {
return -EIO;
}
IFX_TCPWM_Counter_Enable(config->reg_base);
IFX_TCPWM_SetInterruptMask(config->reg_base, old_mask);
/* This must be called after IFX_TCPWM_Counter_Init */
IFX_TCPWM_Counter_SetCounter(config->reg_base, data->value);
/* enable the counter interrupt */
config->irq_enable_func(dev);
return 0;
}
static int ifx_tcpwm_counter_start(const struct device *dev)
{
__ASSERT_NO_MSG(dev != NULL);
const struct ifx_tcpwm_counter_config *config = dev->config;
IFX_TCPWM_Counter_Enable(config->reg_base);
IFX_TCPWM_TriggerStart_Single(config->reg_base);
return 0;
}
static int ifx_tcpwm_counter_stop(const struct device *dev)
{
__ASSERT_NO_MSG(dev != NULL);
const struct ifx_tcpwm_counter_config *config = dev->config;
IFX_TCPWM_Counter_Disable(config->reg_base);
return 0;
}
static int ifx_tcpwm_counter_get_value(const struct device *dev, uint32_t *ticks)
{
__ASSERT_NO_MSG(dev != NULL);
__ASSERT_NO_MSG(ticks != NULL);
const struct ifx_tcpwm_counter_config *config = dev->config;
*ticks = IFX_TCPWM_Counter_GetCounter(config->reg_base);
return 0;
}
static int ifx_tcpwm_counter_set_top_value(const struct device *dev,
const struct counter_top_cfg *cfg)
{
__ASSERT_NO_MSG(dev != NULL);
__ASSERT_NO_MSG(cfg != NULL);
struct ifx_tcpwm_counter_data *const data = dev->data;
const struct ifx_tcpwm_counter_config *const config = dev->config;
data->top_value_cfg_counter = *cfg;
/* Check new top value limit */
if (cfg->ticks > config->counter_info.max_top_value) {
return -ENOTSUP;
}
/* Checks if new period value is not less then old period value */
if (!(cfg->flags & COUNTER_TOP_CFG_DONT_RESET)) {
data->value = 0u;
} else {
/* timer_configure resets timer counter register to value
* defined in config structure 'data->value', so update
* counter value with current value of counter (read by
* IFX_TCPWM_Counter_GetCounter function).
*/
data->value = IFX_TCPWM_Counter_GetCounter(config->reg_base);
}
IFX_TCPWM_Block_SetPeriod(config->reg_base, cfg->ticks);
/* Register an top_value terminal count event callback handler if
* callback is not NULL.
*/
if (cfg->callback != NULL) {
counter_enable_event(dev, COUNTER_IRQ_TERMINAL_COUNT, true);
}
return 0;
}
static uint32_t ifx_tcpwm_counter_get_top_value(const struct device *dev)
{
__ASSERT_NO_MSG(dev != NULL);
struct ifx_tcpwm_counter_data *const data = dev->data;
return data->top_value_cfg_counter.ticks;
}
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 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 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_tcpwm_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_tcpwm_counter_data *const data = dev->data;
const struct ifx_tcpwm_counter_config *const config = dev->config;
uint32_t compare_value = alarm_cfg->ticks;
uint32_t top_val = ifx_tcpwm_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;
data->alarm_cfg = *alarm_cfg;
/* 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 = compare_value < (top_val / 2U);
/* limit max to detect short relative being set too late. */
max_rel_val = irq_on_late ? (top_val / 2U) : top_val;
compare_value = counter_ticks_add(IFX_TCPWM_Counter_GetCounter(config->reg_base),
compare_value, top_val);
}
/* Decrement value to detect the case when compare_value == counter_read(dev). Otherwise,
* condition would need to include comparing diff against 0.
*/
uint32_t curr = IFX_TCPWM_Counter_GetCounter(config->reg_base);
uint32_t diff = counter_ticks_sub((compare_value - 1), curr, top_val);
if ((absolute && (compare_value < 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;
counter_enable_event(dev, COUNTER_IRQ_CAPTURE_COMPARE, true);
IFX_TCPWM_SetInterrupt(config->reg_base, COUNTER_IRQ_CAPTURE_COMPARE);
}
if (absolute) {
return -ETIME;
}
} else {
/* Setting new compare value
*
* timer_configure resets timer counter register to value
* defined in config structure 'data->value', so update
* counter value with current value of counter (read by
* IFX_TCPWM_Counter_GetCounter function).
*/
data->value = IFX_TCPWM_Counter_GetCounter(config->reg_base);
data->compare_value = compare_value;
/* Reconfigure timer */
IFX_TCPWM_Block_SetCC0Val(config->reg_base, compare_value);
counter_enable_event(dev, COUNTER_IRQ_CAPTURE_COMPARE, true);
}
return 0;
}
static int ifx_tcpwm_counter_cancel_alarm(const struct device *dev, uint8_t chan_id)
{
ARG_UNUSED(chan_id);
__ASSERT_NO_MSG(dev != NULL);
counter_enable_event(dev, COUNTER_IRQ_CAPTURE_COMPARE, false);
return 0;
}
static uint32_t ifx_tcpwm_counter_get_pending_int(const struct device *dev)
{
__ASSERT_NO_MSG(dev != NULL);
const struct ifx_tcpwm_counter_config *const config = dev->config;
return NVIC_GetPendingIRQ(config->irq_num);
}
static uint32_t ifx_tcpwm_counter_get_guard_period(const struct device *dev, uint32_t flags)
{
ARG_UNUSED(flags);
__ASSERT_NO_MSG(dev != NULL);
struct ifx_tcpwm_counter_data *const data = dev->data;
return data->guard_period;
}
static int ifx_tcpwm_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_tcpwm_counter_get_top_value(dev));
struct ifx_tcpwm_counter_data *const data = dev->data;
data->guard_period = guard;
return 0;
}
static DEVICE_API(counter, counter_api) = {
.start = ifx_tcpwm_counter_start,
.stop = ifx_tcpwm_counter_stop,
.get_value = ifx_tcpwm_counter_get_value,
.set_alarm = ifx_tcpwm_counter_set_alarm,
.cancel_alarm = ifx_tcpwm_counter_cancel_alarm,
.set_top_value = ifx_tcpwm_counter_set_top_value,
.get_pending_int = ifx_tcpwm_counter_get_pending_int,
.get_top_value = ifx_tcpwm_counter_get_top_value,
.get_guard_period = ifx_tcpwm_counter_get_guard_period,
.set_guard_period = ifx_tcpwm_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_TCPWM_COUNTER_INIT(n) \
\
static void ifx_cat1_spi_irq_enable_func_##n(const struct device *dev) \
{ \
IRQ_CONNECT(DT_IRQN(DT_INST_PARENT(n)), DT_IRQ(DT_INST_PARENT(n), priority), \
counter_isr_handler, DEVICE_DT_INST_GET(n), 0); \
irq_enable(DT_IRQN(DT_INST_PARENT(n))); \
} \
\
static struct ifx_tcpwm_counter_data ifx_tcpwm_counter##n##_data = { \
.clock = \
{ \
.block = IFX_CAT1_PERIPHERAL_GROUP_ADJUST( \
DT_PROP_BY_IDX(DT_INST_PHANDLE(n, clocks), clk_dst, 1), \
DT_INST_PROP_BY_PHANDLE(n, clocks, div_type)), \
.channel = DT_INST_PROP_BY_PHANDLE(n, clocks, div_num), \
}, \
.clock_peri_group = DT_PROP_BY_IDX(DT_INST_PHANDLE(n, clocks), clk_dst, 1), \
}; \
\
static const struct ifx_tcpwm_counter_config ifx_tcpwm_counter##n##_config = { \
.counter_info = {.max_top_value = (DT_PROP(DT_INST_PARENT(n), resolution) == 32) \
? UINT32_MAX \
: UINT16_MAX, \
.freq = DT_INST_PROP(n, clock_frequency), \
.flags = COUNTER_CONFIG_INFO_COUNT_UP, \
.channels = 1}, \
.reg_base = (TCPWM_GRP_CNT_Type *)DT_REG_ADDR(DT_INST_PARENT(n)), \
.index = (DT_REG_ADDR(DT_INST_PARENT(n)) - \
DT_REG_ADDR(DT_PARENT(DT_INST_PARENT(n)))) / \
DT_REG_SIZE(DT_INST_PARENT(n)), \
.irq_num = DT_IRQN(DT_INST_PARENT(n)), \
.resolution_32_bits = \
(DT_PROP(DT_INST_PARENT(n), resolution) == 32) ? true : false, \
.divider_type = DT_PROP(DT_INST_PARENT(n), divider_type), \
.divider_sel = DT_PROP(DT_INST_PARENT(n), divider_sel), \
.divider_val = DT_PROP(DT_INST_PARENT(n), divider_val), \
.irq_enable_func = ifx_cat1_spi_irq_enable_func_##n, \
}; \
\
DEVICE_DT_INST_DEFINE(n, ifx_tcpwm_counter_init, NULL, &ifx_tcpwm_counter##n##_data, \
&ifx_tcpwm_counter##n##_config, PRE_KERNEL_1, \
CONFIG_COUNTER_INIT_PRIORITY, &counter_api);
DT_INST_FOREACH_STATUS_OKAY(INFINEON_TCPWM_COUNTER_INIT);