/*
 * Copyright (c) 2024 Linumiz
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
#include <zephyr/kernel.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/drivers/adc/ads131m02.h>

#define ADC_CONTEXT_USES_KERNEL_TIMER
#include "adc_context.h"

LOG_MODULE_REGISTER(ads131m02, CONFIG_ADC_LOG_LEVEL);

#define ADS131M02_DEVICE_ID		0x22

/* Device settings Registers */
#define ADS131M02_ID_REG		0x00
#define ADS131M02_STATUS_REG		0x01

/* Global settings Registers */
#define ADS131M02_MODE_REG		0x02
#define ADS131M02_CLOCK_REG		0x03
#define ADS131M02_GAIN_REG		0x04
#define ADS131M02_CFG_REG		0x06
#define ADS131M02_THRESH_MSB_REG	0x07
#define ADS131M02_THRESH_LSB_REG	0x08

/* Channel 0 settings Registers */
#define ADS131M02_CH0_CFG_REG		0x09
#define ADS131M02_CH0_OCAL_MSB_REG	0x0A
#define ADS131M02_CH0_OCAL_LSB_REG	0x0B
#define ADS131M02_CH0_GCAL_MSB_REG	0x0C
#define ADS131M02_CH0_GCAL_LSB_REG	0x0D

/* Channel 1 settings Registers */
#define ADS131M02_CH1_CFG_REG		0x0E
#define ADS131M02_CH1_OCAL_MSB_REG	0x0F
#define ADS131M02_CH1_OCAL_LSB_REG	0x10
#define ADS131M02_CH1_GCAL_MSB_REG	0x11
#define ADS131M02_CH1_GCAL_LSB_REG	0x12

/* Register Map CRC Registers */
#define ADS131M02_REGMAP_CRC_REG	0x3E

#define ADC_CHANNEL_0			0
#define ADC_CHANNEL_1			1
#define ADS131M02_REF_INTERNAL		1200
#define ADS131M02_RESOLUTION		24

/* ADS131M02 cmds */
#define ADS131M02_NULL_CMD		0x0000
#define ADS131M02_RESET_CMD		0x0011
#define ADS131M02_STANDBY_CMD		0x0022
#define ADS131M02_WAKEUP_CMD		0x0033
#define ADS131M02_LOCK_CMD		0x0555
#define ADS131M02_UNLOCK_CMD		0x0655
#define ADS131M02_RREG_CMD		0xA000
#define ADS131M02_WREG_CMD		0x6000

#define ADS131M02_RESET_RSP		0xFF22

#define ADS131M02_GAIN0_MASK		GENMASK(2, 0)
#define ADS131M02_GAIN1_MASK		GENMASK(6, 4)
#define ADS131M02_CHANNEL0_ENABLE	BIT(8)
#define ADS131M02_CHANNEL1_ENABLE	BIT(9)
#define ADS131M02_DRDY_CH0_MASK		BIT(0)
#define ADS131M02_DRDY_CH1_MASK		BIT(1)
#define ADS131M02_OSR_256_MASK		BIT(2)
#define ADS131M02_OSR_512_MASK		BIT(3)
#define ADS131M02_OSR_1024_MASK		BIT(3) | BIT(2)
#define ADS131M02_OSR_2048_MASK		BIT(4)
#define ADS131M02_OSR_4096_MASK		BIT(4) | BIT(2)
#define ADS131M02_OSR_8192_MASK		BIT(4) | BIT(3)
#define ADS131M02_OSR_16384_MASK	BIT(4) | BIT(3) | BIT(2)
#define ADS131M02_GC_MODE_MASK		BIT(8)
#define ADS131M02_GC_DELAY_MASK		GENMASK(12, 9)
#define ADS131M02_PWR_HR		BIT(1) | BIT(0)
#define ADS131M02_PWR_LP		BIT(0)

#define ADS131M02_DISABLE_ADC		0x000E
#define ADS131M02_RESET_DELAY		100

