/* Copyright 2019 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/**
 * LIS2DS Accel module for Chrome EC
 * MEMS digital output motion sensor:
 * ultra-low-power high-performance 3-axis "pico" accelerometer
 *
 * For any details on driver implementation please
 * Refer to AN4748 Application Note on www.st.com
 */
#include "accelgyro.h"
#include "common.h"
#include "console.h"
#include "driver/accel_lis2ds.h"
#include "hooks.h"
#include "hwtimer.h"
#include "i2c.h"
#include "math_util.h"
#include "motion_sense_fifo.h"
#include "task.h"
#include "timer.h"
#include "util.h"

#define CPRINTS(format, args...) cprints(CC_ACCEL, format, ## args)

STATIC_IF(CONFIG_ACCEL_FIFO) volatile uint32_t last_interrupt_timestamp;

/**
 * lis2ds_enable_fifo - Enable/Disable FIFO in LIS2DS12
 * @s: Motion sensor pointer
 * @mode: fifo_modes
 */
static int lis2ds_enable_fifo(const struct motion_sensor_t *s, int mode)
{
	return st_write_data_with_mask(s, LIS2DS_FIFO_CTRL_ADDR,
				       LIS2DS_FIFO_MODE_MASK, mode);
}

/**
 * Load data from internal sensor FIFO
 * DIFF8 bits set means FIFO Full because 256 samples -> 0x100
 */
static int lis2ds_load_fifo(struct motion_sensor_t *s, uint16_t nsamples,
			    uint32_t saved_ts)
{
	int ret, read_len, fifo_len, chunk_len, i;
	struct ec_response_motion_sensor_data vect;
	int *axis = s->raw_xyz;
	uint8_t fifo[FIFO_READ_LEN];

	fifo_len = nsamples * OUT_XYZ_SIZE;
	read_len = 0;

	while (read_len < fifo_len) {
		chunk_len = GENERIC_MIN(fifo_len - read_len, sizeof(fifo));

		ret = st_raw_read_n_noinc(s->port, s->i2c_spi_addr_flags,
					  LIS2DS_OUT_X_L_ADDR, fifo, chunk_len);
		if (ret != EC_SUCCESS)
			return ret;

		for (i = 0; i < chunk_len; i += OUT_XYZ_SIZE) {
			/* Apply precision, sensitivity and rotation vector */
			st_normalize(s, axis, &fifo[i]);

			/* Fill vector array */
			vect.data[0] = axis[0];
			vect.data[1] = axis[1];
			vect.data[2] = axis[2];
			vect.flags = 0;
			vect.sensor_num = s - motion_sensors;
			motion_sense_fifo_stage_data(&vect, s, 3, saved_ts);
		}

		read_len += chunk_len;
	};

	if (read_len > 0)
		motion_sense_fifo_commit_data();

	return EC_SUCCESS;
}

__maybe_unused static int lis2ds_config_interrupt(const struct motion_sensor_t *s)
{
	int ret = EC_SUCCESS;

	/* Interrupt trigger level of power-on-reset is HIGH */
	if (!(s->flags & MOTIONSENSE_FLAG_INT_ACTIVE_HIGH)) {
		ret = st_write_data_with_mask(s, LIS2DS_H_ACTIVE_ADDR,
					      LIS2DS_H_ACTIVE_MASK, LIS2DS_EN_BIT);
		if (ret != EC_SUCCESS)
			return ret;
	}

	if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
		/*
		 * Configure FIFO threshold to 1 sample: interrupt on watermark
		 * will be generated every time a new data sample will be stored
		 * in FIFO. The interrupr on watermark is cleared only when the
		 * number or samples still present in FIFO exceeds the
		 * configured threshold.
		 */
		ret = st_raw_write8(s->port, s->i2c_spi_addr_flags,
				    LIS2DS_FIFO_THS_ADDR, 1);
		if (ret != EC_SUCCESS)
			return ret;

		/* Enable interrupt on FIFO watermark and route to int1. */
		ret = st_write_data_with_mask(s, LIS2DS_CTRL4_ADDR,
					      LIS2DS_INT1_FTH, LIS2DS_EN_BIT);
		if (ret != EC_SUCCESS)
			return ret;
	}

	return ret;
}

/**
 * lis2ds_interrupt - interrupt from int1 pin of sensor
 * Schedule Motion Sense Task to manage Interrupts
 */
void lis2ds_interrupt(enum gpio_signal signal)
{
	if (IS_ENABLED(CONFIG_ACCEL_FIFO))
		last_interrupt_timestamp = __hw_clock_source_read();

	task_set_event(TASK_ID_MOTIONSENSE, CONFIG_ACCEL_LIS2DS_INT_EVENT);
}

