/*
 * Copyright (c) 2024-2025 Gerson Fernando Budke <nandojve@gmail.com>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT atmel_sam0_rtc

/** @file
 * @brief RTC driver for Atmel SAM0 MCU family.
 */

#include <stdlib.h>

#include <zephyr/kernel.h>
#include <zephyr/drivers/rtc.h>
#include "rtc_utils.h"

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(rtc_sam0, CONFIG_RTC_LOG_LEVEL);

/* clang-format off */

#define RTC_SAM0_TIME_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	\
	)

#define RTC_SAM0_CALIBRATE_PPB_MAX	(127)
#define RTC_SAM0_CALIBRATE_PPB_QUANTA	(1000)

enum rtc_sam0_counter_mode {
	COUNTER_MODE_0,
	COUNTER_MODE_1,
	COUNTER_MODE_2,
};

struct rtc_sam0_config {
	Rtc *regs;
	enum rtc_sam0_counter_mode mode;
	uint16_t prescaler;

	volatile uint32_t *mclk;
	uint32_t mclk_mask;
	uint32_t gclk_gen;
	uint16_t gclk_id;
	bool has_gclk;
	bool has_osc32kctrl;
	uint8_t osc32_src;
	uint32_t evt_ctrl_msk;

#ifdef CONFIG_RTC_ALARM
	uint8_t alarms_count;
#endif /* CONFIG_RTC_ALARM */
#ifdef CONFIG_RTC_CALIBRATION
	int32_t cal_constant;
#endif
};

struct rtc_sam0_data_cb {
	rtc_alarm_callback cb;
	void *cb_data;
};

struct rtc_sam0_data {
	struct k_spinlock lock;
#ifdef CONFIG_RTC_ALARM
	struct rtc_sam0_data_cb *const alarms;
#endif /* CONFIG_RTC_ALARM */
};

static inline void rtc_sam0_sync(Rtc *rtc)
{
	/* Wait for synchronization */
#ifdef MCLK
	while (rtc->MODE0.SYNCBUSY.reg & RTC_MODE0_SYNCBUSY_MASK) {
	}
#else
	while (rtc->MODE0.STATUS.reg & RTC_STATUS_SYNCBUSY) {
	}
#endif
}

static int rtc_sam0_set_time(const struct device *dev, const struct rtc_time *timeptr)
{
	const struct rtc_sam0_config *cfg = dev->config;
	struct rtc_sam0_data *data = dev->data;
	RtcMode2 *regs = &cfg->regs->MODE2;
	uint32_t datetime = 0;

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

	datetime |= RTC_MODE2_CLOCK_SECOND(timeptr->tm_sec);
	datetime |= RTC_MODE2_CLOCK_MINUTE(timeptr->tm_min);
	datetime |= RTC_MODE2_CLOCK_HOUR(timeptr->tm_hour);
	datetime |= RTC_MODE2_CLOCK_DAY(timeptr->tm_mday);
	datetime |= RTC_MODE2_CLOCK_MONTH(timeptr->tm_mon + 1);
	datetime |= RTC_MODE2_CLOCK_YEAR(timeptr->tm_year - 99);

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

#ifdef MCLK
	regs->CTRLA.reg &= ~RTC_MODE0_CTRLA_ENABLE;
	rtc_sam0_sync(cfg->regs);
	regs->CLOCK.reg = datetime;
	regs->CTRLA.reg |= RTC_MODE0_CTRLA_ENABLE;
#else
	regs->CTRL.reg &= ~RTC_MODE0_CTRL_ENABLE;
	rtc_sam0_sync(cfg->regs);
	regs->CLOCK.reg = datetime;
	regs->CTRL.reg |= RTC_MODE0_CTRL_ENABLE;
#endif

	k_spin_unlock(&data->lock, key);

	return 0;
}

