Skip to content

Commit f7df5e8

Browse files
ssekar15kartben
authored andcommitted
drivers: pwm: Add a support for TI MSPM0 Timer PWM
TI MSPM0 SoC series has General Purpose Timer and Advanced control timers with Compare block which is used to generate time expiry and output waveform like PWM. Add driver support for MSPM0 PWM output. Signed-off-by: Saravanan Sekar <[email protected]>
1 parent 8b4a97f commit f7df5e8

File tree

5 files changed

+300
-0
lines changed

5 files changed

+300
-0
lines changed

drivers/pwm/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_INTEL_BLINKY pwm_intel_blinky.c)
4646
zephyr_library_sources_ifdef(CONFIG_PWM_XMC4XXX_CCU4 pwm_xmc4xxx_ccu4.c)
4747
zephyr_library_sources_ifdef(CONFIG_PWM_XMC4XXX_CCU8 pwm_xmc4xxx_ccu8.c)
4848
zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_CTIMER pwm_mcux_ctimer.c)
49+
zephyr_library_sources_ifdef(CONFIG_PWM_MSPM0 pwm_mspm0.c)
4950
zephyr_library_sources_ifdef(CONFIG_PWM_NUMAKER pwm_numaker.c)
5051
zephyr_library_sources_ifdef(CONFIG_PWM_NXP_FLEXIO pwm_nxp_flexio.c)
5152
zephyr_library_sources_ifdef(CONFIG_PWM_NXP_S32_EMIOS pwm_nxp_s32_emios.c)

drivers/pwm/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ source "drivers/pwm/Kconfig.xmc4xxx_ccu8"
110110

111111
source "drivers/pwm/Kconfig.mcux_ctimer"
112112

113+
source "drivers/pwm/Kconfig.mspm0"
114+
113115
source "drivers/pwm/Kconfig.numaker"
114116

115117
source "drivers/pwm/Kconfig.nxp_s32_emios"

drivers/pwm/Kconfig.mspm0

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Copyright (c) 2024 Linumiz GmbH
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config PWM_MSPM0
5+
bool "TI MSPM0 MCU family PWM driver"
6+
default y
7+
depends on DT_HAS_TI_MSPM0_TIMER_PWM_ENABLED
8+
select PINCTRL
9+
select USE_MSPM0_DL_TIMER
10+
help
11+
Enable TI MSPM0 MCU family PWM driver.

