/*
 * Copyright (c) 2016-2020 Nordic Semiconductor ASA
 * Copyright (c) 2016 Vinayak Kariappa Chettimada
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <soc.h>
#include <zephyr/sys/onoff.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>
#include "nrf_clock_calibration.h"
#include <nrfx_clock.h>
#include <zephyr/logging/log.h>
#include <zephyr/shell/shell.h>
#include <zephyr/irq.h>
#include <nrf_erratas.h>

LOG_MODULE_REGISTER(clock_control, CONFIG_CLOCK_CONTROL_LOG_LEVEL);

#define DT_DRV_COMPAT nordic_nrf_clock


#define CTX_ONOFF		BIT(6)
#define CTX_API			BIT(7)
#define CTX_MASK (CTX_ONOFF | CTX_API)

#define STATUS_MASK		0x7
#define GET_STATUS(flags)	(flags & STATUS_MASK)
#define GET_CTX(flags)		(flags & CTX_MASK)

/* Used only by HF clock */
#define HF_USER_BT		BIT(0)
#define HF_USER_GENERIC		BIT(1)

/* Helper logging macros which prepends subsys name to the log. */
#ifdef CONFIG_LOG
#define CLOCK_LOG(lvl, dev, subsys, ...) \
	LOG_##lvl("%s: " GET_ARG_N(1, __VA_ARGS__), \
		get_sub_config(dev, (enum clock_control_nrf_type)subsys)->name \
		COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__),\
				(), (, GET_ARGS_LESS_N(1, __VA_ARGS__))))
#else
#define CLOCK_LOG(...)
#endif

#define ERR(dev, subsys, ...) CLOCK_LOG(ERR, dev, subsys, __VA_ARGS__)
#define WRN(dev, subsys, ...) CLOCK_LOG(WRN, dev, subsys, __VA_ARGS__)
#define INF(dev, subsys, ...) CLOCK_LOG(INF, dev, subsys, __VA_ARGS__)
#define DBG(dev, subsys, ...) CLOCK_LOG(DBG, dev, subsys, __VA_ARGS__)

/* Clock subsys structure */
struct nrf_clock_control_sub_data {
	clock_control_cb_t cb;
	void *user_data;
	uint32_t flags;
};

typedef void (*clk_ctrl_func_t)(void);

/* Clock subsys static configuration */
struct nrf_clock_control_sub_config {
	clk_ctrl_func_t start;		/* Clock start function */
	clk_ctrl_func_t stop;		/* Clock stop function */
#ifdef CONFIG_LOG
	const char *name;
#endif
};

struct nrf_clock_control_data {
	struct onoff_manager mgr[CLOCK_CONTROL_NRF_TYPE_COUNT];
	struct nrf_clock_control_sub_data subsys[CLOCK_CONTROL_NRF_TYPE_COUNT];
};

struct nrf_clock_control_config {
	struct nrf_clock_control_sub_config
					subsys[CLOCK_CONTROL_NRF_TYPE_COUNT];
};

static atomic_t hfclk_users;
static uint64_t hf_start_tstamp;
static uint64_t hf_stop_tstamp;
#if CONFIG_CLOCK_CONTROL_NRF_K32SRC_SYNTH
/* Client to request HFXO to synthesize low frequency clock. */
static struct onoff_client lfsynth_cli;
#endif

static struct nrf_clock_control_sub_data *get_sub_data(const struct device *dev,
						       enum clock_control_nrf_type type)
{
	struct nrf_clock_control_data *data = dev->data;

	return &data->subsys[type];
}

static const struct nrf_clock_control_sub_config *get_sub_config(const struct device *dev,
								 enum clock_control_nrf_type type)
{
	const struct nrf_clock_control_config *config =
						dev->config;

	return &config->subsys[type];
}

static struct onoff_manager *get_onoff_manager(const struct device *dev,
					       enum clock_control_nrf_type type)
{
	struct nrf_clock_control_data *data = dev->data;

	return &data->mgr[type];
}


#define CLOCK_DEVICE DEVICE_DT_GET(DT_NODELABEL(clock))

