/*
 * Copyright (c) 2024 Renesas Electronics Corporation
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT renesas_rz_gtm_counter

#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/counter.h>
#include <r_gtm.h>

#define RZ_GTM_TOP_VALUE UINT32_MAX

#define counter_rz_gtm_clear_pending(irq) NVIC_ClearPendingIRQ(irq)
#define counter_rz_gtm_set_pending(irq)   NVIC_SetPendingIRQ(irq)
#define counter_rz_gtm_is_pending(irq)    NVIC_GetPendingIRQ(irq)

struct counter_rz_gtm_config {
	struct counter_config_info config_info;
	const timer_api_t *fsp_api;
	uint8_t irqn;
};

struct counter_rz_gtm_data {
	timer_cfg_t *fsp_cfg;
	gtm_instance_ctrl_t *fsp_ctrl;
	/* top callback function */
	counter_top_callback_t top_cb;
	/* alarm callback function */
	counter_alarm_callback_t alarm_cb;
	void *user_data;
	uint32_t clk_freq;
	struct k_spinlock lock;
	uint32_t guard_period;
	uint32_t top_val;
	bool is_started;
	bool is_periodic;
};

static int counter_rz_gtm_get_value(const struct device *dev, uint32_t *ticks)
{
	const struct counter_rz_gtm_config *cfg = dev->config;
	struct counter_rz_gtm_data *data = dev->data;
	timer_status_t timer_status;
	fsp_err_t err;

	err = cfg->fsp_api->statusGet(data->fsp_ctrl, &timer_status);
	if (err != FSP_SUCCESS) {
		return err;
	}
	uint32_t value = (uint32_t)timer_status.counter;
	*ticks = value;

	return 0;
}

static void counter_rz_gtm_irq_handler(timer_callback_args_t *p_args)
{
	const struct device *dev = p_args->p_context;
	struct counter_rz_gtm_data *data = dev->data;
	counter_alarm_callback_t alarm_callback = data->alarm_cb;
	k_spinlock_key_t key;

	key = k_spin_lock(&data->lock);

	if (alarm_callback) {
		uint32_t now;

		counter_rz_gtm_get_value(dev, &now);
		data->alarm_cb = NULL;
		alarm_callback(dev, 0, now, data->user_data);
	} else if (data->top_cb) {
		data->top_cb(dev, data->user_data);
	}

	k_spin_unlock(&data->lock, key);
}

static int counter_rz_gtm_init(const struct device *dev)
{
	struct counter_rz_gtm_data *data = dev->data;
	const struct counter_rz_gtm_config *cfg = dev->config;
	int err;

	data->top_val = data->fsp_cfg->period_counts;

	err = cfg->fsp_api->open(data->fsp_ctrl, data->fsp_cfg);
	if (err != FSP_SUCCESS) {
		return err;
	}
	return err;
}

static void counter_rz_gtm_start_freerun(const struct device *dev)
{
	/* enable counter in free running mode */
	const struct counter_rz_gtm_config *cfg = dev->config;
	struct counter_rz_gtm_data *data = dev->data;

	gtm_extended_cfg_t *fsp_cfg_extend = (gtm_extended_cfg_t *)data->fsp_cfg->p_extend;

	fsp_cfg_extend->gtm_mode = GTM_TIMER_MODE_FREERUN;
	cfg->fsp_api->close(data->fsp_ctrl);
	cfg->fsp_api->open(data->fsp_ctrl, data->fsp_cfg);
	cfg->fsp_api->start(data->fsp_ctrl);
}

static void counter_rz_gtm_start_interval(const struct device *dev)
{
	/* start timer in interval mode */
	const struct counter_rz_gtm_config *cfg = dev->config;
	struct counter_rz_gtm_data *data = dev->data;

	gtm_extended_cfg_t *fsp_cfg_extend = (gtm_extended_cfg_t *)data->fsp_cfg->p_extend;

	fsp_cfg_extend->gtm_mode = GTM_TIMER_MODE_INTERVAL;
	cfg->fsp_api->close(data->fsp_ctrl);
	cfg->fsp_api->open(data->fsp_ctrl, data->fsp_cfg);
	cfg->fsp_api->start(data->fsp_ctrl);
}

