/* Copyright 2016 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.
 */

/**
 * LSM6DSx (x is L/M/3) accelerometer and gyro module for Chrome EC
 * 3D digital accelerometer & 3D digital gyroscope
 * This driver supports both devices LSM6DSM and LSM6DSL
 */

#include "driver/accelgyro_lsm6dsm.h"
#include "driver/mag_lis2mdl.h"
#include "hooks.h"
#include "hwtimer.h"
#include "mag_cal.h"
#include "math_util.h"
#include "motion_sense_fifo.h"
#include "queue.h"
#include "task.h"
#include "timer.h"

#define CPUTS(outstr) cputs(CC_ACCEL, outstr)
#define CPRINTF(format, args...) cprintf(CC_ACCEL, format, ## args)
#define CPRINTS(format, args...) cprints(CC_ACCEL, format, ## args)

#define IS_FSTS_EMPTY(s) ((s).len & LSM6DSM_FIFO_EMPTY)

static volatile uint32_t last_interrupt_timestamp;

/**
 * Resets the lsm6dsm load fifo sensor states to the given timestamp. This
 * should be called at the start of the fifo read sequence.
 *
 * @param s Pointer to the first sensor in the lsm6dsm (accelerometer).
 * @param ts The timestamp to use for the interrupt timestamp.
 */
static void reset_load_fifo_sensor_state(struct motion_sensor_t *s, uint32_t ts)
{
	int i;
	struct lsm6dsm_data *data = LSM6DSM_GET_DATA(s);

	for (i = 0; i < FIFO_DEV_NUM; i++) {
		data->load_fifo_sensor_state[i].int_timestamp = ts;
		data->load_fifo_sensor_state[i].sample_count = 0;
	}
}

/**
 * Gets the dev_fifo enum value for a given sensor.
 *
 * @param s Pointer to the sensor in question.
 * @return the dev_fifo enum value corresponding to the sensor.
 */
static inline enum dev_fifo get_fifo_type(const struct motion_sensor_t *s)
{
	static enum dev_fifo map[] = {
		FIFO_DEV_ACCEL,
		FIFO_DEV_GYRO,
#ifdef CONFIG_LSM6DSM_SEC_I2C
		FIFO_DEV_MAG
#endif /* CONFIG_LSM6DSM_SEC_I2C */
	};
	return map[s->type];
}

/**
 * Gets the sensor type associated with the dev_fifo enum. This type can be used
 * to get the sensor number by using it as an offset from the first sensor in
 * the lsm6dsm (the accelerometer).
 *
 * @param fifo_type The dev_fifo enum in question.
 * @return the type of sensor represented by the fifo type.
 */
static inline uint8_t get_sensor_type(enum dev_fifo fifo_type)
{
	static uint8_t map[] = {
		MOTIONSENSE_TYPE_GYRO,
		MOTIONSENSE_TYPE_ACCEL,
		MOTIONSENSE_TYPE_MAG,
	};
	return map[fifo_type];
}

/**
 * @return output base register for sensor
 */
static inline int get_xyz_reg(enum motionsensor_type type)
{
	return LSM6DSM_ACCEL_OUT_X_L_ADDR -
		(LSM6DSM_ACCEL_OUT_X_L_ADDR - LSM6DSM_GYRO_OUT_X_L_ADDR) * type;
}

#ifdef CONFIG_ACCEL_INTERRUPTS
/**
 * Configure interrupt int 1 to fire handler for:
 *
 * FIFO threshold on watermark
 *
 * @accel: Motion sensor pointer to accelerometer.
 */
static int config_interrupt(const struct motion_sensor_t *accel)
{
	int ret = EC_SUCCESS;
	int int1_ctrl_val;

	ret = st_raw_read8(accel->port, accel->i2c_spi_addr_flags,
			   LSM6DSM_INT1_CTRL, &int1_ctrl_val);
	if (ret != EC_SUCCESS)
		return ret;

	if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
		/* As soon as one sample is ready, trigger an interrupt. */
		ret = st_raw_write8(accel->port, accel->i2c_spi_addr_flags,
				    LSM6DSM_FIFO_CTRL1_ADDR,
				    OUT_XYZ_SIZE / sizeof(uint16_t));
		if (ret != EC_SUCCESS)
			return ret;
		int1_ctrl_val |= LSM6DSM_INT_FIFO_TH | LSM6DSM_INT_FIFO_OVR |
			LSM6DSM_INT_FIFO_FULL;
	}

	return st_raw_write8(accel->port, accel->i2c_spi_addr_flags,
			     LSM6DSM_INT1_CTRL, int1_ctrl_val);
}


