Skip to content

Commit 2f6647d

Browse files
committed
drivers: pwm: add a CH32V00x General-prupose Timer Module (GPTM) driver
The GPTM is a general purpose module with a 16 bit prescaler, 16 bit counter, and 4 compare units that can be used for PWM generation. Signed-off-by: Michael Hope <[email protected]>
1 parent d453fb9 commit 2f6647d

File tree

6 files changed

+204
-0
lines changed

6 files changed

+204
-0
lines changed

drivers/pwm/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ zephyr_library_sources_ifdef(CONFIG_PWM_RENESAS_RA pwm_renesas_ra.c)
5151
zephyr_library_sources_ifdef(CONFIG_PWM_INFINEON_CAT1 pwm_ifx_cat1.c)
5252
zephyr_library_sources_ifdef(CONFIG_PWM_FAKE pwm_fake.c)
5353
zephyr_library_sources_ifdef(CONFIG_PWM_RENESAS_RZ_GPT pwm_renesas_rz_gpt.c)
54+
zephyr_library_sources_ifdef(CONFIG_PWM_WCH_GPTM pwm_wch_gptm.c)
55+
5456
zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c)
5557
zephyr_library_sources_ifdef(CONFIG_PWM_CAPTURE pwm_capture.c)
5658
zephyr_library_sources_ifdef(CONFIG_PWM_SHELL pwm_shell.c)

drivers/pwm/Kconfig

+2
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.wch"
126+
125127
endif # PWM

drivers/pwm/Kconfig.wch

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright (c) 2025 Michael Hope <[email protected]>
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config PWM_WCH_GPTM
5+
bool "WCH General-purpose Timer (GPTM)"
6+
default y
7+
depends on DT_HAS_WCH_GPTM_ENABLED
8+
select PINCTRL
9+
help
10+
PWM driver for the WCH GPTM, such as TIM2 on the CH32V003.

