Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions drivers/counter/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ menuconfig COUNTER

if COUNTER

config COUNTER_CAPTURE
bool "Counter capture"
help
Enable support for counter capture. This is a feature that allows
hardware timestamping of external events as well as a software
interrupt on the event. This feature is not supported by all
counter drivers.

config COUNTER_INIT_PRIORITY
int "Counter init priority"
default 60
Expand Down
1 change: 1 addition & 0 deletions drivers/counter/Kconfig.stm32_timer
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ config COUNTER_TIMER_STM32
depends on DT_HAS_ST_STM32_COUNTER_ENABLED
select USE_STM32_LL_TIM
select RESET
select PINCTRL if COUNTER_CAPTURE
help
Enable the counter driver for STM32 family of processors.
142 changes: 133 additions & 9 deletions drivers/counter/counter_ll_stm32_timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <zephyr/drivers/reset.h>
#include <zephyr/irq.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/drivers/pinctrl.h>

#include <stm32_ll_tim.h>
#include <stm32_ll_rcc.h>
Expand Down Expand Up @@ -53,6 +54,14 @@ static uint32_t(*const get_timer_compare[TIMER_MAX_CH])(TIM_TypeDef *) = {
LL_TIM_OC_GetCompareCH3, LL_TIM_OC_GetCompareCH4,
};
#endif

#ifdef CONFIG_COUNTER_CAPTURE
static uint32_t (*const get_timer_capture[TIMER_MAX_CH])(const TIM_TypeDef *) = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/** Channel to timestamp capture function mapping. */

Could you add an empty line before #ifdef ... above and after #endif below?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

LL_TIM_IC_GetCaptureCH1, LL_TIM_IC_GetCaptureCH2,
LL_TIM_IC_GetCaptureCH3, LL_TIM_IC_GetCaptureCH4,
};
#endif /* CONFIG_COUNTER_CAPTURE */

