/*
 * Copyright (c) 2023 SILA Embedded Solutions GmbH
 *
 * 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/sys/byteorder.h>
#include <zephyr/sys/util.h>

#define ADC_CONTEXT_USES_KERNEL_TIMER 1
#include "adc_context.h"

LOG_MODULE_REGISTER(max11102_17, CONFIG_ADC_LOG_LEVEL);

struct max11102_17_config {
	struct spi_dt_spec bus;
	const struct gpio_dt_spec gpio_chsel;
	uint8_t resolution;
	uint8_t channel_count;
};

struct max11102_17_data {
	struct adc_context ctx;
	struct k_sem acquire_signal;
	int16_t *buffer;
	int16_t *buffer_ptr;
	uint8_t current_channel_id;
	uint8_t sequence_channel_id;
#if CONFIG_ADC_ASYNC
	struct k_thread thread;

	K_KERNEL_STACK_MEMBER(stack, CONFIG_ADC_MAX11102_17_ACQUISITION_THREAD_STACK_SIZE);
#endif /* CONFIG_ADC_ASYNC */
};

static int max11102_17_switch_channel(const struct device *dev)
{
	const struct max11102_17_config *config = dev->config;
	struct max11102_17_data *data = dev->data;
	int result;
	uint8_t buffer_rx[1];
	const struct spi_buf rx_buf[] = {{
		.buf = buffer_rx,
		.len = ARRAY_SIZE(buffer_rx),
	}};
	const struct spi_buf_set rx = {
		.buffers = rx_buf,
		.count = ARRAY_SIZE(rx_buf),
	};
	struct spi_dt_spec bus;

	memcpy(&bus, &config->bus, sizeof(bus));
	bus.config.operation |= SPI_HOLD_ON_CS;

	result = spi_read_dt(&bus, &rx);
	if (result != 0) {
		LOG_ERR("read failed with error %i", result);
		return result;
	}

	gpio_pin_set_dt(&config->gpio_chsel, data->current_channel_id);

	result = spi_read_dt(&config->bus, &rx);
	if (result != 0) {
		LOG_ERR("read failed with error %i", result);
		return result;
	}

	return 0;
}

static int max11102_17_channel_setup(const struct device *dev,
				     const struct adc_channel_cfg *channel_cfg)
{
	const struct max11102_17_config *config = dev->config;

	LOG_DBG("read from ADC channel %i", channel_cfg->channel_id);

	if (channel_cfg->reference != ADC_REF_EXTERNAL0) {
		LOG_ERR("invalid reference %i", channel_cfg->reference);
		return -EINVAL;
	}

	if (channel_cfg->gain != ADC_GAIN_1) {
		LOG_ERR("invalid gain %i", channel_cfg->gain);
		return -EINVAL;
	}

	if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) {
		LOG_ERR("invalid acquisition time %i", channel_cfg->acquisition_time);
		return -EINVAL;
	}

	if (channel_cfg->differential != 0) {
		LOG_ERR("differential inputs are not supported");
		return -EINVAL;
	}

	if (channel_cfg->channel_id > config->channel_count) {
		LOG_ERR("invalid channel selection %i", channel_cfg->channel_id);
		return -EINVAL;
	}

	return 0;
}

static int max11102_17_validate_buffer_size(const struct adc_sequence *sequence)
{
	size_t necessary = sizeof(int16_t);

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

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

	return 0;
}

