From b1aadb67290a38380a1325a4a4654aeb28dc5a3e Mon Sep 17 00:00:00 2001 From: Michael Hope Date: Mon, 24 Jun 2024 20:02:18 +0200 Subject: [PATCH] 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 --- drivers/pwm/CMakeLists.txt | 2 + drivers/pwm/Kconfig | 2 + drivers/pwm/Kconfig.wch | 10 ++ drivers/pwm/pwm_wch_gptm.c | 157 +++++++++++++++++++++++++++++ dts/bindings/counter/wch,gptm.yaml | 29 ++++++ dts/bindings/pwm/wch,gptm-pwm.yaml | 17 ++++ dts/riscv/wch/ch32v0/ch32v003.dtsi | 19 ++++ 7 files changed, 236 insertions(+) create mode 100644 drivers/pwm/Kconfig.wch create mode 100644 drivers/pwm/pwm_wch_gptm.c create mode 100644 dts/bindings/counter/wch,gptm.yaml create mode 100644 dts/bindings/pwm/wch,gptm-pwm.yaml diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index 5b44a63ed38..b0021128d93 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -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) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 1c6c55e45ea..68bb58bf6c8 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -126,4 +126,6 @@ source "drivers/pwm/Kconfig.rts5912" source "drivers/pwm/Kconfig.neorv32" +source "drivers/pwm/Kconfig.wch" + endif # PWM diff --git a/drivers/pwm/Kconfig.wch b/drivers/pwm/Kconfig.wch new file mode 100644 index 00000000000..4bd8962b94d --- /dev/null +++ b/drivers/pwm/Kconfig.wch @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Michael Hope +# 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. diff --git a/drivers/pwm/pwm_wch_gptm.c b/drivers/pwm/pwm_wch_gptm.c new file mode 100644 index 00000000000..b60ae1bd1a7 --- /dev/null +++ b/drivers/pwm/pwm_wch_gptm.c @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2025 Michael Hope + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT wch_gptm_pwm + +#include +#include +#include +#include + +#include + +/* 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) diff --git a/dts/bindings/counter/wch,gptm.yaml b/dts/bindings/counter/wch,gptm.yaml new file mode 100644 index 00000000000..f92a0b2ee63 --- /dev/null +++ b/dts/bindings/counter/wch,gptm.yaml @@ -0,0 +1,29 @@ +# Copyright (c) 2025 Michael Hope +# 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. diff --git a/dts/bindings/pwm/wch,gptm-pwm.yaml b/dts/bindings/pwm/wch,gptm-pwm.yaml new file mode 100644 index 00000000000..01b8772645c --- /dev/null +++ b/dts/bindings/pwm/wch,gptm-pwm.yaml @@ -0,0 +1,17 @@ +# Copyright (c) 2025 Michael Hope +# 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 diff --git a/dts/riscv/wch/ch32v0/ch32v003.dtsi b/dts/riscv/wch/ch32v0/ch32v003.dtsi index 121d4d64d96..52b2b074da9 100644 --- a/dts/riscv/wch/ch32v0/ch32v003.dtsi +++ b/dts/riscv/wch/ch32v0/ch32v003.dtsi @@ -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"; + }; + }; }; };