/** Channel to interrupt enable function mapping. */
static void(*const enable_it[TIMER_MAX_CH])(TIM_TypeDef *) = {
LL_TIM_EnableIT_CC1, LL_TIM_EnableIT_CC2,
Expand Down Expand Up @@ -96,6 +105,9 @@ struct counter_stm32_data {

struct counter_stm32_ch_data {
counter_alarm_callback_t callback;
#ifdef CONFIG_COUNTER_CAPTURE
counter_capture_cb_t capture;
#endif /* CONFIG_COUNTER_CAPTURE */
void *user_data;
};

Expand All @@ -110,6 +122,9 @@ struct counter_stm32_config {
uint32_t irqn;
/* Reset controller device configuration */
const struct reset_dt_spec reset;
#ifdef CONFIG_COUNTER_CAPTURE
const struct pinctrl_dev_config *pcfg;
#endif /* CONFIG_COUNTER_CAPTURE */

LOG_INSTANCE_PTR_DECLARE(log);
};
Expand Down Expand Up @@ -412,6 +427,14 @@ static int counter_stm32_init_timer(const struct device *dev)
/* config/enable IRQ */
cfg->irq_config_func(dev);

#ifdef CONFIG_COUNTER_CAPTURE
r = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
if (r < 0) {
LOG_ERR("%s: Counter Capture pinctrl setup failed (%d)", dev->name, r);
return r;
}
#endif /* CONFIG_COUNTER_CAPTURE */

/* initialize timer */
LL_TIM_StructInit(&init);

Expand Down Expand Up @@ -465,29 +488,121 @@ static void counter_stm32_top_irq_handle(const struct device *dev)
cb(dev, data->top_user_data);
}

static void counter_stm32_alarm_irq_handle(const struct device *dev, uint32_t id)
static void counter_stm32_irq_handle(const struct device *dev, uint32_t id)
{
const struct counter_stm32_config *config = dev->config;
struct counter_stm32_data *data = dev->data;
TIM_TypeDef *timer = config->timer;

struct counter_stm32_ch_data *chdata;
counter_alarm_callback_t cb;

atomic_and(&data->cc_int_pending, ~BIT(id));
disable_it[id](timer);

chdata = &config->ch_data[id];
cb = chdata->callback;
chdata->callback = NULL;

if (cb) {
uint32_t cc_val = get_timer_compare[id](timer);
#ifdef CONFIG_COUNTER_CAPTURE
/* With Counter Capture, we need to check which mode it was configured for */
if (LL_TIM_IC_GetActiveInput(timer, LL_TIM_CHANNEL_CH1 << (4 * id)) ==
LL_TIM_ACTIVEINPUT_DIRECTTI) {
counter_capture_cb_t cb = chdata->capture;

/* TODO: check overcapture flag, then print error?
* CC1OF is also set if at least two consecutive captures
* occurred whereas the flag was not cleared
*/

if (cb) {
uint32_t cc_val = get_timer_capture[id](timer);
uint32_t pol = LL_TIM_IC_GetPolarity(timer, LL_TIM_CHANNEL_CH1 << (4 * id));
uint32_t flags = 0;

/* translate stm32 flags to zephyr flags */
if (pol == LL_TIM_IC_POLARITY_RISING) {
flags = COUNTER_CAPTURE_RISING_EDGE;
} else if (pol == LL_TIM_IC_POLARITY_FALLING) {
flags = COUNTER_CAPTURE_FALLING_EDGE;
} else if (pol == LL_TIM_IC_POLARITY_BOTHEDGE) {
flags = COUNTER_CAPTURE_BOTH_EDGES;
}

cb(dev, id, flags, cc_val, chdata->user_data);
}
} else {
#endif
counter_alarm_callback_t cb;

/* Alarm is One-Shot, so disable the Interrupt */
disable_it[id](timer);

cb = chdata->callback;
chdata->callback = NULL;

cb(dev, id, cc_val, chdata->user_data);
if (cb) {
uint32_t cc_val = get_timer_compare[id](timer);

cb(dev, id, cc_val, chdata->user_data);
}
#ifdef CONFIG_COUNTER_CAPTURE
}
#endif
}

#ifdef CONFIG_COUNTER_CAPTURE
static int counter_stm32_capture_enable(const struct device *dev, uint8_t chan)
{
const struct counter_stm32_config *config = dev->config;
TIM_TypeDef *timer = config->timer;

LL_TIM_CC_EnableChannel(timer, LL_TIM_CHANNEL_CH1 << (4 * chan));

return 0;
}

static int counter_stm32_capture_disable(const struct device *dev, uint8_t chan)
{
const struct counter_stm32_config *config = dev->config;
TIM_TypeDef *timer = config->timer;

LL_TIM_CC_DisableChannel(timer, LL_TIM_CHANNEL_CH1 << (4 * chan));

return 0;
}

static int counter_stm32_capture_callback_set(const struct device *dev, uint8_t chan,
uint32_t flags, counter_capture_cb_t cb,
void *user_data)
{
const struct counter_stm32_config *config = dev->config;
struct counter_stm32_ch_data *chdata = &config->ch_data[chan];
TIM_TypeDef *timer = config->timer;
uint32_t config_flags = LL_TIM_ACTIVEINPUT_DIRECTTI;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be worth the call disable_it[chan](timer); here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it shouldn't be disabling the interrupt in the case where it's being used as a counter capture, the alarm is a one-shot alarm just disables it in that case.

Copy link
Contributor

@etienne-lms etienne-lms Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of preventing the interrupt to trigger in between chdata->capture and chdata->user_data update right below.


chdata->capture = cb;
chdata->user_data = user_data;

if ((flags & COUNTER_CAPTURE_BOTH_EDGES) == COUNTER_CAPTURE_BOTH_EDGES) {
config_flags |= LL_TIM_IC_POLARITY_BOTHEDGE;
} else if (flags & COUNTER_CAPTURE_FALLING_EDGE) {
config_flags |= LL_TIM_IC_POLARITY_FALLING;
} else if (flags & COUNTER_CAPTURE_RISING_EDGE) {
config_flags |= LL_TIM_IC_POLARITY_RISING;
}
/* TODO: add 'vendor' config flags for filter, prescaler div */
config_flags |= LL_TIM_ICPSC_DIV1 | LL_TIM_IC_FILTER_FDIV1;

/* Config is only writable when it is off */
LL_TIM_CC_DisableChannel(timer, LL_TIM_CHANNEL_CH1 << (4 * chan));

LL_TIM_IC_Config(timer, LL_TIM_CHANNEL_CH1 << (4 * chan), config_flags);

LL_TIM_CC_EnableChannel(timer, LL_TIM_CHANNEL_CH1 << (4 * chan));
/* enable interrupt */
enable_it[chan](timer);

return 0;
}
#endif

static DEVICE_API(counter, counter_stm32_driver_api) = {
.start = counter_stm32_start,
.stop = counter_stm32_stop,
Expand All @@ -501,6 +616,11 @@ static DEVICE_API(counter, counter_stm32_driver_api) = {
.get_guard_period = counter_stm32_get_guard_period,
.set_guard_period = counter_stm32_set_guard_period,
.get_freq = counter_stm32_get_freq,
#ifdef CONFIG_COUNTER_CAPTURE
.capture_enable = counter_stm32_capture_enable,
.capture_disable = counter_stm32_capture_disable,
.capture_callback_set = counter_stm32_capture_callback_set,
#endif
};

#define TIM_IRQ_HANDLE_CC(timx, cc) \
Expand All @@ -511,7 +631,7 @@ static DEVICE_API(counter, counter_stm32_driver_api) = {
if (hw_irq) { \
LL_TIM_ClearFlag_CC##cc(timer); \
} \
counter_stm32_alarm_irq_handle(dev, cc - 1U); \
counter_stm32_irq_handle(dev, cc - 1U); \
} \
} while (0)

Expand Down Expand Up @@ -562,6 +682,8 @@ void counter_stm32_irq_handler(const struct device *dev)
BUILD_ASSERT(NUM_CH(TIM(idx)) <= TIMER_MAX_CH, \
"TIMER too many channels"); \
\
IF_ENABLED(CONFIG_COUNTER_CAPTURE, (PINCTRL_DT_INST_DEFINE(idx);)) \
\
static struct counter_stm32_data counter##idx##_data; \
static struct counter_stm32_ch_data counter##idx##_ch_data[TIMER_MAX_CH]; \
\
Expand Down Expand Up @@ -593,6 +715,8 @@ void counter_stm32_irq_handler(const struct device *dev)
.irq_config_func = counter_##idx##_stm32_irq_config, \
.irqn = DT_IRQN(TIMER(idx)), \
.reset = RESET_DT_SPEC_GET(TIMER(idx)), \
IF_ENABLED(CONFIG_COUNTER_CAPTURE, \
(.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(idx),)) \
}; \
\
DEVICE_DT_INST_DEFINE(idx, \
Expand Down
2 changes: 1 addition & 1 deletion dts/bindings/counter/st,stm32-counter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ description: STM32 counters

compatible: "st,stm32-counter"

include: base.yaml
include: [base.yaml, pinctrl-device.yaml]
Loading
Loading