/**
 * fifo_disable - set fifo mode
 * @accel: Motion sensor pointer: must be MOTIONSENSE_TYPE_ACCEL.
 * @fmode: BYPASS or CONTINUOUS
 */
static int fifo_disable(const struct motion_sensor_t *accel)
{
	return st_raw_write8(accel->port, accel->i2c_spi_addr_flags,
			     LSM6DSM_FIFO_CTRL5_ADDR, 0x00);
}

/**
 * fifo_reset_pattern: called at each new FIFO pattern.
 */
static void fifo_reset_pattern(struct lsm6dsm_data *private)
{
	/* The fifo is ready to run. */
	memcpy(&private->current, &private->config,
	       sizeof(struct lsm6dsm_fifo_data));
	private->next_in_patten = FIFO_DEV_INVALID;
}

/**
 * fifo_enable - Configure internal FIFO parameters
 * @accel must be the accelerometer sensor.
 *
 * Configure FIFO decimators to have every time the right pattern
 * with acc/gyro
 */
static int fifo_enable(const struct motion_sensor_t *accel)
{
	const struct motion_sensor_t *s;
	int err, i, rate;
	uint8_t decimators[FIFO_DEV_NUM] = { 0 };
	unsigned int odrs[FIFO_DEV_NUM];
	unsigned int min_odr = LSM6DSM_ODR_MAX_VAL;
	unsigned int max_odr = 0;
	uint8_t odr_reg_val;
	struct lsm6dsm_data *private = LSM6DSM_GET_DATA(accel);
	/* In FIFO sensors are mapped in a different way. */
	uint8_t agm_maps[] = {
		MOTIONSENSE_TYPE_GYRO,
		MOTIONSENSE_TYPE_ACCEL,
		MOTIONSENSE_TYPE_MAG,
	};


	/* Search for min and max odr values for acc, gyro. */
	for (i = FIFO_DEV_GYRO; i < FIFO_DEV_NUM; i++) {
		/* Check if sensor enabled with ODR. */
		s = accel + agm_maps[i];
		rate = s->drv->get_data_rate(s);
		if (rate > 0) {
			min_odr = MIN(min_odr, rate);
			max_odr = MAX(max_odr, rate);
		}
		odrs[i] = rate;
	}

	if (max_odr == 0) {
		/* Leave FIFO disabled. */
		return EC_SUCCESS;
	}

	/* FIFO ODR must be set before the decimation factors */
	odr_reg_val = LSM6DSM_ODR_TO_REG(max_odr) <<
					LSM6DSM_FIFO_CTRL5_ODR_OFF;
	err = st_raw_write8(accel->port, accel->i2c_spi_addr_flags,
			    LSM6DSM_FIFO_CTRL5_ADDR, odr_reg_val);

	/* Scan all sensors configuration to calculate FIFO decimator. */
	private->config.total_samples_in_pattern = 0;
	for (i = FIFO_DEV_GYRO; i < FIFO_DEV_NUM; i++) {
		if (odrs[i] > 0) {
			private->config.samples_in_pattern[i] =
				odrs[i] / min_odr;
			decimators[i] =
				LSM6DSM_FIFO_DECIMATOR(max_odr / odrs[i]);
			private->config.total_samples_in_pattern +=
				private->config.samples_in_pattern[i];
		} else {
			/* Not in FIFO if sensor disabled. */
			private->config.samples_in_pattern[i] = 0;
		}
	}
	st_raw_write8(accel->port, accel->i2c_spi_addr_flags,
		      LSM6DSM_FIFO_CTRL3_ADDR,
		      (decimators[FIFO_DEV_GYRO] << LSM6DSM_FIFO_DEC_G_OFF) |
		      (decimators[FIFO_DEV_ACCEL] << LSM6DSM_FIFO_DEC_XL_OFF));
#ifdef CONFIG_LSM6DSM_SEC_I2C
	st_raw_write8(accel->port, accel->i2c_spi_addr_flags,
		      LSM6DSM_FIFO_CTRL4_ADDR,
		      decimators[FIFO_DEV_MAG]);

	/*
	 * FIFO ODR is limited by odr of gyro or accel.
	 * If we are sampling magnetometer faster than gyro or accel,
	 * bump up ODR of accel. Thanks to decimation we will still measure at
	 * the specified ODR.
	 * Contrary to gyroscope, sampling faster will not affect measurements.
	 * Set the ODR behind the back of set/get_data_rate.
	 *
	 * First samples after ODR changes must be thrown out [See
	 * AN4987, section 3.9].
	 * When increasing accel ODR, the FIFO is going to drop samples,
	 * - except the first one after ODR change.
	 * When decreasing accel ODR, we don't need to drop sample if
	 * frequency is less than 52Hz.
	 * At most, we need to drop one sample, but Android requirement specify
	 * that chaning one sensor ODR should not affect other sensors.
	 * Leave the bad sample alone, it will be a single glitch in the
	 * accelerometer data stream.
	 */
	if (max_odr > MAX(odrs[FIFO_DEV_ACCEL], odrs[FIFO_DEV_GYRO])) {
		st_write_data_with_mask(accel, LSM6DSM_ODR_REG(accel->type),
				LSM6DSM_ODR_MASK,
				LSM6DSM_ODR_TO_REG(max_odr));
	} else {
		st_write_data_with_mask(accel, LSM6DSM_ODR_REG(accel->type),
				LSM6DSM_ODR_MASK,
				LSM6DSM_ODR_TO_REG(odrs[FIFO_DEV_ACCEL]));
	}
#endif /* CONFIG_MAG_LSM6DSM_LIS2MDL */
	/*
	 * After ODR and decimation values are set, continuous mode can be
	 * enabled
	 */
	err = st_raw_write8(accel->port, accel->i2c_spi_addr_flags,
			    LSM6DSM_FIFO_CTRL5_ADDR,
			    odr_reg_val | LSM6DSM_FIFO_MODE_CONTINUOUS_VAL);
	if (err != EC_SUCCESS)
		return err;
	fifo_reset_pattern(private);
	return EC_SUCCESS;
}

