Skip to content

Commit aeb41ac

Browse files
committed
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 <michaelh@juju.nz>
1 parent e92468c commit aeb41ac

File tree

9 files changed

+262
-0
lines changed

9 files changed

+262
-0
lines changed

drivers/interrupt_controller/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ zephyr_library_sources_ifdef(CONFIG_RENESAS_RZ_EXT_IRQ intc_renesas_rz_ext_
4545
zephyr_library_sources_ifdef(CONFIG_NXP_IRQSTEER intc_nxp_irqsteer.c)
4646
zephyr_library_sources_ifdef(CONFIG_INTC_MTK_ADSP intc_mtk_adsp.c)
4747
zephyr_library_sources_ifdef(CONFIG_WCH_PFIC intc_wch_pfic.c)
48+
zephyr_library_sources_ifdef(CONFIG_WCH_EXTI intc_wch_exti.c)
4849

4950
if(CONFIG_INTEL_VTD_ICTL)
5051
zephyr_library_include_directories(${ZEPHYR_BASE}/arch/x86/include)

drivers/interrupt_controller/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,6 @@ source "drivers/interrupt_controller/Kconfig.mtk_adsp"
110110

111111
source "drivers/interrupt_controller/Kconfig.wch_pfic"
112112

113+
source "drivers/interrupt_controller/Kconfig.wch_exti"
114+
113115
endmenu
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright (c) 2025 Michael Hope <michaelh@juju.nz>
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config WCH_EXTI
5+
bool "WCH CH32V00x/20x/30x External Interrupt and Event Controller (EXTI)"
6+
default y
7+
depends on DT_HAS_WCH_EXTI_ENABLED
8+
help
9+
Enable the WCH CH32V00x/20x/30x External Interrupt and Event Controller (EXTI)
10+
driver.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright (c) 2025 Michael Hope <michaelh@juju.nz>
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#define DT_DRV_COMPAT wch_exti
8+
9+
#include <errno.h>
10+
11+
#include <zephyr/device.h>
12+
#include <zephyr/irq.h>
13+
#include <zephyr/sys/util_macro.h>
14+
#include <zephyr/drivers/interrupt_controller/wch_exti.h>
15+
16+
#include <hal_ch32fun.h>
17+
18+
#define WCH_EXTI_NUM_LINES DT_PROP(DT_NODELABEL(exti), num_lines)
19+
20+
/* Per EXTI callback registration */
21+
struct wch_exti_registration {
22+
wch_exti_callback_handler_t callback;
23+
void *user;
24+
};
25+
26+
struct wch_exti_data {
27+
struct wch_exti_registration callbacks[WCH_EXTI_NUM_LINES];
28+
};
29+
30+
#define WCH_EXTI_INIT_RANGE(node_id, interrupts, idx) \
31+
DT_PROP_BY_IDX(node_id, line_ranges, UTIL_X2(idx)),
32+
33+
/*
34+
* List of [start, end) line ranges for each line group, where the range for group n is
35+
* `[wch_exti_ranges[n-1]...wch_exti_ranges[n])`. This uses the fact that the ranges are contiguous,
36+
* so the end of group n is the same as the start of group n+1.
37+
*/
38+
static const uint8_t wch_exti_ranges[] = {
39+
DT_FOREACH_PROP_ELEM(DT_NODELABEL(exti), interrupt_names, WCH_EXTI_INIT_RANGE)
40+
WCH_EXTI_NUM_LINES,
41+
};
42+
43+
#define WCH_EXTI_INIT_INTERRUPT(node_id, interrupts, idx) DT_IRQ_BY_IDX(node_id, idx, irq),
44+
45+
/* Interrupt number for each line group. Used when enabling the interrupt. */
46+
static const uint8_t wch_exti_interrupts[] = {
47+
DT_FOREACH_PROP_ELEM(DT_NODELABEL(exti), interrupt_names, WCH_EXTI_INIT_INTERRUPT)};
48+
49+
BUILD_ASSERT(ARRAY_SIZE(wch_exti_interrupts) + 1 == ARRAY_SIZE(wch_exti_ranges));
50+
51+
static void wch_exti_isr(const void *user)
52+
{
53+
const struct device *const dev = DEVICE_DT_INST_GET(0);
54+
struct wch_exti_data *data = dev->data;
55+
EXTI_TypeDef *regs = (EXTI_TypeDef *)DT_INST_REG_ADDR(0);
56+
const uint8_t *range = user;
57+
uint32_t intfr = regs->INTFR;
58+
59+
for (uint8_t line = range[0]; line < range[1]; line++) {
60+
if ((intfr & BIT(line)) != 0) {
61+
const struct wch_exti_registration *callback = &data->callbacks[line];
62+
/* Clear the interrupt */
63+
regs->INTFR = BIT(line);
64+
if (callback->callback != NULL) {
65+
callback->callback(line, callback->user);
66+
}
67+
}
68+
}
69+
}
70+
71+
void wch_exti_enable(uint8_t line)
72+
{
73+
EXTI_TypeDef *regs = (EXTI_TypeDef *)DT_INST_REG_ADDR(0);
74+
75+
regs->INTENR |= BIT(line);
76+
/* Find the corresponding interrupt and enable it */
77+
for (uint8_t i = 1; i < ARRAY_SIZE(wch_exti_ranges); i++) {
78+
if (line < wch_exti_ranges[i]) {
79+
irq_enable(wch_exti_interrupts[i - 1]);
80+
break;
81+
}
82+
}
83+
}
84+
85+
void wch_exti_disable(uint8_t line)
86+
{
87+
EXTI_TypeDef *regs = (EXTI_TypeDef *)DT_INST_REG_ADDR(0);
88+
89+
regs->INTENR &= ~BIT(line);
90+
}
91+
92+
int wch_exti_configure(uint8_t line, wch_exti_callback_handler_t callback, void *user)
93+
{
94+
const struct device *const dev = DEVICE_DT_INST_GET(0);
95+
struct wch_exti_data *data = dev->data;
96+
struct wch_exti_registration *registration = &data->callbacks[line];
97+
98+
if (callback != NULL && registration->callback != NULL) {
99+
return -EALREADY;
100+
}
101+
102+
registration->callback = callback;
103+
registration->user = user;
104+
105+
return 0;
106+
}
107+
108+
void wch_exti_set_trigger(uint8_t line, enum wch_exti_trigger trigger)
109+
{
110+
EXTI_TypeDef *regs = (EXTI_TypeDef *)DT_INST_REG_ADDR(0);
111+
112+
WRITE_BIT(regs->RTENR, line, (trigger & WCH_EXTI_TRIGGER_RISING_EDGE) != 0);
113+
WRITE_BIT(regs->FTENR, line, (trigger & WCH_EXTI_TRIGGER_FALLING_EDGE) != 0);
114+
}
115+
116+
#define WCH_EXTI_CONNECT_IRQ(node_id, interrupts, idx) \
117+
IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, idx, irq), DT_IRQ_BY_IDX(node_id, idx, priority), \
118+
wch_exti_isr, &wch_exti_ranges[idx], 0);
119+
120+
static int wch_exti_init(const struct device *dev)
121+
{
122+
/* Generate the registrations for each interrupt */
123+
DT_FOREACH_PROP_ELEM(DT_NODELABEL(exti), interrupt_names, WCH_EXTI_CONNECT_IRQ);
124+
125+
return 0;
126+
}
127+
128+
static struct wch_exti_data wch_exti_data_0;
129+
130+
DEVICE_DT_INST_DEFINE(0, wch_exti_init, NULL, &wch_exti_data_0, NULL, PRE_KERNEL_2,
131+
CONFIG_INTC_INIT_PRIORITY, NULL);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright (c) 2025 Michael Hope
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: WCH CH32V003/20x/30x External Interrupt and Event Controller (EXTI)
5+
6+
compatible: "wch,exti"
7+
8+
include: [base.yaml, interrupt-controller.yaml]
9+
10+
properties:
11+
reg:
12+
required: true
13+
14+
interrupts:
15+
required: true
16+
17+
num-lines:
18+
type: int
19+
required: true
20+
description: Number of lines supported by the interrupt controller.
21+
22+
line-ranges:
23+
type: array
24+
required: true
25+
description: |
26+
Describes how the input lines are grouped into ranges. Each range
27+
consists of a (starting line, number of lines) pair and map to
28+
a single interrupt.
29+
30+
For example:
31+
line-ranges = <0 1>, <1 1>, <2 1>, <3 1>,
32+
<4 1>, <5 5>, <10 6>;
33+
34+
defines seven ranges where the first five contain one line, the
35+
sixth starts with line 5 and contains five elements (5 to 9), and
36+
the last starts with line 10 and contains six elements (10 to 15).