struct onoff_manager *z_nrf_clock_control_get_onoff(clock_control_subsys_t sys)
{
	return get_onoff_manager(CLOCK_DEVICE,
				(enum clock_control_nrf_type)(size_t)sys);
}

static enum clock_control_status get_status(const struct device *dev,
					    clock_control_subsys_t subsys)
{
	enum clock_control_nrf_type type = (enum clock_control_nrf_type)(size_t)subsys;

	__ASSERT_NO_MSG(type < CLOCK_CONTROL_NRF_TYPE_COUNT);

	return GET_STATUS(get_sub_data(dev, type)->flags);
}

static int set_off_state(uint32_t *flags, uint32_t ctx)
{
	int err = 0;
	unsigned int key = irq_lock();
	uint32_t current_ctx = GET_CTX(*flags);

	if ((current_ctx != 0) && (current_ctx != ctx)) {
		err = -EPERM;
	} else {
		*flags = CLOCK_CONTROL_STATUS_OFF;
	}

	irq_unlock(key);

	return err;
}

static int set_starting_state(uint32_t *flags, uint32_t ctx)
{
	int err = 0;
	unsigned int key = irq_lock();
	uint32_t current_ctx = GET_CTX(*flags);

	if ((*flags & (STATUS_MASK)) == CLOCK_CONTROL_STATUS_OFF) {
		*flags = CLOCK_CONTROL_STATUS_STARTING | ctx;
	} else if (current_ctx != ctx) {
		err = -EPERM;
	} else {
		err = -EALREADY;
	}

	irq_unlock(key);

	return err;
}

static void set_on_state(uint32_t *flags)
{
	unsigned int key = irq_lock();

	*flags = CLOCK_CONTROL_STATUS_ON | GET_CTX(*flags);
	irq_unlock(key);
}

#ifdef CONFIG_CLOCK_CONTROL_NRF_HFINT_CALIBRATION

static void nrf54l_errata_30_workaround(void)
{
	while (FIELD_GET(CLOCK_XO_STAT_STATE_Msk, NRF_CLOCK->XO.STAT) !=
	       CLOCK_XO_STAT_STATE_Running) {
	}
	const uint32_t higher_bits = *((volatile uint32_t *)0x50120820UL) & 0xFFFFFFC0;
	*((volatile uint32_t *)0x50120864UL) = 1 | BIT(31);
	*((volatile uint32_t *)0x50120848UL) = 1;
	uint32_t off_abs = 24;

	while (off_abs >= 24) {
		*((volatile uint32_t *)0x50120844UL) = 1;
		while (((*((volatile uint32_t *)0x50120840UL)) & (1 << 16)) != 0) {
		}
		const uint32_t current_cal = *((volatile uint32_t *)0x50120820UL) & 0x3F;
		const uint32_t cal_result = *((volatile uint32_t *)0x50120840UL) & 0x7FF;
		int32_t off = 1024 - cal_result;

		off_abs = (off < 0) ? -off : off;

		if (off >= 24 && current_cal < 0x3F) {
			*((volatile uint32_t *)0x50120820UL) = higher_bits | (current_cal + 1);
		} else if (off <= -24 && current_cal > 0) {
			*((volatile uint32_t *)0x50120820UL) = higher_bits | (current_cal - 1);
		}
	}

	*((volatile uint32_t *)0x50120848UL) = 0;
	*((volatile uint32_t *)0x50120864UL) = 0;
}

#if CONFIG_CLOCK_CONTROL_NRF_HFINT_CALIBRATION_PERIOD

static struct onoff_client hf_cal_cli;

static void calibration_finished_callback(struct onoff_manager *mgr,
					  struct onoff_client *cli,
					  uint32_t state,
					  int res)
{
	(void)onoff_cancel_or_release(mgr, cli);
}

static void calibration_handler(struct k_timer *timer)
{
	nrf_clock_hfclk_t clk_src;

	bool ret = nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLK, &clk_src);

	if (ret && (clk_src == NRF_CLOCK_HFCLK_HIGH_ACCURACY)) {
		return;
	}

	sys_notify_init_callback(&hf_cal_cli.notify, calibration_finished_callback);
	(void)onoff_request(z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF),
			    &hf_cal_cli);
}

static K_TIMER_DEFINE(calibration_timer, calibration_handler, NULL);

