/*
 * Copyright (c) 2016 ARM Ltd.
 * Copyright (c) 2019 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT nordic_nrf_temp

#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>
#include <zephyr/logging/log.h>
#include <hal/nrf_temp.h>
#include <zephyr/irq.h>

LOG_MODULE_REGISTER(temp_nrf5, CONFIG_SENSOR_LOG_LEVEL);


/* The nRF5 temperature device returns measurements in 0.25C
 * increments.  Scale to mDegrees C.
 */
#define TEMP_NRF5_TEMP_SCALE (1000000 / 4)

struct temp_nrf5_data {
	struct k_sem device_sync_sem;
	struct k_mutex mutex;
	int32_t sample;
	struct onoff_manager *clk_mgr;
};

static void hfclk_on_callback(struct onoff_manager *mgr,
			      struct onoff_client *cli,
			      uint32_t state,
			      int res)
{
	nrf_temp_task_trigger(NRF_TEMP, NRF_TEMP_TASK_START);
}

static int temp_nrf5_sample_fetch(const struct device *dev,
				  enum sensor_channel chan)
{
	struct temp_nrf5_data *data = dev->data;
	struct onoff_client cli;
	int r;

	/* Error if before sensor initialized */
	if (data->clk_mgr == NULL) {
		return -EAGAIN;
	}

	if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_DIE_TEMP) {
		return -ENOTSUP;
	}

	k_mutex_lock(&data->mutex, K_FOREVER);

	sys_notify_init_callback(&cli.notify, hfclk_on_callback);
	r = onoff_request(data->clk_mgr, &cli);
	__ASSERT_NO_MSG(r >= 0);

	k_sem_take(&data->device_sync_sem, K_FOREVER);

	r = onoff_release(data->clk_mgr);
	__ASSERT_NO_MSG(r >= 0);

	data->sample = nrf_temp_result_get(NRF_TEMP);
	LOG_DBG("sample: %d", data->sample);
	nrf_temp_task_trigger(NRF_TEMP, NRF_TEMP_TASK_STOP);

	k_mutex_unlock(&data->mutex);

	return 0;
}

static int temp_nrf5_channel_get(const struct device *dev,
				 enum sensor_channel chan,
				 struct sensor_value *val)
{
	struct temp_nrf5_data *data = dev->data;
	int32_t uval;


	if (chan != SENSOR_CHAN_DIE_TEMP) {
		return -ENOTSUP;
	}

	uval = data->sample * TEMP_NRF5_TEMP_SCALE;
	val->val1 = uval / 1000000;
	val->val2 = uval % 1000000;

	LOG_DBG("Temperature:%d,%d", val->val1, val->val2);

	return 0;
}

static void temp_nrf5_isr(const void *arg)
{
	const struct device *dev = (const struct device *)arg;
	struct temp_nrf5_data *data = dev->data;

	nrf_temp_event_clear(NRF_TEMP, NRF_TEMP_EVENT_DATARDY);
	k_sem_give(&data->device_sync_sem);
}

static const struct sensor_driver_api temp_nrf5_driver_api = {
	.sample_fetch = temp_nrf5_sample_fetch,
	.channel_get = temp_nrf5_channel_get,
};

static int temp_nrf5_init(const struct device *dev)
{
	struct temp_nrf5_data *data = dev->data;

	/* A null clk_mgr indicates sensor has not been initialized */
	data->clk_mgr =
		z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF);
	__ASSERT_NO_MSG(data->clk_mgr);

	k_sem_init(&data->device_sync_sem, 0, K_SEM_MAX_LIMIT);
	k_mutex_init(&data->mutex);

	IRQ_CONNECT(
		DT_INST_IRQN(0),
		DT_INST_IRQ(0, priority),
		temp_nrf5_isr,
		DEVICE_DT_INST_GET(0),
		0);
	irq_enable(DT_INST_IRQN(0));

	nrf_temp_int_enable(NRF_TEMP, NRF_TEMP_INT_DATARDY_MASK);

	return 0;
}

#define NRF_TEMP_DEFINE(inst)								\
	static struct temp_nrf5_data temp_nrf5_data_##inst;				\
											\
	SENSOR_DEVICE_DT_INST_DEFINE(inst, temp_nrf5_init, NULL,			\
			      &temp_nrf5_data_##inst, NULL, POST_KERNEL,		\
			      CONFIG_SENSOR_INIT_PRIORITY, &temp_nrf5_driver_api);	\

DT_INST_FOREACH_STATUS_OKAY(NRF_TEMP_DEFINE)
