Skip to content

Commit 830fe6e

Browse files
Terezventkartben
authored andcommitted
drivers: pwm: add a SAM0 TC based PWM driver
This runs the Timer/counter in 'normal' PWM mode (for 8-bits) and in 'match' PWM mode (for 16-bits). Signed-off-by: Teresa Zepeda Ventura <[email protected]>
1 parent b98bede commit 830fe6e

File tree

4 files changed

+234
-0
lines changed

4 files changed

+234
-0
lines changed

drivers/pwm/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_RV32M1_TPM pwm_rv32m1_tpm.c)
2626
zephyr_library_sources_ifdef(CONFIG_PWM_MAX32 pwm_max32.c)
2727
zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_TPM pwm_mcux_tpm.c)
2828
zephyr_library_sources_ifdef(CONFIG_PWM_SAM0_TCC pwm_sam0_tcc.c)
29+
zephyr_library_sources_ifdef(CONFIG_PWM_SAM0_TC pwm_sam0_tc.c)
2930
zephyr_library_sources_ifdef(CONFIG_PWM_NPCX pwm_npcx.c)
3031
zephyr_library_sources_ifdef(CONFIG_PWM_XLNX_AXI_TIMER pwm_xlnx_axi_timer.c)
3132
zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_PWT pwm_mcux_pwt.c)

drivers/pwm/Kconfig

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

7575
source "drivers/pwm/Kconfig.sam0"
7676

77+
source "drivers/pwm/Kconfig.sam0_tc"
78+
7779
source "drivers/pwm/Kconfig.npcx"
7880

7981
source "drivers/pwm/Kconfig.xlnx"

drivers/pwm/Kconfig.sam0_tc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Atmel SAM0 TCC as PWM configuration
2+
3+
# Copyright (c) 2020 Google LLC
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
config PWM_SAM0_TC
7+
bool "Atmel SAM0 MCU Family TC PWM Driver"
8+
default y
9+
depends on DT_HAS_ATMEL_SAM0_TC_PWM_ENABLED
10+
help
11+
Enable PWM driver for Atmel SAM0 MCUs using the TC timer/counter.
12+
13+
config PWM_TC_INIT_PRIORITY
14+
int "Init Priority"
15+
default 50
16+
help
17+
Device driver initialization priority

