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

/**
 * BMI160 accelerometer and gyro module for Chrome EC
 * 3D digital accelerometer & 3D digital gyroscope
 */

#include "accelgyro.h"
#include "common.h"
#include "console.h"
#include "accelgyro_bmi_common.h"
#include "accelgyro_bmi160.h"
#include "mag_bmm150.h"
#include "hwtimer.h"
#include "i2c.h"
#include "math_util.h"
#include "motion_orientation.h"
#include "motion_sense_fifo.h"
#include "spi.h"
#include "task.h"
#include "timer.h"
#include "util.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)

STATIC_IF(CONFIG_BMI_ORIENTATION_SENSOR) void irq_set_orientation(
				struct motion_sensor_t *s,
				int interrupt);

STATIC_IF(CONFIG_ACCEL_FIFO) volatile uint32_t last_interrupt_timestamp;

static int wakeup_time[] = {
	[MOTIONSENSE_TYPE_ACCEL] = 4,
	[MOTIONSENSE_TYPE_GYRO] = 80,
	[MOTIONSENSE_TYPE_MAG] = 1
};

/**
 * Control access to the compass on the secondary i2c interface:
 * enable values are:
 * 1: manual access, we can issue i2c to the compass
 * 0: data access: BMI160 gather data periodically from the compass.
 */
static __maybe_unused int bmi160_sec_access_ctrl(
		const int port,
		const uint16_t i2c_spi_addr_flags,
		const int enable)
{
	int mag_if_ctrl;
	bmi_read8(port, i2c_spi_addr_flags,
		  BMI160_MAG_IF_1, &mag_if_ctrl);
	if (enable) {
		mag_if_ctrl |= BMI160_MAG_MANUAL_EN;
		mag_if_ctrl &= ~BMI160_MAG_READ_BURST_MASK;
		mag_if_ctrl |= BMI160_MAG_READ_BURST_1;
	} else {
		mag_if_ctrl &= ~BMI160_MAG_MANUAL_EN;
		mag_if_ctrl &= ~BMI160_MAG_READ_BURST_MASK;
		mag_if_ctrl |= BMI160_MAG_READ_BURST_8;
	}
	return bmi_write8(port, i2c_spi_addr_flags,
			  BMI160_MAG_IF_1, mag_if_ctrl);
}

/**
 * Read register from compass.
 * Assuming we are in manual access mode, read compass i2c register.
 */
int bmi160_sec_raw_read8(const int port,
			 const uint16_t i2c_spi_addr_flags,
			 const uint8_t reg, int *data_ptr)
{
	/* Only read 1 bytes */
	bmi_write8(port, i2c_spi_addr_flags,
		   BMI160_MAG_I2C_READ_ADDR, reg);
	return bmi_read8(port, i2c_spi_addr_flags,
			 BMI160_MAG_I2C_READ_DATA, data_ptr);
}

/**
 * Write register from compass.
 * Assuming we are in manual access mode, write to compass i2c register.
 */
int bmi160_sec_raw_write8(const int port,
			  const uint16_t i2c_spi_addr_flags,
			  const uint8_t reg, int data)
{
	bmi_write8(port, i2c_spi_addr_flags,
		   BMI160_MAG_I2C_WRITE_DATA, data);
	return bmi_write8(port, i2c_spi_addr_flags,
			  BMI160_MAG_I2C_WRITE_ADDR, reg);
}