static int rtc_sam0_get_time(const struct device *dev, struct rtc_time *timeptr)
{
	const struct rtc_sam0_config *cfg = dev->config;
	RTC_MODE2_CLOCK_Type calendar = cfg->regs->MODE2.CLOCK;

	timeptr->tm_sec = calendar.bit.SECOND;
	timeptr->tm_min = calendar.bit.MINUTE;
	timeptr->tm_hour = calendar.bit.HOUR;
	timeptr->tm_mday = calendar.bit.DAY;
	timeptr->tm_mon = calendar.bit.MONTH - 1;
	timeptr->tm_year = calendar.bit.YEAR + 99;
	timeptr->tm_wday = -1;
	timeptr->tm_yday = -1;
	timeptr->tm_isdst = -1;
	timeptr->tm_nsec = 0;

	LOG_DBG("D/M/Y H:M:S %02d/%02d/%02d %02d:%02d:%02d",
		timeptr->tm_mday, timeptr->tm_mon + 1, timeptr->tm_year - 99,
		timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);

	return 0;
}

#ifdef CONFIG_RTC_ALARM
static inline uint32_t rtc_sam0_datetime_from_tm(const struct rtc_time *timeptr,
						 uint32_t mask)
{
	uint32_t datetime = 0;

	if (mask & RTC_ALARM_TIME_MASK_SECOND) {
		datetime |= RTC_MODE2_CLOCK_SECOND(timeptr->tm_sec);
	}

	if (mask & RTC_ALARM_TIME_MASK_MINUTE) {
		datetime |= RTC_MODE2_CLOCK_MINUTE(timeptr->tm_min);
	}

	if (mask & RTC_ALARM_TIME_MASK_HOUR) {
		datetime |= RTC_MODE2_CLOCK_HOUR(timeptr->tm_hour);
	}

	if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) {
		datetime |= RTC_MODE2_CLOCK_DAY(timeptr->tm_mday);
	}

	if (mask & RTC_ALARM_TIME_MASK_MONTH) {
		datetime |= RTC_MODE2_CLOCK_MONTH(timeptr->tm_mon + 1);
	}

	if (mask & RTC_ALARM_TIME_MASK_YEAR) {
		datetime |= RTC_MODE2_CLOCK_YEAR(timeptr->tm_year - 99);
	}

	return datetime;
}

static inline void rtc_sam0_tm_from_datetime(struct rtc_time *timeptr, uint32_t mask,
					     RTC_MODE2_ALARM_Type calendar)
{
	memset(timeptr, 0x00, sizeof(struct rtc_time));

	if (mask & RTC_ALARM_TIME_MASK_SECOND) {
		timeptr->tm_sec = calendar.bit.SECOND;
	}

	if (mask & RTC_ALARM_TIME_MASK_MINUTE) {
		timeptr->tm_min = calendar.bit.MINUTE;
	}

	if (mask & RTC_ALARM_TIME_MASK_HOUR) {
		timeptr->tm_hour = calendar.bit.HOUR;
	}

	if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) {
		timeptr->tm_mday = calendar.bit.DAY;
	}

	if (mask & RTC_ALARM_TIME_MASK_MONTH) {
		timeptr->tm_mon = calendar.bit.MONTH - 1;
	}

	if (mask & RTC_ALARM_TIME_MASK_YEAR) {
		timeptr->tm_year = calendar.bit.YEAR + 99;
	}

	timeptr->tm_wday = -1;
	timeptr->tm_yday = -1;
	timeptr->tm_isdst = -1;
	timeptr->tm_nsec = 0;
}

