/*
 * Copyright (c) 2024 Nuvoton Technology Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT nuvoton_numaker_rtc

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/irq.h>
#include <zephyr/drivers/rtc.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
#include <zephyr/spinlock.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/clock_control_numaker.h>
#include "rtc_utils.h"

LOG_MODULE_REGISTER(rtc_numaker, CONFIG_RTC_LOG_LEVEL);

/* RTC support 2000 ~ 2099 */
#define NVT_RTC_YEAR_MIN 2000U
#define NVT_RTC_YEAR_MAX 2099U
/* struct tm start time:   1st, Jan, 1900 */
#define TM_YEAR_REF 1900U

#define NVT_TIME_SCALE RTC_CLOCK_24
#define NVT_ALARM_MSK 0x3fU
#define NVT_ALARM_UNIT_MSK 0x03U

struct rtc_numaker_config {
	RTC_T *rtc_base;
	uint32_t clk_modidx;
	const struct device *clk_dev;
	uint32_t oscillator;
};

struct rtc_numaker_data {
	struct k_spinlock lock;
#ifdef CONFIG_RTC_ALARM
	rtc_alarm_callback alarm_callback;
	void *alarm_user_data;
	bool alarm_pending;
#endif /* CONFIG_RTC_ALARM */
};

struct rtc_numaker_time {
	uint32_t year;		/* Year value */
	uint32_t month;		/* Month value */
	uint32_t day;		/* Day value */
	uint32_t day_of_week;	/* Day of week value */
	uint32_t hour;		/* Hour value */
	uint32_t minute;	/* Minute value */
	uint32_t second;	/* Second value */
	uint32_t time_scale;	/* 12-Hour, 24-Hour */
	uint32_t am_pm;		/* Only Time Scale select 12-hr used */
};

static int rtc_numaker_set_time(const struct device *dev, const struct rtc_time *timeptr)
{
	struct rtc_numaker_time curr_time;
	struct rtc_numaker_data *data = dev->data;
	uint32_t real_year = timeptr->tm_year + TM_YEAR_REF;
#ifdef CONFIG_RTC_ALARM
	const struct rtc_numaker_config *config = dev->config;
	RTC_T *rtc_base = config->rtc_base;
#endif

	if (real_year < NVT_RTC_YEAR_MIN || real_year > NVT_RTC_YEAR_MAX) {
		/* RTC can't support years out of 2000 ~ 2099 */
		return -EINVAL;
	}

	if (timeptr->tm_wday == -1) {
		return -EINVAL;
	}

	curr_time.year = real_year;
	curr_time.month = timeptr->tm_mon + 1;
	curr_time.day = timeptr->tm_mday;
	curr_time.hour = timeptr->tm_hour;
	curr_time.minute = timeptr->tm_min;
	curr_time.second = timeptr->tm_sec;
	curr_time.day_of_week = timeptr->tm_wday;
	curr_time.time_scale = NVT_TIME_SCALE;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	RTC_SetDateAndTime((S_RTC_TIME_DATA_T *)&curr_time);

#ifdef CONFIG_RTC_ALARM
	/* Restore RTC alarm mask */
	rtc_base->CAMSK = rtc_base->SPR[1];
	rtc_base->TAMSK = rtc_base->SPR[2];
#endif
	k_spin_unlock(&data->lock, key);

	return 0;
}

static int rtc_numaker_get_time(const struct device *dev, struct rtc_time *timeptr)
{
	struct rtc_numaker_data *data = dev->data;
	struct rtc_numaker_time curr_time;

	curr_time.time_scale = NVT_TIME_SCALE;
	k_spinlock_key_t key = k_spin_lock(&data->lock);

	RTC_GetDateAndTime((S_RTC_TIME_DATA_T *)&curr_time);
	k_spin_unlock(&data->lock, key);

	timeptr->tm_year = curr_time.year - TM_YEAR_REF;
	timeptr->tm_mon = curr_time.month - 1;
	timeptr->tm_mday = curr_time.day;
	timeptr->tm_wday = curr_time.day_of_week;

	timeptr->tm_hour = curr_time.hour;
	timeptr->tm_min = curr_time.minute;
	timeptr->tm_sec = curr_time.second;
	timeptr->tm_nsec = 0;

	/* unknown values */
	timeptr->tm_yday = -1;
	timeptr->tm_isdst = -1;

	return 0;
}