drivers/pwm/pwm_sam0_tc.c

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/*
2+
* Copyright (c) 2024 Daikin Comfort Technologies North America, Inc.
3+
*
4+
* Heavily based on pwm_sam0_tcc.c, which is:
5+
* Copyright (c) 2020 Google LLC.
6+
*
7+
* SPDX-License-Identifier: Apache-2.0
8+
*/
9+
10+
/*
11+
* PWM driver using the SAM0 Timer/Counter (TC) Supports the SAMD21 and SAMD5x series,
12+
* 8 and 16 bit counter size is supported.
13+
*
14+
* The 8-bit counter operates in Normal PWM (NPWM) mode, it supports pulse width and period
15+
* values between 0 and 255. It is ideal for applications requiring moderate frequency PWM,
16+
* however, it is not suitable for high-precision or low-frequency applications.
17+
*
18+
* The 16-bit counter operates in Match PWM (MPWM) mode to generate the PWM signal.
19+
* this mode sacrifices the timer's CC0 channel in order to achieve pulse width modulation.
20+
*/
21+
22+
#define DT_DRV_COMPAT atmel_sam0_tc_pwm
23+
24+
#include <zephyr/kernel.h>
25+
#include <zephyr/device.h>
26+
#include <errno.h>
27+
#include <zephyr/drivers/pwm.h>
28+
#include <zephyr/drivers/pinctrl.h>
29+
#include <soc.h>
30+
31+
/* Static configuration */
32+
struct pwm_sam0_config {
33+
Tc *regs;
34+
const struct pinctrl_dev_config *pcfg;
35+
uint8_t channels;
36+
uint8_t counter_size;
37+
uint16_t prescaler;
38+
uint32_t freq;
39+
40+
#ifdef MCLK
41+
volatile uint32_t *mclk;
42+
uint32_t mclk_mask;
43+
uint16_t gclk_id;
44+
#else
45+
uint32_t pm_apbcmask;
46+
uint16_t gclk_clkctrl_id;
47+
#endif
48+
};
49+
50+
#define COUNTER_8BITS 8U
51+
52+
/* Wait for the peripheral to finish all commands */
53+
static void wait_synchronization(Tc *regs, uint8_t counter_size)
54+
{
55+
if (COUNTER_8BITS == counter_size) {
56+
while (regs->COUNT8.SYNCBUSY.reg != 0) {
57+
}
58+
} else {
59+
while (regs->COUNT16.SYNCBUSY.reg != 0) {
60+
}
61+
}
62+
}
63+
64+
static int pwm_sam0_get_cycles_per_sec(const struct device *dev,
65+
uint32_t channel, uint64_t *cycles)
66+
{
67+
const struct pwm_sam0_config *const cfg = dev->config;
68+
69+
if (channel >= cfg->channels) {
70+
return -EINVAL;
71+
}
72+
73+
*cycles = cfg->freq;
74+
75+
return 0;
76+
}
77+
78+
static int pwm_sam0_set_cycles(const struct device *dev, uint32_t channel, uint32_t period_cycles,
79+
uint32_t pulse_cycles, pwm_flags_t flags)
80+
{
81+
const struct pwm_sam0_config *const cfg = dev->config;
82+
Tc *regs = cfg->regs;
83+
uint8_t counter_size = cfg->counter_size;
84+
uint32_t top = 1 << counter_size;
85+
uint32_t invert_mask = 1 << channel;
86+
bool invert = ((flags & PWM_POLARITY_INVERTED) != 0);
87+
bool inverted;
88+
89+
if (channel >= cfg->channels) {
90+
return -EINVAL;
91+
}
92+
if (period_cycles >= top || pulse_cycles >= top) {
93+
return -EINVAL;
94+
}
95+
96+
/*
97+
* Update the buffered width and period. These will be automatically
98+
* loaded on the next cycle.
99+
*/
100+
if (COUNTER_8BITS == counter_size) {
101+
inverted = ((regs->COUNT8.DRVCTRL.vec.INVEN & invert_mask) != 0);
102+
regs->COUNT8.CCBUF[channel].reg = TC_COUNT8_CCBUF_CCBUF(pulse_cycles);
103+
regs->COUNT8.PERBUF.reg = TC_COUNT8_PERBUF_PERBUF(period_cycles);
104+
wait_synchronization(regs, counter_size);
105+
106+
if (invert != inverted) {
107+
regs->COUNT8.CTRLA.bit.ENABLE = 0;
108+
wait_synchronization(regs, counter_size);
109+
110+
regs->COUNT8.DRVCTRL.vec.INVEN ^= invert_mask;
111+
regs->COUNT8.CTRLA.bit.ENABLE = 1;
112+
wait_synchronization(regs, counter_size);
113+
}
114+
} else {
115+
inverted = ((regs->COUNT16.DRVCTRL.vec.INVEN & invert_mask) != 0);
116+
regs->COUNT16.CCBUF[0].reg = TC_COUNT16_CCBUF_CCBUF(period_cycles);
117+
regs->COUNT16.CCBUF[1].reg = TC_COUNT16_CCBUF_CCBUF(pulse_cycles);
118+
wait_synchronization(regs, counter_size);
119+
120+
if (invert != inverted) {
121+
regs->COUNT16.CTRLA.bit.ENABLE = 0;
122+
wait_synchronization(regs, counter_size);
123+
124+
regs->COUNT16.DRVCTRL.vec.INVEN ^= invert_mask;
125+
regs->COUNT16.CTRLA.bit.ENABLE = 1;
126+
wait_synchronization(regs, counter_size);
127+
}
128+
}
129+
130+
return 0;
131+
}
132+
133+
static int pwm_sam0_init(const struct device *dev)
134+
{
135+
const struct pwm_sam0_config *const cfg = dev->config;
136+
int retval;
137+
Tc *regs = cfg->regs;
138+
uint8_t counter_size = cfg->counter_size;
139+
140+
/* Enable the clocks */
141+
#ifdef MCLK
142+
GCLK->PCHCTRL[cfg->gclk_id].reg =
143+
GCLK_PCHCTRL_GEN_GCLK0 | GCLK_PCHCTRL_CHEN;
144+
*cfg->mclk |= cfg->mclk_mask;
145+
#else
146+
GCLK->CLKCTRL.reg = cfg->gclk_clkctrl_id | GCLK_CLKCTRL_GEN_GCLK0 |
147+
GCLK_CLKCTRL_CLKEN;
148+
PM->APBCMASK.reg |= cfg->pm_apbcmask;
149+
#endif
150+
151+
retval = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
152+
if (retval < 0) {
153+
return retval;
154+
}
155+
156+
if (COUNTER_8BITS == counter_size) {
157+
regs->COUNT8.CTRLA.bit.SWRST = 1;
158+
wait_synchronization(regs, counter_size);
159+
160+
regs->COUNT8.CTRLA.reg = cfg->prescaler | TC_CTRLA_MODE_COUNT8 |
161+
TC_CTRLA_PRESCSYNC_PRESC;
162+
regs->COUNT8.WAVE.reg = TC_WAVE_WAVEGEN_NPWM;
163+
regs->COUNT8.PER.reg = TC_COUNT8_PER_PER(1);
164+
165+
regs->COUNT8.CTRLA.bit.ENABLE = 1;
166+
wait_synchronization(regs, counter_size);
167+
} else {
168+
regs->COUNT16.CTRLA.bit.SWRST = 1;
169+
wait_synchronization(regs, counter_size);
170+
171+
regs->COUNT16.CTRLA.reg = cfg->prescaler | TC_CTRLA_MODE_COUNT16 |
172+
TC_CTRLA_PRESCSYNC_PRESC;
173+
regs->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MPWM;
174+
regs->COUNT16.CC[0].reg = TC_COUNT16_CC_CC(1);
175+
176+
regs->COUNT16.CTRLA.bit.ENABLE = 1;
177+
wait_synchronization(regs, cfg->counter_size);
178+
}
179+
180+
return 0;
181+
}
182+
183+
static DEVICE_API(pwm, pwm_sam0_driver_api) = {
184+
.set_cycles = pwm_sam0_set_cycles,
185+
.get_cycles_per_sec = pwm_sam0_get_cycles_per_sec,
186+
};
187+
188+
#ifdef MCLK
189+
#define PWM_SAM0_INIT_CLOCKS(inst) \
190+
.mclk = (volatile uint32_t *)MCLK_MASK_DT_INT_REG_ADDR(inst), \
191+
.mclk_mask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(inst, mclk, bit)), \
192+
.gclk_id = DT_INST_CLOCKS_CELL_BY_NAME(inst, gclk, periph_ch)
193+
#else
194+
#define PWM_SAM0_INIT_CLOCKS(inst) \
195+
.pm_apbcmask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(inst, pm, bit)), \
196+
.gclk_clkctrl_id = DT_INST_CLOCKS_CELL_BY_NAME(inst, gclk, clkctrl_id)
197+
#endif
198+
199+
#define PWM_SAM0_INIT(inst) \
200+
PINCTRL_DT_INST_DEFINE(inst); \
201+
static const struct pwm_sam0_config pwm_sam0_config_##inst = { \
202+
.regs = (Tc *)DT_INST_REG_ADDR(inst), \
203+
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
204+
.channels = DT_INST_PROP(inst, channels), \
205+
.counter_size = DT_INST_PROP(inst, counter_size), \
206+
.prescaler = UTIL_CAT(TC_CTRLA_PRESCALER_DIV, DT_INST_PROP(inst, prescaler)), \
207+
.freq = SOC_ATMEL_SAM0_GCLK0_FREQ_HZ / DT_INST_PROP(inst, prescaler), \
208+
PWM_SAM0_INIT_CLOCKS(inst), \
209+
}; \
210+
\
211+
DEVICE_DT_INST_DEFINE(inst, &pwm_sam0_init, NULL, NULL, &pwm_sam0_config_##inst, \
212+
POST_KERNEL, CONFIG_PWM_TC_INIT_PRIORITY, &pwm_sam0_driver_api);
213+
214+
DT_INST_FOREACH_STATUS_OKAY(PWM_SAM0_INIT)

0 commit comments

Comments
 (0)