1484 lines
39 KiB
C
1484 lines
39 KiB
C
/* 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 "driver/accelgyro_bmi160.h"
|
|
#include "driver/mag_bmm150.h"
|
|
#include "hooks.h"
|
|
#include "hwtimer.h"
|
|
#include "i2c.h"
|
|
#include "math_util.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_ACCEL_FIFO) volatile uint32_t last_interrupt_timestamp;
|
|
|
|
/*
|
|
* Struct for pairing an engineering value with the register value for a
|
|
* parameter.
|
|
*/
|
|
struct accel_param_pair {
|
|
int val; /* Value in engineering units. */
|
|
int reg_val; /* Corresponding register value. */
|
|
};
|
|
|
|
/* List of range values in +/-G's and their associated register values. */
|
|
static const struct accel_param_pair g_ranges[] = {
|
|
{2, BMI160_GSEL_2G},
|
|
{4, BMI160_GSEL_4G},
|
|
{8, BMI160_GSEL_8G},
|
|
{16, BMI160_GSEL_16G}
|
|
};
|
|
|
|
/*
|
|
* List of angular rate range values in +/-dps's
|
|
* and their associated register values.
|
|
*/
|
|
const struct accel_param_pair dps_ranges[] = {
|
|
{125, BMI160_DPS_SEL_125},
|
|
{250, BMI160_DPS_SEL_250},
|
|
{500, BMI160_DPS_SEL_500},
|
|
{1000, BMI160_DPS_SEL_1000},
|
|
{2000, BMI160_DPS_SEL_2000}
|
|
};
|
|
|
|
static int wakeup_time[] = {
|
|
[MOTIONSENSE_TYPE_ACCEL] = 4,
|
|
[MOTIONSENSE_TYPE_GYRO] = 80,
|
|
[MOTIONSENSE_TYPE_MAG] = 1
|
|
};
|
|
|
|
static inline const struct accel_param_pair *get_range_table(
|
|
enum motionsensor_type type, int *psize)
|
|
{
|
|
if (MOTIONSENSE_TYPE_ACCEL == type) {
|
|
if (psize)
|
|
*psize = ARRAY_SIZE(g_ranges);
|
|
return g_ranges;
|
|
} else {
|
|
if (psize)
|
|
*psize = ARRAY_SIZE(dps_ranges);
|
|
return dps_ranges;
|
|
}
|
|
}
|
|
|
|
static inline int get_xyz_reg(enum motionsensor_type type)
|
|
{
|
|
switch (type) {
|
|
case MOTIONSENSE_TYPE_ACCEL:
|
|
return BMI160_ACC_X_L_G;
|
|
case MOTIONSENSE_TYPE_GYRO:
|
|
return BMI160_GYR_X_L_G;
|
|
case MOTIONSENSE_TYPE_MAG:
|
|
return BMI160_MAG_X_L_G;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return reg value that matches the given engineering value passed in.
|
|
* The round_up flag is used to specify whether to round up or down.
|
|
* Note, this function always returns a valid reg value. If the request is
|
|
* outside the range of values, it returns the closest valid reg value.
|
|
*/
|
|
static int get_reg_val(const int eng_val, const int round_up,
|
|
const struct accel_param_pair *pairs, const int size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < size - 1; i++) {
|
|
if (eng_val <= pairs[i].val)
|
|
break;
|
|
|
|
if (eng_val < pairs[i+1].val) {
|
|
if (round_up)
|
|
i += 1;
|
|
break;
|
|
}
|
|
}
|
|
return pairs[i].reg_val;
|
|
}
|
|
|
|
/**
|
|
* @return engineering value that matches the given reg val
|
|
*/
|
|
static int get_engineering_val(const int reg_val,
|
|
const struct accel_param_pair *pairs, const int size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (reg_val == pairs[i].reg_val)
|
|
break;
|
|
}
|
|
return pairs[i].val;
|
|
}
|
|
|
|
#ifdef CONFIG_SPI_ACCEL_PORT
|
|
static inline int spi_raw_read(const int addr, const uint8_t reg,
|
|
uint8_t *data, const int len)
|
|
{
|
|
uint8_t cmd = 0x80 | reg;
|
|
|
|
return spi_transaction(&spi_devices[addr], &cmd, 1, data, len);
|
|
}
|
|
#endif
|
|
/**
|
|
* Read 8bit register from accelerometer.
|
|
*/
|
|
static int raw_read8(const int port,
|
|
const uint16_t i2c_spi_addr_flags,
|
|
const int reg, int *data_ptr)
|
|
{
|
|
int rv = -EC_ERROR_PARAM1;
|
|
|
|
if (SLAVE_IS_SPI(i2c_spi_addr_flags)) {
|
|
#ifdef CONFIG_SPI_ACCEL_PORT
|
|
uint8_t val;
|
|
rv = spi_raw_read(SLAVE_GET_SPI_ADDR(i2c_spi_addr_flags),
|
|
reg, &val, 1);
|
|
if (rv == EC_SUCCESS)
|
|
*data_ptr = val;
|
|
#endif
|
|
} else {
|
|
#ifdef I2C_PORT_ACCEL
|
|
rv = i2c_read8(port, i2c_spi_addr_flags,
|
|
reg, data_ptr);
|
|
#endif
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* Write 8bit register from accelerometer.
|
|
*/
|
|
static int raw_write8(const int port,
|
|
const uint16_t i2c_spi_addr_flags,
|
|
const int reg, int data)
|
|
{
|
|
int rv = -EC_ERROR_PARAM1;
|
|
|
|
if (SLAVE_IS_SPI(i2c_spi_addr_flags)) {
|
|
#ifdef CONFIG_SPI_ACCEL_PORT
|
|
uint8_t cmd[2] = { reg, data };
|
|
rv = spi_transaction(
|
|
&spi_devices[SLAVE_GET_SPI_ADDR(i2c_spi_addr_flags)],
|
|
cmd, 2, NULL, 0);
|
|
#endif
|
|
} else {
|
|
#ifdef I2C_PORT_ACCEL
|
|
rv = i2c_write8(port, i2c_spi_addr_flags,
|
|
reg, data);
|
|
#endif
|
|
}
|
|
/*
|
|
* From Bosch: BMI160 needs a delay of 450us after each write if it
|
|
* is in suspend mode, otherwise the operation may be ignored by
|
|
* the sensor. Given we are only doing write during init, add
|
|
* the delay inconditionally.
|
|
*/
|
|
msleep(1);
|
|
return rv;
|
|
}
|
|
|
|
#ifdef CONFIG_ACCEL_INTERRUPTS
|
|
/**
|
|
* Read 32bit register from accelerometer.
|
|
*/
|
|
static int raw_read32(const int port,
|
|
const uint16_t i2c_spi_addr_flags,
|
|
const uint8_t reg, int *data_ptr)
|
|
{
|
|
int rv = -EC_ERROR_PARAM1;
|
|
if (SLAVE_IS_SPI(i2c_spi_addr_flags)) {
|
|
#ifdef CONFIG_SPI_ACCEL_PORT
|
|
rv = spi_raw_read(SLAVE_GET_SPI_ADDR(i2c_spi_addr_flags),
|
|
reg, (uint8_t *)data_ptr, 4);
|
|
#endif
|
|
} else {
|
|
#ifdef I2C_PORT_ACCEL
|
|
rv = i2c_read32(port, i2c_spi_addr_flags,
|
|
reg, data_ptr);
|
|
#endif
|
|
}
|
|
return rv;
|
|
}
|
|
#endif /* defined(CONFIG_ACCEL_INTERRUPTS) */
|
|
|
|
/**
|
|
* Read n bytes from accelerometer.
|
|
*/
|
|
static int raw_read_n(const int port,
|
|
const uint16_t i2c_spi_addr_flags,
|
|
const uint8_t reg, uint8_t *data_ptr, const int len)
|
|
{
|
|
int rv = -EC_ERROR_PARAM1;
|
|
|
|
if (SLAVE_IS_SPI(i2c_spi_addr_flags)) {
|
|
#ifdef CONFIG_SPI_ACCEL_PORT
|
|
rv = spi_raw_read(SLAVE_GET_SPI_ADDR(i2c_spi_addr_flags),
|
|
reg, data_ptr, len);
|
|
#endif
|
|
} else {
|
|
#ifdef I2C_PORT_ACCEL
|
|
rv = i2c_read_block(port, i2c_spi_addr_flags,
|
|
reg, data_ptr, len);
|
|
#endif
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
#ifdef CONFIG_BMI160_SEC_I2C
|
|
/**
|
|
* 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 int bmi160_sec_access_ctrl(const int port,
|
|
const uint16_t i2c_spi_addr_flags,
|
|
const int enable)
|
|
{
|
|
int mag_if_ctrl;
|
|
raw_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 raw_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 */
|
|
raw_write8(port, i2c_spi_addr_flags,
|
|
BMI160_MAG_I2C_READ_ADDR, reg);
|
|
return raw_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)
|
|
{
|
|
raw_write8(port, i2c_spi_addr_flags,
|
|
BMI160_MAG_I2C_WRITE_DATA, data);
|
|
return raw_write8(port, i2c_spi_addr_flags,
|
|
BMI160_MAG_I2C_WRITE_ADDR, reg);
|
|
}
|
|
#endif
|
|
|
|
static int enable_fifo(const struct motion_sensor_t *s, int enable)
|
|
{
|
|
struct bmi160_drv_data_t *data = BMI160_GET_DATA(s);
|
|
int ret, val;
|
|
|
|
if (enable) {
|
|
/* FIFO start collecting events */
|
|
ret = raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_FIFO_CONFIG_1, &val);
|
|
val |= BMI160_FIFO_SENSOR_EN(s->type);
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_FIFO_CONFIG_1, val);
|
|
if (ret == EC_SUCCESS)
|
|
data->flags |= 1 << (s->type + BMI160_FIFO_FLAG_OFFSET);
|
|
|
|
} else {
|
|
/* FIFO stop collecting events */
|
|
ret = raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_FIFO_CONFIG_1, &val);
|
|
val &= ~BMI160_FIFO_SENSOR_EN(s->type);
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_FIFO_CONFIG_1, val);
|
|
if (ret == EC_SUCCESS)
|
|
data->flags &=
|
|
~(1 << (s->type + BMI160_FIFO_FLAG_OFFSET));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int set_range(const struct motion_sensor_t *s,
|
|
int range,
|
|
int rnd)
|
|
{
|
|
int ret, range_tbl_size;
|
|
uint8_t reg_val, ctrl_reg;
|
|
const struct accel_param_pair *ranges;
|
|
struct accelgyro_saved_data_t *data = BMI160_GET_SAVED_DATA(s);
|
|
|
|
if (s->type == MOTIONSENSE_TYPE_MAG) {
|
|
data->range = range;
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
ctrl_reg = BMI160_RANGE_REG(s->type);
|
|
ranges = get_range_table(s->type, &range_tbl_size);
|
|
reg_val = get_reg_val(range, rnd, ranges, range_tbl_size);
|
|
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
ctrl_reg, reg_val);
|
|
/* Now that we have set the range, update the driver's value. */
|
|
if (ret == EC_SUCCESS)
|
|
data->range = get_engineering_val(reg_val, ranges,
|
|
range_tbl_size);
|
|
return ret;
|
|
}
|
|
|
|
static int get_range(const struct motion_sensor_t *s)
|
|
{
|
|
struct accelgyro_saved_data_t *data = BMI160_GET_SAVED_DATA(s);
|
|
|
|
return data->range;
|
|
}
|
|
|
|
static int get_resolution(const struct motion_sensor_t *s)
|
|
{
|
|
return BMI160_RESOLUTION;
|
|
}
|
|
|
|
static int set_data_rate(const struct motion_sensor_t *s,
|
|
int rate,
|
|
int rnd)
|
|
{
|
|
int ret, val, normalized_rate;
|
|
uint8_t ctrl_reg, reg_val;
|
|
struct accelgyro_saved_data_t *data = BMI160_GET_SAVED_DATA(s);
|
|
#ifdef CONFIG_MAG_BMI160_BMM150
|
|
struct mag_cal_t *moc = BMM150_CAL(s);
|
|
#endif
|
|
|
|
if (rate == 0) {
|
|
/* FIFO stop collecting events */
|
|
if (IS_ENABLED(CONFIG_ACCEL_FIFO))
|
|
enable_fifo(s, 0);
|
|
|
|
/* go to suspend mode */
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_REG,
|
|
BMI160_CMD_MODE_SUSPEND(s->type));
|
|
msleep(3);
|
|
data->odr = 0;
|
|
#ifdef CONFIG_MAG_BMI160_BMM150
|
|
if (s->type == MOTIONSENSE_TYPE_MAG)
|
|
moc->batch_size = 0;
|
|
#endif
|
|
return ret;
|
|
} else if (data->odr == 0) {
|
|
/* back from suspend mode. */
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_REG,
|
|
BMI160_CMD_MODE_NORMAL(s->type));
|
|
msleep(wakeup_time[s->type]);
|
|
}
|
|
ctrl_reg = BMI160_CONF_REG(s->type);
|
|
reg_val = BMI160_ODR_TO_REG(rate);
|
|
normalized_rate = BMI160_REG_TO_ODR(reg_val);
|
|
if (rnd && (normalized_rate < rate)) {
|
|
reg_val++;
|
|
normalized_rate = BMI160_REG_TO_ODR(reg_val);
|
|
}
|
|
|
|
switch (s->type) {
|
|
case MOTIONSENSE_TYPE_ACCEL:
|
|
if (normalized_rate > BMI160_ACCEL_MAX_FREQ ||
|
|
normalized_rate < BMI160_ACCEL_MIN_FREQ)
|
|
return EC_RES_INVALID_PARAM;
|
|
break;
|
|
case MOTIONSENSE_TYPE_GYRO:
|
|
if (normalized_rate > BMI160_GYRO_MAX_FREQ ||
|
|
normalized_rate < BMI160_GYRO_MIN_FREQ)
|
|
return EC_RES_INVALID_PARAM;
|
|
break;
|
|
#ifdef CONFIG_MAG_BMI160_BMM150
|
|
case MOTIONSENSE_TYPE_MAG:
|
|
/* We use the regular preset we can go about 100Hz */
|
|
if (reg_val > BMI160_ODR_100HZ || reg_val < BMI160_ODR_0_78HZ)
|
|
return EC_RES_INVALID_PARAM;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
return EC_RES_INVALID_PARAM;
|
|
}
|
|
|
|
/*
|
|
* Lock accel resource to prevent another task from attempting
|
|
* to write accel parameters until we are done.
|
|
*/
|
|
mutex_lock(s->mutex);
|
|
|
|
ret = raw_read8(s->port, s->i2c_spi_addr_flags, ctrl_reg, &val);
|
|
if (ret != EC_SUCCESS)
|
|
goto accel_cleanup;
|
|
|
|
val = (val & ~BMI160_ODR_MASK) | reg_val;
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags, ctrl_reg, val);
|
|
if (ret != EC_SUCCESS)
|
|
goto accel_cleanup;
|
|
|
|
/* Now that we have set the odr, update the driver's value. */
|
|
data->odr = normalized_rate;
|
|
|
|
#ifdef CONFIG_MAG_BMI160_BMM150
|
|
if (s->type == MOTIONSENSE_TYPE_MAG) {
|
|
/* 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);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* FIFO start collecting events.
|
|
* They will be discarded if AP does not want them.
|
|
*/
|
|
if (IS_ENABLED(CONFIG_ACCEL_FIFO))
|
|
enable_fifo(s, 1);
|
|
|
|
accel_cleanup:
|
|
mutex_unlock(s->mutex);
|
|
return ret;
|
|
}
|
|
|
|
static int get_data_rate(const struct motion_sensor_t *s)
|
|
{
|
|
struct accelgyro_saved_data_t *data = BMI160_GET_SAVED_DATA(s);
|
|
|
|
return data->odr;
|
|
}
|
|
static int get_offset(const struct motion_sensor_t *s,
|
|
int16_t *offset,
|
|
int16_t *temp)
|
|
{
|
|
int i, val, val98;
|
|
intv3_t v;
|
|
|
|
switch (s->type) {
|
|
case MOTIONSENSE_TYPE_ACCEL:
|
|
/*
|
|
* The offset of the accelerometer off_acc_[xyz] is a 8 bit
|
|
* two-complement number in units of 3.9 mg independent of the
|
|
* range selected for the accelerometer.
|
|
*/
|
|
for (i = X; i <= Z; i++) {
|
|
raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_OFFSET_ACC70 + i, &val);
|
|
if (val > 0x7f)
|
|
val = -256 + val;
|
|
v[i] = val * BMI160_OFFSET_ACC_MULTI_MG /
|
|
BMI160_OFFSET_ACC_DIV_MG;
|
|
}
|
|
break;
|
|
case MOTIONSENSE_TYPE_GYRO:
|
|
/* Read the MSB first */
|
|
raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_OFFSET_EN_GYR98, &val98);
|
|
/*
|
|
* The offset of the gyroscope off_gyr_[xyz] is a 10 bit
|
|
* two-complement number in units of 0.061 °/s.
|
|
* Therefore a maximum range that can be compensated is
|
|
* -31.25 °/s to +31.25 °/s
|
|
*/
|
|
for (i = X; i <= Z; i++) {
|
|
raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_OFFSET_GYR70 + i, &val);
|
|
val |= ((val98 >> (2 * i)) & 0x3) << 8;
|
|
if (val > 0x1ff)
|
|
val = -1024 + val;
|
|
v[i] = val * BMI160_OFFSET_GYRO_MULTI_MDS /
|
|
BMI160_OFFSET_GYRO_DIV_MDS;
|
|
}
|
|
break;
|
|
#ifdef CONFIG_MAG_BMI160_BMM150
|
|
case MOTIONSENSE_TYPE_MAG:
|
|
bmm150_get_offset(s, v);
|
|
break;
|
|
#endif /* defined(CONFIG_MAG_BMI160_BMM150) */
|
|
default:
|
|
for (i = X; i <= Z; i++)
|
|
v[i] = 0;
|
|
}
|
|
rotate(v, *s->rot_standard_ref, v);
|
|
offset[X] = v[X];
|
|
offset[Y] = v[Y];
|
|
offset[Z] = v[Z];
|
|
/* Saving temperature at calibration not supported yet */
|
|
*temp = EC_MOTION_SENSE_INVALID_CALIB_TEMP;
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int set_offset(const struct motion_sensor_t *s,
|
|
const int16_t *offset,
|
|
int16_t temp)
|
|
{
|
|
int ret, i, val, val98;
|
|
intv3_t v = { offset[X], offset[Y], offset[Z] };
|
|
|
|
rotate_inv(v, *s->rot_standard_ref, v);
|
|
|
|
ret = raw_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:
|
|
for (i = X; i <= Z; i++) {
|
|
val = v[i] * BMI160_OFFSET_ACC_DIV_MG /
|
|
BMI160_OFFSET_ACC_MULTI_MG;
|
|
if (val > 127)
|
|
val = 127;
|
|
if (val < -128)
|
|
val = -128;
|
|
if (val < 0)
|
|
val = 256 + val;
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_OFFSET_ACC70 + i, val);
|
|
}
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_OFFSET_EN_GYR98,
|
|
val98 | BMI160_OFFSET_ACC_EN);
|
|
break;
|
|
case MOTIONSENSE_TYPE_GYRO:
|
|
for (i = X; i <= Z; i++) {
|
|
val = v[i] * BMI160_OFFSET_GYRO_DIV_MDS /
|
|
BMI160_OFFSET_GYRO_MULTI_MDS;
|
|
if (val > 511)
|
|
val = 511;
|
|
if (val < -512)
|
|
val = -512;
|
|
if (val < 0)
|
|
val = 1024 + val;
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_OFFSET_GYR70 + i, val & 0xFF);
|
|
val98 &= ~(0x3 << (2 * i));
|
|
val98 |= (val >> 8) << (2 * i);
|
|
}
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_OFFSET_EN_GYR98,
|
|
val98 | BMI160_OFFSET_GYRO_EN);
|
|
break;
|
|
#ifdef CONFIG_MAG_BMI160_BMM150
|
|
case MOTIONSENSE_TYPE_MAG:
|
|
ret = bmm150_set_offset(s, v);
|
|
break;
|
|
#endif /* defined(CONFIG_MAG_BMI160) */
|
|
default:
|
|
ret = EC_RES_INVALID_PARAM;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int set_scale(const struct motion_sensor_t *s,
|
|
const uint16_t *scale, int16_t temp)
|
|
{
|
|
struct accelgyro_saved_data_t *data = BMI160_GET_SAVED_DATA(s);
|
|
|
|
data->scale[X] = scale[X];
|
|
data->scale[Y] = scale[Y];
|
|
data->scale[Z] = scale[Z];
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int get_scale(const struct motion_sensor_t *s,
|
|
uint16_t *scale, int16_t *temp)
|
|
{
|
|
struct accelgyro_saved_data_t *data = BMI160_GET_SAVED_DATA(s);
|
|
|
|
scale[X] = data->scale[X];
|
|
scale[Y] = data->scale[Y];
|
|
scale[Z] = data->scale[Z];
|
|
*temp = EC_MOTION_SENSE_INVALID_CALIB_TEMP;
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int perform_calib(const struct motion_sensor_t *s, int enable)
|
|
{
|
|
int ret, val, en_flag, status, rate;
|
|
timestamp_t deadline;
|
|
|
|
if (!enable)
|
|
return EC_SUCCESS;
|
|
|
|
rate = get_data_rate(s);
|
|
/*
|
|
* Temporary set frequency to 100Hz to get enough data in a short
|
|
* period of time.
|
|
*/
|
|
set_data_rate(s, 100000, 0);
|
|
|
|
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;
|
|
break;
|
|
case MOTIONSENSE_TYPE_GYRO:
|
|
val = BMI160_FOC_GYRO_EN;
|
|
en_flag = BMI160_OFFSET_GYRO_EN;
|
|
break;
|
|
default:
|
|
/* Not supported on Magnetometer */
|
|
ret = EC_RES_INVALID_PARAM;
|
|
goto end_perform_calib;
|
|
}
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_FOC_CONF, val);
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_REG, BMI160_CMD_START_FOC);
|
|
deadline.val = get_time().val + 400 * MSEC;
|
|
do {
|
|
if (timestamp_expired(deadline, NULL)) {
|
|
ret = EC_RES_TIMEOUT;
|
|
goto end_perform_calib;
|
|
}
|
|
msleep(50);
|
|
ret = raw_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 = raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_OFFSET_EN_GYR98, &val);
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_OFFSET_EN_GYR98, val | en_flag);
|
|
end_perform_calib:
|
|
set_data_rate(s, rate, 0);
|
|
return ret;
|
|
}
|
|
|
|
void normalize(const struct motion_sensor_t *s, intv3_t v, uint8_t *input)
|
|
{
|
|
int i;
|
|
struct accelgyro_saved_data_t *data = BMI160_GET_SAVED_DATA(s);
|
|
|
|
#ifdef CONFIG_MAG_BMI160_BMM150
|
|
if (s->type == MOTIONSENSE_TYPE_MAG)
|
|
bmm150_normalize(s, v, input);
|
|
else
|
|
#endif
|
|
#ifdef CONFIG_MAG_BMI160_LIS2MDL
|
|
if (s->type == MOTIONSENSE_TYPE_MAG)
|
|
lis2mdl_normalize(s, v, data);
|
|
else
|
|
#endif
|
|
{
|
|
v[0] = ((int16_t)((input[1] << 8) | input[0]));
|
|
v[1] = ((int16_t)((input[3] << 8) | input[2]));
|
|
v[2] = ((int16_t)((input[5] << 8) | input[4]));
|
|
}
|
|
rotate(v, *s->rot_standard_ref, v);
|
|
for (i = X; i <= Z; i++)
|
|
v[i] = SENSOR_APPLY_SCALE(v[i], data->scale[i]);
|
|
}
|
|
|
|
/*
|
|
* Manage gesture recognition.
|
|
* Defined even if host interface is not defined, to enable double tap even
|
|
* when the host does not deal with gesture.
|
|
*/
|
|
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 bmi160_drv_data_t *data = BMI160_GET_DATA(s);
|
|
|
|
switch (activity) {
|
|
#ifdef CONFIG_GESTURE_SIGMO
|
|
case MOTIONSENSE_ACTIVITY_SIG_MOTION: {
|
|
int tmp;
|
|
ret = raw_read8(s->port, s->addr, BMI160_INT_EN_0, &tmp);
|
|
if (ret)
|
|
return ret;
|
|
if (enable) {
|
|
/* We should use parameters from caller */
|
|
raw_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);
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_INT_MOTION_1,
|
|
BMI160_MOTION_TH(s,
|
|
CONFIG_GESTURE_SIGMO_THRES_MG));
|
|
tmp |= BMI160_INT_ANYMO_X_EN |
|
|
BMI160_INT_ANYMO_Y_EN |
|
|
BMI160_INT_ANYMO_Z_EN;
|
|
} else {
|
|
tmp &= ~(BMI160_INT_ANYMO_X_EN |
|
|
BMI160_INT_ANYMO_Y_EN |
|
|
BMI160_INT_ANYMO_Z_EN);
|
|
}
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_INT_EN_0, tmp);
|
|
if (ret)
|
|
ret = EC_RES_UNAVAILABLE;
|
|
break;
|
|
}
|
|
#endif
|
|
#ifdef CONFIG_GESTURE_SENSOR_BATTERY_TAP
|
|
case MOTIONSENSE_ACTIVITY_DOUBLE_TAP: {
|
|
int tmp;
|
|
/* Set double tap interrupt */
|
|
ret = raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_INT_EN_0, &tmp);
|
|
if (ret)
|
|
return ret;
|
|
if (enable)
|
|
tmp |= BMI160_INT_D_TAP_EN;
|
|
else
|
|
tmp &= ~BMI160_INT_D_TAP_EN;
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_INT_EN_0, tmp);
|
|
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;
|
|
}
|
|
|
|
#ifdef CONFIG_GESTURE_HOST_DETECTION
|
|
int list_activities(const struct motion_sensor_t *s,
|
|
uint32_t *enabled,
|
|
uint32_t *disabled)
|
|
{
|
|
struct bmi160_drv_data_t *data = BMI160_GET_DATA(s);
|
|
*enabled = data->enabled_activities;
|
|
*disabled = data->disabled_activities;
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_ACCEL_INTERRUPTS
|
|
|
|
enum fifo_state {
|
|
FIFO_HEADER,
|
|
FIFO_DATA_SKIP,
|
|
FIFO_DATA_TIME,
|
|
FIFO_DATA_CONFIG,
|
|
};
|
|
|
|
|
|
#define BMI160_FIFO_BUFFER 64
|
|
static uint8_t bmi160_buffer[BMI160_FIFO_BUFFER];
|
|
/*
|
|
* Decode the header from the fifo.
|
|
* Return 0 if we need further processing.
|
|
* Sensor mutex must be held during processing, to protect the fifos.
|
|
*
|
|
* @s: base sensor
|
|
* @hdr: the header to decode
|
|
* @bp: current pointer in the buffer, updated when processing the header.
|
|
* @ep: pointer to the end of the valid data in the buffer.
|
|
*/
|
|
static int bmi160_decode_header(struct motion_sensor_t *accel,
|
|
enum fifo_header hdr, uint32_t last_ts,
|
|
uint8_t **bp, uint8_t *ep)
|
|
{
|
|
if ((hdr & BMI160_FH_MODE_MASK) == BMI160_EMPTY &&
|
|
(hdr & BMI160_FH_PARM_MASK) != 0) {
|
|
int i, size = 0;
|
|
/* Check if there is enough space for the data frame */
|
|
for (i = MOTIONSENSE_TYPE_MAG; i >= MOTIONSENSE_TYPE_ACCEL;
|
|
i--) {
|
|
if (hdr & (1 << (i + BMI160_FH_PARM_OFFSET)))
|
|
size += (i == MOTIONSENSE_TYPE_MAG ? 8 : 6);
|
|
}
|
|
if (*bp + size > ep) {
|
|
/* frame is not complete, it will be retransmitted. */
|
|
*bp = ep;
|
|
return 1;
|
|
}
|
|
for (i = MOTIONSENSE_TYPE_MAG; i >= MOTIONSENSE_TYPE_ACCEL;
|
|
i--) {
|
|
struct motion_sensor_t *s = accel + i;
|
|
|
|
if (hdr & (1 << (i + BMI160_FH_PARM_OFFSET))) {
|
|
struct ec_response_motion_sensor_data vector;
|
|
int *v = s->raw_xyz;
|
|
vector.flags = 0;
|
|
normalize(s, v, *bp);
|
|
if (IS_ENABLED(CONFIG_ACCEL_SPOOF_MODE) &&
|
|
s->flags & MOTIONSENSE_FLAG_IN_SPOOF_MODE)
|
|
v = s->spoof_xyz;
|
|
vector.data[X] = v[X];
|
|
vector.data[Y] = v[Y];
|
|
vector.data[Z] = v[Z];
|
|
vector.sensor_num = s - motion_sensors;
|
|
motion_sense_fifo_stage_data(&vector, s, 3,
|
|
last_ts);
|
|
*bp += (i == MOTIONSENSE_TYPE_MAG ? 8 : 6);
|
|
}
|
|
}
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve hardware FIFO from sensor,
|
|
* - put data in Sensor Hub fifo.
|
|
* - update sensor raw_xyz vector with the last information.
|
|
* We put raw data in hub fifo and process data from there.
|
|
* @s Pointer to sensor data.
|
|
*
|
|
* Read only up to bmi160_buffer. If more reads are needed, we will be called
|
|
* again by the interrupt routine.
|
|
*
|
|
* NOTE: If a new driver supports this function, be sure to add a check
|
|
* for spoof_mode in order to load the sensor stack with the spoofed
|
|
* data. See accelgyro_bmi160.c::load_fifo for an example.
|
|
*/
|
|
static int load_fifo(struct motion_sensor_t *s, uint32_t last_ts)
|
|
{
|
|
struct bmi160_drv_data_t *data = BMI160_GET_DATA(s);
|
|
uint16_t length;
|
|
enum fifo_state state = FIFO_HEADER;
|
|
uint8_t *bp = bmi160_buffer;
|
|
uint8_t *ep;
|
|
uint32_t beginning;
|
|
|
|
|
|
if (s->type != MOTIONSENSE_TYPE_ACCEL)
|
|
return EC_SUCCESS;
|
|
|
|
if (!(data->flags &
|
|
(BMI160_FIFO_ALL_MASK << BMI160_FIFO_FLAG_OFFSET))) {
|
|
/*
|
|
* The FIFO was disabled while we were processing it.
|
|
*
|
|
* Flush potential left over:
|
|
* When sensor is resumed, we won't read old data.
|
|
*/
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_REG, BMI160_CMD_FIFO_FLUSH);
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
raw_read_n(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_FIFO_LENGTH_0,
|
|
(uint8_t *)&length, sizeof(length));
|
|
length &= BMI160_FIFO_LENGTH_MASK;
|
|
|
|
/*
|
|
* We have not requested timestamp, no extra frame to read.
|
|
* if we have too much to read, read the whole buffer.
|
|
*/
|
|
if (length == 0) {
|
|
CPRINTS("unexpected empty FIFO");
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/* Add one byte to get an empty FIFO frame.*/
|
|
length++;
|
|
|
|
if (length > sizeof(bmi160_buffer))
|
|
CPRINTS("unexpected large FIFO: %d", length);
|
|
length = MIN(length, sizeof(bmi160_buffer));
|
|
|
|
|
|
raw_read_n(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_FIFO_DATA, bmi160_buffer, length);
|
|
beginning = *(uint32_t *)bmi160_buffer;
|
|
ep = bmi160_buffer + length;
|
|
/*
|
|
* FIFO is invalid when reading while the sensors are all
|
|
* suspended.
|
|
* Instead of returning the empty frame, it can return a
|
|
* pattern that looks like a valid header: 84 or 40.
|
|
* If we see those, assume the sensors have been disabled
|
|
* while this thread was running.
|
|
*/
|
|
if (beginning == 0x84848484 ||
|
|
(beginning & 0xdcdcdcdc) == 0x40404040) {
|
|
CPRINTS("Suspended FIFO: accel ODR/rate: %d/%d: 0x%08x",
|
|
BASE_ODR(s->config[SENSOR_CONFIG_AP].odr),
|
|
get_data_rate(s),
|
|
beginning);
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
while (bp < ep) {
|
|
switch (state) {
|
|
case FIFO_HEADER: {
|
|
enum fifo_header hdr = *bp++;
|
|
|
|
if (bmi160_decode_header(s, hdr, last_ts, &bp, ep))
|
|
continue;
|
|
/* Other cases */
|
|
hdr &= 0xdc;
|
|
switch (hdr) {
|
|
case BMI160_EMPTY:
|
|
return EC_SUCCESS;
|
|
case BMI160_SKIP:
|
|
state = FIFO_DATA_SKIP;
|
|
break;
|
|
case BMI160_TIME:
|
|
state = FIFO_DATA_TIME;
|
|
break;
|
|
case BMI160_CONFIG:
|
|
state = FIFO_DATA_CONFIG;
|
|
break;
|
|
default:
|
|
CPRINTS("Unknown header: 0x%02x @ %d",
|
|
hdr, bp - bmi160_buffer);
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_REG,
|
|
BMI160_CMD_FIFO_FLUSH);
|
|
return EC_ERROR_NOT_HANDLED;
|
|
}
|
|
break;
|
|
}
|
|
case FIFO_DATA_SKIP:
|
|
CPRINTS("@ %d - %d, skipped %d frames",
|
|
bp - bmi160_buffer, length, *bp);
|
|
bp++;
|
|
state = FIFO_HEADER;
|
|
break;
|
|
case FIFO_DATA_CONFIG:
|
|
CPRINTS("@ %d - %d, config change: 0x%02x",
|
|
bp - bmi160_buffer, length, *bp);
|
|
bp++;
|
|
state = FIFO_HEADER;
|
|
break;
|
|
case FIFO_DATA_TIME:
|
|
if (bp + 3 > ep) {
|
|
bp = ep;
|
|
continue;
|
|
}
|
|
/* We are not requesting timestamp */
|
|
CPRINTS("timestamp %d", (bp[2] << 16) |
|
|
(bp[1] << 8) | bp[0]);
|
|
state = FIFO_HEADER;
|
|
bp += 3;
|
|
break;
|
|
default:
|
|
CPRINTS("Unknown data: 0x%02x", *bp++);
|
|
state = FIFO_HEADER;
|
|
}
|
|
}
|
|
motion_sense_fifo_commit_data();
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* 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, 0);
|
|
}
|
|
|
|
|
|
static int config_interrupt(const struct motion_sensor_t *s)
|
|
{
|
|
int ret, tmp;
|
|
|
|
if (s->type != MOTIONSENSE_TYPE_ACCEL)
|
|
return EC_SUCCESS;
|
|
|
|
mutex_lock(s->mutex);
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_REG, BMI160_CMD_FIFO_FLUSH);
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_REG, BMI160_CMD_INT_RESET);
|
|
|
|
#ifdef CONFIG_GESTURE_SENSOR_BATTERY_TAP
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_INT_TAP_0,
|
|
BMI160_TAP_DUR(s, CONFIG_GESTURE_TAP_MAX_INTERSTICE_T));
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_INT_TAP_1,
|
|
BMI160_TAP_TH(s, CONFIG_GESTURE_TAP_THRES_MG));
|
|
#endif
|
|
#ifdef CONFIG_BMI160_ORIENTATION_SENSOR
|
|
/* only use orientation sensor on the lid sensor */
|
|
if (s->location == MOTIONSENSE_LOC_LID) {
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_INT_ORIENT_0,
|
|
BMI160_INT_ORIENT_0_INIT_VAL);
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_INT_ORIENT_1,
|
|
BMI160_INT_ORIENT_1_INIT_VAL);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_ACCELGYRO_BMI160_INT2_OUTPUT
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_INT_LATCH, BMI160_LATCH_5MS);
|
|
#else
|
|
/* Also, configure int2 as an external input. */
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_INT_LATCH,
|
|
BMI160_INT2_INPUT_EN | BMI160_LATCH_5MS);
|
|
#endif
|
|
|
|
/* configure int1 as an interrupt */
|
|
ret = raw_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;
|
|
#ifdef CONFIG_GESTURE_SIGMO
|
|
tmp |= BMI160_INT_ANYMOTION;
|
|
#endif
|
|
#ifdef CONFIG_GESTURE_SENSOR_BATTERY_TAP
|
|
tmp |= BMI160_INT_D_TAP;
|
|
#endif
|
|
#ifdef CONFIG_BMI160_ORIENTATION_SENSOR
|
|
/* enable orientation interrupt for lid sensor only */
|
|
if (s->location == MOTIONSENSE_LOC_LID)
|
|
tmp |= BMI160_INT_ORIENT;
|
|
#endif
|
|
ret = raw_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 = raw_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 = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_FIFO_CONFIG_0, 1);
|
|
#ifdef CONFIG_ACCELGYRO_BMI160_INT2_OUTPUT
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_FIFO_CONFIG_1,
|
|
BMI160_FIFO_HEADER_EN);
|
|
#else
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_FIFO_CONFIG_1,
|
|
BMI160_FIFO_TAG_INT2_EN |
|
|
BMI160_FIFO_HEADER_EN);
|
|
#endif
|
|
|
|
/* Set fifo*/
|
|
ret = raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_INT_EN_1, &tmp);
|
|
tmp |= BMI160_INT_FWM_EN | BMI160_INT_FFUL_EN;
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_INT_EN_1, tmp);
|
|
}
|
|
mutex_unlock(s->mutex);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_BMI160_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 (BMI160_GET_DATA(s)->raw_orientation != shifted_masked_orientation) {
|
|
enum motionsensor_orientation orientation =
|
|
MOTIONSENSE_ORIENTATION_UNKNOWN;
|
|
|
|
BMI160_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_sense_remap_orientation(s, orientation);
|
|
SET_ORIENTATION(s, orientation);
|
|
}
|
|
}
|
|
#endif
|
|
/**
|
|
* 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;
|
|
int rv;
|
|
|
|
if ((s->type != MOTIONSENSE_TYPE_ACCEL) ||
|
|
(!(*event & CONFIG_ACCELGYRO_BMI160_INT_EVENT)))
|
|
return EC_ERROR_NOT_HANDLED;
|
|
|
|
do {
|
|
rv = raw_read32(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;
|
|
|
|
#ifdef CONFIG_GESTURE_SENSOR_BATTERY_TAP
|
|
if (interrupt & BMI160_D_TAP_INT)
|
|
*event |= TASK_EVENT_MOTION_ACTIVITY_INTERRUPT(
|
|
MOTIONSENSE_ACTIVITY_DOUBLE_TAP);
|
|
#endif
|
|
#ifdef CONFIG_GESTURE_SIGMO
|
|
if (interrupt & BMI160_SIGMOT_INT)
|
|
*event |= TASK_EVENT_MOTION_ACTIVITY_INTERRUPT(
|
|
MOTIONSENSE_ACTIVITY_SIG_MOTION);
|
|
#endif
|
|
if (IS_ENABLED(CONFIG_ACCEL_FIFO) &&
|
|
interrupt & (BMI160_FWM_INT | BMI160_FFULL_INT))
|
|
load_fifo(s, last_interrupt_timestamp);
|
|
#ifdef CONFIG_BMI160_ORIENTATION_SENSOR
|
|
irq_set_orientation(s, interrupt);
|
|
#endif
|
|
} while (interrupt != 0);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
#endif /* CONFIG_ACCEL_INTERRUPTS */
|
|
|
|
|
|
static int read(const struct motion_sensor_t *s, intv3_t v)
|
|
{
|
|
uint8_t data[6];
|
|
int ret, status = 0;
|
|
|
|
ret = raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_STATUS, &status);
|
|
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 (!(status & BMI160_DRDY_MASK(s->type))) {
|
|
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 = raw_read_n(s->port, s->i2c_spi_addr_flags,
|
|
get_xyz_reg(s->type), data, 6);
|
|
|
|
if (ret != EC_SUCCESS) {
|
|
CPRINTS("%s: type:0x%X RD XYZ Error %d", s->name, s->type, ret);
|
|
return ret;
|
|
}
|
|
normalize(s, v, data);
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int init(const struct motion_sensor_t *s)
|
|
{
|
|
int ret = 0, tmp, i;
|
|
struct accelgyro_saved_data_t *saved_data = BMI160_GET_SAVED_DATA(s);
|
|
|
|
ret = raw_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. */
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_REG, BMI160_CMD_EXT_MODE_EN_B0);
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_REG, BMI160_CMD_EXT_MODE_EN_B1);
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_REG, BMI160_CMD_EXT_MODE_EN_B2);
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_EXT_MODE_ADDR, BMI160_CMD_PAGING_EN);
|
|
raw_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 bmi160_drv_data_t *data = BMI160_GET_DATA(s);
|
|
|
|
/* Reset the chip to be in a good state */
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_REG, BMI160_CMD_SOFT_RESET);
|
|
msleep(1);
|
|
data->flags &= ~(BMI160_FLAG_SEC_I2C_ENABLED |
|
|
(BMI160_FIFO_ALL_MASK <<
|
|
BMI160_FIFO_FLAG_OFFSET));
|
|
#ifdef CONFIG_GESTURE_HOST_DETECTION
|
|
data->enabled_activities = 0;
|
|
data->disabled_activities = 0;
|
|
#ifdef CONFIG_GESTURE_SIGMO
|
|
data->disabled_activities |=
|
|
1 << MOTIONSENSE_ACTIVITY_SIG_MOTION;
|
|
#endif
|
|
#ifdef CONFIG_GESTURE_SENSOR_BATTERY_TAP
|
|
data->disabled_activities |=
|
|
1 << MOTIONSENSE_ACTIVITY_DOUBLE_TAP;
|
|
#endif
|
|
#endif
|
|
/* To avoid gyro wakeup */
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_PMU_TRIGGER, 0);
|
|
}
|
|
|
|
#ifdef CONFIG_BMI160_SEC_I2C
|
|
if (s->type == MOTIONSENSE_TYPE_MAG) {
|
|
struct bmi160_drv_data_t *data = BMI160_GET_DATA(s);
|
|
|
|
/*
|
|
* To be able to configure the real magnetometer, we must set
|
|
* the BMI160 magnetometer part (a pass through) in normal mode.
|
|
*/
|
|
raw_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 & BMI160_FLAG_SEC_I2C_ENABLED) == 0) {
|
|
int ext_page_reg, pullup_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
|
|
*/
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_REG, BMI160_CMD_EXT_MODE_EN_B0);
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_REG, BMI160_CMD_EXT_MODE_EN_B1);
|
|
raw_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.
|
|
*/
|
|
raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_EXT_MODE_ADDR, &ext_page_reg);
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_EXT_MODE_ADDR,
|
|
ext_page_reg | BMI160_CMD_TARGET_PAGE);
|
|
raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_EXT_MODE_ADDR, &ext_page_reg);
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_EXT_MODE_ADDR,
|
|
ext_page_reg | BMI160_CMD_PAGING_EN);
|
|
raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_COM_C_TRIM_ADDR, &pullup_reg);
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_COM_C_TRIM_ADDR,
|
|
pullup_reg | BMI160_COM_C_TRIM);
|
|
raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_EXT_MODE_ADDR, &ext_page_reg);
|
|
raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_EXT_MODE_ADDR,
|
|
ext_page_reg & ~BMI160_CMD_TARGET_PAGE);
|
|
raw_read8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_CMD_EXT_MODE_ADDR, &ext_page_reg);
|
|
|
|
/* Set the i2c address of the compass */
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_MAG_IF_0,
|
|
I2C_GET_ADDR(
|
|
CONFIG_ACCELGYRO_SEC_ADDR_FLAGS)
|
|
<< 1);
|
|
|
|
/* Enable the secondary interface as I2C */
|
|
ret = raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_IF_CONF,
|
|
BMI160_IF_MODE_AUTO_I2C <<
|
|
BMI160_IF_MODE_OFF);
|
|
data->flags |= BMI160_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 */
|
|
raw_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, 0);
|
|
}
|
|
#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;
|
|
set_range(s, s->default_range, 0);
|
|
|
|
if (s->type == MOTIONSENSE_TYPE_ACCEL) {
|
|
#ifdef CONFIG_ACCEL_INTERRUPTS
|
|
ret = config_interrupt(s);
|
|
#endif
|
|
}
|
|
|
|
return sensor_init_done(s);
|
|
}
|
|
|
|
const struct accelgyro_drv bmi160_drv = {
|
|
.init = init,
|
|
.read = read,
|
|
.set_range = set_range,
|
|
.get_range = get_range,
|
|
.get_resolution = get_resolution,
|
|
.set_data_rate = set_data_rate,
|
|
.get_data_rate = get_data_rate,
|
|
.set_offset = set_offset,
|
|
.get_scale = get_scale,
|
|
.set_scale = set_scale,
|
|
.get_offset = get_offset,
|
|
.perform_calib = perform_calib,
|
|
#ifdef CONFIG_ACCEL_INTERRUPTS
|
|
.irq_handler = irq_handler,
|
|
#endif
|
|
#ifdef CONFIG_GESTURE_HOST_DETECTION
|
|
.manage_activity = manage_activity,
|
|
.list_activities = list_activities,
|
|
#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 = &raw_read8,
|
|
.i2c_write = &raw_write8,
|
|
};
|
|
#endif /* CONFIG_CMD_I2C_STRESS_TEST_ACCEL */
|
|
|
|
int bmi160_get_sensor_temp(int idx, int *temp_ptr)
|
|
{
|
|
struct motion_sensor_t *s = &motion_sensors[idx];
|
|
int16_t temp;
|
|
int ret;
|
|
|
|
ret = raw_read_n(s->port, s->i2c_spi_addr_flags,
|
|
BMI160_TEMPERATURE_0,
|
|
(uint8_t *)&temp, sizeof(temp));
|
|
|
|
if (ret || temp == BMI160_INVALID_TEMP)
|
|
return EC_ERROR_NOT_POWERED;
|
|
|
|
*temp_ptr = C_TO_K(23 + ((temp + 256) >> 9));
|
|
return 0;
|
|
}
|