static inline uint32_t rtc_sam0_alarm_msk_from_mask(uint32_t mask)
{
	uint32_t alarm_mask = 0;

	if (mask & RTC_ALARM_TIME_MASK_SECOND) {
		alarm_mask = RTC_MODE2_MASK_SEL_SS_Val;
	}

	if (mask & RTC_ALARM_TIME_MASK_MINUTE) {
		alarm_mask = RTC_MODE2_MASK_SEL_MMSS_Val;
	}

	if (mask & RTC_ALARM_TIME_MASK_HOUR) {
		alarm_mask = RTC_MODE2_MASK_SEL_HHMMSS_Val;
	}

	if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) {
		alarm_mask = RTC_MODE2_MASK_SEL_DDHHMMSS_Val;
	}

	if (mask & RTC_ALARM_TIME_MASK_MONTH) {
		alarm_mask = RTC_MODE2_MASK_SEL_MMDDHHMMSS_Val;
	}

	if (mask & RTC_ALARM_TIME_MASK_YEAR) {
		alarm_mask = RTC_MODE2_MASK_SEL_YYMMDDHHMMSS_Val;
	}

	return alarm_mask;
}

static inline uint32_t rtc_sam0_mask_from_alarm_msk(uint32_t alarm_mask)
{
	uint32_t mask = 0;

	switch (alarm_mask) {
	case RTC_MODE2_MASK_SEL_YYMMDDHHMMSS_Val:
		mask |= RTC_ALARM_TIME_MASK_YEAR;
		__fallthrough;
	case RTC_MODE2_MASK_SEL_MMDDHHMMSS_Val:
		mask |= RTC_ALARM_TIME_MASK_MONTH;
		__fallthrough;
	case RTC_MODE2_MASK_SEL_DDHHMMSS_Val:
		mask |= RTC_ALARM_TIME_MASK_MONTHDAY;
		__fallthrough;
	case RTC_MODE2_MASK_SEL_HHMMSS_Val:
		mask |= RTC_ALARM_TIME_MASK_HOUR;
		__fallthrough;
	case RTC_MODE2_MASK_SEL_MMSS_Val:
		mask |= RTC_ALARM_TIME_MASK_MINUTE;
		__fallthrough;
	case RTC_MODE2_MASK_SEL_SS_Val:
		mask |= RTC_ALARM_TIME_MASK_SECOND;
		break;
	default:
		break;
	}

	return mask;
}

static int rtc_sam0_alarm_get_supported_fields(const struct device *dev, uint16_t id,
					       uint16_t *mask)
{
	ARG_UNUSED(dev);
	ARG_UNUSED(id);

	*mask = RTC_SAM0_TIME_MASK;

	return 0;
}

static int rtc_sam0_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask,
				   const struct rtc_time *timeptr)
{
	const struct rtc_sam0_config *cfg = dev->config;
	struct rtc_sam0_data *data = dev->data;
	RtcMode2 *regs = &cfg->regs->MODE2;
	uint32_t mask_supported = RTC_SAM0_TIME_MASK;
	uint32_t datetime;
	uint32_t alarm_msk;

	if (BIT(id) > RTC_MODE2_INTFLAG_ALARM_Msk) {
		return -EINVAL;
	}

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

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

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

	datetime = rtc_sam0_datetime_from_tm(timeptr, mask);
	alarm_msk = rtc_sam0_alarm_msk_from_mask(mask);

	LOG_DBG("S: datetime: %d, mask: %d", datetime, alarm_msk);

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

	irq_disable(DT_INST_IRQN(0));

	rtc_sam0_sync(cfg->regs);
	regs->Mode2Alarm[id].ALARM.reg = datetime;
	regs->Mode2Alarm[id].MASK.reg = RTC_MODE2_MASK_SEL(alarm_msk);
	regs->INTFLAG.reg = RTC_MODE2_INTFLAG_ALARM(BIT(id));

	irq_enable(DT_INST_IRQN(0));

	k_spin_unlock(&data->lock, key);

	return 0;
}

static int rtc_sam0_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask,
				   struct rtc_time *timeptr)
{
	const struct rtc_sam0_config *cfg = dev->config;
	struct rtc_sam0_data *data = dev->data;
	RtcMode2 *regs = &cfg->regs->MODE2;
	RTC_MODE2_ALARM_Type datetime;
	uint32_t alarm_msk;

	if (BIT(id) > RTC_MODE2_INTFLAG_ALARM_Msk) {
		return -EINVAL;
	}

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

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

