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>
136 lines
3.9 KiB
C
136 lines
3.9 KiB
C
/*
|
|
* 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);
|