/**
 * lis2ds_irq_handler - bottom half of the interrupt stack.
 */
__maybe_unused static int lis2ds_irq_handler(struct motion_sensor_t *s,
					     uint32_t *event)
{
	int ret = EC_SUCCESS;

	if ((s->type != MOTIONSENSE_TYPE_ACCEL) ||
	    (!(*event & CONFIG_ACCEL_LIS2DS_INT_EVENT)))
		return EC_ERROR_NOT_HANDLED;

	if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
		uint16_t nsamples = 0;
		uint8_t fifo_src_samples[2];

		ret = st_raw_read_n_noinc(s->port,
					  s->i2c_spi_addr_flags,
					  LIS2DS_FIFO_SRC_ADDR,
					  (uint8_t *)fifo_src_samples,
					  sizeof(fifo_src_samples));
		if (ret != EC_SUCCESS)
			return ret;

		/* Check if FIFO is full. */
		if (fifo_src_samples[0] & LIS2DS_FIFO_OVR_MASK)
			CPRINTS("%s FIFO Overrun", s->name);

		/* DIFF8 = 1 FIFO FULL, 256 unread samples. */
		nsamples = fifo_src_samples[1] & LIS2DS_FIFO_DIFF_MASK;
		if (fifo_src_samples[0] & LIS2DS_FIFO_DIFF8_MASK)
			nsamples = 256;

		ret = lis2ds_load_fifo(s, nsamples, last_interrupt_timestamp);
	}

	return ret;
}

/**
 * set_range - set full scale range
 * @s: Motion sensor pointer
 * @range: Range
 * @rnd: Round up/down flag
 */
static int set_range(struct motion_sensor_t *s, int range, int rnd)
{
	int err;
	uint8_t reg_val;
	int newrange = ST_NORMALIZE_RATE(range);

	/* Adjust and check rounded value */
	if (rnd && (newrange < range))
		newrange <<= 1;

	if (newrange > LIS2DS_ACCEL_FS_MAX_VAL)
		newrange = LIS2DS_ACCEL_FS_MAX_VAL;
	else if (newrange < LIS2DS_ACCEL_FS_MIN_VAL)
		newrange = LIS2DS_ACCEL_FS_MIN_VAL;

	reg_val = LIS2DS_FS_REG(newrange);

	mutex_lock(s->mutex);
	err = st_write_data_with_mask(s, LIS2DS_FS_ADDR, LIS2DS_FS_MASK,
				      reg_val);
	if (err == EC_SUCCESS)
		/* Save internally gain for speed optimization. */
		s->current_range = newrange;
	mutex_unlock(s->mutex);

	return EC_SUCCESS;
}

static int set_data_rate(const struct motion_sensor_t *s, int rate, int rnd)
{
	int ret, normalized_rate = 0;
	struct stprivate_data *data = s->drv_data;
	uint8_t reg_val = 0;

	mutex_lock(s->mutex);
	if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
		/* FIFO stop collecting events. Restart FIFO in Bypass mode */
		ret = lis2ds_enable_fifo(s, LIS2DS_FIFO_BYPASS_MODE);
		if (ret != EC_SUCCESS) {
			CPRINTS("Failed to disable FIFO. Error: %d", ret);
			goto unlock_rate;
		}
	}

	/* Avoid LIS2DS_ODR_TO_REG to manage 0 mHz rate */
	if (rate > 0) {
		reg_val = LIS2DS_ODR_TO_REG(rate);
		normalized_rate = LIS2DS_REG_TO_ODR(reg_val);

		if (rnd && (normalized_rate < rate)) {
			reg_val++;
			normalized_rate = LIS2DS_REG_TO_ODR(rate);
		}

		if (normalized_rate < LIS2DS_ODR_MIN_VAL ||
		    normalized_rate > LIS2DS_ODR_MAX_VAL) {
			ret = EC_RES_INVALID_PARAM;
			goto unlock_rate;
		}
	}

	ret = st_write_data_with_mask(s, LIS2DS_ACC_ODR_ADDR,
				      LIS2DS_ACC_ODR_MASK, reg_val);
	if (ret == EC_SUCCESS) {
		data->base.odr = normalized_rate;

		if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
			/* FIFO restart collecting events in Cont. mode. */
			ret = lis2ds_enable_fifo(s, LIS2DS_FIFO_CONT_MODE);
			if (ret != EC_SUCCESS)
				CPRINTS("Failed to enable FIFO. Error: %d",
					ret);
		}
	}

