/*
 * Copyright 2022 NXP
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/drivers/watchdog.h>
#include <zephyr/irq.h>
#include <Swt_Ip.h>
#include <Swt_Ip_Irq.h>

#define LOG_LEVEL CONFIG_WDT_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(swt_nxp_s32);

#define PARAM_UNUSED	0

struct swt_nxp_s32_config {
	uint32_t clock_freq;
	uint8_t instance;
};

struct swt_nxp_s32_data {
	Swt_Ip_ConfigType swt_config;
	wdt_callback_t callback;
	bool timeout_valid;
};

static int swt_nxp_s32_setup(const struct device *dev, uint8_t options)
{
	const struct swt_nxp_s32_config *config = dev->config;
	struct swt_nxp_s32_data *data = dev->data;
	Swt_Ip_StatusType status;

	if (!data->timeout_valid) {
		LOG_ERR("No valid timeouts installed");
		return -EINVAL;
	}

	data->swt_config.bEnRunInStopMode =
		(options & WDT_OPT_PAUSE_IN_SLEEP) == 0U;

	data->swt_config.bEnRunInDebugMode =
		(options & WDT_OPT_PAUSE_HALTED_BY_DBG) == 0U;

	status = Swt_Ip_Init(config->instance, &data->swt_config);

	if (status != SWT_IP_STATUS_SUCCESS) {
		LOG_ERR("Setting up watchdog failed");
		return -EIO;
	}

	return 0;
}

static int swt_nxp_s32_disable(const struct device *dev)
{
	const struct swt_nxp_s32_config *config = dev->config;
	struct swt_nxp_s32_data *data = dev->data;
	Swt_Ip_StatusType status;

	status = Swt_Ip_Deinit(config->instance);

	if (status != SWT_IP_STATUS_SUCCESS) {
		LOG_ERR("Disabling watchdog failed");
		return -EIO;
	}

	data->timeout_valid = false;

	return 0;
}

static int swt_nxp_s32_install_timeout(const struct device *dev,
				       const struct wdt_timeout_cfg *cfg)
{
	const struct swt_nxp_s32_config *config = dev->config;
	struct swt_nxp_s32_data *data = dev->data;

	if (data->timeout_valid) {
		LOG_ERR("No more timeouts can be installed");
		return -ENOMEM;
	}

	data->swt_config.u32TimeoutValue = config->clock_freq / 1000U * cfg->window.max;

	if (cfg->window.min) {
		data->swt_config.bEnWindow = true;
		data->swt_config.u32WindowValue =
			config->clock_freq / 1000U * (cfg->window.max - cfg->window.min);
	} else {
		data->swt_config.bEnWindow = false;
		data->swt_config.u32WindowValue = 0;
	}

	if ((data->swt_config.u32TimeoutValue < SWT_MIN_VALUE_TIMEOUT_U32) ||
	    (data->swt_config.u32TimeoutValue < data->swt_config.u32WindowValue)) {
		LOG_ERR("Invalid timeout");
		return -EINVAL;
	}

	data->swt_config.bEnInterrupt = cfg->callback != NULL;
	data->callback = cfg->callback;
	data->timeout_valid = true;
	LOG_DBG("Installed timeout (timeoutValue = %d)",
		data->swt_config.u32TimeoutValue);

	return 0;
}

static int swt_nxp_s32_feed(const struct device *dev, int channel_id)
{
	ARG_UNUSED(channel_id);
	const struct swt_nxp_s32_config *config = dev->config;

	Swt_Ip_Service(config->instance);
	LOG_DBG("Fed the watchdog");

	return 0;
}

static const struct wdt_driver_api swt_nxp_s32_driver_api = {
	.setup = swt_nxp_s32_setup,
	.disable = swt_nxp_s32_disable,
	.install_timeout = swt_nxp_s32_install_timeout,
	.feed = swt_nxp_s32_feed,
};

#define SWT_NODE(n)		DT_NODELABEL(swt##n)
#define RTU0_SWT_OFFSET_IDX	2
#define RTU1_SWT_OFFSET_IDX	7
#define RTU_SWT(n)								\
	COND_CODE_1(CONFIG_NXP_S32_RTU_INDEX, (RTU1_SWT_OFFSET_IDX + n),	\
		    (RTU0_SWT_OFFSET_IDX + n))

#define SWT_NXP_S32_CALLBACK(n)							\
	void swt_nxp_s32_##n##_callback(void)					\
	{									\
		const struct device *dev = DEVICE_DT_GET(SWT_NODE(n));		\
		struct swt_nxp_s32_data *data = dev->data;			\
										\
		if (data->callback) {						\
			data->callback(dev, PARAM_UNUSED);			\
		}								\
	}

#define SWT_NXP_S32_DEVICE_INIT(n)						\
	SWT_NXP_S32_CALLBACK(n)							\
	static struct swt_nxp_s32_data swt_nxp_s32_data_##n = {			\
		.swt_config = {							\
			.u8MapEnBitmask = SWT_IP_ALL_MAP_DISABLE |		\
				SWT_IP_MAP0_ENABLE | SWT_IP_MAP1_ENABLE |	\
				SWT_IP_MAP2_ENABLE | SWT_IP_MAP3_ENABLE |	\
				SWT_IP_MAP4_ENABLE | SWT_IP_MAP5_ENABLE |	\
				SWT_IP_MAP6_ENABLE | SWT_IP_MAP7_ENABLE,	\
			.bEnResetOnInvalidAccess = TRUE,			\
			.eServiceMode = FALSE,					\
			.u16InitialKey = 0,					\
			.lockConfig = SWT_IP_UNLOCK,				\
			.pfSwtCallback = &swt_nxp_s32_##n##_callback,		\
		},								\
	};									\
	static const struct swt_nxp_s32_config swt_nxp_s32_config_##n = {	\
		.clock_freq = DT_PROP(SWT_NODE(n), clock_frequency),		\
		.instance = (uint8_t)(RTU_SWT(n)),				\
	};									\
										\
	static int swt_nxp_s32_##n##_init(const struct device *dev)		\
	{									\
		IRQ_CONNECT(DT_IRQN(SWT_NODE(n)),				\
			    DT_IRQ(SWT_NODE(n), priority),			\
			    Swt_Ip_IrqHandler,					\
			    (uint8_t)(RTU_SWT(n)),				\
			    DT_IRQ(SWT_NODE(n), flags));			\
		irq_enable(DT_IRQN(SWT_NODE(n)));				\
										\
		return 0;							\
	}									\
										\
	DEVICE_DT_DEFINE(SWT_NODE(n),						\
			 swt_nxp_s32_##n##_init,				\
			 NULL,							\
			 &swt_nxp_s32_data_##n,					\
			 &swt_nxp_s32_config_##n,				\
			 POST_KERNEL,						\
			 CONFIG_KERNEL_INIT_PRIORITY_DEVICE,			\
			 &swt_nxp_s32_driver_api);


#if DT_NODE_HAS_STATUS(SWT_NODE(0), okay)
SWT_NXP_S32_DEVICE_INIT(0)
#endif

#if DT_NODE_HAS_STATUS(SWT_NODE(1), okay)
SWT_NXP_S32_DEVICE_INIT(1)
#endif

#if DT_NODE_HAS_STATUS(SWT_NODE(2), okay)
SWT_NXP_S32_DEVICE_INIT(2)
#endif

#if DT_NODE_HAS_STATUS(SWT_NODE(3), okay)
SWT_NXP_S32_DEVICE_INIT(3)
#endif

#if DT_NODE_HAS_STATUS(SWT_NODE(4), okay)
SWT_NXP_S32_DEVICE_INIT(4)
#endif
