/*
 * Copyright (c) 2021 Espressif Systems (Shanghai) Co., Ltd.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <soc/periph_defs.h>
#include <limits.h>
#include <assert.h>
#include "soc/soc.h"
#include <soc.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/interrupt_controller/intc_esp32c3.h>
#include <zephyr/sw_isr_table.h>
#include <riscv/interrupt.h>

#define ESP32C3_INTC_DEFAULT_PRIO			15

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(intc_esp32c3, CONFIG_LOG_DEFAULT_LEVEL);

/*
 * Define this to debug the choices made when allocating the interrupt. This leads to much debugging
 * output within a critical region, which can lead to weird effects like e.g. the interrupt watchdog
 * being triggered, that is why it is separate from the normal LOG* scheme.
 */
#ifdef CONFIG_INTC_ESP32C3_DECISIONS_LOG
# define INTC_LOG(...) LOG_INF(__VA_ARGS__)
#else
# define INTC_LOG(...) do {} while (0)
#endif

#define ESP32C3_INTC_DEFAULT_PRIORITY   15
#define ESP32C3_INTC_DEFAULT_THRESHOLD  1
#define ESP32C3_INTC_DISABLED_SLOT      31
#define ESP32C3_INTC_SRCS_PER_IRQ       2
#define ESP32C3_INTC_AVAILABLE_IRQS     30

#if defined(CONFIG_SOC_SERIES_ESP32C6)

#define IRQ_NA		0xFF	/* IRQ not available */
#define IRQ_FREE	0xFE

#define ESP32C6_INTC_SRCS_PER_IRQ       2
#define ESP32C6_INTC_AVAILABLE_IRQS     31

/* For ESP32C6 only CPU peripheral interrupts number
 * 1, 2, 5, 6, 8 ~ 31 are available.
 * IRQ 31 is reserved for disabled interrupts
 */
static uint8_t esp_intr_irq_alloc[ESP32C6_INTC_AVAILABLE_IRQS][ESP32C6_INTC_SRCS_PER_IRQ] = {
	[0] = {IRQ_NA, IRQ_NA},
	[3] = {IRQ_NA, IRQ_NA},
	[4] = {IRQ_NA, IRQ_NA},
	[7] = {IRQ_NA, IRQ_NA},
	[1 ... 2]  = {IRQ_FREE, IRQ_FREE},
	[5 ... 6]  = {IRQ_FREE, IRQ_FREE},
	[8 ... 30] = {IRQ_FREE, IRQ_FREE}
};
#endif

#define STATUS_MASK_NUM		3

static uint32_t esp_intr_enabled_mask[STATUS_MASK_NUM] = {0, 0, 0};

#if defined(CONFIG_SOC_SERIES_ESP32C2) || defined(CONFIG_SOC_SERIES_ESP32C3)

static uint32_t esp_intr_find_irq_for_source(uint32_t source)
{
	/* in general case, each 2 sources goes routed to
	 * 1 IRQ line.
	 */
	uint32_t irq = (source / ESP32C3_INTC_SRCS_PER_IRQ);

	if (irq > ESP32C3_INTC_AVAILABLE_IRQS) {
		INTC_LOG("Clamping the source: %d no more IRQs available", source);
		irq = ESP32C3_INTC_AVAILABLE_IRQS;
	} else if (irq == 0) {
		irq = 1;
	}

	INTC_LOG("Found IRQ: %d for source: %d", irq, source);

	return irq;
}

#elif defined(CONFIG_SOC_SERIES_ESP32C6)

static uint32_t esp_intr_find_irq_for_source(uint32_t source)
{
	uint32_t irq = IRQ_NA;
	uint32_t irq_free = IRQ_NA;
	uint8_t *irq_ptr = NULL;

	/* First allocate one source per IRQ, then two
	 * if there are more sources than free IRQs
	 */
	for (int j = 0; j < ESP32C6_INTC_SRCS_PER_IRQ; j++) {
		for (int i = 0; i < ESP32C6_INTC_AVAILABLE_IRQS; i++) {
			/* Find first free slot but keep searching to see
			 * if source is already associated to an IRQ
			 */
			if (esp_intr_irq_alloc[i][j] == source) {
				/* Source is already associated to an IRQ */
				irq = i;
				goto found;
			} else if ((irq_free == IRQ_NA) && (esp_intr_irq_alloc[i][j] == IRQ_FREE)) {
				irq_free = i;
				irq_ptr = &esp_intr_irq_alloc[i][j];
			}
		}
	}

	if (irq_ptr != NULL) {
		*irq_ptr = (uint8_t)source;
		irq = irq_free;
	} else {
		return IRQ_NA;
	}

found:
	INTC_LOG("Found IRQ: %d for source: %d", irq, source);

	return irq;
}

#endif