	rtc_sam0_sync(cfg->regs);

	datetime = regs->Mode2Alarm[id].ALARM;
	alarm_msk = regs->Mode2Alarm[id].MASK.reg;

	LOG_DBG("G: datetime: %d, mask: %d", datetime.reg, alarm_msk);

	k_spin_unlock(&data->lock, key);

	*mask = rtc_sam0_mask_from_alarm_msk(alarm_msk);

	rtc_sam0_tm_from_datetime(timeptr, *mask, datetime);

	return 0;
}

static int rtc_sam0_alarm_is_pending(const struct device *dev, uint16_t id)
{
	const struct rtc_sam0_config *cfg = dev->config;
	struct rtc_sam0_data *data = dev->data;
	RtcMode2 *regs = &cfg->regs->MODE2;

	if (BIT(id) > RTC_MODE2_INTFLAG_ALARM_Msk) {
		return -EINVAL;
	}

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

	if ((regs->INTFLAG.reg & RTC_MODE2_INTFLAG_ALARM(BIT(id))) == 0) {
		k_spin_unlock(&data->lock, key);

		return 0;
	}

	regs->INTFLAG.reg = RTC_MODE2_INTFLAG_ALARM(BIT(id));

	k_spin_unlock(&data->lock, key);

	return 1;
}

static int rtc_sam0_alarm_set_callback(const struct device *dev, uint16_t id,
				       rtc_alarm_callback callback, void *user_data)
{
	const struct rtc_sam0_config *cfg = dev->config;
	struct rtc_sam0_data *data = dev->data;
	RtcMode2 *regs = &cfg->regs->MODE2;

	if (BIT(id) > RTC_MODE2_INTFLAG_ALARM_Msk) {
		return -EINVAL;
	}

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

	data->alarms[id].cb = callback;
	data->alarms[id].cb_data = user_data;

	if (callback) {
		regs->INTENSET.reg = RTC_MODE2_INTENSET_ALARM(BIT(id));
	} else {
		regs->INTENCLR.reg = RTC_MODE2_INTENCLR_ALARM(BIT(id));
	}

	k_spin_unlock(&data->lock, key);

	return 0;
}

static void rtc_sam0_isr(const struct device *dev)
{
	const struct rtc_sam0_config *cfg = dev->config;
	struct rtc_sam0_data *data = dev->data;
	RtcMode2 *regs = &cfg->regs->MODE2;
	uint32_t int_flags = regs->INTFLAG.reg;

	for (int i = 0; i < cfg->alarms_count; ++i) {
		if (int_flags & RTC_MODE2_INTFLAG_ALARM(BIT(i))) {
			if (data->alarms[i].cb != NULL) {
				data->alarms[i].cb(dev, i, data->alarms[i].cb_data);
			}
		}
	}

	regs->INTFLAG.reg |= int_flags;
}

#endif /* CONFIG_RTC_ALARM */

#ifdef CONFIG_RTC_CALIBRATION
static int rtc_sam0_set_calibration(const struct device *dev, int32_t calibration)
{
	const struct rtc_sam0_config *cfg = dev->config;
	RtcMode2 *regs = &cfg->regs->MODE2;
	int32_t correction = calibration / (1000000000 / cfg->cal_constant);
	uint32_t abs_correction = abs(correction);

	LOG_DBG("Correction: %d, Absolute: %d, Calibration: %d",
		correction, abs_correction, calibration);

	if (abs_correction == 0) {
		regs->FREQCORR.reg = 0;
		return 0;
	}

	if (abs_correction > RTC_SAM0_CALIBRATE_PPB_MAX) {
		LOG_ERR("The calibration %d result in an out of range value %d",
			calibration, abs_correction);
		return -EINVAL;
	}

	rtc_sam0_sync(cfg->regs);
	regs->FREQCORR.reg = RTC_FREQCORR_VALUE(abs_correction)
			   | (correction < 0 ? RTC_FREQCORR_SIGN : 0);

	LOG_DBG("W REG: 0x%02x", regs->FREQCORR.reg);

	return 0;
}

