/*
 * Copyright 2024 NXP
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT nxp_p3t1755

#include "p3t1755.h"
#include <zephyr/sys/util.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/logging/log.h>
#include <stdlib.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>

LOG_MODULE_REGISTER(P3T1755, CONFIG_SENSOR_LOG_LEVEL);


#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i3c)
static int p3t1755_i3c_read_reg(const struct device *dev, uint8_t reg, uint8_t *value, uint8_t len)
{
	struct p3t1755_data *data = dev->data;

	return i3c_burst_read(data->i3c_dev, reg, value, len);
}

static int p3t1755_i3c_write_reg(const struct device *dev, uint8_t reg, uint8_t *byte, uint8_t len)
{
	struct p3t1755_data *data = dev->data;

	return i3c_burst_write(data->i3c_dev, reg, byte, len);
}

static int p3t1755_i3c_get(const struct device *dev)
{
	const struct p3t1755_config *config = dev->config;

	return pm_device_runtime_get(config->i3c.bus);
}

static int p3t1755_i3c_put(const struct device *dev)
{
	const struct p3t1755_config *config = dev->config;

	return pm_device_runtime_put(config->i3c.bus);
}
#endif

#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c)
__maybe_unused static int p3t1755_i2c_read_reg(const struct device *dev, uint8_t reg,
					       uint8_t *value, uint8_t len)
{
	const struct p3t1755_config *config = dev->config;

	return i2c_burst_read_dt(&config->bus_cfg.i2c, reg, value, len);
}

__maybe_unused static int p3t1755_i2c_write_reg(const struct device *dev, uint8_t reg,
						uint8_t *byte, uint8_t len)
{
	const struct p3t1755_config *config = dev->config;

	return i2c_burst_write_dt(&config->bus_cfg.i2c, reg, byte, len);
}

__maybe_unused static int p3t1755_i2c_get(const struct device *dev)
{
	const struct p3t1755_config *config = dev->config;

	return pm_device_runtime_get(config->bus_cfg.i2c.bus);
}

__maybe_unused static int p3t1755_i2c_put(const struct device *dev)
{
	const struct p3t1755_config *config = dev->config;

	return pm_device_runtime_put(config->bus_cfg.i2c.bus);
}
#endif

static int p3t1755_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
	const struct p3t1755_config *config = dev->config;
	struct p3t1755_data *data = dev->data;
	uint8_t raw_temp[2] = {0};

	if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_AMBIENT_TEMP) {
		LOG_ERR("Invalid channel provided");
		return -ENOTSUP;
	}

	config->ops.get(dev);

	if (config->oneshot_mode) {
		data->config_reg |= P3T1755_CONFIG_REG_OS;
		config->ops.write(dev, P3T1755_CONFIG_REG, &data->config_reg, 1);
		/* Maximum one-shot conversion time per specification is 12ms */
		k_sleep(K_MSEC(12));
	}

	int status = config->ops.read(dev, P3T1755_TEMPERATURE_REG, raw_temp, 2);

	config->ops.put(dev);

	if (status) {
		LOG_ERR("read return error %d", status);
		return -ENOTSUP;
	}

	/* Byte 1 contains the MSByte and Byte 2 contains the LSByte, we need to swap the 2 bytes.
	 * The 4 least significant bits of the LSByte are zero and should be ignored.
	 */
	data->sample = (((uint16_t)raw_temp[0] << 8U) | (uint16_t)raw_temp[1]) >>
		       P3T1755_TEMPERATURE_REG_SHIFT;

	return 0;
}

/* Decode a register temperature value to a signed temperature */
static inline int p3t1755_convert_to_signed(uint16_t reg)
{
	int rv = reg & P3T1755_TEMPERATURE_ABS_MASK;

	if (reg & P3T1755_TEMPERATURE_SIGN_BIT) {
		/* Convert 12-bit 2s complement to signed negative
		 * value.
		 */
		rv = -(1U + (rv ^ P3T1755_TEMPERATURE_ABS_MASK));
	}
	return rv;
}

static int p3t1755_channel_get(const struct device *dev, enum sensor_channel chan,
			       struct sensor_value *val)
{
	struct p3t1755_data *data = dev->data;
	int32_t raw_val;

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

	raw_val = p3t1755_convert_to_signed(data->sample);

	/* Temperature data resolution is 0.0625 C, apply a temperature scale */
	raw_val = raw_val * P3T1755_TEMPERATURE_SCALE;

	sensor_value_from_micro(val, raw_val);

	return 0;
}