unlock_rate:
	mutex_unlock(s->mutex);

	return ret;
}

static int is_data_ready(const struct motion_sensor_t *s, int *ready)
{
	int ret, tmp;

	ret = st_raw_read8(s->port, s->i2c_spi_addr_flags,
			   LIS2DS_STATUS_REG, &tmp);
	if (ret != EC_SUCCESS) {
		CPRINTS("%s: type:0x%X RD XYZ Error %d", s->name, s->type, ret);
		return ret;
	}

	*ready = (LIS2DS_STS_XLDA_UP == (tmp & LIS2DS_STS_XLDA_UP));

	return EC_SUCCESS;
}

static int read(const struct motion_sensor_t *s, intv3_t v)
{
	uint8_t raw[OUT_XYZ_SIZE];
	int ret, tmp = 0;

	ret = is_data_ready(s, &tmp);
	if (ret != EC_SUCCESS)
		return ret;

	/*
	 * If sensor data is not ready, return the previous read data.
	 * Note: return success so that motion senor task can read again
	 * to get the latest updated sensor data quickly.
	 */
	if (!tmp) {
		if (v != s->raw_xyz)
			memcpy(v, s->raw_xyz, sizeof(s->raw_xyz));
		return EC_SUCCESS;
	}

	/* Read 6 bytes starting at xyz_reg */
	ret = st_raw_read_n_noinc(s->port, s->i2c_spi_addr_flags,
				  LIS2DS_OUT_X_L_ADDR, raw,
				  LIS2DS_OUT_XYZ_SIZE);
	if (ret != EC_SUCCESS) {
		CPRINTS("%s: type:0x%X RD XYZ Error %d", s->name, s->type, ret);
		return ret;
	}

	/* Transform from LSB to real data with rotation and gain */
	st_normalize(s, v, raw);

	return EC_SUCCESS;
}

static int init(struct motion_sensor_t *s)
{
	int ret = 0, tmp;
	struct stprivate_data *data = s->drv_data;

	ret = st_raw_read8(s->port, s->i2c_spi_addr_flags,
			   LIS2DS_WHO_AM_I_REG, &tmp);
	if (ret != EC_SUCCESS)
		return EC_ERROR_UNKNOWN;

	if (tmp != LIS2DS_WHO_AM_I)
		return EC_ERROR_ACCESS_DENIED;

	/*
	 * This sensor can be powered through an EC reboot, so the state of
	 * the sensor is unknown here. Initiate software reset to restore
	 * sensor to default.
	 */
	mutex_lock(s->mutex);

	ret = st_raw_write8(s->port, s->i2c_spi_addr_flags,
			    LIS2DS_SOFT_RESET_ADDR, LIS2DS_SOFT_RESET_MASK);
	if (ret != EC_SUCCESS)
		goto err_unlock;

	msleep(20);

	/* Enable BDU */
	ret = st_write_data_with_mask(s, LIS2DS_BDU_ADDR, LIS2DS_BDU_MASK,
				      LIS2DS_EN_BIT);
	if (ret != EC_SUCCESS)
		goto err_unlock;

	ret = st_write_data_with_mask(s, LIS2DS_LIR_ADDR, LIS2DS_LIR_MASK,
				      LIS2DS_EN_BIT);
	if (ret != EC_SUCCESS)
		goto err_unlock;

	ret = st_write_data_with_mask(s, LIS2DS_INT2_ON_INT1_ADDR,
				      LIS2DS_INT2_ON_INT1_MASK, LIS2DS_EN_BIT);
	if (ret != EC_SUCCESS)
		goto err_unlock;

	if (IS_ENABLED(CONFIG_ACCEL_INTERRUPTS))
		ret = lis2ds_config_interrupt(s);
	if (ret != EC_SUCCESS)
		goto err_unlock;

	mutex_unlock(s->mutex);

	/* Set default resolution */
	data->resol = LIS2DS_RESOLUTION;

	return sensor_init_done(s);

err_unlock:
	mutex_unlock(s->mutex);
	CPRINTS("%s: MS Init type:0x%X Error", s->name, s->type);

	return ret;
}

const struct accelgyro_drv lis2ds_drv = {
	.init = init,
	.read = read,
	.set_range = set_range,
	.get_resolution = st_get_resolution,
	.set_data_rate = set_data_rate,
	.get_data_rate = st_get_data_rate,
	.set_offset = st_set_offset,
	.get_offset = st_get_offset,
#ifdef CONFIG_ACCEL_INTERRUPTS
	.irq_handler = lis2ds_irq_handler,
#endif /* CONFIG_ACCEL_INTERRUPTS */
};