static int rtc_sam0_get_calibration(const struct device *dev, int32_t *calibration)
{
	const struct rtc_sam0_config *cfg = dev->config;
	RtcMode2 *regs = &cfg->regs->MODE2;
	int32_t correction;

	if (calibration == NULL) {
		return -EINVAL;
	}

	correction = regs->FREQCORR.bit.VALUE;

	if (correction == 0) {
		*calibration = 0;
	} else {
		*calibration = (correction * 1000000000) / cfg->cal_constant;
	}

	if (regs->FREQCORR.bit.SIGN) {
		*calibration *= -1;
	}

	LOG_DBG("R REG: 0x%02x", regs->FREQCORR.reg);

	return 0;
}
#endif /* CONFIG_RTC_CALIBRATION */

static int rtc_sam0_init(const struct device *dev)
{
	const struct rtc_sam0_config *cfg = dev->config;
	RtcMode0 *regs = &cfg->regs->MODE0;

	LOG_DBG("Counter Mode %d selected", cfg->mode);
	LOG_DBG("gclk_id: %d, gclk_gen: %d, prescaler: %d, osc32k: %d",
		cfg->gclk_id, cfg->gclk_gen, cfg->prescaler, cfg->osc32_src);

	*cfg->mclk |= cfg->mclk_mask;

#ifdef MCLK
	if (cfg->has_gclk) {
		GCLK->PCHCTRL[cfg->gclk_id].reg = GCLK_PCHCTRL_CHEN
						| GCLK_PCHCTRL_GEN(cfg->gclk_gen);
	}
#else
	GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN
			  | GCLK_CLKCTRL_GEN(cfg->gclk_gen)
			  | GCLK_CLKCTRL_ID(cfg->gclk_id);
#endif
	rtc_sam0_sync(cfg->regs);

#ifdef MCLK
	if (cfg->has_osc32kctrl) {
		OSC32KCTRL->RTCCTRL.reg = OSC32KCTRL_RTCCTRL_RTCSEL(cfg->osc32_src);
	}
#endif

	rtc_sam0_sync(cfg->regs);
	regs->EVCTRL.reg = (cfg->evt_ctrl_msk & RTC_MODE0_EVCTRL_MASK);

#ifdef MCLK
	regs->CTRLA.reg = RTC_MODE0_CTRLA_ENABLE
			| RTC_MODE0_CTRLA_COUNTSYNC
			| RTC_MODE0_CTRLA_MODE(cfg->mode)
			| RTC_MODE0_CTRLA_PRESCALER(cfg->prescaler + 1);
#else
	regs->CTRL.reg = RTC_MODE0_CTRL_ENABLE
		       | RTC_MODE0_CTRL_MODE(cfg->mode)
		       | RTC_MODE0_CTRL_PRESCALER(cfg->prescaler);
#endif

	regs->INTFLAG.reg = 0;
#ifdef CONFIG_RTC_ALARM
	IRQ_CONNECT(DT_INST_IRQN(0),
		    DT_INST_IRQ(0, priority),
		    rtc_sam0_isr,
		    DEVICE_DT_INST_GET(0), 0);
	irq_enable(DT_INST_IRQN(0));
#endif
	return 0;
}

static DEVICE_API(rtc, rtc_sam0_driver_api) = {
	.set_time = rtc_sam0_set_time,
	.get_time = rtc_sam0_get_time,
#ifdef CONFIG_RTC_ALARM
	.alarm_get_supported_fields = rtc_sam0_alarm_get_supported_fields,
	.alarm_set_time = rtc_sam0_alarm_set_time,
	.alarm_get_time = rtc_sam0_alarm_get_time,
	.alarm_is_pending = rtc_sam0_alarm_is_pending,
	.alarm_set_callback = rtc_sam0_alarm_set_callback,
#endif /* CONFIG_RTC_ALARM */
#ifdef CONFIG_RTC_CALIBRATION
	.set_calibration = rtc_sam0_set_calibration,
	.get_calibration = rtc_sam0_get_calibration,
#endif /* CONFIG_RTC_CALIBRATION */
};