static int set_data_rate(const struct motion_sensor_t *s,
				int rate,
				int rnd)
{
	int ret, normalized_rate;
	uint8_t reg_val;
	struct accelgyro_saved_data_t *data = BMI_GET_SAVED_DATA(s);

	if (rate == 0) {
		/* FIFO stop collecting events */
		if (IS_ENABLED(CONFIG_ACCEL_FIFO))
			bmi_enable_fifo(s, 0);

		/* go to suspend mode */
		ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
				 BMI160_CMD_REG,
				 BMI160_CMD_MODE_SUSPEND(s->type));
		msleep(3);
		data->odr = 0;
		if (IS_ENABLED(CONFIG_MAG_BMI_BMM150) &&
		    (s->type == MOTIONSENSE_TYPE_MAG)) {
			struct mag_cal_t *moc = BMM150_CAL(s);

			moc->batch_size = 0;
		}

		return ret;
	} else if (data->odr == 0) {
		/* back from suspend mode. */
		ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
				 BMI160_CMD_REG,
				 BMI160_CMD_MODE_NORMAL(s->type));
		msleep(wakeup_time[s->type]);
	}

	ret = bmi_get_normalized_rate(s, rate, rnd, &normalized_rate, &reg_val);
	if (ret)
		return ret;

	/*
	 * Lock accel resource to prevent another task from attempting
	 * to write accel parameters until we are done.
	 */
	mutex_lock(s->mutex);

	ret = bmi_set_reg8(s, BMI_CONF_REG(s->type),
			   reg_val, BMI_ODR_MASK);
	if (ret != EC_SUCCESS)
		goto accel_cleanup;

	/* Now that we have set the odr, update the driver's value. */
	data->odr = normalized_rate;

	if (IS_ENABLED(CONFIG_MAG_BMI_BMM150) &&
	    (s->type == MOTIONSENSE_TYPE_MAG)) {
		struct mag_cal_t *moc = BMM150_CAL(s);

		/* Reset the calibration */
		init_mag_cal(moc);
		/*
		 * We need at least MIN_BATCH_SIZE amd we must have collected
		 * for at least MIN_BATCH_WINDOW_US.
		 * Given odr is in mHz, multiply by 1000x
		 */
		moc->batch_size = MAX(
			MAG_CAL_MIN_BATCH_SIZE,
			(data->odr * 1000) / (MAG_CAL_MIN_BATCH_WINDOW_US));
		CPRINTS("Batch size: %d", moc->batch_size);
	}

	/*
	 * FIFO start collecting events.
	 * They will be discarded if AP does not want them.
	 */
	if (IS_ENABLED(CONFIG_ACCEL_FIFO))
		bmi_enable_fifo(s, 1);

accel_cleanup:
	mutex_unlock(s->mutex);
	return ret;
}

static int set_offset(const struct motion_sensor_t *s,
			const int16_t *offset,
			int16_t    temp)
{
	int ret, val98;
	intv3_t v = { offset[X], offset[Y], offset[Z] };

	rotate_inv(v, *s->rot_standard_ref, v);

	ret = bmi_read8(s->port, s->i2c_spi_addr_flags,
			BMI160_OFFSET_EN_GYR98, &val98);
	if (ret != 0)
		return ret;

	switch (s->type) {
	case MOTIONSENSE_TYPE_ACCEL:
		ret = bmi_set_accel_offset(s, v);
		if (ret != EC_SUCCESS)
			return ret;

		ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
				 BMI160_OFFSET_EN_GYR98,
				 val98 | BMI160_OFFSET_ACC_EN);
		break;
	case MOTIONSENSE_TYPE_GYRO:
		ret = bmi_set_gyro_offset(s, v, &val98);
		if (ret != EC_SUCCESS)
			return ret;

		ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
				 BMI160_OFFSET_EN_GYR98,
				 val98 | BMI160_OFFSET_GYRO_EN);
		break;
#ifdef CONFIG_MAG_BMI_BMM150
	case MOTIONSENSE_TYPE_MAG:
		ret = bmm150_set_offset(s, v);
		break;
#endif /* defined(CONFIG_MAG_BMI_BMM150) */
	default:
		ret = EC_RES_INVALID_PARAM;
	}
	return ret;
}