static int counter_rz_gtm_start(const struct device *dev)
{
	const struct counter_rz_gtm_config *cfg = dev->config;
	struct counter_rz_gtm_data *data = dev->data;
	k_spinlock_key_t key;

	key = k_spin_lock(&data->lock);

	if (data->is_started) {
		k_spin_unlock(&data->lock, key);
		return -EALREADY;
	}

	if (data->is_periodic) {
		data->fsp_cfg->period_counts = data->top_val;
		counter_rz_gtm_start_interval(dev);
	} else {
		counter_rz_gtm_start_freerun(dev);
	}

	counter_rz_gtm_clear_pending(cfg->irqn);
	data->is_started = true;
	if (data->top_cb) {
		irq_enable(cfg->irqn);
	}

	k_spin_unlock(&data->lock, key);

	return 0;
}

static int counter_rz_gtm_stop(const struct device *dev)
{
	const struct counter_rz_gtm_config *cfg = dev->config;
	struct counter_rz_gtm_data *data = dev->data;
	k_spinlock_key_t key;

	key = k_spin_lock(&data->lock);

	if (!data->is_started) {
		k_spin_unlock(&data->lock, key);
		return 0;
	}

	fsp_err_t err = FSP_SUCCESS;

	/* Stop timer */
	err = cfg->fsp_api->stop(data->fsp_ctrl);

	/* dis irq */
	irq_disable(cfg->irqn);
	counter_rz_gtm_clear_pending(cfg->irqn);

	data->top_cb = NULL;
	data->alarm_cb = NULL;
	data->user_data = NULL;

	data->is_started = false;

	k_spin_unlock(&data->lock, key);

	return err;
}

