From 7a3f79ef86693ec31ec3120bce277e343ae2836f Mon Sep 17 00:00:00 2001 From: Saravanan Sekar Date: Thu, 8 May 2025 21:44:58 +0530 Subject: [PATCH] drivers: counter: Add a support for TI MSPM0 Timer counter TI MSPM0 SoC series has General Purpose Timer and Advanced control timers with Counting module, Capture block (measure input signal period/time) and Compare block (to generate time expiry, output waveform like PWM). Add a support for counter driver with alarm and counter top functions. Signed-off-by: Saravanan Sekar --- drivers/counter/CMakeLists.txt | 1 + drivers/counter/Kconfig | 2 + drivers/counter/Kconfig.mspm0 | 10 + drivers/counter/counter_mspm0_timer.c | 297 +++++++++++++++++++++ dts/bindings/counter/ti,mspm0-counter.yaml | 23 ++ dts/bindings/timer/ti,mspm0-timer.yaml | 38 +++ modules/Kconfig.mspm0 | 3 + 7 files changed, 374 insertions(+) create mode 100644 drivers/counter/Kconfig.mspm0 create mode 100644 drivers/counter/counter_mspm0_timer.c create mode 100644 dts/bindings/counter/ti,mspm0-counter.yaml create mode 100644 dts/bindings/timer/ti,mspm0-timer.yaml diff --git a/drivers/counter/CMakeLists.txt b/drivers/counter/CMakeLists.txt index f9db80476dd..9de937d7c9b 100644 --- a/drivers/counter/CMakeLists.txt +++ b/drivers/counter/CMakeLists.txt @@ -62,3 +62,4 @@ zephyr_library_sources_ifdef(CONFIG_COUNTER_NEORV32_GPTMR counter_neorv32_ zephyr_library_sources_ifdef(CONFIG_COUNTER_WUT_MAX32 counter_max32_wut.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_CC23X0_RTC counter_cc23x0_rtc.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_CC23X0_LGPT counter_cc23x0_lgpt.c) +zephyr_library_sources_ifdef(CONFIG_COUNTER_MSPM0_TIMER counter_mspm0_timer.c) diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig index 465905f55c7..36e1f0bce3c 100644 --- a/drivers/counter/Kconfig +++ b/drivers/counter/Kconfig @@ -122,4 +122,6 @@ source "drivers/counter/Kconfig.cc23x0_rtc" source "drivers/counter/Kconfig.cc23x0_lgpt" +source "drivers/counter/Kconfig.mspm0" + endif # COUNTER diff --git a/drivers/counter/Kconfig.mspm0 b/drivers/counter/Kconfig.mspm0 new file mode 100644 index 00000000000..c1da6b3c441 --- /dev/null +++ b/drivers/counter/Kconfig.mspm0 @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Linumiz GmbH +# SPDX-License-Identifier: Apache-2.0 + +config COUNTER_MSPM0_TIMER + bool "TI MSPM0 MCU family counter driver" + default y + depends on DT_HAS_TI_MSPM0_TIMER_COUNTER_ENABLED + select USE_MSPM0_DL_TIMER + help + Enable the TI MSPM0 MCU family counter driver. diff --git a/drivers/counter/counter_mspm0_timer.c b/drivers/counter/counter_mspm0_timer.c new file mode 100644 index 00000000000..524e606b823 --- /dev/null +++ b/drivers/counter/counter_mspm0_timer.c @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2025, Linumiz GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ti_mspm0_timer_counter + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(mspm0_counter, CONFIG_COUNTER_LOG_LEVEL); + +struct counter_mspm0_data { + void *user_data_top; + void *user_data; + counter_top_callback_t top_cb; + counter_alarm_callback_t alarm_cb; +}; + +struct counter_mspm0_config { + struct counter_config_info counter_info; + GPTIMER_Regs *base; + const struct device *clock_dev; + const struct mspm0_sys_clock clock_subsys; + DL_Timer_ClockConfig clk_config; + void (*irq_config_func)(void); +}; + +static int counter_mspm0_start(const struct device *dev) +{ + const struct counter_mspm0_config *config = dev->config; + + DL_Timer_startCounter(config->base); + + return 0; +} + +static int counter_mspm0_stop(const struct device *dev) +{ + const struct counter_mspm0_config *config = dev->config; + + DL_Timer_stopCounter(config->base); + + return 0; +} + +static int counter_mspm0_get_value(const struct device *dev, uint32_t *ticks) +{ + const struct counter_mspm0_config *config = dev->config; + + *ticks = DL_Timer_getTimerCount(config->base); + + return 0; +} + +static int counter_mspm0_set_top_value(const struct device *dev, + const struct counter_top_cfg *cfg) +{ + const struct counter_mspm0_config *config = dev->config; + struct counter_mspm0_data *data = dev->data; + + if (cfg->ticks > config->counter_info.max_top_value) { + return -ENOTSUP; + } + + if (!(cfg->flags & COUNTER_TOP_CFG_DONT_RESET)) { + DL_Timer_stopCounter(config->base); + DL_Timer_startCounter(config->base); + } else if (DL_Timer_getTimerCount(config->base) >= cfg->ticks) { + if (cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE) { + DL_Timer_stopCounter(config->base); + DL_Timer_startCounter(config->base); + } + + return -ETIME; + } + + DL_Timer_setLoadValue(config->base, cfg->ticks); + + data->top_cb = cfg->callback; + data->user_data_top = cfg->user_data; + if (cfg->callback) { + DL_Timer_clearInterruptStatus(config->base, + DL_TIMER_INTERRUPT_LOAD_EVENT); + DL_Timer_enableInterrupt(config->base, + DL_TIMER_INTERRUPT_LOAD_EVENT); + } + + return 0; +} + +static uint32_t counter_mspm0_get_top_value(const struct device *dev) +{ + const struct counter_mspm0_config *config = dev->config; + + return DL_Timer_getLoadValue(config->base); +} + +static int counter_mspm0_set_alarm(const struct device *dev, + uint8_t chan_id, + const struct counter_alarm_cfg *alarm_cfg) +{ + const struct counter_mspm0_config *config = dev->config; + struct counter_mspm0_data *data = dev->data; + uint32_t top = counter_mspm0_get_top_value(dev); + uint32_t ticks = alarm_cfg->ticks; + + if (alarm_cfg->ticks > top) { + return -EINVAL; + } + + if (data->alarm_cb != NULL) { + LOG_DBG("Alarm busy\n"); + return -EBUSY; + } + + if ((COUNTER_ALARM_CFG_ABSOLUTE & alarm_cfg->flags) == 0) { + ticks += DL_Timer_getTimerCount(config->base); + if (ticks > top) { + ticks %= top; + } + } + + data->alarm_cb = alarm_cfg->callback; + data->user_data = alarm_cfg->user_data; + + DL_Timer_setCaptureCompareValue(config->base, ticks, + DL_TIMER_CC_0_INDEX); + DL_Timer_clearInterruptStatus(config->base, + DL_TIMER_INTERRUPT_CC0_UP_EVENT); + DL_Timer_enableInterrupt(config->base, + DL_TIMER_INTERRUPT_CC0_UP_EVENT); + + return 0; +} + +static int counter_mspm0_cancel_alarm(const struct device *dev, uint8_t chan_id) +{ + const struct counter_mspm0_config *config = dev->config; + + DL_Timer_disableInterrupt(config->base, + DL_TIMER_INTERRUPT_CC0_UP_EVENT); + + return 0; +} + +static uint32_t counter_mspm0_get_pending_int(const struct device *dev) +{ + const struct counter_mspm0_config *config = dev->config; + uint32_t status; + + status = DL_Timer_getRawInterruptStatus(config->base, + (DL_TIMER_INTERRUPT_LOAD_EVENT | + DL_TIMER_INTERRUPT_CC0_UP_EVENT)); + + return !!status; +} + +static uint32_t counter_mspm0_get_freq(const struct device *dev) +{ + const struct counter_mspm0_config *config = dev->config; + DL_Timer_ClockConfig clkcfg; + uint32_t clock_rate; + int ret; + + ret = clock_control_get_rate(config->clock_dev, + (clock_control_subsys_t)&config->clock_subsys, + &clock_rate); + if (ret != 0) { + LOG_ERR("clk get rate err %d", ret); + return 0; + } + + DL_Timer_getClockConfig(config->base, &clkcfg); + clock_rate = clock_rate / + ((clkcfg.divideRatio + 1) * (clkcfg.prescale + 1)); + + return clock_rate; +} + +static int counter_mspm0_init(const struct device *dev) +{ + const struct counter_mspm0_config *config = dev->config; + DL_Timer_TimerConfig tim_config = { + .period = config->counter_info.max_top_value, + .timerMode = DL_TIMER_TIMER_MODE_PERIODIC_UP, + .startTimer = DL_TIMER_STOP, + }; + + if (!device_is_ready(config->clock_dev)) { + LOG_ERR("clock control device not ready"); + return -ENODEV; + } + + DL_Timer_reset(config->base); + if (!DL_Timer_isPowerEnabled(config->base)) { + DL_Timer_enablePower(config->base); + } + + delay_cycles(CONFIG_MSPM0_PERIPH_STARTUP_DELAY); + DL_Timer_setClockConfig(config->base, + (DL_Timer_ClockConfig *)&config->clk_config); + DL_Timer_initTimerMode(config->base, &tim_config); + DL_Timer_setCounterRepeatMode(config->base, + DL_TIMER_REPEAT_MODE_ENABLED); + + config->irq_config_func(); + + return 0; +} + +static DEVICE_API(counter, mspm0_counter_api) = { + .start = counter_mspm0_start, + .stop = counter_mspm0_stop, + .get_value = counter_mspm0_get_value, + .set_top_value = counter_mspm0_set_top_value, + .get_pending_int = counter_mspm0_get_pending_int, + .get_top_value = counter_mspm0_get_top_value, + .get_freq = counter_mspm0_get_freq, + .cancel_alarm = counter_mspm0_cancel_alarm, + .set_alarm = counter_mspm0_set_alarm, +}; + +static void counter_mspm0_isr(void *arg) +{ + const struct device *dev = (const struct device *)arg; + struct counter_mspm0_data *data = dev->data; + const struct counter_mspm0_config *config = dev->config; + uint32_t status; + + status = DL_Timer_getPendingInterrupt(config->base); + if ((status == DL_TIMER_IIDX_CC0_UP) && data->alarm_cb) { + uint32_t now; + counter_alarm_callback_t alarm_cb = data->alarm_cb; + + counter_mspm0_get_value(dev, &now); + data->alarm_cb = NULL; + alarm_cb(dev, 0, now, data->user_data); + } else if ((status == DL_TIMER_IIDX_LOAD) && data->top_cb) { + data->top_cb(dev, data->user_data_top); + } +} + +#define MSPM0_COUNTER_IRQ_REGISTER(n) \ + static void mspm0_ ## n ##_irq_register(void) \ + { \ + IRQ_CONNECT(DT_IRQN(DT_INST_PARENT(n)), \ + DT_IRQ(DT_INST_PARENT(n), priority), \ + counter_mspm0_isr, DEVICE_DT_INST_GET(n), 0); \ + irq_enable(DT_IRQN(DT_INST_PARENT(n))); \ + } + +#define MSPM0_CLK_DIV(div) DT_CAT(DL_TIMER_CLOCK_DIVIDE_, div) + +#define COUNTER_DEVICE_INIT_MSPM0(n) \ + static struct counter_mspm0_data counter_mspm0_data_ ## n; \ + MSPM0_COUNTER_IRQ_REGISTER(n) \ + \ + static const struct counter_mspm0_config counter_mspm0_config_ ## n = { \ + .base = (GPTIMER_Regs *)DT_REG_ADDR(DT_INST_PARENT(n)), \ + .clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR_BY_IDX( \ + DT_INST_PARENT(n), 0)), \ + .clock_subsys = { \ + .clk = DT_CLOCKS_CELL_BY_IDX(DT_INST_PARENT(n), 0, clk), \ + }, \ + .irq_config_func = (mspm0_ ## n ##_irq_register), \ + .clk_config = { \ + .clockSel = MSPM0_CLOCK_PERIPH_REG_MASK( \ + DT_CLOCKS_CELL_BY_IDX(DT_INST_PARENT(n), 0, clk)), \ + .divideRatio = MSPM0_CLK_DIV(DT_PROP(DT_INST_PARENT(n), \ + clk_div)), \ + .prescale = DT_PROP(DT_INST_PARENT(n), clk_prescaler), \ + }, \ + .counter_info = {.max_top_value = (DT_INST_PROP(n, resolution) == 32) \ + ? UINT32_MAX : UINT16_MAX, \ + .flags = COUNTER_CONFIG_INFO_COUNT_UP, \ + .channels = 1}, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, \ + counter_mspm0_init, \ + NULL, \ + &counter_mspm0_data_ ## n, \ + &counter_mspm0_config_ ## n, \ + POST_KERNEL, CONFIG_COUNTER_INIT_PRIORITY, \ + &mspm0_counter_api); + +DT_INST_FOREACH_STATUS_OKAY(COUNTER_DEVICE_INIT_MSPM0) diff --git a/dts/bindings/counter/ti,mspm0-counter.yaml b/dts/bindings/counter/ti,mspm0-counter.yaml new file mode 100644 index 00000000000..3dd808de7fc --- /dev/null +++ b/dts/bindings/counter/ti,mspm0-counter.yaml @@ -0,0 +1,23 @@ +# Copyright 2025 Linumiz GmbH +# SPDX-License-Identifier: Apache-2.0 + +description: | + TI MSPM0 counter node for MSPM0 SoCs. Each timer can be configured to use for + counter operation. + + mspm0counter : counter { + counter_0 { + resolution = <16>; + }; + }; + +compatible: "ti,mspm0-timer-counter" + +include: base.yaml + +properties: + resolution: + type: int + required: true + description: | + Counter resolution diff --git a/dts/bindings/timer/ti,mspm0-timer.yaml b/dts/bindings/timer/ti,mspm0-timer.yaml new file mode 100644 index 00000000000..469b0cab479 --- /dev/null +++ b/dts/bindings/timer/ti,mspm0-timer.yaml @@ -0,0 +1,38 @@ +# Copyright 2025 Linumiz GmbH +# SPDX-License-Identifier: Apache-2.0 + +description: TI MSPM0 Timer + +compatible: "ti,mspm0-timer" + +include: base.yaml + +properties: + reg: + required: true + + interrupts: + required: true + + clk-prescaler: + type: int + required: true + description: | + TIMCLK clock source prescaler value. + Valid range [0 ... 255]. + + clk-div: + type: int + required: true + default: 1 + enum: + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 + description: | + Clock divider selction value. diff --git a/modules/Kconfig.mspm0 b/modules/Kconfig.mspm0 index 97ec56c4454..7125ff49196 100644 --- a/modules/Kconfig.mspm0 +++ b/modules/Kconfig.mspm0 @@ -11,3 +11,6 @@ config USE_MSPM0_DL_GPIO config USE_MSPM0_DL_UART bool + +config USE_MSPM0_DL_TIMER + bool