drivers/pwm/pwm_mspm0.c

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/*
2+
* Copyright (c) 2025, Linumiz GmbH
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#define DT_DRV_COMPAT ti_mspm0_timer_pwm
8+
9+
#include <zephyr/drivers/clock_control.h>
10+
#include <zephyr/drivers/clock_control/mspm0_clock_control.h>
11+
#include <zephyr/drivers/pinctrl.h>
12+
#include <zephyr/drivers/pwm.h>
13+
#include <zephyr/irq.h>
14+
#include <zephyr/kernel.h>
15+
#include <zephyr/logging/log.h>
16+
17+
/* Driverlib includes */
18+
#include <ti/driverlib/dl_timera.h>
19+
#include <ti/driverlib/dl_timerg.h>
20+
#include <ti/driverlib/dl_timer.h>
21+
22+
LOG_MODULE_REGISTER(pwm_mspm0, CONFIG_PWM_LOG_LEVEL);
23+
24+
/* capture and compare block count per timer */
25+
#define MSPM0_TIMER_CC_COUNT 2
26+
#define MSPM0_TIMER_CC_MAX 4
27+
28+
struct pwm_mspm0_config {
29+
const struct mspm0_sys_clock clock_subsys;
30+
const struct pinctrl_dev_config *pincfg;
31+
const struct device *clock_dev;
32+
GPTIMER_Regs *base;
33+
DL_Timer_ClockConfig clk_config;
34+
uint8_t cc_idx[MSPM0_TIMER_CC_MAX];
35+
uint8_t cc_idx_cnt;
36+
};
37+
38+
struct pwm_mspm0_data {
39+
uint32_t pulse_cycle[MSPM0_TIMER_CC_MAX];
40+
uint32_t period;
41+
struct k_mutex lock;
42+
43+
DL_TIMER_PWM_MODE out_mode;
44+
};
45+
46+
static void mspm0_setup_pwm_out(const struct pwm_mspm0_config *config,
47+
struct pwm_mspm0_data *data)
48+
{
49+
int i;
50+
DL_Timer_PWMConfig pwmcfg = { 0 };
51+
uint8_t ccdir_mask = 0;
52+
53+
pwmcfg.period = data->period;
54+
pwmcfg.pwmMode = data->out_mode;
55+
56+
for (i = 0; i < config->cc_idx_cnt; i++) {
57+
if (config->cc_idx[i] >= MSPM0_TIMER_CC_COUNT) {
58+
pwmcfg.isTimerWithFourCC = true;
59+
break;
60+
}
61+
}
62+
63+
DL_Timer_initPWMMode(config->base, &pwmcfg);
64+
65+
for (i = 0; i < config->cc_idx_cnt; i++) {
66+
DL_Timer_setCaptureCompareValue(config->base,
67+
data->pulse_cycle[i],
68+
config->cc_idx[i]);
69+
ccdir_mask |= 1 << config->cc_idx[i];
70+
}
71+
72+
DL_Timer_enableClock(config->base);
73+
DL_Timer_setCCPDirection(config->base, ccdir_mask);
74+
DL_Timer_startCounter(config->base);
75+
}
76+
77+
static int mspm0_pwm_set_cycles(const struct device *dev, uint32_t channel,
78+
uint32_t period_cycles, uint32_t pulse_cycles,
79+
pwm_flags_t flags)
80+
{
81+
const struct pwm_mspm0_config *config = dev->config;
82+
struct pwm_mspm0_data *data = dev->data;
83+
84+
if (channel >= MSPM0_TIMER_CC_MAX) {
85+
LOG_ERR("Invalid channel");
86+
return -EINVAL;
87+
}
88+
89+
if (period_cycles > UINT16_MAX) {
90+
LOG_ERR("period cycles exceeds 16-bit timer limit");
91+
return -ENOTSUP;
92+
}
93+
94+
k_mutex_lock(&data->lock, K_FOREVER);
95+
96+
data->pulse_cycle[channel] = pulse_cycles;
97+
data->period = period_cycles;
98+
99+
if (data->out_mode == DL_TIMER_PWM_MODE_CENTER_ALIGN) {
100+
data->period = period_cycles >> 1;
101+
}
102+
103+
DL_Timer_setLoadValue(config->base, data->period);
104+
DL_Timer_setCaptureCompareValue(config->base,
105+
data->pulse_cycle[channel],
106+
config->cc_idx[channel]);
107+
108+
k_mutex_unlock(&data->lock);
109+
110+
return 0;
111+
}
112+
113+
static int mspm0_pwm_get_cycles_per_sec(const struct device *dev,
114+
uint32_t channel, uint64_t *cycles)
115+
{
116+
const struct pwm_mspm0_config *config = dev->config;
117+
DL_Timer_ClockConfig clkcfg;
118+
uint32_t clock_rate;
119+
int ret;
120+
121+
if (cycles == NULL) {
122+
return -EINVAL;
123+
}
124+
125+
ret = clock_control_get_rate(config->clock_dev,
126+
(clock_control_subsys_t)&config->clock_subsys,
127+
&clock_rate);
128+
if (ret != 0) {
129+
LOG_ERR("clk get rate err %d", ret);
130+
return ret;
131+
}
132+
133+
DL_Timer_getClockConfig(config->base, &clkcfg);
134+
*cycles = clock_rate /
135+
((clkcfg.divideRatio + 1) * (clkcfg.prescale + 1));
136+
137+
return 0;
138+
}
139+
140+
static int pwm_mspm0_init(const struct device *dev)
141+
{
142+
const struct pwm_mspm0_config *config = dev->config;
143+
struct pwm_mspm0_data *data = dev->data;
144+
int err;
145+
146+
k_mutex_init(&data->lock);
147+
148+
if (!device_is_ready(config->clock_dev)) {
149+
LOG_ERR("clock control device not ready");
150+
return -ENODEV;
151+
}
152+
153+
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
154+
if (err < 0) {
155+
return err;
156+
}
157+
158+
DL_Timer_reset(config->base);
159+
if (!DL_Timer_isPowerEnabled(config->base)) {
160+
DL_Timer_enablePower(config->base);
161+
}
162+
163+
delay_cycles(CONFIG_MSPM0_PERIPH_STARTUP_DELAY);
164+
DL_Timer_setClockConfig(config->base,
165+
(DL_Timer_ClockConfig *)&config->clk_config);
166+
167+
mspm0_setup_pwm_out(config, data);
168+
169+
return 0;
170+
}
171+
172+
static const struct pwm_driver_api pwm_mspm0_driver_api = {
173+
.set_cycles = mspm0_pwm_set_cycles,
174+
.get_cycles_per_sec = mspm0_pwm_get_cycles_per_sec,
175+
};
176+
177+
#define MSPM0_PWM_MODE(mode) DT_CAT(DL_TIMER_PWM_MODE_, mode)
178+
#define MSPM0_CLK_DIV(div) DT_CAT(DL_TIMER_CLOCK_DIVIDE_, div)
179+
180+
#define MSPM0_CC_IDX_ARRAY(node_id, prop, idx) \
181+
DT_PROP_BY_IDX(node_id, prop, idx),
182+
183+
#define MSPM0_PWM_DATA(n) \
184+
.out_mode = MSPM0_PWM_MODE(DT_STRING_TOKEN(DT_DRV_INST(n), \
185+
ti_pwm_mode)),
186+
187+
#define PWM_DEVICE_INIT_MSPM0(n) \
188+
static struct pwm_mspm0_data pwm_mspm0_data_ ## n = { \
189+
.period = DT_PROP(DT_DRV_INST(n), ti_period), \
190+
COND_CODE_1(DT_NODE_HAS_PROP(DT_DRV_INST(n), ti_pwm_mode), \
191+
(MSPM0_PWM_DATA(n)), ()) \
192+
}; \
193+
PINCTRL_DT_INST_DEFINE(n); \
194+
\
195+
static const struct pwm_mspm0_config pwm_mspm0_config_ ## n = { \
196+
.base = (GPTIMER_Regs *)DT_REG_ADDR(DT_INST_PARENT(n)), \
197+
.clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR_BY_IDX( \
198+
DT_INST_PARENT(n), 0)), \
199+
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
200+
.clock_subsys = { \
201+
.clk = DT_CLOCKS_CELL_BY_IDX(DT_INST_PARENT(n), 0, clk),\
202+
}, \
203+
.cc_idx = { \
204+
DT_INST_FOREACH_PROP_ELEM(n, ti_cc_index, \
205+
MSPM0_CC_IDX_ARRAY) \
206+
}, \
207+
.cc_idx_cnt = DT_INST_PROP_LEN(n, ti_cc_index), \
208+
.clk_config = { \
209+
.clockSel = MSPM0_CLOCK_PERIPH_REG_MASK( \
210+
DT_CLOCKS_CELL_BY_IDX(DT_INST_PARENT(n), \
211+
0, clk)), \
212+
.divideRatio = MSPM0_CLK_DIV(DT_PROP(DT_INST_PARENT(n), \
213+
ti_clk_div)), \
214+
.prescale = DT_PROP(DT_INST_PARENT(n), ti_clk_prescaler),\
215+
}, \
216+
}; \
217+
\
218+
DEVICE_DT_INST_DEFINE(n, \
219+
pwm_mspm0_init, \
220+
NULL, \
221+
&pwm_mspm0_data_ ## n, \
222+
&pwm_mspm0_config_ ## n, \
223+
POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, \
224+
&pwm_mspm0_driver_api);
225+
226+
DT_INST_FOREACH_STATUS_OKAY(PWM_DEVICE_INIT_MSPM0)