/*
 * Must order FIFO read based on ODR:
 * For example Acc @ 52 Hz, Gyro @ 26 Hz Mag @ 13 Hz in FIFO we have
 * for each pattern this data samples:
 *  ________ _______ _______ _______ ________ _______ _______
 * | Gyro_0 | Acc_0 | Mag_0 | Acc_1 | Gyro_1 | Acc_2 | Acc_3 |
 * |________|_______|_______|_______|________|_______|_______|
 *
 * Total samples for each pattern: 2 Gyro, 4 Acc, 1 Mag.
 *
 * Returns dev_fifo enum value of next sample to process
 */
static int fifo_next(struct lsm6dsm_data *private)
{
	int next_id;

	if (private->current.total_samples_in_pattern == 0)
		fifo_reset_pattern(private);

	if (private->current.total_samples_in_pattern == 0) {
		/*
		 * Not expected we are supposed to be called to process FIFO
		 * data.
		 */
		CPRINTS("FIFO empty pattern");
		return FIFO_DEV_INVALID;
	}

	for (next_id = private->next_in_patten + 1; 1; next_id++) {
		if (next_id == FIFO_DEV_NUM)
			next_id = FIFO_DEV_GYRO;
		if (private->current.samples_in_pattern[next_id] != 0) {
			private->current.samples_in_pattern[next_id]--;
			private->current.total_samples_in_pattern--;
			private->next_in_patten = next_id;
			return next_id;
		}
	}
	/* Will never happen. */
	return FIFO_DEV_INVALID;
}

/**
 * push_fifo_data - Scan data pattern and push upside
 */
