Browse Source

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.

Use the same style as gd32 where the timer is a counter and the PWM
mode is a child node.

Signed-off-by: Michael Hope <michaelh@juju.nz>
pull/89506/head
Michael Hope 1 year ago committed by Benjamin Cabé
parent
commit
b1aadb6729
  1. 2
      drivers/pwm/CMakeLists.txt
  2. 2
      drivers/pwm/Kconfig
  3. 10
      drivers/pwm/Kconfig.wch
  4. 157
      drivers/pwm/pwm_wch_gptm.c
  5. 29
      dts/bindings/counter/wch,gptm.yaml
  6. 17
      dts/bindings/pwm/wch,gptm-pwm.yaml
  7. 19
      dts/riscv/wch/ch32v0/ch32v003.dtsi

2
drivers/pwm/CMakeLists.txt

@ -52,6 +52,8 @@ zephyr_library_sources_ifdef(CONFIG_PWM_INFINEON_CAT1 pwm_ifx_cat1.c) @@ -52,6 +52,8 @@ zephyr_library_sources_ifdef(CONFIG_PWM_INFINEON_CAT1 pwm_ifx_cat1.c)
zephyr_library_sources_ifdef(CONFIG_PWM_FAKE pwm_fake.c)
zephyr_library_sources_ifdef(CONFIG_PWM_RENESAS_RZ_GPT pwm_renesas_rz_gpt.c)
zephyr_library_sources_ifdef(CONFIG_PWM_NEORV32 pwm_neorv32.c)
zephyr_library_sources_ifdef(CONFIG_PWM_WCH_GPTM pwm_wch_gptm.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c)
zephyr_library_sources_ifdef(CONFIG_PWM_CAPTURE pwm_capture.c)
zephyr_library_sources_ifdef(CONFIG_PWM_SHELL pwm_shell.c)

2
drivers/pwm/Kconfig

@ -126,4 +126,6 @@ source "drivers/pwm/Kconfig.rts5912" @@ -126,4 +126,6 @@ source "drivers/pwm/Kconfig.rts5912"
source "drivers/pwm/Kconfig.neorv32"
source "drivers/pwm/Kconfig.wch"
endif # PWM

10
drivers/pwm/Kconfig.wch

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
# Copyright (c) 2025 Michael Hope <michaelh@juju.nz>
# SPDX-License-Identifier: Apache-2.0
config PWM_WCH_GPTM
bool "WCH General-purpose Timer (GPTM) for PWM"
default y
depends on DT_HAS_WCH_GPTM_PWM_ENABLED
select PINCTRL
help
PWM driver for the WCH GPTM, such as TIM2 on the CH32V003.

157
drivers/pwm/pwm_wch_gptm.c