void esp_intr_initialize(void)
{
	/* IRQ 31 is reserved for disabled interrupts,
	 * so route all sources to it
	 */
	for (int i = 0 ; i < ESP32C3_INTC_AVAILABLE_IRQS + 2; i++) {
		irq_disable(i);
	}

	for (int i = 0; i < ETS_MAX_INTR_SOURCE; i++) {
		esp_rom_intr_matrix_set(0, i, ESP32C3_INTC_DISABLED_SLOT);
	}

#if defined(CONFIG_SOC_SERIES_ESP32C6)
	/* Clear up IRQ allocation */
	for (int j = 0; j < ESP32C6_INTC_SRCS_PER_IRQ; j++) {
		for (int i = 0; i < ESP32C6_INTC_AVAILABLE_IRQS; i++) {
			/* screen out reserved IRQs */
			if (esp_intr_irq_alloc[i][j] != IRQ_NA) {
				esp_intr_irq_alloc[i][j] = IRQ_FREE;
			}
		}
	}
#endif

	/* set global esp32c3's INTC masking level */
	esprv_intc_int_set_threshold(ESP32C3_INTC_DEFAULT_THRESHOLD);
}

int esp_intr_alloc(int source,
		int flags,
		isr_handler_t handler,
		void *arg,
		void **ret_handle)
{
	ARG_UNUSED(flags);
	ARG_UNUSED(ret_handle);

	if (handler == NULL) {
		return -EINVAL;
	}

	if (source < 0 || source >= ETS_MAX_INTR_SOURCE) {
		return -EINVAL;
	}

	uint32_t key = irq_lock();

	irq_connect_dynamic(source,
		ESP32C3_INTC_DEFAULT_PRIORITY,
		handler,
		arg,
		0);

	if (source < 32) {
		esp_intr_enabled_mask[0] |= (1 << source);
	} else if (source < 64) {
		esp_intr_enabled_mask[1] |= (1 << (source - 32));
	} else if (source < 96) {
		esp_intr_enabled_mask[2] |= (1 << (source - 64));
	}

	INTC_LOG("Enabled ISRs -- 0: 0x%X -- 1: 0x%X -- 2: 0x%X",
		esp_intr_enabled_mask[0], esp_intr_enabled_mask[1], esp_intr_enabled_mask[2]);

	irq_unlock(key);
	int ret = esp_intr_enable(source);

	return ret;
}

int esp_intr_disable(int source)
{
	if (source < 0 || source >= ETS_MAX_INTR_SOURCE) {
		return -EINVAL;
	}

	uint32_t key = irq_lock();

	esp_rom_intr_matrix_set(0,
		source,
		ESP32C3_INTC_DISABLED_SLOT);

#if defined(CONFIG_SOC_SERIES_ESP32C6)
	for (int j = 0; j < ESP32C6_INTC_SRCS_PER_IRQ; j++) {
		for (int i = 0; i < ESP32C6_INTC_AVAILABLE_IRQS; i++) {
			if (esp_intr_irq_alloc[i][j] == source) {
				esp_intr_irq_alloc[i][j] = IRQ_FREE;
				goto freed;
			}
		}
	}
freed:
#endif

	if (source < 32) {
		esp_intr_enabled_mask[0] &= ~(1 << source);
	} else if (source < 64) {
		esp_intr_enabled_mask[1] &= ~(1 << (source - 32));
	} else if (source < 96) {
		esp_intr_enabled_mask[2] &= ~(1 << (source - 64));
	}

	INTC_LOG("Enabled ISRs -- 0: 0x%X -- 1: 0x%X -- 2: 0x%X",
		esp_intr_enabled_mask[0], esp_intr_enabled_mask[1], esp_intr_enabled_mask[2]);

	irq_unlock(key);

	return 0;
}

int esp_intr_enable(int source)
{
	if (source < 0 || source >= ETS_MAX_INTR_SOURCE) {
		return -EINVAL;
	}

	uint32_t key = irq_lock();
	uint32_t irq = esp_intr_find_irq_for_source(source);

#if defined(CONFIG_SOC_SERIES_ESP32C6)
	if (irq == IRQ_NA) {
		irq_unlock(key);
		return -ENOMEM;
	}
#endif

	esp_rom_intr_matrix_set(0, source, irq);

	if (source < 32) {
		esp_intr_enabled_mask[0] |= (1 << source);
	} else if (source < 64) {
		esp_intr_enabled_mask[1] |= (1 << (source - 32));
	} else if (source < 96) {
		esp_intr_enabled_mask[2] |= (1 << (source - 64));
	}

	INTC_LOG("Enabled ISRs -- 0: 0x%X -- 1: 0x%X -- 2: 0x%X",
		esp_intr_enabled_mask[0], esp_intr_enabled_mask[1], esp_intr_enabled_mask[2]);

	esprv_intc_int_set_priority(irq, ESP32C3_INTC_DEFAULT_PRIO);
	esprv_intc_int_set_type(irq, INTR_TYPE_LEVEL);
	esprv_intc_int_enable(1 << irq);

	irq_unlock(key);

	return 0;
}

uint32_t esp_intr_get_enabled_intmask(int status_mask_number)
{
	INTC_LOG("Enabled ISRs -- 0: 0x%X -- 1: 0x%X -- 2: 0x%X",
		esp_intr_enabled_mask[0], esp_intr_enabled_mask[1], esp_intr_enabled_mask[2]);

	if (status_mask_number < STATUS_MASK_NUM) {
		return esp_intr_enabled_mask[status_mask_number];
	} else {
		return 0;	/* error */
	}
}