static int perform_calib(struct motion_sensor_t *s, int enable)
{
	int ret, val, en_flag, status, rate, range = s->current_range;
	timestamp_t deadline, timeout;

	if (!enable)
		return EC_SUCCESS;

	/* We only support accelerometers and gyroscopes */
	if (s->type != MOTIONSENSE_TYPE_ACCEL &&
	    s->type != MOTIONSENSE_TYPE_GYRO)
		return EC_RES_INVALID_PARAM;

	rate = bmi_get_data_rate(s);
	/*
	 * Temporary set frequency to 100Hz to get enough data in a short
	 * period of time.
	 */
	ret = set_data_rate(s, 100000, 0);
	if (ret != EC_SUCCESS)
		goto end_perform_calib;

	switch (s->type) {
	case MOTIONSENSE_TYPE_ACCEL:
		/* We assume the device is laying flat for calibration */
		if (s->rot_standard_ref == NULL ||
		    (*s->rot_standard_ref)[2][2] > INT_TO_FP(0))
			val = BMI160_FOC_ACC_PLUS_1G;
		else
			val = BMI160_FOC_ACC_MINUS_1G;
		val = (BMI160_FOC_ACC_0G << BMI160_FOC_ACC_X_OFFSET) |
			(BMI160_FOC_ACC_0G << BMI160_FOC_ACC_Y_OFFSET) |
			(val << BMI160_FOC_ACC_Z_OFFSET);
		en_flag = BMI160_OFFSET_ACC_EN;
		/*
		 * Temporary set range to minimum to run calibration with
		 * full sensitivity
		 */
		bmi_set_range(s, 2, 0);
		/* Timeout for accelerometer calibration */
		timeout.val = 400 * MSEC;
		break;
	case MOTIONSENSE_TYPE_GYRO:
		val = BMI160_FOC_GYRO_EN;
		en_flag = BMI160_OFFSET_GYRO_EN;
		/*
		 * Temporary set range to minimum to run calibration with
		 * full sensitivity
		 */
		bmi_set_range(s, 125, 0);
		/* Timeout for gyroscope calibration */
		timeout.val = 800 * MSEC;
		break;
	/* LCOV_EXCL_START */
	default:
		/* Unreachable due to sensor type check above. */
		ASSERT(false);
		return EC_RES_INVALID_PARAM;
	/* LCOV_EXCL_STOP */
	}
	ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
			 BMI160_FOC_CONF, val);
	ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
			 BMI160_CMD_REG, BMI160_CMD_START_FOC);
	deadline.val = get_time().val + timeout.val;
	do {
		if (timestamp_expired(deadline, NULL)) {
			ret = EC_RES_TIMEOUT;
			goto end_perform_calib;
		}
		msleep(50);
		ret = bmi_read8(s->port, s->i2c_spi_addr_flags,
				BMI160_STATUS, &status);
		if (ret != EC_SUCCESS)
			goto end_perform_calib;
	} while ((status & BMI160_FOC_RDY) == 0);

	/* Calibration is successful, and loaded, use the result */
	ret = bmi_enable_reg8(s, BMI160_OFFSET_EN_GYR98, en_flag, 1);
end_perform_calib:
	bmi_set_range(s, range, 0);
	set_data_rate(s, rate, 0);
	return ret;
}

/*
 * Manage gesture recognition.
 * Defined even if host interface is not defined, to enable double tap even
 * when the host does not deal with gesture.
 */
#ifdef CONFIG_GESTURE_HOST_DETECTION
static int manage_activity(const struct motion_sensor_t *s,
			   enum motionsensor_activity activity,
			   int enable,
			   const struct ec_motion_sense_activity *param)
{
	int ret;
	struct bmi_drv_data_t *data = BMI_GET_DATA(s);

	switch (activity) {
#ifdef CONFIG_GESTURE_SIGMO
	case MOTIONSENSE_ACTIVITY_SIG_MOTION: {
		if (enable) {
			/* We should use parameters from caller */
			bmi_write8(s->port, s->i2c_spi_addr_flags,
				BMI160_INT_MOTION_3,
				BMI160_MOTION_PROOF_TIME(
					CONFIG_GESTURE_SIGMO_PROOF_MS) <<
				BMI160_MOTION_PROOF_OFF |
				BMI160_MOTION_SKIP_TIME(
					CONFIG_GESTURE_SIGMO_SKIP_MS) <<
				BMI160_MOTION_SKIP_OFF |
				BMI160_MOTION_SIG_MOT_SEL);
			bmi_write8(s->port, s->i2c_spi_addr_flags,
				BMI160_INT_MOTION_1,
				BMI160_MOTION_TH(s,
					CONFIG_GESTURE_SIGMO_THRES_MG));
		}
		ret = bmi_enable_reg8(s, BMI160_INT_EN_0,
				      BMI160_INT_ANYMO_X_EN |
				      BMI160_INT_ANYMO_Y_EN |
				      BMI160_INT_ANYMO_Z_EN,
				      enable);
		if (ret)
			ret = EC_RES_UNAVAILABLE;
		break;
	}
#endif
#ifdef CONFIG_GESTURE_SENSOR_DOUBLE_TAP
	case MOTIONSENSE_ACTIVITY_DOUBLE_TAP: {
		/* Set double tap interrupt */
		ret = bmi_enable_reg8(s, BMI160_INT_EN_0,
				      BMI160_INT_D_TAP_EN,
				      enable);
		if (ret)
			ret = EC_RES_UNAVAILABLE;
		break;
	}
#endif
	default:
		ret = EC_RES_INVALID_PARAM;
	}
	if (ret == EC_RES_SUCCESS) {
		if (enable) {
			data->enabled_activities |= 1 << activity;
			data->disabled_activities &= ~BIT(activity);
		} else {
			data->enabled_activities &= ~BIT(activity);
			data->disabled_activities |= 1 << activity;
		}
	}
	return ret;
}
#endif

