coreboot-libre-fam15h-rdimm/3rdparty/chromeec/driver/accelgyro_lsm6dso.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 */
};