/*
 * Copyright (c) 2020 TDK Invensense
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>

#include "icm42605.h"
#include "icm42605_reg.h"
#include "icm42605_spi.h"

LOG_MODULE_DECLARE(ICM42605, CONFIG_SENSOR_LOG_LEVEL);

int icm42605_set_fs(const struct device *dev, uint16_t a_sf, uint16_t g_sf)
{
	const struct icm42605_config *cfg = dev->config;
	uint8_t databuf;
	int result;

	result = inv_spi_read(&cfg->spi, REG_ACCEL_CONFIG0, &databuf, 1);
	if (result) {
		return result;
	}
	databuf &= ~BIT_ACCEL_FSR;

	databuf |= a_sf;

	result = inv_spi_single_write(&cfg->spi, REG_ACCEL_CONFIG0, &databuf);

	result = inv_spi_read(&cfg->spi, REG_GYRO_CONFIG0, &databuf, 1);

	if (result) {
		return result;
	}

	databuf &= ~BIT_GYRO_FSR;
	databuf |= g_sf;

	result = inv_spi_single_write(&cfg->spi, REG_GYRO_CONFIG0, &databuf);

	if (result) {
		return result;
	}

	return 0;
}

int icm42605_set_odr(const struct device *dev, uint16_t a_rate, uint16_t g_rate)
{
	const struct icm42605_config *cfg = dev->config;
	uint8_t databuf;
	int result;

	if (a_rate > 8000 || g_rate > 8000 ||
	    a_rate < 1 || g_rate < 12) {
		LOG_ERR("Not supported frequency");
		return -ENOTSUP;
	}

	result = inv_spi_read(&cfg->spi, REG_ACCEL_CONFIG0, &databuf, 1);

	if (result) {
		return result;
	}

	databuf &= ~BIT_ACCEL_ODR;

	if (a_rate > 4000) {
		databuf |= BIT_ACCEL_ODR_8000;
	} else if (a_rate > 2000) {
		databuf |= BIT_ACCEL_ODR_4000;
	} else if (a_rate > 1000) {
		databuf |= BIT_ACCEL_ODR_2000;
	} else if (a_rate > 500) {
		databuf |= BIT_ACCEL_ODR_1000;
	} else if (a_rate > 200) {
		databuf |= BIT_ACCEL_ODR_500;
	} else if (a_rate > 100) {
		databuf |= BIT_ACCEL_ODR_200;
	} else if (a_rate > 50) {
		databuf |= BIT_ACCEL_ODR_100;
	} else if (a_rate > 25) {
		databuf |= BIT_ACCEL_ODR_50;
	} else if (a_rate > 12) {
		databuf |= BIT_ACCEL_ODR_25;
	} else if (a_rate > 6) {
		databuf |= BIT_ACCEL_ODR_12;
	} else if (a_rate > 3) {
		databuf |= BIT_ACCEL_ODR_6;
	} else if (a_rate > 1) {
		databuf |= BIT_ACCEL_ODR_3;
	} else {
		databuf |= BIT_ACCEL_ODR_1;
	}

	result = inv_spi_single_write(&cfg->spi, REG_ACCEL_CONFIG0, &databuf);

	if (result) {
		return result;
	}

	LOG_DBG("Write Accel ODR 0x%X", databuf);

	result = inv_spi_read(&cfg->spi, REG_GYRO_CONFIG0, &databuf, 1);

	if (result) {
		return result;
	}

	databuf &= ~BIT_GYRO_ODR;

	if (g_rate > 4000) {
		databuf |= BIT_GYRO_ODR_8000;
	} else if (g_rate > 2000) {
		databuf |= BIT_GYRO_ODR_4000;
	} else if (g_rate > 1000) {
		databuf |= BIT_GYRO_ODR_2000;
	} else if (g_rate > 500) {
		databuf |= BIT_GYRO_ODR_1000;
	} else if (g_rate > 200) {
		databuf |= BIT_GYRO_ODR_500;
	} else if (g_rate > 100) {
		databuf |= BIT_GYRO_ODR_200;
	} else if (g_rate > 50) {
		databuf |= BIT_GYRO_ODR_100;
	} else if (g_rate > 25) {
		databuf |= BIT_GYRO_ODR_50;
	} else if (g_rate > 12) {
		databuf |= BIT_GYRO_ODR_25;
	} else {
		databuf |= BIT_GYRO_ODR_12;
	}

	LOG_DBG("Write GYRO ODR 0x%X", databuf);

	result = inv_spi_single_write(&cfg->spi, REG_GYRO_CONFIG0, &databuf);
	if (result) {
		return result;
	}

	return result;
}

int icm42605_sensor_init(const struct device *dev)
{
	const struct icm42605_config *cfg = dev->config;
	int result = 0;
	uint8_t v;

	result = inv_spi_read(&cfg->spi, REG_WHO_AM_I, &v, 1);

	if (result) {
		return result;
	}

	LOG_DBG("WHO AM I : 0x%X", v);

	result = inv_spi_read(&cfg->spi, REG_DEVICE_CONFIG, &v, 1);

	if (result) {
		LOG_DBG("read REG_DEVICE_CONFIG_REG failed");
		return result;
	}

	v |= BIT_SOFT_RESET;

	result = inv_spi_single_write(&cfg->spi, REG_DEVICE_CONFIG, &v);

	if (result) {
		LOG_ERR("write REG_DEVICE_CONFIG failed");
		return result;
	}

	/* Need at least 10ms after soft reset */
	k_msleep(10);

	v = BIT_GYRO_AFSR_MODE_HFS | BIT_ACCEL_AFSR_MODE_HFS | BIT_CLK_SEL_PLL;

	result = inv_spi_single_write(&cfg->spi, REG_INTF_CONFIG1, &v);

	if (result) {
		LOG_ERR("write REG_INTF_CONFIG1 failed");
		return result;
	}

	v = BIT_EN_DREG_FIFO_D2A |
	    BIT_TMST_TO_REGS_EN |
	    BIT_TMST_EN;

	result = inv_spi_single_write(&cfg->spi, REG_TMST_CONFIG, &v);

	if (result) {
		LOG_ERR("Write REG_TMST_CONFIG failed");
		return result;
	}

	result = inv_spi_read(&cfg->spi, REG_INTF_CONFIG0, &v, 1);

	if (result) {
		LOG_ERR("Read REG_INTF_CONFIG0 failed");
		return result;
	}

	LOG_DBG("Read REG_INTF_CONFIG0 0x%X", v);

	v |= BIT_UI_SIFS_DISABLE_I2C;

	result = inv_spi_single_write(&cfg->spi, REG_INTF_CONFIG0, &v);

	if (result) {
		LOG_ERR("Write REG_INTF_CONFIG failed");
		return result;
	}

	v = 0;
	result = inv_spi_single_write(&cfg->spi, REG_INT_CONFIG1, &v);

	if (result) {
		return result;
	}

	result = inv_spi_single_write(&cfg->spi, REG_PWR_MGMT0, &v);

	if (result) {
		return result;
	}

	return 0;
}