static void rtc_numaker_isr(const struct device *dev)
{
	const struct rtc_numaker_config *config = dev->config;
	RTC_T *rtc_base = config->rtc_base;
	uint32_t int_status;
#ifdef CONFIG_RTC_ALARM
	struct rtc_numaker_data *data = dev->data;
#endif

	int_status = rtc_base->INTSTS;
	if (int_status & RTC_INTSTS_TICKIF_Msk) {
		/* Clear RTC Tick interrupt flag */
		rtc_base->INTSTS = RTC_INTSTS_TICKIF_Msk;
	}

#ifdef CONFIG_RTC_ALARM
	if (int_status & RTC_INTSTS_ALMIF_Msk) {
		rtc_alarm_callback callback;
		void *user_data;

		/* Clear RTC Alarm interrupt flag */
		rtc_base->INTSTS = RTC_INTSTS_ALMIF_Msk;
		rtc_base->CAMSK = 0x00;
		rtc_base->TAMSK = 0x00;
		callback = data->alarm_callback;
		user_data = data->alarm_user_data;
		data->alarm_pending = callback ? false : true;

		if (callback != NULL) {
			callback(dev, 0, user_data);
		}
	}
#endif /* CONFIG_RTC_ALARM */
}

#ifdef CONFIG_RTC_ALARM
static int rtc_numaker_alarm_get_supported_fields(const struct device *dev, uint16_t id,
						  uint16_t *mask)
{
	ARG_UNUSED(dev);
	ARG_UNUSED(id);

	*mask =  RTC_ALARM_TIME_MASK_SECOND
		 | RTC_ALARM_TIME_MASK_MINUTE
		 | RTC_ALARM_TIME_MASK_HOUR
		 | RTC_ALARM_TIME_MASK_MONTHDAY
		 | RTC_ALARM_TIME_MASK_MONTH
		 | RTC_ALARM_TIME_MASK_YEAR;

	return 0;
}