static int max11102_17_validate_sequence(const struct device *dev,
					 const struct adc_sequence *sequence)
{
	const struct max11102_17_config *config = dev->config;
	struct max11102_17_data *data = dev->data;
	size_t sequence_channel_count = 0;
	const size_t channel_maximum = 8*sizeof(sequence->channels);

	if (sequence->resolution != config->resolution) {
		LOG_ERR("invalid resolution");
		return -EINVAL;
	}

	for (size_t i = 0; i < channel_maximum; ++i) {
		if ((BIT(i) & sequence->channels) == 0) {
			continue;
		}

		if (i > config->channel_count) {
			LOG_ERR("invalid channel selection");
			return -EINVAL;
		}

		sequence_channel_count++;
		data->sequence_channel_id = i;
	}

	if (sequence_channel_count == 0) {
		LOG_ERR("no channel selected");
		return -EINVAL;
	}

	if (sequence_channel_count > 1) {
		LOG_ERR("multiple channels selected");
		return -EINVAL;
	}

	if (sequence->oversampling) {
		LOG_ERR("oversampling is not supported");
		return -EINVAL;
	}

	return max11102_17_validate_buffer_size(sequence);
}

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

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

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

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

static int max11102_17_adc_start_read(const struct device *dev, const struct adc_sequence *sequence,
				      bool wait)
{
	int result;
	struct max11102_17_data *data = dev->data;

	result = max11102_17_validate_sequence(dev, sequence);

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

	data->buffer = sequence->buffer;

	adc_context_start_read(&data->ctx, sequence);

	if (wait) {
		result = adc_context_wait_for_completion(&data->ctx);
	}

	return result;
}

static int max11102_17_read_sample(const struct device *dev, int16_t *sample)
{
	const struct max11102_17_config *config = dev->config;
	int result;
	size_t trailing_bits = 15 - config->resolution;
	uint8_t buffer_rx[2];
	const struct spi_buf rx_buf[] = {{
		.buf = buffer_rx,
		.len = ARRAY_SIZE(buffer_rx),
	}};
	const struct spi_buf_set rx = {
		.buffers = rx_buf,
		.count = ARRAY_SIZE(rx_buf),
	};

	result = spi_read_dt(&config->bus, &rx);

	if (result != 0) {
		LOG_ERR("read failed with error %i", result);
		return result;
	}

	*sample = sys_get_be16(buffer_rx);
	LOG_DBG("raw sample: 0x%04X", *sample);

	*sample = *sample >> trailing_bits;
	*sample = *sample & GENMASK(config->resolution, 0);
	LOG_DBG("sample: 0x%04X", *sample);

	return 0;
}

static int max11102_17_adc_perform_read(const struct device *dev)
{
	int result;
	struct max11102_17_data *data = dev->data;

	k_sem_take(&data->acquire_signal, K_FOREVER);

	if (data->sequence_channel_id != data->current_channel_id) {
		LOG_DBG("switch channel selection");
		data->current_channel_id = data->sequence_channel_id;
		max11102_17_switch_channel(dev);
	}

	result = max11102_17_read_sample(dev, data->buffer);
	if (result != 0) {
		LOG_ERR("reading sample failed");
		adc_context_complete(&data->ctx, result);
		return result;
	}

	data->buffer++;

	adc_context_on_sampling_done(&data->ctx, dev);

	return result;
}

#if CONFIG_ADC_ASYNC
static int max11102_17_adc_read_async(const struct device *dev, const struct adc_sequence *sequence,
				      struct k_poll_signal *async)
{
	int result;
	struct max11102_17_data *data = dev->data;

	adc_context_lock(&data->ctx, true, async);
	result = max11102_17_adc_start_read(dev, sequence, true);
	adc_context_release(&data->ctx, result);

	return result;
}

static int max11102_17_read(const struct device *dev, const struct adc_sequence *sequence)
{
	int result;
	struct max11102_17_data *data = dev->data;

	adc_context_lock(&data->ctx, false, NULL);
	result = max11102_17_adc_start_read(dev, sequence, true);
	adc_context_release(&data->ctx, result);

	return result;
}

#else
static int max11102_17_read(const struct device *dev, const struct adc_sequence *sequence)
{
	int result;
	struct max11102_17_data *data = dev->data;

	adc_context_lock(&data->ctx, false, NULL);
	result = max11102_17_adc_start_read(dev, sequence, false);

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

	adc_context_release(&data->ctx, result);
	return result;
}
#endif