/** Requires that the passed sensor `*s` is an accelerometer */
static __maybe_unused int
config_accel_interrupt(const struct motion_sensor_t *s)
{
	int ret, tmp;

	mutex_lock(s->mutex);
	bmi_write8(s->port, s->i2c_spi_addr_flags,
		   BMI160_CMD_REG, BMI160_CMD_FIFO_FLUSH);
	bmi_write8(s->port, s->i2c_spi_addr_flags,
		   BMI160_CMD_REG, BMI160_CMD_INT_RESET);

	if (IS_ENABLED(CONFIG_GESTURE_SENSOR_DOUBLE_TAP)) {
		bmi_write8(s->port, s->i2c_spi_addr_flags,
				BMI160_INT_TAP_0,
				BMI160_TAP_DUR(s, CONFIG_GESTURE_TAP_MAX_INTERSTICE_T));
		ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
				BMI160_INT_TAP_1,
				BMI160_TAP_TH(s, CONFIG_GESTURE_TAP_THRES_MG));
	}
	/* only use orientation sensor on the lid sensor */
	if (IS_ENABLED(CONFIG_BMI_ORIENTATION_SENSOR) &&
	    (s->location == MOTIONSENSE_LOC_LID)) {
		ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
				 BMI160_INT_ORIENT_0,
				 BMI160_INT_ORIENT_0_INIT_VAL);
		ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
				 BMI160_INT_ORIENT_1,
				 BMI160_INT_ORIENT_1_INIT_VAL);
	}

	if (IS_ENABLED(CONFIG_ACCELGYRO_BMI160_INT2_OUTPUT)) {
		ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
				BMI160_INT_LATCH, BMI160_LATCH_5MS);
	} else {
		/* Also, configure int2 as an external input. */
		ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
				BMI160_INT_LATCH,
				BMI160_INT2_INPUT_EN | BMI160_LATCH_5MS);
	}

	/* configure int1 as an interrupt */
	ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
			 BMI160_INT_OUT_CTRL,
			 BMI160_INT_CTRL(1, OUTPUT_EN));

	/* Map activity interrupt to int 1 */
	tmp = 0;
	if (IS_ENABLED(CONFIG_GESTURE_SIGMO)) {
		tmp |= BMI160_INT_ANYMOTION;
	} else if (IS_ENABLED(CONFIG_GESTURE_SENSOR_DOUBLE_TAP)) {
		tmp |= BMI160_INT_D_TAP;
	} else if (IS_ENABLED(CONFIG_BMI_ORIENTATION_SENSOR) &&
		   (s->location == MOTIONSENSE_LOC_LID)) {
		/* enable orientation interrupt for lid sensor only */
		tmp |= BMI160_INT_ORIENT;
	}
	ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
			 BMI160_INT_MAP_REG(1), tmp);

	if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
		/* map fifo water mark to int 1 */
		ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
				 BMI160_INT_FIFO_MAP,
				 BMI160_INT_MAP(1, FWM) |
				 BMI160_INT_MAP(1, FFULL));

		/*
		 * Configure fifo watermark to int whenever there's any data in
		 * there
		 */
		ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
				 BMI160_FIFO_CONFIG_0, 1);
		if (IS_ENABLED(CONFIG_ACCELGYRO_BMI160_INT2_OUTPUT))
			ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
					BMI160_FIFO_CONFIG_1,
					BMI160_FIFO_HEADER_EN);
		else
			ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
					BMI160_FIFO_CONFIG_1,
					BMI160_FIFO_TAG_INT2_EN |
					BMI160_FIFO_HEADER_EN);

		/* Set fifo*/
		bmi_enable_reg8(s, BMI160_INT_EN_1,
				BMI160_INT_FWM_EN | BMI160_INT_FFUL_EN, 1);
	}
	mutex_unlock(s->mutex);
	return ret;
}

