From 9cf690b315025fddb87a8500e0260387bcf089d5 Mon Sep 17 00:00:00 2001 From: Michael Hope Date: Sat, 26 Apr 2025 12:17:30 +0000 Subject: [PATCH 1/3] drivers: interrupt_controller: add a WCH EXTI external interrupt driver The WCH External Trigger and Interrupt controller (EXTI) supports between 8 and 22 lines where each line can trigger an interrupt on rising edge, falling edge, or both edges. Lines are assigned to a group, and each group has a separate interrupt. On the CH32V003/6, there is one group of 8 lines, while on the CH32V208 there are multiple groups with between one and six lines per group. In the same way as the STM32 and GD32, define an EXTI driver that configures the peripheral and an internal interface that can configure individual lines. Signed-off-by: Michael Hope --- drivers/interrupt_controller/CMakeLists.txt | 1 + drivers/interrupt_controller/Kconfig | 2 + drivers/interrupt_controller/Kconfig.wch_exti | 10 ++ drivers/interrupt_controller/intc_wch_exti.c | 135 ++++++++++++++++++ .../interrupt-controller/wch,exti.yaml | 36 +++++ dts/riscv/wch/ch32v0/ch32v003.dtsi | 13 ++ dts/riscv/wch/ch32v0/ch32v006.dtsi | 13 ++ dts/riscv/wch/ch32v208/ch32v208.dtsi | 15 ++ .../drivers/interrupt_controller/wch_exti.h | 41 ++++++ 9 files changed, 266 insertions(+) create mode 100644 drivers/interrupt_controller/Kconfig.wch_exti create mode 100644 drivers/interrupt_controller/intc_wch_exti.c create mode 100644 dts/bindings/interrupt-controller/wch,exti.yaml create mode 100644 include/zephyr/drivers/interrupt_controller/wch_exti.h diff --git a/drivers/interrupt_controller/CMakeLists.txt b/drivers/interrupt_controller/CMakeLists.txt index 2bb2dbe16336..b652c748470e 100644 --- a/drivers/interrupt_controller/CMakeLists.txt +++ b/drivers/interrupt_controller/CMakeLists.txt @@ -45,6 +45,7 @@ zephyr_library_sources_ifdef(CONFIG_RENESAS_RZ_EXT_IRQ intc_renesas_rz_ext_ zephyr_library_sources_ifdef(CONFIG_NXP_IRQSTEER intc_nxp_irqsteer.c) zephyr_library_sources_ifdef(CONFIG_INTC_MTK_ADSP intc_mtk_adsp.c) zephyr_library_sources_ifdef(CONFIG_WCH_PFIC intc_wch_pfic.c) +zephyr_library_sources_ifdef(CONFIG_WCH_EXTI intc_wch_exti.c) if(CONFIG_INTEL_VTD_ICTL) zephyr_library_include_directories(${ZEPHYR_BASE}/arch/x86/include) diff --git a/drivers/interrupt_controller/Kconfig b/drivers/interrupt_controller/Kconfig index 3468cf4f3f23..a5715de368c7 100644 --- a/drivers/interrupt_controller/Kconfig +++ b/drivers/interrupt_controller/Kconfig @@ -110,4 +110,6 @@ source "drivers/interrupt_controller/Kconfig.mtk_adsp" source "drivers/interrupt_controller/Kconfig.wch_pfic" +source "drivers/interrupt_controller/Kconfig.wch_exti" + endmenu diff --git a/drivers/interrupt_controller/Kconfig.wch_exti b/drivers/interrupt_controller/Kconfig.wch_exti new file mode 100644 index 000000000000..d33739ef1865 --- /dev/null +++ b/drivers/interrupt_controller/Kconfig.wch_exti @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Michael Hope +# SPDX-License-Identifier: Apache-2.0 + +config WCH_EXTI + bool "WCH CH32V00x/20x/30x External Interrupt and Event Controller (EXTI)" + default y + depends on DT_HAS_WCH_EXTI_ENABLED + help + Enable the WCH CH32V00x/20x/30x External Interrupt and Event Controller (EXTI) + driver. diff --git a/drivers/interrupt_controller/intc_wch_exti.c b/drivers/interrupt_controller/intc_wch_exti.c new file mode 100644 index 000000000000..846a27b0b62c --- /dev/null +++ b/drivers/interrupt_controller/intc_wch_exti.c @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2025 Michael Hope + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT wch_exti + +#include + +#include +#include +#include +#include + +#include + +#define WCH_EXTI_NUM_LINES DT_PROP(DT_NODELABEL(exti), num_lines) + +/* Per EXTI callback registration */ +struct wch_exti_registration { + wch_exti_callback_handler_t callback; + void *user; +}; + +struct wch_exti_data { + struct wch_exti_registration callbacks[WCH_EXTI_NUM_LINES]; +}; + +#define WCH_EXTI_INIT_RANGE(node_id, interrupts, idx) \ + DT_PROP_BY_IDX(node_id, line_ranges, UTIL_X2(idx)), + +/* + * List of [start, end) line ranges for each line group, where the range for group n is + * `[wch_exti_ranges[n-1]...wch_exti_ranges[n])`. This uses the fact that the ranges are contiguous, + * so the end of group n is the same as the start of group n+1. + */ +static const uint8_t wch_exti_ranges[] = { + DT_FOREACH_PROP_ELEM(DT_NODELABEL(exti), interrupt_names, WCH_EXTI_INIT_RANGE) + WCH_EXTI_NUM_LINES, +}; + +#define WCH_EXTI_INIT_INTERRUPT(node_id, interrupts, idx) DT_IRQ_BY_IDX(node_id, idx, irq), + +/* Interrupt number for each line group. Used when enabling the interrupt. */ +static const uint8_t wch_exti_interrupts[] = { + DT_FOREACH_PROP_ELEM(DT_NODELABEL(exti), interrupt_names, WCH_EXTI_INIT_INTERRUPT)}; + +BUILD_ASSERT(ARRAY_SIZE(wch_exti_interrupts) + 1 == ARRAY_SIZE(wch_exti_ranges)); + +static void wch_exti_isr(const void *user) +{ + const struct device *const dev = DEVICE_DT_INST_GET(0); + struct wch_exti_data *data = dev->data; + EXTI_TypeDef *regs = (EXTI_TypeDef *)DT_INST_REG_ADDR(0); + const uint8_t *range = user; + uint32_t intfr = regs->INTFR; + + for (uint8_t line = range[0]; line < range[1]; line++) { + if ((intfr & BIT(line)) != 0) { + const struct wch_exti_registration *callback = &data->callbacks[line]; + /* Clear the interrupt */ + regs->INTFR = BIT(line); + if (callback->callback != NULL) { + callback->callback(line, callback->user); + } + } + } +} + +void wch_exti_enable(uint8_t line) +{ + EXTI_TypeDef *regs = (EXTI_TypeDef *)DT_INST_REG_ADDR(0); + + regs->INTENR |= BIT(line); + /* Find the corresponding interrupt and enable it */ + for (uint8_t i = 1; i < ARRAY_SIZE(wch_exti_ranges); i++) { + if (line < wch_exti_ranges[i]) { + irq_enable(wch_exti_interrupts[i - 1]); + break; + } + } +} + +void wch_exti_disable(uint8_t line) +{ + EXTI_TypeDef *regs = (EXTI_TypeDef *)DT_INST_REG_ADDR(0); + + regs->INTENR &= ~BIT(line); +} + +int wch_exti_configure(uint8_t line, wch_exti_callback_handler_t callback, void *user) +{ + const struct device *const dev = DEVICE_DT_INST_GET(0); + struct wch_exti_data *data = dev->data; + struct wch_exti_registration *registration = &data->callbacks[line]; + + if (registration->callback == callback && registration->user == user) { + return 0; + } + + if (callback != NULL && registration->callback != NULL) { + return -EALREADY; + } + + registration->callback = callback; + registration->user = user; + + return 0; +} + +void wch_exti_set_trigger(uint8_t line, enum wch_exti_trigger trigger) +{ + EXTI_TypeDef *regs = (EXTI_TypeDef *)DT_INST_REG_ADDR(0); + + WRITE_BIT(regs->RTENR, line, (trigger & WCH_EXTI_TRIGGER_RISING_EDGE) != 0); + WRITE_BIT(regs->FTENR, line, (trigger & WCH_EXTI_TRIGGER_FALLING_EDGE) != 0); +} + +#define WCH_EXTI_CONNECT_IRQ(node_id, interrupts, idx) \ + IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, idx, irq), DT_IRQ_BY_IDX(node_id, idx, priority), \ + wch_exti_isr, &wch_exti_ranges[idx], 0); + +static int wch_exti_init(const struct device *dev) +{ + /* Generate the registrations for each interrupt */ + DT_FOREACH_PROP_ELEM(DT_NODELABEL(exti), interrupt_names, WCH_EXTI_CONNECT_IRQ); + + return 0; +} + +static struct wch_exti_data wch_exti_data_0; + +DEVICE_DT_INST_DEFINE(0, wch_exti_init, NULL, &wch_exti_data_0, NULL, PRE_KERNEL_2, + CONFIG_INTC_INIT_PRIORITY, NULL); diff --git a/dts/bindings/interrupt-controller/wch,exti.yaml b/dts/bindings/interrupt-controller/wch,exti.yaml new file mode 100644 index 000000000000..3e3cba4d82b3 --- /dev/null +++ b/dts/bindings/interrupt-controller/wch,exti.yaml @@ -0,0 +1,36 @@ +# Copyright (c) 2025 Michael Hope +# SPDX-License-Identifier: Apache-2.0 + +description: WCH CH32V003/20x/30x External Interrupt and Event Controller (EXTI) + +compatible: "wch,exti" + +include: [base.yaml, interrupt-controller.yaml] + +properties: + reg: + required: true + + interrupts: + required: true + + num-lines: + type: int + required: true + description: Number of lines supported by the interrupt controller. + + line-ranges: + type: array + required: true + description: | + Describes how the input lines are grouped into ranges. Each range + consists of a (starting line, number of lines) pair and map to + a single interrupt. + + For example: + line-ranges = <0 1>, <1 1>, <2 1>, <3 1>, + <4 1>, <5 5>, <10 6>; + + defines seven ranges where the first five contain one line, the + sixth starts with line 5 and contains five elements (5 to 9), and + the last starts with line 10 and contains six elements (10 to 15). diff --git a/dts/riscv/wch/ch32v0/ch32v003.dtsi b/dts/riscv/wch/ch32v0/ch32v003.dtsi index d28667135dbe..f88fc2b8e9b3 100644 --- a/dts/riscv/wch/ch32v0/ch32v003.dtsi +++ b/dts/riscv/wch/ch32v0/ch32v003.dtsi @@ -72,6 +72,19 @@ status = "disabled"; }; + exti: interrupt-controller@40010400 { + compatible = "wch,exti"; + interrupt-controller; + #interrupt-cells = <1>; + reg = <0x40010400 16>; + interrupt-parent = <&pfic>; + num-lines = <8>; + interrupts = <20>; + line-ranges = <0 8>; + interrupt-names = "line0-7"; + status = "disabled"; + }; + pinctrl: pin-controller@40010000 { compatible = "wch,afio"; reg = <0x40010000 0x10>; diff --git a/dts/riscv/wch/ch32v0/ch32v006.dtsi b/dts/riscv/wch/ch32v0/ch32v006.dtsi index dd6cac5e0549..7aa311886536 100644 --- a/dts/riscv/wch/ch32v0/ch32v006.dtsi +++ b/dts/riscv/wch/ch32v0/ch32v006.dtsi @@ -72,6 +72,19 @@ status = "disabled"; }; + exti: interrupt-controller@40010400 { + compatible = "wch,exti"; + interrupt-controller; + #interrupt-cells = <1>; + reg = <0x40010400 16>; + interrupt-parent = <&pfic>; + num-lines = <8>; + interrupts = <20>; + line-ranges = <0 8>; + interrupt-names = "line0-7"; + status = "disabled"; + }; + pinctrl: pin-controller@40010000 { compatible = "wch,00x-afio"; reg = <0x40010000 0x10>; diff --git a/dts/riscv/wch/ch32v208/ch32v208.dtsi b/dts/riscv/wch/ch32v208/ch32v208.dtsi index 659fb1e62b5e..c049e1241bd6 100644 --- a/dts/riscv/wch/ch32v208/ch32v208.dtsi +++ b/dts/riscv/wch/ch32v208/ch32v208.dtsi @@ -71,6 +71,21 @@ status = "disabled"; }; + exti: interrupt-controller@40010400 { + compatible = "wch,exti"; + interrupt-controller; + #interrupt-cells = <1>; + reg = <0x40010400 16>; + interrupt-parent = <&pfic>; + num-lines = <16>; + line-ranges = <0 1>, <1 1>, <2 1>, <3 1>, <4 1>, + <5 5>, <10 6>; + interrupts = <22 23 24 25 26 39 56>; + interrupt-names = "line0", "line1", "line2", "line3", + "line4", "line5-9", "line10-15"; + status = "disabled"; + }; + pinctrl: pin-controller@40010000 { compatible = "wch,20x_30x-afio"; reg = <0x40010000 16>; diff --git a/include/zephyr/drivers/interrupt_controller/wch_exti.h b/include/zephyr/drivers/interrupt_controller/wch_exti.h new file mode 100644 index 000000000000..25baf0d5d66d --- /dev/null +++ b/include/zephyr/drivers/interrupt_controller/wch_exti.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Michael Hope + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_INTERRUPT_CONTROLLER_WCH_EXTI_H_ +#define ZEPHYR_INCLUDE_DRIVERS_INTERRUPT_CONTROLLER_WCH_EXTI_H_ + +#include + +#include + +/* Callback for EXTI interrupt. */ +typedef void (*wch_exti_callback_handler_t)(uint8_t line, void *user); + +enum wch_exti_trigger { + /* + * Note that this is a flag set and these values can be ORed to trigger on + * both edges. + */ + + /* Trigger on rising edge */ + WCH_EXTI_TRIGGER_RISING_EDGE = BIT(0), + /* Trigger on falling edge */ + WCH_EXTI_TRIGGER_FALLING_EDGE = BIT(1), +}; + +/* Enable the EXTI interrupt for `line` */ +void wch_exti_enable(uint8_t line); + +/* Disable the EXTI interrupt for `line` */ +void wch_exti_disable(uint8_t line); + +/* Set the trigger mode for `line` */ +void wch_exti_set_trigger(uint8_t line, enum wch_exti_trigger trigger); + +/* Register a callback for `line` */ +int wch_exti_configure(uint8_t line, wch_exti_callback_handler_t callback, void *user); + +#endif /* ZEPHYR_INCLUDE_DRIVERS_INTERRUPT_CONTROLLER_WCH_EXTI_H_ */ From 861cf7ac950471062b87a6f05eece45c87271f42 Mon Sep 17 00:00:00 2001 From: Michael Hope Date: Sat, 26 Apr 2025 14:39:19 +0000 Subject: [PATCH 2/3] drivers: gpio: add interrupt support for the CH32V family The WCH GPIO peripheral integrates with the EXTI and supports firing interrupts when a GPIO pin changes. Add optional support for firing a callback on rising edge, falling edge, or both edges. Tested on the `linkw` and the `ch32v006evt` using `samples/basic/button`. Signed-off-by: Michael Hope --- drivers/gpio/Kconfig.wch_ch32v00x | 9 ++ drivers/gpio/wch_gpio_ch32v00x.c | 131 ++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/drivers/gpio/Kconfig.wch_ch32v00x b/drivers/gpio/Kconfig.wch_ch32v00x index 9f7c447f17d4..c38d635cc753 100644 --- a/drivers/gpio/Kconfig.wch_ch32v00x +++ b/drivers/gpio/Kconfig.wch_ch32v00x @@ -5,3 +5,12 @@ config GPIO_WCH_GPIO bool "WCH CH32V00x GPIO driver" depends on DT_HAS_WCH_GPIO_ENABLED default y + +config GPIO_WCH_GPIO_INTERRUPTS + bool "Interrupt support" + depends on GPIO_WCH_GPIO + depends on DT_HAS_WCH_EXTI_ENABLED + default y + help + Support triggering an interrupt on pin change. Uses approximately + 700 bytes of flash and 60 bytes of RAM. diff --git a/drivers/gpio/wch_gpio_ch32v00x.c b/drivers/gpio/wch_gpio_ch32v00x.c index 1d901b3ebf31..76e660ec2bd7 100644 --- a/drivers/gpio/wch_gpio_ch32v00x.c +++ b/drivers/gpio/wch_gpio_ch32v00x.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,7 @@ struct gpio_ch32v00x_config { struct gpio_ch32v00x_data { struct gpio_driver_data common; + sys_slist_t callbacks; }; static int gpio_ch32v00x_configure(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) @@ -111,6 +113,131 @@ static int gpio_ch32v00x_port_toggle_bits(const struct device *dev, uint32_t pin return 0; } +#if defined(CONFIG_GPIO_WCH_GPIO_INTERRUPTS) + +static void gpio_ch32v00x_isr(uint8_t line, void *user) +{ + const struct device *dev = user; + struct gpio_ch32v00x_data *data = dev->data; + + gpio_fire_callbacks(&data->callbacks, dev, BIT(line)); +} + +static int gpio_ch32v00x_configure_exti(const struct device *dev, gpio_pin_t pin) +{ + const struct gpio_ch32v00x_config *config = dev->config; + AFIO_TypeDef *afio = (AFIO_TypeDef *)DT_REG_ADDR(DT_NODELABEL(pinctrl)); + uint8_t port_id; + uint8_t cr_id; + uint8_t bit0; + + /* Convert the device into a port ID by checking the address */ + switch ((uintptr_t)config->regs) { + case DT_REG_ADDR(DT_NODELABEL(gpioa)): + port_id = 0; + break; +#if DT_NODE_EXISTS(DT_NODELABEL(gpiob)) + case DT_REG_ADDR(DT_NODELABEL(gpiob)): + port_id = 1; + break; +#endif + case DT_REG_ADDR(DT_NODELABEL(gpioc)): + port_id = 2; + break; + case DT_REG_ADDR(DT_NODELABEL(gpiod)): + port_id = 3; + break; +#if DT_NODE_EXISTS(DT_NODELABEL(gpioe)) + case DT_REG_ADDR(DT_NODELABEL(gpioe)): + port_id = 4; + break; +#endif + default: + return -EINVAL; + } + +#if defined(AFIO_EXTICR_EXTI0) + /* CH32V003 style with one register with 2 bits per map. */ + BUILD_ASSERT(AFIO_EXTICR_EXTI0 == 0x03); + + (void)cr_id; + bit0 = pin << 1; + afio->EXTICR = (afio->EXTICR & ~(AFIO_EXTICR_EXTI0 << bit0)) | (port_id << bit0); +#elif defined(AFIO_EXTICR1_EXTI0) + /* + * CH32V20x style with multiple registers with 4 pins per register and 4 bits per + * map. + */ + BUILD_ASSERT(AFIO_EXTICR1_EXTI0 == 0x0F); + BUILD_ASSERT(ARRAY_SIZE(afio->EXTICR) == 4); + + cr_id = pin / 4; + bit0 = (pin % 4) * 4; + afio->EXTICR[cr_id] = + (afio->EXTICR[cr_id] & ~(AFIO_EXTICR1_EXTI0 << bit0)) | (port_id << bit0); +#else +#error Unrecognised EXTICR format +#endif + + return 0; +} + +static int gpio_ch32v00x_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin, + enum gpio_int_mode mode, + enum gpio_int_trig trigger) +{ + int err; + + switch (mode) { + case GPIO_INT_MODE_DISABLED: + wch_exti_disable(pin); + err = wch_exti_configure(pin, NULL, NULL); + break; + case GPIO_INT_MODE_EDGE: + err = wch_exti_configure(pin, gpio_ch32v00x_isr, (void *)dev); + if (err != 0) { + break; + } + + err = gpio_ch32v00x_configure_exti(dev, pin); + if (err != 0) { + break; + } + + switch (trigger) { + case GPIO_INT_TRIG_LOW: + wch_exti_set_trigger(pin, WCH_EXTI_TRIGGER_FALLING_EDGE); + break; + case GPIO_INT_TRIG_HIGH: + wch_exti_set_trigger(pin, WCH_EXTI_TRIGGER_RISING_EDGE); + break; + case GPIO_INT_TRIG_BOTH: + wch_exti_set_trigger(pin, WCH_EXTI_TRIGGER_FALLING_EDGE | + WCH_EXTI_TRIGGER_RISING_EDGE); + break; + default: + return -ENOTSUP; + } + + wch_exti_enable(pin); + break; + default: + return -ENOTSUP; + } + + return err; +} + +static int gpio_ch32v00x_manage_callback(const struct device *dev, struct gpio_callback *callback, + bool set) +{ + struct gpio_ch32v00x_data *data = dev->data; + + return gpio_manage_callback(&data->callbacks, callback, set); +} + +#endif /* CONFIG_GPIO_WCH_GPIO_INTERRUPTS */ + static DEVICE_API(gpio, gpio_ch32v00x_driver_api) = { .pin_configure = gpio_ch32v00x_configure, .port_get_raw = gpio_ch32v00x_port_get_raw, @@ -118,6 +245,10 @@ static DEVICE_API(gpio, gpio_ch32v00x_driver_api) = { .port_set_bits_raw = gpio_ch32v00x_port_set_bits_raw, .port_clear_bits_raw = gpio_ch32v00x_port_clear_bits_raw, .port_toggle_bits = gpio_ch32v00x_port_toggle_bits, +#if defined(CONFIG_GPIO_WCH_GPIO_INTERRUPTS) + .pin_interrupt_configure = gpio_ch32v00x_pin_interrupt_configure, + .manage_callback = gpio_ch32v00x_manage_callback, +#endif }; static int gpio_ch32v00x_init(const struct device *dev) From da14af035cc98effbe759f7039ad408b0d8f55f6 Mon Sep 17 00:00:00 2001 From: Michael Hope Date: Sat, 26 Apr 2025 12:17:51 +0000 Subject: [PATCH 3/3] boards: enable the WCH EXTI peripheral on all WCH boards Now that Zephyr has support, enable by default. Signed-off-by: Michael Hope --- boards/wch/ch32v003evt/ch32v003evt.dts | 4 ++++ boards/wch/ch32v006evt/ch32v006evt.dts | 4 ++++ boards/wch/linkw/linkw.dts | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/boards/wch/ch32v003evt/ch32v003evt.dts b/boards/wch/ch32v003evt/ch32v003evt.dts index c81a00fd30b3..84289f32a92e 100644 --- a/boards/wch/ch32v003evt/ch32v003evt.dts +++ b/boards/wch/ch32v003evt/ch32v003evt.dts @@ -68,6 +68,10 @@ clocks = <&pll>; }; +&exti { + status = "okay"; +}; + &gpioc { status = "okay"; }; diff --git a/boards/wch/ch32v006evt/ch32v006evt.dts b/boards/wch/ch32v006evt/ch32v006evt.dts index 2cd0e20abeb0..34e3212f5005 100644 --- a/boards/wch/ch32v006evt/ch32v006evt.dts +++ b/boards/wch/ch32v006evt/ch32v006evt.dts @@ -74,6 +74,10 @@ clocks = <&pll>; }; +&exti { + status = "okay"; +}; + &gpioc { status = "okay"; }; diff --git a/boards/wch/linkw/linkw.dts b/boards/wch/linkw/linkw.dts index 50f749ef28cb..c01bb92736ee 100644 --- a/boards/wch/linkw/linkw.dts +++ b/boards/wch/linkw/linkw.dts @@ -68,6 +68,10 @@ clocks = <&pll>; }; +&exti { + status = "okay"; +}; + &gpioa { status = "okay"; };