From b99b7d14f1122ccb968d6f7e8e201fb735188556 Mon Sep 17 00:00:00 2001 From: Michael Hope Date: Sat, 26 Apr 2025 12:17:30 +0000 Subject: [PATCH] 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 c6cedd026ad..543fdffdf18 100644 --- a/drivers/interrupt_controller/CMakeLists.txt +++ b/drivers/interrupt_controller/CMakeLists.txt @@ -46,6 +46,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 d29db9270c1..3e6e554bc6d 100644 --- a/drivers/interrupt_controller/Kconfig +++ b/drivers/interrupt_controller/Kconfig @@ -112,4 +112,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 00000000000..d33739ef186 --- /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 00000000000..846a27b0b62 --- /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 00000000000..3e3cba4d82b --- /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 d28667135db..f88fc2b8e9b 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 dd6cac5e054..7aa31188653 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 9c7449ebfed..dc2028c8b7f 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 00000000000..25baf0d5d66 --- /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_ */