blob: 58679dbed96cc5cae3e24f132a7b99a777cb6221 [file] [log] [blame]
/* Copyright 2019 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/**
* LSM6DSO Accel and Gyro module for Chrome EC
* 3D digital accelerometer & 3D digital gyroscope
*
* For any details on driver implementation please
* Refer to AN5192 Application Note on www.st.com
*/
#include "driver/accelgyro_lsm6dso.h"
#include "hooks.h"
#include "hwtimer.h"
#include "math_util.h"
#include "motion_sense_fifo.h"
#include "task.h"
#include "timer.h"
#ifdef CONFIG_ACCEL_LSM6DSO_INT_EVENT
#define ACCEL_LSM6DSO_INT_ENABLE
#endif
#define CPRINTS(format, args...) cprints(CC_ACCEL, format, ##args)
STATIC_IF(ACCEL_LSM6DSO_INT_ENABLE)
volatile uint32_t last_interrupt_timestamp;
/*
* When ODR change, the sensor filters need settling time;
* Add a counter to discard a well known number of data with
* incorrect values.
*/
static uint32_t samples_to_discard[LSM6DSO_FIFO_DEV_NUM];
/**
* @return output data base register for sensor
*/
static inline int get_xyz_reg(enum motionsensor_type type)
{
return LSM6DSO_ACCEL_OUT_X_L_ADDR -
(LSM6DSO_ACCEL_OUT_X_L_ADDR - LSM6DSO_GYRO_OUT_X_L_ADDR) * type;
}
#ifdef ACCEL_LSM6DSO_INT_ENABLE
/**
* Configure interrupt int 1 to fire handler for:
*
* FIFO threshold on watermark (1 sample)
*
* @s: Motion sensor pointer
*/
static int config_interrupt(const struct motion_sensor_t *s)
{
int ret = EC_SUCCESS;
int int1_ctrl_val;
ret = st_raw_read8(s->port, s->i2c_spi_addr_flags, LSM6DSO_INT1_CTRL,
&int1_ctrl_val);
if (ret != EC_SUCCESS)
return ret;
/*
* 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,
LSM6DSO_FIFO_CTRL1_ADDR, 1);
if (ret != EC_SUCCESS)
return ret;
int1_ctrl_val |= LSM6DSO_INT_FIFO_TH | LSM6DSO_INT_FIFO_OVR |
LSM6DSO_INT_FIFO_FULL;
ret = st_raw_write8(s->port, s->i2c_spi_addr_flags, LSM6DSO_INT1_CTRL,
int1_ctrl_val);
return ret;
}
/**
* fifo_disable - set fifo mode to LSM6DSO_FIFO_MODE_BYPASS_VAL
* @s: Motion sensor pointer: must be MOTIONSENSE_TYPE_ACCEL.
*/
static int fifo_disable(const struct motion_sensor_t *s)
{
return st_raw_write8(s->port, s->i2c_spi_addr_flags,
LSM6DSO_FIFO_CTRL4_ADDR,
LSM6DSO_FIFO_MODE_BYPASS_VAL);
}
/**
* set_fifo_params - Configure internal FIFO parameters
*
* Configure FIFO decimator to have every time the right pattern
* with acc/gyro
*/
static int fifo_enable(const struct motion_sensor_t *s)
{
return st_raw_write8(s->port, s->i2c_spi_addr_flags,
LSM6DSO_FIFO_CTRL4_ADDR,
LSM6DSO_FIFO_MODE_CONTINUOUS_VAL);
}
/**
* push_fifo_data - Scan data pattern and push upside
*/
static void push_fifo_data(struct motion_sensor_t *main_s, uint8_t *fifo,
uint32_t saved_ts)
{
struct motion_sensor_t *sensor;
uint8_t tag;
int id;
int *axis;
uint8_t *ptr;
uint8_t ag_maps[] = {
MOTIONSENSE_TYPE_GYRO,
MOTIONSENSE_TYPE_ACCEL,
};
/*
* FIFO pattern is as follow (i.e. Acc/Gyro @ same ODR)
* ________ ____________ _______ ____________
* | TAG_XL | Acc[x,y,z] | TAG_G | Gyr[x,y,z] |
* |________|____________|_______|____________|
* |<-------- 1 -------->|<-------- 2 ------->| (FIFO Threshold)
*
* First byte is tag, next data.
* Data pattern len is fixed for each sample.
* FIFO threshold is related to sample data (7 byte).
*/
ptr = fifo + LSM6DSO_TAG_SIZE;
tag = (*fifo >> 3) - LSM6DSO_GYRO_TAG;
id = ag_maps[tag];
/* Discard samples every ODR changes. */
if (samples_to_discard[id] > 0) {
samples_to_discard[id]--;
return;
}
sensor = main_s + id;
axis = sensor->raw_xyz;
/* Apply precision, sensitivity and rotation. */
st_normalize(sensor, axis, ptr);
if (IS_ENABLED(CONFIG_ACCEL_SPOOF_MODE) &&
sensor->flags & MOTIONSENSE_FLAG_IN_SPOOF_MODE)
axis = sensor->spoof_xyz;
if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
struct ec_response_motion_sensor_data vect;
vect.data[X] = axis[X];
vect.data[Y] = axis[Y];
vect.data[Z] = axis[Z];
vect.flags = 0;
vect.sensor_num = sensor - motion_sensors;
motion_sense_fifo_stage_data(&vect, sensor, 3, saved_ts);
} else {
motion_sense_push_raw_xyz(sensor);
}
}
static inline int load_fifo(struct motion_sensor_t *main_s,
const uint16_t fifo_len)
{
uint8_t fifo[LSM6DSO_FIFO_SAMPLE_SIZE];
int i;
for (i = 0; i < fifo_len; i++) {
RETURN_ERROR(st_raw_read_n_noinc(
main_s->port, main_s->i2c_spi_addr_flags,
LSM6DSO_FIFO_DATA_ADDR_TAG, fifo, sizeof(fifo)));
push_fifo_data(main_s, fifo, last_interrupt_timestamp);
}
return EC_SUCCESS;
}
/**
* accelgyro_config_fifo - update mode and ODR for FIFO decimator
*/
static int accelgyro_config_fifo(const struct motion_sensor_t *s)
{
int err;
struct stprivate_data *data = s->drv_data;
uint8_t reg_val;
uint8_t fifo_odr_mask;
/* Changing in ODR must stop FIFO. */
err = fifo_disable(s);
if (err != EC_SUCCESS)
return err;
/*
* If ODR changes restore to default discard samples number
* the counter related to this sensor.
*/
samples_to_discard[s->type] = LSM6DSO_DISCARD_SAMPLES;
fifo_odr_mask = LSM6DSO_FIFO_ODR_MASK(s);
reg_val = LSM6DSO_ODR_TO_REG(data->base.odr);
err = st_write_data_with_mask(s, LSM6DSO_FIFO_CTRL3_ADDR, fifo_odr_mask,
reg_val);
if (err != EC_SUCCESS)
return err;
return fifo_enable(s);
}
/**
* lsm6dso_interrupt - interrupt from int1 pin of sensor
*/
void lsm6dso_interrupt(enum gpio_signal signal)
{
last_interrupt_timestamp = __hw_clock_source_read();
task_set_event(TASK_ID_MOTIONSENSE, CONFIG_ACCEL_LSM6DSO_INT_EVENT);
}
/**
* irq_handler - bottom half of the interrupt task sheduled by consumer
*/
static int irq_handler(struct motion_sensor_t *s, uint32_t *event)
{
int fifo_len = 0;
struct lsm6dso_fstatus fsts;
bool has_read_fifo = false;
if ((s->type != MOTIONSENSE_TYPE_ACCEL) ||
(!(*event & CONFIG_ACCEL_LSM6DSO_INT_EVENT)))
return EC_ERROR_NOT_HANDLED;
do {
/* Read how many data patterns on FIFO to read. */
RETURN_ERROR(st_raw_read_n_noinc(
s->port, s->i2c_spi_addr_flags, LSM6DSO_FIFO_STS1_ADDR,
(uint8_t *)&fsts, sizeof(fsts)));
if (fsts.len & (LSM6DSO_FIFO_DATA_OVR | LSM6DSO_FIFO_FULL))
CPRINTS("%s FIFO Overrun: %04x", s->name, fsts.len);
fifo_len = fsts.len & LSM6DSO_FIFO_DIFF_MASK;
if (fifo_len) {
RETURN_ERROR(load_fifo(s, fifo_len));
has_read_fifo = true;
}
} while (fifo_len != 0);
if (IS_ENABLED(CONFIG_ACCEL_FIFO) && has_read_fifo)
motion_sense_fifo_commit_data();
return EC_SUCCESS;
}
#endif /* ACCEL_LSM6DSO_INT_ENABLE */
/**
* 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(struct motion_sensor_t *s, int range, int rnd)
{
int err;
uint8_t ctrl_reg, reg_val;
int newrange = range;
ctrl_reg = LSM6DSO_RANGE_REG(s->type);
if (s->type == MOTIONSENSE_TYPE_ACCEL) {
/* Adjust and check rounded value for Acc. */
if (rnd && (newrange < LSM6DSO_ACCEL_NORMALIZE_FS(newrange)))
newrange *= 2;
if (newrange > LSM6DSO_ACCEL_FS_MAX_VAL)
newrange = LSM6DSO_ACCEL_FS_MAX_VAL;
reg_val = lsm6dso_accel_fs_reg(newrange);
} else {
/* Adjust and check rounded value for Gyro. */
reg_val = LSM6DSO_GYRO_FS_REG(range);
if (rnd && (range > LSM6DSO_GYRO_NORMALIZE_FS(reg_val)))
reg_val++;
if (reg_val > LSM6DSO_GYRO_FS_MAX_REG_VAL)
reg_val = LSM6DSO_GYRO_FS_MAX_REG_VAL;
newrange = LSM6DSO_GYRO_NORMALIZE_FS(reg_val);
}
mutex_lock(s->mutex);
err = st_write_data_with_mask(s, ctrl_reg, LSM6DSO_RANGE_MASK, reg_val);
if (err == EC_SUCCESS)
s->current_range = newrange;
mutex_unlock(s->mutex);
return EC_SUCCESS;
}
/**
* set_data_rate set sensor data rate
* @s: Motion sensor pointer
* @range: Rate (mHz)
* @rnd: Round up/down flag
*/
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 ctrl_reg, reg_val = 0;
ctrl_reg = LSM6DSO_ODR_REG(s->type);
if (rate > 0) {
reg_val = LSM6DSO_ODR_TO_REG(rate);
normalized_rate = LSM6DSO_REG_TO_ODR(reg_val);
if (rnd && (normalized_rate < rate)) {
reg_val++;
normalized_rate = LSM6DSO_REG_TO_ODR(reg_val);
}
if (normalized_rate < LSM6DSO_ODR_MIN_VAL ||
normalized_rate > LSM6DSO_ODR_MAX_VAL)
return EC_RES_INVALID_PARAM;
}
mutex_lock(s->mutex);
ret = st_write_data_with_mask(s, ctrl_reg, LSM6DSO_ODR_MASK, reg_val);
if (ret == EC_SUCCESS) {
data->base.odr = normalized_rate;
if (IS_ENABLED(ACCEL_LSM6DSO_INT_ENABLE))
accelgyro_config_fifo(s);
}
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, LSM6DSO_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 = (LSM6DSO_STS_XLDA_UP == (tmp & LSM6DSO_STS_XLDA_MASK));
else
*ready = (LSM6DSO_STS_GDA_UP == (tmp & LSM6DSO_STS_GDA_MASK));
return EC_SUCCESS;
}
/*
* Is not very efficient to collect the data in read: better have an interrupt
* and collect in 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(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, LSM6DSO_WHO_AM_I_REG,
&tmp);
if (ret != EC_SUCCESS)
return EC_ERROR_UNKNOWN;
if (tmp != LSM6DSO_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 so reset it
* LSM6DSO supports both Acc & Gyro features
* Board will see two virtual sensor devices: Acc & Gyro
* Requirement: Acc need be init before Gyro
*/
if (s->type == MOTIONSENSE_TYPE_ACCEL) {
mutex_lock(s->mutex);
/* Software reset. */
ret = st_raw_write8(s->port, s->i2c_spi_addr_flags,
LSM6DSO_CTRL3_ADDR, LSM6DSO_SW_RESET);
if (ret != EC_SUCCESS)
goto err_unlock;
/*
* Output data not updated until have been read.
* Require interrupt to be active low.
*/
ret = st_raw_write8(
s->port, s->i2c_spi_addr_flags, LSM6DSO_CTRL3_ADDR,
LSM6DSO_BDU | LSM6DSO_IF_INC | LSM6DSO_H_L_ACTIVE);
if (ret != EC_SUCCESS)
goto err_unlock;
if (IS_ENABLED(ACCEL_LSM6DSO_INT_ENABLE)) {
ret = fifo_disable(s);
if (ret != EC_SUCCESS)
goto err_unlock;
}
if (IS_ENABLED(ACCEL_LSM6DSO_INT_ENABLE))
ret = config_interrupt(s);
if (ret != EC_SUCCESS)
goto err_unlock;
mutex_unlock(s->mutex);
}
/* Set default resolution common to Acc and Gyro. */
data->resol = LSM6DSO_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;
}
#ifdef CONFIG_BODY_DETECTION
static int get_rms_noise(const struct motion_sensor_t *s)
{
/*
* RMS | Acceleration RMS noise in normal/low-power mode
* FS = ±4 g | 2.0 mg(RMS)
*/
return 2000;
}
#endif
const struct accelgyro_drv lsm6dso_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 ACCEL_LSM6DSO_INT_ENABLE
.irq_handler = irq_handler,
#ifdef CONFIG_BODY_DETECTION
.get_rms_noise = get_rms_noise,
#endif
#ifdef CONFIG_GESTURE_HOST_DETECTION
.list_activities = st_list_activities,
#endif
#endif /* ACCEL_LSM6DSO_INT_ENABLE */
};