Browse Source
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 <saravanan@linumiz.com>pull/91369/head
7 changed files with 374 additions and 0 deletions
@ -0,0 +1,10 @@
@@ -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. |
@ -0,0 +1,297 @@
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Linumiz GmbH |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
#define DT_DRV_COMPAT ti_mspm0_timer_counter |
||||
|
||||
#include <zephyr/drivers/counter.h> |
||||
#include <zephyr/drivers/clock_control.h> |
||||
#include <zephyr/drivers/clock_control/mspm0_clock_control.h> |
||||
#include <zephyr/kernel.h> |
||||
#include <zephyr/device.h> |
||||
#include <zephyr/logging/log.h> |
||||
|
||||
#include <ti/driverlib/dl_timera.h> |
||||
#include <ti/driverlib/dl_timerg.h> |
||||
#include <ti/driverlib/dl_timer.h> |
||||
|
||||
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) |
@ -0,0 +1,23 @@
@@ -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 |
@ -0,0 +1,38 @@
@@ -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. |
Loading…
Reference in new issue