/*
 * Copyright (c) 2025 Nordic Semiconductor ASA
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT nordic_nrf_auxpll

#include <errno.h>
#include <stdint.h>

#include <zephyr/arch/cpu.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>
#include <zephyr/sys/util.h>
#include <zephyr/toolchain.h>
#include <zephyr/dt-bindings/clock/nrf-auxpll.h>
#include "clock_control_nrf2_common.h"

#include <hal/nrf_auxpll.h>


/* Check dt-bindings match MDK frequency division definitions*/
#define CHECK_DTS_BINDING_VS_MDK(dt, mdk) \
	BUILD_ASSERT((mdk) == (dt), \
		"Different " #mdk " definition in MDK and devicetree binding")

CHECK_DTS_BINDING_VS_MDK(NRF_AUXPLL_FREQ_DIV_MIN,	 NRF_AUXPLL_FREQUENCY_DIV_MIN);
CHECK_DTS_BINDING_VS_MDK(NRF_AUXPLL_FREQ_DIV_AUDIO_44K1, NRF_AUXPLL_FREQUENCY_AUDIO_44K1);
CHECK_DTS_BINDING_VS_MDK(NRF_AUXPLL_FREQ_DIV_USB24M,	 NRF_AUXPLL_FREQUENCY_USB_24M);
CHECK_DTS_BINDING_VS_MDK(NRF_AUXPLL_FREQ_DIV_AUDIO_48K,	 NRF_AUXPLL_FREQUENCY_AUDIO_48K);
CHECK_DTS_BINDING_VS_MDK(NRF_AUXPLL_FREQ_DIV_MAX,	 NRF_AUXPLL_FREQUENCY_DIV_MAX);

CHECK_DTS_BINDING_VS_MDK(NRF_AUXPLL_OUT_DIV_1,		 NRF_AUXPLL_CTRL_OUTSEL_DIV_1);
CHECK_DTS_BINDING_VS_MDK(NRF_AUXPLL_OUT_DIV_2,		 NRF_AUXPLL_CTRL_OUTSEL_DIV_2);
CHECK_DTS_BINDING_VS_MDK(NRF_AUXPLL_OUT_DIV_3,		 NRF_AUXPLL_CTRL_OUTSEL_DIV_3);
CHECK_DTS_BINDING_VS_MDK(NRF_AUXPLL_OUT_DIV_4,		 NRF_AUXPLL_CTRL_OUTSEL_DIV_4);
CHECK_DTS_BINDING_VS_MDK(NRF_AUXPLL_OUT_DIV_6,		 NRF_AUXPLL_CTRL_OUTSEL_DIV_6);
CHECK_DTS_BINDING_VS_MDK(NRF_AUXPLL_OUT_DIV_8,		 NRF_AUXPLL_CTRL_OUTSEL_DIV_8);
CHECK_DTS_BINDING_VS_MDK(NRF_AUXPLL_OUT_DIV_12,		 NRF_AUXPLL_CTRL_OUTSEL_DIV_12);
CHECK_DTS_BINDING_VS_MDK(NRF_AUXPLL_OUT_DIV_16,		 NRF_AUXPLL_CTRL_OUTSEL_DIV_16);

/* maximum lock time in us, >10x time observed experimentally */
#define AUXPLL_LOCK_TIME_MAX_US  20000
/* lock wait step in us*/
#define AUXPLL_LOCK_WAIT_STEP_US 1000

struct dev_data_auxpll {
	struct onoff_manager mgr;
	onoff_notify_fn notify;
	const struct device *dev;
};

struct clock_control_nrf_auxpll_config {
	NRF_AUXPLL_Type *auxpll;
	uint32_t ref_clk_hz;
	uint32_t ficr_ctune;
	nrf_auxpll_config_t cfg;
	nrf_auxpll_freq_div_ratio_t frequency;
	nrf_auxpll_ctrl_outsel_t out_div;
};

static int clock_control_nrf_auxpll_on(struct dev_data_auxpll *dev_data)
{
	const struct clock_control_nrf_auxpll_config *config = dev_data->dev->config;
	bool locked;

	nrf_auxpll_task_trigger(config->auxpll, NRF_AUXPLL_TASK_START);

	NRFX_WAIT_FOR(nrf_auxpll_mode_locked_check(config->auxpll),
					AUXPLL_LOCK_TIME_MAX_US / AUXPLL_LOCK_WAIT_STEP_US,
					AUXPLL_LOCK_WAIT_STEP_US, locked);

	return locked ? 0 : -ETIMEDOUT;
}

static int clock_control_nrf_auxpll_off(struct dev_data_auxpll *dev_data)
{
	const struct clock_control_nrf_auxpll_config *config = dev_data->dev->config;

	nrf_auxpll_task_trigger(config->auxpll, NRF_AUXPLL_TASK_STOP);

	while (nrf_auxpll_running_check(config->auxpll)) {
	}

	return 0;
}

static void onoff_start_auxpll(struct onoff_manager *mgr, onoff_notify_fn notify)
{
	struct dev_data_auxpll *dev_data =
		CONTAINER_OF(mgr, struct dev_data_auxpll, mgr);

	int ret = clock_control_nrf_auxpll_on(dev_data);

	notify(&dev_data->mgr, ret);

}

static void onoff_stop_auxpll(struct onoff_manager *mgr, onoff_notify_fn notify)
{
	struct dev_data_auxpll *dev_data =
		CONTAINER_OF(mgr, struct dev_data_auxpll, mgr);

	clock_control_nrf_auxpll_off(dev_data);
	notify(mgr, 0);
}

static int api_request_auxpll(const struct device *dev,
			      const struct nrf_clock_spec *spec,
			      struct onoff_client *cli)
{
	struct dev_data_auxpll *dev_data = dev->data;

	ARG_UNUSED(spec);

	return onoff_request(&dev_data->mgr, cli);
}

static int api_release_auxpll(const struct device *dev,
			      const struct nrf_clock_spec *spec)
{
	struct dev_data_auxpll *dev_data = dev->data;

	ARG_UNUSED(spec);

	return onoff_release(&dev_data->mgr);
}

static int api_cancel_or_release_auxpll(const struct device *dev,
					const struct nrf_clock_spec *spec,
					struct onoff_client *cli)
{
	struct dev_data_auxpll *dev_data = dev->data;

	ARG_UNUSED(spec);

	return onoff_cancel_or_release(&dev_data->mgr, cli);
}

static int clock_control_nrf_auxpll_get_rate(const struct device *dev, clock_control_subsys_t sys,
					     uint32_t *rate)
{
	const struct clock_control_nrf_auxpll_config *config = dev->config;
	uint8_t ratio;

	ARG_UNUSED(sys);

	ratio = nrf_auxpll_static_ratio_get(config->auxpll);

	*rate = (ratio * config->ref_clk_hz +
		 (config->ref_clk_hz * (uint64_t)config->frequency) /
			 (AUXPLL_AUXPLLCTRL_FREQUENCY_FREQUENCY_MaximumDiv + 1U)) /
		config->out_div;

	return 0;
}

static enum clock_control_status clock_control_nrf_auxpll_get_status(const struct device *dev,
								     clock_control_subsys_t sys)
{
	const struct clock_control_nrf_auxpll_config *config = dev->config;

	ARG_UNUSED(sys);

	if (nrf_auxpll_mode_locked_check(config->auxpll)) {
		return CLOCK_CONTROL_STATUS_ON;
	}

	return CLOCK_CONTROL_STATUS_OFF;
}

static const struct onoff_transitions transitions = {
	.start = onoff_start_auxpll,
	.stop = onoff_stop_auxpll
};

static int clock_control_nrf_auxpll_init(const struct device *dev)
{
	struct dev_data_auxpll *dev_data = dev->data;
	const struct clock_control_nrf_auxpll_config *config = dev->config;
	int rc;

	rc = onoff_manager_init(&dev_data->mgr, &transitions);
	if (rc < 0) {
		return rc;
	}

	nrf_auxpll_ctrl_frequency_set(config->auxpll, config->frequency);

	nrf_auxpll_lock(config->auxpll);
	nrf_auxpll_trim_ctune_set(config->auxpll, sys_read8(config->ficr_ctune));
	nrf_auxpll_config_set(config->auxpll, &config->cfg);
	nrf_auxpll_ctrl_outsel_set(config->auxpll, config->out_div);
	nrf_auxpll_unlock(config->auxpll);

	nrf_auxpll_ctrl_mode_set(config->auxpll, NRF_AUXPLL_CTRL_MODE_LOCKED);

	return 0;
}

static DEVICE_API(nrf_clock_control, drv_api_auxpll) = {
	.std_api = {
		.on = api_nosys_on_off,
		.off = api_nosys_on_off,
		.get_rate = clock_control_nrf_auxpll_get_rate,
		.get_status = clock_control_nrf_auxpll_get_status,
	},
	.request = api_request_auxpll,
	.release = api_release_auxpll,
	.cancel_or_release = api_cancel_or_release_auxpll,
};

#define CLOCK_CONTROL_NRF_AUXPLL_DEFINE(n)                                                         \
	BUILD_ASSERT(                                                                              \
		DT_INST_PROP(n, nordic_frequency) == NRF_AUXPLL_FREQUENCY_DIV_MIN     ||           \
		DT_INST_PROP(n, nordic_frequency) == NRF_AUXPLL_FREQUENCY_AUDIO_44K1  ||           \
		DT_INST_PROP(n, nordic_frequency) == NRF_AUXPLL_FREQUENCY_USB_24M     ||           \
		DT_INST_PROP(n, nordic_frequency) == NRF_AUXPLL_FREQUENCY_AUDIO_48K   ||           \
		DT_INST_PROP(n, nordic_frequency) == NRF_AUXPLL_FREQUENCY_DIV_MAX,                 \
		"Invalid nordic,frequency value in DeviceTree for AUXPLL instance " #n);           \
	BUILD_ASSERT(DT_INST_PROP(n, nordic_out_div) > 0,                                          \
		"nordic,out_div must be greater than 0 for AUXPLL instance " #n);                  \
	static struct dev_data_auxpll data_auxpll##n    = {                                        \
		.dev = DEVICE_DT_INST_GET(n),                                                      \
	};                                                                                         \
	static const struct clock_control_nrf_auxpll_config config##n = {                          \
		.auxpll = (NRF_AUXPLL_Type *)DT_INST_REG_ADDR(n),                                  \
		.ref_clk_hz = DT_PROP(DT_INST_CLOCKS_CTLR(n), clock_frequency),                    \
		.ficr_ctune = DT_REG_ADDR(DT_INST_PHANDLE(n, nordic_ficrs)) +                      \
			      DT_INST_PHA(n, nordic_ficrs, offset),                                \
		.cfg =                                                                             \
			{                                                                          \
				.outdrive = DT_INST_PROP(n, nordic_out_drive),                     \
				.current_tune = DT_INST_PROP(n, nordic_current_tune),              \
				.sdm_off = DT_INST_PROP(n, nordic_sdm_disable),                    \
				.dither_off = DT_INST_PROP(n, nordic_dither_disable),              \
				.range = DT_INST_ENUM_IDX(n, nordic_range),                        \
			},                                                                         \
		.frequency = DT_INST_PROP(n, nordic_frequency),                                    \
		.out_div = DT_INST_PROP(n, nordic_out_div),                                        \
	};                                                                                         \
	                                                                                           \
	DEVICE_DT_INST_DEFINE(n, clock_control_nrf_auxpll_init, NULL, &data_auxpll##n, &config##n, \
			      PRE_KERNEL_1, CONFIG_CLOCK_CONTROL_INIT_PRIORITY,                    \
			      &drv_api_auxpll);

DT_INST_FOREACH_STATUS_OKAY(CLOCK_CONTROL_NRF_AUXPLL_DEFINE)
