/*
 * Copyright (c) 2023 Meta
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 * @brief Public interface for multi-level interrupts
 */
#ifndef ZEPHYR_INCLUDE_IRQ_MULTILEVEL_H_
#define ZEPHYR_INCLUDE_IRQ_MULTILEVEL_H_

#ifndef _ASMLANGUAGE
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/types.h>

#ifdef __cplusplus
extern "C" {
#endif

#if defined(CONFIG_MULTI_LEVEL_INTERRUPTS) || defined(__DOXYGEN__)

typedef union _z_irq {
	/* Zephyr multilevel-encoded IRQ */
	uint32_t irq;

	/* Interrupt bits */
	struct {
		/* First level interrupt bits */
		uint32_t l1: CONFIG_1ST_LEVEL_INTERRUPT_BITS;
		/* Second level interrupt bits */
		uint32_t l2: CONFIG_2ND_LEVEL_INTERRUPT_BITS;
		/* Third level interrupt bits */
		uint32_t l3: CONFIG_3RD_LEVEL_INTERRUPT_BITS;
	} bits;

	/* Third level IRQ's interrupt controller */
	struct {
		/* IRQ of the third level interrupt aggregator */
		uint32_t irq: CONFIG_1ST_LEVEL_INTERRUPT_BITS + CONFIG_2ND_LEVEL_INTERRUPT_BITS;
	} l3_intc;

	/* Second level IRQ's interrupt controller */
	struct {
		/* IRQ of the second level interrupt aggregator */
		uint32_t irq: CONFIG_1ST_LEVEL_INTERRUPT_BITS;
	} l2_intc;
} _z_irq_t;

BUILD_ASSERT(sizeof(_z_irq_t) == sizeof(uint32_t), "Size of `_z_irq_t` must equal to `uint32_t`");

static inline uint32_t _z_l1_irq(_z_irq_t irq)
{
	return irq.bits.l1;
}

static inline uint32_t _z_l2_irq(_z_irq_t irq)
{
	return irq.bits.l2 - 1;
}

static inline uint32_t _z_l3_irq(_z_irq_t irq)
{
	return irq.bits.l3 - 1;
}

static inline unsigned int _z_irq_get_level(_z_irq_t z_irq)
{
	if (z_irq.bits.l3 != 0) {
		return 3;
	}

	if (z_irq.bits.l2 != 0) {
		return 2;
	}

	return 1;
}

/**
 * @brief Return IRQ level
 * This routine returns the interrupt level number of the provided interrupt.
 *
 * @param irq IRQ number in its zephyr format
 *
 * @return 1 if IRQ level 1, 2 if IRQ level 2, 3 if IRQ level 3
 */
static inline unsigned int irq_get_level(unsigned int irq)
{
	_z_irq_t z_irq = {
		.irq = irq,
	};

	return _z_irq_get_level(z_irq);
}

/**
 * @brief Return the 2nd level interrupt number
 *
 * This routine returns the second level irq number of the zephyr irq
 * number passed in
 *
 * @param irq IRQ number in its zephyr format
 *
 * @return 2nd level IRQ number
 */
static inline unsigned int irq_from_level_2(unsigned int irq)
{
	_z_irq_t z_irq = {
		.irq = irq,
	};

	return _z_l2_irq(z_irq);
}

/**
 * @brief Preprocessor macro to convert `irq` from level 1 to level 2 format
 *
 * @param irq IRQ number in its zephyr format
 *
 * @return 2nd level IRQ number
 */
#define IRQ_TO_L2(irq) ((irq + 1) << CONFIG_1ST_LEVEL_INTERRUPT_BITS)

/**
 * @brief Converts irq from level 1 to level 2 format
 *
 *
 * This routine converts the input into the level 2 irq number format
 *
 * @note Values >= 0xFF are invalid
 *
 * @param irq IRQ number in its zephyr format
 *
 * @return 2nd level IRQ number
 */
static inline unsigned int irq_to_level_2(unsigned int irq)
{
	_z_irq_t z_irq = {
		.bits = {
			.l1 = 0,
			.l2 = irq + 1,
			.l3 = 0,
		},
	};

	return z_irq.irq;
}

/**
 * @brief Returns the parent IRQ of the level 2 raw IRQ number
 *
 *
 * The parent of a 2nd level interrupt is in the 1st byte
 *
 * @param irq IRQ number in its zephyr format
 *
 * @return 2nd level IRQ parent
 */
static inline unsigned int irq_parent_level_2(unsigned int irq)
{
	_z_irq_t z_irq = {
		.irq = irq,
	};

	return _z_l1_irq(z_irq);
}

/**
 * @brief Return the 3rd level interrupt number
 *
 *
 * This routine returns the third level irq number of the zephyr irq
 * number passed in
 *
 * @param irq IRQ number in its zephyr format
 *
 * @return 3rd level IRQ number
 */
static inline unsigned int irq_from_level_3(unsigned int irq)
{
	_z_irq_t z_irq = {
		.irq = irq,
	};

	return _z_l3_irq(z_irq);
}

/**
 * @brief Preprocessor macro to convert `irq` from level 1 to level 3 format
 *
 * @param irq IRQ number in its zephyr format
 *
 * @return 3rd level IRQ number
 */
#define IRQ_TO_L3(irq)                                                                             \
	((irq + 1) << (CONFIG_1ST_LEVEL_INTERRUPT_BITS + CONFIG_2ND_LEVEL_INTERRUPT_BITS))

/**
 * @brief Converts irq from level 1 to level 3 format
 *
 *
 * This routine converts the input into the level 3 irq number format
 *
 * @note Values >= 0xFF are invalid
 *
 * @param irq IRQ number in its zephyr format
 *
 * @return 3rd level IRQ number
 */
static inline unsigned int irq_to_level_3(unsigned int irq)
{
	_z_irq_t z_irq = {
		.bits = {
			.l1 = 0,
			.l2 = 0,
			.l3 = irq + 1,
		},
	};

	return z_irq.irq;
}

/**
 * @brief Returns the parent IRQ of the level 3 raw IRQ number
 *
 *
 * The parent of a 3rd level interrupt is in the 2nd byte
 *
 * @param irq IRQ number in its zephyr format
 *
 * @return 3rd level IRQ parent
 */
static inline unsigned int irq_parent_level_3(unsigned int irq)
{
	_z_irq_t z_irq = {
		.irq = irq,
	};

	return _z_l2_irq(z_irq);
}

/**
 * @brief Return the interrupt number for a given level
 *
 * @param irq IRQ number in its zephyr format
 * @param level IRQ level
 *
 * @return IRQ number in the level
 */
static inline unsigned int irq_from_level(unsigned int irq, unsigned int level)
{
	if (level == 1) {
		return irq;
	} else if (level == 2) {
		return irq_from_level_2(irq);
	} else if (level == 3) {
		return irq_from_level_3(irq);
	}

	/* level is higher than 3 */
	__ASSERT_NO_MSG(false);
	return irq;
}

/**
 * @brief Converts irq from level 1 to a given level
 *
 * @param irq IRQ number in its zephyr format
 * @param level IRQ level
 *
 * @return Converted IRQ number in the level
 */
static inline unsigned int irq_to_level(unsigned int irq, unsigned int level)
{
	if (level == 1) {
		return irq;
	} else if (level == 2) {
		return irq_to_level_2(irq);
	} else if (level == 3) {
		return irq_to_level_3(irq);
	}

	/* level is higher than 3 */
	__ASSERT_NO_MSG(false);
	return irq;
}

/**
 * @brief Returns the parent IRQ of the given level raw IRQ number
 *
 * @param irq IRQ number in its zephyr format
 * @param level IRQ level
 *
 * @return IRQ parent of the given level
 */
static inline unsigned int irq_parent_level(unsigned int irq, unsigned int level)
{
	if (level == 1) {
		/* doesn't really make sense, but return anyway */
		return irq;
	} else if (level == 2) {
		return irq_parent_level_2(irq);
	} else if (level == 3) {
		return irq_parent_level_3(irq);
	}

	/* level is higher than 3 */
	__ASSERT_NO_MSG(false);
	return irq;
}

/**
 * @brief Returns the parent interrupt controller IRQ of the given IRQ number
 *
 * @param irq IRQ number in its zephyr format
 *
 * @return IRQ of the interrupt controller
 */
static inline unsigned int irq_get_intc_irq(unsigned int irq)
{
	const unsigned int level = irq_get_level(irq);

	__ASSERT_NO_MSG(level <= 3);
	_z_irq_t z_irq = {
		.irq = irq,
	};

	if (level == 3) {
		return z_irq.l3_intc.irq;
	} else if (level == 2) {
		return z_irq.l2_intc.irq;
	} else {
		return irq;
	}
}

/**
 * @brief Increments the multilevel-encoded @a irq by @a val
 *
 * @param irq IRQ number in its zephyr format
 * @param val Amount to increment
 *
 * @return @a irq incremented by @a val
 */
static inline unsigned int irq_increment(unsigned int irq, unsigned int val)
{
	_z_irq_t z_irq = {
		.irq = irq,
	};

	if (z_irq.bits.l3 != 0) {
		z_irq.bits.l3 += val;
	} else if (z_irq.bits.l2 != 0) {
		z_irq.bits.l2 += val;
	} else {
		z_irq.bits.l1 += val;
	}

	return z_irq.irq;
}

#endif /* CONFIG_MULTI_LEVEL_INTERRUPTS */
#ifdef __cplusplus
}
#endif

#endif /* _ASMLANGUAGE */
#endif /* ZEPHYR_INCLUDE_IRQ_MULTILEVEL_H_ */
