Browse Source

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>
pull/92227/head
Michael Hope 2 months ago committed by Benjamin Cabé
parent
commit
b99b7d14f1
  1. 1
      drivers/interrupt_controller/CMakeLists.txt
  2. 2
      drivers/interrupt_controller/Kconfig
  3. 10
      drivers/interrupt_controller/Kconfig.wch_exti
  4. 135
      drivers/interrupt_controller/intc_wch_exti.c
  5. 36
      dts/bindings/interrupt-controller/wch,exti.yaml
  6. 13
      dts/riscv/wch/ch32v0/ch32v003.dtsi
  7. 13
      dts/riscv/wch/ch32v0/ch32v006.dtsi
  8. 15
      dts/riscv/wch/ch32v208/ch32v208.dtsi
  9. 41
      include/zephyr/drivers/interrupt_controller/wch_exti.h

1
drivers/interrupt_controller/CMakeLists.txt

@ -46,6 +46,7 @@ zephyr_library_sources_ifdef(CONFIG_RENESAS_RZ_EXT_IRQ intc_renesas_rz_ext_ @@ -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)

2
drivers/interrupt_controller/Kconfig

@ -112,4 +112,6 @@ source "drivers/interrupt_controller/Kconfig.mtk_adsp" @@ -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

10
drivers/interrupt_controller/Kconfig.wch_exti

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
# Copyright (c) 2025 Michael Hope <michaelh@juju.nz>
# 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.

135
drivers/interrupt_controller/intc_wch_exti.c

@ -0,0 +1,135 @@ @@ -0,0 +1,135 @@
/*
* Copyright (c) 2025 Michael Hope <michaelh@juju.nz>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT wch_exti
#include <errno.h>
#include <zephyr/device.h>
#include <zephyr/irq.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/drivers/interrupt_controller/wch_exti.h>
#include <hal_ch32fun.h>
#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);

36
dts/bindings/interrupt-controller/wch,exti.yaml

@ -0,0 +1,36 @@ @@ -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).

13
dts/riscv/wch/ch32v0/ch32v003.dtsi

@ -72,6 +72,19 @@ @@ -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>;

13
dts/riscv/wch/ch32v0/ch32v006.dtsi

@ -72,6 +72,19 @@ @@ -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>;

15
dts/riscv/wch/ch32v208/ch32v208.dtsi

@ -71,6 +71,21 @@ @@ -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>;

41
include/zephyr/drivers/interrupt_controller/wch_exti.h

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
/*
* Copyright (c) 2025 Michael Hope <michaelh@juju.nz>
*
* 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 <stdint.h>
#include <zephyr/sys/util_macro.h>
/* 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_ */
Loading…
Cancel
Save