/*
 * Copyright (c) 2021 Tokita, Hiroshi <tokita.hiroshi@gmail.com>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @brief Driver for Nuclie's Extended Core Interrupt Controller
 */

#include <zephyr/kernel.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/sys/util.h>
#include <zephyr/device.h>
#include <zephyr/irq_multilevel.h>

#include <zephyr/sw_isr_table.h>
#include <zephyr/drivers/interrupt_controller/riscv_clic.h>

#define DT_DRV_COMPAT nuclei_eclic

union CLICCFG {
	struct {
		uint8_t _reserved0 : 1;
		/** number of interrupt level bits */
		uint8_t nlbits : 4;
		uint8_t _reserved1 : 2;
		uint8_t _reserved2 : 1;
	} b;
	uint8_t w;
};

union CLICINFO {
	struct {
		/** number of max supported interrupts */
		uint32_t numint : 13;
		/** architecture version */
		uint32_t version : 8;
		/** supported bits in the clicintctl */
		uint32_t intctlbits : 4;
		uint32_t _reserved0 : 7;
	} b;
	uint32_t qw;
};

union CLICMTH {
	uint8_t w;
};

union CLICINTIP {
	struct {
		/** Interrupt Pending */
		uint8_t IP : 1;
		uint8_t reserved0 : 7;
	} b;
	uint8_t w;
};

union CLICINTIE {
	struct {
		/** Interrupt Enabled */
		uint8_t IE : 1;
		uint8_t reserved0 : 7;
	} b;
	uint8_t w;
};

union CLICINTATTR {
	struct {
		/** 0: non-vectored 1:vectored */
		uint8_t shv : 1;
		/** 0: level 1: rising edge 2: falling edge */
		uint8_t trg : 2;
		uint8_t reserved0 : 3;
		uint8_t reserved1 : 2;
	} b;
	uint8_t w;
};

struct CLICCTRL {
	volatile union CLICINTIP INTIP;
	volatile union CLICINTIE INTIE;
	volatile union CLICINTATTR INTATTR;
	volatile uint8_t INTCTRL;
};

/** ECLIC Mode mask for MTVT CSR Register */
#define ECLIC_MODE_MTVEC_Msk   3U

/** CLIC INTATTR: TRIG Mask */
#define CLIC_INTATTR_TRIG_Msk  0x3U

#define ECLIC_CFG       (*((volatile union CLICCFG  *)(DT_REG_ADDR_BY_IDX(DT_NODELABEL(eclic), 0))))
#define ECLIC_INFO      (*((volatile union CLICINFO *)(DT_REG_ADDR_BY_IDX(DT_NODELABEL(eclic), 1))))
#define ECLIC_MTH       (*((volatile union CLICMTH  *)(DT_REG_ADDR_BY_IDX(DT_NODELABEL(eclic), 2))))
#define ECLIC_CTRL      ((volatile  struct CLICCTRL *)(DT_REG_ADDR_BY_IDX(DT_NODELABEL(eclic), 3)))
#define ECLIC_CTRL_SIZE (DT_REG_SIZE_BY_IDX(DT_NODELABEL(eclic), 3))

#if CONFIG_3RD_LEVEL_INTERRUPTS
#define INTERRUPT_LEVEL 2
#elif CONFIG_2ND_LEVEL_INTERRUPTS
#define INTERRUPT_LEVEL 1
#else
#define INTERRUPT_LEVEL 0
#endif

static uint8_t nlbits;
static uint8_t intctlbits;
static uint8_t max_prio;
static uint8_t max_level;
static uint8_t intctrl_mask;

static inline uint8_t leftalign8(uint8_t val, uint8_t shift)
{
	return (val << (8U - shift));
}

static inline uint8_t mask8(uint8_t len)
{
	return ((1 << len) - 1) & 0xFFFFU;
}

/**
 * @brief Enable interrupt
 */
void riscv_clic_irq_enable(uint32_t irq)
{
	ECLIC_CTRL[irq].INTIE.b.IE = 1;
}

/**
 * @brief Disable interrupt
 */
void riscv_clic_irq_disable(uint32_t irq)
{
	ECLIC_CTRL[irq].INTIE.b.IE = 0;
}

/**
 * @brief Get enable status of interrupt
 */
int riscv_clic_irq_is_enabled(uint32_t irq)
{
	return ECLIC_CTRL[irq].INTIE.b.IE;
}

/**
 * @brief Set priority and level of interrupt
 */
void riscv_clic_irq_priority_set(uint32_t irq, uint32_t pri, uint32_t flags)
{
	const uint8_t prio = leftalign8(MIN(pri, max_prio), intctlbits);
	const uint8_t level =  leftalign8(MIN((irq_get_level(irq) - 1), max_level), nlbits);
	const uint8_t intctrl = (prio | level) | (~intctrl_mask);

	ECLIC_CTRL[irq].INTCTRL = intctrl;

	union CLICINTATTR intattr = {.w = 0};
#if defined(CONFIG_RISCV_VECTORED_MODE) && !defined(CONFIG_LEGACY_CLIC)
	/*
	 * Set Selective Hardware Vectoring.
	 * Legacy SiFive does not implement smclicshv extension and vectoring is
	 * enabled in the mode bits of mtvec.
	 */
	intattr.b.shv = 1;
#else
	intattr.b.shv = 0;
#endif
	intattr.b.trg = (uint8_t)(flags & CLIC_INTATTR_TRIG_Msk);
	ECLIC_CTRL[irq].INTATTR = intattr;
}

/**
 * @brief Set pending bit of an interrupt
 */
void riscv_clic_irq_set_pending(uint32_t irq)
{
	ECLIC_CTRL[irq].INTIP.b.IP = 1;
}

static int nuclei_eclic_init(const struct device *dev)
{
	/* check hardware support required interrupt levels */
	__ASSERT_NO_MSG(ECLIC_INFO.b.intctlbits >= INTERRUPT_LEVEL);

	ECLIC_MTH.w = 0;
	ECLIC_CFG.w = 0;
	ECLIC_CFG.b.nlbits = INTERRUPT_LEVEL;
	for (int i = 0; i < ECLIC_CTRL_SIZE; i++) {
		ECLIC_CTRL[i] = (struct CLICCTRL) { 0 };
	}

	csr_write(mtvec, ((csr_read(mtvec) & 0xFFFFFFC0) | ECLIC_MODE_MTVEC_Msk));

	nlbits = ECLIC_CFG.b.nlbits;
	intctlbits = ECLIC_INFO.b.intctlbits;
	max_prio = mask8(intctlbits - nlbits);
	max_level = mask8(nlbits);
	intctrl_mask = leftalign8(mask8(intctlbits), intctlbits);

	return 0;
}

DEVICE_DT_INST_DEFINE(0, nuclei_eclic_init, NULL, NULL, NULL,
		      PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, NULL);