drivers/pwm/pwm_wch_gptm.c

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Copyright (c) 2025 Michael Hope <[email protected]>
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#define DT_DRV_COMPAT wch_gptm
8+
9+
#include <zephyr/drivers/clock_control.h>
10+
#include <zephyr/drivers/pinctrl.h>
11+
#include <zephyr/drivers/pwm.h>
12+
#include <zephyr/dt-bindings/pwm/pwm.h>
13+
14+
#include <ch32fun.h>
15+
16+
/* Each of the 4 channels uses 1 byte of CHCTLR{1,2} */
17+
#define CHCTLR_CHANNEL_MASK 0xFF
18+
/* 'Invalid', i.e. low before any inversion. */
19+
#define CHCTLR_OCXM_INVALID 0x04
20+
/* 'Valid', i.e. high before any inversion. */
21+
#define CHCTLR_OCXM_VALID 0x05
22+
#define CHCTLR_OCXM_PWM_MODE1 0x06
23+
/* Start bit offset for OC{1,3}M */
24+
#define CHCTLR_OCXM_ODD_SHIFT 4
25+
/* Start bit offset for OC{2,4}M */
26+
#define CHCTLR_OCXM_EVEN_SHIFT 12
27+
/* Each of the 4 channels uses 1 nibble of CCER */
28+
#define CCER_MASK (TIM_CC1P | TIM_CC1E)
29+
30+
struct pwm_wch_gptm_config {
31+
TIM_TypeDef *regs;
32+
const struct device *clock_dev;
33+
uint8_t clock_id;
34+
uint16_t prescaler;
35+
const struct pinctrl_dev_config *pin_cfg;
36+
};
37+
38+
static int pwm_wch_gptm_set_cycles(const struct device *dev, uint32_t channel,
39+
uint32_t period_cycles, uint32_t pulse_cycles, pwm_flags_t flags)
40+
{
41+
const struct pwm_wch_gptm_config *config = dev->config;
42+
TIM_TypeDef *regs = config->regs;
43+
uint16_t ocxm;
44+
45+
if (period_cycles > UINT16_MAX) {
46+
return -EINVAL;
47+
}
48+
49+
if (period_cycles == 0) {
50+
ocxm = CHCTLR_OCXM_INVALID;
51+
} else if (pulse_cycles >= period_cycles) {
52+
/* If pulse_cycles == period_cycles then there is a one cycle glitch in the output.
53+
* Turn the output always on instead.*/
54+
ocxm = CHCTLR_OCXM_VALID;
55+
} else {
56+
ocxm = CHCTLR_OCXM_PWM_MODE1;
57+
}
58+
59+
switch (channel) {
60+
case 0:
61+
regs->CH1CVR = pulse_cycles;
62+
regs->CHCTLR1 = (regs->CHCTLR1 & ~TIM_OC1M) | (ocxm << CHCTLR_OCXM_ODD_SHIFT);
63+
break;
64+
case 1:
65+
regs->CH2CVR = pulse_cycles;
66+
regs->CHCTLR1 = (regs->CHCTLR1 & ~TIM_OC2M) | (ocxm << CHCTLR_OCXM_EVEN_SHIFT);
67+
break;
68+
case 2:
69+
regs->CH3CVR = pulse_cycles;
70+
regs->CHCTLR2 = (regs->CHCTLR2 & ~TIM_OC3M) | (ocxm << CHCTLR_OCXM_ODD_SHIFT);
71+
break;
72+
case 3:
73+
regs->CH4CVR = pulse_cycles;
74+
regs->CHCTLR2 = (regs->CHCTLR2 & ~TIM_OC4M) | (ocxm << CHCTLR_OCXM_EVEN_SHIFT);
75+
break;
76+
default:
77+
return -EINVAL;
78+
}
79+
80+
if (period_cycles != 0) {
81+
regs->ATRLR = period_cycles;
82+
}
83+
84+
/* Set the polarity and enable */
85+
uint16_t shift = 4 * channel;
86+
87+
if ((flags & PWM_POLARITY_INVERTED) != 0) {
88+
regs->CCER =
89+
(regs->CCER & ~(CCER_MASK << shift)) | ((TIM_CC1P | TIM_CC1E) << shift);
90+
} else {
91+
regs->CCER = (regs->CCER & ~(CCER_MASK << shift)) | (TIM_CC1E << shift);
92+
}
93+
94+
return 0;
95+
}
96+
97+
static int pwm_wch_gptm_get_cycles_per_sec(const struct device *dev, uint32_t channel,
98+
uint64_t *cycles)
99+
{
100+
const struct pwm_wch_gptm_config *config = dev->config;
101+
clock_control_subsys_t clock_sys = (clock_control_subsys_t *)(uintptr_t)config->clock_id;
102+
uint32_t clock_rate;
103+
int err;
104+
105+
err = clock_control_get_rate(config->clock_dev, clock_sys, &clock_rate);
106+
if (err != 0) {
107+
return err;
108+
}
109+
110+
*cycles = clock_rate / (config->prescaler + 1);
111+
112+
return 0;
113+
}
114+
115+
static const struct pwm_driver_api pwm_wch_gptm_driver_api = {
116+
.set_cycles = pwm_wch_gptm_set_cycles,
117+
.get_cycles_per_sec = pwm_wch_gptm_get_cycles_per_sec,
118+
};
119+
120+
static int pwm_wch_gptm_init(const struct device *dev)
121+
{
122+
const struct pwm_wch_gptm_config *config = dev->config;
123+
TIM_TypeDef *regs = config->regs;
124+
int err;
125+
126+
clock_control_on(config->clock_dev, (clock_control_subsys_t *)(uintptr_t)config->clock_id);
127+
128+
err = pinctrl_apply_state(config->pin_cfg, PINCTRL_STATE_DEFAULT);
129+
if (err != 0) {
130+
return err;
131+
}
132+
133+
/* Disable and configure the counter */
134+
regs->CTLR1 = TIM_ARPE & ~TIM_CEN;
135+
regs->PSC = config->prescaler;
136+
regs->CTLR1 |= TIM_CEN;
137+
138+
return 0;
139+
}
140+
141+
#define PWM_WCH_GPTM_INIT(idx) \
142+
PINCTRL_DT_INST_DEFINE(idx); \
143+
\
144+
static const struct pwm_wch_gptm_config pwm_wch_gptm_##idx##_config = { \
145+
.regs = (TIM_TypeDef *)DT_INST_REG_ADDR(idx), \
146+
.prescaler = DT_INST_PROP(idx, prescaler), \
147+
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(idx)), \
148+
.clock_id = DT_INST_CLOCKS_CELL(idx, id), \
149+
.pin_cfg = PINCTRL_DT_INST_DEV_CONFIG_GET(idx), \
150+
}; \
151+
\
152+
DEVICE_DT_INST_DEFINE(idx, &pwm_wch_gptm_init, NULL, NULL, &pwm_wch_gptm_##idx##_config, \
153+
POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, &pwm_wch_gptm_driver_api);
154+
155+
DT_INST_FOREACH_STATUS_OKAY(PWM_WCH_GPTM_INIT)

dts/bindings/pwm/wch,gptm.yaml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright (c) 2025 Michael Hope <[email protected]>
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: WCH General-purpose Timer (GPTM)
5+
6+
compatible: "wch,gptm"
7+
8+
include: [base.yaml, pwm-controller.yaml, pinctrl-device.yaml]
9+
10+
properties:
11+
prescaler:
12+
type: int
13+
default: 0
14+
description: |
15+
Counter prescaler from 0 to 65535. The clock frequency to the counter
16+
is the input frequency divided by (prescaler + 1). For example, if the
17+
input clock is 48 MHz and the desired counter clock is 1 MHz, set this
18+
property to 47.
19+
20+
"#pwm-cells":
21+
const: 3
22+
23+
pwm-cells:
24+
- channel
25+
- period
26+
- flags

dts/riscv/wch/ch32v0/ch32v003.dtsi

+9
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@
126126
interrupts = <22>, <23>, <24>, <25>, <26>, <27>, <28>;
127127
dma-channels = <7>;
128128
};
129+
130+
tim2: counter@40000000 {
131+
compatible = "wch,gptm";
132+
reg = <0x40000000 16>;
133+
pwm-controller;
134+
#pwm-cells = <3>;
135+
prescaler = <1>;
136+
clocks = <&rcc CH32V00X_CLOCK_TIM2>;
137+
};
129138
};
130139
};
131140

0 commit comments

Comments
 (0)