int icm42605_turn_on_fifo(const struct device *dev)
{
	const struct icm42605_data *drv_data = dev->data;
	const struct icm42605_config *cfg = dev->config;

	uint8_t int0_en = BIT_INT_UI_DRDY_INT1_EN;
	uint8_t fifo_en = BIT_FIFO_ACCEL_EN | BIT_FIFO_GYRO_EN | BIT_FIFO_WM_TH;
	uint8_t burst_read[3];
	int result;
	uint8_t v = 0;

	v = BIT_FIFO_MODE_BYPASS;
	result = inv_spi_single_write(&cfg->spi, REG_FIFO_CONFIG, &v);
	if (result) {
		return result;
	}

	v = 0;
	result = inv_spi_single_write(&cfg->spi, REG_FIFO_CONFIG1, &v);
	if (result) {
		return result;
	}

	result = inv_spi_read(&cfg->spi, REG_FIFO_COUNTH, burst_read, 2);
	if (result) {
		return result;
	}

	result = inv_spi_read(&cfg->spi, REG_FIFO_DATA, burst_read, 3);
	if (result) {
		return result;
	}

	v = BIT_FIFO_MODE_STREAM;
	result = inv_spi_single_write(&cfg->spi, REG_FIFO_CONFIG, &v);
	if (result) {
		return result;
	}

	result = inv_spi_single_write(&cfg->spi, REG_FIFO_CONFIG1, &fifo_en);
	if (result) {
		return result;
	}

	result = inv_spi_single_write(&cfg->spi, REG_INT_SOURCE0, &int0_en);
	if (result) {
		return result;
	}

	if (drv_data->tap_en) {
		v = BIT_TAP_ENABLE;
		result = inv_spi_single_write(&cfg->spi, REG_APEX_CONFIG0, &v);
		if (result) {
			return result;
		}

		v = BIT_DMP_INIT_EN;
		result = inv_spi_single_write(&cfg->spi, REG_SIGNAL_PATH_RESET, &v);
		if (result) {
			return result;
		}

		v = BIT_BANK_SEL_4;
		result = inv_spi_single_write(&cfg->spi, REG_BANK_SEL, &v);
		if (result) {
			return result;
		}

		v = BIT_INT_STATUS_TAP_DET;
		result = inv_spi_single_write(&cfg->spi, REG_INT_SOURCE6, &v);
		if (result) {
			return result;
		}

		v = BIT_BANK_SEL_0;
		result = inv_spi_single_write(&cfg->spi, REG_BANK_SEL, &v);
		if (result) {
			return result;
		}
	}

	LOG_DBG("turn on fifo done");
	return 0;
}

