Skip to content

drivers: gpio: add interrupt support for the CH32V family #89139

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 26, 2025

Conversation

nzmichaelh
Copy link
Collaborator

@nzmichaelh nzmichaelh commented Apr 26, 2025

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. Bind into the GPIO driver.

Tested on the linkw and the ch32v006evt using
samples/basic/button. Note that the linkw default stack size is too small for the sample.

@nzmichaelh
Copy link
Collaborator Author

@miggazElquez @paulwedeck FYI

nordicjm
nordicjm previously approved these changes May 12, 2025
VynDragon
VynDragon previously approved these changes May 13, 2025
@kartben kartben requested a review from Copilot May 13, 2025 16:36
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces external interrupt support for the CH32V family by adding a dedicated WCH EXTI driver and integrating it with the GPIO driver, including new DTS nodes and device tree bindings.

  • Added new header and driver files for the WCH EXTI controller
  • Updated DTS files and bindings to include EXTI support for multiple CH32V variants
  • Enabled interrupt configuration in the GPIO driver and updated board DTS files to activate the exti nodes

Reviewed Changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
include/zephyr/drivers/interrupt_controller/wch_exti.h New header defining interface and types for the WCH EXTI driver
dts/riscv/wch/ch32v208/ch32v208.dtsi, ch32v0/ch32v006.dtsi, ch32v0/ch32v003.dtsi New DTS node for EXTI configuration on different CH32V variants
dts/bindings/interrupt-controller/wch,exti.yaml Added binding documentation for the WCH EXTI controller
drivers/interrupt_controller/intc_wch_exti.c New EXTI controller driver implementation
drivers/gpio/wch_gpio_ch32v00x.c Updated GPIO driver to support interrupt configuration via EXTI
Kconfig & CMakeLists.txt files Integrated the new driver with build and configuration systems
boards/wch//.dts Enabled the EXTI node in board configurations

@nzmichaelh
Copy link
Collaborator Author

Rebased on main.

@nzmichaelh
Copy link
Collaborator Author

Rebased to fix an unrelated CI error.

@nzmichaelh
Copy link
Collaborator Author

@SoftwareArchitekt could you check my feedback and re-review please?

VynDragon
VynDragon previously approved these changes Jun 9, 2025
gpio_fire_callbacks(&data->callbacks, dev, BIT(line));
}

static int gpio_ch32v00x_configure_exti(const struct device *dev, gpio_pin_t pin)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this lines are new added, so could it be done?

@@ -7,6 +7,7 @@
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/gpio_utils.h>
#include <zephyr/drivers/interrupt_controller/wch_exti.h>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the code in c-files are only compiled by device-tree entry. but in header are some variables which takes some memory. pls check it again

return 0;
}

static int gpio_ch32v00x_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reply as above.

wch_exti_set_trigger(pin, WCH_EXTI_TRIGGER_FALLING_EDGE |
WCH_EXTI_TRIGGER_RISING_EDGE);
break;
default:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

const struct device *dev = user;
struct gpio_ch32v00x_data *data = dev->data;

gpio_fire_callbacks(&data->callbacks, dev, BIT(line));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why assigning the data here to local variable (on stack) instead of direct using in gpio_fire_callbacks?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistent style. Zephyr drivers typically have an initial variable block where data, config, and regs are resolved as needed. These are then used in the body.

case DT_REG_ADDR(DT_NODELABEL(gpioa)):
port_id = 0;
break;
#if DT_NODE_EXISTS(DT_NODELABEL(gpiob))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this also be done on the other banks? I would never think about, that parts of device tree comes so in driver. It could help in future and the defines doesn't take memory

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this switch is based on the hardware capabilities, not what is enabled (i.e. status = "okay") in Devicetree. All CH32V devices have GPIOA, but at least the CH32V003 does not have GPIOB.