#define ADS131M02_GAIN_1		0
#define ADS131M02_GAIN_2		1
#define ADS131M02_GAIN_4		2
#define ADS131M02_GAIN_8		3
#define ADS131M02_GAIN_16		4
#define ADS131M02_GAIN_32		5
#define ADS131M02_GAIN_64		6
#define ADS131M02_GAIN_128		7

#define ADS131M02_GET_GAIN(channel_id, gain)	\
		FIELD_PREP(channel_id == 0 ? ADS131M02_GAIN0_MASK : \
			   ADS131M02_GAIN1_MASK, gain)

enum ads131m02_data_rate {
	/* SPS */
	ADS131M02_DR_250,
	ADS131M02_DR_500,
	ADS131M02_DR_1k,
	ADS131M02_DR_2k,
	ADS131M02_DR_4k,
	ADS131M02_DR_8k,
	ADS131M02_DR_16k,
	ADS131M02_DR_32k,
};

struct ads131m02_config {
	const struct spi_dt_spec spi;
	const struct gpio_dt_spec gpio_drdy;
};

struct ads131m02_data {
	struct adc_context ctx;
	struct k_sem acq_sem;
	struct k_sem drdy_sem;
	struct gpio_callback callback_drdy;
	int32_t *buffer;
	int32_t *buffer_ptr;
};

static inline int ads131m02_transceive(const struct device *dev,
				       uint8_t *send_buf, size_t send_buf_len,
				       uint8_t *recv_buf, size_t recv_buf_len)
{
	int ret;
	const struct ads131m02_config *cfg = dev->config;

	struct spi_buf tx_buf = {
		.buf = send_buf,
		.len = send_buf_len,
	};
	const struct spi_buf_set tx = {
		.buffers = &tx_buf,
		.count = 1
	};

	struct spi_buf rx_buf = {
		.buf = recv_buf,
		.len = recv_buf_len,
	};
	const struct spi_buf_set rx = {
		.buffers = &rx_buf,
		.count = 1,
	};

	ret = spi_transceive_dt(&cfg->spi, &tx, NULL);
	if (ret != 0) {
		return ret;
	}

	return spi_read_dt(&cfg->spi, &rx);
}

static int ads131m02_reg_read(const struct device *dev, uint16_t addr,
			      uint8_t *read_buf, size_t read_buf_len)
{
	uint16_t temp;
	uint8_t tx_buf[3] = {0};

	temp = (uint16_t)(ADS131M02_RREG_CMD | (addr << 7));
	sys_put_be16(temp, tx_buf);

	return ads131m02_transceive(dev, tx_buf, sizeof(tx_buf),
				    read_buf, read_buf_len);
}

static int ads131m02_reg_write(const struct device *dev, uint16_t addr,
			       uint16_t write_data)
{
	uint16_t temp;
	uint8_t tx_buf[6] = {0};
	uint8_t rx_buf[3] = {0};

	temp = (uint16_t)(ADS131M02_WREG_CMD | (addr << 7));
	sys_put_be16(temp, tx_buf);
	sys_put_be16(write_data, &tx_buf[3]);

	return ads131m02_transceive(dev, tx_buf, sizeof(tx_buf),
				    rx_buf, sizeof(rx_buf));

}