#ifdef CONFIG_ACCEL_INTERRUPTS
#ifdef CONFIG_BMI_ORIENTATION_SENSOR
static void irq_set_orientation(struct motion_sensor_t *s,
				int interrupt)
{
	int shifted_masked_orientation =
		(interrupt >> 24) & BMI160_ORIENT_XY_MASK;
	if (BMI_GET_DATA(s)->raw_orientation != shifted_masked_orientation) {
		enum motionsensor_orientation orientation =
			MOTIONSENSE_ORIENTATION_UNKNOWN;

		BMI_GET_DATA(s)->raw_orientation =
			shifted_masked_orientation;

		switch (shifted_masked_orientation) {
		case BMI160_ORIENT_PORTRAIT:
			orientation = MOTIONSENSE_ORIENTATION_PORTRAIT;
			break;
		case BMI160_ORIENT_PORTRAIT_INVERT:
			orientation =
				MOTIONSENSE_ORIENTATION_UPSIDE_DOWN_PORTRAIT;
			break;
		case BMI160_ORIENT_LANDSCAPE:
			orientation = MOTIONSENSE_ORIENTATION_LANDSCAPE;
			break;
		case BMI160_ORIENT_LANDSCAPE_INVERT:
			orientation =
				MOTIONSENSE_ORIENTATION_UPSIDE_DOWN_LANDSCAPE;
			break;
		default:
			break;
		}
		orientation = motion_orientation_remap(s, orientation);
		*motion_orientation_ptr(s) = orientation;
	}
}
#endif  /* CONFIG_BMI_ORIENTATION_SENSOR */

/**
 * bmi160_interrupt - called when the sensor activates the interrupt line.
 *
 * This is a "top half" interrupt handler, it just asks motion sense ask
 * to schedule the "bottom half", ->irq_handler().
 */
void bmi160_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_ACCELGYRO_BMI160_INT_EVENT);
}

/**
 * irq_handler - bottom half of the interrupt stack.
 * Ran from the motion_sense task, finds the events that raised the interrupt.
 *
 * For now, we just print out. We should set a bitmask motion sense code will
 * act upon.
 */
static int irq_handler(struct motion_sensor_t *s,
				      uint32_t *event)
{
	uint32_t interrupt;
	int8_t has_read_fifo = 0;
	int rv;

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

	do {
		rv = bmi_read16(s->port, s->i2c_spi_addr_flags,
				BMI160_INT_STATUS_0, &interrupt);
		/*
		 * Bail out of this loop there was an error reading the register
		 */
		if (rv)
			return rv;

		if (IS_ENABLED(CONFIG_GESTURE_SENSOR_DOUBLE_TAP) &&
		    (interrupt & BMI160_D_TAP_INT))
			*event |= TASK_EVENT_MOTION_ACTIVITY_INTERRUPT(
					MOTIONSENSE_ACTIVITY_DOUBLE_TAP);
		if (IS_ENABLED(CONFIG_GESTURE_SIGMO) &&
		    (interrupt & BMI160_SIGMOT_INT))
			*event |= TASK_EVENT_MOTION_ACTIVITY_INTERRUPT(
					MOTIONSENSE_ACTIVITY_SIG_MOTION);
		if (IS_ENABLED(CONFIG_ACCEL_FIFO) &&
		    (interrupt & (BMI160_FWM_INT | BMI160_FFULL_INT))) {
			bmi_load_fifo(s, last_interrupt_timestamp);
			has_read_fifo = 1;
		}
		if (IS_ENABLED(CONFIG_BMI_ORIENTATION_SENSOR))
			irq_set_orientation(s, interrupt);
	} while (interrupt != 0);

	if (IS_ENABLED(CONFIG_ACCEL_FIFO) && has_read_fifo)
		motion_sense_fifo_commit_data();

	return EC_SUCCESS;
}
#endif  /* CONFIG_ACCEL_INTERRUPTS */

