/*
 * Copyright (c) 2018 Intel Corporation.
 * Copyright (c) 2023 Meta.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/device.h>
#include <zephyr/irq.h>
#include <zephyr/sw_isr_table.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/util.h>

BUILD_ASSERT(CONFIG_MAX_IRQ_PER_AGGREGATOR < BIT(CONFIG_2ND_LEVEL_INTERRUPT_BITS),
	     "L2 bits not enough to cover the number of L2 IRQs");
#ifdef CONFIG_3RD_LEVEL_INTERRUPTS
BUILD_ASSERT(CONFIG_MAX_IRQ_PER_AGGREGATOR < BIT(CONFIG_3RD_LEVEL_INTERRUPT_BITS),
	     "L3 bits not enough to cover the number of L3 IRQs");
#endif /* CONFIG_3RD_LEVEL_INTERRUPTS */

/**
 * @brief Get the aggregator that's responsible for the given irq
 *
 * @param irq IRQ number to query
 *
 * @return Aggregator entry, NULL if irq is level 1 or not found.
 */
static const struct _irq_parent_entry *get_intc_entry_for_irq(unsigned int irq)
{
	const unsigned int level = irq_get_level(irq);

	/* 1st level aggregator is not registered */
	if (level == 1) {
		return NULL;
	}

	const unsigned int intc_irq = irq_get_intc_irq(irq);

	/* Find an aggregator entry that matches the level & intc_irq */
	STRUCT_SECTION_FOREACH_ALTERNATE(intc_table, _irq_parent_entry, intc) {
		if ((intc->level == level) && (intc->irq == intc_irq)) {
			return intc;
		}
	}

	return NULL;
}

const struct device *z_get_sw_isr_device_from_irq(unsigned int irq)
{
	const struct _irq_parent_entry *intc = get_intc_entry_for_irq(irq);

	__ASSERT(intc != NULL, "can't find an aggregator to handle irq(%X)", irq);

	return intc != NULL ? intc->dev : NULL;
}

unsigned int z_get_sw_isr_irq_from_device(const struct device *dev)
{
	/* Get the IRQN for the aggregator */
	STRUCT_SECTION_FOREACH_ALTERNATE(intc_table, _irq_parent_entry, intc) {
		if (intc->dev == dev) {
			return intc->irq;
		}
	}

	__ASSERT(false, "dev(%p) not found", dev);

	return 0;
}

unsigned int z_get_sw_isr_table_idx(unsigned int irq)
{
	unsigned int table_idx, local_irq;
	const struct _irq_parent_entry *intc = get_intc_entry_for_irq(irq);
	const unsigned int level = irq_get_level(irq);

	if (intc != NULL) {
		local_irq = irq_from_level(irq, level);
		__ASSERT_NO_MSG(local_irq < CONFIG_MAX_IRQ_PER_AGGREGATOR);

		table_idx = intc->offset + local_irq;
	} else {
		/* irq level must be 1 if no intc entry */
		__ASSERT(level == 1, "can't find an aggregator to handle irq(%X)", irq);
		table_idx = irq;
	}

	table_idx -= CONFIG_GEN_IRQ_START_VECTOR;

	__ASSERT(table_idx < IRQ_TABLE_SIZE, "table_idx(%d) < IRQ_TABLE_SIZE(%d)", table_idx,
		 IRQ_TABLE_SIZE);

	return table_idx;
}
