zephyr/drivers/interrupt_controller/intc_nxp_pint.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);