/*
 * Copyright (c) 2022, Hiroki Tada
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Datasheet:
 * https://datasheetspdf.com/pdf/1323325/Hamamatsu/S11059-02DT/1
 */

#define DT_DRV_COMPAT hamamatsu_s11059

#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>

LOG_MODULE_REGISTER(S11059, CONFIG_SENSOR_LOG_LEVEL);

/* register address */
#define S11059_REG_ADDR_CONTROL	      0x00
#define S11059_REG_ADDR_MANUAL_TIMING 0x01
#define S11059_REG_ADDR_DATA	      0x03

/* control bit */
#define S11059_CONTROL_GAIN	       3
#define S11059_CONTROL_STANDBY_MONITOR 5
#define S11059_CONTROL_STADBY	       6
#define S11059_CONTROL_ADC_RESET       7

/* bit mask for control */
#define S11059_BIT_MASK_INTEGRATION_TIME	0x03
#define S11059_BIT_MASK_CONTROL_STANDBY_MONITOR 0x20

/* factors for converting sensor samples to Lux */
#define S11059_CONVERT_FACTOR_LOW_RED	 (112)
#define S11059_CONVERT_FACTOR_LOW_GREEN	 (83)
#define S11059_CONVERT_FACTOR_LOW_BLUE	 (44)
#define S11059_CONVERT_FACTOR_LOW_IR	 (3 * 10)
#define S11059_CONVERT_FACTOR_HIGH_RED	 (117 * 10)
#define S11059_CONVERT_FACTOR_HIGH_GREEN (85 * 10)
#define S11059_CONVERT_FACTOR_HIGH_BLUE	 (448)
#define S11059_CONVERT_FACTOR_HIGH_IR	 (30 * 10)

#define S11059_INTEGRATION_TIME_MODE_00 175
#define S11059_INTEGRATION_TIME_MODE_01 2800
#define S11059_INTEGRATION_TIME_MODE_10 44800
#define S11059_INTEGRATION_TIME_MODE_11 358400

#define S11059_WAIT_PER_LOOP	 400
#define S11059_INITIAL_CONTROL	 0x04
#define S11059_MAX_MANUAL_TIMING UINT16_MAX
#define S11059_CARRY_UP		 10000

#define S11059_NUM_GAIN_MODE 2

enum s11059_channel {
	RED,
	GREEN,
	BLUE,
	IR,
	NUM_OF_COLOR_CHANNELS
};

struct s11059_dev_config {
	struct i2c_dt_spec bus;
	uint8_t gain;
	int64_t integration_time; /* integration period (unit: us) */
};

struct s11059_data {
	uint16_t samples[NUM_OF_COLOR_CHANNELS];
};

static const uint16_t convert_factors[S11059_NUM_GAIN_MODE][NUM_OF_COLOR_CHANNELS] = {
	{S11059_CONVERT_FACTOR_LOW_RED, S11059_CONVERT_FACTOR_LOW_GREEN,
	 S11059_CONVERT_FACTOR_LOW_BLUE, S11059_CONVERT_FACTOR_LOW_IR},
	{S11059_CONVERT_FACTOR_HIGH_RED, S11059_CONVERT_FACTOR_HIGH_GREEN,
	 S11059_CONVERT_FACTOR_HIGH_BLUE, S11059_CONVERT_FACTOR_HIGH_IR}};

/* Integration timing in Manual integration mode */
static const uint32_t integ_time_factor[] = {
	S11059_INTEGRATION_TIME_MODE_00, S11059_INTEGRATION_TIME_MODE_01,
	S11059_INTEGRATION_TIME_MODE_10, S11059_INTEGRATION_TIME_MODE_11};

static int s11059_convert_channel_to_index(enum sensor_channel chan)
{
	switch (chan) {
	case SENSOR_CHAN_RED:
		return RED;
	case SENSOR_CHAN_GREEN:
		return GREEN;
	case SENSOR_CHAN_BLUE:
		return BLUE;
	default:
		return IR;
	}
}

static int s11059_samples_read(const struct device *dev, uint8_t addr, uint16_t *val, uint32_t size)
{
	const struct s11059_dev_config *cfg = dev->config;
	int rc;

	if (size < NUM_OF_COLOR_CHANNELS * 2) {
		return -EINVAL;
	}

	rc = i2c_burst_read_dt(&cfg->bus, addr, (uint8_t *)val, size);
	if (rc < 0) {
		return rc;
	}

	for (size_t i = 0; i < NUM_OF_COLOR_CHANNELS; i++) {
		val[i] = sys_be16_to_cpu(val[i]);
	}

	return 0;
}

static int s11059_control_write(const struct device *dev, uint8_t control)
{
	const struct s11059_dev_config *cfg = dev->config;
	const uint8_t opcode[] = {S11059_REG_ADDR_CONTROL, control};

	return i2c_write_dt(&cfg->bus, opcode, sizeof(opcode));
}

static int s11059_manual_timing_write(const struct device *dev, uint16_t manual_time)
{
	const struct s11059_dev_config *cfg = dev->config;
	const uint8_t opcode[] = {S11059_REG_ADDR_MANUAL_TIMING, manual_time >> 8,
				  manual_time & 0xFF};

	return i2c_write_dt(&cfg->bus, opcode, sizeof(opcode));
}