static int calibration_init(void)
{
	k_timer_start(&calibration_timer,
		      K_NO_WAIT,
		      K_MSEC(CONFIG_CLOCK_CONTROL_NRF_HFINT_CALIBRATION_PERIOD));

	return 0;
}

SYS_INIT(calibration_init, APPLICATION, 0);

#endif /* CONFIG_CLOCK_CONTROL_NRF_HFINT_CALIBRATION_PERIOD */
#endif /* CONFIG_CLOCK_CONTROL_NRF_HFINT_CALIBRATION */

static void clkstarted_handle(const struct device *dev,
			      enum clock_control_nrf_type type)
{
#if CONFIG_CLOCK_CONTROL_NRF_HFINT_CALIBRATION
	if (nrf54l_errata_30() && (type == CLOCK_CONTROL_NRF_TYPE_HFCLK)) {
		nrf54l_errata_30_workaround();
	}
#endif
	struct nrf_clock_control_sub_data *sub_data = get_sub_data(dev, type);
	clock_control_cb_t callback = sub_data->cb;
	void *user_data = sub_data->user_data;

	sub_data->cb = NULL;
	set_on_state(&sub_data->flags);
	DBG(dev, type, "Clock started");

	if (callback) {
		callback(dev, (clock_control_subsys_t)type, user_data);
	}
}

static inline void anomaly_132_workaround(void)
{
#if (CONFIG_NRF52_ANOMALY_132_DELAY_US - 0)
	static bool once;

	if (!once) {
		k_busy_wait(CONFIG_NRF52_ANOMALY_132_DELAY_US);
		once = true;
	}
#endif
}

static void lfclk_start(void)
{
	if (IS_ENABLED(CONFIG_NRF52_ANOMALY_132_WORKAROUND)) {
		anomaly_132_workaround();
	}

#if CONFIG_CLOCK_CONTROL_NRF_K32SRC_SYNTH
	sys_notify_init_spinwait(&lfsynth_cli.notify);
	(void)onoff_request(z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF),
			    &lfsynth_cli);
#endif

	nrfx_clock_lfclk_start();
}

static void lfclk_stop(void)
{
	if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_DRIVER_CALIBRATION)) {
		z_nrf_clock_calibration_lfclk_stopped();
	}

	nrfx_clock_lfclk_stop();

#if CONFIG_CLOCK_CONTROL_NRF_K32SRC_SYNTH
	(void)onoff_cancel_or_release(z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF),
				      &lfsynth_cli);
#endif
}

static void hfclk_start(void)
{
	if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_SHELL)) {
		hf_start_tstamp = k_uptime_get();
	}

	nrfx_clock_hfclk_start();
}

static void hfclk_stop(void)
{
	if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_SHELL)) {
		hf_stop_tstamp = k_uptime_get();
	}

	nrfx_clock_hfclk_stop();
}

#if NRF_CLOCK_HAS_HFCLK24M
static void hfclk24m_start(void)
{
	nrfx_clock_start(NRF_CLOCK_DOMAIN_HFCLK24M);
}

static void hfclk24m_stop(void)
{
	nrfx_clock_stop(NRF_CLOCK_DOMAIN_HFCLK24M);
}
#endif

#if NRF_CLOCK_HAS_HFCLK192M
static void hfclk192m_start(void)
{
	nrfx_clock_start(NRF_CLOCK_DOMAIN_HFCLK192M);
}

static void hfclk192m_stop(void)
{
	nrfx_clock_stop(NRF_CLOCK_DOMAIN_HFCLK192M);
}
#endif

#if NRF_CLOCK_HAS_HFCLKAUDIO
static void hfclkaudio_start(void)
{
	nrfx_clock_start(NRF_CLOCK_DOMAIN_HFCLKAUDIO);
}

static void hfclkaudio_stop(void)
{
	nrfx_clock_stop(NRF_CLOCK_DOMAIN_HFCLKAUDIO);
}
#endif

static uint32_t *get_hf_flags(void)
{
	struct nrf_clock_control_data *data = CLOCK_DEVICE->data;

	return &data->subsys[CLOCK_CONTROL_NRF_TYPE_HFCLK].flags;
}

