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 #include <fsl_power.h>
16 
17 #define DT_DRV_COMPAT nxp_pint
18 
19 static PINT_Type *pint_base = (PINT_Type *)DT_INST_REG_ADDR(0);
20 
21 /* Describes configuration of PINT IRQ slot */
22 struct pint_irq_slot {
23 	nxp_pint_cb_t callback;
24 	void *user_data;
25 	uint8_t pin: 6;
26 	uint8_t used: 1;
27 	uint8_t irq;
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_INST_PROP(0, num_lines)];
34 /* Tracks pint interrupt source selected for each pin */
35 static uint8_t pin_pint_id[DT_INST_PROP(0, 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  * @param wake: indicates if the pin should wakeup the system
64  * @return 0 on success, or negative value on error
65  */
nxp_pint_pin_enable(uint8_t pin,enum nxp_pint_trigger trigger,bool wake)66 int nxp_pint_pin_enable(uint8_t pin, enum nxp_pint_trigger trigger, bool wake)
67 {
68 	uint8_t slot = 0U;
69 
70 	if (pin > ARRAY_SIZE(pin_pint_id)) {
71 		/* Invalid pin ID */
72 		return -EINVAL;
73 	}
74 	/* Find unused IRQ slot */
75 	if (pin_pint_id[pin] != NO_PINT_ID) {
76 		slot = pin_pint_id[pin];
77 	} else {
78 		for (slot = 0; slot < ARRAY_SIZE(pint_irq_cfg); slot++) {
79 			if (!pint_irq_cfg[slot].used) {
80 				break;
81 			}
82 		}
83 		if (slot == ARRAY_SIZE(pint_irq_cfg)) {
84 			/* No free IRQ slots */
85 			return -EBUSY;
86 		}
87 		pin_pint_id[pin] = slot;
88 	}
89 	pint_irq_cfg[slot].used = true;
90 	pint_irq_cfg[slot].pin = pin;
91 	/* Attach pin to interrupt slot using INPUTMUX */
92 	attach_pin_to_pint(pin, slot);
93 	/* Now configure the interrupt. No need to install callback, this
94 	 * driver handles the IRQ
95 	 */
96 	PINT_PinInterruptConfig(pint_base, slot, trigger, NULL);
97 #if !(defined(FSL_FEATURE_POWERLIB_EXTEND) && (FSL_FEATURE_POWERLIB_EXTEND != 0))
98 	if (wake) {
99 		EnableDeepSleepIRQ(pint_irq_cfg[slot].irq);
100 	} else {
101 		DisableDeepSleepIRQ(pint_irq_cfg[slot].irq);
102 		irq_enable(pint_irq_cfg[slot].irq);
103 	}
104 #endif
105 	return 0;
106 }
107 
108 
109 /**
110  * @brief disable PINT interrupt source.
111  *
112  * @param pin: pin interrupt source to disable
113  */
nxp_pint_pin_disable(uint8_t pin)114 void nxp_pint_pin_disable(uint8_t pin)
115 {
116 	uint8_t slot;
117 
118 	if (pin > ARRAY_SIZE(pin_pint_id)) {
119 		return;
120 	}
121 
122 	slot = pin_pint_id[pin];
123 	if (slot == NO_PINT_ID) {
124 		return;
125 	}
126 	/* Remove this pin from the PINT slot if one was in use */
127 	pint_irq_cfg[slot].used = false;
128 	PINT_PinInterruptConfig(pint_base, slot, kPINT_PinIntEnableNone, NULL);
129 }
130 
131 /**
132  * @brief Install PINT callback
133  *
134  * @param pin: interrupt source to install callback for
135  * @param cb: callback to install
136  * @param data: user data to include in callback
137  * @return 0 on success, or negative value on error
138  */
nxp_pint_pin_set_callback(uint8_t pin,nxp_pint_cb_t cb,void * data)139 int nxp_pint_pin_set_callback(uint8_t pin, nxp_pint_cb_t cb, void *data)
140 {
141 	uint8_t slot;
142 
143 	if (pin > ARRAY_SIZE(pin_pint_id)) {
144 		return -EINVAL;
145 	}
146 
147 	slot = pin_pint_id[pin];
148 	if (slot == NO_PINT_ID) {
149 		return -EINVAL;
150 	}
151 
152 	pint_irq_cfg[slot].callback = cb;
153 	pint_irq_cfg[slot].user_data = data;
154 	return 0;
155 }
156 
157 /**
158  * @brief Remove PINT callback
159  *
160  * @param pin: interrupt source to remove callback for
161  */
nxp_pint_pin_unset_callback(uint8_t pin)162 void nxp_pint_pin_unset_callback(uint8_t pin)
163 {
164 	uint8_t slot;
165 
166 	if (pin > ARRAY_SIZE(pin_pint_id)) {
167 		return;
168 	}
169 
170 	slot = pin_pint_id[pin];
171 	if (slot == NO_PINT_ID) {
172 		return;
173 	}
174 
175 	pint_irq_cfg[slot].callback = NULL;
176 }
177 
178 /* NXP PINT ISR handler- called with PINT slot ID */
nxp_pint_isr(uint8_t * slot)179 static void nxp_pint_isr(uint8_t *slot)
180 {
181 	PINT_PinInterruptClrStatus(pint_base, *slot);
182 	if (pint_irq_cfg[*slot].used && pint_irq_cfg[*slot].callback) {
183 		pint_irq_cfg[*slot].callback(pint_irq_cfg[*slot].pin,
184 					pint_irq_cfg[*slot].user_data);
185 	}
186 }
187 
188 
189 /* Defines PINT IRQ handler for a given irq index */
190 #define NXP_PINT_IRQ(idx, node_id)						\
191 	IF_ENABLED(DT_IRQ_HAS_IDX(node_id, idx),				\
192 	(static uint8_t nxp_pint_idx_##idx = idx;				\
193 	do {									\
194 		IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, idx, irq),			\
195 			    DT_IRQ_BY_IDX(node_id, idx, priority),		\
196 			    nxp_pint_isr, &nxp_pint_idx_##idx, 0);		\
197 		irq_enable(DT_IRQ_BY_IDX(node_id, idx, irq));			\
198 		pint_irq_cfg[idx].irq = DT_IRQ_BY_IDX(node_id, idx, irq);	\
199 	} while (false)))
200 
intc_nxp_pint_init(const struct device * dev)201 static int intc_nxp_pint_init(const struct device *dev)
202 {
203 	/* First, connect IRQs for each interrupt.
204 	 * The IRQ handler will receive the PINT slot as a
205 	 * parameter.
206 	 */
207 	LISTIFY(8, NXP_PINT_IRQ, (;), DT_INST(0, DT_DRV_COMPAT));
208 	PINT_Init(pint_base);
209 	memset(pin_pint_id, NO_PINT_ID, ARRAY_SIZE(pin_pint_id));
210 	return 0;
211 }
212 
213 DEVICE_DT_INST_DEFINE(0, intc_nxp_pint_init, NULL, NULL, NULL,
214 		      PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, NULL);
215