Skip to content

Commit 90ca724

Browse files
committed
drivers: pwm: introduce ti_am3352_ecap
This patch adds driver support for TI ECAP module which can capture an external input or work as a standalone APWM generator. Signed-off-by: Amneesh Singh <[email protected]>
1 parent 90cd350 commit 90ca724

File tree

5 files changed

+369
-0
lines changed

5 files changed

+369
-0
lines changed

drivers/pwm/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,4 @@ zephyr_library_sources_ifdef(CONFIG_PWM_RENESAS_RZ_GPT pwm_renesas_rz_gpt.c)
5454
zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c)
5555
zephyr_library_sources_ifdef(CONFIG_PWM_CAPTURE pwm_capture.c)
5656
zephyr_library_sources_ifdef(CONFIG_PWM_SHELL pwm_shell.c)
57+
zephyr_library_sources_ifdef(CONFIG_PWM_TI_AM3352_ECAP pwm_ti_am3352_ecap.c)

drivers/pwm/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,6 @@ source "drivers/pwm/Kconfig.fake"
122122

123123
source "drivers/pwm/Kconfig.renesas_rz"
124124

125+
source "drivers/pwm/Kconfig.ti_am3352_ecap"
126+
125127
endif # PWM

drivers/pwm/Kconfig.ti_am3352_ecap

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) 2025 Texas Instruments
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config PWM_TI_AM3352_ECAP
5+
bool "TI ECAP based PWM controller"
6+
default y
7+
depends on DT_HAS_TI_AM3352_ECAP_ENABLED
8+
help
9+
Enable ECAP controller for TI SoCs