int icm42605_turn_off_fifo(const struct device *dev)
{
	const struct icm42605_data *drv_data = dev->data;
	const struct icm42605_config *cfg = dev->config;
	uint8_t int0_en = 0;
	uint8_t burst_read[3];
	int result;
	uint8_t v = 0;

	v = BIT_FIFO_MODE_BYPASS;
	result = inv_spi_single_write(&cfg->spi, REG_FIFO_CONFIG, &v);
	if (result) {
		return result;
	}

	v = 0;
	result = inv_spi_single_write(&cfg->spi, REG_FIFO_CONFIG1, &v);
	if (result) {
		return result;
	}

	result = inv_spi_read(&cfg->spi, REG_FIFO_COUNTH, burst_read, 2);
	if (result) {
		return result;
	}

	result = inv_spi_read(&cfg->spi, REG_FIFO_DATA, burst_read, 3);
	if (result) {
		return result;
	}

	result = inv_spi_single_write(&cfg->spi, REG_INT_SOURCE0, &int0_en);
	if (result) {
		return result;
	}

	if (drv_data->tap_en) {
		v = 0;
		result = inv_spi_single_write(&cfg->spi, REG_APEX_CONFIG0, &v);
		if (result) {
			return result;
		}

		result = inv_spi_single_write(&cfg->spi, REG_SIGNAL_PATH_RESET, &v);
		if (result) {
			return result;
		}

		v = BIT_BANK_SEL_4;
		result = inv_spi_single_write(&cfg->spi, REG_BANK_SEL, &v);
		if (result) {
			return result;
		}

		v = 0;
		result = inv_spi_single_write(&cfg->spi, REG_INT_SOURCE6, &v);
		if (result) {
			return result;
		}

		v = BIT_BANK_SEL_0;
		result = inv_spi_single_write(&cfg->spi, REG_BANK_SEL, &v);
		if (result) {
			return result;
		}
	}

	return 0;
}

int icm42605_turn_on_sensor(const struct device *dev)
{
	struct icm42605_data *drv_data = dev->data;
	const struct icm42605_config *cfg = dev->config;
	uint8_t v = 0;
	int result = 0;


	if (drv_data->sensor_started) {
		LOG_ERR("Sensor already started");
		return -EALREADY;
	}

	icm42605_set_fs(dev, drv_data->accel_sf, drv_data->gyro_sf);

	icm42605_set_odr(dev, drv_data->accel_hz, drv_data->gyro_hz);

	v |= BIT_ACCEL_MODE_LNM;
	v |= BIT_GYRO_MODE_LNM;

	result = inv_spi_single_write(&cfg->spi, REG_PWR_MGMT0, &v);
	if (result) {
		return result;
	}

	/* Accelerometer sensor need at least 10ms startup time
	 * Gyroscope sensor need at least 30ms startup time
	 */
	k_msleep(100);

	icm42605_turn_on_fifo(dev);

	drv_data->sensor_started = true;

	return 0;
}

int icm42605_turn_off_sensor(const struct device *dev)
{
	const struct icm42605_config *cfg = dev->config;
	uint8_t v = 0;
	int result = 0;

	result = inv_spi_read(&cfg->spi, REG_PWR_MGMT0, &v, 1);

	v ^= BIT_ACCEL_MODE_LNM;
	v ^= BIT_GYRO_MODE_LNM;

	result = inv_spi_single_write(&cfg->spi, REG_PWR_MGMT0, &v);
	if (result) {
		return result;
	}

	/* Accelerometer sensor need at least 10ms startup time
	 * Gyroscope sensor need at least 30ms startup time
	 */
	k_msleep(100);

	icm42605_turn_off_fifo(dev);

	return 0;
}
