Skip to content

Commit d7eee0f

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 d7eee0f

File tree

5 files changed

+370
-0
lines changed

5 files changed

+370
-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: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
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+
struct ti_ecap_data *data = DEV_DATA(dev);
88+
uint32_t ecctl;
89+
90+
ecctl = regs->ECCTL;
91+
92+
/* enable apwm and counter */
93+
ecctl |= (TI_ECAP_ECCTL_TSCNTSTP | TI_ECAP_ECCTL_CAP_APWM);
94+
95+
/* polarity */
96+
if ((flags & PWM_POLARITY_MASK) == PWM_POLARITY_NORMAL) {
97+
ecctl &= ~TI_ECAP_ECCTL_APWMPOL;
98+
} else {
99+
ecctl |= TI_ECAP_ECCTL_APWMPOL;
100+
}
101+
102+
/* update shadow period and shadow cmp registers in apwm mode */
103+
regs->CAP3 = period_cycles;
104+
regs->CAP4 = pulse_cycles;
105+
106+
/* update ecctl */
107+
regs->ECCTL = ecctl;
108+
109+
return 0;
110+
}
111+
112+
static int ti_ecap_get_cycles_per_sec(const struct device *dev, uint32_t channel, uint64_t *cycles)
113+
{
114+
ARG_UNUSED(channel);
115+
const struct ti_ecap_cfg *cfg = DEV_CFG(dev);
116+
117+
if (cfg->clock_dev != NULL) {
118+
return clock_control_get_rate(cfg->clock_dev, cfg->clock_subsys,
119+
(uint32_t *)cycles);
120+
}
121+
122+
if (cfg->clock_frequency != 0U) {
123+
*cycles = cfg->clock_frequency;
124+
return 0;
125+
}
126+
127+
return -ENOTSUP;
128+
}
129+
130+
#ifdef CONFIG_PWM_CAPTURE
131+
static int ti_ecap_enable_capture(const struct device *dev, uint32_t channel)
132+
{
133+
ARG_UNUSED(channel);
134+
struct ti_ecap_capture_data *cpt = &DEV_DATA(dev)->cpt;
135+
struct ti_ecap_regs *regs = DEV_REGS(dev);
136+
137+
/* enable interrupt for 3rd event */
138+
regs->ECINT_EN_FLG |= (TI_ECAP_ECINT_EN_CEVT3 | TI_ECAP_ECINT_EN_CNTOVF);
139+
140+
/* start counter and enable loading capture registers */
141+
regs->ECCTL |= (TI_ECAP_ECCTL_TSCNTSTP | TI_ECAP_ECCTL_CAPLDEN);
142+
143+
if (!cpt->continuous) {
144+
/* rearms one shot */
145+
regs->ECCTL |= TI_ECAP_ECCTL_REARM_RESET;
146+
}
147+
148+
return 0;
149+
}
150+
151+
static int ti_ecap_disable_capture(const struct device *dev, uint32_t channel)
152+
{
153+
ARG_UNUSED(channel);
154+
struct ti_ecap_regs *regs = DEV_REGS(dev);
155+
156+
/* disable interrupts */
157+
regs->ECINT_EN_FLG &= ~(TI_ECAP_ECINT_EN_CEVT3 | TI_ECAP_ECINT_EN_CNTOVF);
158+
159+
/* stop counter and disable loading capture registers */
160+
regs->ECCTL &= ~(TI_ECAP_ECCTL_TSCNTSTP | TI_ECAP_ECCTL_CAPLDEN);
161+
162+
return 0;
163+
}
164+
165+
static int ti_ecap_configure_capture(const struct device *dev, uint32_t channel, pwm_flags_t flags,
166+
pwm_capture_callback_handler_t cb, void *user_data)
167+
{
168+
ARG_UNUSED(channel);
169+
struct ti_ecap_capture_data *cpt = &DEV_DATA(dev)->cpt;
170+
struct ti_ecap_regs *regs = DEV_REGS(dev);
171+
uint32_t ecctl;
172+
173+
cpt->callback = cb;
174+
cpt->user_data = user_data;
175+
cpt->capture_period = !!(flags & PWM_CAPTURE_TYPE_PERIOD);
176+
cpt->capture_pulse = !!(flags & PWM_CAPTURE_TYPE_PULSE);
177+
cpt->continuous = !!(flags & PWM_CAPTURE_MODE_CONTINUOUS);
178+
179+
/* disable interrupts */
180+
regs->ECINT_EN_FLG &= ~(TI_ECAP_ECINT_EN_CEVT3 | TI_ECAP_ECINT_EN_CNTOVF);
181+
182+
/* clear event flags */
183+
regs->ECINT_CLR_FRC |=
184+
(TI_ECAP_ECINT_CLR_CNTOVF | TI_ECAP_ECINT_CLR_CEVT3 | TI_ECAP_ECINT_CLR_INT);
185+
186+
ecctl = regs->ECCTL;
187+
188+
if (cpt->continuous) {
189+
/* continuous */
190+
ecctl &= ~TI_ECAP_ECCTL_CONT_ONESHT;
191+
} else {
192+
/* single shot */
193+
ecctl |= TI_ECAP_ECCTL_CONT_ONESHT;
194+
}
195+
196+
/* we only care about first 3 events */
197+
ecctl &= ~TI_ECAP_ECCTL_STOPVALUE;
198+
ecctl |= FIELD_PREP(TI_ECAP_ECCTL_STOPVALUE, TI_ECAP_ECCTL_STOPVALUE_EVT3);
199+
200+
/* configure capture timestamp to reset after edge to capture delta */
201+
ecctl |= (TI_ECAP_ECCTL_CTRRST1 | TI_ECAP_ECCTL_CTRRST2 | TI_ECAP_ECCTL_CTRRST3);
202+
203+
if ((flags & PWM_POLARITY_MASK) == PWM_POLARITY_NORMAL) {
204+
/* active high */
205+
ecctl &= ~TI_ECAP_ECCTL_CAP1POL; /* cap 1 - rising edge */
206+
ecctl |= TI_ECAP_ECCTL_CAP2POL; /* cap 2 - falling edge */
207+
ecctl &= ~TI_ECAP_ECCTL_CAP3POL; /* cap 3 - rising edge */
208+
} else {
209+
ecctl |= TI_ECAP_ECCTL_CAP1POL; /* cap 1 - falling edge */
210+
ecctl &= ~TI_ECAP_ECCTL_CAP2POL; /* cap 2 - rising edge */
211+
ecctl |= TI_ECAP_ECCTL_CAP3POL; /* cap 3 - falling edge */
212+
}
213+
214+
/* stop counter and disable loading capture registers */
215+
ecctl &= ~(TI_ECAP_ECCTL_TSCNTSTP | TI_ECAP_ECCTL_CAPLDEN);
216+
217+
/* enable capture mode and disable APWM */
218+
ecctl &= ~TI_ECAP_ECCTL_CAP_APWM;
219+
220+
regs->ECCTL = ecctl;
221+
222+
return 0;
223+
}
224+
#endif /* CONFIG_PWM_CAPTURE */
225+
226+
static int ti_ecap_init(const struct device *dev)
227+
{
228+
const struct ti_ecap_cfg *cfg = DEV_CFG(dev);
229+
int ret;
230+
231+
DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
232+
233+
ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
234+
if (ret < 0) {
235+
LOG_ERR("Fail to configure pinctrl\n");
236+
return ret;
237+
}
238+
239+
cfg->irq_config_func();
240+
241+
return 0;
242+
}
243+
244+
static void ti_ecap_isr(const struct device *dev)
245+
{
246+
struct ti_ecap_capture_data *cpt = &DEV_DATA(dev)->cpt;
247+
struct ti_ecap_regs *regs = DEV_REGS(dev);
248+
uint32_t period = 0;
249+
uint32_t pulse = 0;
250+
uint32_t ecint_en_flg = regs->ECINT_EN_FLG;
251+
252+
if (ecint_en_flg & TI_ECAP_ECINT_EN_CNTOVF_FLG) {
253+
254+
if (cpt->callback != NULL) {
255+
cpt->callback(dev, 0, period, pulse, -ERANGE, cpt->user_data);
256+
}
257+
258+
/* clear event */
259+
regs->ECINT_CLR_FRC |= TI_ECAP_ECINT_CLR_CNTOVF;
260+
} else if (ecint_en_flg & TI_ECAP_ECINT_EN_CEVT3_FLG) {
261+
uint32_t cap2 = regs->CAP2;
262+
uint32_t cap3 = regs->CAP3;
263+
264+
if (cpt->capture_period) {
265+
period = cap2 + cap3;
266+
}
267+
268+
if (cpt->capture_pulse) {
269+
pulse = cap2;
270+
}
271+
272+
if (cpt->callback != NULL) {
273+
cpt->callback(dev, 0, period, pulse, 0, cpt->user_data);
274+
}
275+
276+
/* clear event */
277+
regs->ECINT_CLR_FRC |= TI_ECAP_ECINT_CLR_CEVT3;
278+
}
279+
280+
/* clear global interrupt */
281+
regs->ECINT_CLR_FRC |= TI_ECAP_ECINT_CLR_INT;
282+
}
283+
284+
static DEVICE_API(pwm, ti_ecap_api) = {
285+
.set_cycles = ti_ecap_set_cycles,
286+
.get_cycles_per_sec = ti_ecap_get_cycles_per_sec,
287+
#ifdef CONFIG_PWM_CAPTURE
288+
.enable_capture = ti_ecap_enable_capture,
289+
.disable_capture = ti_ecap_disable_capture,
290+
.configure_capture = ti_ecap_configure_capture,
291+
#endif /* CONFIG_PWM_CAPTURE */
292+
};
293+
294+
#define TI_ECAP_CLK_CONFIG(n) \
295+
COND_CODE_1(DT_INST_CLOCKS_HAS_NAME(n, fck), ( \
296+
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR_BY_NAME(n, fck)), \
297+
.clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL_BY_NAME( \
298+
n, fck, clk_id), \
299+
.clock_frequency = 0 \
300+
), ( \
301+
.clock_dev = NULL, \
302+
.clock_subsys = NULL, \
303+
.clock_frequency = DT_INST_PROP_OR(n, clock_frequency, 0) \
304+
) \
305+
)
306+
307+
#define TI_ECAP_INIT(n) \
308+
static void ti_ecap_irq_config_func_##n(void) \
309+
{ \
310+
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), ti_ecap_isr, \
311+
DEVICE_DT_INST_GET(n), DT_INST_IRQ(n, flags)); \
312+
irq_enable(DT_INST_IRQN(n)); \
313+
} \
314+
\
315+
PINCTRL_DT_INST_DEFINE(n); \
316+
\
317+
static struct ti_ecap_cfg ti_ecap_config_##n = { \
318+
DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)), \
319+
TI_ECAP_CLK_CONFIG(n), \
320+
.irq_config_func = ti_ecap_irq_config_func_##n, \
321+
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
322+
}; \
323+
\
324+
static struct ti_ecap_data ti_ecap_data_##n; \
325+
\
326+
DEVICE_DT_INST_DEFINE(n, ti_ecap_init, NULL, &ti_ecap_data_##n, &ti_ecap_config_##n, \
327+
POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, &ti_ecap_api);
328+
329+
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)