static void generic_hfclk_start(void)
{
	nrf_clock_hfclk_t type;
	bool already_started = false;
	unsigned int key = irq_lock();

	hfclk_users |= HF_USER_GENERIC;
	if (hfclk_users & HF_USER_BT) {
		(void)nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLK, &type);
		if (type == NRF_CLOCK_HFCLK_HIGH_ACCURACY) {
			already_started = true;
			/* Set on state in case clock interrupt comes and we
			 * want to avoid handling that.
			 */
			set_on_state(get_hf_flags());
		}
	}

	irq_unlock(key);

	if (already_started) {
		/* Clock already started by z_nrf_clock_bt_ctlr_hf_request */
		clkstarted_handle(CLOCK_DEVICE,
				  CLOCK_CONTROL_NRF_TYPE_HFCLK);
		return;
	}

	hfclk_start();
}

static void generic_hfclk_stop(void)
{
	/* It's not enough to use only atomic_and() here for synchronization,
	 * as the thread could be preempted right after that function but
	 * before hfclk_stop() is called and the preempting code could request
	 * the HFCLK again. Then, the HFCLK would be stopped inappropriately
	 * and hfclk_user would be left with an incorrect value.
	 */
	unsigned int key = irq_lock();

	hfclk_users &= ~HF_USER_GENERIC;
	/* Skip stopping if BT is still requesting the clock. */
	if (!(hfclk_users & HF_USER_BT)) {
		hfclk_stop();
	}

	irq_unlock(key);
}


void z_nrf_clock_bt_ctlr_hf_request(void)
{
	if (atomic_or(&hfclk_users, HF_USER_BT) & HF_USER_GENERIC) {
		/* generic request already activated clock. */
		return;
	}

	hfclk_start();
}

void z_nrf_clock_bt_ctlr_hf_release(void)
{
	/* It's not enough to use only atomic_and() here for synchronization,
	 * see the explanation in generic_hfclk_stop().
	 */
	unsigned int key = irq_lock();

	hfclk_users &= ~HF_USER_BT;
	/* Skip stopping if generic is still requesting the clock. */
	if (!(hfclk_users & HF_USER_GENERIC)) {
		hfclk_stop();
	}

	irq_unlock(key);
}

#if DT_NODE_EXISTS(DT_NODELABEL(hfxo))
uint32_t z_nrf_clock_bt_ctlr_hf_get_startup_time_us(void)
{
	return DT_PROP(DT_NODELABEL(hfxo), startup_time_us);
}
#endif

static int stop(const struct device *dev, clock_control_subsys_t subsys,
		uint32_t ctx)
{
	enum clock_control_nrf_type type = (enum clock_control_nrf_type)(size_t)subsys;
	struct nrf_clock_control_sub_data *subdata = get_sub_data(dev, type);
	int err;

	__ASSERT_NO_MSG(type < CLOCK_CONTROL_NRF_TYPE_COUNT);

	err = set_off_state(&subdata->flags, ctx);
	if (err < 0) {
		return err;
	}

	get_sub_config(dev, type)->stop();

	return 0;
}

static int api_stop(const struct device *dev, clock_control_subsys_t subsys)
{
	return stop(dev, subsys, CTX_API);
}

static int async_start(const struct device *dev, clock_control_subsys_t subsys,
			clock_control_cb_t cb, void *user_data, uint32_t ctx)
{
	enum clock_control_nrf_type type = (enum clock_control_nrf_type)(size_t)subsys;
	struct nrf_clock_control_sub_data *subdata = get_sub_data(dev, type);
	int err;

	err = set_starting_state(&subdata->flags, ctx);
	if (err < 0) {
		return err;
	}

	subdata->cb = cb;
	subdata->user_data = user_data;

	get_sub_config(dev, type)->start();

	return 0;
}

static int api_start(const struct device *dev, clock_control_subsys_t subsys,
		     clock_control_cb_t cb, void *user_data)
{
	return async_start(dev, subsys, cb, user_data, CTX_API);
}

static void blocking_start_callback(const struct device *dev,
				    clock_control_subsys_t subsys,
				    void *user_data)
{
	struct k_sem *sem = user_data;

	k_sem_give(sem);
}