static int counter_rz_gtm_set_alarm(const struct device *dev, uint8_t chan,
				    const struct counter_alarm_cfg *alarm_cfg)
{
	const struct counter_rz_gtm_config *cfg = dev->config;
	struct counter_rz_gtm_data *data = dev->data;

	bool absolute;
	uint32_t val;
	k_spinlock_key_t key;
	bool irq_on_late;
	uint32_t max_rel_val;
	uint32_t now, diff;
	int err = 0;

	if (!alarm_cfg) {
		return -EINVAL;
	}

	absolute = alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE;
	val = alarm_cfg->ticks;

	/* Alarm callback is mandatory */
	if (!alarm_cfg->callback) {
		return -EINVAL;
	}

	key = k_spin_lock(&data->lock);

	if (!data->is_started) {
		k_spin_unlock(&data->lock, key);
		return -EINVAL;
	}

	/** Alarm_cb need equal NULL before */
	if (data->alarm_cb) {
		k_spin_unlock(&data->lock, key);
		return -EBUSY;
	}

	/** Timer is currently in interval mode*/
	if (data->is_periodic) {
		/** return error because val exceeded the limit set alarm */
		if (val > data->fsp_cfg->period_counts) {
			k_spin_unlock(&data->lock, key);
			return -EINVAL;
		}

		/* restore free running mode */
		irq_disable(cfg->irqn);
		data->top_cb = NULL;
		data->alarm_cb = alarm_cfg->callback;
		data->user_data = NULL;
		data->top_val = RZ_GTM_TOP_VALUE;
		data->is_periodic = false;

		if (data->is_started) {
			data->fsp_cfg->period_counts = data->top_val;
			counter_rz_gtm_start_freerun(dev);
		}
	}

	counter_rz_gtm_get_value(dev, &now);
	data->alarm_cb = alarm_cfg->callback;
	data->user_data = alarm_cfg->user_data;

	if (absolute) {
		max_rel_val = RZ_GTM_TOP_VALUE - data->guard_period;
		irq_on_late = alarm_cfg->flags & COUNTER_ALARM_CFG_EXPIRE_WHEN_LATE;
	} else {
		/* If relative value is smaller than half of the counter range
		 * it is assumed that there is a risk of setting value too late
		 * and late detection algorithm must be applied. When late
		 * setting is detected, interrupt shall be triggered for
		 * immediate expiration of the timer. Detection is performed
		 * by limiting relative distance between CC and counter.
		 *
		 * Note that half of counter range is an arbitrary value.
		 */
		irq_on_late = val < (RZ_GTM_TOP_VALUE / 2U);
		/* limit max to detect short relative being set too late. */
		max_rel_val = irq_on_late ? RZ_GTM_TOP_VALUE / 2U : RZ_GTM_TOP_VALUE;
		val = (now + val) & RZ_GTM_TOP_VALUE;
	}

	/** Set new period */
	data->fsp_cfg->period_counts = val;
	err = cfg->fsp_api->periodSet(data->fsp_ctrl, data->fsp_cfg->period_counts);
	if (err != FSP_SUCCESS) {
		k_spin_unlock(&data->lock, key);
		return err;
	}

	uint32_t read_counter_again = 0;

	counter_rz_gtm_get_value(dev, &read_counter_again);
	diff = ((val - 1U) - read_counter_again) & RZ_GTM_TOP_VALUE;
	if (diff > max_rel_val) {
		if (absolute) {
			err = -ETIME;
		}

		/* Interrupt is triggered always for relative alarm and
		 * for absolute depending on the flag.
		 */
		if (irq_on_late) {
			irq_enable(cfg->irqn);
			counter_rz_gtm_set_pending(cfg->irqn);
		} else {
			data->alarm_cb = NULL;
		}
	} else {
		if (diff == 0) {
			/* RELOAD value could be set just in time for interrupt
			 * trigger or too late. In any case time is interrupt
			 * should be triggered. No need to enable interrupt
			 * on TIMER just make sure interrupt is pending.
			 */
			irq_enable(cfg->irqn);
			counter_rz_gtm_set_pending(cfg->irqn);
		} else {
			counter_rz_gtm_clear_pending(cfg->irqn);
			irq_enable(cfg->irqn);
		}
	}

	k_spin_unlock(&data->lock, key);

	return err;
}

static int counter_rz_gtm_cancel_alarm(const struct device *dev, uint8_t chan)
{
	const struct counter_rz_gtm_config *cfg = dev->config;
	struct counter_rz_gtm_data *data = dev->data;
	k_spinlock_key_t key;

	ARG_UNUSED(chan);

	key = k_spin_lock(&data->lock);

	if (!data->is_started) {
		k_spin_unlock(&data->lock, key);
		return -EINVAL;
	}

	if (!data->alarm_cb) {
		k_spin_unlock(&data->lock, key);
		return 0;
	}

	irq_disable(cfg->irqn);
	counter_rz_gtm_clear_pending(cfg->irqn);
	data->alarm_cb = NULL;
	data->user_data = NULL;

	k_spin_unlock(&data->lock, key);

	return 0;
}