static int p3t1755_pm_resume(const struct device *dev)
{
	const struct p3t1755_config *config = dev->config;
	struct p3t1755_data *data = dev->data;
	int ret = -ENODEV;

	if (config->ops.get(dev)) {
		LOG_ERR("Bus device get failed");
		goto put_and_ret;
	}

#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i3c)
	if (config->i3c.bus != NULL) {
		data->i3c_dev = i3c_device_find(config->i3c.bus, &config->i3c.dev_id);
		if (data->i3c_dev == NULL) {
			LOG_ERR("Cannot find I3C device descriptor");
			goto put_and_ret;
		}
	}
#endif
#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c)
	if (config->inst_on_bus == P3T1755_BUS_I2C) {
		if (!i2c_is_ready_dt(&config->bus_cfg.i2c)) {
			LOG_ERR("I2C bus device not ready");
			goto put_and_ret;
		}
	}
#endif

	if (config->oneshot_mode) {
		config->ops.read(dev, P3T1755_CONFIG_REG, &data->config_reg, 1);
		/* Operate in shutdown mode. Set the OS bit to start the
		 * one-shot temperature measurement.
		 */
		data->config_reg |= P3T1755_CONFIG_REG_SD;
		config->ops.write(dev, P3T1755_CONFIG_REG, &data->config_reg, 1);
	}

	ret = 0;

put_and_ret:
	(void)config->ops.put(dev);

	return ret;
}

static int p3t1755_pm_hook(const struct device *dev, enum pm_device_action action)
{
	int ret;

	switch (action) {
	case PM_DEVICE_ACTION_SUSPEND:
		ret = 0;
		break;
	case PM_DEVICE_ACTION_RESUME:
		ret = p3t1755_pm_resume(dev);
		break;
	default:
		ret = -ENOTSUP;
	}

	return ret;
}

static int p3t1755_init(const struct device *dev)
{
	return pm_device_driver_init(dev, p3t1755_pm_hook);
}

static DEVICE_API(sensor, p3t1755_driver_api) = {
	.sample_fetch = p3t1755_sample_fetch,
	.channel_get = p3t1755_channel_get,
};

/*
 * Instantiation macros used when a device is on an I2C bus.
 */
#define P3T1755_CONFIG_I2C(inst)                                                                   \
	.bus_cfg = {.i2c = I2C_DT_SPEC_INST_GET(inst)},                                            \
	.ops = {                                                                                   \
		.read = p3t1755_i2c_read_reg,                                                      \
		.write = p3t1755_i2c_write_reg,                                                    \
		.get = p3t1755_i2c_get,                                                            \
		.put = p3t1755_i2c_put,                                                            \
	},                                                                                         \
	.inst_on_bus = P3T1755_BUS_I2C,

/*
 * Instantiation macros used when a device is on an I#C bus.
 */
#define P3T1755_CONFIG_I3C(inst)                                                                   \
	.bus_cfg = {.i3c = &p3t1755_data_##inst.i3c_dev},                                          \
	.ops = {                                                                                   \
		.read = p3t1755_i3c_read_reg,                                                      \
		.write = p3t1755_i3c_write_reg,                                                    \
		.get = p3t1755_i3c_get,                                                            \
		.put = p3t1755_i3c_put,                                                            \
	},                                                                                         \
	.inst_on_bus = P3T1755_BUS_I3C,                                                            \
	.i3c.bus = DEVICE_DT_GET(DT_INST_BUS(inst)), .i3c.dev_id = I3C_DEVICE_ID_DT_INST(inst),

#define P3T1755_INIT(n)                                                                            \
	static struct p3t1755_data p3t1755_data_##n;                                               \
	static const struct p3t1755_config p3t1755_config_##n = {                                  \
		COND_CODE_1(DT_INST_ON_BUS(n, i3c),                                                \
				(P3T1755_CONFIG_I3C(n)),                                           \
				(P3T1755_CONFIG_I2C(n)))                                           \
		.oneshot_mode = DT_INST_PROP(n, oneshot_mode),                                     \
	};                                                                                         \
                                                                                                   \
	PM_DEVICE_DT_INST_DEFINE(n, p3t1755_pm_hook);                                              \
	SENSOR_DEVICE_DT_INST_DEFINE(n, p3t1755_init, PM_DEVICE_DT_INST_GET(n), &p3t1755_data_##n, \
				     &p3t1755_config_##n, POST_KERNEL,                             \
				     CONFIG_SENSOR_INIT_PRIORITY, &p3t1755_driver_api);

DT_INST_FOREACH_STATUS_OKAY(P3T1755_INIT)
