/*
 * Copyright (c) 2022 Renesas Electronics Corporation and/or its affiliates
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <soc.h>
#include <zephyr/arch/arm/nmi.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/watchdog.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(wdog_smartbond, CONFIG_WDT_LOG_LEVEL);

#define DT_DRV_COMPAT renesas_smartbond_watchdog

/* driver data */
struct wdog_smartbond_data {
	/* Reload value calculated in setup */
	uint32_t reload_val;
#ifdef CONFIG_WDT_SMARTBOND_NMI
	const struct device *wdog_device;
	wdt_callback_t callback;
#endif
};

static struct wdog_smartbond_data wdog_smartbond_dev_data = {};

static int wdg_smartbond_setup(const struct device *dev, uint8_t options)
{
	ARG_UNUSED(dev);

	if (options & WDT_OPT_PAUSE_IN_SLEEP) {
		LOG_ERR("Watchdog pause in sleep is not supported");
		return -ENOTSUP;
	}
	return 0;
}

static int wdg_smartbond_disable(const struct device *dev)
{
	ARG_UNUSED(dev);

	if (SYS_WDOG->WATCHDOG_CTRL_REG & SYS_WDOG_WATCHDOG_CTRL_REG_NMI_RST_Msk) {
		/* watchdog cannot be stopped once started when NMI_RST is 1 */
		return -EPERM;
	}

	GPREG->SET_FREEZE_REG = GPREG_SET_FREEZE_REG_FRZ_SYS_WDOG_Msk;
	return 0;
}

#ifdef CONFIG_WDT_SMARTBOND_NMI
static void wdog_smartbond_nmi_isr(void)
{
	if (wdog_smartbond_dev_data.callback) {
		wdog_smartbond_dev_data.callback(wdog_smartbond_dev_data.wdog_device, 0);
	}
}
#endif /* CONFIG_RUNTIME_NMI */

static int wdg_smartbond_install_timeout(const struct device *dev,
					 const struct wdt_timeout_cfg *config)
{
	struct wdog_smartbond_data *data = (struct wdog_smartbond_data *)(dev)->data;
	uint32_t reload_val;

#ifndef CONFIG_WDT_SMARTBOND_NMI
	if (config->callback != NULL) {
		return -ENOTSUP;
	}
#endif
	/* For RC32K timer ticks every ~10ms, for RCX ~21ms */
	if (CRG_TOP->CLK_RCX_REG & CRG_TOP_CLK_RCX_REG_RCX_ENABLE_Msk) {
		reload_val = config->window.max / 21;
	} else {
		reload_val = config->window.max / 10;
	}

	if (reload_val < 1 || reload_val >= 0x2000 || config->window.min != 0) {
		/* Out of range supported by watchdog */
		LOG_ERR("Watchdog timeout out of range");
		return -EINVAL;
	}
#if CONFIG_WDT_SMARTBOND_NMI
	data->callback = config->callback;
	data->wdog_device = dev;
	z_arm_nmi_set_handler(wdog_smartbond_nmi_isr);
	SYS_WDOG->WATCHDOG_CTRL_REG = 2;
#else
	SYS_WDOG->WATCHDOG_CTRL_REG = 2 | SYS_WDOG_WATCHDOG_CTRL_REG_NMI_RST_Msk;
#endif

	data->reload_val = reload_val;
	while (SYS_WDOG->WATCHDOG_CTRL_REG & SYS_WDOG_WATCHDOG_CTRL_REG_WRITE_BUSY_Msk) {
		/* wait */
	}
	SYS_WDOG->WATCHDOG_REG = reload_val;

	return 0;
}

static int wdg_smartbond_feed(const struct device *dev, int channel_id)
{
	struct wdog_smartbond_data *data = (struct wdog_smartbond_data *)(dev)->data;

	ARG_UNUSED(channel_id);

	while (SYS_WDOG->WATCHDOG_CTRL_REG & SYS_WDOG_WATCHDOG_CTRL_REG_WRITE_BUSY_Msk) {
		/* wait */
	}
	SYS_WDOG->WATCHDOG_REG = data->reload_val;

	return 0;
}

static const struct wdt_driver_api wdg_smartbond_api = {
	.setup = wdg_smartbond_setup,
	.disable = wdg_smartbond_disable,
	.install_timeout = wdg_smartbond_install_timeout,
	.feed = wdg_smartbond_feed,
};

static int wdg_smartbond_init(const struct device *dev)
{
	ARG_UNUSED(dev);

#ifdef CONFIG_WDT_DISABLE_AT_BOOT
	GPREG->SET_FREEZE_REG = GPREG_SET_FREEZE_REG_FRZ_SYS_WDOG_Msk;
#endif

	return 0;
}

DEVICE_DT_INST_DEFINE(0, wdg_smartbond_init, NULL, &wdog_smartbond_dev_data, NULL, POST_KERNEL,
		      CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdg_smartbond_api);