static void push_fifo_data(struct motion_sensor_t *accel, uint8_t *fifo,
			   uint16_t flen,
			   uint32_t timestamp)
{
	struct motion_sensor_t *s;
	struct lsm6dsm_data *private = LSM6DSM_GET_DATA(accel);

	while (flen > 0) {
		struct ec_response_motion_sensor_data vect;
		int id;
		int *axis;
		int next_fifo = fifo_next(private);
		/*
		 * This should never happen, but it could. There will be a
		 * report from inside fifo_next about it, so no extra message
		 * required here.
		 */
		if (next_fifo == FIFO_DEV_INVALID) {
			return;
		}

		id = get_sensor_type(next_fifo);
		if (private->samples_to_discard[id] > 0) {
			private->samples_to_discard[id]--;
		} else {
			s = accel + id;
			axis = s->raw_xyz;

			/* Apply precision, sensitivity and rotation. */
#ifdef CONFIG_MAG_LSM6DSM_LIS2MDL
			if (s->type == MOTIONSENSE_TYPE_MAG) {
				lis2mdl_normalize(s, axis, fifo);
				rotate(axis, *s->rot_standard_ref, axis);
			} else
#endif
			{
				st_normalize(s, axis, fifo);
			}


			vect.data[X] = axis[X];
			vect.data[Y] = axis[Y];
			vect.data[Z] = axis[Z];

			vect.flags = 0;
			vect.sensor_num = s - motion_sensors;
			motion_sense_fifo_stage_data(&vect, s, 3, timestamp);
		}

		fifo += OUT_XYZ_SIZE;
		flen -= OUT_XYZ_SIZE;
	}
}

static int load_fifo(struct motion_sensor_t *s, const struct fstatus *fsts,
		     uint32_t *last_fifo_read_ts)
{
	uint32_t interrupt_timestamp = last_interrupt_timestamp;
	int err, left, length;
	uint8_t fifo[FIFO_READ_LEN];

	/* Reset the load_fifo_sensor_state so we can start a new read. */
	reset_load_fifo_sensor_state(s, interrupt_timestamp);

	/*
	 * DIFF[11:0] are number of unread uint16 in FIFO
	 * mask DIFF and compute total byte len to read from FIFO.
	 */
	left = fsts->len & LSM6DSM_FIFO_DIFF_MASK;
	left *= sizeof(uint16_t);
	left = (left / OUT_XYZ_SIZE) * OUT_XYZ_SIZE;

	/*
	 * TODO(b/122912601): phaser360: Investigate Standard Deviation error
	 *				 during CtsSensorTests
	 * - check "pattern" register versus where code thinks it is parsing
	 */

	/* Push all data on upper side. */
	do {
		/* Fit len to pre-allocated static buffer. */
		if (left > FIFO_READ_LEN)
			length = FIFO_READ_LEN;
		else
			length = left;

		/* Read data and copy in buffer. */
		err = st_raw_read_n_noinc(s->port, s->i2c_spi_addr_flags,
					  LSM6DSM_FIFO_DATA_ADDR,
					  fifo, length);
		*last_fifo_read_ts = __hw_clock_source_read();
		if (err != EC_SUCCESS)
			return err;

		/*
		 * Manage patterns and push data. Data is pushed with the
		 * timestamp of the interrupt that got us into this function
		 * in the first place. This avoids a potential race condition
		 * where we empty the FIFO, and a new IRQ comes in between
		 * reading the last sample and pushing it into the FIFO.
		 */

		push_fifo_data(s, fifo, length, interrupt_timestamp);
		left -= length;
	} while (left > 0);

	motion_sense_fifo_commit_data();

	return EC_SUCCESS;
}

static int is_fifo_empty(struct motion_sensor_t *s, struct fstatus *fsts)
{
	int res;

	if (s->flags & MOTIONSENSE_FLAG_INT_SIGNAL)
		return gpio_get_level(s->int_signal);
	CPRINTS("Interrupt signal not set for %s", s->name);
	res = st_raw_read_n_noinc(s->port, s->i2c_spi_addr_flags,
				  LSM6DSM_FIFO_STS1_ADDR,
				  (int8_t *)fsts, sizeof(*fsts));
	/* If we failed to read the FIFO size assume empty. */
	if (res != EC_SUCCESS)
		return 1;
	return IS_FSTS_EMPTY(*fsts);
}

static void handle_interrupt_for_fifo(uint32_t ts)
{
	if (IS_ENABLED(CONFIG_ACCEL_FIFO) &&
	    time_after(ts, last_interrupt_timestamp))
		last_interrupt_timestamp = ts;
	task_set_event(TASK_ID_MOTIONSENSE,
		       CONFIG_ACCEL_LSM6DSM_INT_EVENT, 0);
}

/**
 * lsm6dsm_interrupt - interrupt from int1/2 pin of sensor
 */