#define ASSIGNED_CLOCKS_CELL_BY_NAME							\
	ATMEL_SAM0_DT_INST_ASSIGNED_CLOCKS_CELL_BY_NAME

#define RTC_SAM0_GCLK(n)								\
	COND_CODE_1(DT_INST_CLOCKS_HAS_NAME(n, gclk),					\
	(										\
		.has_gclk = true,							\
		.gclk_gen = ASSIGNED_CLOCKS_CELL_BY_NAME(n, gclk, gen),			\
		.gclk_id = DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, id)			\
	),										\
	(										\
		.has_gclk = false,							\
		.gclk_gen = 0,								\
		.gclk_id = 0								\
	))

#define RTC_SAM0_OSC32KCTRL(n)								\
	COND_CODE_1(DT_INST_CLOCKS_HAS_NAME(n, osc32kctrl),				\
	(										\
		.has_osc32kctrl = true,							\
		.osc32_src = ASSIGNED_CLOCKS_CELL_BY_NAME(n, osc32kctrl, src)		\
	),										\
	(										\
		.has_osc32kctrl = false,						\
		.osc32_src = 0								\
	))

#define RTC_SAM0_DEVICE(n)								\
	BUILD_ASSERT(DT_INST_NODE_HAS_PROP(n, counter_mode),				\
	"sam0:rtc: Missing counter-mode devicetree property");				\
	BUILD_ASSERT(DT_INST_NODE_HAS_PROP(n, prescaler),				\
	"sam0:rtc: Missing prescaler devicetree property");				\
											\
	static const struct rtc_sam0_config rtc_sam0_config_##n = {			\
		.regs = (Rtc *)DT_INST_REG_ADDR(n),					\
		.mode = DT_INST_ENUM_IDX(n, counter_mode),				\
		.prescaler = DT_INST_ENUM_IDX(n, prescaler),				\
		.evt_ctrl_msk = DT_INST_PROP(n, event_control_msk),			\
		RTC_SAM0_GCLK(n),							\
		RTC_SAM0_OSC32KCTRL(n),							\
		.mclk = ATMEL_SAM0_DT_INST_MCLK_PM_REG_ADDR_OFFSET(n),			\
		.mclk_mask = ATMEL_SAM0_DT_INST_MCLK_PM_PERIPH_MASK(n, bit),		\
		IF_ENABLED(CONFIG_RTC_ALARM, (						\
			.alarms_count = DT_INST_PROP(n, alarms_count),			\
		))									\
		IF_ENABLED(CONFIG_RTC_CALIBRATION, (					\
			.cal_constant = DT_INST_PROP(n, cal_constant),			\
		))									\
	};										\
											\
	IF_ENABLED(CONFIG_RTC_ALARM, (							\
		static struct rtc_sam0_data_cb						\
			rtc_sam0_data_cb_##n[DT_INST_PROP(n, alarms_count)] = {};	\
	))										\
											\
	static struct rtc_sam0_data rtc_sam0_data_##n = {				\
		IF_ENABLED(CONFIG_RTC_ALARM, (						\
			.alarms = rtc_sam0_data_cb_##n,					\
		))									\
	};										\
											\
	DEVICE_DT_INST_DEFINE(n, rtc_sam0_init,						\
			      NULL,							\
			      &rtc_sam0_data_##n,					\
			      &rtc_sam0_config_##n, POST_KERNEL,			\
			      CONFIG_RTC_INIT_PRIORITY,					\
			      &rtc_sam0_driver_api);					\

DT_INST_FOREACH_STATUS_OKAY(RTC_SAM0_DEVICE);

/* clang-format on */