static int rtc_numaker_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask,
				      const struct rtc_time *timeptr)
{
	struct rtc_numaker_data *data = dev->data;
	const struct rtc_numaker_config *config = dev->config;
	RTC_T *rtc_base = config->rtc_base;
	uint16_t mask_capable;
	struct rtc_numaker_time alarm_time;

	rtc_numaker_alarm_get_supported_fields(dev, 0, &mask_capable);

	if ((id != 0)) {
		return -EINVAL;
	}

	if ((mask != 0) && (timeptr == NULL)) {
		return -EINVAL;
	}

	if (mask & ~mask_capable) {
		return -EINVAL;
	}

	if (rtc_utils_validate_rtc_time(timeptr, mask) == false) {
		return -EINVAL;
	}

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	irq_disable(DT_INST_IRQN(0));
	if ((mask == 0) || (timeptr == NULL)) {
		/* Disable the alarm */
		rtc_base->SPR[0] = mask;
		rtc_base->SPR[1] = 0x00;
		rtc_base->SPR[2] = 0x00;
		irq_enable(DT_INST_IRQN(0));
		k_spin_unlock(&data->lock, key);
		rtc_base->CAMSK = rtc_base->SPR[1];
		rtc_base->TAMSK = rtc_base->SPR[2];
		/* Disable RTC Alarm Interrupt */
		RTC_DisableInt(RTC_INTEN_ALMIEN_Msk);
		return 0;
	}

	alarm_time.time_scale = NVT_TIME_SCALE;
	RTC_GetDateAndTime((S_RTC_TIME_DATA_T *)&alarm_time);

	/* Reset RTC alarm mask of camsk & tamsk */
	uint32_t camsk = NVT_ALARM_MSK;
	uint32_t tamsk = NVT_ALARM_MSK;

	/* Set H/W care to match bits */
	if (mask & RTC_ALARM_TIME_MASK_YEAR) {
		alarm_time.year = timeptr->tm_year + TM_YEAR_REF;
		camsk &= ~(NVT_ALARM_UNIT_MSK << RTC_CAMSK_MYEAR_Pos);
	}
	if (mask & RTC_ALARM_TIME_MASK_MONTH) {
		alarm_time.month = timeptr->tm_mon + 1;
		camsk &= ~(NVT_ALARM_UNIT_MSK << RTC_CAMSK_MMON_Pos);
	}
	if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) {
		alarm_time.day = timeptr->tm_mday;
		camsk &= ~(NVT_ALARM_UNIT_MSK << RTC_CAMSK_MDAY_Pos);
	}
	if (mask & RTC_ALARM_TIME_MASK_HOUR) {
		alarm_time.hour = timeptr->tm_hour;
		tamsk &= ~(NVT_ALARM_UNIT_MSK << RTC_TAMSK_MHR_Pos);
	}
	if (mask & RTC_ALARM_TIME_MASK_MINUTE) {
		alarm_time.minute = timeptr->tm_min;
		tamsk &= ~(NVT_ALARM_UNIT_MSK << RTC_TAMSK_MMIN_Pos);
	}
	if (mask & RTC_ALARM_TIME_MASK_SECOND) {
		alarm_time.second = timeptr->tm_sec;
		tamsk &= ~(NVT_ALARM_UNIT_MSK << RTC_TAMSK_MSEC_Pos);
	}

	/* Disable RTC Alarm Interrupt */
	RTC_DisableInt(RTC_INTEN_ALMIEN_Msk);

	/* Set the alarm time */
	RTC_SetAlarmDateAndTime((S_RTC_TIME_DATA_T *)&alarm_time);

	/* Clear RTC alarm interrupt flag */
	RTC_CLEAR_ALARM_INT_FLAG();

	rtc_base->SPR[0] = mask;
	rtc_base->SPR[1] = camsk;
	rtc_base->SPR[2] = tamsk;

	rtc_base->CAMSK = rtc_base->SPR[1];
	rtc_base->TAMSK = rtc_base->SPR[2];

	k_spin_unlock(&data->lock, key);
	irq_enable(DT_INST_IRQN(0));

	/* Enable RTC Alarm Interrupt */
	RTC_EnableInt(RTC_INTEN_ALMIEN_Msk);

	return 0;
}

static int rtc_numaker_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask,
				      struct rtc_time *timeptr)
{
	struct rtc_numaker_data *data = dev->data;
	const struct rtc_numaker_config *config = dev->config;
	RTC_T *rtc_base = config->rtc_base;
	struct rtc_numaker_time alarm_time;

	if ((id != 0) || (mask == NULL) || (timeptr == NULL)) {
		return -EINVAL;
	}

	alarm_time.time_scale = NVT_TIME_SCALE;

	K_SPINLOCK(&data->lock) {
		RTC_GetAlarmDateAndTime((S_RTC_TIME_DATA_T *)&alarm_time);
	}

	*mask = rtc_base->SPR[0];
	if (*mask & RTC_ALARM_TIME_MASK_YEAR) {
		timeptr->tm_year = alarm_time.year - TM_YEAR_REF;
	}
	if (*mask & RTC_ALARM_TIME_MASK_MONTH) {
		timeptr->tm_mon = alarm_time.month - 1;
	}
	if (*mask & RTC_ALARM_TIME_MASK_MONTHDAY) {
		timeptr->tm_mday = alarm_time.day;
	}
	if (*mask & RTC_ALARM_TIME_MASK_HOUR) {
		timeptr->tm_hour = alarm_time.hour;
	}
	if (*mask & RTC_ALARM_TIME_MASK_MINUTE) {
		timeptr->tm_min = alarm_time.minute;
	}
	if (*mask & RTC_ALARM_TIME_MASK_SECOND) {
		timeptr->tm_sec = alarm_time.second;
	}