#if CONFIG_ADC_ASYNC
static void max11102_17_acquisition_thread(const struct device *dev)
{
	while (true) {
		max11102_17_adc_perform_read(dev);
	}
}
#endif

static int max11102_17_init(const struct device *dev)
{
	int result;
	const struct max11102_17_config *config = dev->config;
	struct max11102_17_data *data = dev->data;
	int16_t sample;

	adc_context_init(&data->ctx);

	k_sem_init(&data->acquire_signal, 0, 1);

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

	switch (config->channel_count) {
	case 1:
		if (config->gpio_chsel.port != NULL) {
			LOG_ERR("GPIO for chsel set with only one channel");
			return -EINVAL;
		}
		break;
	case 2:
		if (config->gpio_chsel.port == NULL) {
			LOG_ERR("no GPIO for chsel set with two channels");
			return -EINVAL;
		}

		result = gpio_pin_configure_dt(&config->gpio_chsel, GPIO_OUTPUT_INACTIVE);
		if (result != 0) {
			LOG_ERR("failed to initialize GPIO for chsel");
			return result;
		}
		break;
	default:
		LOG_ERR("invalid number of channels (%i)", config->channel_count);
		return -EINVAL;
	}

	data->current_channel_id = 0;

#if CONFIG_ADC_ASYNC
	const k_tid_t tid = k_thread_create(
		&data->thread, data->stack, CONFIG_ADC_MAX11102_17_ACQUISITION_THREAD_STACK_SIZE,
		(k_thread_entry_t)max11102_17_acquisition_thread, (void *)dev, NULL, NULL,
		CONFIG_ADC_MAX11102_17_ACQUISITION_THREAD_INIT_PRIO, 0, K_NO_WAIT);
	k_thread_name_set(tid, "adc_max11102_17");
#endif

	/* power up time is one conversion cycle */
	result = max11102_17_read_sample(dev, &sample);
	if (result != 0) {
		LOG_ERR("unable to read dummy sample for power up timing");
		return result;
	}

	adc_context_unlock_unconditionally(&data->ctx);

	return result;
}

static const struct adc_driver_api api = {
	.channel_setup = max11102_17_channel_setup,
	.read = max11102_17_read,
	.ref_internal = 0,
#ifdef CONFIG_ADC_ASYNC
	.read_async = max11102_17_adc_read_async,
#endif
};

BUILD_ASSERT(CONFIG_ADC_INIT_PRIORITY > CONFIG_SPI_INIT_PRIORITY,
	     "CONFIG_ADC_INIT_PRIORITY must be higher than CONFIG_SPI_INIT_PRIORITY");

#define ADC_MAX11102_17_INST_DEFINE(index, name, res, channels)                                    \
	static const struct max11102_17_config config_##name##_##index = {                         \
		.bus = SPI_DT_SPEC_INST_GET(                                                       \
			index,                                                                     \
			SPI_OP_MODE_MASTER | SPI_MODE_CPOL | SPI_MODE_CPHA | SPI_WORD_SET(8), 0),  \
		.gpio_chsel = GPIO_DT_SPEC_INST_GET_OR(index, chsel_gpios, {0}),                   \
		.resolution = res,                                                                 \
		.channel_count = channels,                                                         \
	};                                                                                         \
	static struct max11102_17_data data_##name##_##index;                                      \
	DEVICE_DT_INST_DEFINE(index, max11102_17_init, NULL, &data_##name##_##index,               \
			      &config_##name##_##index, POST_KERNEL, CONFIG_ADC_INIT_PRIORITY,     \
			      &api);

#define DT_DRV_COMPAT           maxim_max11102
#define ADC_MAX11102_RESOLUTION 12
#define ADC_MAX11102_CHANNELS   2
DT_INST_FOREACH_STATUS_OKAY_VARGS(ADC_MAX11102_17_INST_DEFINE, DT_DRV_COMPAT,
				  ADC_MAX11102_RESOLUTION, ADC_MAX11102_CHANNELS)