static int api_blocking_start(const struct device *dev,
			      clock_control_subsys_t subsys)
{
	struct k_sem sem = Z_SEM_INITIALIZER(sem, 0, 1);
	int err;

	if (!IS_ENABLED(CONFIG_MULTITHREADING)) {
		return -ENOTSUP;
	}

	err = api_start(dev, subsys, blocking_start_callback, &sem);
	if (err < 0) {
		return err;
	}

	return k_sem_take(&sem, K_MSEC(500));
}

static clock_control_subsys_t get_subsys(struct onoff_manager *mgr)
{
	struct nrf_clock_control_data *data = CLOCK_DEVICE->data;
	size_t offset = (size_t)(mgr - data->mgr);

	return (clock_control_subsys_t)offset;
}

static void onoff_stop(struct onoff_manager *mgr,
			onoff_notify_fn notify)
{
	int res;

	res = stop(CLOCK_DEVICE, get_subsys(mgr), CTX_ONOFF);
	notify(mgr, res);
}

static void onoff_started_callback(const struct device *dev,
				   clock_control_subsys_t sys,
				   void *user_data)
{
	enum clock_control_nrf_type type = (enum clock_control_nrf_type)(size_t)sys;
	struct onoff_manager *mgr = get_onoff_manager(dev, type);
	onoff_notify_fn notify = user_data;

	notify(mgr, 0);
}

static void onoff_start(struct onoff_manager *mgr,
			onoff_notify_fn notify)
{
	int err;

	err = async_start(CLOCK_DEVICE, get_subsys(mgr),
			  onoff_started_callback, notify, CTX_ONOFF);
	if (err < 0) {
		notify(mgr, err);
	}
}

/** @brief Wait for LF clock availability or stability.
 *
 * If LF clock source is SYNTH or RC then there is no distinction between
 * availability and stability. In case of XTAL source clock, system is initially
 * starting RC and then seamlessly switches to XTAL. Running RC means clock
 * availability and running target source means stability, That is because
 * significant difference in startup time (<1ms vs >200ms).
 *
 * In order to get event/interrupt when RC is ready (allowing CPU sleeping) two
 * stage startup sequence is used. Initially, LF source is set to RC and when
 * LFSTARTED event is handled it is reconfigured to the target source clock.
 * This approach is implemented in nrfx_clock driver and utilized here.
 *
 * @param mode Start mode.
 */
static void lfclk_spinwait(enum nrf_lfclk_start_mode mode)
{
	static const nrf_clock_domain_t d = NRF_CLOCK_DOMAIN_LFCLK;
	static const nrf_clock_lfclk_t target_type =
		/* For sources XTAL, EXT_LOW_SWING, and EXT_FULL_SWING,
		 * NRF_CLOCK_LFCLK_XTAL is returned as the type of running clock.
		 */
		(IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_K32SRC_XTAL) ||
		 IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_K32SRC_EXT_LOW_SWING) ||
		 IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_K32SRC_EXT_FULL_SWING))
		? NRF_CLOCK_LFCLK_XTAL
		: CLOCK_CONTROL_NRF_K32SRC;
	nrf_clock_lfclk_t type;

	if ((mode == CLOCK_CONTROL_NRF_LF_START_AVAILABLE) &&
	    (target_type == NRF_CLOCK_LFCLK_XTAL) &&
	    (nrf_clock_lf_srccopy_get(NRF_CLOCK) == CLOCK_CONTROL_NRF_K32SRC)) {
		/* If target clock source is using XTAL then due to two-stage
		 * clock startup sequence, RC might already be running.
		 * It can be determined by checking current LFCLK source. If it
		 * is set to the target clock source then it means that RC was
		 * started.
		 */
		return;
	}

	bool isr_mode = k_is_in_isr() || k_is_pre_kernel();
	int key = isr_mode ? irq_lock() : 0;

	if (!isr_mode) {
		nrf_clock_int_disable(NRF_CLOCK, NRF_CLOCK_INT_LF_STARTED_MASK);
	}

	while (!(nrfx_clock_is_running(d, (void *)&type)
		 && ((type == target_type)
		     || (mode == CLOCK_CONTROL_NRF_LF_START_AVAILABLE)))) {
		/* Synth source start is almost instant and LFCLKSTARTED may
		 * happen before calling idle. That would lead to deadlock.
		 */
		if (!IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_K32SRC_SYNTH)) {
			if (isr_mode || !IS_ENABLED(CONFIG_MULTITHREADING)) {
				k_cpu_atomic_idle(key);
			} else {
				k_msleep(1);
			}
		}

		/* Clock interrupt is locked, LFCLKSTARTED is handled here. */
		if ((target_type ==  NRF_CLOCK_LFCLK_XTAL)
		    && (nrf_clock_lf_src_get(NRF_CLOCK) == NRF_CLOCK_LFCLK_RC)
		    && nrf_clock_event_check(NRF_CLOCK,
					     NRF_CLOCK_EVENT_LFCLKSTARTED)) {
			nrf_clock_event_clear(NRF_CLOCK,
					      NRF_CLOCK_EVENT_LFCLKSTARTED);
			nrf_clock_lf_src_set(NRF_CLOCK,
					     CLOCK_CONTROL_NRF_K32SRC);

			/* Clear pending interrupt, otherwise new clock event
			 * would not wake up from idle.
			 */
			NVIC_ClearPendingIRQ(DT_INST_IRQN(0));
			nrf_clock_task_trigger(NRF_CLOCK,
					       NRF_CLOCK_TASK_LFCLKSTART);
		}
	}

	if (isr_mode) {
		irq_unlock(key);
	} else {
		nrf_clock_int_enable(NRF_CLOCK, NRF_CLOCK_INT_LF_STARTED_MASK);
	}
}