drivers/pwm/pwm_ti_am3352_ecap.c

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
/*
2+
* Copyright (c) 2025 Texas Instruments Incorporated
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#include <zephyr/irq.h>
7+
#include <zephyr/dt-bindings/pwm/pwm.h>
8+
#include <zephyr/drivers/pinctrl.h>
9+
#include <zephyr/drivers/pwm.h>
10+
#include <zephyr/drivers/clock_control.h>
11+
#include <zephyr/logging/log.h>
12+
13+
LOG_MODULE_REGISTER(ti_ecap);
14+
15+
#define DT_DRV_COMPAT ti_am3352_ecap
16+
17+
struct ti_ecap_regs {
18+
uint8_t RESERVED_1[0xC];
19+
volatile uint32_t CAP2;
20+
volatile uint32_t CAP3;
21+
volatile uint32_t CAP4;
22+
uint8_t RESERVED_2[0x10];
23+
volatile uint32_t ECCTL;
24+
volatile uint32_t ECINT_EN_FLG;
25+
volatile uint32_t ECINT_CLR_FRC;
26+
};
27+
28+
/* ECAP Control Register */
29+
#define TI_ECAP_ECCTL_APWMPOL BIT(26)
30+
#define TI_ECAP_ECCTL_CAP_APWM BIT(25)
31+
#define TI_ECAP_ECCTL_SYNCO_SEL GENMASK(23, 22)
32+
#define TI_ECAP_ECCTL_TSCNTSTP BIT(20)
33+
#define TI_ECAP_ECCTL_REARM_RESET BIT(19)
34+
#define TI_ECAP_ECCTL_STOPVALUE GENMASK(18, 17)
35+
#define TI_ECAP_ECCTL_STOPVALUE_EVT3 (0x2)
36+
#define TI_ECAP_ECCTL_CONT_ONESHT BIT(16)
37+
#define TI_ECAP_ECCTL_CAPLDEN BIT(8)
38+
#define TI_ECAP_ECCTL_CTRRST3 BIT(5)
39+
#define TI_ECAP_ECCTL_CAP3POL BIT(4)
40+
#define TI_ECAP_ECCTL_CTRRST2 BIT(3)
41+
#define TI_ECAP_ECCTL_CAP2POL BIT(2)
42+
#define TI_ECAP_ECCTL_CTRRST1 BIT(1)
43+
#define TI_ECAP_ECCTL_CAP1POL BIT(0)
44+
45+
/* ECAP Interrupt Enable and Flag Register */
46+
#define TI_ECAP_ECINT_EN_CNTOVF_FLG BIT(21)
47+
#define TI_ECAP_ECINT_EN_CEVT3_FLG BIT(19)
48+
#define TI_ECAP_ECINT_EN_CNTOVF BIT(5)
49+
#define TI_ECAP_ECINT_EN_CEVT3 BIT(3)
50+
51+
/* ECAP Interrupt Clear and Force Register */
52+
#define TI_ECAP_ECINT_CLR_CNTOVF BIT(5)
53+
#define TI_ECAP_ECINT_CLR_CEVT3 BIT(3)
54+
#define TI_ECAP_ECINT_CLR_INT BIT(0)
55+
56+
#define DEV_CFG(dev) ((const struct ti_ecap_cfg *)(dev)->config)
57+
#define DEV_DATA(dev) ((struct ti_ecap_data *)(dev)->data)
58+
#define DEV_REGS(dev) ((struct ti_ecap_regs *)DEVICE_MMIO_GET(dev))
59+
60+
struct ti_ecap_capture_data {
61+
pwm_capture_callback_handler_t callback;
62+
void *user_data;
63+
bool capture_period;
64+
bool capture_pulse;
65+
bool continuous;
66+
};
67+
68+
struct ti_ecap_cfg {
69+
DEVICE_MMIO_ROM;
70+
void (*irq_config_func)();
71+
const struct device *clock_dev;
72+
clock_control_subsys_t clock_subsys;
73+
uint32_t clock_frequency;
74+
const struct pinctrl_dev_config *pcfg;
75+
};
76+
77+
struct ti_ecap_data {
78+
DEVICE_MMIO_RAM;
79+
struct ti_ecap_capture_data cpt;
80+
};
81+
82+
static int ti_ecap_set_cycles(const struct device *dev, uint32_t channel, uint32_t period_cycles,
83+
uint32_t pulse_cycles, pwm_flags_t flags)
84+
{
85+
ARG_UNUSED(channel);
86+
struct ti_ecap_regs *regs = DEV_REGS(dev);
87+
uint32_t ecctl;
88+
89+
ecctl = regs->ECCTL;
90+
91+
/* enable apwm and counter */
92+
ecctl |= (TI_ECAP_ECCTL_TSCNTSTP | TI_ECAP_ECCTL_CAP_APWM);
93+
94+
/* polarity */
95+
if ((flags & PWM_POLARITY_MASK) == PWM_POLARITY_NORMAL) {
96+
ecctl &= ~TI_ECAP_ECCTL_APWMPOL;
97+
} else {
98+
ecctl |= TI_ECAP_ECCTL_APWMPOL;
99+
}
100+
101+
/* update shadow period and shadow cmp registers in apwm mode */
102+
regs->CAP3 = period_cycles;
103+
regs->CAP4 = pulse_cycles;
104+
105+
/* update ecctl */
106+
regs->ECCTL = ecctl;
107+
108+
return 0;
109+
}
110+
111+
static int ti_ecap_get_cycles_per_sec(const struct device *dev, uint32_t channel, uint64_t *cycles)
112+
{
113+
ARG_UNUSED(channel);
114+
const struct ti_ecap_cfg *cfg = DEV_CFG(dev);
115+
116+
if (cfg->clock_dev != NULL) {
117+
return clock_control_get_rate(cfg->clock_dev, cfg->clock_subsys,
118+
(uint32_t *)cycles);
119+
}
120+
121+
if (cfg->clock_frequency != 0U) {
122+
*cycles = cfg->clock_frequency;
123+
return 0;
124+
}
125+
126+
return -ENOTSUP;
127+
}
128+
129+
#ifdef CONFIG_PWM_CAPTURE
130+
static int ti_ecap_enable_capture(const struct device *dev, uint32_t channel)
131+
{
132+
ARG_UNUSED(channel);
133+
struct ti_ecap_capture_data *cpt = &DEV_DATA(dev)->cpt;
134+
struct ti_ecap_regs *regs = DEV_REGS(dev);
135+
136+
/* enable interrupt for 3rd event */
137+
regs->ECINT_EN_FLG |= (TI_ECAP_ECINT_EN_CEVT3 | TI_ECAP_ECINT_EN_CNTOVF);
138+
139+
/* start counter and enable loading capture registers */
140+
regs->ECCTL |= (TI_ECAP_ECCTL_TSCNTSTP | TI_ECAP_ECCTL_CAPLDEN);
141+
142+
if (!cpt->continuous) {
143+
/* rearms one shot */
144+
regs->ECCTL |= TI_ECAP_ECCTL_REARM_RESET;
145+
}
146+
147+
return 0;
148+
}
149+
150+
static int ti_ecap_disable_capture(const struct device *dev, uint32_t channel)
151+
{
152+
ARG_UNUSED(channel);
153+
struct ti_ecap_regs *regs = DEV_REGS(dev);
154+
155+
/* disable interrupts */
156+
regs->ECINT_EN_FLG &= ~(TI_ECAP_ECINT_EN_CEVT3 | TI_ECAP_ECINT_EN_CNTOVF);
157+
158+
/* stop counter and disable loading capture registers */
159+
regs->ECCTL &= ~(TI_ECAP_ECCTL_TSCNTSTP | TI_ECAP_ECCTL_CAPLDEN);
160+
161+
return 0;
162+
}
163+
164+
static int ti_ecap_configure_capture(const struct device *dev, uint32_t channel, pwm_flags_t flags,
165+
pwm_capture_callback_handler_t cb, void *user_data)
166+
{
167+
ARG_UNUSED(channel);
168+
struct ti_ecap_capture_data *cpt = &DEV_DATA(dev)->cpt;
169+
struct ti_ecap_regs *regs = DEV_REGS(dev);
170+
uint32_t ecctl;
171+
172+
cpt->callback = cb;
173+
cpt->user_data = user_data;
174+
cpt->capture_period = !!(flags & PWM_CAPTURE_TYPE_PERIOD);
175+
cpt->capture_pulse = !!(flags & PWM_CAPTURE_TYPE_PULSE);
176+
cpt->continuous = !!(flags & PWM_CAPTURE_MODE_CONTINUOUS);
177+
178+
/* disable interrupts */
179+
regs->ECINT_EN_FLG &= ~(TI_ECAP_ECINT_EN_CEVT3 | TI_ECAP_ECINT_EN_CNTOVF);
180+
181+
/* clear event flags */
182+
regs->ECINT_CLR_FRC |=
183+
(TI_ECAP_ECINT_CLR_CNTOVF | TI_ECAP_ECINT_CLR_CEVT3 | TI_ECAP_ECINT_CLR_INT);
184+
185+
ecctl = regs->ECCTL;
186+
187+
if (cpt->continuous) {
188+
/* continuous */
189+
ecctl &= ~TI_ECAP_ECCTL_CONT_ONESHT;
190+
} else {
191+
/* single shot */
192+
ecctl |= TI_ECAP_ECCTL_CONT_ONESHT;
193+
}
194+
195+
/* we only care about first 3 events */
196+
ecctl &= ~TI_ECAP_ECCTL_STOPVALUE;
197+
ecctl |= FIELD_PREP(TI_ECAP_ECCTL_STOPVALUE, TI_ECAP_ECCTL_STOPVALUE_EVT3);
198+
199+
/* configure capture timestamp to reset after edge to capture delta */
200+
ecctl |= (TI_ECAP_ECCTL_CTRRST1 | TI_ECAP_ECCTL_CTRRST2 | TI_ECAP_ECCTL_CTRRST3);
201+
202+
if ((flags & PWM_POLARITY_MASK) == PWM_POLARITY_NORMAL) {
203+
/* active high */
204+
ecctl &= ~TI_ECAP_ECCTL_CAP1POL; /* cap 1 - rising edge */
205+
ecctl |= TI_ECAP_ECCTL_CAP2POL; /* cap 2 - falling edge */
206+
ecctl &= ~TI_ECAP_ECCTL_CAP3POL; /* cap 3 - rising edge */
207+
} else {
208+
ecctl |= TI_ECAP_ECCTL_CAP1POL; /* cap 1 - falling edge */
209+
ecctl &= ~TI_ECAP_ECCTL_CAP2POL; /* cap 2 - rising edge */
210+
ecctl |= TI_ECAP_ECCTL_CAP3POL; /* cap 3 - falling edge */
211+
}
212+
213+
/* stop counter and disable loading capture registers */
214+
ecctl &= ~(TI_ECAP_ECCTL_TSCNTSTP | TI_ECAP_ECCTL_CAPLDEN);
215+
216+
/* enable capture mode and disable APWM */
217+
ecctl &= ~TI_ECAP_ECCTL_CAP_APWM;
218+
219+
regs->ECCTL = ecctl;
220+
221+
return 0;
222+
}
223+
#endif /* CONFIG_PWM_CAPTURE */
224+
225+
static int ti_ecap_init(const struct device *dev)
226+
{
227+
const struct ti_ecap_cfg *cfg = DEV_CFG(dev);
228+
int ret;
229+
230+
DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
231+
232+
ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
233+
if (ret < 0) {
234+
LOG_ERR("Fail to configure pinctrl\n");
235+
return ret;
236+
}
237+
238+
cfg->irq_config_func();
239+
240+
return 0;
241+
}
242+
243+
static void ti_ecap_isr(const struct device *dev)
244+
{
245+
struct ti_ecap_capture_data *cpt = &DEV_DATA(dev)->cpt;
246+
struct ti_ecap_regs *regs = DEV_REGS(dev);
247+
uint32_t period = 0;
248+
uint32_t pulse = 0;
249+
uint32_t ecint_en_flg = regs->ECINT_EN_FLG;
250+
251+
if (ecint_en_flg & TI_ECAP_ECINT_EN_CNTOVF_FLG) {
252+
253+
if (cpt->callback != NULL) {
254+
cpt->callback(dev, 0, period, pulse, -ERANGE, cpt->user_data);
255+
}
256+
257+
/* clear event */
258+
regs->ECINT_CLR_FRC |= TI_ECAP_ECINT_CLR_CNTOVF;
259+
} else if (ecint_en_flg & TI_ECAP_ECINT_EN_CEVT3_FLG) {
260+
uint32_t cap2 = regs->CAP2;
261+
uint32_t cap3 = regs->CAP3;
262+
263+
if (cpt->capture_period) {
264+
period = cap2 + cap3;
265+
}
266+
267+
if (cpt->capture_pulse) {
268+
pulse = cap2;
269+
}
270+
271+
if (cpt->callback != NULL) {
272+
cpt->callback(dev, 0, period, pulse, 0, cpt->user_data);
273+
}
274+
275+
/* clear event */
276+
regs->ECINT_CLR_FRC |= TI_ECAP_ECINT_CLR_CEVT3;
277+
}
278+
279+
/* clear global interrupt */
280+
regs->ECINT_CLR_FRC |= TI_ECAP_ECINT_CLR_INT;
281+
}
282+
283+
static DEVICE_API(pwm, ti_ecap_api) = {
284+
.set_cycles = ti_ecap_set_cycles,
285+
.get_cycles_per_sec = ti_ecap_get_cycles_per_sec,
286+
#ifdef CONFIG_PWM_CAPTURE
287+
.enable_capture = ti_ecap_enable_capture,
288+
.disable_capture = ti_ecap_disable_capture,
289+
.configure_capture = ti_ecap_configure_capture,
290+
#endif /* CONFIG_PWM_CAPTURE */
291+
};
292+
293+
#define TI_ECAP_CLK_CONFIG(n) \
294+
COND_CODE_1(DT_INST_CLOCKS_HAS_NAME(n, fck), ( \
295+
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR_BY_NAME(n, fck)), \
296+
.clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL_BY_NAME( \
297+
n, fck, clk_id), \
298+
.clock_frequency = 0 \
299+
), ( \
300+
.clock_dev = NULL, \
301+
.clock_subsys = NULL, \
302+
.clock_frequency = DT_INST_PROP_OR(n, clock_frequency, 0) \
303+
) \
304+
)
305+
306+
#define TI_ECAP_INIT(n) \
307+
static void ti_ecap_irq_config_func_##n(void) \
308+
{ \
309+
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), ti_ecap_isr, \
310+
DEVICE_DT_INST_GET(n), DT_INST_IRQ(n, flags)); \
311+
irq_enable(DT_INST_IRQN(n)); \
312+
} \
313+
\
314+
PINCTRL_DT_INST_DEFINE(n); \
315+
\
316+
static struct ti_ecap_cfg ti_ecap_config_##n = { \
317+
DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)), \
318+
TI_ECAP_CLK_CONFIG(n), \
319+
.irq_config_func = ti_ecap_irq_config_func_##n, \
320+
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
321+
}; \
322+
\
323+
static struct ti_ecap_data ti_ecap_data_##n; \
324+
\
325+
DEVICE_DT_INST_DEFINE(n, ti_ecap_init, NULL, &ti_ecap_data_##n, &ti_ecap_config_##n, \
326+
POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, &ti_ecap_api);
327+
328+
DT_INST_FOREACH_STATUS_OKAY(TI_ECAP_INIT)

dts/bindings/pwm/ti,am3352-ecap.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright (c) 2025 Texas Instruments
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
6+
description: TI SOC ECAP based PWM controller
7+
8+
include: [pwm-controller.yaml, base.yaml, pinctrl-device.yaml]
9+
10+
compatible: "ti,am3352-ecap"
11+
12+
properties:
13+
reg:
14+
required: true
15+
16+
interrupts:
17+
required: true
18+
19+
clock-frequency:
20+
type: int
21+
description: Optional peripheral frequency to use if the clock is absent
22+
23+
"#pwm-cells":
24+
const: 3
25+
26+
pwm-cells:
27+
- channel
28+
- period
29+
- flags

0 commit comments

Comments
 (0)