/*
 * SPDX-FileCopyrightText: Copyright (c) 2023 Carl Zeiss Meditec AG
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT adi_adltc2990

#include <zephyr/sys/util.h>
#include <zephyr/drivers/i2c.h>

#include "adltc2990_reg.h"
#include "adltc2990.h"

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(adltc2990, CONFIG_SENSOR_LOG_LEVEL);

static enum adltc2990_monitoring_type adltc2990_get_v1_v2_measurement_modes(uint8_t mode_4_3,
									    uint8_t mode_2_0)
{
	if (mode_2_0 > ADLTC2990_MODE_2_0_MAX_VALUE || mode_4_3 > ADLTC2990_MODE_4_3_MAX_VALUE) {
		LOG_ERR("Invalid Measurement Mode");
		return -EINVAL;
	}
	if (mode_4_3 == ADLTC2990_MEASURE_INTERNAL_TEMPERATURE_ONLY ||
	    mode_4_3 == ADLTC2990_MEASURE_PINS_V3_V4_ONLY) {
		return NOTHING;
	}

	enum adltc2990_monitoring_type type = NOTHING;

	switch (mode_2_0) {
	case ADLTC2990_MODE_V1_V2_TR2:
	case ADLTC2990_MODE_V1_V2_V3_V4: {
		type = VOLTAGE_SINGLEENDED;
		break;
	}
	case ADLTC2990_MODE_V1_MINUS_V2_TR2:
	case ADLTC2990_MODE_V1_MINUS_V2_V3_V4:
	case ADLTC2990_MODE_V1_MINUS_V2_V3_MINUS_V4: {
		type = VOLTAGE_DIFFERENTIAL;
		break;
	}
	case ADLTC2990_MODE_TR1_V3_V4:
	case ADLTC2990_MODE_TR1_V3_MINUS_V4: {
	case ADLTC2990_MODE_TR1_TR2:
		type = TEMPERATURE;
		break;
	}
	default: {
		break;
	}
	}
	return type;
}

static enum adltc2990_monitoring_type adltc2990_get_v3_v4_measurement_modes(uint8_t mode_4_3,
									    uint8_t mode_2_0)
{
	if (mode_2_0 > ADLTC2990_MODE_2_0_MAX_VALUE || mode_4_3 > ADLTC2990_MODE_4_3_MAX_VALUE) {
		LOG_ERR("Invalid Measurement Mode");
		return -EINVAL;
	}
	if (mode_4_3 == ADLTC2990_MEASURE_INTERNAL_TEMPERATURE_ONLY ||
	    mode_4_3 == ADLTC2990_MEASURE_PINS_V1_V2_ONLY) {
		return NOTHING;
	}

	enum adltc2990_monitoring_type type = NOTHING;

	switch (mode_2_0) {
	case ADLTC2990_MODE_V1_V2_TR2:
	case ADLTC2990_MODE_V1_MINUS_V2_TR2:
	case ADLTC2990_MODE_TR1_TR2: {
		type = TEMPERATURE;
		break;
	}

	case ADLTC2990_MODE_V1_MINUS_V2_V3_V4:
	case ADLTC2990_MODE_TR1_V3_V4:
	case ADLTC2990_MODE_V1_V2_V3_V4: {
		type = VOLTAGE_SINGLEENDED;
		break;
	}
	case ADLTC2990_MODE_TR1_V3_MINUS_V4:
	case ADLTC2990_MODE_V1_MINUS_V2_V3_MINUS_V4: {
		type = VOLTAGE_DIFFERENTIAL;
		break;
	}
	default: {
		break;
	}
	}
	return type;
}

static bool adltc2990_is_busy(const struct device *dev)
{
	const struct adltc2990_config *cfg = dev->config;
	uint8_t status_reg = 0;

	i2c_reg_read_byte_dt(&cfg->bus, ADLTC2990_REG_STATUS, &status_reg);
	return status_reg & BIT(0);
}

static void adltc2990_get_v1_v2_val(const struct device *dev, struct sensor_value *val,
				    uint8_t num_values, uint8_t *const offset_index)
{
	struct adltc2990_data *data = dev->data;

	for (uint8_t index = 0; index < num_values; index++) {
		val[index].val1 = data->pins_v1_v2_values[index] / 1000000;
		val[index].val2 = data->pins_v1_v2_values[index] % 1000000;
		*offset_index = index + 1;
	}
}

static void adltc2990_get_v3_v4_val(const struct device *dev, struct sensor_value *val,
				    uint8_t num_values, uint8_t const *const offset)
{
	struct adltc2990_data *data = dev->data;

	uint8_t offset_index = *offset;

	for (uint8_t index = 0; index < num_values; index++) {
		val[index + offset_index].val1 = data->pins_v3_v4_values[index] / 1000000;
		val[index + offset_index].val2 = data->pins_v3_v4_values[index] % 1000000;
	}
}

static int adltc2990_trigger_measurement(const struct device *dev)
{
	const struct adltc2990_config *cfg = dev->config;

	return i2c_reg_write_byte_dt(&cfg->bus, ADLTC2990_REG_TRIGGER, 0x1);
}

static int32_t adltc2990_get_property_value(const struct device *dev,
					    enum adltc2990_monitoring_type type,
					    enum adltc2990_monitor_pins pin)
{
	const struct adltc2990_config *cfg = dev->config;

	uint8_t msb_value = 0, lsb_value = 0;
	uint8_t msb_address, lsb_address;

	switch (pin) {
	case V1: {
		msb_address = ADLTC2990_REG_V1_MSB;
		lsb_address = ADLTC2990_REG_V1_LSB;
		break;
	}
	case V2: {
		msb_address = ADLTC2990_REG_V2_MSB;
		lsb_address = ADLTC2990_REG_V2_LSB;
		break;
	}
	case V3: {
		msb_address = ADLTC2990_REG_V3_MSB;
		lsb_address = ADLTC2990_REG_V3_LSB;
		break;
	}
	case V4: {
		msb_address = ADLTC2990_REG_V4_MSB;
		lsb_address = ADLTC2990_REG_V4_LSB;
		break;
	}
	case INTERNAL_TEMPERATURE: {
		msb_address = ADLTC2990_REG_INTERNAL_TEMP_MSB;
		lsb_address = ADLTC2990_REG_INTERNAL_TEMP_LSB;
		break;
	}
	case SUPPLY_VOLTAGE: {
		msb_address = ADLTC2990_REG_VCC_MSB;
		lsb_address = ADLTC2990_REG_VCC_LSB;
		break;
	}
	default: {
		LOG_ERR("Trying to access illegal register");
		return -EINVAL;
	}
	}

	i2c_reg_read_byte_dt(&cfg->bus, msb_address, &msb_value);
	i2c_reg_read_byte_dt(&cfg->bus, lsb_address, &lsb_value);
	uint16_t conversion_factor;
	uint8_t negative_bit_index;

	if (type == VOLTAGE_SINGLEENDED) {
		conversion_factor = ADLTC2990_VOLTAGE_SINGLEENDED_CONVERSION_FACTOR;
		negative_bit_index = 14;
	} else if (type == VOLTAGE_DIFFERENTIAL) {
		conversion_factor = ADLTC2990_VOLTAGE_DIFFERENTIAL_CONVERSION_FACTOR;
		negative_bit_index = 14;
	} else if (type == TEMPERATURE) {
		conversion_factor = ADLTC2990_TEMPERATURE_CONVERSION_FACTOR;
		negative_bit_index = 12;
	} else {
		LOG_ERR("unknown type");
		return -EINVAL;
	}

	int16_t value = (msb_value<<8)+lsb_value;

	int32_t voltage_value = (value << (31-negative_bit_index))>>(31-negative_bit_index);

	return (voltage_value * conversion_factor) / 100;
}

static int adltc2990_init(const struct device *dev)
{
	const struct adltc2990_config *cfg = dev->config;

	if (!i2c_is_ready_dt(&cfg->bus)) {
		LOG_ERR("I2C bus %s not ready", cfg->bus.bus->name);
		return -ENODEV;
	}

	const uint8_t ctrl_reg_setting = cfg->temp_format << 7 | cfg->acq_format << 6 | 0 << 5 |
					 cfg->measurement_mode[1] << 3 | cfg->measurement_mode[0];

	LOG_DBG("Setting Control Register to: 0x%x", ctrl_reg_setting);
	int err = i2c_reg_write_byte_dt(&cfg->bus, ADLTC2990_REG_CONTROL, ctrl_reg_setting);

	if (err < 0) {
		LOG_ERR("configuring for single bus failed: %d", err);
		return err;
	}
	LOG_INF("Initializing ADLTC2990 with name %s", dev->name);
	return 0;
}

static int adltc2990_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
	struct adltc2990_data *data = dev->data;
	const struct adltc2990_config *cfg = dev->config;
	enum adltc2990_monitoring_type mode_v1_v2 = adltc2990_get_v1_v2_measurement_modes(
		cfg->measurement_mode[1], cfg->measurement_mode[0]);
	enum adltc2990_monitoring_type mode_v3_v4 = adltc2990_get_v3_v4_measurement_modes(
		cfg->measurement_mode[1], cfg->measurement_mode[0]);

	float voltage_divider_ratio;

	switch (chan) {
	case SENSOR_CHAN_DIE_TEMP: {
		data->internal_temperature =
			adltc2990_get_property_value(dev, TEMPERATURE, INTERNAL_TEMPERATURE);
		break;
	}
	case SENSOR_CHAN_CURRENT: {
		if (!(mode_v1_v2 == VOLTAGE_DIFFERENTIAL || mode_v3_v4 == VOLTAGE_DIFFERENTIAL)) {
			LOG_ERR("Sensor is not configured to measure Current");
			return -EINVAL;
		}
		if (mode_v1_v2 == VOLTAGE_DIFFERENTIAL) {
			data->pins_v1_v2_values[0] =
				(adltc2990_get_property_value(dev, VOLTAGE_DIFFERENTIAL, V1) *
				 ADLTC2990_MICROOHM_CONVERSION_FACTOR) /
				cfg->pins_v1_v2.pins_current_resistor;
		}
		if (mode_v3_v4 == VOLTAGE_DIFFERENTIAL) {
			data->pins_v3_v4_values[0] =
				(adltc2990_get_property_value(dev, VOLTAGE_DIFFERENTIAL, V3) *
				 ADLTC2990_MICROOHM_CONVERSION_FACTOR) /
				cfg->pins_v3_v4.pins_current_resistor;
		}
		break;
	}
	case SENSOR_CHAN_VOLTAGE: {
		data->supply_voltage =
			adltc2990_get_property_value(dev, VOLTAGE_SINGLEENDED, SUPPLY_VOLTAGE) +
			2500000;

		if (mode_v1_v2 == VOLTAGE_DIFFERENTIAL) {
			data->pins_v1_v2_values[0] =
				adltc2990_get_property_value(dev, VOLTAGE_DIFFERENTIAL, V1);
		} else if (mode_v1_v2 == VOLTAGE_SINGLEENDED) {
			uint32_t v1_r1 =
				cfg->pins_v1_v2.voltage_divider_resistors.v1_r1_r2[0];
			uint32_t v1_r2 =
				cfg->pins_v1_v2.voltage_divider_resistors.v2_r1_r2[1];
			voltage_divider_ratio = DIV_ROUND_CLOSEST(v1_r1 + v1_r2, v1_r2);
			data->pins_v1_v2_values[0] =
				adltc2990_get_property_value(dev, VOLTAGE_SINGLEENDED, V1) *
				voltage_divider_ratio;

			uint32_t v2_r1 =
				cfg->pins_v1_v2.voltage_divider_resistors.v2_r1_r2[0];
			uint32_t v2_r2 =
				cfg->pins_v1_v2.voltage_divider_resistors.v2_r1_r2[1];
			voltage_divider_ratio = DIV_ROUND_CLOSEST(v2_r1 + v2_r2, v2_r2);
			data->pins_v1_v2_values[1] =
				adltc2990_get_property_value(dev, VOLTAGE_SINGLEENDED, V2) *
				voltage_divider_ratio;
		}

		if (mode_v3_v4 == VOLTAGE_DIFFERENTIAL) {
			data->pins_v3_v4_values[0] =
				adltc2990_get_property_value(dev, VOLTAGE_DIFFERENTIAL, V3);
		} else if (mode_v3_v4 == VOLTAGE_SINGLEENDED) {
			uint32_t v3_r1 =
				cfg->pins_v3_v4.voltage_divider_resistors.v3_r1_r2[0];
			uint32_t v3_r2 =
				cfg->pins_v3_v4.voltage_divider_resistors.v3_r1_r2[1];
			voltage_divider_ratio = DIV_ROUND_CLOSEST(v3_r1 + v3_r2, v3_r2);
			data->pins_v3_v4_values[0] =
				adltc2990_get_property_value(dev, VOLTAGE_SINGLEENDED, V3) *
				voltage_divider_ratio;

			uint32_t v4_r1 =
				cfg->pins_v3_v4.voltage_divider_resistors.v4_r1_r2[0];
			uint32_t v4_r2 =
				cfg->pins_v3_v4.voltage_divider_resistors.v4_r1_r2[1];
			voltage_divider_ratio = DIV_ROUND_CLOSEST((v4_r1 + v4_r2), v4_r2);
			data->pins_v3_v4_values[1] =
				adltc2990_get_property_value(dev, VOLTAGE_SINGLEENDED, V4) *
				voltage_divider_ratio;
		}
		break;
	}
	case SENSOR_CHAN_AMBIENT_TEMP: {
		if (!(mode_v1_v2 == TEMPERATURE || mode_v3_v4 == TEMPERATURE)) {
			LOG_ERR("Sensor is not configured to measure Ambient Temperature");
			return -EINVAL;
		}
		if (mode_v1_v2 == TEMPERATURE) {
			data->pins_v1_v2_values[0] =
				adltc2990_get_property_value(dev, TEMPERATURE, V1);
		}
		if (mode_v3_v4 == TEMPERATURE) {
			data->pins_v3_v4_values[1] =
				adltc2990_get_property_value(dev, TEMPERATURE, V3);
		}
		break;
	}
	case SENSOR_CHAN_ALL: {
		if (adltc2990_is_busy(dev)) {
			LOG_INF("ADLTC2990 conversion ongoing");
			return -EBUSY;
		}
		adltc2990_trigger_measurement(dev);
		break;
	}
	default: {
		LOG_ERR("does not measure channel: %d", chan);
		return -ENOTSUP;
	}
	}

	return 0;
}

static int adltc2990_channel_get(const struct device *dev, enum sensor_channel chan,
				 struct sensor_value *val)
{
	if (val == NULL) {
		LOG_ERR("Argument of type sensor_value* cannot be null ");
		return -EINVAL;
	}
	struct adltc2990_data *data = dev->data;
	const struct adltc2990_config *cfg = dev->config;
	enum adltc2990_monitoring_type mode_v1_v2 = adltc2990_get_v1_v2_measurement_modes(
		cfg->measurement_mode[1], cfg->measurement_mode[0]);
	enum adltc2990_monitoring_type mode_v3_v4 = adltc2990_get_v3_v4_measurement_modes(
		cfg->measurement_mode[1], cfg->measurement_mode[0]);

	uint8_t offset_index = 0, num_values_v1_v2 = 0, num_values_v3_v4 = 0;

	switch (chan) {
	case SENSOR_CHAN_DIE_TEMP: {
		val->val1 = data->internal_temperature / 10000;
		val->val2 = data->internal_temperature % 10000;
		LOG_DBG("Internal Temperature Value is:%d.%d", val->val1, val->val2);
		break;
	}
	case SENSOR_CHAN_VOLTAGE: {
		if (mode_v1_v2 == VOLTAGE_SINGLEENDED) {
			LOG_DBG("Getting V1,V2");
			num_values_v1_v2 = ADLTC2990_VOLTAGE_SINGLE_ENDED_VALUES;
		} else if (mode_v1_v2 == VOLTAGE_DIFFERENTIAL) {
			LOG_DBG("Getting V3-V4");
			num_values_v1_v2 = ADLTC2990_VOLTAGE_DIFF_VALUES;
		}
		if (mode_v3_v4 == VOLTAGE_SINGLEENDED) {
			LOG_DBG("Getting V3,V4");
			num_values_v3_v4 = ADLTC2990_VOLTAGE_SINGLE_ENDED_VALUES;
		} else if (mode_v3_v4 == VOLTAGE_DIFFERENTIAL) {
			LOG_DBG("Getting V3-V4");
			num_values_v3_v4 = ADLTC2990_VOLTAGE_DIFF_VALUES;
		}
		val[num_values_v1_v2 + num_values_v3_v4].val1 = data->supply_voltage / 1000000;
		val[num_values_v1_v2 + num_values_v3_v4].val2 = data->supply_voltage % 1000000;
		break;
	}
	case SENSOR_CHAN_CURRENT: {
		if (!(mode_v1_v2 == VOLTAGE_DIFFERENTIAL || mode_v3_v4 == VOLTAGE_DIFFERENTIAL)) {
			LOG_ERR("Sensor is not configured to measure Current");
			return -EINVAL;
		}
		if (mode_v1_v2 == VOLTAGE_DIFFERENTIAL && mode_v3_v4 == VOLTAGE_DIFFERENTIAL) {
			LOG_DBG("Getting I12 and I34");
			num_values_v1_v2 = ADLTC2990_CURRENT_VALUES;
			num_values_v3_v4 = ADLTC2990_CURRENT_VALUES;
		} else if (mode_v1_v2 == VOLTAGE_DIFFERENTIAL) {
			LOG_DBG("Getting I12");
			num_values_v1_v2 = ADLTC2990_CURRENT_VALUES;
		} else if (mode_v3_v4 == VOLTAGE_DIFFERENTIAL) {
			LOG_DBG("Getting I34");
			num_values_v3_v4 = ADLTC2990_CURRENT_VALUES;
		}
		break;
	}
	case SENSOR_CHAN_AMBIENT_TEMP: {
		if (!(mode_v1_v2 == TEMPERATURE || mode_v3_v4 == TEMPERATURE)) {
			LOG_ERR("Sensor is not configured to measure Ambient Temperature");
			return -EINVAL;
		}
		if (mode_v1_v2 == TEMPERATURE && mode_v3_v4 == TEMPERATURE) {
			LOG_DBG("Getting T12 and T34");
			num_values_v1_v2 = ADLTC2990_TEMP_VALUES;
			num_values_v3_v4 = ADLTC2990_TEMP_VALUES;
		} else if (mode_v1_v2 == TEMPERATURE) {
			LOG_DBG("Getting T12");
			num_values_v1_v2 = ADLTC2990_TEMP_VALUES;
		} else if (mode_v3_v4 == TEMPERATURE) {
			LOG_DBG("Getting T34");
			num_values_v3_v4 = ADLTC2990_TEMP_VALUES;
		}
		break;
	}
	default: {
		return -ENOTSUP;
	}
	}

	adltc2990_get_v1_v2_val(dev, val, num_values_v1_v2, &offset_index);
	adltc2990_get_v3_v4_val(dev, val, num_values_v3_v4, &offset_index);
	return 0;
}

static const struct sensor_driver_api adltc2990_driver_api = {
	.sample_fetch = adltc2990_sample_fetch,
	.channel_get = adltc2990_channel_get,
};

#define ADLTC2990_DEFINE(inst)                                                                     \
	static struct adltc2990_data adltc2990_data_##inst;                                        \
	static const struct adltc2990_config adltc2990_config_##inst = {                           \
		.bus = I2C_DT_SPEC_INST_GET(inst),                                                 \
		.temp_format = DT_INST_PROP(inst, temperature_format),                             \
		.acq_format = DT_INST_PROP(inst, acquistion_format),                               \
		.measurement_mode = DT_INST_PROP(inst, measurement_mode),                          \
		.pins_v1_v2.pins_current_resistor =                                  \
			DT_INST_PROP_OR(inst, pins_v1_v2_current_resistor, 1),                     \
		.pins_v1_v2.voltage_divider_resistors.v1_r1_r2 =                     \
			DT_INST_PROP_OR(inst, pin_v1_voltage_divider_resistors, NULL),             \
		.pins_v1_v2.voltage_divider_resistors.v2_r1_r2 =                     \
			DT_INST_PROP_OR(inst, pin_v2_voltage_divider_resistors, NULL),             \
		.pins_v3_v4.pins_current_resistor =                                  \
			DT_INST_PROP_OR(inst, pins_v3_v4_current_resistor, 1),                     \
		.pins_v3_v4.voltage_divider_resistors.v3_r1_r2 =                     \
			DT_INST_PROP_OR(inst, pin_v3_voltage_divider_resistors, NULL),             \
		.pins_v3_v4.voltage_divider_resistors.v4_r1_r2 =                     \
			DT_INST_PROP_OR(inst, pin_v4_voltage_divider_resistors, NULL)};            \
                                                                                                   \
	SENSOR_DEVICE_DT_INST_DEFINE(inst, adltc2990_init, NULL, &adltc2990_data_##inst,           \
				     &adltc2990_config_##inst, POST_KERNEL,                        \
				     CONFIG_SENSOR_INIT_PRIORITY, &adltc2990_driver_api);

DT_INST_FOREACH_STATUS_OKAY(ADLTC2990_DEFINE)