static int s11059_start_measurement(const struct device *dev)
{
	const struct s11059_dev_config *cfg = dev->config;
	uint8_t control;
	int rc;

	/* read current control */
	rc = i2c_reg_read_byte_dt(&cfg->bus, S11059_REG_ADDR_CONTROL, &control);
	if (rc < 0) {
		LOG_ERR("%s, Failed to read current control.", dev->name);
		return rc;
	}

	/* reset adc block */
	WRITE_BIT(control, S11059_CONTROL_ADC_RESET, 1);
	WRITE_BIT(control, S11059_CONTROL_STADBY, 0);
	rc = s11059_control_write(dev, control);
	if (rc < 0) {
		LOG_ERR("%s, Failed to reset adc.", dev->name);
		return rc;
	}

	/* start device */
	WRITE_BIT(control, S11059_CONTROL_ADC_RESET, 0);
	rc = s11059_control_write(dev, control);
	if (rc < 0) {
		LOG_ERR("%s, Failed to start device.", dev->name);
		return rc;
	}

	return 0;
}

static int s11059_integ_time_calculate(const struct device *dev, uint16_t *manual_time,
				       uint8_t *mode)
{
	const struct s11059_dev_config *cfg = dev->config;
	int64_t tmp;

	if (cfg->integration_time < integ_time_factor[0]) {
		*mode = 0;
		*manual_time = 1;
	} else {
		*manual_time = S11059_MAX_MANUAL_TIMING;
		for (uint8_t i = 0; i < ARRAY_SIZE(integ_time_factor); i++) {
			*mode = i;
			tmp = cfg->integration_time / integ_time_factor[i];
			if (tmp < S11059_MAX_MANUAL_TIMING) {
				*manual_time = (uint16_t)tmp;
				break;
			}
		}
	}

	return 0;
}

static int s11059_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
	const struct s11059_dev_config *cfg = dev->config;
	struct s11059_data *drv_data = dev->data;
	uint16_t values[NUM_OF_COLOR_CHANNELS];
	uint8_t control;
	int rc;

	if (chan != SENSOR_CHAN_ALL) {
		LOG_ERR("%s, Unsupported sensor channel", dev->name);
		return -ENOTSUP;
	}

	rc = s11059_start_measurement(dev);
	if (rc < 0) {
		LOG_ERR("%s, Failed to start measurement.", dev->name);
		return rc;
	}

	do {
		rc = i2c_reg_read_byte_dt(&cfg->bus, S11059_REG_ADDR_CONTROL, &control);
		if (rc < 0) {
			LOG_ERR("%s, Failed to read control.", dev->name);
			return rc;
		}
		k_usleep(S11059_WAIT_PER_LOOP);

	} while (!(control & S11059_BIT_MASK_CONTROL_STANDBY_MONITOR));

	rc = s11059_samples_read(dev, S11059_REG_ADDR_DATA, values, sizeof(values));
	if (rc < 0) {
		LOG_ERR("%s, Failed to get sample.", dev->name);
		return rc;
	}

	for (size_t i = 0; i < NUM_OF_COLOR_CHANNELS; i++) {
		drv_data->samples[i] = values[i];
	}

	return 0;
}

static int s11059_channel_get(const struct device *dev, enum sensor_channel chan,
			      struct sensor_value *val)
{
	const struct s11059_dev_config *cfg = dev->config;
	struct s11059_data *drv_data = dev->data;
	const uint8_t index = s11059_convert_channel_to_index(chan);
	const uint16_t factor = convert_factors[cfg->gain][index];
	uint32_t meas_value;

	meas_value = drv_data->samples[index] * S11059_CARRY_UP / factor;
	val->val1 = meas_value / (S11059_CARRY_UP / 10);
	val->val2 = meas_value % (S11059_CARRY_UP / 10);

	return 0;
}

static int s11059_init(const struct device *dev)
{
	const struct s11059_dev_config *cfg = dev->config;
	uint8_t control = S11059_INITIAL_CONTROL;
	uint16_t manual_time;
	uint8_t timing_mode;
	int rc;

	/* device set */
	if (!i2c_is_ready_dt(&cfg->bus)) {
		LOG_ERR("%s, device is not ready.", dev->name);
		return -ENODEV;
	}

	rc = s11059_integ_time_calculate(dev, &manual_time, &timing_mode);
	if (rc < 0) {
		LOG_ERR("%s, Failed to calculate manual timing.", dev->name);
		return rc;
	}

	rc = s11059_manual_timing_write(dev, manual_time);
	if (rc < 0) {
		LOG_ERR("%s, Failed to set manual timing.", dev->name);
		return rc;
	}

	/* set integration time mode and gain*/
	control |= timing_mode & S11059_BIT_MASK_INTEGRATION_TIME;
	WRITE_BIT(control, S11059_CONTROL_GAIN, cfg->gain);
	rc = s11059_control_write(dev, control);
	if (rc < 0) {
		LOG_ERR("%s, Failed to set gain and integration time.", dev->name);
		return rc;
	}

	return 0;
}

static const struct sensor_driver_api s11059_driver_api = {
	.sample_fetch = s11059_sample_fetch,
	.channel_get = s11059_channel_get,
};

#define S11059_INST(inst)                                                                          \
	static struct s11059_data s11059_data_##inst;                                              \
	static const struct s11059_dev_config s11059_config_##inst = {                             \
		.bus = I2C_DT_SPEC_INST_GET(inst),                                                 \
		.gain = DT_INST_PROP(inst, high_gain),                                             \
		.integration_time = DT_INST_PROP(inst, integration_time)};                         \
	SENSOR_DEVICE_DT_INST_DEFINE(inst, s11059_init, NULL, &s11059_data_##inst,                 \
				     &s11059_config_##inst, POST_KERNEL,                           \
				     CONFIG_SENSOR_INIT_PRIORITY, &s11059_driver_api);

DT_INST_FOREACH_STATUS_OKAY(S11059_INST)