void lsm6dsm_interrupt(enum gpio_signal signal)
{
	handle_interrupt_for_fifo(__hw_clock_source_read());
}

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

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

	if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
		struct fstatus fsts;
		uint32_t last_fifo_read_ts;
		uint32_t triggering_interrupt_timestamp =
			last_interrupt_timestamp;

		/* Read how many data pattern on FIFO to read and pattern. */
		ret = st_raw_read_n_noinc(s->port, s->i2c_spi_addr_flags,
					  LSM6DSM_FIFO_STS1_ADDR,
					  (uint8_t *)&fsts, sizeof(fsts));
		if (ret != EC_SUCCESS)
			return ret;
		last_fifo_read_ts = __hw_clock_source_read();
		if (fsts.len & (LSM6DSM_FIFO_DATA_OVR | LSM6DSM_FIFO_FULL))
			CPRINTS("%s FIFO Overrun: %04x", s->name, fsts.len);
		if (!IS_FSTS_EMPTY(fsts))
			ret = load_fifo(s, &fsts, &last_fifo_read_ts);

		/*
		 * Check if FIFO isn't empty and we never got an interrupt.
		 * This can happen if new entries were added to the FIFO after
		 * the count was read, but before the FIFO was cleared out.
		 * In the long term it might be better to use the last
		 * spread timestamp instead.
		 */
		if (!is_fifo_empty(s, &fsts) &&
		    triggering_interrupt_timestamp == last_interrupt_timestamp)
			handle_interrupt_for_fifo(last_fifo_read_ts);
	}

	return ret;
}
#endif /* CONFIG_ACCEL_INTERRUPTS */

/**
 * set_range - set full scale range
 * @s: Motion sensor pointer
 * @range: Range
 * @rnd: Round up/down flag
 * Note: Range is sensitivity/gain for speed purpose
 */
static int set_range(const struct motion_sensor_t *s, int range, int rnd)
{
	int err;
	uint8_t ctrl_reg, reg_val;
	struct stprivate_data *data = s->drv_data;
	int newrange = range;

	switch (s->type) {
	case MOTIONSENSE_TYPE_ACCEL:
		/* Adjust and check rounded value for acc. */
		if (rnd && (newrange < LSM6DSM_ACCEL_NORMALIZE_FS(newrange)))
			newrange *= 2;

		if (newrange > LSM6DSM_ACCEL_FS_MAX_VAL)
			newrange = LSM6DSM_ACCEL_FS_MAX_VAL;

		reg_val = LSM6DSM_ACCEL_FS_REG(newrange);
		break;
	case MOTIONSENSE_TYPE_GYRO:
		/* Adjust and check rounded value for gyro. */
		reg_val = LSM6DSM_GYRO_FS_REG(range);
		if (rnd && (range > LSM6DSM_GYRO_NORMALIZE_FS(reg_val)))
			reg_val++;

		if (reg_val > LSM6DSM_GYRO_FS_MAX_REG_VAL)
			reg_val = LSM6DSM_GYRO_FS_MAX_REG_VAL;
		newrange = LSM6DSM_GYRO_NORMALIZE_FS(reg_val);
		break;
	default:
		return EC_RES_INVALID_PARAM;
	}

	ctrl_reg = LSM6DSM_RANGE_REG(s->type);
	mutex_lock(s->mutex);
	err = st_write_data_with_mask(s, ctrl_reg, LSM6DSM_RANGE_MASK, reg_val);
	if (err == EC_SUCCESS)
		/* Save internally gain for speed optimization. */
		data->base.range = newrange;
	mutex_unlock(s->mutex);
	return err;
}

/**
 * get_range - get full scale range
 * @s: Motion sensor pointer
 *
 * For mag range is fixed to LIS2MDL_RANGE by hardware
 */
static int get_range(const struct motion_sensor_t *s)
{
	struct stprivate_data *data = s->drv_data;

	return data->base.range;
}

/**
 * lsm6dsm_set_data_rate
 * @s: Motion sensor pointer
 * @range: Rate (mHz)
 * @rnd: Round up/down flag
 *
 * For mag in cascade with lsm6dsm/l we use acc trigger and FIFO decimators
 */