enum gpio_int_mode mode,
enum gpio_int_trig trigger)
{
int err;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you assign here a 0? I like to have always clear declaration

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the intent, but that's not Zephyr style and, with a modern compiler, lets through more bugs.

For example, if you initialise err to zero, and you intended to write:

err = do_something();
if (err < 0) { return err; }

but instead you wrote:

if (do_something() < 0) {
   return err;
}

then the compiler will let this logic bug through. If you have the use of uninitialized variable warning on, then the compiler would warn.

{
EXTI_TypeDef *regs = (EXTI_TypeDef *)DT_INST_REG_ADDR(0);

regs->INTENR |= BIT(line);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where I can find error checking of parameter line?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no real Zephyr policy on validating arguments.

Copy link
Contributor

@petejohanson petejohanson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple items I found from testing this on my custom v305 board. Thanks for working on this!

Comment on lines +98 to +104
if (callback != NULL && registration->callback != NULL) {
return -EALREADY;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a check similar to the STM32 EXTI one, to just return 0 if a caller attempts to configure with the exact same callback and user:

Suggested change
if (callback != NULL && registration->callback != NULL) {
return -EALREADY;
}
if (registration->callback == callback && registration->user == user) {
return 0;
}
if (callback != NULL && registration->callback != NULL) {
return -EALREADY;
}

Without this, the test_gpio_config_twice_trigger test fails.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

BUILD_ASSERT(ARRAY_SIZE(afio->EXTICR) == 4);

cr_id = pin / 4;
bit0 = (pin % 4) << 4;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From testing here, this logic looks slightly off. Need to multiply by 4, not shift:

Suggested change
bit0 = (pin % 4) << 4;
bit0 = (pin % 4) * 4;

With this in place, on top of my work in #91966 I can run the tests/drivers/gpio on my custom v305 board, with the -X gpio_loopback in place, and they all pass:

❯ west twister --device-testing -p wch32v305pad --device-serial /dev/ttyACM0 --west-runner wlink --west-flash -T tests/drivers/gpio -X gpio_loopback

Renaming output directory to /home/peter/git/zmk/zephyr/twister-out.7
INFO    - Using Ninja..
INFO    - Zephyr version: 9996afb64654
INFO    - Using 'zephyr' toolchain.
INFO    - Building initial testsuite list...
INFO    - Writing JSON report /home/peter/git/zmk/zephyr/twister-out/testplan.json

Device testing on:

| Platform              | ID   | Serial device   |
|-----------------------|------|-----------------|
| wch32v305pad/ch32v305 |      | /dev/ttyACM0    |

INFO    - JOBS: 8
INFO    - Adding tasks to the queue...
INFO    - Added initial list of jobs to queue
INFO    - Total complete:    4/   4  100%  built (not run):    0, filtered:   17, failed:    0, error:    0
INFO    - 18 test scenarios (18 configurations) selected, 17 configurations filtered (14 by static filter, 3 at runtime).
INFO    - 1 of 1 executed test configurations passed (100.00%), 0 built (not run), 0 failed, 0 errored, with no warnings in 42.31 seconds.
INFO    - 7 of 7 executed test cases passed (100.00%) on 1 out of total 1094 platforms (0.09%).
INFO    - 1 test configurations executed on platforms, 0 test configurations were only built.

Hardware distribution summary:

| Board                 | ID   |   Counter |   Failures |
|-----------------------|------|-----------|------------|
| wch32v305pad/ch32v305 |      |         1 |          0 |
INFO    - Saving reports...
INFO    - Writing JSON report /home/peter/git/zmk/zephyr/twister-out/twister.json
INFO    - Writing xunit report /home/peter/git/zmk/zephyr/twister-out/twister.xml...
INFO    - Writing xunit report /home/peter/git/zmk/zephyr/twister-out/twister_report.xml...
INFO    - Run completed

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Done.

@nzmichaelh
Copy link
Collaborator Author

Rebased on main.

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>
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 <michaelh@juju.nz>
Now that Zephyr has support, enable by default.

Signed-off-by: Michael Hope <michaelh@juju.nz>
Copy link

Copy link
Contributor

@petejohanson petejohanson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Cherry picked the changes onto my v305 work and the full GPIO tests pass.

@nzmichaelh nzmichaelh requested review from VynDragon and nordicjm June 25, 2025 17:27
@nzmichaelh
Copy link
Collaborator Author

@VynDragon @nordicjm PTAL. I'd submit this before Friday's freeze if practical.

Copy link
Collaborator

@VynDragon VynDragon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a quick look at the code and tested button sample on linkw that now works. Looks good to me.

@kartben kartben merged commit badce12 into zephyrproject-rtos:main Jun 26, 2025
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants