/*
 * Copyright (c) 2023 AMD-Xilinx Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT xlnx_zynqmp_ipi_mailbox

#include "ipm_xlnx_ipi.h"

#include <errno.h>
#include <zephyr/device.h>
#include <zephyr/drivers/ipm.h>
#include <zephyr/irq.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(ipm_xlnx_ipi, CONFIG_IPM_LOG_LEVEL);

#define XLNX_IPI_MAX_BUF_SIZE_BYTES 32

struct xlnx_ipi_data {
	size_t len;
	void *user_data;
	uint8_t data[];
};

struct xlnx_ipi_reg_info {
	uint32_t ipi_ch_bit;
};

static const struct xlnx_ipi_reg_info xlnx_ipi_reg_info_zynqmp[] = {
	{.ipi_ch_bit = IPI_CH0_BIT},  /* IPI CH ID 0 - Default APU  */
	{.ipi_ch_bit = IPI_CH1_BIT},  /* IPI CH ID 1 - Default RPU0 */
	{.ipi_ch_bit = IPI_CH2_BIT},  /* IPI CH ID 2 - Default RPU1 */
	{.ipi_ch_bit = IPI_CH3_BIT},  /* IPI CH ID 3 - Default PMU0 */
	{.ipi_ch_bit = IPI_CH4_BIT},  /* IPI CH ID 4 - Default PMU1 */
	{.ipi_ch_bit = IPI_CH5_BIT},  /* IPI CH ID 5 - Default PMU2 */
	{.ipi_ch_bit = IPI_CH6_BIT},  /* IPI CH ID 6 - Default PMU3 */
	{.ipi_ch_bit = IPI_CH7_BIT},  /* IPI CH ID 7 - Default PL0 */
	{.ipi_ch_bit = IPI_CH8_BIT},  /* IPI CH ID 8 - Default PL1 */
	{.ipi_ch_bit = IPI_CH9_BIT},  /* IPI CH ID 9 - Default PL2 */
	{.ipi_ch_bit = IPI_CH10_BIT}, /* IPI CH ID 10 - Default PL3 */
};

struct xlnx_ipi_config {
	uint32_t ipi_ch_bit;
	uint32_t host_ipi_reg;
	int (*xlnx_ipi_config_func)(const struct device *dev);
	const struct device **cdev_list;
	int num_cdev;
};

struct xlnx_ipi_child_data {
	bool enabled;
	ipm_callback_t ipm_callback;
	void *user_data;
};

struct xlnx_ipi_child_config {
	const char *node_id;
	uint32_t local_request_region;
	uint32_t local_response_region;
	uint32_t remote_request_region;
	uint32_t remote_response_region;
	uint32_t host_ipi_reg;
	uint32_t remote_ipi_id;
	uint32_t remote_ipi_ch_bit;
};

static void xlnx_mailbox_rx_isr(const struct device *dev)
{
	const struct xlnx_ipi_config *config;
	const struct device **cdev_list;
	const struct xlnx_ipi_child_config *cdev_conf;
	const struct xlnx_ipi_child_data *cdev_data;
	uint8_t ipi_buf[XLNX_IPI_MAX_BUF_SIZE_BYTES + sizeof(struct xlnx_ipi_data)];
	int num_cdev;
	struct xlnx_ipi_data *msg;
	const struct device *cdev;
	uint32_t remote_ipi_ch_bit;
	int i, j;

	config = dev->config;
	cdev_list = config->cdev_list;
	num_cdev = config->num_cdev;
	msg = (struct xlnx_ipi_data *)ipi_buf;
	for (i = 0; i < num_cdev; i++) {
		cdev = cdev_list[i];
		cdev_conf = cdev->config;
		cdev_data = cdev->data;

		if (!cdev_data->enabled) {
			continue;
		}

		remote_ipi_ch_bit = cdev_conf->remote_ipi_ch_bit;
		if (!sys_test_bit(config->host_ipi_reg + IPI_ISR, remote_ipi_ch_bit)) {
			continue;
		}

		msg->len = XLNX_IPI_MAX_BUF_SIZE_BYTES;
		msg->user_data = cdev_data->user_data;
		for (j = 0; j < XLNX_IPI_MAX_BUF_SIZE_BYTES; j++) {
			msg->data[j] = sys_read8(cdev_conf->remote_request_region + j);
		}
		if (cdev_data->ipm_callback) {
			cdev_data->ipm_callback(cdev, cdev_data->user_data,
						cdev_conf->remote_ipi_id, msg);
		}
		sys_set_bit(config->host_ipi_reg + IPI_ISR, remote_ipi_ch_bit);
	}
}

static int xlnx_ipi_send(const struct device *ipmdev, int wait, uint32_t id, const void *data,
			 int size)
{
	const uint8_t *msg = (uint8_t *)data;
	const struct xlnx_ipi_child_config *config = ipmdev->config;
	unsigned int key;
	int i, obs_bit;

	ARG_UNUSED(id);

	if (size > XLNX_IPI_MAX_BUF_SIZE_BYTES) {
		return -EMSGSIZE;
	}

	key = irq_lock();
	if (msg) {
		/* Write buffer to send data */
		for (i = 0; i < size; i++) {
			sys_write8(msg[i], config->local_request_region + i);
		}
	}
	irq_unlock(key);

	sys_set_bit(config->host_ipi_reg + IPI_TRIG, config->remote_ipi_ch_bit);

	obs_bit = 0;
	do {
		obs_bit = sys_test_bit(config->host_ipi_reg + IPI_OBS, config->remote_ipi_ch_bit);
	} while (obs_bit && wait);

	return 0;
}