@ -0,0 +1,157 @@ @@ -0,0 +1,157 @@
/*
* Copyright (c) 2025 Michael Hope <michaelh@juju.nz>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT wch_gptm_pwm
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/dt-bindings/pwm/pwm.h>
#include <ch32fun.h>
/* Each of the 4 channels uses 1 byte of CHCTLR{1,2} */
#define CHCTLR_CHANNEL_MASK 0xFF
/* 'Invalid', i.e. low before any inversion. */
#define CHCTLR_OCXM_INVALID 0x04
/* 'Valid', i.e. high before any inversion. */
#define CHCTLR_OCXM_VALID 0x05
#define CHCTLR_OCXM_PWM_MODE1 0x06
/* Start bit offset for OC{1,3}M */
#define CHCTLR_OCXM_ODD_SHIFT 4
/* Start bit offset for OC{2,4}M */
#define CHCTLR_OCXM_EVEN_SHIFT 12
/* Each of the 4 channels uses 1 nibble of CCER */
#define CCER_MASK (TIM_CC1P | TIM_CC1E)
struct pwm_wch_gptm_config {
TIM_TypeDef *regs;
const struct device *clock_dev;
uint8_t clock_id;
uint16_t prescaler;
const struct pinctrl_dev_config *pin_cfg;
};
static int pwm_wch_gptm_set_cycles(const struct device *dev, uint32_t channel,
uint32_t period_cycles, uint32_t pulse_cycles, pwm_flags_t flags)
{
const struct pwm_wch_gptm_config *config = dev->config;
TIM_TypeDef *regs = config->regs;
uint16_t ocxm;
if (period_cycles > UINT16_MAX) {
return -EINVAL;
}
if (period_cycles == 0) {
ocxm = CHCTLR_OCXM_INVALID;
} else if (pulse_cycles >= period_cycles) {
/*
* If pulse_cycles == period_cycles then there is a one cycle glitch in the output.
* Mitigate by setting the output to 'always on'.
*/
ocxm = CHCTLR_OCXM_VALID;
} else {
ocxm = CHCTLR_OCXM_PWM_MODE1;
}
switch (channel) {
case 0:
regs->CH1CVR = pulse_cycles;
regs->CHCTLR1 = (regs->CHCTLR1 & ~TIM_OC1M) | (ocxm << CHCTLR_OCXM_ODD_SHIFT);
break;
case 1:
regs->CH2CVR = pulse_cycles;
regs->CHCTLR1 = (regs->CHCTLR1 & ~TIM_OC2M) | (ocxm << CHCTLR_OCXM_EVEN_SHIFT);
break;
case 2:
regs->CH3CVR = pulse_cycles;
regs->CHCTLR2 = (regs->CHCTLR2 & ~TIM_OC3M) | (ocxm << CHCTLR_OCXM_ODD_SHIFT);
break;
case 3:
regs->CH4CVR = pulse_cycles;
regs->CHCTLR2 = (regs->CHCTLR2 & ~TIM_OC4M) | (ocxm << CHCTLR_OCXM_EVEN_SHIFT);
break;
default:
return -EINVAL;
}
if (period_cycles != 0) {
regs->ATRLR = period_cycles;
}
/* Set the polarity and enable */
uint16_t shift = 4 * channel;
if ((flags & PWM_POLARITY_INVERTED) != 0) {
regs->CCER =
(regs->CCER & ~(CCER_MASK << shift)) | ((TIM_CC1P | TIM_CC1E) << shift);
} else {
regs->CCER = (regs->CCER & ~(CCER_MASK << shift)) | (TIM_CC1E << shift);
}
return 0;
}
static int pwm_wch_gptm_get_cycles_per_sec(const struct device *dev, uint32_t channel,
uint64_t *cycles)
{
const struct pwm_wch_gptm_config *config = dev->config;
clock_control_subsys_t clock_sys = (clock_control_subsys_t *)(uintptr_t)config->clock_id;
uint32_t clock_rate;
int err;
err = clock_control_get_rate(config->clock_dev, clock_sys, &clock_rate);
if (err != 0) {
return err;
}
*cycles = clock_rate / (config->prescaler + 1);
return 0;
}
static const struct pwm_driver_api pwm_wch_gptm_driver_api = {
.set_cycles = pwm_wch_gptm_set_cycles,
.get_cycles_per_sec = pwm_wch_gptm_get_cycles_per_sec,
};
static int pwm_wch_gptm_init(const struct device *dev)
{
const struct pwm_wch_gptm_config *config = dev->config;
TIM_TypeDef *regs = config->regs;
int err;
clock_control_on(config->clock_dev, (clock_control_subsys_t *)(uintptr_t)config->clock_id);
err = pinctrl_apply_state(config->pin_cfg, PINCTRL_STATE_DEFAULT);
if (err != 0) {
return err;
}
/* Disable and configure the counter */
regs->CTLR1 = TIM_ARPE;
regs->PSC = config->prescaler;
regs->CTLR1 |= TIM_CEN;
return 0;
}
#define PWM_WCH_GPTM_INIT(idx) \
PINCTRL_DT_INST_DEFINE(idx); \
\
static const struct pwm_wch_gptm_config pwm_wch_gptm_##idx##_config = { \
.regs = (TIM_TypeDef *)DT_REG_ADDR(DT_INST_PARENT(idx)), \
.prescaler = DT_PROP(DT_INST_PARENT(idx), prescaler), \
.clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_INST_PARENT(idx))), \
.clock_id = DT_CLOCKS_CELL(DT_INST_PARENT(idx), id), \
.pin_cfg = PINCTRL_DT_INST_DEV_CONFIG_GET(idx), \
}; \
\
DEVICE_DT_INST_DEFINE(idx, &pwm_wch_gptm_init, NULL, NULL, &pwm_wch_gptm_##idx##_config, \
POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, &pwm_wch_gptm_driver_api);
DT_INST_FOREACH_STATUS_OKAY(PWM_WCH_GPTM_INIT)

29
dts/bindings/counter/wch,gptm.yaml

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
# Copyright (c) 2025 Michael Hope <michaelh@juju.nz>
# SPDX-License-Identifier: Apache-2.0
description: WCH General-purpose Timer (GPTM) for PWM
compatible: "wch,gptm"
include: [base.yaml]
properties:
reg:
required: true
clocks:
required: true
channels:
type: int
required: true
description: Number of timer channels.
prescaler:
type: int
default: 0
description: |
Counter prescaler from 0 to 65535. The clock frequency to the counter
is the input frequency divided by (prescaler + 1). For example, if the
input clock is 48 MHz and the desired counter clock is 1 MHz, set this
property to 47.

17
dts/bindings/pwm/wch,gptm-pwm.yaml

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
# Copyright (c) 2025 Michael Hope <michaelh@juju.nz>
# SPDX-License-Identifier: Apache-2.0
description: WCH General-purpose Timer (GPTM) for PWM
compatible: "wch,gptm-pwm"
include: [base.yaml, pwm-controller.yaml, pinctrl-device.yaml]
properties:
"#pwm-cells":
const: 3
pwm-cells:
- channel
- period
- flags

19
dts/riscv/wch/ch32v0/ch32v003.dtsi

@ -143,6 +143,25 @@ @@ -143,6 +143,25 @@
interrupts = <22>, <23>, <24>, <25>, <26>, <27>, <28>;
dma-channels = <7>;
};
tim2: counter@40000000 {
compatible = "wch,gptm";
reg = <0x40000000 16>;
prescaler = <1>;
channels = <4>;
clocks = <&rcc CH32V00X_CLOCK_TIM2>;
interrupt-parent = <&pfic>;
interrupts = <38>;
interrupt-names = "global";
status = "disabled";
pwm2: pwm {
compatible = "wch,gptm-pwm";
pwm-controller;
#pwm-cells = <3>;
status = "disabled";
};
};
};
};

Loading…
Cancel
Save