	return 0;
}

static int rtc_numaker_alarm_is_pending(const struct device *dev, uint16_t id)
{
	struct rtc_numaker_data *data = dev->data;
	int ret;

	if (id != 0) {
		return -EINVAL;
	}

	K_SPINLOCK(&data->lock) {
		ret = data->alarm_pending ? 1 : 0;
		data->alarm_pending = false;
	}

	return ret;
}

static int rtc_numaker_alarm_set_callback(const struct device *dev, uint16_t id,
					  rtc_alarm_callback callback, void *user_data)
{
	struct rtc_numaker_data *data = dev->data;

	if (id != 0) {
		return -EINVAL;
	}

	K_SPINLOCK(&data->lock) {
		irq_disable(DT_INST_IRQN(0));
		data->alarm_callback = callback;
		data->alarm_user_data = user_data;
		if ((callback == NULL) && (user_data == NULL)) {
			/* Disable RTC Alarm Interrupt */
			RTC_DisableInt(RTC_INTEN_ALMIEN_Msk);
		}
		irq_enable(DT_INST_IRQN(0));
	}

	return 0;
}
#endif /* CONFIG_RTC_ALARM */

static DEVICE_API(rtc, rtc_numaker_driver_api) = {
	.set_time = rtc_numaker_set_time,
	.get_time = rtc_numaker_get_time,
#ifdef CONFIG_RTC_ALARM
	.alarm_get_supported_fields = rtc_numaker_alarm_get_supported_fields,
	.alarm_set_time = rtc_numaker_alarm_set_time,
	.alarm_get_time = rtc_numaker_alarm_get_time,
	.alarm_is_pending = rtc_numaker_alarm_is_pending,
	.alarm_set_callback = rtc_numaker_alarm_set_callback,
#endif /* CONFIG_RTC_ALARM */
};

static int rtc_numaker_init(const struct device *dev)
{
	const struct rtc_numaker_config *cfg = dev->config;
	struct numaker_scc_subsys scc_subsys;
	RTC_T *rtc_base = cfg->rtc_base;
	int err;

	/* CLK controller */
	memset(&scc_subsys, 0x00, sizeof(scc_subsys));
	scc_subsys.subsys_id = NUMAKER_SCC_SUBSYS_ID_PCC;
	scc_subsys.pcc.clk_modidx = cfg->clk_modidx;

	SYS_UnlockReg();

	/* CLK_EnableModuleClock */
	err = clock_control_on(cfg->clk_dev, (clock_control_subsys_t)&scc_subsys);
	if (err != 0) {
		goto done;
	}

	RTC_SetClockSource(cfg->oscillator);
	/* Enable spare registers */
	rtc_base->SPRCTL = RTC_SPRCTL_SPRRWEN_Msk;

	irq_disable(DT_INST_IRQN(0));

	IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), rtc_numaker_isr,
		    DEVICE_DT_INST_GET(0), 0);

	irq_enable(DT_INST_IRQN(0));
	err = RTC_Open(0);

done:
	SYS_LockReg();
	return err;
}

static struct rtc_numaker_data rtc_data;

/* Set config based on DTS */
static const struct rtc_numaker_config rtc_config = {
	.rtc_base = (RTC_T *)DT_INST_REG_ADDR(0),
	.clk_modidx = DT_INST_CLOCKS_CELL(0, clock_module_index),
	.clk_dev = DEVICE_DT_GET(DT_PARENT(DT_INST_CLOCKS_CTLR(0))),
	.oscillator = DT_ENUM_IDX(DT_NODELABEL(rtc), oscillator),
};

DEVICE_DT_INST_DEFINE(0, &rtc_numaker_init, NULL, &rtc_data, &rtc_config, PRE_KERNEL_1,
		      CONFIG_RTC_INIT_PRIORITY, &rtc_numaker_driver_api);