static int counter_rz_gtm_set_top_value(const struct device *dev,
					const struct counter_top_cfg *top_cfg)
{
	const struct counter_rz_gtm_config *cfg = dev->config;
	struct counter_rz_gtm_data *data = dev->data;
	k_spinlock_key_t key;
	uint32_t cur_tick;
	int ret = 0;

	if (!top_cfg) {
		return -EINVAL;
	}

	/** -EBUSY if any alarm is active */
	if (data->alarm_cb) {
		return -EBUSY;
	}

	key = k_spin_lock(&data->lock);

	if (!data->is_periodic && top_cfg->ticks == RZ_GTM_TOP_VALUE) {
		goto exit_unlock;
	}

	if (top_cfg->ticks == RZ_GTM_TOP_VALUE) {
		/* restore free running mode */
		irq_disable(cfg->irqn);
		counter_rz_gtm_clear_pending(cfg->irqn);
		data->top_cb = NULL;
		data->user_data = NULL;
		data->top_val = RZ_GTM_TOP_VALUE;
		data->is_periodic = false;

		if (data->is_started) {
			counter_rz_gtm_start_freerun(dev);
			counter_rz_gtm_clear_pending(cfg->irqn);
		}
		goto exit_unlock;
	}

	data->top_cb = top_cfg->callback;
	data->user_data = top_cfg->user_data;
	data->top_val = top_cfg->ticks;

	if (!data->is_started) {
		data->is_periodic = true;
		goto exit_unlock;
	}

	if (!data->is_periodic) {
		/* switch to interval mode first time, restart timer */
		ret = cfg->fsp_api->stop(data->fsp_ctrl);
		irq_disable(cfg->irqn);
		data->is_periodic = true;
		data->fsp_cfg->period_counts = data->top_val;
		counter_rz_gtm_start_interval(dev);

		if (data->top_cb) {
			counter_rz_gtm_clear_pending(cfg->irqn);
			irq_enable(cfg->irqn);
		}
		goto exit_unlock;
	}

	if (!data->top_cb) {
		/* new top cfg is without callback - stop IRQs */
		irq_disable(cfg->irqn);
		counter_rz_gtm_clear_pending(cfg->irqn);
	}
	/* timer already in interval mode - only change top value */
	data->fsp_cfg->period_counts = data->top_val;
	cfg->fsp_api->periodSet(&data->fsp_ctrl, data->fsp_cfg->period_counts);

	/* check if counter reset is required */
	if (top_cfg->flags & COUNTER_TOP_CFG_DONT_RESET) {
		/* Don't reset counter */
		counter_rz_gtm_get_value(dev, &cur_tick);

		if (cur_tick >= data->top_val) {
			ret = -ETIME;
			if (top_cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE) {
				/* Reset counter if current is late */
				cfg->fsp_api->stop(data->fsp_ctrl);
				cfg->fsp_api->start(data->fsp_ctrl);
			}
		}
	} else {
		/* reset counter */
		cfg->fsp_api->stop(data->fsp_ctrl);
		cfg->fsp_api->start(data->fsp_ctrl);
	}

exit_unlock:
	k_spin_unlock(&data->lock, key);
	return ret;
}

static uint32_t counter_rz_gtm_get_pending_int(const struct device *dev)
{
	const struct counter_rz_gtm_config *cfg = dev->config;

	/* There is no register to check TIMER peripheral to check for interrupt
	 * pending, check directly in NVIC.
	 */
	return counter_rz_gtm_is_pending(cfg->irqn);
}

static uint32_t counter_rz_gtm_get_top_value(const struct device *dev)
{
	struct counter_rz_gtm_data *data = dev->data;
	const struct counter_rz_gtm_config *cfg = dev->config;
	uint32_t top_val = RZ_GTM_TOP_VALUE;

	if (data->is_periodic) {
		timer_info_t info;

		cfg->fsp_api->infoGet(data->fsp_ctrl, &info);
		top_val = info.period_counts;
	}

	return top_val;
}
static uint32_t counter_rz_gtm_get_guard_period(const struct device *dev, uint32_t flags)
{
	struct counter_rz_gtm_data *data = dev->data;

	ARG_UNUSED(flags);
	return data->guard_period;
}

static int counter_rz_gtm_set_guard_period(const struct device *dev, uint32_t guard, uint32_t flags)
{
	struct counter_rz_gtm_data *data = dev->data;

	ARG_UNUSED(flags);
	__ASSERT_NO_MSG(guard < counter_rz_gtm_get_top_value(dev));

	data->guard_period = guard;

	return 0;
}

static uint32_t counter_rz_gtm_get_freq(const struct device *dev)
{
	struct counter_rz_gtm_data *data = dev->data;
	const struct counter_rz_gtm_config *cfg = dev->config;
	timer_info_t info;

	cfg->fsp_api->infoGet(data->fsp_ctrl, &info);

	return info.clock_frequency;
}