#undef DT_DRV_COMPAT

#define DT_DRV_COMPAT           maxim_max11103
#define ADC_MAX11103_RESOLUTION 12
#define ADC_MAX11103_CHANNELS   2
DT_INST_FOREACH_STATUS_OKAY_VARGS(ADC_MAX11102_17_INST_DEFINE, DT_DRV_COMPAT,
				  ADC_MAX11103_RESOLUTION, ADC_MAX11103_CHANNELS)
#undef DT_DRV_COMPAT

#define DT_DRV_COMPAT           maxim_max11105
#define ADC_MAX11105_RESOLUTION 12
#define ADC_MAX11105_CHANNELS   1
DT_INST_FOREACH_STATUS_OKAY_VARGS(ADC_MAX11102_17_INST_DEFINE, DT_DRV_COMPAT,
				  ADC_MAX11105_RESOLUTION, ADC_MAX11105_CHANNELS)
#undef DT_DRV_COMPAT

#define DT_DRV_COMPAT           maxim_max11106
#define ADC_MAX11106_RESOLUTION 10
#define ADC_MAX11106_CHANNELS   2
DT_INST_FOREACH_STATUS_OKAY_VARGS(ADC_MAX11102_17_INST_DEFINE, DT_DRV_COMPAT,
				  ADC_MAX11106_RESOLUTION, ADC_MAX11106_CHANNELS)
#undef DT_DRV_COMPAT

#define DT_DRV_COMPAT           maxim_max11110
#define ADC_MAX11110_RESOLUTION 10
#define ADC_MAX11110_CHANNELS   1
DT_INST_FOREACH_STATUS_OKAY_VARGS(ADC_MAX11102_17_INST_DEFINE, DT_DRV_COMPAT,
				  ADC_MAX11110_RESOLUTION, ADC_MAX11110_CHANNELS)
#undef DT_DRV_COMPAT

#define DT_DRV_COMPAT           maxim_max11111
#define ADC_MAX11111_RESOLUTION 8
#define ADC_MAX11111_CHANNELS   2
DT_INST_FOREACH_STATUS_OKAY_VARGS(ADC_MAX11102_17_INST_DEFINE, DT_DRV_COMPAT,
				  ADC_MAX11111_RESOLUTION, ADC_MAX11111_CHANNELS)
#undef DT_DRV_COMPAT

#define DT_DRV_COMPAT           maxim_max11115
#define ADC_MAX11115_RESOLUTION 8
#define ADC_MAX11115_CHANNELS   1
DT_INST_FOREACH_STATUS_OKAY_VARGS(ADC_MAX11102_17_INST_DEFINE, DT_DRV_COMPAT,
				  ADC_MAX11115_RESOLUTION, ADC_MAX11115_CHANNELS)
#undef DT_DRV_COMPAT

#define DT_DRV_COMPAT           maxim_max11116
#define ADC_MAX11116_RESOLUTION 8
#define ADC_MAX11116_CHANNELS   1
DT_INST_FOREACH_STATUS_OKAY_VARGS(ADC_MAX11102_17_INST_DEFINE, DT_DRV_COMPAT,
				  ADC_MAX11116_RESOLUTION, ADC_MAX11116_CHANNELS)
#undef DT_DRV_COMPAT

#define DT_DRV_COMPAT           maxim_max11117
#define ADC_MAX11117_RESOLUTION 10
#define ADC_MAX11117_CHANNELS   1
DT_INST_FOREACH_STATUS_OKAY_VARGS(ADC_MAX11102_17_INST_DEFINE, DT_DRV_COMPAT,
				  ADC_MAX11117_RESOLUTION, ADC_MAX11117_CHANNELS)
#undef DT_DRV_COMPAT