static inline int ads131m02_configure_gain(const struct device *dev,
					   const struct adc_channel_cfg *channel_cfg)
{
	uint16_t gain_cfg;

	switch (channel_cfg->gain) {
	case ADC_GAIN_1:
		gain_cfg = ADS131M02_GET_GAIN(channel_cfg->channel_id,
					      ADS131M02_GAIN_1);
		break;
	case ADC_GAIN_2:
		gain_cfg = ADS131M02_GET_GAIN(channel_cfg->channel_id,
					      ADS131M02_GAIN_2);
		break;
	case ADC_GAIN_4:
		gain_cfg = ADS131M02_GET_GAIN(channel_cfg->channel_id,
					      ADS131M02_GAIN_4);
		break;
	case ADC_GAIN_8:
		gain_cfg = ADS131M02_GET_GAIN(channel_cfg->channel_id,
					      ADS131M02_GAIN_8);
		break;
	case ADC_GAIN_16:
		gain_cfg = ADS131M02_GET_GAIN(channel_cfg->channel_id,
					      ADS131M02_GAIN_16);
		break;
	case ADC_GAIN_32:
		gain_cfg = ADS131M02_GET_GAIN(channel_cfg->channel_id,
					      ADS131M02_GAIN_32);
		break;
	case ADC_GAIN_64:
		gain_cfg = ADS131M02_GET_GAIN(channel_cfg->channel_id,
					      ADS131M02_GAIN_64);
		break;
	case ADC_GAIN_128:
		gain_cfg = ADS131M02_GET_GAIN(channel_cfg->channel_id,
					      ADS131M02_GAIN_128);
		break;
	default:
		return -EINVAL;
	}

	return ads131m02_reg_write(dev, ADS131M02_GAIN_REG, gain_cfg);
}