static void xlnx_ipi_register_callback(const struct device *port, ipm_callback_t cb,
				       void *user_data)
{
	struct xlnx_ipi_child_data *data = port->data;

	data->ipm_callback = cb;
	data->user_data = user_data;
}

static int xlnx_ipi_max_data_size_get(const struct device *ipmdev)
{
	return XLNX_IPI_MAX_BUF_SIZE_BYTES;
}

static uint32_t xlnx_ipi_max_id_val_get(const struct device *ipmdev)
{
	return UINT32_MAX;
}

static int xlnx_ipi_set_enabled(const struct device *ipmdev, int enable)
{
	const struct xlnx_ipi_child_config *config = ipmdev->config;
	struct xlnx_ipi_child_data *data = ipmdev->data;

	if (enable) {
		sys_set_bit(config->host_ipi_reg + IPI_IER, config->remote_ipi_ch_bit);
	} else {
		sys_set_bit(config->host_ipi_reg + IPI_IDR, config->remote_ipi_ch_bit);
	}

	/* If IPI channel bit in IPI Mask Register is not set, then interrupt is enabled */
	if (!sys_test_bit(config->host_ipi_reg + IPI_IMR, config->remote_ipi_ch_bit)) {
		data->enabled = enable;
		return 0;
	}

	return -EINVAL;
}

static int xlnx_ipi_init(const struct device *dev)
{
	const struct xlnx_ipi_config *conf = dev->config;

	/* disable all the interrupts */
	sys_write32(0xFFFFFFFF, conf->host_ipi_reg + IPI_IDR);

	/* clear status of any previous interrupts */
	sys_write32(0xFFFFFFFF, conf->host_ipi_reg + IPI_ISR);

	conf->xlnx_ipi_config_func(dev);

	return 0;
}

static struct ipm_driver_api xlnx_ipi_api = {
	.send = xlnx_ipi_send,
	.register_callback = xlnx_ipi_register_callback,
	.max_data_size_get = xlnx_ipi_max_data_size_get,
	.max_id_val_get = xlnx_ipi_max_id_val_get,
	.set_enabled = xlnx_ipi_set_enabled,
};

#define GET_CHILD_DEV(node_id) DEVICE_DT_GET(node_id),

#define XLNX_IPI_CHILD(ch_node)                                                                    \
	struct xlnx_ipi_child_data xlnx_ipi_child_data##ch_node = {                                \
		.enabled = false,                                                                  \
		.ipm_callback = NULL,                                                              \
	};                                                                                         \
	struct xlnx_ipi_child_config xlnx_ipi_child_config##ch_node = {                            \
		.local_request_region = DT_REG_ADDR_BY_NAME(ch_node, local_request_region),        \
		.local_response_region = DT_REG_ADDR_BY_NAME(ch_node, local_response_region),      \
		.remote_request_region = DT_REG_ADDR_BY_NAME(ch_node, remote_request_region),      \
		.remote_response_region = DT_REG_ADDR_BY_NAME(ch_node, remote_response_region),    \
		.remote_ipi_id = DT_PROP(ch_node, remote_ipi_id),                                  \
		.remote_ipi_ch_bit =                                                               \
			xlnx_ipi_reg_info_zynqmp[DT_PROP(ch_node, remote_ipi_id)].ipi_ch_bit,      \
		.host_ipi_reg = DT_REG_ADDR_BY_NAME(DT_PARENT(ch_node), host_ipi_reg),             \
	};                                                                                         \
	DEVICE_DT_DEFINE(ch_node, NULL, NULL, &xlnx_ipi_child_data##ch_node,			   \
			 &xlnx_ipi_child_config##ch_node, POST_KERNEL,                             \
			 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &xlnx_ipi_api);

#define XLNX_IPI(inst)                                                                             \
	DT_INST_FOREACH_CHILD_STATUS_OKAY(inst, XLNX_IPI_CHILD);                                   \
	static const struct device *cdev##inst[] = {                                               \
		DT_INST_FOREACH_CHILD_STATUS_OKAY(inst, GET_CHILD_DEV)};                           \
	static int xlnx_ipi_config_func##inst(const struct device *dev);                           \
	struct xlnx_ipi_config xlnx_ipi_config##inst = {                                           \
		.host_ipi_reg = DT_INST_REG_ADDR_BY_NAME(inst, host_ipi_reg),                      \
		.xlnx_ipi_config_func = xlnx_ipi_config_func##inst,                                \
		.cdev_list = cdev##inst,                                                           \
		.num_cdev = ARRAY_SIZE(cdev##inst),                                                \
	};                                                                                         \
	DEVICE_DT_INST_DEFINE(inst, &xlnx_ipi_init, NULL, NULL, /* data */                         \
			      &xlnx_ipi_config##inst,           /* conf */                         \
			      POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL);             \
	static int xlnx_ipi_config_func##inst(const struct device *dev)                            \
	{                                                                                          \
		IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority), xlnx_mailbox_rx_isr,  \
			    DEVICE_DT_INST_GET(inst), 0);                                          \
		irq_enable(DT_INST_IRQN(inst));                                                    \
		LOG_DBG("irq %d is enabled: %s\n", DT_INST_IRQN(inst),                             \
			irq_is_enabled(DT_INST_IRQN(inst)) ? "true" : "false");                    \
		return 0;                                                                          \
	}

DT_INST_FOREACH_STATUS_OKAY(XLNX_IPI)