int lsm6dsm_set_data_rate(const struct motion_sensor_t *s, int rate, int rnd)
{
	struct stprivate_data *data = s->drv_data;
	const struct motion_sensor_t *accel = IS_ENABLED(CONFIG_ACCEL_FIFO) ?
		LSM6DSM_MAIN_SENSOR(s) : NULL;
	struct lsm6dsm_data *private = IS_ENABLED(CONFIG_ACCEL_FIFO) ?
		LSM6DSM_GET_DATA(accel) : NULL;
	int ret = EC_SUCCESS, normalized_rate = 0;
	uint8_t ctrl_reg, reg_val = 0;

	if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
		/* FIFO must be disabled before setting any ODR values */
		ret = fifo_disable(accel);
		if (ret != EC_SUCCESS) {
			CPRINTS("Failed to disable FIFO. Error: %d", ret);
			return ret;
		}
	}

	if (rate > 0) {
		reg_val = LSM6DSM_ODR_TO_REG(rate);
		normalized_rate = LSM6DSM_REG_TO_ODR(reg_val);

		if (rnd && (normalized_rate < rate)) {
			reg_val++;
			normalized_rate = LSM6DSM_REG_TO_ODR(reg_val);
		}
		if (normalized_rate < LSM6DSM_ODR_MIN_VAL ||
		    normalized_rate > LSM6DSM_ODR_MAX_VAL)
			return EC_RES_INVALID_PARAM;
	}

#ifdef CONFIG_MAG_LSM6DSM_LIS2MDL
	/*
	 * TODO(b:110143516) Improve data rate selection:
	 * Sensor is always running at 100Hz, even when not used.
	 */
	if (s->type == MOTIONSENSE_TYPE_MAG) {
		struct mag_cal_t *cal = LIS2MDL_CAL(s);

		init_mag_cal(cal);
		/*
		 * Magnetometer ODR is calculating at 100Hz, but we are reading
		 * less often.
		 */
		if (normalized_rate > 0)
			cal->batch_size = MAX(
				MAG_CAL_MIN_BATCH_SIZE,
				(normalized_rate * 1000) /
					MAG_CAL_MIN_BATCH_WINDOW_US);
		else
			cal->batch_size = 0;
		CPRINTS("Batch size: %d", cal->batch_size);
		mutex_lock(s->mutex);
	} else
#endif
	{
		mutex_lock(s->mutex);
		ctrl_reg = LSM6DSM_ODR_REG(s->type);
		ret = st_write_data_with_mask(s, ctrl_reg, LSM6DSM_ODR_MASK,
					      reg_val);
	}
	if (ret == EC_SUCCESS) {
		data->base.odr = normalized_rate;
		if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
			private->samples_to_discard[s->type] =
				LSM6DSM_DISCARD_SAMPLES;
			private->load_fifo_sensor_state[get_fifo_type(s)]
				.sample_rate = normalized_rate == 0
					? 0 : SECOND * 1000 / normalized_rate;
			ret = fifo_enable(accel);
			if (ret != EC_SUCCESS)
				CPRINTS("Failed to enable FIFO. Error: %d",
					ret);
		}
	}

	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,
			   LSM6DSM_STATUS_REG, &tmp);
	if (ret != EC_SUCCESS) {
		CPRINTS("%s type:0x%X RS Error", s->name, s->type);
		return ret;
	}

	if (MOTIONSENSE_TYPE_ACCEL == s->type)
		*ready = (LSM6DSM_STS_XLDA_UP == (tmp & LSM6DSM_STS_XLDA_MASK));
	else
		*ready = (LSM6DSM_STS_GDA_UP == (tmp & LSM6DSM_STS_GDA_MASK));

	return EC_SUCCESS;
}

/*
 * Is not very efficient to collect the data in read: better have an interrupt
 * and collect the FIFO, even if it has one item: we don't have to check if the
 * sensor is ready (minimize I2C access).
 */