dts/riscv/wch/ch32v0/ch32v003.dtsi

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,19 @@
7272
status = "disabled";
7373
};
7474

75+
exti: interrupt-controller@40010400 {
76+
compatible = "wch,exti";
77+
interrupt-controller;
78+
#interrupt-cells = <1>;
79+
reg = <0x40010400 16>;
80+
interrupt-parent = <&pfic>;
81+
num-lines = <8>;
82+
interrupts = <20>;
83+
line-ranges = <0 8>;
84+
interrupt-names = "line0-7";
85+
status = "disabled";
86+
};
87+
7588
pinctrl: pin-controller@40010000 {
7689
compatible = "wch,afio";
7790
reg = <0x40010000 0x10>;

dts/riscv/wch/ch32v0/ch32v006.dtsi

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,19 @@
7272
status = "disabled";
7373
};
7474

75+
exti: interrupt-controller@40010400 {
76+
compatible = "wch,exti";
77+
interrupt-controller;
78+
#interrupt-cells = <1>;
79+
reg = <0x40010400 16>;
80+
interrupt-parent = <&pfic>;
81+
num-lines = <8>;
82+
interrupts = <20>;
83+
line-ranges = <0 8>;
84+
interrupt-names = "line0-7";
85+
status = "disabled";
86+
};
87+
7588
pinctrl: pin-controller@40010000 {
7689
compatible = "wch,00x-afio";
7790
reg = <0x40010000 0x10>;

dts/riscv/wch/ch32v208/ch32v208.dtsi

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,21 @@
7171
status = "disabled";
7272
};
7373

