/*
 * 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);
	}
#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);