static inline int ads131m02_acquistion_time(uint16_t acq_time, uint16_t *enable)
{
	uint16_t acq_value = ADC_ACQ_TIME_VALUE(acq_time);

	if (acq_time == ADC_ACQ_TIME_DEFAULT) {
		*enable |=  ADS131M02_OSR_1024_MASK;
		return 0;
	}

	if (ADC_ACQ_TIME_UNIT(acq_time) != ADC_ACQ_TIME_TICKS) {
		return -EINVAL;
	}

	if (acq_time == ADC_ACQ_TIME_MAX) {
		*enable |= ADS131M02_OSR_16384_MASK;
		return 0;
	}

	switch (acq_value) {
	case ADS131M02_DR_250:
		*enable |= ADS131M02_OSR_16384_MASK;
		break;
	case ADS131M02_DR_500:
		*enable |= ADS131M02_OSR_8192_MASK;
		break;
	case ADS131M02_DR_1k:
		*enable |= ADS131M02_OSR_4096_MASK;
		break;
	case ADS131M02_DR_2k:
		*enable |= ADS131M02_OSR_2048_MASK;
		break;
	case ADS131M02_DR_4k:
		*enable |= ADS131M02_OSR_1024_MASK;
		break;
	case ADS131M02_DR_8k:
		*enable |= ADS131M02_OSR_512_MASK;
		break;
	case ADS131M02_DR_16k:
		*enable |= ADS131M02_OSR_256_MASK;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int ads131m02_setup(const struct device *dev,
			   const struct adc_channel_cfg *channel_cfg)
{
	int ret;
	uint16_t enable;
	uint8_t read_data[3] = {0};

	ret  = ads131m02_reg_read(dev, ADS131M02_CLOCK_REG, read_data,
				  sizeof(read_data));
	if (ret != 0) {
		return ret;
	}

	enable = sys_get_be16(read_data);
	switch (channel_cfg->channel_id) {
	case ADC_CHANNEL_0:
		enable |= ADS131M02_CHANNEL0_ENABLE;
		break;
	case ADC_CHANNEL_1:
		enable |= ADS131M02_CHANNEL1_ENABLE;
		break;
	default:
		return -EINVAL;
	}

	enable &= ~(ADS131M02_OSR_16384_MASK);
	ret = ads131m02_acquistion_time(channel_cfg->acquisition_time, &enable);
	if (ret < 0) {
		return ret;
	}

	return ads131m02_reg_write(dev, ADS131M02_CLOCK_REG, enable);
}

static int ads131m02_channel_setup(const struct device *dev,
				   const struct adc_channel_cfg *channel_cfg)
{
	int ret;

	if (channel_cfg->channel_id != 0 && channel_cfg->channel_id != 1) {
		return -EINVAL;
	}

	if (channel_cfg->reference != ADC_REF_INTERNAL) {
		LOG_DBG("Unsupported Reference Voltage");
		return -ENOTSUP;
	}

	if (!channel_cfg->differential) {
		return -EINVAL;
	}

	ret = ads131m02_configure_gain(dev, channel_cfg);
	if (ret != 0) {
		return ret;
	}

	ret = ads131m02_setup(dev, channel_cfg);
	if (ret != 0) {
		return ret;
	}

	return 0;
}

static int ads131m02_validate_buffer_size(const struct adc_sequence *sequence)
{
	size_t needed = sizeof(int32_t);

	if (sequence->options) {
		needed *= (1 + sequence->options->extra_samplings);
	}

	if (sequence->buffer_size < needed) {
		return -ENOMEM;
	}

	return 0;
}

static int ads131m02_validate_sequence(const struct adc_sequence *sequence)
{
	if (sequence->resolution != ADS131M02_RESOLUTION) {
		return -EINVAL;
	}

	if (sequence->channels != BIT(0) && sequence->channels != BIT(1)) {
		LOG_ERR("invalid channel");
		return -EINVAL;
	}

	if (sequence->oversampling) {
		return -EINVAL;
	}

	return ads131m02_validate_buffer_size(sequence);
}

static void adc_context_update_buffer_pointer(struct adc_context *ctx,
					      bool repeat_sampling)
{
	struct ads131m02_data *data = CONTAINER_OF(ctx, struct ads131m02_data, ctx);

	if (repeat_sampling) {
		data->buffer = data->buffer_ptr;
	}
}

static void adc_context_start_sampling(struct adc_context *ctx)
{
	struct ads131m02_data *data = CONTAINER_OF(ctx, struct ads131m02_data, ctx);

	data->buffer_ptr = data->buffer;
	k_sem_give(&data->acq_sem);
}

static int ads131m02_adc_start_read(const struct device *dev,
				    const struct adc_sequence *sequence,
				    bool wait)
{
	int ret;
	struct ads131m02_data *data = dev->data;

	ret = ads131m02_validate_sequence(sequence);
	if (ret != 0) {
		LOG_ERR("sequence validation failed");
		return ret;
	}

	data->buffer = sequence->buffer;
	adc_context_start_read(&data->ctx, sequence);
	if (wait) {
		ret = adc_context_wait_for_completion(&data->ctx);
	}

	return ret;
}

static int ads131m02_wait_drdy(const struct device *dev)
{
	struct ads131m02_data *data = dev->data;

	return k_sem_take(&data->drdy_sem,
			  ADC_CONTEXT_WAIT_FOR_COMPLETION_TIMEOUT);
}

static int ads131m02_read_sample(const struct device *dev,
				 uint32_t channels, uint32_t *buffer)
{
	int ret;
	uint16_t int_status;
	uint8_t tx_buf[4] = {0};
	uint8_t rx_buf[12] = {0};

	ret = ads131m02_transceive(dev, tx_buf, sizeof(tx_buf),
				   rx_buf, sizeof(rx_buf));
	if (ret != 0) {
		return ret;
	}

	int_status = sys_get_be16(&rx_buf[0]);
	if ((int_status & ADS131M02_DRDY_CH0_MASK) && (channels & BIT(0))) {
		*buffer = sys_get_be24(&rx_buf[3]);
	} else if ((int_status & ADS131M02_DRDY_CH1_MASK) &&
		   (channels & BIT(1))) {
		*buffer = sys_get_be24(&rx_buf[6]);
	} else {
		LOG_INF("No ADC Data Available");
	}

	return 0;
}

static int ads131m02_perform_read(const struct device *dev,
				  const struct adc_sequence *sequence)
{
	int ret;
	struct ads131m02_data *data = dev->data;

	k_sem_take(&data->acq_sem, K_FOREVER);
	k_sem_reset(&data->drdy_sem);

	ret = ads131m02_wait_drdy(dev);
	if (ret != 0) {
		goto error;
	}

	ret = ads131m02_read_sample(dev, sequence->channels, data->buffer);
	if (ret != 0) {
		goto error;
	}

	data->buffer++;
	adc_context_on_sampling_done(&data->ctx, dev);

	return 0;
error:
	adc_context_complete(&data->ctx, ret);
	return ret;
}

static int ads131m02_read(const struct device *dev,
			  const struct adc_sequence *seq)
{
	int ret;
	struct ads131m02_data *data =  dev->data;

	adc_context_lock(&data->ctx, false, NULL);
	ret = ads131m02_adc_start_read(dev, seq, false);

	while (ret == 0 && k_sem_take(&data->ctx.sync, K_NO_WAIT) != 0) {
		ret = ads131m02_perform_read(dev, seq);
	}

	adc_context_release(&data->ctx, ret);

	return ret;
}

static void ads131m02_data_ready_handler(const struct device *dev,
					 struct gpio_callback *gpio_cb,
					 uint32_t pins)
{
	ARG_UNUSED(dev);
	ARG_UNUSED(pins);

	struct ads131m02_data *data = CONTAINER_OF(gpio_cb,
				      struct ads131m02_data, callback_drdy);

	k_sem_give(&data->drdy_sem);
}

static int ads131m02_configure_gpio(const struct device *dev)
{
	int ret;
	const struct ads131m02_config *cfg = dev->config;
	struct ads131m02_data *data = dev->data;

	ret = gpio_pin_configure_dt(&cfg->gpio_drdy, GPIO_INPUT);
	if (ret != 0) {
		return ret;
	}

	ret = gpio_pin_interrupt_configure_dt(&cfg->gpio_drdy,
					      GPIO_INT_EDGE_TO_ACTIVE);
	if (ret != 0) {
		return ret;
	}

	gpio_init_callback(&data->callback_drdy, ads131m02_data_ready_handler,
			   BIT(cfg->gpio_drdy.pin));

	return gpio_add_callback(cfg->gpio_drdy.port, &data->callback_drdy);
}

static int ads131m02_device_reset(const struct device *dev)
{
	int ret;
	uint8_t tx_buf[12] = {0};
	uint8_t rx_buf[3] = {0};

	sys_put_be16(ADS131M02_RESET_CMD, tx_buf);
	ret = ads131m02_transceive(dev, tx_buf, sizeof(tx_buf),
				   rx_buf, sizeof(rx_buf));

	if (ret != 0) {
		return ret;
	}

	if (sys_get_be16(rx_buf) != ADS131M02_RESET_RSP) {
		return -EIO;
	}

	k_msleep(ADS131M02_RESET_DELAY);

	return 0;
}

#if defined CONFIG_PM_DEVICE
static int ads131m02_pm(const struct device *dev, uint16_t cmd)
{
	int ret;
	uint8_t tx_buf[3] = {0};
	uint8_t rx_buf[3] = {0};

	sys_put_be16(cmd, tx_buf);
	ret = ads131m02_transceive(dev, tx_buf, sizeof(tx_buf),
				   rx_buf, sizeof(rx_buf));
	if (ret != 0) {
		return ret;
	}

	if (rx_buf[1] != cmd) {
		return -EIO;
	}

	return 0;
}

static int ads131m02_pm_action(const struct device *dev,
			       enum pm_device_action action)
{
	switch (action) {
	case PM_DEVICE_ACTION_RESUME:
		return ads131m02_pm(dev, ADS131M02_WAKEUP_CMD);
	case PM_DEVICE_ACTION_SUSPEND:
		return ads131m02_pm(dev, ADS131M02_STANDBY_CMD);
	default:
		return -EINVAL;
	}
}
#endif /* CONFIG_PM_DEVICE */

int ads131m02_set_adc_mode(const struct device *dev,
			   enum ads131m02_adc_mode mode,
			   enum ads131m02_gc_delay gc_delay)
{
	uint16_t temp = 0;

	switch (mode) {
	case ADS131M02_CONTINUOUS_MODE:
		break;
	case ADS131M02_GLOBAL_CHOP_MODE:
		temp |= ADS131M02_GC_MODE_MASK;
		temp |= FIELD_PREP(ADS131M02_GC_DELAY_MASK, gc_delay);
		break;
	default:
		return -EINVAL;
	}

	return ads131m02_reg_write(dev, ADS131M02_CFG_REG, temp);
}

int ads131m02_set_power_mode(const struct device *dev,
			     enum ads131m02_adc_power_mode mode)
{
	int ret;
	uint16_t temp;
	uint8_t buf[3] = {0};

	ret = ads131m02_reg_read(dev, ADS131M02_CLOCK_REG, buf, sizeof(buf));
	if (ret != 0) {
		return ret;
	}

	temp = sys_get_be16(buf);
	temp &= ~(ADS131M02_PWR_HR);

	switch (mode) {
	case ADS131M02_VLP:
		break;
	case ADS131M02_LP:
		temp |= ADS131M02_PWR_LP;
		break;
	case ADS131M02_HR:
		temp |= ADS131M02_PWR_HR;
		break;
	default:
		return -EINVAL;
	}

	return ads131m02_reg_write(dev, ADS131M02_CLOCK_REG, temp);
}

static DEVICE_API(adc, ads131m02_api) = {
	.channel_setup = ads131m02_channel_setup,
	.read = ads131m02_read,
	.ref_internal = ADS131M02_REF_INTERNAL,
};

static int ads131m02_init(const struct device *dev)
{
	int ret;
	uint8_t buf[3] = {0};
	const struct ads131m02_config *cfg = dev->config;
	struct ads131m02_data *data = dev->data;

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

	adc_context_init(&data->ctx);
	k_sem_init(&data->acq_sem, 0, 1);
	k_sem_init(&data->drdy_sem, 0, 1);

	ret = ads131m02_configure_gpio(dev);
	if (ret != 0) {
		LOG_ERR("GPIO config failed %d", ret);
		return ret;
	}

	ret = ads131m02_reg_read(dev, ADS131M02_ID_REG, buf, sizeof(buf));
	if (ret != 0) {
		return ret;
	}

	if (buf[0] != ADS131M02_DEVICE_ID) {
		LOG_ERR("Device ID mismatch %d", buf[0]);
		return -ENODEV;
	}

	ret = ads131m02_device_reset(dev);
	if (ret != 0) {
		LOG_WRN("Device is not reset");
	}

	/* By default, adc is configured, so disabling it */
	ret = ads131m02_reg_write(dev, ADS131M02_CLOCK_REG,
				  ADS131M02_DISABLE_ADC);
	if (ret != 0) {
		return ret;
	}

	adc_context_unlock_unconditionally(&data->ctx);

#if defined CONFIG_PM_DEVICE
	ret = ads131m02_pm(dev, ADS131M02_STANDBY_CMD);
	if (ret != 0) {
		return ret;
	}

	pm_device_init_suspended(dev);
#endif /* CONFIG_PM_DEVICE */

	LOG_INF("ADS131M02 Initialised");

	return 0;
}

#define DT_DRV_COMPAT ti_ads131m02

#define ADC_ADS131M02_INST_DEFINE(n)						\
	PM_DEVICE_DT_INST_DEFINE(n, ads131m02_pm_action);			\
	static const struct ads131m02_config config_##n = {			\
		.spi = SPI_DT_SPEC_INST_GET(					\
			n, SPI_OP_MODE_MASTER | SPI_MODE_CPHA |			\
			SPI_WORD_SET(8)),					\
		.gpio_drdy = GPIO_DT_SPEC_INST_GET(n, drdy_gpios),		\
	};									\
	static struct ads131m02_data data_##n;					\
	DEVICE_DT_INST_DEFINE(n, ads131m02_init,				\
			      PM_DEVICE_DT_INST_GET(n),				\
			      &data_##n, &config_##n, POST_KERNEL,		\
			      CONFIG_ADC_INIT_PRIORITY, &ads131m02_api);

DT_INST_FOREACH_STATUS_OKAY(ADC_ADS131M02_INST_DEFINE)
