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_ */