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