74+
exti: interrupt-controller@40010400 {
75+
compatible = "wch,exti";
76+
interrupt-controller;
77+
#interrupt-cells = <1>;
78+
reg = <0x40010400 16>;
79+
interrupt-parent = <&pfic>;
80+
num-lines = <16>;
81+
line-ranges = <0 1>, <1 1>, <2 1>, <3 1>, <4 1>,
82+
<5 5>, <10 6>;
83+
interrupts = <22 23 24 25 26 39 56>;
84+
interrupt-names = "line0", "line1", "line2", "line3",
85+
"line4", "line5-9", "line10-15";
86+
status = "disabled";
87+
};
88+
7489
pinctrl: pin-controller@40010000 {
7590
compatible = "wch,20x_30x-afio";
7691
reg = <0x40010000 16>;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (c) 2025 Michael Hope <michaelh@juju.nz>
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_INCLUDE_DRIVERS_INTERRUPT_CONTROLLER_WCH_EXTI_H_
8+
#define ZEPHYR_INCLUDE_DRIVERS_INTERRUPT_CONTROLLER_WCH_EXTI_H_
9+
10+
#include <stdint.h>
11+
12+
#include <zephyr/sys/util_macro.h>
13+
14+
/* Callback for EXTI interrupt. */
15+
typedef void (*wch_exti_callback_handler_t)(uint8_t line, void *user);
16+
17+
enum wch_exti_trigger {
18+
/*
19+
* Note that this is a flag set and these values can be ORed to trigger on
20+
* both edges.
21+
*/
22+
23+
/* Trigger on rising edge */
24+
WCH_EXTI_TRIGGER_RISING_EDGE = BIT(0),
25+
/* Trigger on falling edge */
26+
WCH_EXTI_TRIGGER_FALLING_EDGE = BIT(1),
27+
};
28+
29+
/* Enable the EXTI interrupt for `line` */
30+
void wch_exti_enable(uint8_t line);
31+
32+
/* Disable the EXTI interrupt for `line` */
33+
void wch_exti_disable(uint8_t line);
34+
35+
/* Set the trigger mode for `line` */
36+
void wch_exti_set_trigger(uint8_t line, enum wch_exti_trigger trigger);
37+
38+
/* Register a callback for `line` */
39+
int wch_exti_configure(uint8_t line, wch_exti_callback_handler_t callback, void *user);
40+
41+
#endif /* ZEPHYR_INCLUDE_DRIVERS_INTERRUPT_CONTROLLER_WCH_EXTI_H_ */

0 commit comments

Comments
 (0)