dts/bindings/pwm/ti,mspm0-pwm.yaml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Copyright 2025 Linumiz GmbH
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: |
5+
TI MSPM0 PWM node for MSPM0 SoCs. Each channel in MSPM0 can be configured
6+
to use for PWM operation. There are three PWM modes supported by hardware.
7+
8+
timer0: timer {
9+
pwm0: pwm {
10+
compatible = "ti,mspm0g-timer-pwm";
11+
#pwm-cells = <3>;
12+
13+
ti,cc-index = <0>;
14+
ti,pwm-mode = "PULSE_WIDTH";
15+
ti,period = <1000>;
16+
};
17+
};
18+
19+
compatible: "ti,mspm0-timer-pwm"
20+
21+
include: [pwm-controller.yaml, base.yaml, pinctrl-device.yaml]
22+
23+
properties:
24+
pinctrl-0:
25+
required: true
26+
27+
pinctrl-names:
28+
required: true
29+
30+
ti,cc-index:
31+
type: array
32+
required: true
33+
description: |
34+
Capture input/Compare output index, should match with the pin function.
35+
36+
ti,pwm-mode:
37+
type: string
38+
description: |
39+
Select PWM OUT mode:
40+
- EDGE_ALIGN: generate pulses with edge aligned down counting mode.
41+
42+
- EDGE_ALIGN_UP: generate pulses with edge aligned up counting mode.
43+
44+
- CENTER_ALIGN: generates pulses center aligned up and down counting mode.
45+
46+
enum:
47+
- "EDGE_ALIGN"
48+
- "EDGE_ALIGN_UP"
49+
- "CENTER_ALIGN"
50+
51+
ti,period:
52+
type: int
53+
required: true
54+
description: |
55+
Time period in TIMCLK cycle for Capture mode or PWM output.
56+
57+
pwm-cells:
58+
- channel
59+
- period
60+
- flags

0 commit comments

Comments
 (0)