215 lines
5.2 KiB
C
215 lines
5.2 KiB
C
/*
|
|
* Copyright 2023 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/* Based on STM32 EXTI driver, which is (c) 2016 Open-RnD Sp. z o.o. */
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/irq.h>
|
|
#include <errno.h>
|
|
#include <zephyr/drivers/interrupt_controller/nxp_pint.h>
|
|
|
|
#include <fsl_inputmux.h>
|
|
#include <fsl_power.h>
|
|
|
|
#define DT_DRV_COMPAT nxp_pint
|
|
|
|
static PINT_Type *pint_base = (PINT_Type *)DT_INST_REG_ADDR(0);
|
|
|
|
/* Describes configuration of PINT IRQ slot */
|
|
struct pint_irq_slot {
|
|
nxp_pint_cb_t callback;
|
|
void *user_data;
|
|
uint8_t pin: 6;
|
|
uint8_t used: 1;
|
|
uint8_t irq;
|
|
};
|
|
|
|
#define NO_PINT_ID 0xFF
|
|
|
|
/* Tracks IRQ configuration for each pint interrupt source */
|
|
static struct pint_irq_slot pint_irq_cfg[DT_INST_PROP(0, num_lines)];
|
|
/* Tracks pint interrupt source selected for each pin */
|
|
static uint8_t pin_pint_id[DT_INST_PROP(0, num_inputs)];
|
|
|
|
#define PIN_TO_INPUT_MUX_CONNECTION(pin) \
|
|
((PINTSEL_PMUX_ID << PMUX_SHIFT) + (pin))
|
|
|
|
/* Attaches pin to PINT IRQ slot using INPUTMUX */
|
|
static void attach_pin_to_pint(uint8_t pin, uint8_t pint_slot)
|
|
{
|
|
INPUTMUX_Init(INPUTMUX);
|
|
|
|
/* Three parameters here- INPUTMUX base, the ID of the PINT slot,
|
|
* and a integer describing the GPIO pin.
|
|
*/
|
|
INPUTMUX_AttachSignal(INPUTMUX, pint_slot,
|
|
PIN_TO_INPUT_MUX_CONNECTION(pin));
|
|
|
|
/* Disable INPUTMUX after making changes, this gates clock and
|
|
* saves power.
|
|
*/
|
|
INPUTMUX_Deinit(INPUTMUX);
|
|
}
|
|
|
|
/**
|
|
* @brief Enable PINT interrupt source.
|
|
*
|
|
* @param pin: pin to use as interrupt source
|
|
* 0-64, corresponding to GPIO0 pin 1 - GPIO1 pin 31)
|
|
* @param trigger: one of nxp_pint_trigger flags
|
|
* @param wake: indicates if the pin should wakeup the system
|
|
* @return 0 on success, or negative value on error
|
|
*/
|
|
int nxp_pint_pin_enable(uint8_t pin, enum nxp_pint_trigger trigger, bool wake)
|
|
{
|
|
uint8_t slot = 0U;
|
|
|
|
if (pin > ARRAY_SIZE(pin_pint_id)) {
|
|
/* Invalid pin ID */
|
|
return -EINVAL;
|
|
}
|
|
/* Find unused IRQ slot */
|
|
if (pin_pint_id[pin] != NO_PINT_ID) {
|
|
slot = pin_pint_id[pin];
|
|
} else {
|
|
for (slot = 0; slot < ARRAY_SIZE(pint_irq_cfg); slot++) {
|
|
if (!pint_irq_cfg[slot].used) {
|
|
break;
|
|
}
|
|
}
|
|
if (slot == ARRAY_SIZE(pint_irq_cfg)) {
|
|
/* No free IRQ slots */
|
|
return -EBUSY;
|
|
}
|
|
pin_pint_id[pin] = slot;
|
|
}
|
|
pint_irq_cfg[slot].used = true;
|
|
pint_irq_cfg[slot].pin = pin;
|
|
/* Attach pin to interrupt slot using INPUTMUX */
|
|
attach_pin_to_pint(pin, slot);
|
|
/* Now configure the interrupt. No need to install callback, this
|
|
* driver handles the IRQ
|
|
*/
|
|
PINT_PinInterruptConfig(pint_base, slot, trigger, NULL);
|
|
#if !(defined(FSL_FEATURE_POWERLIB_EXTEND) && (FSL_FEATURE_POWERLIB_EXTEND != 0))
|
|
if (wake) {
|
|
EnableDeepSleepIRQ(pint_irq_cfg[slot].irq);
|
|
} else {
|
|
DisableDeepSleepIRQ(pint_irq_cfg[slot].irq);
|
|
irq_enable(pint_irq_cfg[slot].irq);
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief disable PINT interrupt source.
|
|
*
|
|
* @param pin: pin interrupt source to disable
|
|
*/
|
|
void nxp_pint_pin_disable(uint8_t pin)
|
|
{
|
|
uint8_t slot;
|
|
|
|
if (pin > ARRAY_SIZE(pin_pint_id)) {
|
|
return;
|
|
}
|
|
|
|
slot = pin_pint_id[pin];
|
|
if (slot == NO_PINT_ID) {
|
|
return;
|
|
}
|
|
/* Remove this pin from the PINT slot if one was in use */
|
|
pint_irq_cfg[slot].used = false;
|
|
PINT_PinInterruptConfig(pint_base, slot, kPINT_PinIntEnableNone, NULL);
|
|
}
|
|
|
|
/**
|
|
* @brief Install PINT callback
|
|
*
|
|
* @param pin: interrupt source to install callback for
|
|
* @param cb: callback to install
|
|
* @param data: user data to include in callback
|
|
* @return 0 on success, or negative value on error
|
|
*/
|
|
int nxp_pint_pin_set_callback(uint8_t pin, nxp_pint_cb_t cb, void *data)
|
|
{
|
|
uint8_t slot;
|
|
|
|
if (pin > ARRAY_SIZE(pin_pint_id)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
slot = pin_pint_id[pin];
|
|
if (slot == NO_PINT_ID) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
pint_irq_cfg[slot].callback = cb;
|
|
pint_irq_cfg[slot].user_data = data;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Remove PINT callback
|
|
*
|
|
* @param pin: interrupt source to remove callback for
|
|
*/
|
|
void nxp_pint_pin_unset_callback(uint8_t pin)
|
|
{
|
|
uint8_t slot;
|
|
|
|
if (pin > ARRAY_SIZE(pin_pint_id)) {
|
|
return;
|
|
}
|
|
|
|
slot = pin_pint_id[pin];
|
|
if (slot == NO_PINT_ID) {
|
|
return;
|
|
}
|
|
|
|
pint_irq_cfg[slot].callback = NULL;
|
|
}
|
|
|
|
/* NXP PINT ISR handler- called with PINT slot ID */
|
|
static void nxp_pint_isr(uint8_t *slot)
|
|
{
|
|
PINT_PinInterruptClrStatus(pint_base, *slot);
|
|
if (pint_irq_cfg[*slot].used && pint_irq_cfg[*slot].callback) {
|
|
pint_irq_cfg[*slot].callback(pint_irq_cfg[*slot].pin,
|
|
pint_irq_cfg[*slot].user_data);
|
|
}
|
|
}
|
|
|
|
|
|
/* Defines PINT IRQ handler for a given irq index */
|
|
#define NXP_PINT_IRQ(idx, node_id) \
|
|
IF_ENABLED(DT_IRQ_HAS_IDX(node_id, idx), \
|
|
(static uint8_t nxp_pint_idx_##idx = idx; \
|
|
do { \
|
|
IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, idx, irq), \
|
|
DT_IRQ_BY_IDX(node_id, idx, priority), \
|
|
nxp_pint_isr, &nxp_pint_idx_##idx, 0); \
|
|
irq_enable(DT_IRQ_BY_IDX(node_id, idx, irq)); \
|
|
pint_irq_cfg[idx].irq = DT_IRQ_BY_IDX(node_id, idx, irq); \
|
|
} while (false)))
|
|
|
|
static int intc_nxp_pint_init(const struct device *dev)
|
|
{
|
|
/* First, connect IRQs for each interrupt.
|
|
* The IRQ handler will receive the PINT slot as a
|
|
* parameter.
|
|
*/
|
|
LISTIFY(8, NXP_PINT_IRQ, (;), DT_INST(0, DT_DRV_COMPAT));
|
|
PINT_Init(pint_base);
|
|
memset(pin_pint_id, NO_PINT_ID, ARRAY_SIZE(pin_pint_id));
|
|
return 0;
|
|
}
|
|
|
|
DEVICE_DT_INST_DEFINE(0, intc_nxp_pint_init, NULL, NULL, NULL,
|
|
PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, NULL);
|