diff --git a/drivers/counter/counter_mcux_lptmr.c b/drivers/counter/counter_mcux_lptmr.c index 3f55a8601d3fe..117dffc245a95 100644 --- a/drivers/counter/counter_mcux_lptmr.c +++ b/drivers/counter/counter_mcux_lptmr.c @@ -12,6 +12,8 @@ #include #include +#define NUM_CHANNELS 1 + struct mcux_lptmr_config { struct counter_config_info info; LPTMR_Type *base; @@ -22,11 +24,15 @@ struct mcux_lptmr_config { lptmr_pin_select_t pin; lptmr_pin_polarity_t polarity; void (*irq_config_func)(const struct device *dev); + unsigned int irqn; }; struct mcux_lptmr_data { counter_top_callback_t top_callback; + counter_alarm_callback_t alarm_callback; void *top_user_data; + void *alarm_user_data; + uint32_t guard_period; }; static int mcux_lptmr_start(const struct device *dev) @@ -118,11 +124,132 @@ static void mcux_lptmr_isr(const struct device *dev) if (data->top_callback) { data->top_callback(dev, data->top_user_data); } + + if (data->alarm_callback) { + uint32_t ticks = LPTMR_GetCurrentTimerCount(config->base); + counter_alarm_callback_t alarm_callback = data->alarm_callback; + void *alarm_user_data = data->alarm_user_data; + + data->alarm_callback = NULL; + data->alarm_user_data = NULL; + alarm_callback(dev, 0, ticks, alarm_user_data); + } +} + +static int mcux_lptmr_set_guard_period(const struct device *dev, uint32_t guard, uint32_t flags) +{ + struct mcux_lptmr_data *data = dev->data; + + ARG_UNUSED(flags); + + __ASSERT_NO_MSG(guard < mcux_lptmr_get_top_value(dev)); + data->guard_period = guard; + + return 0; +} + +static uint32_t mcux_lptmr_get_guard_period(const struct device *dev, uint32_t flags) +{ + struct mcux_lptmr_data *data = dev->data; + + ARG_UNUSED(flags); + + return data->guard_period; +} + +static int mcux_lptmr_set_alarm(const struct device *dev, uint8_t chan_id, + const struct counter_alarm_cfg *alarm_cfg) +{ + const struct mcux_lptmr_config *config = dev->config; + struct mcux_lptmr_data *data = dev->data; + uint32_t current; + uint32_t max_rel_val; + bool irq_on_late = false; + uint32_t ticks = alarm_cfg->ticks; + bool absolute = alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE; + int err = 0; + uint32_t top = mcux_lptmr_get_top_value(dev); + + if (alarm_cfg->ticks > top) { + return -EINVAL; + } + + if (chan_id != 0) { + return -EINVAL; + } + + if (data->alarm_callback) { + return -EBUSY; + } + + data->alarm_callback = alarm_cfg->callback; + data->alarm_user_data = alarm_cfg->user_data; + + current = LPTMR_GetCurrentTimerCount(config->base); + max_rel_val = (current + data->guard_period) % config->info.max_top_value; + + if (absolute == 0) { + /* + * 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 CMP and CNT. + * + * Note that half of counter range is an arbitrary value. + */ + irq_on_late = ticks < (config->info.max_top_value / 2); + ticks = (ticks + current) % config->info.max_top_value; + } else { + irq_on_late = alarm_cfg->flags & COUNTER_ALARM_CFG_EXPIRE_WHEN_LATE; + ticks = alarm_cfg->ticks % config->info.max_top_value; + } + + /* + * When LPTMR is enabled, the CMR can be altered only when CSR[TCF] = 1, since this cannot + * be guaranteed, the timer must be stopped before setting the CMR. + */ + LPTMR_StopTimer(config->base); + + LPTMR_SetTimerPeriod(config->base, ticks); + LPTMR_EnableInterrupts(config->base, kLPTMR_TimerInterruptEnable); + + LPTMR_StartTimer(config->base); + + if (ticks <= max_rel_val) { + if (absolute) { + err = -ETIME; + } + + if (irq_on_late) { + NVIC_SetPendingIRQ(config->irqn); + } else { + data->alarm_callback = NULL; + } + } + + return err; +} + +static int mcux_lptmr_cancel_alarm(const struct device *dev, uint8_t chan_id) +{ + const struct mcux_lptmr_config *config = dev->config; + struct mcux_lptmr_data *data = dev->data; + + if (chan_id != 0) { + return -EINVAL; + } + + LPTMR_DisableInterrupts(config->base, kLPTMR_TimerInterruptEnable); + data->alarm_callback = NULL; + + return 0; } static int mcux_lptmr_init(const struct device *dev) { const struct mcux_lptmr_config *config = dev->config; + struct mcux_lptmr_data *data = dev->data; lptmr_config_t lptmr_config; LPTMR_GetDefaultConfig(&lptmr_config); @@ -143,6 +270,8 @@ static int mcux_lptmr_init(const struct device *dev) config->irq_config_func(dev); + data->guard_period = 0; + return 0; } @@ -153,6 +282,10 @@ static DEVICE_API(counter, mcux_lptmr_driver_api) = { .set_top_value = mcux_lptmr_set_top_value, .get_pending_int = mcux_lptmr_get_pending_int, .get_top_value = mcux_lptmr_get_top_value, + .set_alarm = mcux_lptmr_set_alarm, + .cancel_alarm = mcux_lptmr_cancel_alarm, + .set_guard_period = mcux_lptmr_set_guard_period, + .get_guard_period = mcux_lptmr_get_guard_period, }; #define COUNTER_MCUX_LPTMR_DEVICE_INIT(n) \ @@ -181,7 +314,7 @@ static DEVICE_API(counter, mcux_lptmr_driver_api) = { .freq = DT_INST_PROP(n, clock_frequency) / \ DT_INST_PROP(n, prescaler), \ .flags = COUNTER_CONFIG_INFO_COUNT_UP, \ - .channels = 0, \ + .channels = NUM_CHANNELS, \ }, \ .base = (LPTMR_Type *)DT_INST_REG_ADDR(n), \ .clk_source = DT_INST_PROP(n, clk_source), \ @@ -193,6 +326,7 @@ static DEVICE_API(counter, mcux_lptmr_driver_api) = { .prescaler_glitch = DT_INST_PROP(n, prescale_glitch_filter) + \ DT_INST_PROP(n, timer_mode_sel) - 1, \ .irq_config_func = mcux_lptmr_irq_config_##n, \ + .irqn = DT_INST_IRQN(n), \ }; \ \ DEVICE_DT_INST_DEFINE(n, &mcux_lptmr_init, NULL, \ @@ -201,5 +335,4 @@ static DEVICE_API(counter, mcux_lptmr_driver_api) = { POST_KERNEL, CONFIG_COUNTER_INIT_PRIORITY, \ &mcux_lptmr_driver_api); - DT_INST_FOREACH_STATUS_OKAY(COUNTER_MCUX_LPTMR_DEVICE_INIT)