void z_nrf_clock_control_lf_on(enum nrf_lfclk_start_mode start_mode)
{
	static atomic_t on;
	static struct onoff_client cli;

	if (atomic_set(&on, 1) == 0) {
		int err;
		struct onoff_manager *mgr =
				get_onoff_manager(CLOCK_DEVICE,
						  CLOCK_CONTROL_NRF_TYPE_LFCLK);

		sys_notify_init_spinwait(&cli.notify);
		err = onoff_request(mgr, &cli);
		__ASSERT_NO_MSG(err >= 0);
	}

	/* In case of simulated board leave immediately. */
	if (IS_ENABLED(CONFIG_SOC_SERIES_BSIM_NRFXX)) {
		return;
	}

	switch (start_mode) {
	case CLOCK_CONTROL_NRF_LF_START_AVAILABLE:
	case CLOCK_CONTROL_NRF_LF_START_STABLE:
		lfclk_spinwait(start_mode);
		break;

	case CLOCK_CONTROL_NRF_LF_START_NOWAIT:
		break;

	default:
		__ASSERT_NO_MSG(false);
	}
}

static void hfclkstarted_handle(const struct device *dev)
{
	struct nrf_clock_control_sub_data *data =
			get_sub_data(dev, CLOCK_CONTROL_NRF_TYPE_HFCLK);

	if (GET_STATUS(data->flags) == CLOCK_CONTROL_STATUS_STARTING) {
		/* Handler is called only if state is set. BT specific API
		 * does not set this state and does not require handler to
		 * be called.
		 */
		clkstarted_handle(dev, CLOCK_CONTROL_NRF_TYPE_HFCLK);
	}
}

