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
wch_exti_isr(const void * user)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
wch_exti_enable(uint8_t line)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
wch_exti_disable(uint8_t line)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
wch_exti_configure(uint8_t line,wch_exti_callback_handler_t callback,void * user)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 (registration->callback == callback && registration->user == user) {
99 return 0;
100 }
101
102 if (callback != NULL && registration->callback != NULL) {
103 return -EALREADY;
104 }
105
106 registration->callback = callback;
107 registration->user = user;
108
109 return 0;
110 }
111
wch_exti_set_trigger(uint8_t line,enum wch_exti_trigger trigger)112 void wch_exti_set_trigger(uint8_t line, enum wch_exti_trigger trigger)
113 {
114 EXTI_TypeDef *regs = (EXTI_TypeDef *)DT_INST_REG_ADDR(0);
115
116 WRITE_BIT(regs->RTENR, line, (trigger & WCH_EXTI_TRIGGER_RISING_EDGE) != 0);
117 WRITE_BIT(regs->FTENR, line, (trigger & WCH_EXTI_TRIGGER_FALLING_EDGE) != 0);
118 }
119
120 #define WCH_EXTI_CONNECT_IRQ(node_id, interrupts, idx) \
121 IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, idx, irq), DT_IRQ_BY_IDX(node_id, idx, priority), \
122 wch_exti_isr, &wch_exti_ranges[idx], 0);
123
wch_exti_init(const struct device * dev)124 static int wch_exti_init(const struct device *dev)
125 {
126 /* Generate the registrations for each interrupt */
127 DT_FOREACH_PROP_ELEM(DT_NODELABEL(exti), interrupt_names, WCH_EXTI_CONNECT_IRQ);
128
129 return 0;
130 }
131
132 static struct wch_exti_data wch_exti_data_0;
133
134 DEVICE_DT_INST_DEFINE(0, wch_exti_init, NULL, &wch_exti_data_0, NULL, PRE_KERNEL_2,
135 CONFIG_INTC_INIT_PRIORITY, NULL);
136