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