static int init(struct motion_sensor_t *s)
{
	int ret = 0, tmp, i;
	struct accelgyro_saved_data_t *saved_data = BMI_GET_SAVED_DATA(s);

	ret = bmi_read8(s->port, s->i2c_spi_addr_flags,
			BMI160_CHIP_ID, &tmp);
	if (ret)
		return EC_ERROR_UNKNOWN;

	if (tmp != BMI160_CHIP_ID_MAJOR && tmp != BMI168_CHIP_ID_MAJOR) {
		/* The device may be lock on paging mode. Try to unlock it. */
		bmi_write8(s->port, s->i2c_spi_addr_flags,
			   BMI160_CMD_REG, BMI160_CMD_EXT_MODE_EN_B0);
		bmi_write8(s->port, s->i2c_spi_addr_flags,
			   BMI160_CMD_REG, BMI160_CMD_EXT_MODE_EN_B1);
		bmi_write8(s->port, s->i2c_spi_addr_flags,
			   BMI160_CMD_REG, BMI160_CMD_EXT_MODE_EN_B2);
		bmi_write8(s->port, s->i2c_spi_addr_flags,
			   BMI160_CMD_EXT_MODE_ADDR, BMI160_CMD_PAGING_EN);
		bmi_write8(s->port, s->i2c_spi_addr_flags,
			   BMI160_CMD_EXT_MODE_ADDR, 0);
		return EC_ERROR_ACCESS_DENIED;
	}


	if (s->type == MOTIONSENSE_TYPE_ACCEL) {
		struct bmi_drv_data_t *data = BMI_GET_DATA(s);

		/* Reset the chip to be in a good state */
		bmi_write8(s->port, s->i2c_spi_addr_flags,
			   BMI160_CMD_REG, BMI160_CMD_SOFT_RESET);
		msleep(1);
		data->flags &= ~(BMI_FLAG_SEC_I2C_ENABLED |
				(BMI_FIFO_ALL_MASK <<
				 BMI_FIFO_FLAG_OFFSET));
		if (IS_ENABLED(CONFIG_GESTURE_HOST_DETECTION)) {
			data->enabled_activities = 0;
			data->disabled_activities = 0;
			if (IS_ENABLED(CONFIG_GESTURE_SIGMO))
				data->disabled_activities |=
					BIT(MOTIONSENSE_ACTIVITY_SIG_MOTION);
			if (IS_ENABLED(CONFIG_GESTURE_SENSOR_DOUBLE_TAP))
				data->disabled_activities |=
					BIT(MOTIONSENSE_ACTIVITY_DOUBLE_TAP);
		}
		/* To avoid gyro wakeup */
		bmi_write8(s->port, s->i2c_spi_addr_flags,
			   BMI160_PMU_TRIGGER, 0);
	}

#ifdef CONFIG_BMI_SEC_I2C
	if (s->type == MOTIONSENSE_TYPE_MAG) {
		struct bmi_drv_data_t *data = BMI_GET_DATA(s);

		/*
		 * To be able to configure the real magnetometer, we must set
		 * the BMI160 magnetometer part (a pass through) in normal mode.
		 */
		bmi_write8(s->port, s->i2c_spi_addr_flags,
			   BMI160_CMD_REG, BMI160_CMD_MODE_NORMAL(s->type));
		msleep(wakeup_time[s->type]);

		if ((data->flags & BMI_FLAG_SEC_I2C_ENABLED) == 0) {
			int ext_page_reg;
			/* Enable secondary interface */
			/*
			 * This is not part of the normal configuration but from
			 * code on Bosh github repo:
			 * https://github.com/BoschSensortec/BMI160_driver
			 *
			 * Magic command sequences
			 */
			bmi_write8(s->port, s->i2c_spi_addr_flags,
				   BMI160_CMD_REG, BMI160_CMD_EXT_MODE_EN_B0);
			bmi_write8(s->port, s->i2c_spi_addr_flags,
				   BMI160_CMD_REG, BMI160_CMD_EXT_MODE_EN_B1);
			bmi_write8(s->port, s->i2c_spi_addr_flags,
				   BMI160_CMD_REG, BMI160_CMD_EXT_MODE_EN_B2);

			/*
			 * Change the register page to target mode, to change
			 * the internal pull ups of the secondary interface.
			 */
			bmi_enable_reg8(s, BMI160_CMD_EXT_MODE_ADDR,
					BMI160_CMD_TARGET_PAGE, 1);
			bmi_enable_reg8(s, BMI160_CMD_EXT_MODE_ADDR,
					BMI160_CMD_PAGING_EN, 1);
			bmi_enable_reg8(s, BMI160_COM_C_TRIM_ADDR,
					BMI160_COM_C_TRIM, 1);
			bmi_enable_reg8(s, BMI160_CMD_EXT_MODE_ADDR,
					BMI160_CMD_TARGET_PAGE, 0);
			bmi_read8(s->port, s->i2c_spi_addr_flags,
				  BMI160_CMD_EXT_MODE_ADDR, &ext_page_reg);

			/* Set the i2c address of the compass */
			ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
					 BMI160_MAG_IF_0,
					 I2C_STRIP_FLAGS(
					     CONFIG_ACCELGYRO_SEC_ADDR_FLAGS)
					 << 1);

			/* Enable the secondary interface as I2C */
			ret = bmi_write8(s->port, s->i2c_spi_addr_flags,
					 BMI160_IF_CONF,
					 BMI160_IF_MODE_AUTO_I2C <<
					     BMI160_IF_MODE_OFF);
			data->flags |= BMI_FLAG_SEC_I2C_ENABLED;
		}


		bmi160_sec_access_ctrl(s->port, s->i2c_spi_addr_flags, 1);

		ret = bmm150_init(s);
		if (ret)
			/* Leave the compass open for tinkering. */
			return ret;

		/* Leave the address for reading the data */
		bmi_write8(s->port, s->i2c_spi_addr_flags,
			   BMI160_MAG_I2C_READ_ADDR, BMM150_BASE_DATA);
		/*
		 * Put back the secondary interface in normal mode.
		 * BMI160 will poll based on the configure ODR.
		 */
		bmi160_sec_access_ctrl(s->port, s->i2c_spi_addr_flags, 0);

		/*
		 * Clean interrupt event that may have occurred while the
		 * BMI160 was in management mode.
		 */
		task_set_event(TASK_ID_MOTIONSENSE,
			       CONFIG_ACCELGYRO_BMI160_INT_EVENT);
	}