static DEVICE_API(counter, counter_rz_gtm_driver_api) = {
	.start = counter_rz_gtm_start,
	.stop = counter_rz_gtm_stop,
	.get_value = counter_rz_gtm_get_value,
	.set_alarm = counter_rz_gtm_set_alarm,
	.cancel_alarm = counter_rz_gtm_cancel_alarm,
	.set_top_value = counter_rz_gtm_set_top_value,
	.get_pending_int = counter_rz_gtm_get_pending_int,
	.get_top_value = counter_rz_gtm_get_top_value,
	.get_guard_period = counter_rz_gtm_get_guard_period,
	.set_guard_period = counter_rz_gtm_set_guard_period,
	.get_freq = counter_rz_gtm_get_freq,
};

extern void gtm_int_isr(void);

#define GTM(idx) DT_INST_PARENT(idx)

#define COUNTER_RZG_GTM_INIT(inst)                                                                 \
	static gtm_instance_ctrl_t g_timer##inst##_ctrl;                                           \
	static gtm_extended_cfg_t g_timer##inst##_extend = {                                       \
		.generate_interrupt_when_starts = GTM_GIWS_TYPE_DISABLED,                          \
		.gtm_mode = GTM_TIMER_MODE_FREERUN,                                                \
	};                                                                                         \
	static timer_cfg_t g_timer##inst##_cfg = {                                                 \
		.mode = TIMER_MODE_PERIODIC,                                                       \
		.period_counts = (uint32_t)RZ_GTM_TOP_VALUE,                                       \
		.channel = DT_PROP(GTM(inst), channel),                                            \
		.p_callback = counter_rz_gtm_irq_handler,                                          \
		.p_context = DEVICE_DT_GET(DT_DRV_INST(inst)),                                     \
		.p_extend = &g_timer##inst##_extend,                                               \
		.cycle_end_ipl = DT_IRQ_BY_NAME(GTM(inst), overflow, priority),                    \
		.cycle_end_irq = DT_IRQ_BY_NAME(GTM(inst), overflow, irq),                         \
	};                                                                                         \
	static const struct counter_rz_gtm_config counter_rz_gtm_config_##inst = {                 \
		.config_info =                                                                     \
			{                                                                          \
				.max_top_value = RZ_GTM_TOP_VALUE,                                 \
				.flags = COUNTER_CONFIG_INFO_COUNT_UP,                             \
				.channels = 1,                                                     \
			},                                                                         \
		.fsp_api = &g_timer_on_gtm,                                                        \
		.irqn = DT_IRQ_BY_NAME(GTM(inst), overflow, irq),                                  \
	};                                                                                         \
	static struct counter_rz_gtm_data counter_rz_gtm_data_##inst = {                           \
		.fsp_cfg = &g_timer##inst##_cfg, .fsp_ctrl = &g_timer##inst##_ctrl};               \
	static int counter_rz_gtm_init_##inst(const struct device *dev)                            \
	{                                                                                          \
		IRQ_CONNECT(DT_IRQ_BY_NAME(GTM(inst), overflow, irq),                              \
			    DT_IRQ_BY_NAME(GTM(inst), overflow, priority), gtm_int_isr,            \
			    DEVICE_DT_INST_GET(inst), 0);                                          \
		return counter_rz_gtm_init(dev);                                                   \
	}                                                                                          \
	DEVICE_DT_INST_DEFINE(inst, counter_rz_gtm_init_##inst, NULL, &counter_rz_gtm_data_##inst, \
			      &counter_rz_gtm_config_##inst, PRE_KERNEL_1,                         \
			      CONFIG_COUNTER_INIT_PRIORITY, &counter_rz_gtm_driver_api);

DT_INST_FOREACH_STATUS_OKAY(COUNTER_RZG_GTM_INIT)
