/*
 * Copyright (c) 2020 TDK Invensense
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT invensense_icp20100

#include <zephyr/drivers/spi.h>
#include <zephyr/init.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>

#include "icp20100.h"
#include "icp20100_reg.h"
#include "icp20100_setup.h"
#include "icp20100_spi.h"

LOG_MODULE_REGISTER(ICP20100, CONFIG_SENSOR_LOG_LEVEL);

/* see "Temperature Measurement" section from register map description */
static inline void icp20100_convert_temp(struct sensor_value *val,
					 int32_t raw_val)
{
	val->val1 = (((int64_t)raw_val * TEMP_SCALE) / TEMP_DIVIDER) + TEMP_OFFSET;
	val->val2 = ((((int64_t)raw_val * TEMP_SCALE) % TEMP_DIVIDER) * 1000000) / TEMP_DIVIDER;

	if (val->val2 < 0) {
		val->val1--;
		val->val2 += 1000000;
	} else if (val->val2 >= 1000000) {
		val->val1++;
		val->val2 -= 1000000;
	}
}

/* see "Pressure Measurement" section from register map description */
static inline void icp20100_convert_pressure(struct sensor_value *val,
					 int32_t raw_val)
{
	val->val1 = (((int64_t)raw_val * PRESSURE_SCALE) / PRESSURE_DIVIDER) + PRESSURE_OFFSET;
	val->val2 = ((((int64_t)raw_val * PRESSURE_SCALE) % PRESSURE_DIVIDER) * 1000000) / PRESSURE_OFFSET;

	if (val->val2 < 0) {
		val->val1--;
		val->val2 += 1000000;
	} else if (val->val2 >= 1000000) {
		val->val1++;
		val->val2 -= 1000000;
	}
}

static int icp20100_channel_get(const struct device *dev,
				enum sensor_channel chan,
				struct sensor_value *val)
{
	const struct icp20100_data *drv_data = dev->data;

	switch (chan) {
	case SENSOR_CHAN_AMBIENT_TEMP:
		icp20100_convert_temp(val, drv_data->temperature);
		break;
	case SENSOR_CHAN_PRESS:
		icp20100_convert_pressure(val, drv_data->pressure);
		break;
	default:
		return -ENOTSUP;
	}

	return 0;
}

static int icp20100_sample_fetch(const struct device *dev,
				 enum sensor_channel chan)
{
	int result = 0;
	struct icp20100_data *drv_data = dev->data;
	const struct icp20100_config *cfg = dev->config;

	/* Read FIFO_FILL(0xC4) */
	result = inv20100_spi_read(&cfg->spi, REG_FIFO_FILL, drv_data->fifo_data, 1);

	if ((drv_data->fifo_data[0] & BIT_FIFO_FILL_LVL_EMPT) == 0) {
		result = inv20100_spi_read(&cfg->spi, REG_PRESS_DATA_0, drv_data->fifo_data,
				      6);
		drv_data->pressure = (drv_data->fifo_data[2] << 16) | (drv_data->fifo_data[1] << 8) | drv_data->fifo_data[0];
		drv_data->temperature = (drv_data->fifo_data[5] << 16) | (drv_data->fifo_data[4] << 8) | drv_data->fifo_data[3];
	}

	return 0;
}

static int icp20100_attr_set(const struct device *dev,
			     enum sensor_channel chan,
			     enum sensor_attribute attr,
			     const struct sensor_value *val)
{
	__ASSERT_NO_MSG(val != NULL);

	switch (chan) {
	default:
		LOG_ERR("Not support");
		return -EINVAL;
	}

	return 0;
}

static int icp20100_attr_get(const struct device *dev,
			     enum sensor_channel chan,
			     enum sensor_attribute attr,
			     struct sensor_value *val)
{
	__ASSERT_NO_MSG(val != NULL);

	switch (chan) {
	default:
		LOG_ERR("Not support");
		return -EINVAL;
	}

	return 0;
}

static int icp20100_data_init(struct icp20100_data *data,
			      const struct icp20100_config *cfg)
{
	data->temperature = 0;
	data->pressure = 0;
	data->operation_mode = cfg->operation_mode;

	data->sensor_started = false;

	return 0;
}


static int icp20100_init(const struct device *dev)
{
	struct icp20100_data *drv_data = dev->data;
	const struct icp20100_config *cfg = dev->config;

	if (!spi_is_ready_dt(&cfg->spi)) {
		LOG_ERR("SPI bus is not ready");
		return -ENODEV;
	}

	icp20100_data_init(drv_data, cfg);
	icp20100_sensor_init(dev);

#ifdef CONFIG_ICP20100_TRIGGER
	if (icp20100_init_interrupt(dev) < 0) {
		LOG_ERR("Failed to initialize interrupts.");
		return -EIO;
	}
#endif

	LOG_DBG("Initialize interrupt done");

	return 0;
}

static const struct sensor_driver_api icp20100_driver_api = {
#ifdef CONFIG_ICP20100_TRIGGER
	.trigger_set = icp20100_trigger_set,
#endif
	.sample_fetch = icp20100_sample_fetch,
	.channel_get = icp20100_channel_get,
	.attr_set = icp20100_attr_set,
	.attr_get = icp20100_attr_get,
};

#define ICP20100_DEFINE_CONFIG(index)					\
	static const struct icp20100_config icp20100_cfg_##index = {	\
		.spi = SPI_DT_SPEC_INST_GET(index,			\
					    SPI_OP_MODE_MASTER |	\
					    SPI_MODE_CPOL |		\
					    SPI_MODE_CPHA |		\
					    SPI_WORD_SET(8) |		\
					    SPI_TRANSFER_MSB,		\
					    0U),			\
		.gpio_int = GPIO_DT_SPEC_INST_GET(index, int_gpios),    \
		.operation_mode = DT_INST_PROP(index, operation_mode),		\
	}

#define ICP20100_INIT(index)						\
	ICP20100_DEFINE_CONFIG(index);					\
	static struct icp20100_data icp20100_driver_##index;		\
	SENSOR_DEVICE_DT_INST_DEFINE(index, icp20100_init,		\
			    NULL,					\
			    &icp20100_driver_##index,			\
			    &icp20100_cfg_##index, POST_KERNEL,		\
			    CONFIG_SENSOR_INIT_PRIORITY,		\
			    &icp20100_driver_api);

DT_INST_FOREACH_STATUS_OKAY(ICP20100_INIT)