static void clock_event_handler(nrfx_clock_evt_type_t event)
{
	const struct device *dev = CLOCK_DEVICE;

	switch (event) {
#if NRF_CLOCK_HAS_XO_TUNE
	case NRFX_CLOCK_EVT_XO_TUNED:
		hfclkstarted_handle(dev);
		break;
	case NRFX_CLOCK_EVT_XO_TUNE_ERROR:
	case NRFX_CLOCK_EVT_XO_TUNE_FAILED:
		/* No processing needed. */
		break;
	case NRFX_CLOCK_EVT_HFCLK_STARTED:
		/* HFCLK is stable after XOTUNED event.
		 * HFCLK_STARTED means only that clock has been started.
		 */
		break;
#else
	/* HFCLK started should be used only if tune operation is done implicitly. */
	case NRFX_CLOCK_EVT_HFCLK_STARTED:
		hfclkstarted_handle(dev);
		break;
#endif
#if NRF_CLOCK_HAS_HFCLK24M
	case NRFX_CLOCK_EVT_HFCLK24M_STARTED:
		clkstarted_handle(dev, CLOCK_CONTROL_NRF_TYPE_HFCLK24M);
		break;
#endif
#if NRF_CLOCK_HAS_HFCLK192M
	case NRFX_CLOCK_EVT_HFCLK192M_STARTED:
		clkstarted_handle(dev, CLOCK_CONTROL_NRF_TYPE_HFCLK192M);
		break;
#endif
#if NRF_CLOCK_HAS_HFCLKAUDIO
	case NRFX_CLOCK_EVT_HFCLKAUDIO_STARTED:
		clkstarted_handle(dev, CLOCK_CONTROL_NRF_TYPE_HFCLKAUDIO);
		break;
#endif
	case NRFX_CLOCK_EVT_LFCLK_STARTED:
		if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_DRIVER_CALIBRATION)) {
			z_nrf_clock_calibration_lfclk_started();
		}
		clkstarted_handle(dev, CLOCK_CONTROL_NRF_TYPE_LFCLK);
		break;
#if NRF_CLOCK_HAS_CALIBRATION || NRF_LFRC_HAS_CALIBRATION
	case NRFX_CLOCK_EVT_CAL_DONE:
		if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_DRIVER_CALIBRATION)) {
			z_nrf_clock_calibration_done_handler();
		} else {
			/* Should not happen when calibration is disabled. */
			__ASSERT_NO_MSG(false);
		}
		break;
#endif
#if NRF_CLOCK_HAS_PLL
	case NRFX_CLOCK_EVT_PLL_STARTED:
		/* No processing needed. */
		break;
#endif
	default:
		__ASSERT_NO_MSG(0);
		break;
	}
}

static void hfclkaudio_init(void)
{
#if DT_NODE_HAS_PROP(DT_NODELABEL(clock), hfclkaudio_frequency)
	const uint32_t frequency =
		DT_PROP(DT_NODELABEL(clock), hfclkaudio_frequency);
	/* As specified in the nRF5340 PS:
	 *
	 * FREQ_VALUE = 2^16 * ((12 * f_out / 32M) - 4)
	 */
	const uint32_t freq_value =
		(uint32_t)((384ULL * frequency) / 15625) - 262144;

#if NRF_CLOCK_HAS_HFCLKAUDIO
	nrf_clock_hfclkaudio_config_set(NRF_CLOCK, freq_value);
#else
#error "hfclkaudio-frequency specified but HFCLKAUDIO clock is not present."
#endif /* NRF_CLOCK_HAS_HFCLKAUDIO */
#endif
}

static int clk_init(const struct device *dev)
{
	nrfx_err_t nrfx_err;
	int err;
	static const struct onoff_transitions transitions = {
		.start = onoff_start,
		.stop = onoff_stop
	};

#if NRF_LFRC_HAS_CALIBRATION
	IRQ_CONNECT(LFRC_IRQn, DT_INST_IRQ(0, priority), nrfx_isr, nrfx_power_clock_irq_handler, 0);
#endif

	IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority),
		    nrfx_isr, nrfx_power_clock_irq_handler, 0);

	nrfx_err = nrfx_clock_init(clock_event_handler);
	if (nrfx_err != NRFX_SUCCESS) {
		return -EIO;
	}

	hfclkaudio_init();

	if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_DRIVER_CALIBRATION)) {
		struct nrf_clock_control_data *data = dev->data;

		z_nrf_clock_calibration_init(data->mgr);
	}

	nrfx_clock_enable();

	for (enum clock_control_nrf_type i = 0;
		i < CLOCK_CONTROL_NRF_TYPE_COUNT; i++) {
		struct nrf_clock_control_sub_data *subdata =
						get_sub_data(dev, i);

		err = onoff_manager_init(get_onoff_manager(dev, i),
					 &transitions);
		if (err < 0) {
			return err;
		}

		subdata->flags = CLOCK_CONTROL_STATUS_OFF;
	}

	return 0;
}

