/*
 * Copyright (c) 2024 Nordic Semiconductor ASA
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT nordic_nrf_vevif_event_rx

#include <zephyr/devicetree.h>
#include <zephyr/drivers/mbox.h>

#include <haly/nrfy_vpr.h>

#define EVENTS_IDX_MIN NRF_VPR_EVENTS_TRIGGERED_MIN
#define EVENTS_IDX_MAX NRF_VPR_EVENTS_TRIGGERED_MAX

#if defined(CONFIG_MBOX_NRF_VEVIF_EVENT_USE_54L_ERRATA_16)
#define VEVIF_54L_EVENT_IDX 20
BUILD_ASSERT(DT_INST_PROP(0, nordic_events) == 1);
BUILD_ASSERT(DT_INST_PROP(0, nordic_events_mask) & BIT(VEVIF_54L_EVENT_IDX));
#endif

/* callbacks */
struct mbox_vevif_event_rx_cbs {
	mbox_callback_t cb[EVENTS_IDX_MAX - EVENTS_IDX_MIN + 1U];
	void *user_data[EVENTS_IDX_MAX - EVENTS_IDX_MIN + 1U];
	uint32_t enabled_mask;
};

struct mbox_vevif_event_rx_conf {
	NRF_VPR_Type *vpr;
	uint32_t events_mask;
	uint8_t events;
	void (*irq_connect)(void);
};

static void trigger_callback(const struct device *dev, struct mbox_vevif_event_rx_cbs *cbs,
			     uint8_t id)
{
	uint8_t idx = id - EVENTS_IDX_MIN;

	if ((cbs->enabled_mask & BIT(id)) && (cbs->cb[idx] != NULL)) {
		cbs->cb[idx](dev, id, cbs->user_data[idx], NULL);
	}
}

static void vevif_event_rx_isr(const void *device)
{
	const struct device *dev = (struct device *)device;
	struct mbox_vevif_event_rx_cbs *cbs = dev->data;

#if !defined(CONFIG_MBOX_NRF_VEVIF_EVENT_USE_54L_ERRATA_16)
	const struct mbox_vevif_event_rx_conf *config = dev->config;

	for (uint8_t id = EVENTS_IDX_MIN; id < EVENTS_IDX_MAX + 1U; id++) {
		nrf_vpr_event_t event = nrfy_vpr_triggered_event_get(id);
		if (nrfy_vpr_event_check(config->vpr, event)) {
			nrfy_vpr_event_clear(config->vpr, event);
			trigger_callback(dev, cbs, id);
		}
	}
#else
	trigger_callback(dev, cbs, VEVIF_54L_EVENT_IDX);
#endif
}

static inline bool vevif_event_rx_event_is_valid(uint32_t events_mask, uint32_t id)
{
	return ((id <= EVENTS_IDX_MAX) && ((events_mask & BIT(id)) != 0U));
}

static uint32_t vevif_event_rx_max_channels_get(const struct device *dev)
{
	const struct mbox_vevif_event_rx_conf *config = dev->config;

	return config->events;
}

static int vevif_event_rx_register_callback(const struct device *dev, uint32_t id,
					    mbox_callback_t cb, void *user_data)
{
	const struct mbox_vevif_event_rx_conf *config = dev->config;
	struct mbox_vevif_event_rx_cbs *cbs = dev->data;
	uint8_t idx = id - EVENTS_IDX_MIN;

	if (!vevif_event_rx_event_is_valid(config->events_mask, id)) {
		return -EINVAL;
	}

	cbs->cb[idx] = cb;
	cbs->user_data[idx] = user_data;

	return 0;
}

static int vevif_event_rx_set_enabled(const struct device *dev, uint32_t id, bool enable)
{
	const struct mbox_vevif_event_rx_conf *config = dev->config;
	struct mbox_vevif_event_rx_cbs *cbs = dev->data;

	if (!vevif_event_rx_event_is_valid(config->events_mask, id)) {
		return -EINVAL;
	}

	if (enable) {
		if ((cbs->enabled_mask & BIT(id)) != 0U) {
			return -EALREADY;
		}

		cbs->enabled_mask |= BIT(id);
		nrfy_vpr_int_enable(config->vpr, BIT(id));
	} else {
		if ((cbs->enabled_mask & BIT(id)) == 0U) {
			return -EALREADY;
		}

		cbs->enabled_mask &= ~BIT(id);
		nrfy_vpr_int_disable(config->vpr, BIT(id));
	}

	return 0;
}

static DEVICE_API(mbox, vevif_event_rx_driver_api) = {
	.max_channels_get = vevif_event_rx_max_channels_get,
	.register_callback = vevif_event_rx_register_callback,
	.set_enabled = vevif_event_rx_set_enabled,
};

static int vevif_event_rx_init(const struct device *dev)
{
	const struct mbox_vevif_event_rx_conf *config = dev->config;

	config->irq_connect();

	return 0;
}

#define VEVIF_EVENT_RX_DEFINE(inst)                                                                \
	BUILD_ASSERT(DT_INST_PROP(inst, nordic_events) <= NRF_VPR_EVENTS_TRIGGERED_COUNT,          \
		     "Number of events exceeds maximum");                                          \
                                                                                                   \
	static void irq_connect##inst(void)                                                        \
	{                                                                                          \
		IRQ_CONNECT(DT_IRQN(DT_DRV_INST(inst)), DT_IRQ(DT_DRV_INST(inst), priority),       \
			    vevif_event_rx_isr, (const void *)DEVICE_DT_GET(DT_DRV_INST(inst)),    \
			    0);                                                                    \
		irq_enable(DT_IRQN(DT_DRV_INST(inst)));                                            \
	};                                                                                         \
                                                                                                   \
	static struct mbox_vevif_event_rx_cbs data##inst = {                                       \
		.enabled_mask = 0,                                                                 \
	};                                                                                         \
	static const struct mbox_vevif_event_rx_conf conf##inst = {                                \
		.vpr = (NRF_VPR_Type *)DT_REG_ADDR(DT_INST_PARENT(inst)),                          \
		.events = DT_INST_PROP(inst, nordic_events),                                       \
		.events_mask = DT_INST_PROP(inst, nordic_events_mask),                             \
		.irq_connect = irq_connect##inst,                                                  \
	};                                                                                         \
                                                                                                   \
	DEVICE_DT_INST_DEFINE(inst, vevif_event_rx_init, NULL, &data##inst, &conf##inst,           \
			      POST_KERNEL, CONFIG_MBOX_INIT_PRIORITY, &vevif_event_rx_driver_api);

DT_INST_FOREACH_STATUS_OKAY(VEVIF_EVENT_RX_DEFINE)
