501 lines
13 KiB
C
501 lines
13 KiB
C
/* Copyright 2019 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
/**
|
|
* 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 "task.h"
|
|
#include "timer.h"
|
|
|
|
#define CPRINTS(format, args...) cprints(CC_ACCEL, format, ## args)
|
|
|
|
STATIC_IF(CONFIG_ACCEL_FIFO) 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 CONFIG_ACCEL_INTERRUPTS
|
|
/**
|
|
* 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;
|
|
|
|
if (!IS_ENABLED(CONFIG_ACCEL_FIFO))
|
|
return ret;
|
|
|
|
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 ec_response_motion_sensor_data vect;
|
|
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);
|
|
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_add_data(&vect, sensor, 3, saved_ts);
|
|
}
|
|
|
|
static inline int load_fifo(struct motion_sensor_t *s,
|
|
const struct lsm6dso_fstatus *fsts,
|
|
uint32_t saved_ts)
|
|
{
|
|
uint8_t fifo[FIFO_READ_LEN], *ptr;
|
|
int i, err, read_len = 0, word_len, fifo_len;
|
|
uint16_t fifo_depth;
|
|
|
|
fifo_depth = fsts->len & LSM6DSO_FIFO_DIFF_MASK;
|
|
fifo_len = fifo_depth * LSM6DSO_FIFO_SAMPLE_SIZE;
|
|
while (read_len < fifo_len) {
|
|
word_len = GENERIC_MIN(fifo_len - read_len, sizeof(fifo));
|
|
err = st_raw_read_n_noinc(s->port, s->i2c_spi_addr_flags,
|
|
LSM6DSO_FIFO_DATA_ADDR_TAG,
|
|
fifo, word_len);
|
|
if (err != EC_SUCCESS)
|
|
return err;
|
|
|
|
for (i = 0; i < word_len; i += LSM6DSO_FIFO_SAMPLE_SIZE) {
|
|
ptr = &fifo[i];
|
|
push_fifo_data(LSM6DSO_MAIN_SENSOR(s), ptr, saved_ts);
|
|
}
|
|
read_len += word_len;
|
|
}
|
|
|
|
return read_len;
|
|
}
|
|
|
|
/**
|
|
* 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 = LSM6DSO_GET_DATA(s);
|
|
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_TO_REG(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)
|
|
{
|
|
if (IS_ENABLED(CONFIG_ACCEL_FIFO))
|
|
last_interrupt_timestamp = __hw_clock_source_read();
|
|
|
|
task_set_event(TASK_ID_MOTIONSENSE,
|
|
CONFIG_ACCEL_LSM6DSO_INT_EVENT, 0);
|
|
}
|
|
|
|
/**
|
|
* irq_handler - bottom half of the interrupt task sheduled by consumer
|
|
*/
|
|
static int irq_handler(struct motion_sensor_t *s, uint32_t *event)
|
|
{
|
|
int ret = EC_SUCCESS;
|
|
struct lsm6dso_fstatus fsts;
|
|
|
|
if (((s->type != MOTIONSENSE_TYPE_ACCEL) &&
|
|
(s->type != MOTIONSENSE_TYPE_GYRO)) ||
|
|
(!(*event & CONFIG_ACCEL_LSM6DSO_INT_EVENT)))
|
|
return EC_ERROR_NOT_HANDLED;
|
|
|
|
if (IS_ENABLED(CONFIG_ACCEL_FIFO)) {
|
|
/* Read how many data patterns on FIFO to read. */
|
|
ret = st_raw_read_n_noinc(s->port, s->i2c_spi_addr_flags,
|
|
LSM6DSO_FIFO_STS1_ADDR,
|
|
(uint8_t *)&fsts, sizeof(fsts));
|
|
if (ret != EC_SUCCESS)
|
|
return ret;
|
|
|
|
if (fsts.len & (LSM6DSO_FIFO_DATA_OVR | LSM6DSO_FIFO_FULL))
|
|
CPRINTS("%s FIFO Overrun: %04x", s->name, fsts.len);
|
|
|
|
if (fsts.len & LSM6DSO_FIFO_DIFF_MASK)
|
|
ret = load_fifo(s, &fsts, last_interrupt_timestamp);
|
|
}
|
|
|
|
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 = LSM6DSO_GET_DATA(s);
|
|
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)
|
|
data->base.range = newrange;
|
|
|
|
mutex_unlock(s->mutex);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* get_range - get full scale range
|
|
* @s: Motion sensor pointer
|
|
*/
|
|
static int get_range(const struct motion_sensor_t *s)
|
|
{
|
|
struct stprivate_data *data = LSM6DSO_GET_DATA(s);
|
|
|
|
return data->base.range;
|
|
}
|
|
|
|
/**
|
|
* 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 = LSM6DSO_GET_DATA(s);
|
|
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 > MIN(LSM6DSO_ODR_MAX_VAL,
|
|
CONFIG_EC_MAX_SENSOR_FREQ_MILLIHZ))
|
|
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(CONFIG_ACCEL_FIFO))
|
|
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(const struct motion_sensor_t *s)
|
|
{
|
|
int ret = 0, tmp;
|
|
struct stprivate_data *data = LSM6DSO_GET_DATA(s);
|
|
|
|
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.
|
|
* Prefer interrupt to be active low.
|
|
*/
|
|
ret = st_raw_write8(s->port, s->i2c_spi_addr_flags,
|
|
LSM6DSO_CTRL3_ADDR,
|
|
LSM6DSO_BDU | LSM6DSO_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 = 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;
|
|
}
|
|
|
|
const struct accelgyro_drv lsm6dso_drv = {
|
|
.init = init,
|
|
.read = read,
|
|
.set_range = set_range,
|
|
.get_range = get_range,
|
|
.get_resolution = st_get_resolution,
|
|
.set_data_rate = set_data_rate,
|
|
.get_data_rate = st_get_data_rate,
|
|
.set_offset = st_set_offset,
|
|
.get_offset = st_get_offset,
|
|
#ifdef CONFIG_ACCEL_INTERRUPTS
|
|
.irq_handler = irq_handler,
|
|
#endif /* CONFIG_ACCEL_INTERRUPTS */
|
|
};
|