static int read(const struct motion_sensor_t *s, intv3_t v)
{
	uint8_t raw[OUT_XYZ_SIZE];
	uint8_t xyz_reg;
	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;
	}

	xyz_reg = get_xyz_reg(s->type);

	/* Read data bytes starting at xyz_reg. */
	ret = st_raw_read_n_noinc(s->port, s->i2c_spi_addr_flags,
				  xyz_reg, raw, OUT_XYZ_SIZE);
	if (ret != EC_SUCCESS)
		return ret;

	/* Apply precision, sensitivity and rotation vector. */
	st_normalize(s, v, raw);
	return EC_SUCCESS;
}

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

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

	if (tmp != LSM6DS3_WHO_AM_I && tmp != LSM6DSM_WHO_AM_I) {
		/* Unrecognized sensor */
		CPRINTS("Unknown WHO_AM_I value: 0x%x", tmp);
		return EC_ERROR_ACCESS_DENIED;
	}

	/*
	 * This sensor can be powered through an EC reboot, so the state of the
	 * sensor is unknown here so reset it
	 * LSM6DSM/L supports both accel & gyro features
	 * Board will see two virtual sensor devices: accel & gyro
	 * Requirement: Accel need be init before gyro and mag
	 */
	if (s->type == MOTIONSENSE_TYPE_ACCEL) {
		mutex_lock(s->mutex);

		/* Software reset procedure. */
		reg_val = LSM6DSM_ODR_TO_REG(LSM6DSM_ODR_MIN_VAL);
		ctrl_reg = LSM6DSM_ODR_REG(MOTIONSENSE_TYPE_ACCEL);

		/* Power OFF gyro. */
		ret = st_raw_write8(s->port, s->i2c_spi_addr_flags,
				    LSM6DSM_CTRL2_ADDR, 0);
		if (ret != EC_SUCCESS)
			goto err_unlock;

		/* Power ON Accel. */
		ret = st_raw_write8(s->port, s->i2c_spi_addr_flags,
				    ctrl_reg, reg_val);
		if (ret != EC_SUCCESS)
			goto err_unlock;

		/* Software reset. */
		ret = st_raw_write8(s->port, s->i2c_spi_addr_flags,
				    LSM6DSM_CTRL3_ADDR, LSM6DSM_SW_RESET);
		if (ret != EC_SUCCESS)
			goto err_unlock;

#ifdef CONFIG_LSM6DSM_SEC_I2C
		/*
		 * Reboot to reload memory content as pass-through mode can get
		 * stuck.
		 * Direct to the AN: See "AN4987 - LSM6DSM: always-on 3D
		 * accelerometer and 3D gyroscope".
		 */

		/* Power ON Accel. */
		ret = st_raw_write8(s->port, s->i2c_spi_addr_flags,
				    ctrl_reg, reg_val);
		if (ret != EC_SUCCESS)
			goto err_unlock;

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

		/*
		 * Refer to AN4987, wait 15ms for accelerometer to doing full
		 * reboot.
		 */
		msleep(15);

		/* Power OFF Accel. */
		ret = st_raw_write8(s->port, s->i2c_spi_addr_flags,
				    ctrl_reg, 0);
		if (ret != EC_SUCCESS)
			goto err_unlock;
#endif

		/*
		 * Output data not updated until have been read.
		 * Prefer interrupt to be active low.
		 */
		ret = st_raw_write8(s->port, s->i2c_spi_addr_flags,
				    LSM6DSM_CTRL3_ADDR,
				    LSM6DSM_BDU
				    | LSM6DSM_H_L_ACTIVE
				    | LSM6DSM_IF_INC);
		if (ret != EC_SUCCESS)
			goto err_unlock;

		if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
			ret = fifo_disable(s);
			if (ret != EC_SUCCESS)
				goto err_unlock;
		}

#ifdef CONFIG_ACCEL_INTERRUPTS
		ret = config_interrupt(s);
		if (ret != EC_SUCCESS)
			goto err_unlock;
#endif /* CONFIG_ACCEL_INTERRUPTS */

		mutex_unlock(s->mutex);
	}

	/* Set default resolution common to acc and gyro. */
	data->resol = LSM6DSM_RESOLUTION;
	return sensor_init_done(s);

err_unlock:
	mutex_unlock(s->mutex);
	CPRINTF("[%T %s: MS Init type:0x%X Error]\n", s->name, s->type);

	return ret;
}

const struct accelgyro_drv lsm6dsm_drv = {
	.init = init,
	.read = read,
	.set_range = set_range,
	.get_range = get_range,
	.get_resolution = st_get_resolution,
	.set_data_rate = lsm6dsm_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 = irq_handler,
#endif /* CONFIG_ACCEL_INTERRUPTS */
};