static DEVICE_API(clock_control, clock_control_api) = {
	.on = api_blocking_start,
	.off = api_stop,
	.async_on = api_start,
	.get_status = get_status,
};

static struct nrf_clock_control_data data;

static const struct nrf_clock_control_config config = {
	.subsys = {
		[CLOCK_CONTROL_NRF_TYPE_HFCLK] = {
			.start = generic_hfclk_start,
			.stop = generic_hfclk_stop,
			IF_ENABLED(CONFIG_LOG, (.name = "hfclk",))
		},
		[CLOCK_CONTROL_NRF_TYPE_LFCLK] = {
			.start = lfclk_start,
			.stop = lfclk_stop,
			IF_ENABLED(CONFIG_LOG, (.name = "lfclk",))
		},
#if NRF_CLOCK_HAS_HFCLK24M
		[CLOCK_CONTROL_NRF_TYPE_HFCLK24M] = {
			.start = hfclk24m_start,
			.stop = hfclk24m_stop,
			IF_ENABLED(CONFIG_LOG, (.name = "hfclk24m",))
		},
#endif
#if NRF_CLOCK_HAS_HFCLK192M
		[CLOCK_CONTROL_NRF_TYPE_HFCLK192M] = {
			.start = hfclk192m_start,
			.stop = hfclk192m_stop,
			IF_ENABLED(CONFIG_LOG, (.name = "hfclk192m",))
		},
#endif
#if NRF_CLOCK_HAS_HFCLKAUDIO
		[CLOCK_CONTROL_NRF_TYPE_HFCLKAUDIO] = {
			.start = hfclkaudio_start,
			.stop = hfclkaudio_stop,
			IF_ENABLED(CONFIG_LOG, (.name = "hfclkaudio",))
		},
#endif
	}
};

DEVICE_DT_DEFINE(DT_NODELABEL(clock), clk_init, NULL,
		 &data, &config,
		 PRE_KERNEL_1, CONFIG_CLOCK_CONTROL_INIT_PRIORITY,
		 &clock_control_api);

#if defined(CONFIG_SHELL)

static int cmd_status(const struct shell *sh, size_t argc, char **argv)
{
	nrf_clock_hfclk_t hfclk_src;
	bool hf_status;
	bool lf_status = nrfx_clock_is_running(NRF_CLOCK_DOMAIN_LFCLK, NULL);
	struct onoff_manager *hf_mgr =
				get_onoff_manager(CLOCK_DEVICE,
						  CLOCK_CONTROL_NRF_TYPE_HFCLK);
	struct onoff_manager *lf_mgr =
				get_onoff_manager(CLOCK_DEVICE,
						  CLOCK_CONTROL_NRF_TYPE_LFCLK);
	uint32_t abs_start, abs_stop;
	unsigned int key = irq_lock();
	uint64_t now = k_uptime_get();

	(void)nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLK, (void *)&hfclk_src);
	hf_status = (hfclk_src == NRF_CLOCK_HFCLK_HIGH_ACCURACY);

	abs_start = hf_start_tstamp;
	abs_stop = hf_stop_tstamp;
	irq_unlock(key);

	shell_print(sh, "HF clock:");
	shell_print(sh, "\t- %srunning (users: %u)",
			hf_status ? "" : "not ", hf_mgr->refs);
	shell_print(sh, "\t- last start: %u ms (%u ms ago)",
			(uint32_t)abs_start, (uint32_t)(now - abs_start));
	shell_print(sh, "\t- last stop: %u ms (%u ms ago)",
			(uint32_t)abs_stop, (uint32_t)(now - abs_stop));
	shell_print(sh, "LF clock:");
	shell_print(sh, "\t- %srunning (users: %u)",
			lf_status ? "" : "not ", lf_mgr->refs);

	return 0;
}

SHELL_STATIC_SUBCMD_SET_CREATE(subcmds,
	SHELL_CMD_ARG(status, NULL, "Status", cmd_status, 1, 0),
	SHELL_SUBCMD_SET_END
);

SHELL_COND_CMD_REGISTER(CONFIG_CLOCK_CONTROL_NRF_SHELL,
			nrf_clock_control, &subcmds,
			"Clock control commands",
			cmd_status);

#endif /* defined(CONFIG_SHELL) */