#endif

	for (i = X; i <= Z; i++)
		saved_data->scale[i] = MOTION_SENSE_DEFAULT_SCALE;
	/*
	 * The sensor is in Suspend mode at init,
	 * so set data rate to 0.
	 */
	saved_data->odr = 0;

	if (IS_ENABLED(CONFIG_ACCEL_INTERRUPTS) &&
	    (s->type == MOTIONSENSE_TYPE_ACCEL))
		ret = config_accel_interrupt(s);

	return sensor_init_done(s);
}

const struct accelgyro_drv bmi160_drv = {
	.init = init,
	.read = bmi_read,
	.set_range = bmi_set_range,
	.get_resolution = bmi_get_resolution,
	.set_data_rate = set_data_rate,
	.get_data_rate = bmi_get_data_rate,
	.set_offset = set_offset,
	.get_scale = bmi_get_scale,
	.set_scale = bmi_set_scale,
	.get_offset = bmi_get_offset,
	.perform_calib = perform_calib,
	.read_temp = bmi_read_temp,
#ifdef CONFIG_ACCEL_INTERRUPTS
	.irq_handler = irq_handler,
#endif
#ifdef CONFIG_GESTURE_HOST_DETECTION
	.manage_activity = manage_activity,
	.list_activities = bmi_list_activities,
#endif
#ifdef CONFIG_BODY_DETECTION
	.get_rms_noise = bmi_get_rms_noise,
#endif
};

#ifdef CONFIG_CMD_I2C_STRESS_TEST_ACCEL
struct i2c_stress_test_dev bmi160_i2c_stress_test_dev = {
	.reg_info = {
		.read_reg = BMI160_CHIP_ID,
		.read_val = BMI160_CHIP_ID_MAJOR,
		.write_reg = BMI160_PMU_TRIGGER,
	},
	.i2c_read = &bmi_read8,
	.i2c_write = &bmi_write8,
};
#endif /* CONFIG_CMD_I2C_STRESS_TEST_ACCEL */

/*
 * TODO(chingkang): Replace bmi160_get_sensor_temp in some board config to
 *                  bmi_get_sensor_temp. Then, remove this definition.
 */
int bmi160_get_sensor_temp(int idx, int *temp_ptr)
{
	return bmi_get_sensor_temp(idx, temp_ptr);
}
