1 /*
2 * Copyright 2023 NXP
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /* Based on STM32 EXTI driver, which is (c) 2016 Open-RnD Sp. z o.o. */
8
9 #include <zephyr/device.h>
10 #include <zephyr/irq.h>
11 #include <errno.h>
12 #include <zephyr/drivers/interrupt_controller/nxp_pint.h>
13
14 #include <fsl_inputmux.h>
15
16 #define DT_DRV_COMPAT nxp_pint
17
18 #define PINT_NODE DT_INST(0, DT_DRV_COMPAT)
19
20 static PINT_Type *pint_base = (PINT_Type *)DT_REG_ADDR(PINT_NODE);
21
22 /* Describes configuration of PINT IRQ slot */
23 struct pint_irq_slot {
24 nxp_pint_cb_t callback;
25 void *user_data;
26 uint8_t pin: 6;
27 uint8_t used: 1;
28 };
29
30 #define NO_PINT_ID 0xFF
31
32 /* Tracks IRQ configuration for each pint interrupt source */
33 static struct pint_irq_slot pint_irq_cfg[DT_PROP(PINT_NODE, num_lines)];
34 /* Tracks pint interrupt source selected for each pin */
35 static uint8_t pin_pint_id[DT_PROP(PINT_NODE, num_inputs)];
36
37 #define PIN_TO_INPUT_MUX_CONNECTION(pin) \
38 ((PINTSEL_PMUX_ID << PMUX_SHIFT) + (pin))
39
40 /* Attaches pin to PINT IRQ slot using INPUTMUX */
attach_pin_to_pint(uint8_t pin,uint8_t pint_slot)41 static void attach_pin_to_pint(uint8_t pin, uint8_t pint_slot)
42 {
43 INPUTMUX_Init(INPUTMUX);
44
45 /* Three parameters here- INPUTMUX base, the ID of the PINT slot,
46 * and a integer describing the GPIO pin.
47 */
48 INPUTMUX_AttachSignal(INPUTMUX, pint_slot,
49 PIN_TO_INPUT_MUX_CONNECTION(pin));
50
51 /* Disable INPUTMUX after making changes, this gates clock and
52 * saves power.
53 */
54 INPUTMUX_Deinit(INPUTMUX);
55 }
56
57 /**
58 * @brief Enable PINT interrupt source.
59 *
60 * @param pin: pin to use as interrupt source
61 * 0-64, corresponding to GPIO0 pin 1 - GPIO1 pin 31)
62 * @param trigger: one of nxp_pint_trigger flags
63 * @return 0 on success, or negative value on error
64 */
nxp_pint_pin_enable(uint8_t pin,enum nxp_pint_trigger trigger)65 int nxp_pint_pin_enable(uint8_t pin, enum nxp_pint_trigger trigger)
66 {
67 uint8_t slot = 0U;
68
69 if (pin > ARRAY_SIZE(pin_pint_id)) {
70 /* Invalid pin ID */
71 return -EINVAL;
72 }
73 /* Find unused IRQ slot */
74 if (pin_pint_id[pin] != NO_PINT_ID) {
75 slot = pin_pint_id[pin];
76 } else {
77 for (slot = 0; slot < ARRAY_SIZE(pint_irq_cfg); slot++) {
78 if (!pint_irq_cfg[slot].used) {
79 break;
80 }
81 }
82 if (slot == ARRAY_SIZE(pint_irq_cfg)) {
83 /* No free IRQ slots */
84 return -EBUSY;
85 }
86 pin_pint_id[pin] = slot;
87 }
88 pint_irq_cfg[slot].used = true;
89 pint_irq_cfg[slot].pin = pin;
90 /* Attach pin to interrupt slot using INPUTMUX */
91 attach_pin_to_pint(pin, slot);
92 /* Now configure the interrupt. No need to install callback, this
93 * driver handles the IRQ
94 */
95 PINT_PinInterruptConfig(pint_base, slot, trigger, NULL);
96 return 0;
97 }
98
99
100 /**
101 * @brief disable PINT interrupt source.
102 *
103 * @param pin: pin interrupt source to disable
104 */
nxp_pint_pin_disable(uint8_t pin)105 void nxp_pint_pin_disable(uint8_t pin)
106 {
107 uint8_t slot;
108
109 if (pin > ARRAY_SIZE(pin_pint_id)) {
110 return;
111 }
112
113 slot = pin_pint_id[pin];
114 if (slot == NO_PINT_ID) {
115 return;
116 }
117 /* Remove this pin from the PINT slot if one was in use */
118 pint_irq_cfg[slot].used = false;
119 PINT_PinInterruptConfig(pint_base, slot, kPINT_PinIntEnableNone, NULL);
120 }
121
122 /**
123 * @brief Install PINT callback
124 *
125 * @param pin: interrupt source to install callback for
126 * @param cb: callback to install
127 * @param data: user data to include in callback
128 * @return 0 on success, or negative value on error
129 */
nxp_pint_pin_set_callback(uint8_t pin,nxp_pint_cb_t cb,void * data)130 int nxp_pint_pin_set_callback(uint8_t pin, nxp_pint_cb_t cb, void *data)
131 {
132 uint8_t slot;
133
134 if (pin > ARRAY_SIZE(pin_pint_id)) {
135 return -EINVAL;
136 }
137
138 slot = pin_pint_id[pin];
139 if (slot == NO_PINT_ID) {
140 return -EINVAL;
141 }
142
143 pint_irq_cfg[slot].callback = cb;
144 pint_irq_cfg[slot].user_data = data;
145 return 0;
146 }
147
148 /**
149 * @brief Remove PINT callback
150 *
151 * @param pin: interrupt source to remove callback for
152 */
nxp_pint_pin_unset_callback(uint8_t pin)153 void nxp_pint_pin_unset_callback(uint8_t pin)
154 {
155 uint8_t slot;
156
157 if (pin > ARRAY_SIZE(pin_pint_id)) {
158 return;
159 }
160
161 slot = pin_pint_id[pin];
162 if (slot == NO_PINT_ID) {
163 return;
164 }
165
166 pint_irq_cfg[slot].callback = NULL;
167 }
168
169 /* NXP PINT ISR handler- called with PINT slot ID */
nxp_pint_isr(uint8_t * slot)170 static void nxp_pint_isr(uint8_t *slot)
171 {
172 PINT_PinInterruptClrStatus(pint_base, *slot);
173 if (pint_irq_cfg[*slot].used && pint_irq_cfg[*slot].callback) {
174 pint_irq_cfg[*slot].callback(pint_irq_cfg[*slot].pin,
175 pint_irq_cfg[*slot].user_data);
176 }
177 }
178
179
180 /* Defines PINT IRQ handler for a given irq index */
181 #define NXP_PINT_IRQ(idx, node_id) \
182 IF_ENABLED(DT_IRQ_HAS_IDX(node_id, idx), \
183 (static uint8_t nxp_pint_idx_##idx = idx; \
184 do { \
185 IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, idx, irq), \
186 DT_IRQ_BY_IDX(node_id, idx, priority), \
187 nxp_pint_isr, &nxp_pint_idx_##idx, 0); \
188 irq_enable(DT_IRQ_BY_IDX(node_id, idx, irq)); \
189 } while (false)))
190
intc_nxp_pint_init(void)191 static int intc_nxp_pint_init(void)
192 {
193 /* First, connect IRQs for each interrupt.
194 * The IRQ handler will receive the PINT slot as a
195 * parameter.
196 */
197 LISTIFY(8, NXP_PINT_IRQ, (;), PINT_NODE);
198 PINT_Init(pint_base);
199 memset(pin_pint_id, NO_PINT_ID, ARRAY_SIZE(pin_pint_id));
200 return 0;
201 }
202
203 SYS_INIT(intc_nxp_pint_init, PRE_KERNEL_1,
204 CONFIG_INTC_INIT_PRIORITY);
205